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 shows the account, handle, claim state, and current 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
482Examples:
483 heddle whoami # capture actor first, then hosted auth
484 heddle whoami --output json # machine-readable, stable output_kind shape
485 heddle whoami --server api.heddle.sh")]
486 Whoami {
487 /// Heddle server address (defaults to the configured server).
488 #[arg(long)]
489 server: Option<String>,
490 },
491
492 /// Manage code context annotations.
493 ///
494 /// Native Heddle only. Annotations live in `.heddle`, and travel
495 /// over `heddle push` / `heddle pull` to a Heddle remote. They are
496 /// deliberately not projected into Git — not into `refs/notes/*`,
497 /// not into a tracked file — so `git push` and `git clone` do not
498 /// carry them. In Git Overlay mode annotations still work and are
499 /// still useful; they are simply local to that working copy.
500 #[command(after_help = "\
501Scope:
502 Native Heddle only. Annotations are stored in `.heddle` and move over
503 `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
504 Git Overlay repository arrives with no annotations and no Heddle store.
505
506Examples:
507 heddle context set --path src/auth.rs --symbol verify --kind invariant -m 'returns false on timing mismatch'
508 heddle context get --path src/auth.rs --symbol verify
509 heddle context history --path src/auth.rs # same --path as set, or pass the id
510 heddle context list --prefix src/auth # everything attached under a path
511 heddle context check --path src/auth.rs # surface annotations for editor tooling
512")]
513 Context {
514 #[command(subcommand)]
515 command: ContextCommands,
516 },
517
518 /// Manage ambient harness integrations.
519 Integration {
520 #[command(subcommand)]
521 command: IntegrationCommands,
522 },
523
524 /// Semantic analysis queries (call-graph hot-spots, churn,
525 /// signature-stability surfaces).
526 #[cfg(feature = "semantic")]
527 Semantic {
528 #[command(subcommand)]
529 command: SemanticCommands,
530 },
531
532 /// FUSE mount-daemon control plane — distinct from `agent`.
533 ///
534 /// `heddle daemon serve` runs a foreground mount daemon that
535 /// owns FUSE sessions for `--workspace virtualized --daemon`
536 /// threads. It is normally spawned on demand by the per-thread
537 /// CLI; running it interactively is for debugging.
538 /// `status` reports liveness/uptime/mount count without spawning;
539 /// `stop` asks a running daemon to drain mounts and exit.
540 Daemon {
541 #[command(subcommand)]
542 command: DaemonCommands,
543 },
544
545 /// Box-scoped network daemon control plane — distinct from
546 /// `daemon` (FUSE mounts).
547 ///
548 /// `heddle netd serve` runs a long-lived async daemon that binds
549 /// the machine's single persistent Iroh endpoint on the persisted
550 /// device node id and keeps it relay-reachable, so outstanding
551 /// claim links keep resolving across restarts. Hosted verbs
552 /// (`whoami`, `push`, `pull`, `clone`) reuse that endpoint's warm
553 /// weft session when netd is running. Unlike `daemon`, it is not
554 /// gated on Linux/FUSE and never idle-exits. `status` reports
555 /// liveness and the advertised node id; `stop` asks a running
556 /// daemon to close its endpoint and exit.
557 Netd {
558 #[command(subcommand)]
559 command: NetdCommands,
560 },
561
562 /// Agent reservation and one-shot orchestration API.
563 ///
564 /// `heddle agent reserve|capture|ready|release|list|heartbeat` is the stable
565 /// JSON contract orchestrators use to coordinate parallel
566 /// writers. `heddle daemon` remains the distinct FUSE mount control plane.
567 Agent {
568 #[command(subcommand)]
569 command: AgentCommands,
570 },
571
572 /// Inspect and refresh rebuildable performance sidecars.
573 Maintenance {
574 #[command(subcommand)]
575 command: MaintenanceCommands,
576 },
577
578 /// Download an existing repository into a local directory.
579 Clone(CloneArgs),
580
581 /// Manage repository hooks.
582 Hook {
583 #[command(subcommand)]
584 command: HookCommands,
585 },
586}
587
588/// Maintenance subcommands.
589#[derive(Clone, Debug, clap::Subcommand)]
590pub enum MaintenanceCommands {
591 /// Verify repository integrity or explicitly repair one surface.
592 Fsck(FsckArgs),
593
594 /// Inspect repository performance sidecars and repo shape.
595 Inspect,
596
597 /// Refresh repository performance sidecars without changing repository meaning.
598 Refresh,
599
600 /// Repack native objects now through the resource-controlled scheduler.
601 Repack,
602
603 /// Garbage collect unreachable objects.
604 Gc {
605 /// Prune unreachable objects.
606 #[arg(long)]
607 prune: bool,
608
609 /// Aggressive garbage collection.
610 #[arg(long)]
611 aggressive: bool,
612
613 /// Show what would be removed without removing.
614 #[arg(long)]
615 dry_run: bool,
616 },
617
618 /// Inspect and repair the operation log.
619 ///
620 /// `heddle maintenance oplog recover` explicitly salvages a truncated or
621 /// torn oplog, reporting what was recovered — the operator-facing
622 /// entrypoint over the same recovery the everyday read path runs
623 /// automatically.
624 Oplog {
625 #[command(subcommand)]
626 command: OplogCommands,
627 },
628}
629
630/// Daemon control plane subcommands. See `Commands::Daemon`.
631#[derive(Clone, Debug, clap::Subcommand)]
632pub enum DaemonCommands {
633 /// Run a foreground mount daemon for this repository.
634 ///
635 /// Normally spawned on demand by the per-thread CLI when
636 /// `--daemon` is passed. Running interactively is for
637 /// debugging the daemon protocol.
638 Serve,
639
640 /// Report daemon liveness, version, uptime, and active mount
641 /// count. No-op success when the daemon isn't running.
642 Status,
643
644 /// Ask the running daemon to drain its mounts and exit. Sweeps
645 /// any leftover registry entries with `fusermount -u` as a
646 /// safety net before returning.
647 Stop,
648}
649
650/// Box-scoped network daemon subcommands. See `Commands::Netd`.
651#[derive(Clone, Debug, clap::Subcommand)]
652pub enum NetdCommands {
653 /// Run the foreground network daemon: bind the persistent device
654 /// endpoint, keep relays online, hold warm weft sessions for hosted
655 /// CLI verbs, and serve same-uid control RPCs. Never idle-exits.
656 Serve,
657
658 /// Report network-daemon liveness and the advertised device node
659 /// id. No-op success when the daemon isn't running.
660 Status,
661
662 /// Ask the running network daemon to close its endpoint and exit.
663 Stop,
664}