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, CompletionSubject, ContextCommands, DiscussCommands, EnvCommands, HookCommands,
12    IntegrationCommands, OplogCommands, QueryArgs, RedactCommands, RemoteCommands, ReviewCommands,
13    ShellCommands, ThreadCommands, VisibilityCommands,
14    commands_args::{
15        AdoptArgs, CloneArgs, DiffArgs, DoctorArgs, 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, ClaimArgs, 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    /// Show the authority-valid repair without changing refs.
71    #[arg(long)]
72    pub preview: bool,
73}
74
75#[derive(Subcommand)]
76pub enum Commands {
77    /// Initialize Heddle in a directory or existing Git checkout.
78    #[command(name = INIT_VERB)]
79    Init(InitArgs),
80
81    /// Adopt Git history into Heddle-native source authority.
82    ///
83    /// Git Overlay is the normal existing-Git mode: Git keeps source objects,
84    /// refs, index, and worktree state while Heddle stores metadata in
85    /// `.heddle`. `adopt` imports history and moves source authority to Heddle.
86    Adopt(AdoptArgs),
87
88    /// Curated, progressive-disclosure help.
89    ///
90    /// `heddle help` prints the locked everyday verbs. `heddle help
91    /// <topic>` prints the topic page (e.g. `model`, `daemon`,
92    /// `signals`, `git-concepts`). `heddle help <command path>` falls
93    /// through to that command's `--help` so the printer never
94    /// duplicates clap's per-verb derivation.
95    Help {
96        /// Topic name (`model`, `daemon`, `signals`, …) or command
97        /// path. When omitted, prints the curated default.
98        #[arg(value_name = "TOPIC_OR_COMMAND")]
99        topics: Vec<String>,
100    },
101
102    /// Show what needs attention and the next safe Heddle action.
103    #[command(after_help = "\
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 Heddle CI checks. Finds `ci.ts` / `ci.rs` / `ci.go`, compiles if needed, then runs.
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 a ready thread into its local target.
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    /// Prepare this thread for review or merge.
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    /// Open or resolve discussions anchored to symbols.
218    ///
219    /// Open a discussion against a symbol; append turns;
220    /// resolve by edit or dismiss. Anchors
221    /// travel across renames and cross-file moves on subsequent
222    /// state mutations.
223    ///
224    /// Native Heddle only. Discussions live in `.heddle` and travel
225    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
226    /// not projected into Git, so `git clone` does not carry them; in
227    /// Git Overlay mode they are local to that working copy.
228    #[command(after_help = "\
229Scope:
230  Native Heddle only. Discussions are stored in `.heddle` and move over
231  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
232  Git Overlay repository arrives with no discussions and no Heddle store.
233
234Examples:
235  heddle discuss open src/auth.rs verify 'Should this reject expired tokens?'  # anchor a discussion
236  heddle discuss append <id> 'switched to argon2'          # add a turn
237  heddle discuss resolve <id> --mode by-edit --state HEAD
238")]
239    Discuss {
240        #[command(subcommand)]
241        command: DiscussCommands,
242    },
243
244    /// Structured query over the operation log. Filter by
245    /// actor, time window, signal kind, symbol, thread, verbs. Returns
246    /// structured results consumable by agents.
247    Query(QueryArgs),
248
249    /// Review a state — render the payload, sign, see signal health.
250    ///
251    /// `heddle review show` renders the review payload (summary,
252    /// agent narrative, in-budget signals, anchored discussions).
253    /// `heddle review sign` submits a `read` / `agent_preview` /
254    /// `agent_co_review` signature on the state. `heddle review
255    /// health` reports per-module signal fire rates over a rolling
256    /// window.
257    #[command(after_help = "\
258Examples:
259  heddle review show HEAD                                # render the review payload for HEAD
260  heddle review show HEAD --base last-turn               # review this agent peer's turn
261  heddle review sign HEAD --kind read --public-key <hex> --signature <hex> --signed-at-unix <ts>
262  heddle review health --window 7                       # signal fire-rates over recent states
263")]
264    Review {
265        #[command(subcommand)]
266        command: ReviewCommands,
267    },
268
269    /// Redact a sensitive blob in a state so reads return a stub
270    /// instead of the content.
271    ///
272    /// `heddle redact apply` declares a redaction; the blob bytes stay
273    /// on disk and reads return the operator-supplied stub. `heddle
274    /// redact purge` afterward physically removes the bytes. Both are signed,
275    /// attributed, oplog-audited operations. See
276    /// `docs/PRINCIPLES.md` (the honesty principle) for context.
277    Redact {
278        #[command(subcommand)]
279        command: RedactCommands,
280    },
281
282    /// Declare and inspect a state's audience visibility tier.
283    ///
284    /// `heddle visibility set` binds a tier to a state; `promote` lifts it to
285    /// a less-restrictive tier via a superseding record; `show` reports the
286    /// effective tier; `list` enumerates non-public states. Capture binds the
287    /// inherited `[review.discussion] default_visibility` automatically
288    /// (Invariant A) — these verbs are the explicit operator overrides.
289    Visibility {
290        #[command(subcommand)]
291        command: VisibilityCommands,
292    },
293
294    /// Run a child with a confidential runtime profile.
295    ///
296    /// `heddle env run --profile <name> -- <cmd>` asks the local policy
297    /// broker to unwrap named slots and injects them into the child
298    /// environment only. Values never land in the worktree, the store, or
299    /// command JSON. Same-UID callers are cooperative; OS isolation is later.
300    #[command(after_help = "\
301Examples:
302  heddle env list
303  heddle env create --name local --from-env DATABASE_URL
304  heddle env run --profile local -- printenv DATABASE_URL
305")]
306    Env {
307        #[command(subcommand)]
308        command: EnvCommands,
309    },
310
311    /// Revert changes from a state.
312    Revert(RevertArgs),
313
314    /// Undo the last Heddle operation.
315    Undo(UndoArgs),
316
317    /// Manage threads.
318    Thread {
319        #[command(subcommand)]
320        command: ThreadCommands,
321    },
322
323    /// Shell integration helpers (auto-cd on thread start/switch/cd).
324    Shell {
325        #[command(subcommand)]
326        command: ShellCommands,
327    },
328
329    /// Print a tab-completion script for bash, zsh, or fish.
330    ///
331    /// With no shell, prints install lines. With `bash`, `zsh`, or `fish`,
332    /// emits the same script as `heddle shell completion`.
333    Completions {
334        /// Shell to generate completion for: bash, zsh, or fish.
335        #[arg(value_name = "SHELL")]
336        shell: Option<String>,
337    },
338
339    /// Internal shell-completion candidate helper.
340    #[command(name = "complete", alias = "__complete", hide = true)]
341    Complete {
342        /// Candidate set to print, one candidate per line.
343        #[arg(value_enum)]
344        subject: CompletionSubject,
345    },
346
347    /// Resolve merge conflicts.
348    Resolve(ResolveArgs),
349
350    /// Explicit interoperability with other version-control formats.
351    #[cfg(feature = "git-overlay")]
352    Bridge {
353        #[command(subcommand)]
354        command: BridgeCommands,
355    },
356
357    /// Push the source-authoritative history to a remote.
358    Push(PushArgs),
359
360    /// Pull source-authoritative history from a remote.
361    Pull(PullArgs),
362
363    /// Manage remote repositories.
364    Remote {
365        #[command(subcommand)]
366        command: RemoteCommands,
367    },
368
369    /// Authenticate with a Heddle server.
370    #[cfg(feature = "client")]
371    Auth {
372        #[command(subcommand)]
373        command: AuthCommands,
374    },
375
376    /// Promote a personal hosted spool to a root-level spool.
377    ///
378    /// Moves `spool/<your-handle>/<name>` to `spool/<name>` after the server
379    /// confirms the root slug is free, the account is claimed/verified, and
380    /// you hold an owner grant. Clone a bare name still prefers your personal
381    /// copy first.
382    #[cfg(feature = "client")]
383    Promote(PromoteArgs),
384
385    /// Offer this agent account for a human to claim.
386    ///
387    /// Prints a short-lived bearer link, then keeps the agent's Iroh endpoint
388    /// online until the human finishes, the offer expires, or Ctrl-C stops it.
389    #[cfg(feature = "client")]
390    #[command(after_help = "\
391Examples:
392  heddle claim
393  heddle claim --timeout 30m
394  heddle claim --server weft.example --web-origin https://heddle.example
395")]
396    Claim(ClaimArgs),
397
398    /// Report the capture actor, then hosted auth.
399    ///
400    /// The capture actor is who the next capture is attributed to
401    /// (`user_config`, `init --principal-*`, or `HEDDLE_PRINCIPAL_*`).
402    /// Hosted auth is whether this machine has a server credential.
403    /// These are different objects. `heddle auth login` does not set the
404    /// local actor. `whoami` only reads; it never attaches a credential.
405    #[cfg(feature = "client")]
406    #[command(after_help = "\
407The capture actor and hosted auth are different objects:
408  capture actor  who the next capture is attributed to
409                 (user_config, init --principal-*, or HEDDLE_PRINCIPAL_*)
410  hosted auth    whether this machine has a credential for the server
411                 (heddle auth login). whoami never attaches a credential.
412
413Examples:
414  heddle whoami                       # capture actor first, then hosted auth
415  heddle whoami --output json         # machine-readable, stable output_kind shape
416  heddle whoami --server api.heddle.sh")]
417    Whoami {
418        /// Heddle server address (defaults to the configured server).
419        #[arg(long)]
420        server: Option<String>,
421    },
422
423    /// Manage code context annotations.
424    ///
425    /// Native Heddle only. Annotations live in `.heddle`, and travel
426    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
427    /// deliberately not projected into Git — not into `refs/notes/*`,
428    /// not into a tracked file — so `git push` and `git clone` do not
429    /// carry them. In Git Overlay mode annotations still work and are
430    /// still useful; they are simply local to that working copy.
431    #[command(after_help = "\
432Scope:
433  Native Heddle only. Annotations are stored in `.heddle` and move over
434  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
435  Git Overlay repository arrives with no annotations and no Heddle store.
436
437Examples:
438  heddle context set --path src/auth.rs --scope symbol:verify --kind invariant -m 'returns false on timing mismatch'
439  heddle context get --path src/auth.rs --scope symbol:verify
440  heddle context list --prefix src/auth          # everything attached under a path
441  heddle context check --path src/auth.rs        # surface annotations for editor tooling
442")]
443    Context {
444        #[command(subcommand)]
445        command: ContextCommands,
446    },
447
448    /// Manage ambient harness integrations.
449    Integration {
450        #[command(subcommand)]
451        command: IntegrationCommands,
452    },
453
454    /// Semantic analysis queries (call-graph hot-spots, churn,
455    /// signature-stability surfaces).
456    #[cfg(feature = "semantic")]
457    Semantic {
458        #[command(subcommand)]
459        command: SemanticCommands,
460    },
461
462    /// FUSE mount-daemon control plane — distinct from `agent`.
463    ///
464    /// `heddle daemon serve` runs a foreground mount daemon that
465    /// owns FUSE sessions for `--workspace virtualized --daemon`
466    /// threads. It is normally spawned on demand by the per-thread
467    /// CLI; running it interactively is for debugging.
468    /// `status` reports liveness/uptime/mount count without spawning;
469    /// `stop` asks a running daemon to drain mounts and exit.
470    Daemon {
471        #[command(subcommand)]
472        command: DaemonCommands,
473    },
474
475    /// Box-scoped network daemon control plane — distinct from
476    /// `daemon` (FUSE mounts).
477    ///
478    /// `heddle netd serve` runs a long-lived async daemon that binds
479    /// the machine's single persistent Iroh endpoint on the persisted
480    /// device node id and keeps it relay-reachable, so outstanding
481    /// claim links keep resolving across restarts. Unlike `daemon`, it
482    /// is not gated on Linux/FUSE and never idle-exits. `status`
483    /// reports liveness and the advertised node id; `stop` asks a
484    /// running daemon to close its endpoint and exit.
485    Netd {
486        #[command(subcommand)]
487        command: NetdCommands,
488    },
489
490    /// Agent reservation and one-shot orchestration API.
491    ///
492    /// `heddle agent reserve|capture|ready|release|list|heartbeat` is the stable
493    /// JSON contract orchestrators use to coordinate parallel
494    /// writers. `heddle daemon` remains the distinct FUSE mount control plane.
495    Agent {
496        #[command(subcommand)]
497        command: AgentCommands,
498    },
499
500    /// Inspect and refresh rebuildable performance sidecars.
501    Maintenance {
502        #[command(subcommand)]
503        command: MaintenanceCommands,
504    },
505
506    /// Clone from remote.
507    Clone(CloneArgs),
508
509    /// Manage repository hooks.
510    Hook {
511        #[command(subcommand)]
512        command: HookCommands,
513    },
514}
515
516/// Maintenance subcommands.
517#[derive(Clone, Debug, clap::Subcommand)]
518pub enum MaintenanceCommands {
519    /// Verify repository integrity or explicitly repair one surface.
520    Fsck(FsckArgs),
521
522    /// Inspect repository performance sidecars and repo shape.
523    Inspect,
524
525    /// Refresh repository performance sidecars without changing repository meaning.
526    Refresh,
527
528    /// Repack native objects now through the resource-controlled scheduler.
529    Repack,
530
531    /// Garbage collect unreachable objects.
532    Gc {
533        /// Prune unreachable objects.
534        #[arg(long)]
535        prune: bool,
536
537        /// Aggressive garbage collection.
538        #[arg(long)]
539        aggressive: bool,
540
541        /// Show what would be removed without removing.
542        #[arg(long)]
543        dry_run: bool,
544    },
545
546    /// Inspect and repair the operation log.
547    ///
548    /// `heddle maintenance oplog recover` explicitly salvages a truncated or
549    /// torn oplog, reporting what was recovered — the operator-facing
550    /// entrypoint over the same recovery the everyday read path runs
551    /// automatically.
552    Oplog {
553        #[command(subcommand)]
554        command: OplogCommands,
555    },
556}
557
558/// Daemon control plane subcommands. See `Commands::Daemon`.
559#[derive(Clone, Debug, clap::Subcommand)]
560pub enum DaemonCommands {
561    /// Run a foreground mount daemon for this repository.
562    ///
563    /// Normally spawned on demand by the per-thread CLI when
564    /// `--daemon` is passed. Running interactively is for
565    /// debugging the daemon protocol.
566    Serve,
567
568    /// Report daemon liveness, version, uptime, and active mount
569    /// count. No-op success when the daemon isn't running.
570    Status,
571
572    /// Ask the running daemon to drain its mounts and exit. Sweeps
573    /// any leftover registry entries with `fusermount -u` as a
574    /// safety net before returning.
575    Stop,
576}
577
578/// Box-scoped network daemon subcommands. See `Commands::Netd`.
579#[derive(Clone, Debug, clap::Subcommand)]
580pub enum NetdCommands {
581    /// Run the foreground network daemon: bind the persistent device
582    /// endpoint, keep relays online, and serve same-uid control RPCs.
583    /// Never idle-exits.
584    Serve,
585
586    /// Report network-daemon liveness and the advertised device node
587    /// id. No-op success when the daemon isn't running.
588    Status,
589
590    /// Ask the running network daemon to close its endpoint and exit.
591    Stop,
592}