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}