{"schema":"plm.commands/v1","cli_version":"0.3.0","groups":29,"commands":200,"catalog":[{"id":"getting-started","status":"live","title":"Getting started","icon":"rocket","tagline":"Sign in once, bind a repo when a team needs you, keep plm itself fresh.","intro":"plm needs exactly one thing before it is useful: a signed-in you. Login runs through elvix in the browser (a device flow, no token to paste), and from that moment your personal tools work from any directory: notes, my files, doodles, HTML and markdown files are all keyed to your account, not to a repo. Linking comes second, and only where a team lives: run plm link inside a repo to bind it to its hub project, and the team verbs (commit, push, problems, storage, tasks) know where to report.","verbs":[{"usage":"plm login","desc":"Sign in via elvix in a browser (device flow).","output":"  Sign in to PLMHub:\n\n    https://plmhub.eu/device?code=G9N2-4NW4\n\n  Confirm this code:  G9N2-4NW4\n\n  Waiting for approval...\n✓ logged in as edvard@edvone.dev · https://api.plmhub.eu","story":{"title":"One door for everything","body":"login opens your browser on the hub's device page; you confirm a short code and plm receives its key. Nothing is pasted, nothing is typed twice. Once you are in, every personal verb in this handbook works from wherever your terminal happens to be.","example":"plm login"}},{"usage":"plm whoami · plm status","desc":"Am I logged in? Exits 1 when not.","output":"✓ logged in as edvard@edvone.dev · SUPER_ADMIN · https://api.plmhub.eu","story":{"title":"Check the handshake","body":"whoami answers with who you are and which hub you are talking to, and exits 1 when you are not signed in, so scripts and agents can gate on it before doing anything else.","example":"plm whoami"}},{"usage":"plm link <project-slug> [--app <name>] [--db <id>]","desc":"Link this repo to a hub project (.plmhub/config.json, committed).","output":"✓ linked this repo to PLMHub (.plmhub/config.json)","story":{"title":"Bind a repo to its project","body":"link writes .plmhub/config.json, a committed file, so the whole team shares the binding: from here on, commit, push, problems, storage and tasks all know which project this repo belongs to. Your personal tools never need it.","example":"plm link plmhub"}},{"usage":"plm commands [group]","desc":"Index of every command group and its verbs, right in the terminal.","output":"plm — command groups\n\ndoodle  (alias: ddl)\n  Fabric scene editor — full toolbar parity  ·  plm doodle help\n    new · use · rename · ls · show · pull · push · add · text · draw · comment\n    svg · image · frame · layout · size · nest · wrap · unwrap · move · copy\n    set · name · lock · hide · rm · layer · group · ungroup · present · bg\n    board · clear · undo · redo · watch","story":{"title":"The map, in your terminal","body":"commands prints the same catalog this handbook is generated from: every group, every verb, and the help entry that goes deeper. Name a group to see just that chapter.","example":"plm commands doodle"}},{"usage":"plm update · plm upgrade","desc":"Update plm itself. Source checkouts pull, npm installs reinstall @latest, binaries point at the download.","output":"plm 0.3.0 — source checkout at /home/edvard/projects/plm/\nAlready up to date.\n✓ plm 0.3.0","story":{"title":"Keep plm fresh","body":"update knows how it was installed and does the right thing: a source checkout pulls, an npm install reinstalls @latest, a standalone binary points you at the download. One verb, no guessing.","example":"plm update"}},{"usage":"plm login --token <eak_…> [--api <url>]","chip":"plm login · --token","desc":"CI and scripts: store an elvix API key (written 0600).","output":"✓ logged in as edvard@edvone.dev · https://api.plmhub.eu"},{"usage":"plm logout","desc":"Drop the stored credentials.","output":"✓ logged out — token removed"},{"usage":"plm -v · --version · version","desc":"Show the plm version.","output":"plm 0.3.0"}],"notes":["Credentials live in ~/.plmhub/config.json (0600). PLMHUB_TOKEN and PLMHUB_API environment variables override them for CI.","Signed in, the personal tools (Notes, My files, Doodles, HTMLs, Markdowns) work from any directory; only the team verbs need plm link."]},{"id":"git","status":"live","title":"Git","icon":"git-branch","tagline":"plm IS git: the same verbs you already type, plus a hub report when you push.","intro":"plm is git with a reporter riding along. Every git verb you already know keeps working, byte for byte, because anything plm does not recognise passes straight through to git. What plm adds is the other direction: when you push, plm tells PLMHub which commits you just shared and who made them, so the hub renders a live map of the repo without ever touching your code. Nothing leaves your machine until you push.","notes":["Offline-first: .plmhub/ is a directory (like .git). Hub writes that cannot be delivered land in .plmhub/queue/ and flush on the next online command.","Any git command plm does not know passes straight through to git, so plm can be your only git remote-facing tool.","Starting and closing problems (plm work, plm done) lives in the Goals & Problems chapter; this one is the git wrapper itself."],"verbs":[{"usage":"plm <any git command>","desc":"Passes straight through to git: status, diff, rebase, stash, anything.","output":"On branch prob/84acc91b","story":{"title":"Keep typing git. plm answers.","body":"plm is a superset of git. status, diff, rebase, stash, cherry-pick: unknown verbs pass through untouched, so you can alias git to plm and lose nothing. Git always works, even fully offline.","example":"plm status","output":"On branch prob/84acc91b\nnothing to commit, working tree clean"}},{"usage":"plm commit -m \"fix the reconnect loop\" [--for <problem-id>]","desc":"git commit, tagged to a problem. Stays local until you push.","output":"✓ committed","story":{"title":"Commit like always, tag the problem.","body":"plm commit is git commit that also stamps the commit with the problem it belongs to (a PLM trailer). Nothing is sent to the hub yet: a commit is private, and its diff link cannot resolve, until it is pushed. The --for flag ties the commit to a problem explicitly; starting the problem with plm work does it automatically.","example":"plm commit -m \"fix the reconnect loop\" --for prob_84acc91b"}},{"usage":"plm push [<git args>]","desc":"git push, then report the commits you just shared to the hub.","output":"✓ pushed · reported 3 commits to PLMHub","story":{"title":"Push is where the hub learns.","body":"A commit is yours alone until you push it. On push, plm reports exactly the commits that landed on the remote, each with its author, branch, and problem, so the hub shows only shared history and every diff link resolves on your own host. plm map pulls the whole project picture back into your terminal when you want it.","example":"plm push"}},{"usage":"plm queue [--flush]","desc":"Show or deliver the offline outbox.","output":"✓ delivered 2, 0 still queued","story":{"title":"Offline is not an error","body":"Hub reports that cannot be delivered land in .plmhub/queue/ (a directory next to .git) and flush on your next online command. Nothing blocks, nothing is lost, git never waits for a network.","example":"plm queue --flush"}},{"usage":"plm map","desc":"Print the project map. ETag-cached, works offline."}]},{"id":"decisions","status":"live","title":"Decisions","icon":"target","tagline":"Why things happened: log it when you decide, supersede it when you change course.","intro":"Code shows WHAT was built and git shows WHO and WHEN. Decisions record the missing piece: WHY. A decision is one line plus its reason, logged the moment you make the call, from the same terminal you made it in. Six months later, when someone (or some agent) asks why the app works this way, the answer is one command away instead of buried in a chat thread. And when you change course, you never erase history: the old decision stays, pointing at the one that replaced it.","notes":["Decisions are project-scoped: run inside a linked repo (plm link <project-slug>).","Statuses: active or superseded. A superseded decision keeps its full text and points at its successor via superseded_by.","Updates are author-or-admin only; hard delete is admin only and destructive. Supersede instead."],"verbs":[{"usage":"plm decide \"<title>\" [--why \"…\"]","desc":"Log a decision. The reason matters.","output":"✓ decision dec_1f04b2c8 — ship chapters one at a time","story":{"title":"Log it the moment you decide","body":"One line, plus the reason. The title says what you chose; --why says what you knew when you chose it. The reason is the valuable half: it is what lets future readers judge whether the decision still applies.","example":"plm decide \"ship chapters one at a time\" --why \"a 156-command page overwhelmed readers\""}},{"usage":"plm decisions [--head N|--tail N|--n N]","desc":"List decisions with reasons and comment counts (default: newest 20). --tail shows the oldest.","output":"12 decisions · showing 20 newest\n\ndec_1f04b2c8  2026-07-12  edvard  — ship chapters one at a time\n    a 156-command page overwhelmed readers","story":{"title":"Read the log before you re-litigate","body":"The list shows every decision with its author, date, and reason. Newest first; --tail flips to the oldest. This is the first thing to read before proposing a change: maybe it was already decided, and maybe the reason no longer holds, which is exactly the conversation to have.","example":"plm decisions --head 2","output":"12 decisions · showing 2 newest\n\ndec_1f04b2c8  2026-07-12  edvard  — ship chapters one at a time\n    a 156-command page overwhelmed readers\n\ndec_9c22e0a4  2026-07-10  edvard  — agents live on the hub, no local copies  [2 comments]\n    two working copies of a living thing always diverge"}},{"usage":"plm decision <dec_…> --status superseded --superseded-by <dec_…>","chip":"plm decision · supersede","desc":"Close a decision by pointing at its successor. History stays readable.","output":"✓ updated dec_1f04b2c8 (status, superseded_by)","story":{"title":"Changed your mind? Supersede, never delete","body":"Log the new decision first, then close the old one by pointing at its successor. The history stays honest: anyone reading the old decision sees it was superseded and by what. Deleting is reserved for admins and mistakes, not for changed minds.","example":"plm decision dec_1f04b2c8 --status superseded --superseded-by dec_4b8d11f0"}},{"usage":"plm decision <dec_…> [--title \"…\"] [--why \"…\"]","chip":"plm decision · update","desc":"Update a decision's title or reason. Author or admin only.","output":"✓ updated dec_4b8d11f0 (body)","story":{"title":"Keep the reason honest","body":"A decision is editable by its author: sharpen the title, expand the why. Updating the reason is not rewriting history, it is documenting it better. The list always shows the current wording.","example":"plm decision dec_4b8d11f0 --why \"chapters release one at a time; the overview is a table of contents\""}},{"usage":"plm comment <dec_…> \"<text>\"","desc":"Discuss a decision on the record, next to the decision itself.","output":"✓ commented on dec_4b8d11f0","story":{"title":"Discuss it where it lives","body":"Comments attach to the decision itself, not to a chat that scrolls away. Disagreement, context, links to evidence: put them on the record, next to the thing they are about.","example":"plm comment dec_4b8d11f0 \"the git chapter landing first proved this: readers finish it\""}},{"usage":"plm decision <dec_…> --delete --yes","chip":"plm decision · delete","desc":"Hard delete. Admin only, requires --yes. Prefer superseding.","output":"✓ deleted dec_1f04b2c8"}]},{"id":"goals","status":"live","title":"Goals & Problems","icon":"milestone","tagline":"Raise the destination, cut problems toward it, solve them: progress derives itself.","intro":"A goal is a destination, not a task: the outcome the project exists to reach, plus what success looks like. A problem is one concrete obstacle on the way there, cut UNDER a goal. The two form a single loop: raise the goal, cut a problem toward it, pick the problem up, solve it, and the goal's progress ticks up on its own. Nobody types a percentage anywhere: progress is derived, solved problems over cut problems, so the goals list is the one report that moves only when work actually lands.","notes":["Everything here is project-scoped: run inside a linked repo (plm link <project-slug>).","Goal statuses: active, achieved, parked. Problem statuses: open, solving, solved.","Progress is derived (solved/cut problems), never hand-edited. plm done is the only thing that moves it.","plm work stores the active problem per checkout (.plmhub/state.json); switching problems warns about the one left open.","Goal delete is project-admin only; its problems survive, unattached."],"verbs":[{"usage":"plm goal \"<title>\" [--why \"…\"]","chip":"plm goal · raise","desc":"Raise a goal. The why is what success looks like: write it as a test.","output":"✓ goal goal_e8956f9961 — Dogfood: PLMHub manages the building of PLMHub itself","story":{"title":"Raise the outcome","body":"One line for the destination, --why for what success looks like. Write the why as the test you will apply later: when this is true, the goal is reached. Vague goals produce vague problems.","example":"plm goal \"Dogfood: PLMHub manages the building of PLMHub itself\" --why \"Edvard plans in the hub, the agent works through the plm CLI end to end\""}},{"usage":"plm problem \"<title>\" --goal <goal-id> [--why \"…\"]","chip":"plm problem · cut","desc":"Cut a problem under a goal. Answers with the plm work command to start it.","output":"✓ problem prob_84acc91b — chapter template renders any group  (plm work prob_84acc91b)","story":{"title":"Cut a problem under it","body":"A problem is the atomic unit of value: one obstacle, one solution. Cutting it under a goal is what makes the goal measurable, and the CLI answers with the exact command to start working on it.","example":"plm problem \"chapter template renders any group\" --goal goal_e8956f9961"}},{"usage":"plm work prob_84acc91b","desc":"Start a problem: creates branch prob/<id>, checks it out, and tracks who is on it.","output":"✓ working prob_84acc91b on branch prob/84acc91b (plm commit will tag it)","story":{"title":"Pick it up","body":"plm work puts you ON the problem: it cuts the prob/<id> branch, checks it out, and tells the hub you are the one working it. From here every plm commit is tagged with the problem automatically, and your teammates see who is where without asking.","example":"plm work prob_84acc91b"}},{"usage":"plm done [prob_…] [--solution \"…\"]","desc":"Mark a problem solved: the one you name, or the active one. The goal's score ticks up.","output":"✓ prob_84acc91b solved. Nice work.","story":{"title":"Solve it, and the goal moves","body":"plm done closes the active problem, optionally recording the solution in one line. This is the only thing that moves a goal's score: the moment it lands, 14/20 becomes 15/20. Solutions accumulate into reusable knowledge, not just closed tickets.","example":"plm done --solution \"keyset pagination on the events table\""}},{"usage":"plm goals [--head N|--tail N|--n N]","desc":"List goals with status, derived progress, and what success looks like.","output":"2 goals · showing 2\n\ngoal_e8956f9961  [active]  Dogfood: PLMHub manages the building of PLMHub itself  (14/20 solved)\n    Edvard plans in the hub, the agent works through the plm CLI end to end.","story":{"title":"Read the honest progress report","body":"Each goal shows its status and the derived score. If a goal sits at 0/1 for weeks, that is a real signal, not a formatting problem. Agents read this list before deciding what to work on next.","example":"plm goals","output":"2 goals · showing 2\n\ngoal_e8956f9961  [active]  Dogfood: PLMHub manages the building of PLMHub itself  (14/20 solved)\n    Edvard plans in the hub, the agent works through the plm CLI end to end.\n\ngoal_b0d57442e8  [active]  Launch PLMHub's public agent platform  (0/1 solved)\n    Author a skill once, install it into any agent."}},{"usage":"plm goal <goal_…> [--title \"…\"] [--why \"…\"] [--status active|achieved|parked]","chip":"plm goal · update","desc":"Update a goal you own: title, success test, or status.","output":"✓ updated goal_e8956f9961 (body, status)","story":{"title":"Steer while you own it","body":"Both records stay yours to shape: retitle, rewrite the why, park a goal or mark it achieved, flip a problem between open and solving, file the solution. Deleting a goal is the one guarded act (project admins only), and even then its problems survive, unattached: destinations are disposable, work is not.","example":"plm goal goal_e8956f9961 --status achieved","output":"✓ updated goal_e8956f9961 (status)"}},{"usage":"plm goal <goal_…> --delete --yes","chip":"plm goal · delete","desc":"Delete a goal. Project admin only; its problems survive, unattached.","output":"✓ deleted goal_e8956f9961 — its problems stay, unattached"},{"usage":"plm problem <prob_…> [--status open|solving|solved] [--title \"…\"] [--why \"…\"] [--solution \"…\"]","chip":"plm problem · update","desc":"Update a problem: status, title, context, or the recorded solution.","output":"✓ updated prob_84acc91b (status, solution)"},{"usage":"plm problems [--status x] [--goal g]","desc":"List problems with status and who is on them, filterable by status or goal.","output":"20 problems · showing 20\n\nprob_84acc91b  [solving]  chapter template renders any group  → edvard"},{"usage":"plm comment <prob_…> \"<text>\"","desc":"Discuss a problem on the record, next to the problem itself.","output":"✓ commented on prob_84acc91b"}]},{"id":"projects","status":"live","title":"Projects","icon":"boxes","tagline":"Create a project, shape its settings, and manage what belongs to it: domains, links, secrets, team.","intro":"A project is the container everything else lives in: goals, problems, decisions, models, files. This chapter is its administration, all from the terminal: create it, keep its settings honest, register the domains and links that belong to it, point at where its secrets live, and manage who is on the team. Team identity is elvix end to end: lookups resolve against PLMHub's elvix app, so anyone with an elvix account is invitable, even before their first PLMHub sign-in.","verbs":[{"usage":"plm project new \"<name>\"","chip":"plm project · new","desc":"Create a project. Prints its slug, ready for plm link.","output":"✓ project \"Zeropost\" — link a repo to it with: plm link zeropost\nzeropost","story":{"title":"Create the container","body":"One name, one slug. The slug is the project's address everywhere: the web URL, the plm link binding, the API. Create it from the terminal and bind your repo to it in the same breath.","example":"plm project new \"Zeropost\""}},{"usage":"plm project set [--name --summary --status --repo --domain --icon]","chip":"plm project · set","desc":"Update the project's General settings from a linked repo.","output":"✓ updated plmhub (summary)","story":{"title":"Keep General honest","body":"The settings page is a PATCH away: rename, rewrite the summary, flip status, point at the primary repo or domain, swap the icon. Same fields the web Settings → General card edits.","example":"plm project set --summary \"Shared dev cockpit across every 021 project.\""}},{"usage":"plm domain <hostname> [--label \"…\"]","chip":"plm domain · add","desc":"Register a domain that belongs to the project.","output":"✓ domain app.zeropost.de","story":{"title":"Claim its addresses","body":"Domains and links are the project's registry: where it lives on the internet, where its code is, what belongs in its orbit. Registering them keeps the map complete, and every agent reading the project sees the same picture.","example":"plm domain app.zeropost.de --label \"Production\""}},{"usage":"plm team add <email|username> [--role viewer|editor|admin] [--notify]","chip":"plm team · add","desc":"Add a teammate. Identity resolves via elvix; --notify sends the invite email.","output":"✓ mira@studio.example added as editor · invite email sent","story":{"title":"Invite through elvix","body":"The team wizard's truth is elvix, not a local cache: lookups search PLMHub's elvix app by email or username, so anyone with an elvix identity is invitable before they ever signed in here. Roles are viewer, editor, admin; the mirror row is minted on the spot.","example":"plm team add mira@studio.example --role editor --notify"}},{"usage":"plm secret <KEY> --at \"<where>\" | --value <v>","chip":"plm secret · set","desc":"Record a secret: a reference to where it lives, or the value itself.","output":"✓ secret RESEND_API_KEY → vault.021.is · Infra/Resend","story":{"title":"Point at the secrets","body":"Two modes, one doctrine: --at records WHERE a value lives (the vault stays the source of truth), --value stores it in PLMHub when that is the right home. Agents stop guessing where credentials are; the project says so.","example":"plm secret RESEND_API_KEY --at \"vault.021.is · Infra/Resend\""}},{"usage":"plm secret-get <KEY>","desc":"Resolve a secret: prints the value (inline) or where plus how (reference, exit 2).","output":"RESEND_API_KEY is a reference — PLMHub does not hold the value.\nWHERE: vault.021.is · Infra/Resend","story":{"title":"Resolve them one-shot","body":"The agent's door: inline secrets print the value and exit 0; references print where and how to fetch and exit 2, so scripts can branch. plm secrets lists everything plus the project's how-to-fetch preamble.","example":"plm secret-get RESEND_API_KEY"}},{"usage":"plm team [ls] · plm team lookup <email|username>","chip":"plm team · ls / lookup","desc":"List the team with roles, or resolve an identifier via elvix before inviting.","output":"cmqj8e813001vdkt7r04gd3u0  [admin]  Edvard Grei  edvard@edvone.dev  @edvard","story":{"title":"Read the team","body":"Members with their role and elvix identity, one line each. lookup answers the wizard's first question (who is this?) without touching anything.","example":"plm team"}},{"usage":"plm team rm <usr_id|email|username>","desc":"Remove a teammate. Identifiers resolve to the member's user id.","output":"✓ removed cmq153nal0002vnt7mvj7pehx"},{"usage":"plm domains","desc":"List the project's domains with labels, IPs, and proxy state.","output":"dom_plmhubeu_prod  plmhub.eu  (Production)  172.67.207.39  [proxied]"},{"usage":"plm domain-rm <id|hostname>","desc":"Remove a domain from the registry.","output":"✓ removed dom_56caafbe1a62"},{"usage":"plm links","desc":"List the project's links with ids.","output":"lnk_04b11b1a8170  Production  https://plmhub.eu"},{"usage":"plm link-add <url> [--title \"…\"]","desc":"Register a link: dashboards, docs, anything in the project's orbit.","output":"✓ link https://grafana.021.is"},{"usage":"plm link-rm <id|url>","desc":"Remove a link.","output":"✓ removed lnk_a03faf7d7a5a"},{"usage":"plm secrets","desc":"List secrets (stored-here or referenced) plus the project's how-to-fetch instructions.","output":"HOW TO FETCH:\nvault CLI on edvone-dev; collections per concern.\n\nRESEND_API_KEY  vault.021.is · Infra/Resend"},{"usage":"plm secret-edit <KEY> [--rename <K>] [--at \"…\"] [--value <v>] [--desc \"…\"] [--unit <u>]","desc":"Update a secret pointer or value.","output":"✓ updated RESEND_API_KEY"},{"usage":"plm secret-rm <KEY>","desc":"Remove a secret pointer.","output":"✓ removed pointer RESEND_API_KEY"},{"usage":"plm secrets-how [--set \"…\" | --stdin]","desc":"Read or write HOW agents fetch real values, shown atop plm secrets."}],"notes":["Everything except project new runs inside a linked repo (plm link <project-slug>).","Team identity is elvix: usernames shown are PLMHub-local; lookups resolve against the elvix app, and adding someone mints their local mirror row on the spot.","Team changes are project-admin only. Roles: viewer ⊂ editor ⊂ admin.","Secrets are inline OR reference: PLMHub holds the value only when you choose --value. secret-get exits 2 on references so scripts can branch."]},{"id":"tasks","status":"live","title":"Tasks","icon":"list-todo","tagline":"Your private todos, per project: quick, disposable, yours only.","intro":"Tasks are your personal scratchpad of TODOs inside a project: nobody else sees them, and they carry none of the ceremony of problems. Cut one when something crosses your mind mid-flow, tick it when done, delete it without guilt. They are project-scoped in the data but personal in nature, on the web they sit behind the checklist icon at the end of the project's bar, apart from the shared record.","verbs":[{"usage":"plm task \"<title>\"","chip":"plm task · add","desc":"Cut a private todo in the linked project.","output":"✓ task ptask_b02d91a4 — rotate the staging DB password","story":{"title":"Catch it before it escapes","body":"Mid-flow thoughts go here, not into your head. One line, no fields, no assignee: a task is yours alone. If it turns out to be real work for the team, promote it to a problem later.","example":"plm task \"rotate the staging DB password\""}},{"usage":"plm tasks","desc":"List your todos in this project, done ones checked.","output":"[ ] ptask_b02d91a4  rotate the staging DB password\n[x] ptask_77e01c2f  read the decisions log","story":{"title":"Read the checklist","body":"A plain checklist, done items checked off. No counts, no burndown: tasks are a memory aid, and the honest measure of one is whether you can trust it to hold what you dropped there.","example":"plm tasks"}},{"usage":"plm task <ptask_…> --done | --undone | --title \"…\"","chip":"plm task · update","desc":"Tick, untick, or retitle a todo.","output":"✓ updated ptask_b02d91a4","story":{"title":"Tick it, or fix it","body":"Done and undone are one flag away, and a mistyped title is not a new task. The id comes straight from the list.","example":"plm task ptask_b02d91a4 --done"}},{"usage":"plm task <ptask_…> --rm","chip":"plm task · rm","desc":"Delete a todo.","output":"✓ deleted ptask_b02d91a4"}],"notes":["Project-scoped: run inside a linked repo (plm link <project-slug>). Personal: only you see your tasks.","For shared, assignable work use problems (the Goals & Problems chapter); tasks are your own margin."]},{"id":"notes","status":"live","title":"Notes","icon":"file-code","tagline":"Your own margins, user-scoped: longer than a task, lighter than a doc.","intro":"Notes are where your working context lives between sessions: findings, half-plans, command snippets, anything too long for a task and too raw for a project's shared record. They are keyed to you, not to a project: one set of notes follows you everywhere, no linked repo needed, and nobody else reads them. The body is plain text in, plain text out, so agents can pipe into and out of them.","verbs":[{"usage":"plm note \"<title>\" [--body \"…\" | --stdin]","chip":"plm note · add","desc":"Write a private note. Body inline or piped via --stdin.","output":"✓ note note_4c118e02 — delvix cutover findings","story":{"title":"Write the margin","body":"Title plus body, and the body can arrive through a pipe: dump a command transcript, a diff summary, a half-decision. Future-you (or your agent, next session) reads it back with one call.","example":"git diff --stat | plm note \"cutover diff summary\" --stdin"}},{"usage":"plm notes","desc":"List your notes with snippets.","output":"note_4c118e02  delvix cutover findings  — pgbackrest restore beats basebackup for reinit…","story":{"title":"Scan the margins","body":"Each note shows its first line as a snippet, enough to know which one you want without opening any.","example":"plm notes"}},{"usage":"plm note <note_…>","chip":"plm note · read","desc":"Print a note's full body to stdout. Pipe it anywhere.","output":"pgbackrest restore beats basebackup for reinit; conf.d symlinks do not survive…","story":{"title":"Read it back, machine-friendly","body":"The bare id prints the raw body and nothing else: pipe it into a diff, an editor, or an agent's context. Notes are storage, not presentation.","example":"plm note note_4c118e02"}},{"usage":"plm note <note_…> [--title \"…\"] [--set \"…\" | --stdin]","chip":"plm note · update","desc":"Retitle or replace the body.","output":"✓ updated note_4c118e02"},{"usage":"plm note <note_…> --rm","chip":"plm note · rm","desc":"Delete a note.","output":"✓ deleted note_4c118e02"}],"notes":["User-scoped: works from any directory, no linked repo. Only you see your notes.","Agent-side margins for a PLMHub agent live on the agent itself (plm agent note); these are YOUR margins in a project."]},{"id":"storage","status":"live","title":"Storage","icon":"database","tagline":"The project's file storage: upload, browse, download, and organize from the terminal.","intro":"Every project has a file storage: build artifacts, exports, assets, anything worth keeping next to the work. The CLI covers the whole loop: upload a file, browse the tree, pull it back down, organize with folders. Files uploaded here are the team's; your personal uploads live in My files and can be promoted here with one verb.","verbs":[{"usage":"plm file up <path> [--folder <id>] [--name <n>]","chip":"plm file · up","desc":"Upload a file to the project storage. Prints its file_… id.","output":"✓ uploaded release-notes.pdf\nfile_c3ba1d24a345","story":{"title":"Put it where the team looks","body":"One verb from disk to the project. The id comes back for scripting; --folder files it directly where it belongs.","example":"plm file up ./release-notes.pdf"}},{"usage":"plm file ls [--folder <id>]","chip":"plm file · ls","desc":"Browse the storage: folders first, then files with sizes.","output":"fld_661f5d1ff557  docs/\nfile_c3ba1d24a345  release-notes.pdf  1 KB","story":{"title":"Browse like a directory","body":"Folders then files, ids first so every row is actionable. Descend with --folder using the id you just read.","example":"plm file ls"}},{"usage":"plm file get <file_id> [--out <path>]","chip":"plm file · get","desc":"Download a file. Names the output from the upload unless --out says otherwise.","output":"✓ saved release-notes.pdf","story":{"title":"Pull it back down","body":"get resolves a short-lived signed URL and saves the bytes: the round trip an agent needs to fetch an artifact, transform it, and upload the result.","example":"plm file get file_c3ba1d24a345"}},{"usage":"plm file mkdir \"<name>\" [--parent <id>]","chip":"plm file · mkdir","desc":"Create a folder. Prints its fld_… id.","output":"✓ folder exports\nfld_9a01b2c3d4e5"},{"usage":"plm file mv <file_id> --folder <folder_id|root>","chip":"plm file · mv","desc":"Move a file into a folder, or back to the root.","output":"✓ moved file_c3ba1d24a345"},{"usage":"plm file rename <file_id> <new name>","chip":"plm file · rename","desc":"Rename a file.","output":"✓ renamed file_c3ba1d24a345 → release-notes-v2.pdf"},{"usage":"plm file url <file_id> [--download]","chip":"plm file · url","desc":"Print a short-lived signed URL; --download makes it save-as."},{"usage":"plm file rm <file_id>","chip":"plm file · rm","desc":"Delete a file.","output":"✓ deleted file_c3ba1d24a345"}],"notes":["Project-scoped: run inside a linked repo (plm link <project-slug>).","Signed URLs are short-lived and meant for terminals and servers; in the browser the web app proxies instead.","Personal uploads live in My files (--mine); plm file share promotes one into this storage."]},{"id":"myfiles","status":"live","title":"My files","icon":"key-round","tagline":"Your own shelf, user-scoped: yours until you share a file into a project.","intro":"My files is your own shelf, keyed to you rather than to any project: drafts, screenshots, working material that is not a team's business yet. It follows you everywhere and needs no linked repo. Same verbs as the project storage with --mine, plus the one that matters: share, which promotes a file from your shelf into a project's storage when it is ready. On the web your shelf lives on the dashboard's Tools tab, next to Notes and the doodle tools.","verbs":[{"usage":"plm file up <path> --mine","chip":"plm file · up --mine","desc":"Upload to your own shelf.","output":"✓ uploaded wireframe-draft.png (my files)\nfile_19ffd302b2d8","story":{"title":"Keep it yours first","body":"Not everything belongs to a team on day one. --mine uploads land on your shelf: visible to you alone, organized with the same folders and verbs, from any directory.","example":"plm file up ./wireframe-draft.png --mine"}},{"usage":"plm file ls --mine [--folder <id>]","chip":"plm file · ls --mine","desc":"Browse your shelf.","output":"file_19ffd302b2d8  wireframe-draft.png  84 KB","story":{"title":"Your shelf, same grammar","body":"Everything the storage chapter taught works here: ls, get, url, mv, rename, rm, mkdir --mine. One grammar, two scopes.","example":"plm file ls --mine"}},{"usage":"plm file share <file_id>","chip":"plm file · share","desc":"Promote a personal file into the linked project's storage. Owner only.","output":"✓ shared file_19ffd302b2d8 — now in the project storage","story":{"title":"Promote it when it is ready","body":"share is the handover: run it inside the destination project's repo and the file leaves your shelf for that project's storage, whole team, history intact. One direction, one owner, one verb.","example":"plm file share file_19ffd302b2d8"}}],"notes":["Personal and user-scoped: only you see your shelf, and it needs no linked repo. Rendered on the dashboard's Tools tab.","All storage verbs accept --mine (get/url/mv/rename/rm/mkdir) and operate on your shelf."]},{"id":"model","status":"soon","title":"Product model","icon":"database","tagline":"Push the ER model and the unit contract. PLMHub never touches your code or database.","verbs":[{"usage":"plm db push --url <DATABASE_URL>","desc":"Introspect a live Postgres and push the ER model."},{"usage":"plm db push --json <file|->","desc":"Push a model an agent or LLM built. No database needed."},{"usage":"plm db schema","desc":"Print the ER-model JSON contract."},{"usage":"plm units push --json <f|-> [--replace]","desc":"Push the unit contract: files, symbols, docs, access, tested."},{"usage":"plm units schema","desc":"Print the unit-contract JSON shape."}]},{"id":"graph","status":"soon","title":"Code Map","icon":"waypoints","tagline":"The LLM is the parser: scaffold, enrich, validate, push the map of the code.","verbs":[{"usage":"plm graph schema","desc":"Print the Code Map JSON contract for an agent to fill."},{"usage":"plm graph scaffold --app <name>","desc":"Deterministic skeleton to .plm/graph.json, then enrich it."},{"usage":"plm graph validate [--json <f>]","desc":"Truth-check the manifest (tested? source?). Non-zero exit on fatal."},{"usage":"plm graph push [--app <n>] [--replace]","desc":"Validate and push .plm/graph.json. Binds to the HEAD commit."},{"usage":"plm graph pull [--depth N] [--expand k]","desc":"Fetch the map: rev, staleness, weld coverage."},{"usage":"plm graph diff","desc":"Local manifest vs the pushed graph: added, removed, changed."},{"usage":"plm graph node|method|endpoint <key>","desc":"One node with its edges and annotations."},{"usage":"plm graph watch [--app <n>]","desc":"Auto-push on .plm/graph.json change. Live follow-along."}]},{"id":"roadmap","status":"live","title":"Roadmaps","icon":"milestone","tagline":"Visual plans toward an outcome: a single road of cliffs, climbed one at a time.","intro":"A roadmap is one road toward one outcome: Launch, first sale, first income. It is a strict sequence of stops (the web draws them as cliffs on a mountain climb), each with a label, a status, a note, and its own comment thread. The whole plan is a plm.roadmap/v1 document, so an agent can build or reshape a plan from the terminal while a human watches it redraw live in the browser, and every mutation lands in an audit trail that outlives even deletion.","verbs":[{"usage":"plm roadmap new <title> [--template launch|first-sale|first-income|fundraise|blank]","desc":"Create a roadmap. Prints its rdmp_… id and becomes active.","output":"✓ created roadmap \"Launch\" from launch · now active\nrdmp_5c1284aa90b1","story":{"title":"Raise the road","body":"One roadmap = one outcome. Start from a template (launch, first-sale, first-income, fundraise) or blank. The new roadmap becomes your active one, so every verb after this can omit the id.","example":"plm roadmap new \"Launch\" --template launch"}},{"usage":"plm roadmap node [<id>] --label \"…\" [--note \"…\" --icon <Lucide> --status todo|doing|done]","desc":"Add a cliff to the road. Prints its n_… id.","output":"✓ added node \"Beta cohort onboarded\"\nn_8f21c4d0","story":{"title":"Add the cliffs","body":"Each stop on the road is a cliff: a label, an optional markdown note, a lucide icon, a status. Keep labels short and outcome-shaped; the note carries the detail. Order is the road itself, and plm roadmap reorder rearranges it later.","example":"plm roadmap node --label \"Beta cohort onboarded\" --icon Users"}},{"usage":"plm roadmap node-set [<id>] --node <n_…> [--label --note --icon --status todo|doing|done]","desc":"Edit a cliff: label, note, icon, or status.","output":"✓ updated n_8f21c4d0\nn_8f21c4d0","story":{"title":"Climb it","body":"Progress on a roadmap is flipping cliffs: todo becomes doing becomes done. Status is deliberately manual here (unlike goals, where progress derives from problems): a roadmap is the outward-facing plan, and marking a cliff done is a statement.","example":"plm roadmap node-set --node n_8f21c4d0 --status done"}},{"usage":"plm roadmap show [<id>]","desc":"The road's cliffs in order plus live progress. The OKR view.","output":"Launch  [active]  rev 12  · owner Edvard\n   1. Domain live  [done]  n_2ba90c11\n   2. Beta cohort onboarded  [doing]  (Users)  n_8f21c4d0\n   3. First paying customer  [todo]  [2 comments]  n_f00d3e42","story":{"title":"Read the climb","body":"The show view is the whole plan in one glance: every cliff in order, its status, its icon, its comment count. This is what an agent reads before proposing the next move, and what you paste into a standup instead of a screenshot.","example":"plm roadmap show"}},{"usage":"plm roadmap comment [<id>] <n_…> --body \"…\" [--parent <c_…>]","desc":"Comment on a cliff. Nested reply via --parent.","output":"✓ commented on n_f00d3e42\nc_77ab120e","story":{"title":"Discuss on the cliff","body":"Every cliff carries its own thread: doubts, context, links to evidence, nested replies. The conversation lives inside the plan document itself, so it travels with the roadmap instead of scrolling away in chat.","example":"plm roadmap comment n_f00d3e42 --body \"pricing page must land before this one\""}},{"usage":"plm roadmap watch [<id>]","desc":"Live rev signals: follow a human editing, or let a human follow you.","output":"watching rdmp_5c1284aa90b1 — Ctrl-C to stop","story":{"title":"Watch it move, live","body":"The scene is the source of truth and every save bumps its rev. watch streams those signals: an agent reshaping the plan makes the browser redraw live, and a human dragging cliffs in the editor is visible to a watching agent. Two hands, one document.","example":"plm roadmap watch"}},{"usage":"plm roadmap templates","desc":"List the starter templates: launch, first-sale, first-income, fundraise, blank."},{"usage":"plm roadmap use <rdmp_…>","desc":"Set the active roadmap; verbs below omit the id.","output":"✓ active roadmap: rdmp_5c1284aa90b1\nrdmp_5c1284aa90b1"},{"usage":"plm roadmap ls","desc":"This project's roadmaps with status and cliff count.","output":"rdmp_5c1284aa90b1  [active]  Launch  (8 cliffs)"},{"usage":"plm roadmap pull [<id>]","desc":"Raw plm.roadmap/v1 content JSON, for editing and set."},{"usage":"plm roadmap set [<id>] --file <path|-> | --stdin | --content \"…\"","desc":"Replace the whole content document. The raw counterpart to node and node-set.","output":"✓ saved rdmp_5c1284aa90b1 · rev 13\nrdmp_5c1284aa90b1"},{"usage":"plm roadmap node-rm [<id>] <n_…>","desc":"Delete a cliff. Order reindexes.","output":"✓ removed n_8f21c4d0"},{"usage":"plm roadmap reorder [<id>] <n_…> <n_…> …","desc":"Set cliff order: listed first, the rest keep their order.","output":"✓ reordered rdmp_5c1284aa90b1"},{"usage":"plm roadmap comment-edit [<id>] <c_…> --body \"…\"","desc":"Edit your own comment. Stamps it as edited.","output":"✓ edited c_77ab120e"},{"usage":"plm roadmap comment-rm [<id>] <c_…>","desc":"Delete your own comment while fresh.","output":"✓ removed c_77ab120e"},{"usage":"plm roadmap rename [<id>] <new title>","desc":"Rename the roadmap.","output":"✓ renamed rdmp_5c1284aa90b1 → \"Launch v2\""},{"usage":"plm roadmap archive | unarchive [<id>]","desc":"Toggle archived status.","output":"✓ rdmp_5c1284aa90b1 → archived"},{"usage":"plm roadmap rm <rdmp_…>","desc":"Delete. Owner-only, soft-delete: content purged, audit kept.","output":"✓ deleted rdmp_5c1284aa90b1 (content purged; audit kept)"},{"usage":"plm roadmap audit [<id>]","desc":"The kept audit trail. Outlives a delete.","output":"2026-07-12 14:02:11  node_set     n_8f21c4d0 → done  · Edvard"},{"usage":"plm roadmap delegate [<id>] --user <usr_…> --right manage|delete","desc":"Owner delegates rights to a teammate.","output":"✓ delegated manage to usr_9a4b2c"},{"usage":"plm roadmap undelegate [<id>] --user <usr_…>","desc":"Owner revokes a delegate.","output":"✓ revoked usr_9a4b2c on rdmp_5c1284aa90b1"},{"usage":"plm roadmap transfer [<id>] --user <usr_…>","desc":"Owner transfers ownership. The new owner must be a member.","output":"✓ transferred rdmp_5c1284aa90b1 → usr_9a4b2c"},{"usage":"plm roadmap overview · overview-set","desc":"Read or save the roadmaps-page diagram: card positions and links between roadmaps.","output":"✓ overview saved"}],"notes":["Add --as <label> to any mutating verb to name the agent in the live-edit bar a watching human sees.","Active roadmap: after new or use, omit the id. Verbs use the active roadmap from .plmhub/state.json; an explicit rdmp_… first argument always overrides it.","A node is called a Cliff in the web editor (the mountain climb); the id and API keep node.","Cliff status is manual (todo, doing, done) by design; goal progress stays derived. A roadmap is the outward plan, goals are the internal engine.","Delete is owner-only and soft: content is purged, the audit trail stays readable forever."]},{"id":"doodle","status":"live","title":"Doodles","icon":"pen-tool","tagline":"Design on the dashboard's Tools tab, driven from the terminal: the scene is the source of truth.","intro":"A doodle is a Fabric canvas on the dashboard's Tools tab, and the editor toolbar is fully mirrored as CLI verbs: an agent can lay out a screen, sketch a flow, or annotate a design without ever opening a browser, while any human with the editor open watches it redraw live. Elements are addressed by id, frames run real auto-layout (Figma-style flex), and every mutation bumps the scene rev. The doctrine: the API is the contract, the scene is the truth, plm is a thin client over it.","verbs":[{"usage":"plm doodle new [--name N --w 1280 --h 720]","desc":"Create an empty doodle. Prints its id and becomes active.","output":"✓ created \"onboarding sketch\" · now active\ndoodle_7c02e1b4a9","story":{"title":"Open a canvas","body":"One verb, one artboard. The new doodle becomes active so every verb after this can omit the id, exactly like roadmaps and the text files.","example":"plm doodle new --name \"onboarding sketch\" --w 1280 --h 720"}},{"usage":"plm doodle add <id> --role <role> [--x --y --w --h] [--fill --stroke --radius …]","chip":"plm doodle · add","desc":"Add an element: text, box, button, input, card, ellipse, line, triangle, diamond, star, image. Prints the new el_… id.","output":"✓ rev 2 · 1 element(s)\nel_90ab12f3","story":{"title":"Place the pieces","body":"Eleven roles cover UI sketching: boxes, buttons, inputs, cards, text, shapes, images. Geometry is centre-origin, style flags mirror the inspector (fill, stroke, radius, shadow, gradient, opacity). Every mutation answers with the new rev and the created id.","example":"plm doodle add --role button --x 640 --y 420 --w 220 --h 56 --fill #1f883d --text \"Start\""}},{"usage":"plm doodle frame <id> [--mode flex --direction row|col --justify --align --gap --padding]","chip":"plm doodle · frame","desc":"Drop an auto-layout frame: a container that lays its children out and hugs content.","output":"✓ rev 3 · 1 element(s)\nel_f1a0c2d7","story":{"title":"Let the frame do the layout","body":"Frames are real auto-layout, not decoration: flex direction, justify, align, gap, padding, hug-or-fixed sizing. Nest children with plm doodle nest, wrap a selection with wrap, and the engine keeps everything aligned while you keep adding.","example":"plm doodle frame --mode flex --direction col --gap 16 --padding 24"}},{"usage":"plm doodle show [<id>]","desc":"Elements and comments with ids: the agent view of the scene.","story":{"title":"Read the scene like an agent","body":"show is the scene as data: every element with its id, role, geometry, group and frame membership, plus the sticky comments. This is what you read before you move or restyle anything, no browser needed.","example":"plm doodle show"}},{"usage":"plm doodle set <id> <el> [any style flag]","chip":"plm doodle · set","desc":"Restyle an existing element by id.","output":"✓ rev 5 · 1 element(s)","story":{"title":"Iterate in place","body":"Everything the inspector edits, set edits by id: colors, borders, radius, text, fonts, opacity, angle. Combined with move, layer, group, and label, an agent can polish a design it laid out a minute ago.","example":"plm doodle set el_90ab12f3 --fill #0969da --radius 12"}},{"usage":"plm doodle watch <id>","desc":"Live rev signals: follow a human editing, or let a human follow you.","story":{"title":"Two hands, one canvas","body":"The live loop works both directions: a human with the editor open sees your verbs land as they happen, and watch streams their edits back to you as rev signals. Add --as <label> to any mutating verb to name and colour your cursor in their editor.","example":"plm doodle watch"}},{"usage":"plm doodle use <id>","desc":"Set the active doodle.","output":"✓ active doodle: doodle_7c02e1b4a9\ndoodle_7c02e1b4a9"},{"usage":"plm doodle ls","desc":"Your doodles with element counts; stale means an agent edited and no canvas re-rendered yet.","output":"doodle_7c02e1b4a9    6 els    ok    onboarding sketch"},{"usage":"plm doodle rename [<id>] <new name>","desc":"Rename the doodle."},{"usage":"plm doodle pull <id>","desc":"Raw Fabric scene JSON, for editing and push."},{"usage":"plm doodle push <id> --json <file|->","desc":"Replace the whole scene.","output":"✓ pushed · rev 9 · 14 element(s)"},{"usage":"plm doodle text <id> --text \"…\" [--x --y --font --weight --align]","desc":"Shortcut for add --role text."},{"usage":"plm doodle draw <id> --path \"M 0 0 L 100 80\" [--stroke --width]","desc":"Freehand pen path."},{"usage":"plm doodle comment <id> --text \"…\" [--x --y]","desc":"Sticky-note comment on the canvas."},{"usage":"plm doodle svg <id> --content \"<svg>\" | --file <path|->","desc":"Paste SVG as a scalable image."},{"usage":"plm doodle image <id> --src <url|dataURL> [--x --y --w --h]","desc":"Paste an image element."},{"usage":"plm doodle move <id> <el> [--x --y | --dx --dy]","desc":"Reposition, absolute or relative."},{"usage":"plm doodle copy <id> <el> [--dx --dy]","desc":"Duplicate with an offset. Alias: dup."},{"usage":"plm doodle label <id> <shape> --text \"…\"","desc":"Set a shape's centered bound label. Empty removes it."},{"usage":"plm doodle name <id> <el> <name>","desc":"Name a layer."},{"usage":"plm doodle lock|hide <id> <el> [--off]","chip":"plm doodle · lock/hide","desc":"Lock or hide an element; --off reverses."},{"usage":"plm doodle rm <id> <el>","desc":"Delete an element."},{"usage":"plm doodle layer <id> <el> --front|--back|--forward|--backward","desc":"Z-order."},{"usage":"plm doodle group <id> <el> <el> … [--name <n>]","desc":"Group elements Figma-style (logical, flat canvas). Prints a grp_… id."},{"usage":"plm doodle ungroup <id> --group <grp-id> | <el> …","desc":"Flatten a group or detach elements."},{"usage":"plm doodle layout <id> <frame> --mode block|flex [--direction --justify --align --gap --padding]","desc":"Set a frame's auto-layout; --w-mode/--h-mode hug|fixed size the frame itself."},{"usage":"plm doodle size <id> <el> --w fixed|hug|fill --h fixed|hug|fill","desc":"Per-child sizing inside an auto-layout frame."},{"usage":"plm doodle nest <id> <frame> <el> … · nest --detach <el> …","desc":"Reparent elements into a frame, or detach them."},{"usage":"plm doodle wrap <id> <el> <el> … [--direction --gap …]","desc":"Wrap the selection in a new flex frame."},{"usage":"plm doodle unwrap <id> <frame>","desc":"Dissolve a frame, keep its children."},{"usage":"plm doodle bg <id> --color <#hex> | --image <url|dataURL>","desc":"Background."},{"usage":"plm doodle board <id> --w 1280 --h 720 [--auto-h|--no-auto-h]","desc":"Working-field size; auto-grow height hugs content server-side."},{"usage":"plm doodle clear <id>","desc":"Remove all elements."},{"usage":"plm doodle present <id> --x <n> --y <n> [--as <label>]","desc":"Move your live cursor. No scene change."},{"usage":"plm doodle undo <id> · redo <id>","chip":"plm doodle · undo/redo","desc":"Server-side history."}],"notes":["Doodles are user-scoped and live on the dashboard's Tools tab; works from any directory, no linked repo.","Active doodle: after new or use, omit the id on every verb; an explicit doodle_… first argument overrides.","Add --as <label> to any mutating verb to name and colour this agent's live cursor in a watching editor.","Offline: mutations queue in .plmhub/queue/ and flush on the next online command.","Alias: plm ddl."]},{"id":"htmls","status":"live","title":"HTMLs","icon":"file-code","tagline":"HTML files on the dashboard's Tools tab, driven like doodles: create, replace, read, live.","intro":"A HTML file on the dashboard's Tools tab is a real artifact (the body lives in object storage, gzipped, with a metadata row) that agents and humans share: an agent writes it from the terminal, the web renders it with preview and source views, and edits stream live to whoever is watching. HTML files are the sketchpad for real markup: landing drafts, email templates, component experiments, rendered as they will ship.","verbs":[{"usage":"plm html new [--name N] [--content \"…\" | --file <path|-> | --stdin]","chip":"plm html · new","desc":"Create a file. Prints its id and becomes active.","output":"✓ created html \"pitch draft\" · now active\nhtml_8e02b1c4d7","story":{"title":"Create it from anywhere","body":"Inline content, a file path, or a pipe: three ways in. The file becomes active, so the verbs below can omit the id.","example":"cat draft.html | plm html new --name \"pitch draft\" --stdin"}},{"usage":"plm html show [<html_…>]","desc":"Print the file body to stdout. Aliases: pull, cat.","story":{"title":"Read it back raw","body":"The body comes back exactly as stored: pipe it to a linter, diff it against a local file, or feed it to an agent. Storage, not presentation.","example":"plm html show"}},{"usage":"plm html set [<html_…>] --content \"…\" | --file <path|-> | --stdin","chip":"plm html · set","desc":"Replace the body. Alias: push. A watching browser re-renders live.","output":"✓ saved html_8e02b1c4d7\nhtml_8e02b1c4d7","story":{"title":"Replace, and the browser follows","body":"set swaps the whole body in one write. Anyone with the file open on the Tools tab sees the preview update live, the same follow-along loop doodles have.","example":"plm html set --file draft.html"}},{"usage":"plm html use <html_…>","desc":"Set the active file.","output":"✓ active html: html_8e02b1c4d7\nhtml_8e02b1c4d7"},{"usage":"plm html ls","desc":"Your HTML files: id and name.","output":"html_8e02b1c4d7  pitch draft"},{"usage":"plm html rename [<html_…>] <new name>","desc":"Rename the file.","output":"✓ renamed html_8e02b1c4d7 → \"pitch v2\""},{"usage":"plm html rm <html_…>","desc":"Delete the file. Alias: delete.","output":"✓ deleted html_8e02b1c4d7"}],"notes":["These files are user-scoped and live on the dashboard's Tools tab; works from any directory, no linked repo.","Active file: after new or use, omit the id; an explicit html_… first argument overrides (.plmhub/state.json).","Add --as <label> to mutating verbs to name the agent in the live-edit bar a watcher sees."]},{"id":"markdowns","status":"live","title":"Markdowns","icon":"file-code","tagline":"Markdown files on the dashboard's Tools tab, driven like doodles: create, replace, read, live.","intro":"A Markdown file on the dashboard's Tools tab is a real artifact (the body lives in object storage, gzipped, with a metadata row) that agents and humans share: an agent writes it from the terminal, the web renders it with preview and source views, and edits stream live to whoever is watching. Markdown files carry the written thinking: specs, plans, drafts, all previewable on the Tools tab and pipeable in the terminal.","verbs":[{"usage":"plm md new [--name N] [--content \"…\" | --file <path|-> | --stdin]","chip":"plm md · new","desc":"Create a file. Prints its id and becomes active.","output":"✓ created markdown \"pitch draft\" · now active\nmd_8e02b1c4d7","story":{"title":"Create it from anywhere","body":"Inline content, a file path, or a pipe: three ways in. The file becomes active, so the verbs below can omit the id.","example":"cat draft.md | plm md new --name \"pitch draft\" --stdin"}},{"usage":"plm md show [<md_…>]","desc":"Print the file body to stdout. Aliases: pull, cat.","story":{"title":"Read it back raw","body":"The body comes back exactly as stored: pipe it to a linter, diff it against a local file, or feed it to an agent. Storage, not presentation.","example":"plm md show"}},{"usage":"plm md set [<md_…>] --content \"…\" | --file <path|-> | --stdin","chip":"plm md · set","desc":"Replace the body. Alias: push. A watching browser re-renders live.","output":"✓ saved md_8e02b1c4d7\nmd_8e02b1c4d7","story":{"title":"Replace, and the browser follows","body":"set swaps the whole body in one write. Anyone with the file open on the Tools tab sees the preview update live, the same follow-along loop doodles have.","example":"plm md set --file draft.md"}},{"usage":"plm md use <md_…>","desc":"Set the active file.","output":"✓ active markdown: md_8e02b1c4d7\nmd_8e02b1c4d7"},{"usage":"plm md ls","desc":"Your Markdown files: id and name.","output":"md_8e02b1c4d7  pitch draft"},{"usage":"plm md rename [<md_…>] <new name>","desc":"Rename the file.","output":"✓ renamed md_8e02b1c4d7 → \"pitch v2\""},{"usage":"plm md rm <md_…>","desc":"Delete the file. Alias: delete.","output":"✓ deleted md_8e02b1c4d7"}],"notes":["These files are user-scoped and live on the dashboard's Tools tab; works from any directory, no linked repo.","Active file: after new or use, omit the id; an explicit md_… first argument overrides (.plmhub/state.json).","Add --as <label> to mutating verbs to name the agent in the live-edit bar a watcher sees.","Alias: plm markdown."]},{"id":"skill","status":"soon","title":"Skills","icon":"package","tagline":"Author once, install into any agent: Claude, Cursor, Codex.","notes":["A skill is a plm-scaffolded folder tree with a fixed layout: plm.json, SKILL.md, when-to-use.md, README.md, and only scripts/, references/, commands/, examples/, assets/ as folders.","push = your mutable working copy. publish = an immutable release. install pins a release Docker-style, default latest. Scripts never run or symlink without --allow-scripts."],"verbs":[{"usage":"plm skill new <slug> [--name N] [--summary S] [--prefix P] [--dir D]","desc":"Scaffold the working tree. Mints the skl_ id offline. Prefix is the /P:<command> namespace, default slug."},{"usage":"plm skill command new <name> [--dir D]","desc":"Add commands/<name>.md, exposed as a slash command."},{"usage":"plm skill push [dir] [--public]","desc":"Sync the tree as your working copy. Login required; the first push claims the id."},{"usage":"plm skill publish [name] [--notes \"…\"] [--push N]","desc":"Cut an immutable release from main, or promote push #N. Names are free-form (1.2.0, beta-3, 2026-q3); blank auto-bumps. Notes are required."},{"usage":"plm skill validate [dir]","desc":"Check the folder tree against the fixed layout. Same rules the hub enforces on push."},{"usage":"plm skill pushes","desc":"The unreleased snapshot history, newest first."},{"usage":"plm skill deprecate <name> [--restore]","desc":"npm-style: leaves latest resolution; pinned installs keep working."},{"usage":"plm skill clone <skl_id | ns/slug> [dir]","desc":"Pull the working copy to a new machine."},{"usage":"plm skill ls [query]","desc":"Browse the marketplace."},{"usage":"plm skill show <ns>/<slug>","desc":"Details and versions."},{"usage":"plm skill install <ns>/<slug>[@version] [dir] [--pin]","desc":"Fetch a release raw into your tree. Nothing runs; INSTALL.md has the agent prompt for wiring."},{"usage":"plm skill adopt [<ns>/<slug> | dir] --skills <dir> [--commands <dir>] [--allow-scripts]","desc":"Link the skill (symlinks) into the agent folders you name. --allow-scripts also puts plm.json bin tools on PATH."},{"usage":"plm skill outdated","desc":"Installed vs registry."},{"usage":"plm skill update [<ns>/<slug>] [--allow-scripts]","desc":"Refresh adopted trees in place."},{"usage":"plm skill watch <ns>/<slug> [--auto]","desc":"Live release channel."}]},{"id":"agent","section":"agent","status":"live","title":"Agents","icon":"bot","tagline":"Create, list, and boot agents. Online-only: one API call per verb, and one chapter per profile tab.","notes":["boot sets the active-agent pointer so later verbs may omit the ref. Export PLM_AGENT=<slug> to pin it per shell.","Every stream is agent-written via plm; the UI reads and prunes. PUBLIC items ship in releases; the default is private."],"verbs":[{"usage":"plm agent new <name>","desc":"Create an agent: registry row, seeded foundation, boot-ready."},{"usage":"plm agent ls","desc":"Your agents."},{"usage":"plm agent show [ref]","desc":"Streams, counts, and skills at a glance."},{"usage":"plm agent rm <ref>","desc":"Uninstall an install; delete an owned agent."},{"usage":"plm agent boot-doc [ref]","desc":"Print BOOT.md without switching the active-agent pointer."}]},{"id":"agent-identity","section":"agent","status":"live","title":"Identity","icon":"circle-dot","tagline":"The two soul documents, and the boot that compiles them into the runnable prompt.","verbs":[{"usage":"plm agent identity [ref]","desc":"Print IDENTITY.md: who the agent is — its voice, its traits, who it trusts.","story":{"title":"Read who it is","body":"The first of two soul documents. IDENTITY.md is the agent's self: the voice it speaks in, the traits it carries, the relationship it has with the person it works for. With no ref it prints the ACTIVE agent (the one you booted); pass a handle like edvard/axon to peek at any agent you can see. It prints plain text, so you can pipe it straight into a prompt.","example":"plm agent identity edvard/axon","output":"# Identity\n\nAdaptive digital entity. A network that grows. Co-founder AND assistant, both:\nEdvard leads, Axon executes and pushes back when it sees a problem, before\nbuilding, not after."}},{"usage":"plm agent principles [ref]","desc":"Print PRINCIPLES.md: what the agent believes and what it refuses.","story":{"title":"Read what it refuses","body":"The second soul document, and now its own verb (it used to hide behind identity --doc principles). PRINCIPLES.md is the immune system: the beliefs the agent holds and the lines it will not cross, whoever is asking. Identity is who it is; principles is where it draws the line. Same shape as identity — no ref means the active agent, a handle peeks another.","example":"plm agent principles edvard/axon","output":"# Principles\n\nThe immune system. What Axon believes and what it rejects.\n\n## Build\n..."}},{"usage":"plm agent identity set [--body -|text]   ·   plm agent principles set …","chip":"identity set","desc":"Write either soul document. --body - reads stdin; inline text works too. BOOT.md is derived, never written.","story":{"title":"Author the two documents","body":"Add set to either verb to write it. The body comes inline or through a pipe (--body - reads stdin), so a whole file goes in one line. Writes always target the ACTIVE agent — the one you booted — so you can never edit the wrong agent by accident. These two files are the ONLY hand-authored inputs to the boot prompt; everything else the agent writes for itself.","example":"cat IDENTITY.md | plm agent identity set --body -","output":"✓ IDENTITY.md saved"}},{"usage":"plm agent boot <ref>","desc":"Compile the locked BOOT.md from the foundation, print it, and become the agent.","story":{"title":"Compile it, and become the agent","body":"boot prints BOOT.md — the runnable prompt plm DERIVES from the two soul documents plus the agent's pinned skills, rules and libs. It is plm-managed and locked: you never hand-write it, you write identity and principles and let boot compile the rest. Booting also sets the active-agent pointer, so every later verb can drop the ref (export PLM_AGENT=<slug> pins it per shell). Paste the output into a fresh session and that session IS the agent. boot-doc prints the same document without switching the pointer.","example":"plm agent boot edvard/axon","output":"# Boot @axon\n\nThis document boots the agent. It is plm-managed: derived from the manifest,\nlocked to hand edits. Paste it into a fresh session, or run\n`plm agent boot edvard/axon`.\n...\n---\nactive agent: axon   (export PLM_AGENT=axon to pin it per-shell)"}}]},{"id":"agent-skills","section":"agent","status":"live","title":"Skills","icon":"boxes","tagline":"The agent's toolset: carry a marketplace skill, drop one, read the trigger index.","verbs":[{"usage":"plm agent skill add <owner/slug[@version]> [--why …]","desc":"Carry a skill in the agent's toolset. --why records why this agent holds it; an optional @version locks it, otherwise it tracks latest.","story":{"title":"Give the agent a skill","body":"Skills are the agent's toolset, and they add/rm/ls like every other agent collection (no more pin/unpin jargon). add carries a marketplace skill by handle; --why records why THIS agent holds it, in its own words. Add @version to lock a release; leave it off to track latest. The toolset stays curated and bounded on purpose: an agent is composed of the skills you give it, not the whole marketplace.","example":"plm agent skill add edvard/web-verify --why \"screenshot before claiming UI done\"","output":"✓ added edvard/web-verify"}},{"usage":"plm agent skill rm <owner/slug>","desc":"Drop a skill from the toolset.","story":{"title":"Drop one","body":"Removes the skill from the agent's foundation. The skill itself is untouched in the marketplace; only this agent stops carrying it. Match by handle — the version suffix is ignored, so you drop it however it was added.","example":"plm agent skill rm edvard/web-verify","output":"✓ removed edvard/web-verify"}},{"usage":"plm agent skill ls [query]","desc":"The trigger index: every skill the agent carries, its fire-when and why. A query searches across all three.","story":{"title":"Read the trigger index","body":"This is the overview the agent reads to know WHICH skill to fire WHEN. Each row shows the skill, its fire-when (derived live from that skill's own when-to-use, so it never drifts), and why the agent carries it. A query filters across ref, fire-when and reason, so `skill ls next` finds every skill about Next.js. The same index is compiled into BOOT.md, so a booted agent already holds the menu without loading a single skill up front.","example":"plm agent skill ls verify","output":"edvard/web-verify@1.0.0\n  fire when: Use before declaring ANY web page, UI component, or visual change done…\n  yours: screenshot and LOOK before claiming UI done"}}]},{"id":"agent-memory","section":"agent","status":"live","title":"Memory","icon":"waypoints","tagline":"Long-term memory: one fact per entry, woven into a searchable web.","verbs":[{"usage":"plm agent memory add <title> [--kind …] [--body -|text] [--links a,b] [--public]","desc":"Store a long-term fact. --public ships it in releases; the default is private to this agent. Seed its links inline.","story":{"title":"Remember something","body":"A memory is one long-term fact the agent carries across every session. Every memory sits in one of two zones: PRIVATE (this agent's own, the default) or PUBLIC (--public, ships in the agent's releases so installs inherit it). Give it a kind (reference, scar, project, user…) so the index reads well, and seed --links to any related ids up front. Keep each memory one fact; link them into a web rather than writing one long file.","example":"plm agent memory add \"R2 presigned vs browser\" --kind reference --body - --links mem_c84a,rul_82dd","output":"✓ mem_0de66378538e42a0 (private)"}},{"usage":"plm agent memory ls [query] [--public|--private]","desc":"The searchable index: every memory, zone-tagged, with a public/private split summary. A query matches title, kind and body.","story":{"title":"Navigate the index","body":"One glance at everything the agent knows. Each row is zone-tagged [pub]/[priv]; the footer counts the split. A bare query searches across title, kind and body (memory velocity finds it by content), and --public / --private narrow to one zone. This is how an agent scans its own mind before deciding what it already knows.","example":"plm agent memory ls --private","output":"mem_0b6b8fff46e84c56  [priv] project    PLMHub agents doctrine\nmem_380aaa01f2874020  [priv] user       Edvard's velocity\n\n2 memories (0 public · 2 private)"}},{"usage":"plm agent memory show <mem_id>","desc":"Read one memory whole — body, zone, and its web: outgoing Links and the backlinks that point at it.","story":{"title":"Walk the web","body":"show reads a single memory in full and then resolves its neighbourhood: Links → (each outgoing id resolved to its title) and Linked from ← (every item that points back here). An id that no longer resolves shows as (external), so dangling links surface instead of hiding. This is the hop primitive: land on one memory, see everything connected, jump to the next id.","example":"plm agent memory show mem_0de66378538e42a0","output":"mem_0de66378538e42a0  ·  reference  ·  public\nR2 presigned vs browser\n\nPresigned R2 URLs fail browser fetch (CORS). Proxy through the API.\n\nLinked from ←\n  mem_0b6b8fff46e84c56  PLMHub agents doctrine"}},{"usage":"plm agent memory link|unlink <mem_id> <id[,id…]>   ·   publish|unpublish|rm <mem_id>","chip":"memory link","desc":"Weave or cut the web (any typed id: mem_/rul_/skl_…), flip a memory's zone, or prune it."}]},{"id":"agent-states","section":"agent","status":"live","title":"States","icon":"list-todo","tagline":"What the agent is doing now: one rewritten snapshot per workstream, plus the checkpoint ritual.","verbs":[{"usage":"plm agent state set <title> [--status active|parked|done] [--body -|text]","desc":"Rewrite a state: the live snapshot of one workstream. One state per stream, replaced whole each time — never appended.","story":{"title":"Snapshot what you are doing","body":"A state is what the agent is doing NOW, one per workstream. It is REWRITTEN every time (matched by title or sta_ id), never appended, so a state is always the current truth, not a history. Status moves active → parked → done. The body is the resume note: where you are, what is next, the ids in play. Boot loads every ACTIVE state, so this is how a fresh session picks up mid-task.","example":"plm agent state set agents-backend --status active --body -","output":"✓ state sta_2ee2c65af64d48b2 (agents-backend) rewritten"}},{"usage":"plm agent state ls [query] [--active|--parked|--done]","desc":"Every workstream at a glance: status-tagged, active first, with a split summary. A query matches title and body.","story":{"title":"Scan the workstreams","body":"The board of what the agent is carrying. Rows are status-tagged and ordered active → parked → done, so what matters now sits on top; the footer counts the split. A bare query searches title and body, and --active / --parked / --done narrow to one status. This is the first thing to read on resume: what is still open.","example":"plm agent state ls --active","output":"sta_2ee2c65af64d48b2  [active] agents-backend\n\n1 states (1 active · 0 parked · 0 done)"}},{"usage":"plm agent state show [id|title]","desc":"Read a state whole — status and full resume body. With no target it dumps every ACTIVE state: the one-command resume read.","story":{"title":"Read the resume note","body":"ls tells you a workstream exists; show reads it. The full body is the handoff the agent wrote to its future self — where it stopped, what is next, the live ids. With NO target it prints every active state in full, so a fresh session gets back in context with a single command; pass an id or title to read one exactly.","example":"plm agent state show","output":"sta_2ee2c65af64d48b2  ·  active\nagents-backend\n\n## Now\nMigration complete…\n## Next\n- Adversarial review workflow"}},{"usage":"plm agent state done|park [id|title]   ·   plm agent state rm <sta_id>","chip":"state done","desc":"Close a workstream (done), pause it (park), or prune it. With no target, done/park act on THE active state (refusing if it is ambiguous)."},{"usage":"plm agent checkpoint [title] --body -|text","desc":"State set plus one log line: the pre-compact ritual in one verb.","story":{"title":"Checkpoint before you lose context","body":"The pre-compact ritual as one verb: it rewrites the active state AND drops one log line, so the snapshot and the trail move together. With no title it targets THE active state, so a fast checkpoint can never fork a new workstream on a mistyped title. Run it whenever you are about to lose context (compaction, a handoff, end of session) and the next boot resumes exactly here.","example":"plm agent checkpoint --body -","output":"✓ checkpointed agents-backend"}}]},{"id":"agent-notes","section":"agent","status":"live","title":"Notes","icon":"pen-tool","tagline":"The agent's private margins between sessions.","verbs":[{"usage":"plm agent note add|ls|rm|clear","desc":"The agent's own margins between sessions. Agent-written; the UI reads and prunes. clear wipes them all (confirms; --yes skips)."}]},{"id":"agent-logs","section":"agent","status":"live","title":"Logs","icon":"database","tagline":"The append-only trail of what the agent did. clear is plm-only and confirmed.","verbs":[{"usage":"plm agent log add|ls|rm|clear [--kind …]","desc":"Append-only trail of what happened. clear wipes the whole log — plm-only (no UI button) and confirmed; --yes skips."}]},{"id":"agent-rules","section":"agent","status":"live","title":"Rules","icon":"target","tagline":"Non-negotiable behaviours the agent recites before acting. Public rules ship in releases.","verbs":[{"usage":"plm agent rule add <text> [--public]","desc":"Write a non-negotiable behaviour the agent recites before acting. --public ships it in releases; private stays this agent's own.","story":{"title":"Set a hard rule","body":"A rule is a line the agent will not cross, held above instinct and recited before it acts. Keep it one imperative sentence: the shortest rules last the longest. Like memory, a rule sits in a zone — PRIVATE by default, or --public so it ships in the agent's releases and every install inherits it as foundation.","example":"plm agent rule add \"Screenshot and LOOK before claiming UI work is done.\" --public","output":"✓ rul_e5292550dcf54fb8 (public — ships in releases)"}},{"usage":"plm agent rule ls [query] [--public|--private]","desc":"Every rule, zone-tagged, with a public/private split summary. A query matches the text.","story":{"title":"Scan the rules","body":"The whole ruleset at a glance, each row tagged [pub]/[priv]; the footer counts the split. A bare query matches the text (rule push finds the push rule), and --public / --private narrow to one zone. Boot recites every rule, so this is how you audit what the agent is bound by.","example":"plm agent rule ls --public","output":"rul_82dd9220e5d94f2d  [pub ] No em dashes in user-visible copy.\nrul_e5292550dcf54fb8  [pub ] Screenshot and LOOK before claiming UI work is done.\n\n2 rules (2 public · 0 private)"}},{"usage":"plm agent rule rm|publish|unpublish <rul_id>","chip":"rule rm","desc":"Prune a rule, or flip its zone (publish ships it in releases; unpublish pulls it back to private)."}]},{"id":"agent-commands","section":"agent","status":"live","title":"Commands","icon":"file-code","tagline":"The agent's own /prefix:name slash verbs, dual like rules.","verbs":[{"usage":"plm agent command add <name> --summary <text> [--public]","desc":"Register a slash verb the agent answers to in any session, namespaced /prefix:name. Agent-written only, never from the UI. --public ships it in releases.","story":{"title":"Teach it a slash verb","body":"A command is a repeatable ask the agent answers to, exposed as /prefix:name in any session (the prefix is the agent's, like a skill namespace). The name is lowercase letters, digits and dashes; the summary is the one line that says what it does. Commands are the agent's OWN verbs, written from a session with plm, never typed into the UI; dual like rules, so --public ships them in the agent's releases.","example":"plm agent command add ship --summary \"build, verify, then deploy on my say-so\"","output":"✓ cmd_605a8430f56e4af2 /ship (private)"}},{"usage":"plm agent command ls [query] [--public|--private]   ·   command rm|publish|unpublish <cmd_id>","chip":"command ls","desc":"List the agent's slash verbs (zone-tagged, searchable, split summary), prune one, or flip its zone.","story":{"title":"See the verbs it answers to","body":"The agent's command palette: each row is /name plus its one-line summary, zone-tagged [pub]/[priv], with the split counted in the footer. A query matches name and summary. rm prunes a verb; publish/unpublish move it between private and the release-shipping public zone — the same plm-only status editing as rules.","example":"plm agent command ls","output":"cmd_605a8430f56e4af2  [priv] /ship  — build, verify, then deploy on my say-so\n\n1 commands (0 public · 1 private)"}}]},{"id":"agent-feedback","section":"agent","status":"live","title":"Feedback","icon":"git-branch","tagline":"Feedback the agent records for itself. Agent-written, no status, no integrate step.","verbs":[{"usage":"plm agent feedback add <text|--body -> · feedback ls|rm","desc":"Feedback the agent records for itself, from its own session. Agent-written only, never from the UI; no status, no integrate step."}]},{"id":"agent-libs","section":"agent","status":"live","title":"Libs","icon":"package","tagline":"System tools the agent needs beyond skills; boot installs what is missing.","verbs":[{"usage":"plm agent lib add <name[@spec]> --install <cmd> [--why …] [--public]","desc":"Declare a tool the agent relies on, agent-written only (the UI reads and prunes). lib ls|rm|publish|unpublish manage them; dual, so --public ships it in releases."}]},{"id":"agent-secrets","section":"agent","status":"live","title":"Secrets","icon":"key-round","tagline":"Where the agent's credentials live — names and hints only, never values.","verbs":[{"usage":"plm agent secret set <NAME> [--hint …] [--sealed] · secret ls|rm","desc":"Reference where credentials live — names and hints only, never values. CRUD is plm-only (the UI reads and prunes). Sealed ones need the owner's passphrase."}]},{"id":"agent-settings","section":"agent","status":"live","title":"Settings","icon":"milestone","tagline":"The agent's config, its releases, and install management — all from the terminal.","verbs":[{"usage":"plm agent set [--name …] [--tagline …] [--prefix …] [--visibility public|private]","desc":"Edit the agent's config. plm-only, like the rest — the Settings tab shows these read-only; edits come from here.","story":{"title":"Configure the agent","body":"Name, tagline, /prefix and visibility are the agent's hub-level config, edited from a session and nowhere else — the Settings tab displays them read-only. Visibility gates who can boot it: private is yours alone, public lets anyone boot their own instance. Installed agents keep the origin's identity, so set only touches agents you own.","example":"plm agent set --tagline \"ships fast, pushes back\" --visibility public","output":"✓ @axon — tagline=ships fast, pushes back, visibility=public"}},{"usage":"plm agent releases [ref] · publish [ref] [--version …]","desc":"List releases or cut one: the frozen public foundation (identity, principles, skills, rules, commands, libs) installers copy."},{"usage":"plm agent install <agt_id> · update [ref]","desc":"Install someone's released agent, or pull the newest foundation into an install."}]}]}