#[cfg(feature = "git-overlay")]
use super::commands_git_projection::SyncCommands;
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle init # initialize here; existing Git becomes Git Overlay
heddle init my-project # initialize a native Heddle subdirectory
heddle init --principal-name 'Ada Lovelace' --principal-email ada@example.com
")]
pub struct InitArgs {
pub path: Option<std::path::PathBuf>,
#[arg(long)]
pub principal_name: Option<String>,
#[arg(long)]
pub principal_email: Option<String>,
#[arg(long)]
pub install_harnesses: Option<String>,
#[arg(long)]
pub no_harness_install: bool,
#[arg(long, visible_alias = "scope", default_value = "repo")]
pub harness_install_scope: String,
#[arg(long)]
pub harness_install_force: bool,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle adopt # adopt all local Git refs into native Heddle storage
heddle adopt --ref main # adopt one branch or tag
heddle adopt ../repo --ref main --ref v1.0 # adopt selected refs in another repo
Adoption imports Git refs, makes Heddle the source authority, and retains `.git` for explicit Git Projection. Normal Git Overlay setup uses `heddle init` instead.
")]
pub struct AdoptArgs {
pub path: Option<std::path::PathBuf>,
#[arg(long = "ref", value_name = "REF")]
pub refs: Vec<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct DoctorArgs {
#[arg(long, global = false)]
pub profile: bool,
#[command(subcommand)]
pub command: Option<DoctorCommands>,
}
#[derive(Clone, Debug, clap::Subcommand)]
pub enum DoctorCommands {
Docs(DoctorDocsArgs),
Schemas(DoctorSchemasArgs),
}
#[derive(Clone, Debug, clap::Args)]
pub struct DoctorDocsArgs {
#[arg(long, value_name = "PATH")]
pub path: Vec<std::path::PathBuf>,
#[arg(long)]
pub all: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct DoctorSchemasArgs {
#[arg(long)]
pub update_docs: bool,
}
fn parse_confidence(s: &str) -> Result<f32, String> {
let value = s
.parse::<f32>()
.map_err(|_| format!("confidence must be a finite number from 0.0 to 1.0, got `{s}`"))?;
if !value.is_finite() || !(0.0..=1.0).contains(&value) {
return Err(format!(
"confidence must be a finite number from 0.0 to 1.0, got `{s}`"
));
}
Ok(value)
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle capture -m 'add login route' # capture the worktree with intent
heddle capture -m 'wip' --confidence 0.6 # honest confidence on a draft step
Agent automation flags (provider/model/session/policy/split) are hidden here.
Run `heddle help agent-flags`, or `heddle capture --help-agent` to list them inline.
")]
pub struct SnapshotArgs {
#[arg(long, hide = true)]
pub help_agent: bool,
#[arg(short = 'm', long, visible_alias = "message")]
pub intent: Option<String>,
#[arg(long, value_parser = parse_confidence)]
pub confidence: Option<f32>,
#[arg(short, long)]
pub force: bool,
#[arg(long, hide = true)]
pub agent_provider: Option<String>,
#[arg(long, hide = true)]
pub agent_model: Option<String>,
#[arg(long, hide = true)]
pub agent_session: Option<String>,
#[arg(long, hide = true)]
pub agent_segment: Option<String>,
#[arg(long, hide = true)]
pub policy: Option<String>,
#[arg(long, hide = true)]
pub no_policy: bool,
#[arg(long, hide = true)]
pub no_agent: bool,
#[arg(long, hide = true)]
pub split: bool,
#[arg(long, hide = true, requires = "split")]
pub into: Option<String>,
#[arg(long = "path", hide = true, requires = "split", value_name = "PATH")]
pub paths: Vec<String>,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle capture -m 'add login route'
heddle commit
heddle commit -m 'add login route'
Behavior:
Commits the complete captured tree and replaces the Git index with that tree.
Git pre-commit and commit-msg hooks are not run.
")]
pub struct CommitArgs {
#[arg(short = 'm', long = "message")]
pub message: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle log # walk the current thread
heddle log --oneline -n 20 # 20 most recent states in compact form
heddle log --timeline # show agent timeline tool-call cursor
heddle log --reflog # include re-attributed history
heddle log --path src/auth.rs # restrict to states touching a path
")]
pub struct LogArgs {
pub state: Option<String>,
#[arg(short = 'n', long, default_value = "20")]
pub limit: usize,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub graph: bool,
#[arg(long)]
pub oneline: bool,
#[arg(long)]
pub reflog: bool,
#[arg(long)]
pub timeline: bool,
#[arg(long, default_value = "main")]
pub thread: String,
#[arg(long)]
pub agent: Option<String>,
#[arg(long = "path", value_name = "PATH")]
pub paths: Vec<String>,
#[arg(long, value_name = "STATE")]
pub since: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineArgs {
#[command(subcommand)]
pub command: TimelineCommands,
}
#[derive(Clone, Debug, clap::Subcommand)]
pub enum TimelineCommands {
Status(TimelineStatusArgs),
#[command(name = "record-start")]
RecordStart(TimelineRecordStartArgs),
#[command(name = "record-finish")]
RecordFinish(TimelineRecordFinishArgs),
#[command(after_help = "\
Examples:
heddle timeline fork --step tls-abc --branch tlb-experiment
heddle timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
")]
Fork(TimelineForkArgs),
#[command(after_help = "\
Examples:
heddle timeline reset --step tls-abc
heddle timeline reset --tool-call call_123 --materialize
")]
Reset(TimelineResetArgs),
Recover(TimelineRecoverArgs),
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineTargetArgs {
#[arg(long, default_value = "main")]
pub thread: String,
#[arg(long = "from-branch", value_name = "BRANCH")]
pub from_branch: Option<String>,
#[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
pub step: Option<String>,
#[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
pub tool_call: Option<String>,
#[arg(long, default_value = "opencode")]
pub harness: String,
#[arg(long)]
pub session: Option<String>,
#[arg(long)]
pub message: Option<String>,
#[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
pub undo: bool,
#[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
pub redo: bool,
#[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
pub current: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineForkArgs {
#[command(flatten)]
pub target: TimelineTargetArgs,
#[arg(long, value_name = "BRANCH")]
pub branch: Option<String>,
#[arg(long, default_value = "explicit-fork")]
pub reason: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineResetArgs {
#[command(flatten)]
pub target: TimelineTargetArgs,
#[arg(long)]
pub materialize: bool,
#[arg(long, default_value = "fail-if-dirty")]
pub mode: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineRecoverArgs {
#[arg(long, default_value = "main")]
pub thread: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineStatusArgs {
#[arg(long, default_value = "main")]
pub thread: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineRecordToolArgs {
#[arg(long, default_value = "main")]
pub thread: String,
#[arg(long, default_value = "opencode")]
pub harness: String,
#[arg(long)]
pub session: Option<String>,
#[arg(long)]
pub message: Option<String>,
#[arg(long = "tool-call")]
pub tool_call: String,
#[arg(long = "step-id")]
pub step_id: Option<String>,
#[arg(long = "branch")]
pub branch: Option<String>,
#[arg(long = "summary")]
pub summary: Option<String>,
#[arg(long = "payload-hash")]
pub payload_hash: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineRecordStartArgs {
#[command(flatten)]
pub tool: TimelineRecordToolArgs,
#[arg(long = "tool-name", default_value = "tool")]
pub tool_name: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TimelineRecordFinishArgs {
#[command(flatten)]
pub tool: TimelineRecordToolArgs,
#[arg(long, default_value = "succeeded")]
pub status: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct RetroArgs {
#[arg(long)]
pub since: Option<String>,
#[arg(long)]
pub include_merges: bool,
#[arg(long)]
pub include_undos: bool,
#[arg(long = "full", alias = "expand")]
pub full: bool,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Patch compatibility:
--patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
")]
pub struct DiffArgs {
pub from: Option<String>,
pub to: Option<String>,
#[arg(long)]
pub semantic: bool,
#[arg(long)]
pub stat: bool,
#[arg(long)]
pub name_only: bool,
#[arg(short = 'U', long = "unified", default_value_t = 3)]
pub unified: usize,
#[arg(long)]
pub context: bool,
#[arg(short = 'p', long = "patch")]
pub patch: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct RevertArgs {
pub state: String,
#[arg(short = 'm', long)]
pub message: Option<String>,
#[arg(long)]
pub no_commit: bool,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle undo # roll back the most recent operation
heddle undo -n 3 # roll back the last three operations
heddle undo --recover # restore the state preserved by the last undo
heddle undo --list # preview undoable operations on this thread
heddle undo --dry-run # show what would change without applying
Undoable operations:
- heddle capture (restores HEAD to the pre-capture parent)
- heddle land (non-FF) (restores HEAD + both thread refs)
- heddle land (FF) (restores HEAD + the landed-into thread ref to
the pre-merge tip; the merged-in thread is
untouched.)
- heddle thread switch (restores HEAD to the previous thread state)
- heddle thread create/drop/rename
- heddle thread marker create/drop
- heddle redact apply (with --allow-redact-undo; removes the
redaction record so future materializes
restore the original blob bytes. Refused
when a Purge has destroyed the bytes.)
- heddle undo --redo re-apply the most recently undone operation
Not undoable (file a follow-up if you need one):
- heddle push / pull (remote-affecting; out of scope)
- heddle redact purge apply (destructive by design; irreversible)
- heddle start <name> --path <dir> (refused while the materialized worktree
still exists — run `heddle thread drop
<name> --delete-thread` first, then
re-run `heddle undo`)
- cross-worktree shared-backend undo (no worktree registry yet; single-
worktree usage is the supported
configuration for 0.3)
")]
pub struct UndoArgs {
#[arg(short = 'n', long, default_value = "1")]
pub steps: usize,
#[arg(long)]
pub list: bool,
#[arg(long, default_value = "20")]
pub depth: usize,
#[arg(long, visible_alias = "dry-run")]
pub preview: bool,
#[arg(long, conflicts_with = "list")]
pub redo: bool,
#[arg(
long,
conflicts_with_all = ["steps", "list", "preview", "redo", "allow_redact_undo"]
)]
pub recover: bool,
#[arg(long)]
pub allow_redact_undo: bool,
}
#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
pub enum WorkspaceModeArg {
Auto,
Materialized,
Virtualized,
Solid,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Examples:
heddle start feature/auth --path ../feature-auth # create an isolated checkout
heddle start scratch --path ../scratch # place the checkout explicitly
heddle start fix-flake --task 'fix CI flake' # attach a task description
Isolated 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.
`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.
Advanced (hidden) flags:
--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.
")]
pub struct ThreadStartArgs {
pub name: String,
#[arg(long)]
pub from: Option<String>,
#[arg(long)]
pub path: Option<std::path::PathBuf>,
#[arg(long, value_enum, default_value_t = WorkspaceModeArg::Auto)]
pub workspace: WorkspaceModeArg,
#[arg(long, hide = true)]
pub agent_provider: Option<String>,
#[arg(long, hide = true)]
pub agent_model: Option<String>,
#[arg(long)]
pub task: Option<String>,
#[arg(long, hide = true)]
pub parent_thread: Option<String>,
#[arg(long, hide = true)]
pub automated: bool,
#[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
pub print_cd_path: bool,
#[arg(
long,
overrides_with = "no_daemon",
action = clap::ArgAction::SetTrue,
default_value_t = true,
hide = true,
)]
pub daemon: bool,
#[arg(
long,
overrides_with = "daemon",
action = clap::ArgAction::SetTrue,
hide = true,
)]
pub no_daemon: bool,
#[arg(long)]
pub interactive_setup: bool,
#[arg(
long,
overrides_with = "no_shared_target",
action = clap::ArgAction::SetTrue,
hide = true,
)]
pub shared_target: bool,
#[arg(
long,
overrides_with = "shared_target",
action = clap::ArgAction::SetTrue,
hide = true,
)]
pub no_shared_target: bool,
#[arg(long)]
pub hydrate: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct TryArgs {
#[arg(long)]
pub name: Option<String>,
#[arg(long, value_enum, default_value_t = WorkspaceModeArg::Materialized)]
pub workspace: WorkspaceModeArg,
#[arg(long = "auto-merge")]
pub auto_merge: bool,
#[arg(long = "keep-on-success")]
pub keep_on_success: bool,
#[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
pub command: Vec<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct RunArgs {
#[arg(long = "thread")]
pub thread: Option<String>,
#[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
pub command: Vec<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ReadyArgs {
#[arg(long = "thread")]
pub thread: Option<String>,
#[arg(short = 'm', long)]
pub message: Option<String>,
#[arg(long, value_parser = parse_confidence)]
pub confidence: Option<f32>,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct SyncArgs {
#[cfg(feature = "git-overlay")]
#[command(subcommand)]
pub command: Option<SyncCommands>,
#[arg(long = "thread")]
pub thread: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct LandArgs {
#[arg(long = "thread")]
pub thread: Option<String>,
#[arg(long = "threads", value_delimiter = ',')]
pub threads: Vec<String>,
#[arg(short = 'm', long)]
pub message: Option<String>,
#[arg(long)]
pub no_squash: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadShowArgs {
pub thread: Option<String>,
#[arg(long)]
pub watch: bool,
#[arg(long, hide = true)]
pub watch_iterations: Option<usize>,
#[arg(long, hide = true)]
pub watch_interval_ms: Option<u64>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadCapturesArgs {
pub thread: Option<String>,
#[arg(long, default_value_t = 20)]
pub limit: usize,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadNameArgs {
pub thread: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadRenameArgs {
pub old: String,
pub new: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadPromoteArgs {
pub thread: String,
#[arg(long)]
pub path: Option<std::path::PathBuf>,
#[arg(long)]
pub force: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadMoveArgs {
pub from: String,
pub to: String,
#[arg(long = "path", required = true, value_name = "PATH")]
pub paths: Vec<String>,
#[arg(short = 'm', long)]
pub message: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadAbsorbArgs {
pub thread: String,
#[arg(long)]
pub into: Option<String>,
#[arg(short = 'm', long)]
pub message: Option<String>,
#[arg(long)]
pub preview: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadResolveArgs {
pub thread: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadDropArgs {
pub thread: String,
#[arg(long)]
pub delete_thread: bool,
#[arg(short, long)]
pub force: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadApproveArgs {
pub source: String,
pub target: String,
#[arg(long)]
pub note: Option<String>,
#[arg(long, default_value = "origin")]
pub remote: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadApprovalsArgs {
pub source: String,
pub target: String,
#[arg(long, default_value = "origin")]
pub remote: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadRevokeApprovalArgs {
pub id: String,
#[arg(long, default_value = "origin")]
pub remote: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ThreadCheckMergeArgs {
pub source: String,
pub target: String,
#[arg(long, default_value = "merge")]
pub gated_action: String,
#[arg(long = "path", value_delimiter = ',')]
pub changed_paths: Vec<String>,
#[arg(long, default_value = "origin")]
pub remote: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct CollapseArgs {
#[arg(required = true)]
pub states: Vec<String>,
#[arg(long)]
pub into: String,
#[arg(long)]
pub confidence: Option<f32>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ExpandArgs {
pub reference: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct ResolveArgs {
pub path: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub list: bool,
#[arg(long, conflicts_with = "theirs")]
pub ours: bool,
#[arg(long, conflicts_with = "ours")]
pub theirs: bool,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub abort: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct RemoteOperationArgs {
pub remote: Option<String>,
#[arg(short, long)]
pub thread: Option<String>,
#[arg(long)]
pub insecure: bool,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Git Overlay refs:
A normal push writes refs/heads/<thread> and refs/notes/heddle.
--all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
")]
pub struct PushArgs {
pub remote: Option<String>,
#[arg(short, long, conflicts_with = "thread_arg")]
pub thread: Option<String>,
#[arg(value_name = "THREAD")]
pub thread_arg: Option<String>,
#[arg(short, long)]
pub state: Option<String>,
#[arg(short, long)]
pub force: bool,
#[arg(long)]
pub all_threads: bool,
#[arg(long)]
pub insecure: bool,
#[arg(long)]
pub dry_run: bool,
}
impl PushArgs {
pub fn thread_name(&self) -> Option<String> {
self.thread.clone().or_else(|| self.thread_arg.clone())
}
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Advanced (hidden) flags:
--lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
")]
pub struct PullArgs {
#[command(flatten)]
pub remote_op: RemoteOperationArgs,
#[arg(short, long)]
pub local_thread: Option<String>,
#[arg(long, hide = true)]
pub lazy: bool,
}
#[derive(Clone, Debug, clap::Args)]
#[command(after_help = "\
Behavior:
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`.
Advanced/planned flags: see `heddle help clone`.
Examples:
heddle clone ../native-repo ./clone # local native Heddle repository
heddle clone heddle://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
")]
pub struct CloneArgs {
pub remote: String,
pub local: String,
#[arg(long)]
pub thread: Option<String>,
#[arg(long)]
pub depth: Option<u32>,
#[arg(long, hide = true)]
pub lazy: bool,
#[arg(long)]
pub insecure: bool,
#[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
pub filter: Option<String>,
#[arg(long, visible_alias = "monorepo")]
pub recursive: bool,
}
fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
match s {
"blob:none" => Ok(s.to_string()),
other => Err(format!(
"unsupported --filter spec `{other}`; only `blob:none` is supported today"
)),
}
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentProvenanceBeginArgs {
#[arg(long)]
pub provider: String,
#[arg(long)]
pub model: String,
#[arg(long)]
pub policy: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentProvenanceSegmentArgs {
#[arg(long)]
pub provider: String,
#[arg(long)]
pub model: String,
#[arg(long)]
pub policy: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentProvenanceEndArgs {
pub session_id: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentProvenanceShowArgs {
pub session_id: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentProvenanceListArgs {
#[arg(long)]
pub active: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct WorktreeAddArgs {
pub path: std::path::PathBuf,
#[arg(long)]
pub thread: Option<String>,
#[arg(long)]
pub from: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct WorktreeRemoveArgs {
pub path: std::path::PathBuf,
#[arg(long)]
pub delete_thread: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentPresenceListArgs {
#[arg(long)]
pub active: bool,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentPresenceShowArgs {
pub session: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentPresenceExplainArgs {
pub session: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentPresenceCompleteArgs {
#[arg(long)]
pub session: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentReserveArgs {
#[arg(long)]
pub thread: String,
#[arg(long)]
pub anchor: Option<String>,
#[arg(long)]
pub task: Option<String>,
#[arg(long)]
pub task_id: Option<String>,
#[arg(long, value_name = "PID")]
pub hold_for_pid: Option<u32>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentHeartbeatArgs {
#[arg(long)]
pub lease: String,
#[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
pub token: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentReleaseArgs {
#[arg(long)]
pub lease: String,
#[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
pub token: String,
#[arg(long, default_value = "complete")]
pub status: AgentReleaseStatusArg,
}
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum AgentReleaseStatusArg {
Complete,
Abandoned,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentApiListArgs {
#[arg(long)]
pub thread: Option<String>,
#[arg(long)]
pub alive_only: bool,
}
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum AgentTaskStatusArg {
Open,
InProgress,
Blocked,
Complete,
Abandoned,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentTaskCreateArgs {
#[arg(long)]
pub task_id: Option<String>,
#[arg(long)]
pub title: String,
#[arg(long)]
pub body: Option<String>,
#[arg(long)]
pub thread: String,
#[arg(long)]
pub base_state: Option<String>,
#[arg(long)]
pub base_root: Option<String>,
#[arg(long)]
pub parent_task_id: Option<String>,
#[arg(long)]
pub coordination_discussion_id: Option<String>,
#[arg(long)]
pub allow_offline: bool,
#[arg(long)]
pub delegated_by: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentTaskListArgs {
#[arg(long)]
pub thread: Option<String>,
#[arg(long)]
pub status: Option<AgentTaskStatusArg>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentTaskShowArgs {
pub task_id: String,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentTaskUpdateArgs {
pub task_id: String,
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub body: Option<String>,
#[arg(long)]
pub status: Option<AgentTaskStatusArg>,
#[arg(long)]
pub thread: Option<String>,
#[arg(long)]
pub base_state: Option<String>,
#[arg(long)]
pub base_root: Option<String>,
#[arg(long)]
pub parent_task_id: Option<String>,
#[arg(long)]
pub coordination_discussion_id: Option<String>,
#[arg(long, conflicts_with = "no_allow_offline")]
pub allow_offline: bool,
#[arg(long, conflicts_with = "allow_offline")]
pub no_allow_offline: bool,
#[arg(long)]
pub delegated_by: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentFanoutPlanArgs {
#[arg(long)]
pub title: String,
#[arg(long, value_name = "THREAD=PATH:TITLE")]
pub lane: Vec<String>,
#[arg(long)]
pub coordination_discussion_id: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentFanoutStartArgs {
#[arg(long)]
pub title: String,
#[arg(long, value_name = "THREAD=PATH:TITLE")]
pub lane: Vec<String>,
#[arg(long)]
pub coordination_discussion_id: Option<String>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentCaptureArgs {
#[arg(long)]
pub lease: String,
#[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
pub token: String,
#[arg(long, short = 'm', alias = "intent")]
pub message: Option<String>,
#[arg(long, value_parser = parse_confidence)]
pub confidence: Option<f32>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct AgentReadyArgs {
#[arg(long)]
pub lease: String,
#[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
pub token: String,
#[arg(long, short = 'm')]
pub message: Option<String>,
#[arg(long, value_parser = parse_confidence)]
pub confidence: Option<f32>,
}
#[derive(Clone, Debug, clap::Args)]
pub struct WatchArgs {
#[arg(long, value_name = "DURATION")]
pub since: Option<String>,
#[arg(long, value_name = "KINDS")]
pub filter: Option<String>,
#[arg(long, hide = true)]
pub max_iterations: Option<usize>,
#[arg(long, hide = true)]
pub poll_interval_ms: Option<u64>,
}
#[cfg(test)]
mod capture_message_alias_tests {
use clap::Parser;
use crate::cli::{Cli, Commands, SnapshotArgs};
fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
let mut argv: Vec<&str> = vec!["heddle", "capture"];
argv.extend_from_slice(extra);
let cli = Cli::try_parse_from(argv)?;
match cli.command {
Commands::Capture(args) => Ok(args),
_ => panic!("expected Commands::Capture"),
}
}
#[test]
fn capture_accepts_message_alias() {
let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
assert_eq!(args.intent.as_deref(), Some("my change"));
}
#[test]
fn capture_accepts_intent_long_form() {
let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
assert_eq!(args.intent.as_deref(), Some("my change"));
}
#[test]
fn capture_accepts_short_m() {
let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
assert_eq!(args.intent.as_deref(), Some("my change"));
}
#[test]
fn capture_rejects_non_finite_or_out_of_range_confidence() {
for value in ["NaN", "inf", "-0.1", "1.7"] {
let confidence_arg = format!("--confidence={value}");
let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
.expect_err("invalid confidence should fail to parse");
assert!(
err.to_string()
.contains("confidence must be a finite number from 0.0 to 1.0"),
"unexpected parse error for {value}: {err}"
);
}
}
}
#[cfg(test)]
mod clone_filter_tests {
use clap::Parser;
use crate::cli::{Cli, CloneArgs, Commands};
fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
argv.extend_from_slice(extra);
let cli = Cli::try_parse_from(argv)?;
match cli.command {
Commands::Clone(args) => Ok(args),
_ => panic!("expected Commands::Clone"),
}
}
#[test]
fn parses_clone_filter_blob_none() {
let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
assert_eq!(args.filter.as_deref(), Some("blob:none"));
assert!(!args.lazy);
}
#[test]
fn rejects_unknown_filter_spec() {
let err = parse_clone(&["--filter", "tree:0"])
.expect_err("unknown --filter spec should fail to parse");
let msg = err.to_string();
assert!(
msg.contains("tree:0") && msg.contains("blob:none"),
"error should name the bad spec and the supported one: {msg}"
);
}
}