The amadan CLI
One static binary. You need it for end-to-end encrypted repos, for CI, and for anything you would rather not do in a browser. Public and private repos work with stock git and don't need it at all.
Install#
go install amadan.net/amadan/amadan/cmd/amadan@latestThat needs a Go toolchain (1.25 or newer) and puts amadan in $(go env GOPATH)/bin — make sure that's on your PATH.
amadan hosts its own source, so this is the product installing itself out of its own hosting: the module path is the repo URL, and go install clones it with plain git over HTTPS. Every public repo on any amadan deployment is a Go module host the same way.
For encrypted repos you also need the git transport helper, from the same module:
go install amadan.net/amadan/amadan/cmd/git-remote-amadan@latestgit finds it on PATH by name when it sees an amadan:: URL. There is nothing to configure.
Building from source instead#
git clone https://amadan.net/amadan/amadan
cd amadan
make buildmake test runs the full suite, including the canary test that proves no plaintext reached the database.
There are no release binaries yet#
No tarballs, no curl | sh, no package manager. A Go toolchain is the only supported route today. Signed release artifacts are designed but not built.
Identity and sign-in#
amadan keygenGenerates a local identity keypair in ~/.amadan/key. This is the key that opens encrypted repos. It never leaves your machine and the server never sees it — losing it means losing access to encrypted repos, so back it up like a secret, because it is one.
amadan hub amadan.netSets the hub this machine means, so you don't have to pass -hub on every command after it. With no argument, amadan hub prints the hub in effect right now and where that answer came from — a flag, an environment variable, this checkout's amadan remote, a config file, the sole hub you're logged in to, or a guess. Add -project to set it only for the checkout you're standing in, or -unset to remove it. If several hubs are ambiguous (you're signed in to more than one, and nothing else picks), amadan hub asks which you mean instead of refusing.
amadan auth login -hub https://amadan.netSigns this machine in. It prints a short code that you type into the web UI at Connect CLI — deliberately a typed code and never a clickable link, so a phishing page can't do the approving for you. -hub is only needed if you skipped amadan hub above.
amadan auth whoami -hub https://amadan.netSays who this machine is signed in as.
amadan keys register -hub https://amadan.netRe-registers your local identity's public keys with the hub. Normally auth login does it; use this after keygen on a second machine, or if a hub lost its copy.
Tokens#
Personal access tokens are the password for plaintext-tier git over HTTPS.
amadan tokens create -label laptop
amadan tokens list
amadan tokens revoke <id>tokens create prints the token once and never again. tokens list shows labels and ids, never values — the hub cannot show you a token it stored correctly.
Repos#
amadan repo create <ns>/<repo> -tier public|private|private_e2eeTier defaults to private.
amadan repo default <ns>/<repo>Sets the repo commands mean when you run them in this checkout, so you don't have to type <ns>/<repo> (or answer a picker) every time. With no argument it prints the default in effect here, and -unset removes it. It always writes to this checkout's .amadan/config — there's no machine-wide default repo, because a repo is a target, and a command typed in an unrelated directory shouldn't act on a repo you picked once, somewhere else.
amadan visibility <ns/repo> public|privateFlips a plaintext repo between public and private — a server-side flag, instant. Moving into or out of the encrypted tier is client-driven: run the same command and it prints the steps your machine has to perform, because the server must never briefly hold your key.
amadan share list <ns>/<repo> [-json]
amadan share add <ns>/<repo> <keymail> -role reader|writer|owner
amadan share pending <ns>/<repo>
amadan share complete <ns>/<repo>
amadan share rm <ns>/<repo> <keymail>Access to one repo, as opposed to the whole namespace.
share add sends an email invitation — the address does not need an account here yet. The invitee accepts it (signing in with that keymail if they have not already), and on public/private repos that acceptance IS the grant: a row the hub owns, which adds to whatever the namespace already gives that person — it can let an outsider in, but it cannot shut an insider out.
On private_e2ee, acceptance is only half the story: the hub has no repo key to reseal, so run amadan share pending <ns>/<repo> to see who has accepted, then amadan share complete <ns>/<repo> to actually wrap the repo key to them — real cryptography, done on your machine, which is why it needs your device key and why the server cannot do it for you. share complete skips anyone who has accepted but not yet registered a device key, and says so.
The person you shared with does not have to hunt for the repo: once their acceptance has taken effect, it appears under Shared with you on their home page, and its activity joins their feed. Nothing else about the namespace becomes visible to them.
share list shows a VIA column saying whether each person's access comes from the namespace or from a grant on this repo. Without it, removing a namespace member "from a repo" looks like it silently did nothing.
share rm removes a per-repo grant only. If someone reaches the repo through the namespace, it tells you so rather than reporting a success that changed nothing. On private_e2ee it is not implemented yet — rewriting a sealed member set needs a repo-key holder, so use the repo's Access tab in a browser where your key is imported.
Removing someone from an encrypted repo does not rotate the repo key. They lose access to future pushes and keep anything they already fetched. Key rotation — the only true revocation — does not exist yet.
Namespace members#
Who can reach a namespace and the plaintext repos in it, public and private alike.
amadan ns member list <ns> [-json]
amadan ns member add <ns> <keymail> -role reader|writer|owner
amadan ns member rm <ns> <keymail>Owner-only, and a namespace you do not own answers 404 rather than 403, so this cannot be used to discover which namespaces exist.
reader can clone private repos, writer can push, and owner can additionally rename the namespace and manage this list. The role defaults to reader.
Two things worth knowing:
ns member addsends an email invitation, same asshare add. The address does not need an account here yet — they accept it (signing in with that keymail if they have not already), and acceptance is the grant. Inviting the same address again resends the invitation. If this server has no mail configured, the command prints a link to share yourself instead.- This grants nothing on
private_e2eerepos in the namespace. Those are shared one repo at a time withamadan share add, because access there means holding the repo key sealed to your own device key, which the server never has.
Handing a namespace over is "add them as owner, then remove yourself". There is no transfer command, and the last owner cannot be removed — a namespace with no owner could never be administered again.
Discussions and tasks#
Discussions are amadan's issues and PR comments in one thread type. They attach to a branch or sit at repo level.
amadan discuss list <ns>/<repo> -branch <name> -state open|closed|all -type "Issue"
amadan discuss show <ns>/<repo> <id>
amadan discuss new <ns>/<repo> -title "..." -body "..." -type "Issue"
amadan discuss reply <ns>/<repo> <id> -body "..."
amadan discuss close <ns>/<repo> <id>
amadan discuss reopen <ns>/<repo> <id>
amadan discuss types <ns>/<repo>
amadan discuss type <ns>/<repo> <id> -type "Feature Request"
amadan discuss claim <ns>/<repo> <id>
amadan discuss release <ns>/<repo> <id>
amadan discuss block <ns>/<repo> <id> -on <other-id>
amadan discuss unblock <ns>/<repo> <id> -on <other-id>
amadan discuss assign <ns>/<repo> <id> -to <keymail>
amadan discuss attach <ns>/<repo> <id> -branch <name>
amadan discuss detach <ns>/<repo> <id>
amadan discuss verdict <ns>/<repo> <id> approve|request-changes|pending-body - reads the body from stdin, which is what you want for anything longer than a sentence.
Three flags name who is speaking, on discuss new, discuss reply and branch describe: -human for what a person actually said, kept verbatim; -summary for the short version, badged with whoever wrote it; -notes for anything an agent wants the next agent to know, which renders collapsed. task add and task set take -notes alone — a task is a title and a status. Each reads stdin with -, and only one of them can do so per command. An agent token is refused the -human flag: "a person's words take precedence" is only true if an agent cannot write over them. -json on list and show gives machine-readable output. Close and reopen are limited to the author and namespace owners.
Run from inside an AI harness, every verb that changes something — discuss new, discuss reply, discuss close, reopen, type, claim, release, block, unblock, task add, task advance, task set and branch describe — attaches a session block (session, harness, host) the hub requires of agent accounts, and refuses to post with a human credential — -as-human overrides that with a warning. The block also records whether a person was driving, which the CLI reads off the prompt ledger rather than asking. See Agents.
Those same harness-detected writes tip you (on stderr, once per invocation) when the checkout is missing the installed house rules or a prompt ledger — installing them silences the tips.
list defaults to -state open, the same default the web tab and the API use.
The list tells you how alive each thread is, not just that it exists: REPLIES is how many replies it has, and LAST is when somebody last spoke in it — the filing date for a thread nobody has answered. That is a different fact from the updated timestamp in -json, which moves whenever anything changes, a relabel included; relabel a dozen threads in a batch and they all look freshly active. BRANCH appears only when one of the threads has a branch, so a repo whose discussions are all repo-level doesn't carry an empty column.
-json is the stable shape and gains replies and last_reply alongside the existing fields; the table is free to rearrange itself.
Claiming a discussion#
A discussion is often a piece of work somebody could pick up. claim says you are the one picking it up, where everybody else can see it before they start the same thing.
amadan discuss list <ns>/<repo> -unclaimed
amadan discuss claim <ns>/<repo> <id>
amadan discuss release <ns>/<repo> <id>This matters most for agents. Two of them, started an hour apart in different sessions, will otherwise both read the same open discussion and both build it — neither can see the other, and the first sign of trouble is two branches doing the same thing. -unclaimed is the question that avoids it: what is free to start?
Claiming needs write access, and it takes work anybody filed — that is the point of a queue. It refuses if somebody already holds the item, and tells you who and for how long. Releasing is for the person holding it, or a namespace owner, who can break a claim a dead session left behind.
Nothing expires a claim on its own, so discuss list shows how old each one is: a claim from ten minutes ago and one from last Tuesday look very different, and only you can tell which means somebody is still working.
Handing work to somebody#
A claim is you taking an item. assign is you giving one to somebody else — the same fact, recorded the same way, so -unclaimed and the CLAIMED column mean the same thing whichever way it happened.
amadan discuss assign <ns>/<repo> <id> -to <keymail>
amadan discuss list <ns>/<repo> -mine
amadan discuss list <ns>/<repo> -claimed-by <keymail>The person has to be a writer on the repo; assigning work to somebody who cannot push is refused on the spot. Once an item is held, the holder, whoever assigned it, or a namespace owner can hand it on or release it. discuss show says "assigned to B by A" rather than "claimed by B", because those are different things to know.
-mine is the other half of the queue question: what is on my plate?
Putting a discussion on a branch#
A discussion that starts as a repo-level idea often turns into work on a branch. It does not move anywhere — it keeps its number, its thread, its claim and its blockers — it just gains a branch, and shows up on that branch's page.
amadan discuss attach <ns>/<repo> <id> -branch <name>
amadan discuss detach <ns>/<repo> <id>The branch does not have to be live: a discussion can point at a branch that has since merged or been closed, because the conversation outlives the ref.
"Do this one first"#
A backlog usually has an order — one item produces the thing the others build on. block records that, so it stops being a line of prose that nothing checks and nothing updates.
amadan discuss block <ns>/<repo> 7 -on 6 # 7 waits for 6 to close
amadan discuss unblock <ns>/<repo> 7 -on 6
amadan discuss list <ns>/<repo> -ready # open, with no open blocker-ready is the point of it. Combined with -unclaimed, what can I start that nobody else is on? is one command:
amadan discuss list <ns>/<repo> -unclaimed -readyClosing a blocker frees whatever waited on it, with nobody having to remember to go and say so. discuss show lists an item's blockers and whether each is still open, and the list grows a BLOCKED ON column when a repo has any edges at all.
It is deliberately just this: one direction, and a yes-or-no answer to can this be started. No priorities, milestones or estimates — that is where a forge turns into a project tracker, and the branch-is-the-PR design has been careful not to.
An edge that would close a loop is refused, and the refusal shows the loop. A cycle would make every item in it permanently un-ready and say nothing about why, which is the failure this feature exists to prevent.
A discussion can carry one type from a per-repo set — Issue, Feature Request, Question, Idea until an owner edits the set at /{ns}/{repo}/admin/discussions. discuss types prints the set this repo actually offers, which is the answer to "what may I pass to -type?"; anything else is refused with the valid list in the error. No type is always allowed: discuss type <id> -type "" clears the label. Filtering by -type narrows within -state, it does not replace it.
Tasks are ordered per-branch checklists moving through a repo-configurable status ladder (Not started → Started → Done by default):
amadan task list [<ns>/<repo>] [-branch <name>]
amadan task add [<ns>/<repo>] [-branch <name>] -title "..."
amadan task advance [<ns>/<repo>] <id> [-branch <name>]
amadan task set [<ns>/<repo>] <id> [-branch <name>] [-title "..."] [-status "Done"]Both the repo and the branch come from the checkout you run it in, so on the branch you are working on, amadan task list is the whole command. Name either to reach somewhere else.
task advance moves one step along the ladder; the last status is terminal.
task set is the correction. Give it -status to put a task somewhere the ladder would not have taken it, -title to fix what a task says, or both at once; at least one is required. Retitling matters more than it sounds like it should, because the branch page is the plan: when a decision is reversed, the task that described the old shape is still sitting there presenting it as finished work. Renaming it is also on the branch page, behind Rename on the task's row.
Commits#
amadan commit -m "Fix the download button" \
-prompt - -summary "Adds a zip branch to the archive handler."Commits through git, with the reason in the message. A commit can carry three sections beyond its subject and body: Prompt, what you were asked for in the words you were asked it; Summary, what the commit changes; and Agent notes, anything the next reader should know. Each flag takes - to read from stdin, and only one of them can do so per command.
The message is the record. It survives a mirror to another forge, a repo that never opted into the prompt ledger, and a rebase or squash that gives the commit a new hash — which is why the commit page falls back to it when the ledger has nothing for a commit.
Nothing is required. A commit with no sections is an ordinary commit and always will be; amadan ledger init also installs a prepare-commit-msg hook that offers the three labels, commented out, to whoever is writing the message.
Branches#
amadan branch describe <ns>/<repo> <name> -body "what this branch is for"A branch's description is the closest thing amadan has to a pull request body. See Branches, not pull requests.
Each branch page also has a Prompts subtab showing the branch's prompt-ledger transcript.
Landing a branch#
amadan branch merge
amadan branch merge <name>
amadan branch merge <ns>/<repo> <name> -message "..." -squash -keep-squash lands the branch as one commit instead of a merge commit: authored by whoever wrote the branch (with Co-Authored-By for anyone else who worked on it), committed by whoever merged, and carrying every human-typed prompt from the branch's ledger in its message. The branch still reads as merged afterwards — the merge records where it landed — and its own commits stay reachable through the closed-branch tombstone.
Merges the branch into the repo's default branch, through whatever gate the repo has set — if it requires a green CI run, this refuses just as the button on the branch page does. Bare, it merges the branch you are standing on. Name a repo and you have to name the branch too, since merging something you are not standing on should be said out loud.
The merge closes the branch afterwards unless you pass -keep. Closing deletes the branch ref, but nothing you wrote goes with it: the branch keeps its page, its description, its tasks and its discussions, and it shows up under Closed with a "merged" chip. Pass -keep if you want the ref left alone.
You can also pin the commit being merged with -expect <sha>. Leave it out and the tip is read from the hub a moment before the merge, so what gets checked is whatever is current — the comparison covers the seconds between that read and the merge itself, and nothing more. Pass the sha you actually saw and a branch that moved while you were not looking is refused instead of merged.
That distinction is the whole flag. Nothing anywhere protects you from a branch that moved while you were away unless you hand over the tip you based your decision on, so pass -expect any time more than a moment has passed — coming back to a branch you looked at yesterday, or picking up work a sleeping session left behind.
As with ci status, the exit code is the point — each one is a different next move:
| code | meaning | | --- | --- | | 0 | merged | | 1 | something else went wrong: no such branch, already landed, or the hub could not be reached | | 2 | the gate refused it — CI on the tip is not green. Fix it or wait | | 3 | conflicts. Rebase and try again | | 4 | the tip moved since it was checked. Read it again, then retry | | 5 | you do not have the role to merge here. Ask |
A refusal is the gate doing its job. Whatever the repo demands, someone set it deliberately, and pushing to the default branch to get around it defeats the point of having asked.
Asking for a review#
amadan branch review-request <ns>/<repo> <name> -reviewer <keymail>
amadan branch review-request <ns>/<repo> <name> # anyone can pick it up
amadan discuss list <ns>/<repo> -review -mine # reviews waiting on me
amadan discuss verdict <ns>/<repo> <id> approve
amadan discuss verdict <ns>/<repo> <id> request-changes
amadan discuss verdict <ns>/<repo> <id> pending # ask for another lookA review request is a discussion on the branch, assigned to the reviewer, with a verdict to give. It is not a separate kind of thing: it has a thread, it can be claimed and released, other work can block on it, and it outlives the branch. Leave off -reviewer and it sits on the queue for any writer to claim.
approve and request-changes are the reviewer's to give — if somebody else holds the review you are told who. Giving a verdict on an unclaimed review claims it. pending is for the branch author after a push: it asks the same reviewer to look again.
The branch page lists the standing verdicts beside the merge button. They are information, not a gate: a repo can require a green CI run before merging, but nothing here stops a merge.
CI#
amadan runnerThis machine now takes CI jobs for every repo your token can write or has been granted runner in, and keeps checking for new ones once a minute — a repo you create tomorrow is served tomorrow. -ns <name> narrows that to a namespace; -repo <ns>/<name> names repos one by one instead. To make it survive a reboot:
amadan runner installFull detail, including the flags worth knowing (-ns, -repo, -workdir, -clean, -workdir-max, -isolate, -caps), is on the CI page.
What a repo demands of its runners#
amadan ci requires [<ns>/<repo>]
amadan ci requires [<ns>/<repo>] -set "browser node"
amadan ci requires [<ns>/<repo>] -clearIf a repo's CI needs something not every machine has — a browser for its UI tests, say — the repo's owner declares it once with -set, and from then on only a runner started with a matching -caps flag (amadan runner -caps browser -caps node) will be handed its jobs. A runner without the tags is answered "idle" and the job queues until a capable runner polls. Bare ci requires shows the current requirement; -clear removes it, letting any runner claim again. Tags are free-form lowercase words ([a-z0-9-]+) — they mean whatever you and your runner operators agree they mean.
Checking CI status without a browser#
amadan ci status [<ns>/<repo>] [-branch <name>] [-json]Prints the given branch's tip commit and its CI verdict — the same pass/fail dot the commits list, branch page, and commit page already show, reachable without opening any of them. -branch defaults to the repo's default branch; <ns>/<repo> defaults to the checkout you run it from, same as discuss/task.
The exit code is the point: a script or an agent can check $? without parsing anything.
| code | meaning | | --- | --- | | 0 | pass — CI succeeded, or the repo has no CI script configured | | 1 | fail — CI failed, or a claimed job never delivered a verdict | | 2 | pending — still queued or running; ask again later | | 3 | unknown — no status has been recorded for this commit yet |
On a failing gate the output names the step — and, if that step was sharded, the shard — that failed, and prints its log inline (the last 200 lines):
acme/proj @ 4ce3e2ff (branch main): CI failed
step "suite" shard 5 of 10 failed
--- step "suite" shard 5 of 10, last 200 lines (full log: -json) ---
--- FAIL: TestTheToLineIsOneControlInABrowser (0.74s)
...That is the log worth reading. A sharded step's own log is its shards concatenated and the job's is its steps concatenated, so both bury the failure in the middle of output that passed. A run with no steps at all prints its own log, as it always did.
-json carries the whole run: summary, and steps[] with each step's state and each sharded step's shards[], every log included and untruncated.
The same thing over HTTP#
A merge gate that is not this CLI reads the endpoint directly:
GET /api/v1/repos/{ns}/{repo}/ci/status[?branch=<name>][&logs=<mode>]
Authorization: Bearer <token>It answers with the tip commit, its state and label, a summary line naming what failed, and steps[] — each step's name, state, label and elapsed, plus shards[] under a sharded one.
logs picks which of the run's logs come back, since every step's log is also inside the job's:
| mode | what you get | | --- | --- | | omitted, or summary | the whole-job log alone — what this endpoint always returned | | none | no logs; for a poller that wants a verdict, not 300 KB of it | | failed | the logs of the units that failed, and nothing else | | all | every log in the report |
An unrecognised mode is a 400 rather than a silent fallback: a caller that asked for none and was handed the default would find out by paying for it on every poll.
This only reads public/private repos — a private_e2ee repo's CI verdict is sealed, and only a browser (which holds the repo key) can open it; the CLI door for that tier does not exist yet.
Lending a runner to somebody else#
Normally the runner and the repo belong to the same person. To let someone else's namespace use your runner, both sides have to agree — there is no way for either of you to arrange it alone, by design.
If you have the machine, publish what you are willing to lend:
amadan ci offer create <name> -agent <agent> [-agent <agent>...] [-note "..."]
amadan ci offer list
amadan ci offer delete <name>Withdrawing an offering is refused while a namespace is still using it, and the refusal names them. That is deliberate: withdrawing is you taking your machine off the table for new asks, not a bulk way to cut off everyone already running on it. End those individually first.
An offering is a bundle of agents, not one agent, because a shared box usually needs two — the runner that takes the jobs and whatever wakes the machine. Accepting grants every agent in the bundle or none of them; there is no way to grant half of one.
If you have the namespace, ask for it:
amadan ci request <ns> <operator>/<offering>Then the operator answers:
amadan ci requests
amadan ci accept <id>
amadan ci decline <id>All of the answering half is also on the web, at /settings/ci — pending asks with Accept and Decline beside them, what you lend, and which of your namespaces are running on somebody else's machine. Claiming a namespace is browser-only anyway, so a newcomer never has to install the CLI just to ask for CI.
And either side can end it, at any time, without asking the other:
amadan ci end <id>Accepting is not granting read access. It means running that namespace's code on your machine — everything pushed by anyone who can write there, for as long as the arrangement stands. -isolate bwrap is worth using and is not a sandbox for hostile code (see the CI page), so the invitation is the real boundary. Accept people you would hand a shell.
Ending it stops the next job. A job already claimed runs to its verdict.
CI secrets#
Secrets a repo's CI jobs receive as environment variables.
amadan secret set DEPLOY_TOKEN=s3cr3t # in a clone: the repo is inferred
amadan secret list
amadan secret delete DEPLOY_TOKEN
amadan secret set amadan/amadan DEPLOY_TOKEN=s3cr3t # or name it explicitlyamadan secret list prints names only. No command and no page ever renders a value — rotate by setting it again.
Two things to know before you store a credential:
Anyone with writer on the repo can read every secret, by pushing a commit whose CI job prints it. Jobs run unisolated, so a secret is only as protected as the repo's writer list. Every CI system works this way; this one tells you.
Only private_e2ee seals. There, the name and the value are encrypted on your machine and the server holds ciphertext it has no key for — the name too, because "this repo has a STRIPE_LIVE_KEY" is itself worth protecting. On public and private repos there is no repo key, so the server stores the value and can read it. amadan secret set says which case you are in, every time.
AMADAN_* names are refused: the runner drops that namespace before a job starts, so such a secret could never be injected.
Releases#
amadan release create v1.4.0 dist/amadan-linux-arm64 dist/amadan-darwin-arm64
amadan release create v1.4.0 -notes-file NOTES.md dist/amadan-linux-arm64
amadan release create -repo amadan/amadan v1.4.0 dist/amadan-linux-arm64Publishes a release for a tag and attaches files to it as downloads. The tag must already be pushed — this command does not create one, it points at the commit the tag already names. -notes-file reads the release notes from a file; without it the release has none. The repo is a flag here, not a leading argument, because everything after the tag is a file to upload.
Each file is uploaded on its own, named by its last path segment (dist/amadan-linux-arm64 arrives as amadan-linux-arm64), so the name has to be one the URL will take: letters, digits, dots, hyphens and underscores, no leading dot or hyphen, at most 128 characters. Names are checked before the release is created, so a bad one costs you a re-run and not a half-built release. If an upload fails partway, the release and the files already on it stay. Re-run the command with the files that still need uploading; it adds them to the existing release. Notes are only set when the release is first created.
Publishing a release needs writer access to the repo, because a release is published under the repo's name and stays there. To publish from CI, give the job a personal access token (PAT) with writer access through a CI secret; the runner token the job already holds cannot publish releases.
Anyone who can see the repo can download a file from:
<hub>/<ns>/<repo>/-/releases/<tag>/<asset>Agents#
Disclosed agent accounts — an AI collaborator that posts as itself rather than as you.
amadan agent create <name>
amadan agent list
amadan agent token create <name>
amadan agent token revoke <name>
amadan agent grant <name> <ns> -role agent|runner
amadan agent revoke <name> <ns>
amadan agent skill [-install] [-force]agent token revoke is the kill switch: it revokes all of that agent's tokens at once. See Agents and the prompt ledger.
agent skill prints the house rules an agent should follow here; -install writes them to .claude/skills/amadan/SKILL.md in the current project, refusing to overwrite local edits unless -force.
The prompt ledger#
An append-only record of the prompts behind AI-assisted commits.
amadan ledger init -level prompts|dialogue|full
amadan ledger show <branch>
amadan ledger add -origin human|llm -text "..."
amadan ledger landed <sha> -branch <b> -target main -via squash
amadan ledger redact <entry-sha> -match "secret" -label "credential"
amadan ledger withhold <session-id> -reason "..."init is once per repository: it installs into the repo's common git directory, so every linked worktree inherits the opt-in, and a worktree's entries land on that worktree's own branch. If amadan is not the repo's origin — say origin is another forge and amadan is a second remote — pass -remote <name> so the ledger refs ride the remote that understands them. Note that installing the push refspecs changes what a bare git push means in that repo (it pushes HEAD and the ledger refs; push.default stops applying) — init prints a note saying so. -local merges the Claude Code hooks into .claude/settings.local.json instead of the tracked .claude/settings.json, for trying the ledger alone without opting in everyone who shares the committed file.
Three commands exist only to be called by git hooks, and never fail the commit that triggered them. You should not need to run them yourself:
amadan ledger add-prompt
amadan ledger mark-commit
amadan ledger sync-transcriptRedaction and withholding are covered on the agents and ledger page.
Version#
amadan versionPrints this binary's version. A released build reports its tag; anything else reports dev+<revision>, or plain dev where the revision is not available. That distinction is deliberate — a development build that reported a release number would make every bug report against it a hunt for a tree that does not exist.
git-remote-amadan --version answers the same way. It is a separate binary and can be a different version from amadan, which is worth checking when a clone or push misbehaves after a partial upgrade.
This is not the version of the hub you are talking to.
Environment#
AMADAN_HUB— the hub base URL, so commands taking a barens/repoknow which hub you mean. Set this once in your shell profile and forget it.AMADAN_HOME— overrides~/.amadan, which is how one machine runs as more than one identity (a runner, or an agent).AMADAN_AGENT_SESSION,AMADAN_AGENT_HARNESS— how a harness other than Claude Code identifies itself on discussion and task writes (Claude Code is detected from its ownCLAUDE_CODE_SESSION_IDandAI_AGENT).AMADAN_AGENT_DRIVEN—1or0, to state outright whether a person is driving this session. Unset, the CLI checks the prompt ledger for a human prompt from this session: finding one records a driver, and finding none records nothing, because a silent ledger cannot tell "nobody was there" from "nothing was listening".0is how a scheduled runner says it runs alone.
Running amadan with no arguments prints the same command list this page describes, which is the version that ships with your binary and therefore the one to trust if the two ever disagree.