Skip to content

Latest commit

 

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

 ██████╗  █████╗  ██████╗ ██████╗  █████╗  ██████╗ ██╗  ██╗
 ██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██╔╝
 ██████╔╝███████║██║     ██████╔╝███████║██║     █████╔╝
 ██╔══██╗██╔══██║██║     ██╔═══╝ ██╔══██║██║     ██╔═██╗
 ██████╔╝██║  ██║╚██████╗██║     ██║  ██║╚██████╗██║  ██╗
 ╚═════╝ ╚═╝  ╚═╝ ╚═════╝╚═╝     ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝

MANAGEBAC // FROM THE TERMINAL

your deadlines, your CAS hours and your learner portfolio, without the four clicks

runtime deps writes api license


📚 What is this

ManageBac has a public API. It is administrator-only, and if you are a student you are not getting a key. bacpack takes the other route: it logs in as you and reads the pages you can already see.

It does three things. It tells you what is due, it logs CAS experiences, and it adds entries to a Learner Portfolio. Reads are live, so there is no local copy to go stale. Writes print what they are about to send and then stop, because a school record is not a place to find out your flags were wrong.

The interesting parts are not the HTTP. ManageBac answers 422 to about half of all requests unless you send Accept: text/html, which reads like rate limiting and is not. It prints dates without a year. Teachers post the same deadline twice. A dead session arrives as a redirect with an empty body, which is indistinguishable from a page whose markup moved unless you look at where it points. bacpack is mostly the handling of those four facts.

nick@bacpack:~$ bacpack due --days 14
[✓] 2 deadlines found. both are the same IA, posted twice by the same teacher.
[i] dedupe matches exact title and time only. anything fuzzier hides real work.

🎒 The commands

command what it actually does
01 due upcoming deadlines, --days N to cut it short. infers the missing year, keeps genuine duplicates
02 classes your classes and their ids, mostly so you can see what --class will match
03 cas list every CAS experience with hours, strands and reflection count
04 cas outcomes the seven IB learning outcomes and their ids — per school, so read, never hardcoded
05 cas groups school groups you can attach an experience to
06 cas add creates an experience: strands, service type, approaches, supervisor, group, outcomes
07 cas edit changes one. anything you don't pass keeps its current value
08 cas delete removes one, after showing you its hours and reflection count
09 cas reflect adds a reflection to an experience, found by name. body only — see below
10 class units unit plans with their status and HL/SL badges
11 class files every file in the class, folder by folder. --match narrows, --download DIR saves
12 class discussions discussion posts in full: date, category, author and the body teachers put the homework in
13 portfolio list portfolio entries with their tags and the first lines of each body
14 portfolio tags the works and the whole IB tag taxonomy, grouped. works change every September
15 portfolio add adds an entry. --tags concepts/culture by name, no id lookup
16 portfolio edit changes an entry. anything you don't pass keeps its current value
17 portfolio star toggles the star
18 portfolio delete removes an entry, after showing you which one

Every read takes --json. Every write previews and exits; --confirm is what actually posts.

cas edit and portfolio edit read the record first and reuse whatever you leave out. Both edit forms overwrite every field they post, so --service 9 on its own would otherwise wipe the approaches, supervisor and outcomes without mentioning it.

Two ManageBac quirks worth knowing. A CAS group can only be set when the experience is created: the edit form has no group field, so cas edit omits it rather than posting a blank that would unlink it. And class_stream has no command, because it renders no items in the page source for any class tested, so it is either drawn by JavaScript or unused.

cas reflect takes a body and nothing else. The create form carries no learning-outcome checkboxes, and an evidence[url] value posts cleanly and then appears nowhere. Both were tested live and silently dropped, so there are no flags for them — a flag that previews confidently and changes nothing is worse than a missing feature.

class files --download DIR is the one write that needs no --confirm, because it writes to your disk and not to ManageBac. It rebuilds the class folder layout under DIR/<class name>/, skips anything already on disk so an interrupted run resumes by running it again, and numbers collisions rather than overwriting: a folder really can hold two different files under one name. Modern Greek A comes to 67 files and 235 MB, so it is not a small command: --match "paper 2" narrows it to one folder, --match annotated to one kind of file. The needle is tested against folder/name, so a single flag does both, and a needle that matches nothing is an error rather than a quiet empty download.

class discussions reads the first page only, which is five posts. ManageBac hides the rest behind a "Show More" button whose route is not mapped, and at roughly a post a week that is weeks of headroom. A class that goes quiet for a term and then posts six times in one day loses the oldest of them. The body keeps its line breaks, because that is where the homework lives: teachers list the exercises one to a line.

portfolio list walks every page. The index holds ten entries a page, and reading only the first one returned the newest ten with exit code 0, which is indistinguishable from a class that has ten. A Greek class with sixteen entries reported ten, and the six it dropped were the oldest. The loop stops on the first page that adds nothing new, so a short portfolio still costs one request and a long one costs a request per ten entries.

--class takes part of a class name, not an id: --class greek. Ids change each school year, and every class exposes the portfolio route, so a name is both safer and shorter than the number.

🚀 Run it

Needs Bun. You supply two things: your school's subdomain, and your ManageBac login.

git clone https://github.com/nitrimandylis/bacpack.git
cd bacpack
bun install
bun run compile   # → ~/.bun/bin/bacpack, plus man bacpack

Then, once:

export MANAGEBAC_SCHOOL=yourschool          # yourschool.managebac.com
mkdir -p ~/.config/managebac
# your ManageBac email on line 1, your password on line 2:
$EDITOR ~/.config/managebac/credentials
chmod 600 ~/.config/managebac/credentials

MANAGEBAC_EMAIL and MANAGEBAC_PASSWORD work instead of the file, which is what CI should use.

bacpack logs itself in and caches the session in ~/.config/managebac/cookie. Sessions last about a fortnight rather than the year the cookie's expires attribute advertises, so when one dies the next command logs in again and carries on. You never refresh it by hand. If you would rather not store a password, paste a _managebac_session value into that cookie file yourself and bacpack will use it until it expires.

bacpack due
bacpack class files --class greek --match "paper 2" --download ~/Downloads
bacpack portfolio add --class greek --body-file entry.html --tags culture
man bacpack        # full reference, offline

The last write prints what it would send and stops. Add --confirm when you mean it.

🤖 Driving it with an agent

bacpack-cli/SKILL.md is the operating manual for coding agents. It covers the flags, but the useful half is what --help has no room for: which flag emails a real teacher, which field ManageBac accepts and then silently discards, which ids expire every September, and why a page that parses to nothing is breakage rather than a quiet week.

It's a plain directory, not a vendor one, so drop it wherever your agent keeps skills:

cp -R bacpack-cli ~/.claude/skills/     # or wherever yours looks

bun run compile runs that line for you if ~/.claude/skills already exists, and skips it if it doesn't — it won't create an agent's directory on a machine that never asked for one. Copy it wherever yours looks instead.

🔩 Under the hood

flowchart LR
    A[credentials] --> B[client.ts]
    B -->|Accept: text/html, retry, re-login| B
    B --> C[due.ts]
    B --> D[cas.ts]
    B --> E[portfolio.ts]
    B --> I[classes.ts]
    C --> F[index.ts]
    D --> F
    E --> F
    I --> F
    I -->|signed CDN links| J[your disk]
    F -->|preview| G[stdout]
    F -->|--confirm| H[ManageBac]
Loading
layer path job
client src/client.ts one session, replayed unchanged. sends Accept: text/html, retries, tells 401 from 422, resolves a class by name
due src/due.ts year inference, dedupe, and the guard that shouts when the selectors rot instead of reporting nothing due
cas src/cas.ts experiences and the create form, including the advisor-email checkbox that ships pre-ticked
portfolio src/portfolio.ts reflections, the tag taxonomy, and the create form that has no title field
classes src/classes.ts units, discussions, and the file tree — including the download, which reads each row's signed CDN link straight out of the markup
cli src/index.ts argument parsing and the preview that stands between you and a permanent record

Four failure modes are handled by name, because all four have happened. A 302 to /login, or a 401, means the session died: bacpack logs in again and replays the request, once. 422 means Rails refused the request format, which the Accept header prevents and the retry loop covers. 200 with nothing parseable means ManageBac changed its markup, so it exits non-zero rather than telling you your week is clear.

Stack: Bun · TypeScript · node:util parseArgs · no runtime dependencies

🙏 Credit

The idea came from rhijjawi/ManageBac-API by Ramzi, which worked out the part that is actually hard to guess: that a plain _managebac_session cookie is enough, and which page to point it at. Everything downstream of that follows.

No code was taken. It last saw a real commit in 2021 and its selectors (upcoming-tasks, task-node, js-presentation, date-badge) match nothing on the 2026 pages, so bacpack reads the markup as it stands today. Knowing the door was unlocked was the contribution, and it saved an afternoon of wondering whether it was.


Nick Trimandylis

READ LIVE. WRITE TWICE, THE SECOND TIME ON PURPOSE

MIT licensed.

About

ManageBac from the terminal: what's due, CAS experiences and reflections, and Learner Portfolio entries

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages