CLI commands
At a glance
Section titled “At a glance”| Command | Purpose | Key flags / args |
|---|---|---|
init [<git-url>] | Create ~/.agentsync/; optionally clone a bootstrap repo. --scope project / --project <path> scaffolds a project tree instead. | --scope --project |
doctor | Diagnose setup: PATH, home/state writability, schema, secrets backend, destination-git-backup mode + per-dir repo status. | |
check | Validate the config: schema lint + unresolved ${secret:} / ${env:} refs. --scope project / --project <path> lints the project tree instead. (Renamed from verify.) | --scope --project |
agent add|remove|list|enable|disable <name> | Manage the agent registry — the user’s, or a project tree’s own [agents] declaration with --scope project / --project <path>. | disable --purge --scope --project |
skill|subagent|command|hook|lsp list | List that component in the canonical source (read-side only — none of these is flag-authorable). | --scope --project |
migrate subagents | One-shot move of the retired canonical agents/ directory to subagents/ (rewrites that tree’s recorded source_id values). | --scope --project |
mcp add|remove|list|enable|disable <name> | Manage MCP servers. enable/disable flip the enabled bit (keep the definition, stop rendering). --header "Name: Value" (repeatable, http/sse only) sets request headers — the usual remote-auth secret site. | --type --command --args --url --env --agents --header |
marketplace add|remove|list <url-or-name> | Manage marketplaces. | |
plugin add|upgrade|enable|disable|remove <id[@marketplace]> / list / outdated / explain | Manage plugins (the lifecycle subcommands all accept the same id[@marketplace] ref add accepts; the bare id also works, and a qualifier naming a different marketplace than the one the plugin was installed from is refused). | add <id[@marketplace]> |
secret set|get|list|remove <key> / secret edit | Manage age-encrypted secrets (list prints KEYS only; edit opens the whole vault, no <key>; set refuses an empty value unless --allow-empty). | set --stdin |
plugin outdated / plugin upgrade [<id>] | (network) Poll marketplaces and report pending bumps / re-fetch a plugin (or --all pending bumps) and run apply. | upgrade --all --lossless --scope --project |
apply | Render source → write agent configs (offline). Git-versions each destination dir (opt-out). | --agents --dry-run --scope --project --no-git-backup |
revert <agent> | Roll a destination dir back to a prior apply checkpoint (local-only git history). | --agents --to --all --dry-run |
status | Summarize drift / pending across agents (skill dirs collapse by default; converged displays as clean, --json keeps them distinct). --legend prints the nine-class glossary and exits. --exit-code → exit 2 on any drift, 0 when clean (CI gate). | --agents --verbose --legend --json --exit-code --scope --project |
diff [<path>] | Show pending / drift changes; secrets redacted. --exit-code → exit 2 on any hunk, 0 when clean. | --agents --json --exit-code --scope --project |
reconcile | Interactively merge drift back into source. | --agents --auto-writeback --auto-override --auto-safe --scope --project |
import <agent>[:<component>[:<name>]] | Capture native config into source. --scope project captures the agent’s native project-scope config into the project tree. | --dry-run --scope --project |
plugin explain [<plugin>...] | Show per-agent translation coverage for one or more plugins. | --all --json |
explain <path>[#<pointer>] | Show what produced a destination file (or one merged key). Metadata only — never file content. | --pointer --scope --project --json |
version | Print version information (alias for --version). |
Setup & diagnostics
Section titled “Setup & diagnostics”init [<git-url>]
Section titled “init [<git-url>]”Creates the ~/.agentsync/ directory and its layout (user scope). Pass a git URL
to clone an existing bootstrap config instead of starting empty (user scope only).
--scope project scaffolds a project source tree at <cwd>/.agentsync/
instead — the same on-disk layout as the user tree (agentsync.toml plus mcp/,
lsp/, skills/, subagents/, commands/, hooks/, memory/ with fragments/,
plugins/, secrets/), but no .state/ (apply records state centrally under
~/.agentsync/.state/, keyed by project root). --project <path> targets
<path>/.agentsync/ and implies project scope; --scope user together with
--project is an error. Commit the .agentsync/ tree to share project agent
config with collaborators. Full guide: Project-local config.
migrate subagents
Section titled “migrate subagents”A one-shot migration for a retired canonical layout. agentsync used to spell the
canonical subagent directory agents/, one directory away from the [agents]
harness registry in agentsync.toml — the same word naming two different types.
The subagent side moved to subagents/; the harness side did not ([agents],
--agents, and the agent command group are unchanged).
Until a tree is migrated, every command that loads the source refuses with a
distinguishable error naming this command, and doctor reports it as a failing
check. That refusal is deliberate: an unmigrated tree loads with zero
subagents, and one apply would then delete every subagent it had already
rendered.
agentsync migrate subagents # the user treeagentsync migrate subagents --project ~/code/x # a project's committed treeInteractively, the refusal offers to run the move for you. The move never
overwrites: if a file exists under both agents/ and subagents/, it refuses
with the colliding names and moves nothing. After a user-scope move, commit the
rename — ~/.agentsync is usually a dotfiles repo.
doctor
Section titled “doctor”A health check: verifies your PATH, that home and state directories are
writable, that the config schema parses, and that the secrets backend is reachable.
It also lists plugins installed natively in an agent (Claude and Codex) that aren’t in your
source yet — run import <agent>:plugin to capture them. A Destination git
backup section reports the configured [destination_directory_git_backup] mode
(prompt/on/off) and, per versioned destination dir on disk, whether it is
agentsync-versioned, under foreign source control, or untracked. An untracked
candidate dir that contains a nested git repo below it is flagged as a warning,
since apply refuses to init a repo inside a repo and skips git backup there.
Run it first when something feels off.
Validates your config — the sibling of doctor, which validates your
machine. Reach for doctor when something is set up wrong, and check when
something is written wrong. (This command was named verify before v0.11.0;
folding it into doctor --config was considered and declined, because check is
scope-aware and doctor is not.)
It schema-lints the canonical source and surfaces every unresolved
${secret:…} / ${env:…} reference. Ideal for CI — see
Verify config in CI. Pair with
AGENTSYNC_ALLOW_OFFLINE_VERIFY=1 on runners without an age key: offline mode
validates reference shape (a malformed ${secret:} still fails) but skips
resolution, so a well-formed-but-unresolvable name only surfaces on a run without
the flag.
Defaults to user scope. --scope project (walks up from cwd to the
.agentsync/ tree) or --project <path> schema-lints the project source
tree and validates its references — resolving ${secret:…} against the
inherited user secrets backend, exactly as apply does, so the two never
disagree. With no scope flag inside a project tree the choice is ambiguous:
verify prompts project-vs-user, or fails closed under --no-input / a non-TTY
stdin.
Building your config
Section titled “Building your config”Manage which agents agentsync applies to.
agentsync agent add claude # registeragentsync agent list # registry + enabled stateagentsync agent list --all # every supported agent (registered or not)agentsync agent disable opencode # stop applying (keeps source)agentsync agent disable opencode --purge # also remove what it wroteEvery subcommand also takes --scope project / --project <path> to manage the
project’s own [agents] declaration in <root>/.agentsync/agentsync.toml —
the set project scope renders to (user-scope agents are never inherited; see
Project-local config):
agentsync agent add claude --scope project # declare a project agentagentsync agent list --project ~/code/myrepo # read a project's declarationagentsync agent disable claude --purge --scope project # purge only this project's rendered filesManage MCP servers. Full guide: MCP servers.
agentsync mcp add github --command npx --args "-y,@modelcontextprotocol/server-github"agentsync mcp add linear --type http --url https://mcp.linear.app/sse \ --header "Authorization: Bearer ${secret:LINEAR_TOKEN}"agentsync mcp listagentsync mcp enable githubagentsync mcp disable githubagentsync mcp remove githubenable / disable flip the server’s enabled bit: the definition stays in
the canonical source, but a disabled server stops rendering to every agent (the
next apply removes what it had written). That is the difference from remove,
which deletes mcp/<id>.toml outright. The enabled field and the loader that
reads it both predate these verbs — until #200 F4 the only way to flip it was to
hand-edit the TOML.
Flags: --type (stdio|http|sse), --command, --args, --url, --env,
--agents, --header. --header "Name: Value" is repeatable and http/sse
only (rejected for stdio); it sets request headers, the usual remote-auth
secret site. A header value may reference a secret
(--header "Authorization: Bearer ${secret:TOKEN}") — headers is an existing
secret-resolving field, so the reference resolves at apply and re-references on
capture, never landing in the canonical source as cleartext.
marketplace & plugin
Section titled “marketplace & plugin”Manage plugin registries and the plugins drawn from them. Full guide: Marketplaces & plugins.
agentsync marketplace add github:anthropics/claude-plugins-officialagentsync plugin add atlassian@anthropicagentsync plugin listsecret
Section titled “secret”Manage the age-encrypted vault. Full guide: Secrets.
agentsync secret set github.token --stdinagentsync secret get github.tokenagentsync secret listagentsync secret remove github.tokenagentsync secret editsecret set refuses an empty (or whitespace-only) value by default; pass
--allow-empty to store one deliberately.
secret list prints keys only, never values, which is what lets it exist
alongside secret edit — edit decrypts the whole vault into $EDITOR, and
before list/remove it was the only way to see or delete a key.
secret get writes the raw value to stdout for piping. When stdout is a
terminal it prints a caution first, since a bare secret get puts a live
credential in your scrollback.
The apply / capture loop
Section titled “The apply / capture loop”Renders the source and writes each agent’s native config. Offline — it never
touches the network. --dry-run runs the full pipeline and prints the plan and
translation report without writing a byte; each planned destination is labeled
✓ synced (already holds our exact bytes) or → write (would be created or
changed), with a — N to write, M already synced tally, so an in-sync tree reads
as a no-op rather than a list of pending writes.
agentsync apply --dry-runagentsync applyDestination git backup. After a successful user-scope apply that changes
managed files, apply keeps each agent’s destination dir (~/.claude, ~/.codex,
…) in its own local-only git repo and records a checkpoint commit, so a bad
apply is one agentsync revert away — including the first apply: before the
first apply overwrites a dir, agentsync records a pre-apply baseline commit of the
prior content of the files it manages, so the apply checkpoint’s parent is the genuine
pre-apply state and revert right after a first apply restores it. Pre-existing files
agentsync did not write (credentials, transcripts, your own files) are left out of the
history — untracked, so a revert leaves them untouched. They are preserved, not
versioned: a revert never deletes them, but the history cannot restore one you
delete. The first apply to an untracked
dir prompts to enable this (opt-out); answer once and it’s remembered in
[destination_directory_git_backup]. These repos are never pushed (the history
may hold cleartext secrets, like the files it versions — which is why it stays
local). A dir already under your own source control is left untouched. Pass
--no-git-backup to skip it for one run (CI/scripting) without touching config.
The [destination_directory_git_backup] table (mode = "prompt" | "on" | "off",
optional author_name/author_email) controls the default; agentsync doctor
shows the current mode and per-dir status. mode is validated at load — it must
be exactly prompt, on, or off (case-sensitive; omit it for the prompt
default). Any other value now errors at load with a path-prefixed message,
rather than being silently ignored. See agentsync revert below.
revert <agent>
Section titled “revert <agent>”Rolls an agent’s destination dir back to a prior apply checkpoint from its
local-only git history — recovering from a bad apply. It is append-only: it
records a new commit, so the history is never rewritten and the revert is itself
revertible. Untracked files you dropped into the dir (and gitignored files) are
left untouched — revert only rewinds the files agentsync versions, so your own
scratch files are never deleted. By default it undoes the most recent apply
(restores the previous checkpoint); --to <ref> picks a specific one (a commit
hash or HEAD~2) and must name one of the dir’s own checkpoints — the current
one or an ancestor; any other resolvable commit is refused. --all reverts
every managed dir, and --dry-run previews the
file changes without writing (noting when untracked files are present). Because the
first apply records a pre-apply baseline (see apply above), even reverting a
brand-new dir’s first apply works — it rolls back to that baseline rather than
refusing with a “only one checkpoint” message.
revert moves only the destination. The next apply re-renders from the
canonical source and would overwrite the reverted state, so revert prints a notice
telling you to reconcile (agentsync reconcile / import) or fix the canonical
source first. Only agentsync-managed (git-backed) user-scope dirs can be reverted.
agentsync revert claude # undo the most recent apply to ~/.claudeagentsync revert claude --to HEAD~3 # roll back furtheragentsync revert --all --dry-run # preview reverting every managed dirFull guide: Rolling back a bad apply.
status
Section titled “status”Summarizes what’s out of sync across all agents — pending source changes and
destination drift. To stay readable, each skill directory collapses to one
row by default (the skill dir, its most-severe drift class, and a faint
SKILL.md + N files count — with a per-class breakdown when the bundled files
don’t all share one class), so a skill shipping hundreds of assets no longer
floods the report. Pass -v/--verbose to expand every skill back to one row
per bundled file. A one-line summary footer tallies items by drift class, and
a brief “What apply will do:” legend follows it, explaining each class
present (new will be created, pending will be updated to match source,
drift will be overwritten — use reconcile to keep the edit, etc.). Clean
items are tallied but never glossed.
Use --agents <list> (a comma-separated allowlist, matching mcp add --agents;
* = all enabled) to scope the report — agentsync status --agents claude shows
just Claude Code. Orphaned-state warnings still consider the full enabled set, so
narrowing the view never mislabels a deselected agent as an orphan.
Pass --json to emit the structured drift report (per-agent items + a summary
tally keyed by drift class) for CI gates or dashboards; advisory diagnostics
still flow to stderr so the JSON payload stays clean. --json is never
collapsed — it carries every tracked file regardless of the human view — and
omits the legend (the class field is the machine contract).
The formatted report shows a converged item as clean. The two are distinct
in the internal drift classifier — converged means the source and the
destination changed independently but landed on the same value, versus clean
where neither changed — but both mean apply has nothing to do, so the
dashboard folds them into one word and one tally rather than surfacing a
distinction only the classifier cares about. --json keeps the real
classification, since a script may want it.
agentsync status --legend prints a standalone glossary explaining all nine
drift classification statuses — including clean and converged spelled out
separately, since that’s exactly the distinction the formatted view folds
away — and exits without running the drift scan at all. It rejects being
combined with --json, --exit-code, or --agents (all of which it would
otherwise silently ignore) rather than accept-and-ignore them. A run whose
summary has anything to explain ends with a one-line hint pointing at
--legend; it’s suppressed whenever there’s nothing tracked to explain —
the “no agents enabled” short-circuit, or an enabled agent that renders
nothing yet (“(no tracked items)”).
For a gate that should fail the build on drift, add --exit-code:
agentsync status --exit-code exits 2 when any item is not clean/converged and
0 when the tree is fully in sync (exit 2 is distinct from the generic error
exit 1, and the report itself is emitted first with no extra error line).
Without the flag, status always exits 0. Interactive prompts such as the
scope menu are written to stderr, so a --json payload piped from stdout is
never corrupted.
diff [<path>]
Section titled “diff [<path>]”Shows the pending / drift changes in detail. Resolved secrets are redacted, so
a piped diff can’t leak credentials. Pass a filesystem path to scope the diff;
a path that no enabled agent renders (a typo, or an unmanaged file) is reported
distinctly — path <p> is not managed by agentsync … — rather than the ambiguous
no diff. --agents <list> narrows the diff to an agent allowlist exactly like
status --agents (same validation and empty-rejection). Color is TTY-gated —
piped output uses [-…-] / {+…+} text markers instead of raw ANSI. Pass
--json for a structured hunk list (the same masked source/dest strings, so the
redaction protects both modes), and --exit-code to exit 2 when any hunk exists
(0 when clean) for a CI gate.
reconcile
Section titled “reconcile”The interactive merge UX for drift and conflicts. Full guide:
The daily loop. Non-interactive flags: --auto-writeback,
--auto-override, --auto-safe.
import <agent>[:<component>[:<name>]]
Section titled “import <agent>[:<component>[:<name>]]”Captures native config back into your canonical source. Drop parts of the selector
to widen scope. The plugin component (Claude and Codex) captures installed
plugins + their marketplaces by re-fetching them into the agentsync cache, so a
real plugin import uses the network. A plugin’s marketplace is resolved from
agentsync’s own registered marketplaces first, then the agent’s native config; a
plugin whose marketplace is registered in neither (e.g. a built-in like
claude-plugins-official you have not yet marketplace added) is reported and
skipped.
--scope project (optionally --project <path>) reads the agent’s native
project-scope config (e.g. <root>/.claude/) and captures it into the project
source tree <root>/.agentsync/ instead of your user tree, seeding central state
with the project scope + root so the next apply doesn’t foreign-collide. Plugin
import is user-scope only: a named import claude:plugin:<name> --scope project errors, and a bulk import claude:plugin --scope project silently skips
(plugins are a user-scope concept across the harnesses). Full guide:
Already have configs?.
plugin outdated
Section titled “plugin outdated”(network, writes state) Re-fetches every registered marketplace into the
local cache, recomputes version pins, and prints the pending bumps — without
touching any agent config. Despite the npm outdated prior it is not a pure
read: it uses the network and it records each marketplace’s fetch timestamp +
head SHA in .state/targets.json. It also re-checks every installed plugin’s
manifest SHA and warns on a same-version re-upload. Full guide:
Updating from the network.
plugin upgrade [<id>] --all --lossless
Section titled “plugin upgrade [<id>] --all --lossless”(network) Re-fetches a plugin, updates its recorded version + manifest sha,
and re-applies so the upgrade reaches your agents in the same command.
--all first polls every marketplace (like plugin outdated), then upgrades
every plugin with a pending bump and re-applies once. --lossless only upgrades
when the candidate version introduces no new translation loss (an adapter
skip) for an enabled agent — an excluded upgrade is reported, never silently
dropped. --scope/--project resolve exactly as apply does.
The check renders every enabled agent and does not honour a plugin’s
agents / native_agents, so it can decline over a loss falling on an agent
the plugin is never projected to. A refusal on that basis says so and points at
agentsync plugin explain <id>; if the affected agent is not listed there, the
upgrade is safe for you and you can re-run without the flag. (Under --all that
note prints once for the run, not once per bump.) A bump that could not be
evaluated is excluded too, but reported separately and without that note —
such a refusal is not about targeting, and dropping the flag is not the answer
to it.
agentsync plugin outdatedagentsync plugin upgrade --all --losslessagentsync plugin upgrade atlassianplugin explain [<plugin>...]
Section titled “plugin explain [<plugin>...]”Prints per-agent translation coverage for one or more installed plugins, without
applying. Pass space-separated plugin ids to explain just those, or --all to
explain every installed plugin (agentsync plugin list prints the ids). A
◐ partial row’s trailing note is
split by kind — (N reduced · M dropped) — so it never reads as “N whole
components discarded”: a reduced part still rendered, just without some fields
the agent has no home for, while a dropped part had no native target and was
not emitted. Each part is itemized beneath a framing header, tagged reduced or
dropped with its reason, so the tally is never a dead end. Add --json for
machine-readable output (rows for plugin/agent coverage — each with a
skipDetails array of {component, name, reason, kind}, where kind is
"reduced" or "dropped" and component is the plain kind).
A row’s coverage is one of "full", "partial", "none" — how much of what
the plugin ships this agent got — or one of three values meaning it contributed
nothing here by CONFIGURATION rather than by failed translation: "disabled"
(the plugin is off for this scope, paired with disabled: true),
"not-targeted" (its agents allowlist excludes this agent) or "native"
(its native_agents list defers to this agent’s own plugin manager). The last
two are paired with notTargeted: true; branch on the booleans rather than the
strings.
agentsync plugin explain atlassian@anthropicagentsync plugin explain atlassian@anthropic superpowers@obraagentsync plugin explain --allexplain <path>[#<pointer>]
Section titled “explain <path>[#<pointer>]”Answers “why does this file look like this?” for a destination an agent reads.
diff tells you what changed; explain tells you where it came from — which is
exactly what you need on a converged file, where diff is empty.
agentsync explain ~/.claude/agents/reviewer.mdagentsync explain ~/.claude.json#/mcpServers/githubagentsync explain ~/.claude.json --pointer /mcpServers/githubagentsync explain ~/.claude.json --jsonPer item it reports the source of record (mcp/github.toml,
subagents/reviewer.md, memory/AGENTS.md plus its fragments), the
plugin origin when the component came from a projection, the adapter
transform (what was reduced or dropped en route), the ownership
(managed / untracked / foreign), the drift class in the classifier’s
own vocabulary, and which ${secret:…} / ${env:…} references resolved there.
The argument splits on the last #; --pointer is the unambiguous escape
hatch and wins over an in-argument #. Paths are symlink-resolved before
matching, project scope is inferred when the path lies inside a project tree,
and an unmanaged or typo’d path is reported distinctly (with the nearest managed
path suggested) — mirroring diff [<path>].
One path can have several owners (the breadth tier shares ~/.agents/skills/),
so output is grouped per owning agent, and two agents rendering different
content to one path is surfaced as its own answer rather than an error.
For per-plugin translation coverage, see plugin explain.
Flags that work across commands
Section titled “Flags that work across commands”| Flag | Applies to | Effect |
|---|---|---|
--scope user|project | root flag — every scope-aware command (init, check, import, apply, status, diff, reconcile, explain, migrate, agent, mcp, plugin upgrade, the component lists) | Force the config scope. Commands that act on per-machine state (doctor, revert, version, the plugin / marketplace / secret groups) refuse it with a reason instead of ignoring it. |
--project <path> | root flag — same set as --scope | Target a specific project’s tree (implies --scope project). |
--dry-run | apply, import | Preview without writing. |
--auto-safe | reconcile | Auto-resolve only no-risk drift classes. (The plugin-side flag of the same name became plugin upgrade --lossless — they meant two unrelated things.) |
ls / rm | every list / remove subcommand | Aliases. |
--json | status, diff, explain, plugin explain | Emit the structured payload to stdout (advisory diagnostics still go to stderr). plugin explain --json emits the translation rows; explain <path> --json emits the provenance envelope (and still emits it, with "unmanaged": true, on a path nothing renders). |
--agents <list> | apply, status, diff, reconcile, revert | The single “which agents” selector, with identical parsing everywhere (* = all enabled; an empty or unknown value is rejected identically by all five). On revert it is a spelling of the positional form — --agents, a positional agent, and --all stay mutually exclusive. |
--exit-code | status, diff | Exit 2 when drift (status) / any hunk (diff) exists, 0 when clean — for CI gates. Distinct from the generic error exit 1; prints no extra error line. Default (unset) always exits 0. |
--legend | status | Print a standalone glossary of all nine drift classification statuses (including clean/converged spelled out separately) and exit, skipping the drift scan. Cannot be combined with --json, --exit-code, or --agents — each would otherwise be silently ignored, so it errors instead. A status run whose summary has anything to explain hints at this flag. |
--header "Name: Value" | mcp add | Repeatable; http/sse only. Sets a request header (secret refs allowed). |
--color auto|always|never | all | Control ANSI color + bold. Default auto (on for a TTY, off when piped/redirected; honors NO_COLOR). |
-v, --verbose | all | Verbose logging. In status, also expands each collapsed skill directory back to one row per bundled file. |
--version | root | Print version information and exit. The agentsync version subcommand is an alias. |
How output is formatted
Section titled “How output is formatted”Every command splits what it prints into two kinds, and the difference is visible at a glance.
Diagnostics — anything that is a notice about the run — carry a level label and go to stderr:
✗ ERROR render codex: codex subagents feature-dev/code-reviewer and pr-review-toolkit/code-reviewer both resolve to the agent name "code-reviewer"; rename one or set distinct frontmatter names⚠ WARN plugin component frontmatter is not strict YAML; parsed leniently (consider quoting values containing ': ') path=~/.agentsync/.state/cache/plugins/pr-review-toolkit/agents/silent-failure-hunter.mdℹ INFO scope: project (~/dev/app)A fourth level, • DEBUG, exists in the vocabulary and is what --verbose unlocks,
but nothing in agentsync currently logs at that level — so you will not see one
today. It is the level a future library-side diagnostic would use.
The label is a glyph plus the level word, padded so message text always starts
at the same column; wrapped detail lines hang under it. When color is on the
label is bold red, bold yellow, or bold cyan; DEBUG is faint rather than bold. With color off — piped, redirected,
NO_COLOR, or --color never — the glyph and the word carry the whole signal,
so the level survives into a log file or a CI transcript.
This applies to the last line a failing run prints too: the terminal error is
an ✗ ERROR diagnostic like any other, not a bare agentsync: … line.
Results — what the command was asked to produce — go to stdout and carry
no level label. A --json payload, a status table, a diff, and a list are
all results, which is why a warning never lands in the middle of one. A success
outcome leads with an emoji chosen by what happened:
🎉 applied: 12 ops✅ added agent: claude🧹 removed mcp server: github📥 imported 6 items from claude🔙 reverted ~/.claude to checkpoint a1b2c3d✨ agentsync home initialized at ~/.agentsyncSuccess lines deliberately carry no INFO: labeling an outcome would dilute the
labels that mean something.