1#[cfg(feature = "git-overlay")]
5use super::commands_git_projection::SyncCommands;
6
7#[derive(Clone, Debug, clap::Args)]
9#[command(after_help = "\
10Examples:
11 heddle init # initialize here; existing Git becomes Git Overlay
12 heddle init my-project # initialize a native Heddle subdirectory
13 heddle init --principal-name 'Ada Lovelace' --principal-email ada@example.com
14")]
15pub struct InitArgs {
16 pub path: Option<std::path::PathBuf>,
18
19 #[arg(long)]
21 pub principal_name: Option<String>,
22
23 #[arg(long)]
25 pub principal_email: Option<String>,
26
27 #[arg(long)]
29 pub install_harnesses: Option<String>,
30
31 #[arg(long)]
33 pub no_harness_install: bool,
34
35 #[arg(long, visible_alias = "scope", default_value = "repo")]
37 pub harness_install_scope: String,
38
39 #[arg(long)]
41 pub harness_install_force: bool,
42}
43
44#[derive(Clone, Debug, clap::Args)]
46#[command(after_help = "\
47Examples:
48 heddle adopt # adopt all local Git refs into native Heddle storage
49 heddle adopt --ref main # adopt one branch or tag
50 heddle adopt ../repo --ref main --ref v1.0 # adopt selected refs in another repo
51
52Adoption imports Git refs, makes Heddle the source authority, and retains `.git` for explicit Git Projection. Normal Git Overlay setup uses `heddle init` instead.
53")]
54pub struct AdoptArgs {
55 pub path: Option<std::path::PathBuf>,
57
58 #[arg(long = "ref", value_name = "REF")]
60 pub refs: Vec<String>,
61}
62
63#[derive(Clone, Debug, clap::Args)]
70pub struct DoctorArgs {
71 #[arg(long, global = false)]
76 pub profile: bool,
77
78 #[command(subcommand)]
79 pub command: Option<DoctorCommands>,
80}
81
82#[derive(Clone, Debug, clap::Subcommand)]
84pub enum DoctorCommands {
85 Docs(DoctorDocsArgs),
96
97 Schemas(DoctorSchemasArgs),
107}
108
109#[derive(Clone, Debug, clap::Args)]
111pub struct DoctorDocsArgs {
112 #[arg(long, value_name = "PATH")]
117 pub path: Vec<std::path::PathBuf>,
118
119 #[arg(long)]
121 pub all: bool,
122}
123
124#[derive(Clone, Debug, clap::Args)]
126pub struct DoctorSchemasArgs {
127 #[arg(long)]
130 pub update_docs: bool,
131}
132
133fn parse_confidence(s: &str) -> Result<f32, String> {
134 let value = s
135 .parse::<f32>()
136 .map_err(|_| format!("confidence must be a finite number from 0.0 to 1.0, got `{s}`"))?;
137 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
138 return Err(format!(
139 "confidence must be a finite number from 0.0 to 1.0, got `{s}`"
140 ));
141 }
142 Ok(value)
143}
144
145#[derive(Clone, Debug, clap::Args)]
147#[command(after_help = "\
148Examples:
149 heddle capture -m 'add login route' # capture the worktree with intent
150 heddle capture -m 'wip' --confidence 0.6 # honest confidence on a draft step
151
152Agent automation flags (provider/model/session/policy/split) are hidden here.
153Run `heddle help agent-flags`, or `heddle capture --help-agent` to list them inline.
154")]
155pub struct SnapshotArgs {
156 #[arg(long, hide = true)]
166 pub help_agent: bool,
167
168 #[arg(short = 'm', long, visible_alias = "message")]
170 pub intent: Option<String>,
171
172 #[arg(long, value_parser = parse_confidence)]
174 pub confidence: Option<f32>,
175
176 #[arg(short, long)]
178 pub force: bool,
179
180 #[arg(long, hide = true)]
182 pub agent_provider: Option<String>,
183
184 #[arg(long, hide = true)]
186 pub agent_model: Option<String>,
187
188 #[arg(long, hide = true)]
190 pub agent_session: Option<String>,
191
192 #[arg(long, hide = true)]
194 pub agent_segment: Option<String>,
195
196 #[arg(long, hide = true)]
198 pub policy: Option<String>,
199
200 #[arg(long, hide = true)]
202 pub no_policy: bool,
203
204 #[arg(long, hide = true)]
206 pub no_agent: bool,
207
208 #[arg(long, hide = true)]
210 pub split: bool,
211
212 #[arg(long, hide = true, requires = "split")]
214 pub into: Option<String>,
215
216 #[arg(long = "path", hide = true, requires = "split", value_name = "PATH")]
218 pub paths: Vec<String>,
219}
220
221#[derive(Clone, Debug, clap::Args)]
223#[command(after_help = "\
224Examples:
225 heddle capture -m 'add login route'
226 heddle commit
227 heddle commit -m 'add login route'
228
229Behavior:
230 Commits the complete captured tree and replaces the Git index with that tree.
231 Git pre-commit and commit-msg hooks are not run.
232")]
233pub struct CommitArgs {
234 #[arg(short = 'm', long = "message")]
236 pub message: Option<String>,
237}
238
239#[derive(Clone, Debug, clap::Args)]
241#[command(after_help = "\
242Examples:
243 heddle log # walk the current thread
244 heddle log --oneline -n 20 # 20 most recent states in compact form
245 heddle log --timeline # show agent timeline tool-call cursor
246 heddle log --reflog # include re-attributed history
247 heddle log --path src/auth.rs # restrict to states touching a path
248")]
249pub struct LogArgs {
250 pub state: Option<String>,
252
253 #[arg(short = 'n', long, default_value = "20")]
255 pub limit: usize,
256
257 #[arg(long)]
259 pub all: bool,
260
261 #[arg(long)]
263 pub graph: bool,
264
265 #[arg(long)]
267 pub oneline: bool,
268
269 #[arg(long)]
271 pub reflog: bool,
272
273 #[arg(long)]
275 pub timeline: bool,
276
277 #[arg(long, default_value = "main")]
279 pub thread: String,
280
281 #[arg(long)]
283 pub agent: Option<String>,
284
285 #[arg(long = "path", value_name = "PATH")]
287 pub paths: Vec<String>,
288
289 #[arg(long, value_name = "STATE")]
295 pub since: Option<String>,
296}
297
298#[derive(Clone, Debug, clap::Args)]
300pub struct TimelineArgs {
301 #[command(subcommand)]
302 pub command: TimelineCommands,
303}
304
305#[derive(Clone, Debug, clap::Subcommand)]
307pub enum TimelineCommands {
308 Status(TimelineStatusArgs),
310
311 #[command(name = "record-start")]
313 RecordStart(TimelineRecordStartArgs),
314
315 #[command(name = "record-finish")]
317 RecordFinish(TimelineRecordFinishArgs),
318
319 #[command(after_help = "\
321Examples:
322 heddle timeline fork --step tls-abc --branch tlb-experiment
323 heddle timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
324")]
325 Fork(TimelineForkArgs),
326
327 #[command(after_help = "\
329Examples:
330 heddle timeline reset --step tls-abc
331 heddle timeline reset --tool-call call_123 --materialize
332")]
333 Reset(TimelineResetArgs),
334
335 Recover(TimelineRecoverArgs),
337}
338
339#[derive(Clone, Debug, clap::Args)]
341pub struct TimelineTargetArgs {
342 #[arg(long, default_value = "main")]
344 pub thread: String,
345
346 #[arg(long = "from-branch", value_name = "BRANCH")]
348 pub from_branch: Option<String>,
349
350 #[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
352 pub step: Option<String>,
353
354 #[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
356 pub tool_call: Option<String>,
357
358 #[arg(long, default_value = "opencode")]
360 pub harness: String,
361
362 #[arg(long)]
364 pub session: Option<String>,
365
366 #[arg(long)]
368 pub message: Option<String>,
369
370 #[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
372 pub undo: bool,
373
374 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
376 pub redo: bool,
377
378 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
380 pub current: bool,
381}
382
383#[derive(Clone, Debug, clap::Args)]
385pub struct TimelineForkArgs {
386 #[command(flatten)]
387 pub target: TimelineTargetArgs,
388
389 #[arg(long, value_name = "BRANCH")]
391 pub branch: Option<String>,
392
393 #[arg(long, default_value = "explicit-fork")]
395 pub reason: String,
396}
397
398#[derive(Clone, Debug, clap::Args)]
400pub struct TimelineResetArgs {
401 #[command(flatten)]
402 pub target: TimelineTargetArgs,
403
404 #[arg(long)]
406 pub materialize: bool,
407
408 #[arg(long, default_value = "fail-if-dirty")]
410 pub mode: String,
411}
412
413#[derive(Clone, Debug, clap::Args)]
415pub struct TimelineRecoverArgs {
416 #[arg(long, default_value = "main")]
418 pub thread: String,
419}
420
421#[derive(Clone, Debug, clap::Args)]
423pub struct TimelineStatusArgs {
424 #[arg(long, default_value = "main")]
426 pub thread: String,
427}
428
429#[derive(Clone, Debug, clap::Args)]
431pub struct TimelineRecordToolArgs {
432 #[arg(long, default_value = "main")]
434 pub thread: String,
435
436 #[arg(long, default_value = "opencode")]
438 pub harness: String,
439
440 #[arg(long)]
442 pub session: Option<String>,
443
444 #[arg(long)]
446 pub message: Option<String>,
447
448 #[arg(long = "tool-call")]
450 pub tool_call: String,
451
452 #[arg(long = "step-id")]
454 pub step_id: Option<String>,
455
456 #[arg(long = "branch")]
458 pub branch: Option<String>,
459
460 #[arg(long = "summary")]
462 pub summary: Option<String>,
463
464 #[arg(long = "payload-hash")]
466 pub payload_hash: Option<String>,
467}
468
469#[derive(Clone, Debug, clap::Args)]
471pub struct TimelineRecordStartArgs {
472 #[command(flatten)]
473 pub tool: TimelineRecordToolArgs,
474
475 #[arg(long = "tool-name", default_value = "tool")]
477 pub tool_name: String,
478}
479
480#[derive(Clone, Debug, clap::Args)]
482pub struct TimelineRecordFinishArgs {
483 #[command(flatten)]
484 pub tool: TimelineRecordToolArgs,
485
486 #[arg(long, default_value = "succeeded")]
488 pub status: String,
489}
490
491#[derive(Clone, Debug, clap::Args)]
498pub struct RetroArgs {
499 #[arg(long)]
504 pub since: Option<String>,
505
506 #[arg(long)]
509 pub include_merges: bool,
510
511 #[arg(long)]
514 pub include_undos: bool,
515
516 #[arg(long = "full", alias = "expand")]
520 pub full: bool,
521}
522
523#[derive(Clone, Debug, clap::Args)]
525#[command(after_help = "\
526Patch compatibility:
527 --patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
528")]
529pub struct DiffArgs {
530 pub from: Option<String>,
532
533 pub to: Option<String>,
535
536 #[arg(long)]
538 pub semantic: bool,
539
540 #[arg(long)]
542 pub stat: bool,
543
544 #[arg(long)]
546 pub name_only: bool,
547
548 #[arg(short = 'U', long = "unified", default_value_t = 3)]
550 pub unified: usize,
551
552 #[arg(long)]
554 pub context: bool,
555
556 #[arg(short = 'p', long = "patch")]
558 pub patch: bool,
559}
560
561#[derive(Clone, Debug, clap::Args)]
563pub struct RevertArgs {
564 pub state: String,
566
567 #[arg(short = 'm', long)]
569 pub message: Option<String>,
570
571 #[arg(long)]
573 pub no_commit: bool,
574}
575
576#[derive(Clone, Debug, clap::Args)]
578#[command(after_help = "\
579Examples:
580 heddle undo # roll back the most recent operation
581 heddle undo -n 3 # roll back the last three operations
582 heddle undo --recover # restore the state preserved by the last undo
583 heddle undo --list # preview undoable operations on this thread
584 heddle undo --dry-run # show what would change without applying
585
586Undoable operations:
587 - heddle capture (restores HEAD to the pre-capture parent)
588 - heddle land (non-FF) (restores HEAD + both thread refs)
589 - heddle land (FF) (restores HEAD + the landed-into thread ref to
590 the pre-merge tip; the merged-in thread is
591 untouched.)
592 - heddle thread switch (restores HEAD to the previous thread state)
593 - heddle thread create/drop/rename
594 - heddle thread marker create/drop
595 - heddle redact apply (with --allow-redact-undo; removes the
596 redaction record so future materializes
597 restore the original blob bytes. Refused
598 when a Purge has destroyed the bytes.)
599 - heddle undo --redo re-apply the most recently undone operation
600
601Not undoable (file a follow-up if you need one):
602 - heddle push / pull (remote-affecting; out of scope)
603 - heddle redact purge apply (destructive by design; irreversible)
604 - heddle start <name> --path <dir> (refused while the materialized worktree
605 still exists — run `heddle thread drop
606 <name> --delete-thread` first, then
607 re-run `heddle undo`)
608 - cross-worktree shared-backend undo (no worktree registry yet; single-
609 worktree usage is the supported
610 configuration for 0.3)
611")]
612pub struct UndoArgs {
613 #[arg(short = 'n', long, default_value = "1")]
615 pub steps: usize,
616
617 #[arg(long)]
619 pub list: bool,
620
621 #[arg(long, default_value = "20")]
623 pub depth: usize,
624
625 #[arg(long, visible_alias = "dry-run")]
628 pub preview: bool,
629
630 #[arg(long, conflicts_with = "list")]
632 pub redo: bool,
633
634 #[arg(
637 long,
638 conflicts_with_all = ["steps", "list", "preview", "redo", "allow_redact_undo"]
639 )]
640 pub recover: bool,
641
642 #[arg(long)]
650 pub allow_redact_undo: bool,
651}
652
653#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
659pub enum WorkspaceModeArg {
660 Auto,
662 Materialized,
664 Virtualized,
666 Solid,
668}
669
670#[derive(Clone, Debug, clap::Args)]
672#[command(after_help = "\
673Examples:
674 heddle start feature/auth --path ../feature-auth # create an isolated checkout
675 heddle start scratch --path ../scratch # place the checkout explicitly
676 heddle start fix-flake --task 'fix CI flake' # attach a task description
677
678Isolated checkouts are Heddle-managed working directories. They do not contain a .git directory; use Heddle commands inside them, and run Git-authority operations through Heddle from the parent Git-overlay repository.
679
680`heddle start <name> --path <dir>` is the one-step form of the advanced split flow: `heddle thread create <name>` creates the ref now, and `heddle thread promote <name> --path <dir>` materializes it later. Use the split form only when you intentionally need ref-first, checkout-later staging.
681
682Advanced (hidden) flags:
683 --agent-provider/--agent-model (agent attribution for the registered thread), --parent-thread (delegated child work), --print-cd-path (print only the checkout path for shell wrappers), --daemon/--no-daemon (virtualized-mount ownership), --shared-target/--no-shared-target (workspace-shared cargo target dir; default on for Rust solid/materialized). All are accepted here; they stay out of the flag list to keep everyday help terse.
684")]
685pub struct ThreadStartArgs {
686 pub name: String,
688
689 #[arg(long)]
691 pub from: Option<String>,
692
693 #[arg(long)]
695 pub path: Option<std::path::PathBuf>,
696
697 #[arg(long, value_enum, default_value_t = WorkspaceModeArg::Auto)]
699 pub workspace: WorkspaceModeArg,
700
701 #[arg(long, hide = true)]
703 pub agent_provider: Option<String>,
704
705 #[arg(long, hide = true)]
707 pub agent_model: Option<String>,
708
709 #[arg(long)]
711 pub task: Option<String>,
712
713 #[arg(long, hide = true)]
715 pub parent_thread: Option<String>,
716
717 #[arg(long, hide = true)]
719 pub automated: bool,
720
721 #[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
728 pub print_cd_path: bool,
729
730 #[arg(
737 long,
738 overrides_with = "no_daemon",
739 action = clap::ArgAction::SetTrue,
740 default_value_t = true,
741 hide = true,
742 )]
743 pub daemon: bool,
744
745 #[arg(
751 long,
752 overrides_with = "daemon",
753 action = clap::ArgAction::SetTrue,
754 hide = true,
755 )]
756 pub no_daemon: bool,
757
758 #[arg(long)]
762 pub interactive_setup: bool,
763
764 #[arg(
778 long,
779 overrides_with = "no_shared_target",
780 action = clap::ArgAction::SetTrue,
781 hide = true,
782 )]
783 pub shared_target: bool,
784
785 #[arg(
789 long,
790 overrides_with = "shared_target",
791 action = clap::ArgAction::SetTrue,
792 hide = true,
793 )]
794 pub no_shared_target: bool,
795
796 #[arg(long)]
807 pub hydrate: bool,
808}
809
810#[derive(Clone, Debug, clap::Args)]
818pub struct TryArgs {
819 #[arg(long)]
822 pub name: Option<String>,
823
824 #[arg(long, value_enum, default_value_t = WorkspaceModeArg::Materialized)]
829 pub workspace: WorkspaceModeArg,
830 #[arg(long = "auto-merge")]
833 pub auto_merge: bool,
834
835 #[arg(long = "keep-on-success")]
839 pub keep_on_success: bool,
840
841 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
844 pub command: Vec<String>,
845}
846
847#[derive(Clone, Debug, clap::Args)]
849pub struct RunArgs {
850 #[arg(long = "thread")]
852 pub thread: Option<String>,
853
854 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
856 pub command: Vec<String>,
857}
858
859#[derive(Clone, Debug, clap::Args)]
861pub struct ReadyArgs {
862 #[arg(long = "thread")]
864 pub thread: Option<String>,
865
866 #[arg(short = 'm', long)]
868 pub message: Option<String>,
869
870 #[arg(long, value_parser = parse_confidence)]
872 pub confidence: Option<f32>,
873
874 #[arg(long)]
878 pub dry_run: bool,
879}
880
881#[derive(Clone, Debug, clap::Args)]
883pub struct SyncArgs {
884 #[cfg(feature = "git-overlay")]
886 #[command(subcommand)]
887 pub command: Option<SyncCommands>,
888
889 #[arg(long = "thread")]
891 pub thread: Option<String>,
892}
893
894#[derive(Clone, Debug, clap::Args)]
896pub struct LandArgs {
897 #[arg(long = "thread")]
899 pub thread: Option<String>,
900
901 #[arg(long = "threads", value_delimiter = ',')]
906 pub threads: Vec<String>,
907
908 #[arg(short = 'm', long)]
910 pub message: Option<String>,
911
912 #[arg(long)]
914 pub no_squash: bool,
915
916 #[arg(long)]
920 pub dry_run: bool,
921}
922
923#[derive(Clone, Debug, clap::Args)]
925pub struct ThreadShowArgs {
926 pub thread: Option<String>,
928
929 #[arg(long)]
931 pub watch: bool,
932
933 #[arg(long, hide = true)]
935 pub watch_iterations: Option<usize>,
936
937 #[arg(long, hide = true)]
939 pub watch_interval_ms: Option<u64>,
940}
941
942#[derive(Clone, Debug, clap::Args)]
944pub struct ThreadCapturesArgs {
945 pub thread: Option<String>,
947
948 #[arg(long, default_value_t = 20)]
950 pub limit: usize,
951}
952
953#[derive(Clone, Debug, clap::Args)]
957pub struct ThreadNameArgs {
958 pub thread: Option<String>,
960}
961
962#[derive(Clone, Debug, clap::Args)]
964pub struct ThreadRenameArgs {
965 pub old: String,
967
968 pub new: String,
970}
971
972#[derive(Clone, Debug, clap::Args)]
974pub struct ThreadPromoteArgs {
975 pub thread: String,
977
978 #[arg(long)]
980 pub path: Option<std::path::PathBuf>,
981
982 #[arg(long)]
984 pub force: bool,
985}
986
987#[derive(Clone, Debug, clap::Args)]
989pub struct ThreadMoveArgs {
990 pub from: String,
992
993 pub to: String,
995
996 #[arg(long = "path", required = true, value_name = "PATH")]
998 pub paths: Vec<String>,
999
1000 #[arg(short = 'm', long)]
1002 pub message: Option<String>,
1003}
1004
1005#[derive(Clone, Debug, clap::Args)]
1007pub struct ThreadAbsorbArgs {
1008 pub thread: String,
1010
1011 #[arg(long)]
1013 pub into: Option<String>,
1014
1015 #[arg(short = 'm', long)]
1017 pub message: Option<String>,
1018
1019 #[arg(long)]
1021 pub preview: bool,
1022}
1023
1024#[derive(Clone, Debug, clap::Args)]
1026pub struct ThreadResolveArgs {
1027 pub thread: String,
1029}
1030
1031#[derive(Clone, Debug, clap::Args)]
1033pub struct ThreadDropArgs {
1034 pub thread: String,
1036
1037 #[arg(long)]
1039 pub delete_thread: bool,
1040
1041 #[arg(short, long)]
1043 pub force: bool,
1044}
1045
1046#[derive(Clone, Debug, clap::Args)]
1050pub struct ThreadApproveArgs {
1051 pub source: String,
1053
1054 pub target: String,
1056
1057 #[arg(long)]
1059 pub note: Option<String>,
1060
1061 #[arg(long, default_value = "origin")]
1063 pub remote: String,
1064}
1065
1066#[derive(Clone, Debug, clap::Args)]
1069pub struct ThreadApprovalsArgs {
1070 pub source: String,
1071 pub target: String,
1072 #[arg(long, default_value = "origin")]
1073 pub remote: String,
1074}
1075
1076#[derive(Clone, Debug, clap::Args)]
1079pub struct ThreadRevokeApprovalArgs {
1080 pub id: String,
1082 #[arg(long, default_value = "origin")]
1083 pub remote: String,
1084}
1085
1086#[derive(Clone, Debug, clap::Args)]
1089pub struct ThreadCheckMergeArgs {
1090 pub source: String,
1091 pub target: String,
1092
1093 #[arg(long, default_value = "merge")]
1095 pub gated_action: String,
1096
1097 #[arg(long = "path", value_delimiter = ',')]
1100 pub changed_paths: Vec<String>,
1101
1102 #[arg(long, default_value = "origin")]
1103 pub remote: String,
1104}
1105
1106#[derive(Clone, Debug, clap::Args)]
1108pub struct CollapseArgs {
1109 #[arg(required = true)]
1111 pub states: Vec<String>,
1112
1113 #[arg(long)]
1115 pub into: String,
1116
1117 #[arg(long)]
1119 pub confidence: Option<f32>,
1120}
1121
1122#[derive(Clone, Debug, clap::Args)]
1124pub struct ExpandArgs {
1125 pub reference: String,
1127}
1128
1129#[derive(Clone, Debug, clap::Args)]
1131pub struct ResolveArgs {
1132 pub path: Option<String>,
1134
1135 #[arg(long)]
1137 pub all: bool,
1138
1139 #[arg(long)]
1141 pub list: bool,
1142
1143 #[arg(long, conflicts_with = "theirs")]
1145 pub ours: bool,
1146
1147 #[arg(long, conflicts_with = "ours")]
1149 pub theirs: bool,
1150
1151 #[arg(long)]
1153 pub force: bool,
1154
1155 #[arg(long)]
1157 pub abort: bool,
1158}
1159
1160#[derive(Clone, Debug, clap::Args)]
1163pub struct RemoteOperationArgs {
1164 pub remote: Option<String>,
1166
1167 #[arg(short, long)]
1169 pub thread: Option<String>,
1170
1171 #[arg(long)]
1174 pub insecure: bool,
1175}
1176
1177#[derive(Clone, Debug, clap::Args)]
1179#[command(after_help = "\
1180Git Overlay refs:
1181 A normal push writes refs/heads/<thread> and refs/notes/heddle.
1182 --all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
1183 JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
1184")]
1185pub struct PushArgs {
1186 pub remote: Option<String>,
1188
1189 #[arg(short, long, conflicts_with = "thread_arg")]
1191 pub thread: Option<String>,
1192
1193 #[arg(value_name = "THREAD")]
1195 pub thread_arg: Option<String>,
1196
1197 #[arg(short, long)]
1199 pub state: Option<String>,
1200
1201 #[arg(short, long)]
1203 pub force: bool,
1204
1205 #[arg(long)]
1207 pub all_threads: bool,
1208
1209 #[arg(long)]
1212 pub insecure: bool,
1213
1214 #[arg(long)]
1218 pub dry_run: bool,
1219}
1220
1221impl PushArgs {
1222 pub fn thread_name(&self) -> Option<String> {
1223 self.thread.clone().or_else(|| self.thread_arg.clone())
1224 }
1225}
1226
1227#[derive(Clone, Debug, clap::Args)]
1229#[command(after_help = "\
1230Advanced (hidden) flags:
1231 --lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
1232")]
1233pub struct PullArgs {
1234 #[command(flatten)]
1235 pub remote_op: RemoteOperationArgs,
1236
1237 #[arg(short, long)]
1239 pub local_thread: Option<String>,
1240
1241 #[arg(long, hide = true)]
1243 pub lazy: bool,
1244}
1245
1246#[derive(Clone, Debug, clap::Args)]
1254#[command(after_help = "\
1255Behavior:
1256 Clones native Heddle or Git repositories and checks out the selected default branch. Git transport runs through Sley and does not require a Git executable. Never prompts. Full details: `heddle help clone`.
1257
1258Advanced/planned flags: see `heddle help clone`.
1259
1260Examples:
1261 heddle clone ../native-repo ./clone # local native Heddle repository
1262 heddle clone heddle://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
1263")]
1264pub struct CloneArgs {
1265 pub remote: String,
1267
1268 pub local: String,
1270
1271 #[arg(long)]
1273 pub thread: Option<String>,
1274
1275 #[arg(long)]
1277 pub depth: Option<u32>,
1278
1279 #[arg(long, hide = true)]
1283 pub lazy: bool,
1284
1285 #[arg(long)]
1287 pub insecure: bool,
1288
1289 #[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
1295 pub filter: Option<String>,
1296
1297 #[arg(long, visible_alias = "monorepo")]
1301 pub recursive: bool,
1302}
1303
1304fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
1305 match s {
1306 "blob:none" => Ok(s.to_string()),
1307 other => Err(format!(
1308 "unsupported --filter spec `{other}`; only `blob:none` is supported today"
1309 )),
1310 }
1311}
1312
1313#[derive(Clone, Debug, clap::Args)]
1315pub struct AgentProvenanceBeginArgs {
1316 #[arg(long)]
1318 pub provider: String,
1319
1320 #[arg(long)]
1322 pub model: String,
1323
1324 #[arg(long)]
1326 pub policy: Option<String>,
1327}
1328
1329#[derive(Clone, Debug, clap::Args)]
1331pub struct AgentProvenanceSegmentArgs {
1332 #[arg(long)]
1334 pub provider: String,
1335
1336 #[arg(long)]
1338 pub model: String,
1339
1340 #[arg(long)]
1342 pub policy: Option<String>,
1343}
1344
1345#[derive(Clone, Debug, clap::Args)]
1347pub struct AgentProvenanceEndArgs {
1348 pub session_id: Option<String>,
1350}
1351
1352#[derive(Clone, Debug, clap::Args)]
1354pub struct AgentProvenanceShowArgs {
1355 pub session_id: Option<String>,
1357}
1358
1359#[derive(Clone, Debug, clap::Args)]
1361pub struct AgentProvenanceListArgs {
1362 #[arg(long)]
1364 pub active: bool,
1365}
1366
1367#[derive(Clone, Debug, clap::Args)]
1369pub struct WorktreeAddArgs {
1370 pub path: std::path::PathBuf,
1372
1373 #[arg(long)]
1375 pub thread: Option<String>,
1376
1377 #[arg(long)]
1379 pub from: Option<String>,
1380}
1381
1382#[derive(Clone, Debug, clap::Args)]
1384pub struct WorktreeRemoveArgs {
1385 pub path: std::path::PathBuf,
1387
1388 #[arg(long)]
1390 pub delete_thread: bool,
1391}
1392
1393#[derive(Clone, Debug, clap::Args)]
1395pub struct AgentPresenceListArgs {
1396 #[arg(long)]
1398 pub active: bool,
1399}
1400
1401#[derive(Clone, Debug, clap::Args)]
1403pub struct AgentPresenceShowArgs {
1404 pub session: Option<String>,
1406}
1407
1408#[derive(Clone, Debug, clap::Args)]
1410pub struct AgentPresenceExplainArgs {
1411 pub session: Option<String>,
1413}
1414
1415#[derive(Clone, Debug, clap::Args)]
1417pub struct AgentPresenceCompleteArgs {
1418 #[arg(long)]
1420 pub session: Option<String>,
1421}
1422
1423#[derive(Clone, Debug, clap::Args)]
1425pub struct AgentReserveArgs {
1426 #[arg(long)]
1428 pub thread: String,
1429
1430 #[arg(long)]
1432 pub anchor: Option<String>,
1433
1434 #[arg(long)]
1436 pub task: Option<String>,
1437
1438 #[arg(long)]
1440 pub task_id: Option<String>,
1441
1442 #[arg(long, value_name = "PID")]
1444 pub hold_for_pid: Option<u32>,
1445}
1446
1447#[derive(Clone, Debug, clap::Args)]
1449pub struct AgentHeartbeatArgs {
1450 #[arg(long)]
1452 pub lease: String,
1453
1454 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1456 pub token: String,
1457}
1458
1459#[derive(Clone, Debug, clap::Args)]
1461pub struct AgentReleaseArgs {
1462 #[arg(long)]
1464 pub lease: String,
1465
1466 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1468 pub token: String,
1469
1470 #[arg(long, default_value = "complete")]
1472 pub status: AgentReleaseStatusArg,
1473}
1474
1475#[derive(Clone, Debug, clap::ValueEnum)]
1476pub enum AgentReleaseStatusArg {
1477 Complete,
1478 Abandoned,
1479}
1480
1481#[derive(Clone, Debug, clap::Args)]
1483pub struct AgentApiListArgs {
1484 #[arg(long)]
1486 pub thread: Option<String>,
1487
1488 #[arg(long)]
1490 pub alive_only: bool,
1491}
1492
1493#[derive(Clone, Debug, clap::ValueEnum)]
1494pub enum AgentTaskStatusArg {
1495 Open,
1496 InProgress,
1497 Blocked,
1498 Complete,
1499 Abandoned,
1500}
1501
1502#[derive(Clone, Debug, clap::Args)]
1504pub struct AgentTaskCreateArgs {
1505 #[arg(long)]
1507 pub task_id: Option<String>,
1508
1509 #[arg(long)]
1511 pub title: String,
1512
1513 #[arg(long)]
1515 pub body: Option<String>,
1516
1517 #[arg(long)]
1519 pub thread: String,
1520
1521 #[arg(long)]
1523 pub base_state: Option<String>,
1524
1525 #[arg(long)]
1527 pub base_root: Option<String>,
1528
1529 #[arg(long)]
1531 pub parent_task_id: Option<String>,
1532
1533 #[arg(long)]
1535 pub coordination_discussion_id: Option<String>,
1536
1537 #[arg(long)]
1539 pub allow_offline: bool,
1540
1541 #[arg(long)]
1543 pub delegated_by: Option<String>,
1544}
1545
1546#[derive(Clone, Debug, clap::Args)]
1548pub struct AgentTaskListArgs {
1549 #[arg(long)]
1551 pub thread: Option<String>,
1552
1553 #[arg(long)]
1555 pub status: Option<AgentTaskStatusArg>,
1556}
1557
1558#[derive(Clone, Debug, clap::Args)]
1560pub struct AgentTaskShowArgs {
1561 pub task_id: String,
1563}
1564
1565#[derive(Clone, Debug, clap::Args)]
1567pub struct AgentTaskUpdateArgs {
1568 pub task_id: String,
1570
1571 #[arg(long)]
1573 pub title: Option<String>,
1574
1575 #[arg(long)]
1577 pub body: Option<String>,
1578
1579 #[arg(long)]
1581 pub status: Option<AgentTaskStatusArg>,
1582
1583 #[arg(long)]
1585 pub thread: Option<String>,
1586
1587 #[arg(long)]
1589 pub base_state: Option<String>,
1590
1591 #[arg(long)]
1593 pub base_root: Option<String>,
1594
1595 #[arg(long)]
1597 pub parent_task_id: Option<String>,
1598
1599 #[arg(long)]
1601 pub coordination_discussion_id: Option<String>,
1602
1603 #[arg(long, conflicts_with = "no_allow_offline")]
1605 pub allow_offline: bool,
1606
1607 #[arg(long, conflicts_with = "allow_offline")]
1609 pub no_allow_offline: bool,
1610
1611 #[arg(long)]
1613 pub delegated_by: Option<String>,
1614}
1615
1616#[derive(Clone, Debug, clap::Args)]
1618pub struct AgentFanoutPlanArgs {
1619 #[arg(long)]
1621 pub title: String,
1622
1623 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1625 pub lane: Vec<String>,
1626
1627 #[arg(long)]
1629 pub coordination_discussion_id: Option<String>,
1630}
1631
1632#[derive(Clone, Debug, clap::Args)]
1634pub struct AgentFanoutStartArgs {
1635 #[arg(long)]
1637 pub title: String,
1638
1639 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1641 pub lane: Vec<String>,
1642
1643 #[arg(long)]
1645 pub coordination_discussion_id: Option<String>,
1646}
1647
1648#[derive(Clone, Debug, clap::Args)]
1650pub struct AgentCaptureArgs {
1651 #[arg(long)]
1653 pub lease: String,
1654
1655 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1657 pub token: String,
1658
1659 #[arg(long, short = 'm', alias = "intent")]
1661 pub message: Option<String>,
1662
1663 #[arg(long, value_parser = parse_confidence)]
1665 pub confidence: Option<f32>,
1666}
1667
1668#[derive(Clone, Debug, clap::Args)]
1670pub struct AgentReadyArgs {
1671 #[arg(long)]
1673 pub lease: String,
1674
1675 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1677 pub token: String,
1678
1679 #[arg(long, short = 'm')]
1681 pub message: Option<String>,
1682
1683 #[arg(long, value_parser = parse_confidence)]
1685 pub confidence: Option<f32>,
1686}
1687
1688#[derive(Clone, Debug, clap::Args)]
1696pub struct WatchArgs {
1697 #[arg(long, value_name = "DURATION")]
1701 pub since: Option<String>,
1702
1703 #[arg(long, value_name = "KINDS")]
1707 pub filter: Option<String>,
1708
1709 #[arg(long, hide = true)]
1712 pub max_iterations: Option<usize>,
1713
1714 #[arg(long, hide = true)]
1717 pub poll_interval_ms: Option<u64>,
1718}
1719
1720#[cfg(test)]
1726mod capture_message_alias_tests {
1727 use clap::Parser;
1728
1729 use crate::cli::{Cli, Commands, SnapshotArgs};
1730
1731 fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
1732 let mut argv: Vec<&str> = vec!["heddle", "capture"];
1733 argv.extend_from_slice(extra);
1734 let cli = Cli::try_parse_from(argv)?;
1735 match cli.command {
1736 Commands::Capture(args) => Ok(args),
1737 _ => panic!("expected Commands::Capture"),
1738 }
1739 }
1740
1741 #[test]
1742 fn capture_accepts_message_alias() {
1743 let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
1744 assert_eq!(args.intent.as_deref(), Some("my change"));
1745 }
1746
1747 #[test]
1748 fn capture_accepts_intent_long_form() {
1749 let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
1750 assert_eq!(args.intent.as_deref(), Some("my change"));
1751 }
1752
1753 #[test]
1754 fn capture_accepts_short_m() {
1755 let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
1756 assert_eq!(args.intent.as_deref(), Some("my change"));
1757 }
1758
1759 #[test]
1760 fn capture_rejects_non_finite_or_out_of_range_confidence() {
1761 for value in ["NaN", "inf", "-0.1", "1.7"] {
1762 let confidence_arg = format!("--confidence={value}");
1763 let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
1764 .expect_err("invalid confidence should fail to parse");
1765 assert!(
1766 err.to_string()
1767 .contains("confidence must be a finite number from 0.0 to 1.0"),
1768 "unexpected parse error for {value}: {err}"
1769 );
1770 }
1771 }
1772}
1773
1774#[cfg(test)]
1775mod clone_filter_tests {
1776 use clap::Parser;
1777
1778 use crate::cli::{Cli, CloneArgs, Commands};
1779
1780 fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
1781 let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
1782 argv.extend_from_slice(extra);
1783 let cli = Cli::try_parse_from(argv)?;
1784 match cli.command {
1785 Commands::Clone(args) => Ok(args),
1786 _ => panic!("expected Commands::Clone"),
1787 }
1788 }
1789
1790 #[test]
1791 fn parses_clone_filter_blob_none() {
1792 let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
1793 assert_eq!(args.filter.as_deref(), Some("blob:none"));
1794 assert!(!args.lazy);
1795 }
1796
1797 #[test]
1798 fn rejects_unknown_filter_spec() {
1799 let err = parse_clone(&["--filter", "tree:0"])
1800 .expect_err("unknown --filter spec should fail to parse");
1801 let msg = err.to_string();
1802 assert!(
1803 msg.contains("tree:0") && msg.contains("blob:none"),
1804 "error should name the bad spec and the supported one: {msg}"
1805 );
1806 }
1807}