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