Skip to main content

heddle_cli_args/cli/cli_args/
commands_main.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Top-level CLI commands.
3
4use clap::{Args, Subcommand};
5
6#[cfg(feature = "git-overlay")]
7use super::BridgeCommands;
8#[cfg(feature = "semantic")]
9use super::SemanticCommands;
10use super::{
11    AgentCommands, BlameArgs, CompletionSubject, ContextCommands, DiscussArgs, EnvCommands,
12    HookCommands, ImportArgs, IntegrationCommands, OplogCommands, QueryArgs, RedactCommands,
13    RemoteCommands, ReviewCommands, ShellCommands, ThreadCommands, VisibilityCommands,
14    commands_args::{
15        CloneArgs, DiffArgs, DoctorArgs, IMPORT_VERB, INIT_VERB, InitArgs, LandArgs, LogArgs,
16        PullArgs, PushArgs, ReadyArgs, ResolveArgs, RevertArgs, SnapshotArgs, SyncArgs,
17        ThreadStartArgs, UndoArgs, WatchArgs,
18    },
19};
20#[cfg(feature = "client")]
21use super::{AuthCommands, AuthInviteCommands, ClaimArgs, GrantCommands, PromoteArgs};
22
23#[derive(Clone, Debug, Args)]
24pub struct FsckArgs {
25    /// Full check (includes content verification).
26    #[arg(long)]
27    pub full: bool,
28
29    /// Run slower graph and signature integrity checks.
30    #[arg(long)]
31    pub thorough: bool,
32
33    /// Verify offline authorship identity and review-signature chains.
34    #[arg(long, requires = "thorough")]
35    pub provenance: bool,
36
37    /// Include Git projection, mapping, notes, and checkout checks.
38    #[arg(long)]
39    pub git: bool,
40
41    #[command(subcommand)]
42    pub command: Option<FsckCommands>,
43}
44
45#[derive(Clone, Debug, Subcommand)]
46pub enum FsckCommands {
47    /// Repair an integrity surface, then verify it.
48    Repair {
49        #[command(subcommand)]
50        target: FsckRepairCommands,
51    },
52}
53
54#[derive(Clone, Debug, Subcommand)]
55pub enum FsckRepairCommands {
56    /// Reconcile Git projection metadata or one projected ref.
57    Git(FsckRepairGitArgs),
58}
59
60#[derive(Clone, Debug, Args)]
61pub struct FsckRepairGitArgs {
62    /// Git ref to reconcile. Required for native repositories.
63    #[arg(long = "ref", value_name = "BRANCH")]
64    pub ref_name: Option<String>,
65
66    /// Assert the intended authority direction.
67    #[arg(long, value_parser = ["git", "heddle"])]
68    pub prefer: Option<String>,
69
70    #[command(flatten)]
71    pub dry_run: super::DryRunArgs,
72}
73
74#[derive(Subcommand)]
75pub enum Commands {
76    /// Initialize Heddle in a directory or existing Git checkout.
77    #[command(name = INIT_VERB)]
78    Init(InitArgs),
79
80    /// Bring an existing Git repository into Heddle.
81    #[command(name = IMPORT_VERB, verbatim_doc_comment)]
82    Import(ImportArgs),
83
84    /// Curated, progressive-disclosure help.
85    ///
86    /// `heddle help` prints the task map. `heddle help --all` prints the
87    /// full command tree. `heddle help <topic>` prints the topic page
88    /// (e.g. `model`, `advanced`, `git-concepts`). `heddle help
89    /// <command path>` falls through to that command's `--help`.
90    Help {
91        /// Print the full command tree instead of the task map.
92        #[arg(long)]
93        all: bool,
94        /// Topic name (`model`, `advanced`, `git-concepts`, …) or command
95        /// path. When omitted, prints the curated default.
96        #[arg(value_name = "TOPIC_OR_COMMAND")]
97        topics: Vec<String>,
98    },
99
100    /// Show what needs attention and the next safe Heddle action.
101    #[command(after_help = "\
102Examples:
103  heddle status               # current thread, dirty paths, recommended next step
104  heddle status --short       # one-line summary for shell prompts
105  heddle status --watch       # live dashboard that refreshes in place
106")]
107    Status {
108        /// Short format.
109        #[arg(short, long)]
110        short: bool,
111
112        /// Continuously refresh status.
113        #[arg(long)]
114        watch: bool,
115
116        /// Internal helper for tests: stop after N watch updates.
117        #[arg(long, hide = true)]
118        watch_iterations: Option<usize>,
119
120        /// Internal helper for tests: polling interval in milliseconds.
121        #[arg(long, hide = true)]
122        watch_interval_ms: Option<u64>,
123    },
124
125    /// Stream live oplog activity.
126    ///
127    /// Tails the repository's append-only oplog file like `tail -f`,
128    /// emitting snapshots, merges, and thread events as they happen.
129    /// Exits on Ctrl-C.
130    Watch(WatchArgs),
131
132    /// Verify this workspace; exits nonzero until every check is clean.
133    #[command(after_help = "\
134Checks: Git mapping, worktree, remote, operation, clone verification, machine contract.
135
136Examples:
137  heddle verify                # strict verification gate and next recovery step
138  heddle verify --verbose      # full proof rows and machine-contract details
139  heddle verify --output json  # proof JSON when clean; error envelope when blocked
140")]
141    Verify {
142        /// Verify each state's offline authorship and review-signature chain.
143        #[arg(long)]
144        provenance: bool,
145    },
146
147    /// Explain repository health, or run targeted doctor checks.
148    ///
149    /// `heddle doctor` (no subcommand) reports repository health and
150    /// the next recovery step. `heddle doctor docs` diff-checks markdown
151    /// documentation against
152    /// the actual CLI surface and exits non-zero on drift — wire it
153    /// into CI to stop docs from going stale.
154    Doctor(DoctorArgs),
155
156    /// Create or resume an isolated thread for focused work.
157    Start(ThreadStartArgs),
158
159    /// Run Heddle CI checks. Finds `ci.ts` / `ci.rs` / `ci.go`, compiles if needed, then runs.
160    #[cfg(feature = "ci")]
161    Ci {
162        #[command(subcommand)]
163        command: super::CiCommands,
164    },
165
166    /// Automation/workflow command: refresh the current thread onto its target when safe.
167    Sync(SyncArgs),
168
169    /// Continue the active operation without remembering the specific subcommand.
170    Continue,
171
172    /// Abort the active operation without remembering the specific subcommand.
173    Abort,
174
175    /// Integrate into the local target thread; push to publish.
176    ///
177    /// `land` is the local integration verb: capture outstanding work if needed,
178    /// refresh against the target when safe, and land the thread. It fails
179    /// closed when conflicts or other blockers exist. Pair it with `ready`
180    /// when you want the verdict and next action before landing anything.
181    Land(LandArgs),
182
183    /// Check this checkout before local integration.
184    ///
185    /// `ready` captures outstanding work if needed, checks conflicts,
186    /// blockers, freshness, and semantic risk, then marks the thread
187    /// ready or blocked and prints the next action. It never lands,
188    /// checkpoints, or pushes; use it when you want Heddle's verdict
189    /// before integrating the work.
190    Ready(ReadyArgs),
191
192    /// Capture a recoverable Heddle step for undo, provenance, and review.
193    Capture(SnapshotArgs),
194
195    /// Show state history.
196    ///
197    /// By default, when a thread name is given (e.g. `heddle log master`),
198    /// the walk is *first-parent only* — equivalent to `git log
199    /// --first-parent <branch>`. To see every ancestor reachable through
200    /// merge commits, pass `--graph` (which renders the full DAG) or
201    /// `--all` (which lists every state regardless of ancestry).
202    #[command(visible_alias = "history")]
203    Log(LogArgs),
204
205    /// Show state details.
206    Show {
207        /// State by physical state ID, logical change ID, or unambiguous prefix.
208        /// Defaults to HEAD.
209        state: Option<String>,
210    },
211
212    /// Show what changed in the worktree, a thread, or two states.
213    Diff(DiffArgs),
214
215    /// Show line-by-line attribution for a tracked file.
216    ///
217    /// Names the state that last changed each line, with the same
218    /// structured principal / agent shape as `log` and `show`.
219    /// `heddle query --attribution <path>` remains as the equivalent
220    /// query form.
221    #[command(after_help = "\
222Examples:
223  heddle blame src/auth.rs
224  heddle blame src/auth.rs --state HEAD
225  heddle blame src/auth.rs --context
226  heddle blame src/auth.rs --output json
227")]
228    Blame(BlameArgs),
229
230    /// Open or resolve discussions anchored to code.
231    ///
232    /// Subcommands: `new`, `reply`, `resolve`, `reopen`, `list`, `show`,
233    /// `wait`. `--path` / `--symbol` / `--line` are the code anchor;
234    /// `--state` is the historical revision; `--body` / `--file` is the
235    /// markdown body.
236    ///
237    /// Native Heddle only. Discussions live in `.heddle` and travel
238    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
239    /// not projected into Git, so `git clone` does not carry them; in
240    /// Git Overlay mode they are local to that working copy.
241    #[command(after_help = "\
242Scope:
243  Native Heddle only. Discussions are stored in `.heddle` and move over
244  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
245  Git Overlay repository arrives with no discussions and no Heddle store.
246
247Examples:
248  heddle discuss new --path src/lib.rs --symbol greet --body \"why greet?\"
249  heddle discuss new --path src/lib.rs --file why.md
250  heddle discuss reply disc-01a0afc6 --body \"second thought\"
251  heddle discuss reply disc-01a0afc6 --turn 2 --body \"reply to that turn\"
252  heddle discuss resolve <id> --mode by-edit --state HEAD
253")]
254    Discuss(DiscussArgs),
255
256    /// Structured query over the operation log. Filter by
257    /// actor, time window, signal kind, symbol, thread, verbs. Returns
258    /// structured results consumable by agents.
259    Query(QueryArgs),
260
261    /// Review and approve an exact hosted Thread comparison.
262    ///
263    /// `show`, `approve`, and `list` default to the current Thread.
264    /// `readiness` checks the exact source and target comparison before
265    /// hosted landing. Remote selection is explicit, then the configured
266    /// default, and fails when neither is available.
267    #[command(after_help = "\
268Examples:
269  heddle review show feature
270  heddle review approve feature -m \"Looks good\"
271  heddle review list feature
272  heddle review revoke <review-id> --thread feature
273  heddle review readiness feature --into main
274")]
275    Review {
276        #[command(subcommand)]
277        command: ReviewCommands,
278    },
279
280    /// Redact a sensitive blob in a state so reads return a stub
281    /// instead of the content.
282    ///
283    /// `heddle redact apply` declares a redaction; the blob bytes stay
284    /// on disk and reads return the operator-supplied stub. `heddle
285    /// redact purge` afterward physically removes the bytes. Both are signed,
286    /// attributed, oplog-audited operations. See
287    /// `docs/PRINCIPLES.md` (the honesty principle) for context.
288    ///
289    /// Redaction is path/blob hide inside history. It is not `heddle env`
290    /// (runtime secrets; literal `.env` capture is reserved, exit 65) and
291    /// not `heddle visibility` (per-state audience, downward-closed).
292    Redact {
293        #[command(subcommand)]
294        command: RedactCommands,
295    },
296
297    /// Declare and inspect a state's audience visibility tier.
298    ///
299    /// `heddle visibility set` binds a tier to a state; `promote` lifts it to
300    /// a less-restrictive tier via a superseding record; `show` reports the
301    /// effective declared tier; `list` enumerates non-public sidecar records
302    /// in this store. Capture binds the inherited
303    /// `[review.discussion] default_visibility` automatically (Invariant A)
304    /// — these verbs are the explicit operator overrides.
305    ///
306    /// Private is per-state and downward-closed: a public descendant that
307    /// still names private-ancestor blobs is withheld from lesser audiences.
308    /// It does not hide one path inside a later public tip. Runtime secrets
309    /// belong in `heddle env` (literal `.env` capture is reserved, exit 65);
310    /// path-level hide of bytes already in history is `heddle redact`.
311    /// See `heddle help visibility`.
312    Visibility {
313        #[command(subcommand)]
314        command: VisibilityCommands,
315    },
316
317    /// Run a child with a confidential runtime profile.
318    ///
319    /// `heddle env run --profile <name> -- <cmd>` asks the local policy
320    /// broker to unwrap named slots and injects them into the child
321    /// environment only. Values never land in the worktree, the store, or
322    /// command JSON. Same-UID callers are cooperative; OS isolation is later.
323    ///
324    /// Literal `.env` / `.env.local` files are reserved (capture exits 65).
325    /// `heddle visibility` does not replace this for secrets beside a public
326    /// tip; `heddle redact` stubs a blob already in history.
327    #[command(after_help = "\
328Examples:
329  heddle env list
330  heddle env create --name local --from-env DATABASE_URL
331  heddle env run --profile local -- printenv DATABASE_URL
332
333Literal `.env` capture is reserved (exit 65). Use this verb for runtime
334secrets. `heddle visibility` embargoes a state and its descendants;
335`heddle redact` hides a blob already in history. See `heddle help visibility`.
336")]
337    Env {
338        #[command(subcommand)]
339        command: EnvCommands,
340    },
341
342    /// Revert changes from a state.
343    Revert(RevertArgs),
344
345    /// Undo the last Heddle operation.
346    Undo(UndoArgs),
347
348    /// Manage threads.
349    Thread {
350        #[command(subcommand)]
351        command: ThreadCommands,
352    },
353
354    /// Shell integration helpers (auto-cd on thread start/switch/cd).
355    Shell {
356        #[command(subcommand)]
357        command: ShellCommands,
358    },
359
360    /// Print a tab-completion script for bash, zsh, or fish.
361    ///
362    /// With no shell, prints install lines. With `bash`, `zsh`, or `fish`,
363    /// emits the same script as `heddle shell completion`.
364    Completions {
365        /// Shell to generate completion for: bash, zsh, or fish.
366        #[arg(value_name = "SHELL")]
367        shell: Option<String>,
368    },
369
370    /// Internal shell-completion candidate helper.
371    #[command(name = "complete", alias = "__complete", hide = true)]
372    Complete {
373        /// Candidate set to print, one candidate per line.
374        #[arg(value_enum)]
375        subject: CompletionSubject,
376    },
377
378    /// Resolve merge conflicts.
379    Resolve(ResolveArgs),
380
381    /// Explicit interoperability with other version-control formats.
382    #[cfg(feature = "git-overlay")]
383    Bridge {
384        #[command(subcommand)]
385        command: BridgeCommands,
386    },
387
388    /// Push the source-authoritative history to a remote.
389    Push(PushArgs),
390
391    /// Pull source-authoritative history from a remote.
392    Pull(PullArgs),
393
394    /// Manage remote repositories.
395    Remote {
396        #[command(subcommand)]
397        command: RemoteCommands,
398    },
399
400    /// Authenticate with a Heddle server.
401    #[cfg(feature = "client")]
402    Auth {
403        #[command(subcommand)]
404        command: AuthCommands,
405    },
406
407    /// Create or list signup invites (thin alias of `auth invite`).
408    ///
409    /// Signup-only: this mints an account-creation code. It does not grant
410    /// another principal access to a spool. Use `heddle grant` to add a
411    /// collaborator.
412    #[cfg(feature = "client")]
413    #[command(args_conflicts_with_subcommands = true)]
414    #[command(after_help = "\
415Signup-only. `heddle invite` is the same as `heddle auth invite`.
416It does not grant spool access. Add a collaborator with:
417
418  heddle grant create --spool <path|url> --principal <handle> --role writer
419")]
420    Invite {
421        /// Bind the new invite to an email address.
422        #[arg(long)]
423        email: Option<String>,
424
425        /// Heddle server address. Omit to use the configured default
426        /// (`api.heddle.sh` when none is stored).
427        #[arg(long, global = true)]
428        server: Option<String>,
429
430        #[command(subcommand)]
431        command: Option<AuthInviteCommands>,
432    },
433
434    /// Grant a principal access to a hosted spool.
435    ///
436    /// Separate from `heddle auth invite`, which is signup-only. Create,
437    /// list, and delete collaborator grants on a spool you can administer.
438    /// Agents may grant writer or below; maintainer, admin, and owner stay human-verified.
439    #[cfg(feature = "client")]
440    Grant {
441        #[command(subcommand)]
442        command: GrantCommands,
443    },
444
445    /// Promote a personal hosted spool to a root-level spool.
446    ///
447    /// Moves `spool/<your-handle>/<name>` to `spool/<name>` after the server
448    /// confirms the root slug is free, the account is claimed/verified, and
449    /// you hold an owner grant. Clone a bare name still prefers your personal
450    /// copy first.
451    #[cfg(feature = "client")]
452    Promote(PromoteArgs),
453
454    /// Offer this agent account for a human to claim.
455    ///
456    /// Prints a short-lived bearer link, then keeps the agent's Iroh endpoint
457    /// online until the human finishes, the offer expires, or Ctrl-C stops it.
458    #[cfg(feature = "client")]
459    #[command(after_help = "\
460Examples:
461  heddle claim
462  heddle claim --timeout 30m
463  heddle claim --server weft.example --web-origin https://heddle.example
464")]
465    Claim(ClaimArgs),
466
467    /// Report the capture actor, then hosted auth.
468    ///
469    /// The capture actor is who the next capture is attributed to
470    /// (`user_config`, `init --principal-*`, or `HEDDLE_PRINCIPAL_*`).
471    /// Hosted auth is whether this machine has a server credential.
472    /// These are different objects. `heddle auth login` does not set the
473    /// local actor. `whoami` only reads; it never attaches a credential.
474    #[cfg(feature = "client")]
475    #[command(after_help = "\
476The capture actor and hosted auth are different objects:
477  capture actor  who the next capture is attributed to
478                 (user_config, init --principal-*, or HEDDLE_PRINCIPAL_*)
479  hosted auth    whether this machine has a credential for the server
480                 (heddle auth login). whoami never attaches a credential.
481
482When the server answers, whoami lists grant-reachable spools as
483spool/<handle>/<name>.
484
485Examples:
486  heddle whoami                       # capture actor first, then hosted auth
487  heddle whoami --output json         # machine-readable, stable output_kind shape
488  heddle whoami --server api.heddle.sh")]
489    Whoami {
490        /// Heddle server address (defaults to the configured server).
491        #[arg(long)]
492        server: Option<String>,
493    },
494
495    /// Manage code context annotations.
496    ///
497    /// Native Heddle only. Annotations live in `.heddle`, and travel
498    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
499    /// deliberately not projected into Git — not into `refs/notes/*`,
500    /// not into a tracked file — so `git push` and `git clone` do not
501    /// carry them. In Git Overlay mode annotations still work and are
502    /// still useful; they are simply local to that working copy.
503    #[command(after_help = "\
504Scope:
505  Native Heddle only. Annotations are stored in `.heddle` and move over
506  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
507  Git Overlay repository arrives with no annotations and no Heddle store.
508
509Examples:
510  heddle context set --path src/auth.rs --symbol verify --kind invariant -m 'returns false on timing mismatch'
511  heddle context get --path src/auth.rs --symbol verify
512  heddle context history --path src/auth.rs      # same --path as set, or pass the id
513  heddle context list --prefix src/auth          # everything attached under a path
514  heddle context check --path src/auth.rs        # surface annotations for editor tooling
515")]
516    Context {
517        #[command(subcommand)]
518        command: ContextCommands,
519    },
520
521    /// Manage ambient harness integrations.
522    Integration {
523        #[command(subcommand)]
524        command: IntegrationCommands,
525    },
526
527    /// Semantic analysis queries (call-graph hot-spots, churn,
528    /// signature-stability surfaces).
529    #[cfg(feature = "semantic")]
530    Semantic {
531        #[command(subcommand)]
532        command: SemanticCommands,
533    },
534
535    /// FUSE mount-daemon control plane — distinct from `agent`.
536    ///
537    /// `heddle daemon serve` runs a foreground mount daemon that
538    /// owns FUSE sessions for `--workspace virtualized --daemon`
539    /// threads. It is normally spawned on demand by the per-thread
540    /// CLI; running it interactively is for debugging.
541    /// `status` reports liveness/uptime/mount count without spawning;
542    /// `stop` asks a running daemon to drain mounts and exit.
543    Daemon {
544        #[command(subcommand)]
545        command: DaemonCommands,
546    },
547
548    /// Box-scoped network daemon control plane — distinct from
549    /// `daemon` (FUSE mounts).
550    ///
551    /// `heddle netd serve` runs a long-lived async daemon that binds
552    /// the machine's single persistent Iroh endpoint on the persisted
553    /// device node id and keeps it relay-reachable, so outstanding
554    /// claim links keep resolving across restarts. Hosted verbs
555    /// (`whoami`, `push`, `pull`, `clone`) reuse that endpoint's warm
556    /// weft session when netd is running. Unlike `daemon`, it is not
557    /// gated on Linux/FUSE and never idle-exits. `status` reports
558    /// liveness and the advertised node id; `stop` asks a running
559    /// daemon to close its endpoint and exit.
560    Netd {
561        #[command(subcommand)]
562        command: NetdCommands,
563    },
564
565    /// Agent reservation and one-shot orchestration API.
566    ///
567    /// `heddle agent reserve|capture|ready|release|list|heartbeat` is the stable
568    /// JSON contract orchestrators use to coordinate parallel
569    /// writers. `heddle daemon` remains the distinct FUSE mount control plane.
570    Agent {
571        #[command(subcommand)]
572        command: AgentCommands,
573    },
574
575    /// Inspect and refresh rebuildable performance sidecars.
576    Maintenance {
577        #[command(subcommand)]
578        command: MaintenanceCommands,
579    },
580
581    /// Download an existing repository into a local directory.
582    Clone(CloneArgs),
583
584    /// Manage repository hooks.
585    Hook {
586        #[command(subcommand)]
587        command: HookCommands,
588    },
589}
590
591/// Maintenance subcommands.
592#[derive(Clone, Debug, clap::Subcommand)]
593pub enum MaintenanceCommands {
594    /// Verify repository integrity or explicitly repair one surface.
595    Fsck(FsckArgs),
596
597    /// Inspect repository performance sidecars and repo shape.
598    Inspect,
599
600    /// Refresh repository performance sidecars without changing repository meaning.
601    Refresh,
602
603    /// Repack native objects now through the resource-controlled scheduler.
604    Repack,
605
606    /// Garbage collect unreachable objects.
607    Gc {
608        /// Prune unreachable objects.
609        #[arg(long)]
610        prune: bool,
611
612        /// Aggressive garbage collection.
613        #[arg(long)]
614        aggressive: bool,
615
616        /// Show what would be removed without removing.
617        #[arg(long)]
618        dry_run: bool,
619    },
620
621    /// Inspect and repair the operation log.
622    ///
623    /// `heddle maintenance oplog recover` explicitly salvages a truncated or
624    /// torn oplog, reporting what was recovered — the operator-facing
625    /// entrypoint over the same recovery the everyday read path runs
626    /// automatically.
627    Oplog {
628        #[command(subcommand)]
629        command: OplogCommands,
630    },
631}
632
633/// Daemon control plane subcommands. See `Commands::Daemon`.
634#[derive(Clone, Debug, clap::Subcommand)]
635pub enum DaemonCommands {
636    /// Run a foreground mount daemon for this repository.
637    ///
638    /// Normally spawned on demand by the per-thread CLI when
639    /// `--daemon` is passed. Running interactively is for
640    /// debugging the daemon protocol.
641    Serve,
642
643    /// Report daemon liveness, version, uptime, and active mount
644    /// count. No-op success when the daemon isn't running.
645    Status,
646
647    /// Ask the running daemon to drain its mounts and exit. Sweeps
648    /// any leftover registry entries with `fusermount -u` as a
649    /// safety net before returning.
650    Stop,
651}
652
653/// Box-scoped network daemon subcommands. See `Commands::Netd`.
654#[derive(Clone, Debug, clap::Subcommand)]
655pub enum NetdCommands {
656    /// Run the foreground network daemon: bind the persistent device
657    /// endpoint, keep relays online, hold warm weft sessions for hosted
658    /// CLI verbs, and serve same-uid control RPCs. Never idle-exits.
659    Serve,
660
661    /// Report network-daemon liveness and the advertised device node
662    /// id. No-op success when the daemon isn't running.
663    Status,
664
665    /// Ask the running network daemon to close its endpoint and exit.
666    Stop,
667}