cli

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package cli wires the `clu` command-line interface on top of internal/store.

Index

Constants

This section is empty.

Variables

View Source
var Version = ""

Version is set via -ldflags "-X github.com/arjia-labs/clu/internal/cli.Version=…" at release time. Falls back to module/VCS info from runtime/debug when empty.

Functions

func Run

func Run(ctx context.Context, stdout, stderr io.Writer, args []string) int

Run is the entrypoint used by main and the tests.

Types

type AgentCmd

type AgentCmd struct {
	Start AgentStartCmd `cmd:"" help:"Launch a declared agent (config.yaml command + prompts), heartbeating while it runs."`
	Ls    AgentLsCmd    `cmd:"" aliases:"list" help:"List declared agents (config) with an active indicator."`
	Show  AgentShowCmd  `cmd:"" help:"Show one agent's full record."`
	Gc    AgentGcCmd    `cmd:"" help:"Forcibly drop stale active_agents rows (older than --stale-seconds)."`
}

type AgentGcCmd

type AgentGcCmd struct {
	StaleSec int64 `name:"stale-seconds" default:"30" help:"Delete rows older than this."`
}

func (*AgentGcCmd) Run

func (c *AgentGcCmd) Run(r *runCtx) error

type AgentLsCmd

type AgentLsCmd struct {
	StaleSec int64 `name:"stale-seconds" default:"30" help:"Consider rows older than this dead."`
}

func (*AgentLsCmd) Run

func (c *AgentLsCmd) Run(r *runCtx) error

type AgentShowCmd

type AgentShowCmd struct {
	Name string `arg:"" help:"Agent name."`
}

func (*AgentShowCmd) Run

func (c *AgentShowCmd) Run(r *runCtx) error

type AgentStartCmd

type AgentStartCmd struct {
	Name  string   `arg:"" help:"Agent name (declared in config.yaml)."`
	Print bool     `name:"print" aliases:"dry-run" help:"Print the assembled command and exit without launching."`
	Rest  []string `arg:"" optional:"" name:"args" passthrough:"" help:"Extra arguments forwarded to the agent command."`
}

AgentStartCmd launches a declared agent: it assembles a command from the agent's config.yaml launch spec and execs it in the foreground, inheriting the terminal. While the child runs, a heartbeat keeps the agent visible as live in `clu agent ls`; the row is cleared on exit.

Prompt files are layered: the shared base (<dir>/agents/_shared/*.md, sorted) comes first, then the agent's own prompts, so a persona refines the shared contract rather than restating it. An optional startup_prompt is passed as the command's trailing positional — the agent's first message.

This is a thin launcher, not a supervisor — no daemonizing, no restart-on-crash. Use --print to see the assembled command without running it (handy for wiring clu into your own launcher).

func (*AgentStartCmd) Run

func (c *AgentStartCmd) Run(r *runCtx) error

type ApproveCmd

type ApproveCmd struct {
	ID     string `arg:"" help:"Checkpoint issue ID."`
	As     string `` /* 198-byte string literal not displayed */
	Reason string `name:"reason" help:"Optional note appended to the issue when approving."`
}

ApproveCmd is sugar for `clu checkpoint pass`. Mirrors the pattern of assign/tag/link — a short verb that wraps a longer command.

func (*ApproveCmd) Run

func (c *ApproveCmd) Run(r *runCtx) error

type AssignCmd

type AssignCmd struct {
	ID string `arg:"" help:"Issue ID."`
	To string `arg:"" optional:"" help:"Assignee name (empty clears)."`
}

AssignCmd is sugar for `clu update <id> --assignee <who>`. Pass an empty <who> to clear the assignee.

func (*AssignCmd) Run

func (c *AssignCmd) Run(r *runCtx) error

type BatchCmd

type BatchCmd struct {
	File       string `arg:"" optional:"" help:"JSON file (default: stdin)."`
	Docs       bool   `name:"docs" help:"Print the full batch JSON format reference (fields, semantics, example) and exit."`
	DryRun     bool   `name:"dry-run" help:"Validate and report stats without writing anything."`
	Group      string `` /* 180-byte string literal not displayed */
	OnExisting string `` /* 178-byte string literal not displayed */
	Agent      string `short:"a" name:"agent" default:"${user}" help:"Identity recorded as the actor on the created issues' audit events."`
}

BatchCmd instantiates a whole issue graph from a single JSON document in one transaction. The producer (any script / "codemode" / generator) emits the graph; clu validates it (acyclic, all refs resolve, fields valid), allocates real IDs, and writes it atomically. Built for scale — a thousand+ issues with complex deps in one shot.

node gen.js | clu batch --dry-run   # validate + stats, write nothing
node gen.js | clu batch --json      # commit; returns {alias: real-id}

func (*BatchCmd) Run

func (c *BatchCmd) Run(r *runCtx) error

type BlockedCmd

type BlockedCmd struct {
	Limit int    `short:"n" name:"limit" default:"20" help:"Limit results (0 = unlimited)."`
	Agent string `short:"a" help:"Lane to query (default: unassigned)."`
}

func (*BlockedCmd) Run

func (c *BlockedCmd) Run(r *runCtx) error

type BriefCmd

type BriefCmd struct{}

BriefCmd prints the workflow context for an agent: the agent-facing manual (AGENTS.md, embedded at build time), the agents declared in the project's config.yaml, who's currently live (heartbeat), and any persisted memories.

Intended for `clu brief | $YOUR_AGENT_RUNTIME` at session start so a fresh agent loads the right context in one shot.

func (*BriefCmd) Run

func (c *BriefCmd) Run(r *runCtx) error

type CLI

type CLI struct {
	Dir   string `` /* 197-byte string literal not displayed */
	JSON  bool   `name:"json" help:"Emit machine-readable JSON instead of human output."`
	Quiet bool   `short:"q" name:"quiet" help:"Suppress non-essential output (errors still go to stderr)."`

	// --- issues: the core CRUD + state loop ---
	Init    InitCmd    `cmd:"" group:"issues" help:"Initialize the database in the current directory."`
	Create  CreateCmd  `cmd:"" group:"issues" help:"Create a new issue."`
	List    ListCmd    `cmd:"" group:"issues" aliases:"ls" help:"List issues."`
	Ready   ReadyCmd   `cmd:"" group:"issues" help:"List issues that are ready to work on."`
	Blocked BlockedCmd `cmd:"" group:"issues" help:"List issues that have at least one open dependency."`
	Show    ShowCmd    `cmd:"" group:"issues" help:"Show details for one issue."`
	Claim   ClaimCmd   `cmd:"" group:"issues" help:"Claim the next ready issue (or a specific one)."`
	Close   CloseCmd   `cmd:"" group:"issues" help:"Close an issue."`
	Cancel  CancelCmd  `cmd:"" group:"issues" help:"Cancel an issue and all its transitive dependents."`
	Reopen  ReopenCmd  `cmd:"" group:"issues" help:"Reopen a closed issue."`
	Update  UpdateCmd  `cmd:"" group:"issues" help:"Update fields on an issue."`

	// --- edits: sugar + relations ---
	Assign   AssignCmd   `cmd:"" group:"edits" help:"Assign an issue (sugar for 'update --assignee')."`
	Priority PriorityCmd `cmd:"" group:"edits" help:"Set an issue's priority (sugar for 'update -p N')."`
	Describe DescribeCmd `cmd:"" group:"edits" help:"Set or clear an issue's description (sugar for 'update --description')."`
	Note     NoteCmd     `cmd:"" group:"edits" help:"Manage an issue's freeform notes."`
	Comment  CommentCmd  `cmd:"" group:"edits" help:"Manage comments on an issue."`
	Label    LabelCmd    `cmd:"" group:"edits" help:"Manage labels on an issue."`
	Tag      TagCmd      `cmd:"" group:"edits" help:"Add labels to an issue (alias for 'label add')."`
	Dep      DepCmd      `cmd:"" group:"edits" help:"Manage dependency edges."`
	Link     LinkCmd     `cmd:"" group:"edits" help:"Add a dependency edge (alias for 'dep add')."`
	Defer    DeferCmd    `cmd:"" group:"edits" help:"Defer an issue until a later time."`
	Undefer  UndeferCmd  `cmd:"" group:"edits" help:"Clear an issue's deferral."`

	// --- coordination: agents, brief, locks, mailbox ---
	Agent  AgentCmd  `cmd:"" group:"coord" help:"Manage agents — list declared (config.yaml) and live (heartbeat) state."`
	Brief  BriefCmd  `cmd:"" group:"coord" help:"Print agent workflow context: AGENTS.md, declared agents, who's live, persisted memories."`
	Lock   LockCmd   `cmd:"" group:"coord" help:"Acquire a named lock for ad-hoc coordination (deploy slots, build dirs, shared resources)."`
	Unlock UnlockCmd `cmd:"" group:"coord" help:"Release a named lock."`
	Locks  LocksCmd  `cmd:"" group:"coord" help:"List current locks (live and stale)."`
	Ping   PingCmd   `cmd:"" group:"coord" help:"Send a fire-and-forget message to another agent's mailbox (TTL'd, doesn't pollute the work log)."`
	Inbox  InboxCmd  `cmd:"" group:"coord" help:"Read pings addressed to you (the mailbox). Marks read on consume unless --peek."`

	// --- workflows ---
	Run        RunCmd        `cmd:"" group:"workflow" help:"Instantiate a workflow template into issues + deps."`
	Template   TemplateCmd   `cmd:"" group:"workflow" help:"Inspect and validate workflow templates."`
	Checkpoint CheckpointCmd `cmd:"" group:"workflow" help:"Pass or fail a checkpoint step."`
	Approve    ApproveCmd    `cmd:"" group:"workflow" help:"Approve a checkpoint (sugar for 'checkpoint pass')."`

	// --- inspection: read-only diagnostics ---
	History  HistoryCmd  `cmd:"" group:"inspect" help:"Show the audit trail for one issue (oldest first)."`
	Log      LogCmd      `cmd:"" group:"inspect" help:"Show the global event stream, newest first (filter by --actor/--kind/--issue/--since)."`
	Count    CountCmd    `cmd:"" group:"inspect" help:"Count issues matching the same filters as 'list'."`
	Stats    StatsCmd    `cmd:"" group:"inspect" help:"Show issue counts grouped by status, agent, and type."`
	Info     InfoCmd     `cmd:"" group:"inspect" help:"Show database path, schema version, and a summary of issues."`
	Doctor   DoctorCmd   `cmd:"" group:"inspect" help:"Run integrity and health checks against the database."`
	Statuses StatusesCmd `cmd:"" group:"inspect" help:"List valid issue statuses."`
	Types    TypesCmd    `cmd:"" group:"inspect" help:"List valid issue types."`

	// --- data + servers ---
	KV     KVCmd     `cmd:"" group:"data" help:"Manage a generic key-value store (feature flags, env, scratch data)."`
	Export ExportCmd `cmd:"" group:"data" help:"Export all issues + deps + labels as JSONL."`
	Import ImportCmd `cmd:"" group:"data" help:"Import JSONL produced by 'clu export'."`
	Sync   SyncCmd   `cmd:"" group:"data" help:"Sync issue state to a branch-independent git ref (refs/clu/store)."`
	Batch  BatchCmd  `cmd:"" group:"data" help:"Instantiate a whole issue graph from one JSON doc, atomically (validated, alias-resolved)."`
	SQL    SqlCmd    `cmd:"" group:"data" name:"sql" help:"Run an ad-hoc SQL query (read-only by default; pass --write for DML/DDL)."`
	Cron   CronCmd   `cmd:"" group:"data" help:"Schedule recurring clu invocations (drive from OS cron / launchd)."`
	HTTP   HTTPCmd   `cmd:"" group:"data" name:"http" help:"Start a REST API server backed by the project's store."`
	Web    WebCmd    `cmd:"" group:"data" name:"web" help:"Launch the web UI (REST API in-process + TanStack Start server)."`

	// --- environments: git worktrees and their bootstrap ---
	Worktree WorktreeCmd `cmd:"" group:"coord" help:"Manage git worktrees with project-defined bootstrap (copy files, run setup commands)."`

	// --- meta ---
	Version    VersionCmd    `cmd:"" group:"meta" help:"Print version information."`
	Completion CompletionCmd `cmd:"" group:"meta" help:"Generate a shell completion script."`
}

CLI is the kong-defined command structure.

type CancelCmd

type CancelCmd struct {
	IDs    []string `arg:"" required:"" name:"id" help:"Issue IDs to cancel (cascades to dependents)."`
	Reason string   `name:"reason" short:"r" help:"Optional comment posted on each root issue before cancel."`
	Agent  string   `short:"a" name:"agent" default:"${user}" help:"Author for --reason comments. Defaults to $USER."`
}

CancelCmd cancels one or more issues *and all their transitive dependents*. "Cancelled" is a distinct terminal status from "closed": closed = done successfully (downstream unblocks); cancelled = abandoned (downstream stays blocked or is also cancelled here).

To cancel only the target without cascade, use `clu update --status cancelled <id>` instead.

func (*CancelCmd) Run

func (c *CancelCmd) Run(r *runCtx) error

type CheckpointCmd

type CheckpointCmd struct {
	Pass CheckpointPassCmd `cmd:"" help:"Mark a checkpoint as passed (clears the wait, closes the issue, unblocks downstream)."`
	Fail CheckpointFailCmd `cmd:"" help:"Mark a checkpoint as failed (closes the issue with checkpoint:failed; downstream stays blocked)."`
}

CheckpointCmd is the parent for `clu checkpoint pass|fail`.

type CheckpointFailCmd

type CheckpointFailCmd struct {
	ID     string `arg:"" help:"Checkpoint issue ID."`
	As     string `short:"a" name:"agent" default:"${user}" help:"Caller identity (defaults to $USER)."`
	Reason string `name:"reason" help:"Optional note appended to the issue when failing."`
}

func (*CheckpointFailCmd) Run

func (c *CheckpointFailCmd) Run(r *runCtx) error

type CheckpointPassCmd

type CheckpointPassCmd struct {
	ID     string `arg:"" help:"Checkpoint issue ID."`
	As     string `` /* 198-byte string literal not displayed */
	Reason string `name:"reason" help:"Optional note appended to the issue when passing."`
}

func (*CheckpointPassCmd) Run

func (c *CheckpointPassCmd) Run(r *runCtx) error

type ClaimCmd

type ClaimCmd struct {
	Agent     string        `` /* 140-byte string literal not displayed */
	Wait      bool          `help:"Block until something is claimable."`
	Interval  time.Duration `default:"250ms" help:"Poll interval when --wait is set."`
	Heartbeat bool          `` /* 127-byte string literal not displayed */
	Context   bool          `` /* 136-byte string literal not displayed */
	Depth     int           `name:"context-depth" help:"Cap how far up the dependency chain --context walks (0 = unlimited)."`
	ID        string        `arg:"" optional:"" help:"Specific issue to claim; omit for next ready."`
}

ClaimCmd: one identity flag does everything.

clu claim                       → assignee = $USER, claims from the unassigned lane
clu claim -a code-reviewer      → claims as code-reviewer; lane filter = code-reviewer's
                                  (+ cap-routed unassigned work if declared in config)
clu claim --wait --heartbeat    → register as live in 'agent ls' while waiting

func (*ClaimCmd) Run

func (c *ClaimCmd) Run(r *runCtx) error

type CloseCmd

type CloseCmd struct {
	IDs    []string `arg:"" required:"" name:"id" help:"One or more issue IDs to close."`
	Reason string   `name:"reason" short:"r" help:"Optional comment posted on each issue before close. Avoids the comment-then-close two-step."`
	Agent  string   `short:"a" name:"agent" default:"${user}" help:"Author for --reason comments. Defaults to $USER."`
}

func (*CloseCmd) Run

func (c *CloseCmd) Run(r *runCtx) error

type CommentAddCmd

type CommentAddCmd struct {
	ID     string   `arg:"" help:"Issue ID."`
	Body   []string `arg:"" optional:"" help:"Comment body. Use '-' or omit (with piped stdin) to read from stdin."`
	Author string   `short:"a" name:"agent" default:"${user}" help:"Agent identity used as the comment author. Defaults to $USER."`
}

CommentAddCmd appends a comment to an issue. The body can come from positional args, the literal "-" (read stdin), or — if no body args are given and stdin isn't a TTY — implicit stdin. The implicit form keeps heredoc/here-string usage tidy without needing an explicit "-".

func (*CommentAddCmd) Run

func (c *CommentAddCmd) Run(r *runCtx) error

type CommentCmd

type CommentCmd struct {
	Add  CommentAddCmd  `cmd:"" help:"Add a comment to an issue. Pass body as args, '-', or via stdin (heredoc)."`
	Edit CommentEditCmd `cmd:"" help:"Replace the body of an existing comment."`
	Ls   CommentLsCmd   `cmd:"" aliases:"list" help:"List comments on an issue, chronological."`
	Rm   CommentRmCmd   `cmd:"" aliases:"remove" help:"Remove a comment by its numeric ID."`
}

type CommentEditCmd

type CommentEditCmd struct {
	ID    int64    `arg:"" help:"Comment ID (numeric, as shown in 'clu comment ls')."`
	Body  []string `arg:"" optional:"" help:"New body. Same input modes as 'comment add' (positional, '-', or stdin)."`
	Agent string   `short:"a" name:"agent" default:"${user}" help:"Agent identity recorded as the actor for this edit. Defaults to $USER."`
}

CommentEditCmd replaces the body of an existing comment. The author + created timestamp are preserved so references to the comment ID stay stable.

func (*CommentEditCmd) Run

func (c *CommentEditCmd) Run(r *runCtx) error

type CommentLsCmd

type CommentLsCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

func (*CommentLsCmd) Run

func (c *CommentLsCmd) Run(r *runCtx) error

type CommentRmCmd

type CommentRmCmd struct {
	ID    int64  `arg:"" help:"Comment ID (numeric, as shown in 'cli comment ls')."`
	Agent string `short:"a" name:"agent" default:"${user}" help:"Agent identity recorded as the actor for this removal. Defaults to $USER."`
}

func (*CommentRmCmd) Run

func (c *CommentRmCmd) Run(r *runCtx) error

type CompletionCmd

type CompletionCmd struct {
	Shell string `arg:"" enum:"bash,zsh,fish" help:"Target shell (bash, zsh, or fish)."`
}

func (*CompletionCmd) Run

func (c *CompletionCmd) Run(r *runCtx) error

type CountCmd

type CountCmd struct {
	// contains filtered or unexported fields
}

func (*CountCmd) Run

func (c *CountCmd) Run(r *runCtx) error

type CreateCmd

type CreateCmd struct {
	Priority    int      `short:"p" default:"2" help:"Priority (0=highest)."`
	Type        string   `short:"t" default:"task" help:"Issue type."`
	Agent       string   `short:"a" help:"Assign directly to an agent lane (e.g. code-reviewer)."`
	Capability  []string `` /* 139-byte string literal not displayed */
	Dep         []string `` /* 170-byte string literal not displayed */
	Description string   `name:"description" help:"Long description. Set atomically with the issue (avoids the race between create + 'clu describe')."`
	Notes       string   `name:"notes" help:"Initial working notes. Set atomically with the issue."`
	Title       []string `arg:"" required:"" help:"Issue title."`
}

func (*CreateCmd) Run

func (c *CreateCmd) Run(r *runCtx) error

type CronAddCmd

type CronAddCmd struct {
	Name     string   `arg:"" help:"Unique job name."`
	Schedule string   `name:"schedule" required:"" help:"+Nh/+Nd/+Nw or @hourly/@daily/@weekly/@monthly."`
	Args     []string `arg:"" passthrough:"" help:"After '--': the cli args to invoke (e.g. -- create -a infra-agent 'Check CI')."`
	StartNow bool     `name:"start-now" help:"Treat next_run as now; the first execution happens on the next 'cron run'."`
}

func (*CronAddCmd) Run

func (c *CronAddCmd) Run(r *runCtx) error

type CronCmd

type CronCmd struct {
	Add     CronAddCmd     `cmd:"" help:"Add a scheduled job."`
	List    CronListCmd    `cmd:"" aliases:"ls" help:"List all scheduled jobs."`
	Rm      CronRmCmd      `cmd:"" aliases:"delete,remove" help:"Delete a scheduled job."`
	Enable  CronEnableCmd  `cmd:"" help:"Enable a scheduled job."`
	Disable CronDisableCmd `cmd:"" help:"Disable a scheduled job (keeps the row, skips it on run)."`
	Run     CronRunCmd     `cmd:"" help:"Run every job whose next_run has elapsed. Invoke periodically from OS cron / launchd."`
}

CronCmd dispatches the `clu cron …` subtree.

type CronDisableCmd

type CronDisableCmd struct {
	Name string `arg:"" help:"Job name."`
}

func (*CronDisableCmd) Run

func (c *CronDisableCmd) Run(r *runCtx) error

type CronEnableCmd

type CronEnableCmd struct {
	Name string `arg:"" help:"Job name."`
}

func (*CronEnableCmd) Run

func (c *CronEnableCmd) Run(r *runCtx) error

type CronListCmd

type CronListCmd struct{}

func (*CronListCmd) Run

func (c *CronListCmd) Run(r *runCtx) error

type CronRmCmd

type CronRmCmd struct {
	Name string `arg:"" help:"Job name."`
}

func (*CronRmCmd) Run

func (c *CronRmCmd) Run(r *runCtx) error

type CronRunCmd

type CronRunCmd struct {
	Force  string `name:"force" help:"Run this one job regardless of schedule (ignores enabled bit too)."`
	DryRun bool   `name:"dry-run" help:"Print what would run; don't execute or advance schedules."`
}

func (*CronRunCmd) Run

func (c *CronRunCmd) Run(r *runCtx) error

type DeferCmd

type DeferCmd struct {
	ID   string `arg:"" help:"Issue ID."`
	When string `arg:"" help:"When to wake up: YYYY-MM-DD, RFC3339, or relative (+6h, +2d, +1w, tomorrow)."`
}

func (*DeferCmd) Run

func (c *DeferCmd) Run(r *runCtx) error

type DepAddCmd

type DepAddCmd struct {
	Child  string `arg:"" help:"Child issue (the one that depends)."`
	Parent string `arg:"" help:"Parent issue (the blocker)."`
}

func (*DepAddCmd) Run

func (c *DepAddCmd) Run(r *runCtx) error

type DepCmd

type DepCmd struct {
	Add DepAddCmd `cmd:"" help:"Add a dependency edge."`
	Rm  DepRmCmd  `cmd:"" aliases:"remove" help:"Remove a dependency edge."`
	Ls  DepLsCmd  `cmd:"" aliases:"list" help:"List dependency edges for an issue (parents it needs + children it blocks)."`
}

type DepLsCmd

type DepLsCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

DepLsCmd prints the dep edges for an issue, both directions: what it depends on (parents) and what depends on it (children/blocks). Mirrors the `Depends:` / `Blocks:` lines in `clu show` but standalone + scriptable via --json.

func (*DepLsCmd) Run

func (c *DepLsCmd) Run(r *runCtx) error

type DepRmCmd

type DepRmCmd struct {
	Child  string `arg:"" help:"Child issue."`
	Parent string `arg:"" help:"Parent issue."`
}

func (*DepRmCmd) Run

func (c *DepRmCmd) Run(r *runCtx) error

type DescribeCmd

type DescribeCmd struct {
	ID   string `arg:"" help:"Issue ID."`
	Text string `arg:"" optional:"" help:"Description text (empty clears)."`
}

DescribeCmd is sugar for `clu update <id> --description <text>`. Pass an empty <text> to clear the description.

func (*DescribeCmd) Run

func (c *DescribeCmd) Run(r *runCtx) error

type DoctorCmd

type DoctorCmd struct {
	StuckHours int `name:"stuck-hours" default:"24" help:"Flag in-progress issues not updated in this many hours (0 disables)."`
}

func (*DoctorCmd) Run

func (c *DoctorCmd) Run(r *runCtx) error

type ExportCmd

type ExportCmd struct {
	Out string `` /* 157-byte string literal not displayed */
}

func (*ExportCmd) Run

func (c *ExportCmd) Run(r *runCtx) error

type HTTPCmd

type HTTPCmd struct {
	Bind string `name:"bind" default:"127.0.0.1" help:"Interface to bind. Defaults to loopback; use 0.0.0.0 to share across the LAN."`
	Port int    `name:"port" default:"7777" help:"TCP port. 0 picks a free port and prints it on startup."`
}

HTTPCmd starts a REST API server backed by the project's store. Used by `clu web` (which spawns it as a child process) and is also usable on its own for scripting or for pointing a separate frontend dev server at a live tracker.

func (*HTTPCmd) Run

func (c *HTTPCmd) Run(r *runCtx) error

type HistoryCmd

type HistoryCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

HistoryCmd prints the audit trail for a single issue, oldest first — the timeline of who did what to it.

func (*HistoryCmd) Run

func (c *HistoryCmd) Run(r *runCtx) error

type ImportCmd

type ImportCmd struct {
	File    string `arg:"" optional:"" help:"Path to JSONL file (default: stdin)."`
	Lenient bool   `name:"lenient" help:"Skip lines that fail to parse instead of erroring."`
}

func (*ImportCmd) Run

func (c *ImportCmd) Run(r *runCtx) error

type InboxCmd

type InboxCmd struct {
	Agent    string        `short:"a" name:"agent" default:"${user}" help:"Whose inbox to read (defaults to $USER). Ignored when --global is set."`
	Global   bool          `short:"A" name:"global" help:"Tail/list every recipient's pings (system-wide). Never marks as read; --peek is implied."`
	All      bool          `name:"all" help:"Include already-read pings (default: unread only)."`
	Since    string        `name:"since" help:"Only pings newer than this (e.g. 1h, 2d, 1w). Empty = no floor."`
	Peek     bool          `name:"peek" help:"Read without marking as read. Default: listing marks them."`
	Clear    bool          `name:"clear" help:"Mark every unread ping as read without listing them. Mutually exclusive with --peek and --watch."`
	Watch    bool          `short:"w" name:"watch" help:"Keep emitting as new pings arrive; redraws the screen on each tick. Ctrl+C to exit."`
	Tail     bool          `` /* 142-byte string literal not displayed */
	Interval time.Duration `name:"interval" default:"1s" help:"Poll interval when --watch or --tail is set."`
	Limit    int           `short:"n" default:"50" help:"Max rows to list (0 = unlimited)."`

	Topic    []string      `` /* 198-byte string literal not displayed */
	NoTopic  []string      `name:"no-topic" help:"Unsubscribe from a topic pattern. Repeatable."`
	TopicTTL time.Duration `name:"topic-ttl" default:"15m" help:"How long a subscription lives between refreshes. Capped at 168h."`
	Topics   bool          `` /* 127-byte string literal not displayed */
}

InboxCmd reads the mailbox addressed to one agent. By default lists unread pings and marks them read on the way out. Also manages topic subscriptions — when --topic is set with --watch/--tail, the subscription is refreshed every tick and lives for the duration of the loop. Persistent subscriptions can be created by passing --topic without --watch/--tail (uses --topic-ttl).

func (*InboxCmd) Run

func (c *InboxCmd) Run(r *runCtx) error

type InfoCmd

type InfoCmd struct{}

func (*InfoCmd) Run

func (c *InfoCmd) Run(r *runCtx) error

type InitCmd

type InitCmd struct {
	Prefix  string `name:"prefix" help:"ID prefix for newly-created issues (only used on first init; ignored if config.yaml already exists)."`
	Stealth bool   `` /* 227-byte string literal not displayed */
}

InitCmd lays down the project skeleton:

.clu/
  config.yaml          # project config (id_prefix, future knobs)
  data.sqlite          # the database (created by the schema migrations)
  templates/
    example.yaml       # a starter workflow you can copy

Each piece is created only if missing — re-running `clu init` is a safe no-op for an already-initialised project. The command also surfaces the local-state gitignore recipe so the user knows what to add to their repo's .gitignore.

func (*InitCmd) Run

func (c *InitCmd) Run(r *runCtx) error

type KVClearCmd

type KVClearCmd struct {
	Key string `arg:"" help:"Key to delete."`
}

func (*KVClearCmd) Run

func (c *KVClearCmd) Run(r *runCtx) error

type KVCmd

type KVCmd struct {
	Set   KVSetCmd   `cmd:"" help:"Set (or overwrite) a key."`
	Get   KVGetCmd   `cmd:"" help:"Print the value of a key."`
	Clear KVClearCmd `cmd:"" aliases:"rm,delete,unset" help:"Delete a key."`
	List  KVListCmd  `cmd:"" aliases:"ls" help:"List every key=value pair."`
}

type KVGetCmd

type KVGetCmd struct {
	Key string `arg:"" help:"Key."`
}

func (*KVGetCmd) Run

func (c *KVGetCmd) Run(r *runCtx) error

Get prints just the value (no key, no trailing newline only what was stored plus a single \n) so it's safe inside shell substitution:

VAL=$(cli kv get my_key)

type KVListCmd

type KVListCmd struct{}

func (*KVListCmd) Run

func (c *KVListCmd) Run(r *runCtx) error

type KVSetCmd

type KVSetCmd struct {
	Key   string   `arg:"" help:"Key."`
	Value []string `arg:"" required:"" help:"Value (joined with spaces if multiple words)."`
}

func (*KVSetCmd) Run

func (c *KVSetCmd) Run(r *runCtx) error

type LabelAddCmd

type LabelAddCmd struct {
	ID     string   `arg:"" help:"Issue ID."`
	Labels []string `arg:"" required:"" help:"Label(s) to add."`
}

func (*LabelAddCmd) Run

func (c *LabelAddCmd) Run(r *runCtx) error

type LabelCmd

type LabelCmd struct {
	Add       LabelAddCmd       `cmd:"" help:"Add labels to an issue."`
	Rm        LabelRmCmd        `cmd:"" aliases:"remove" help:"Remove labels from an issue."`
	Ls        LabelLsCmd        `cmd:"" aliases:"list" help:"List labels on an issue."`
	Propagate LabelPropagateCmd `` /* 139-byte string literal not displayed */
}

type LabelLsCmd

type LabelLsCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

func (*LabelLsCmd) Run

func (c *LabelLsCmd) Run(r *runCtx) error

type LabelPropagateCmd

type LabelPropagateCmd struct {
	Parent string   `arg:"" name:"parent" help:"Parent issue ID."`
	Labels []string `arg:"" required:"" name:"label" help:"Label(s) to propagate."`
	Deep   bool     `name:"deep" aliases:"recursive" help:"Propagate to every transitive descendant, not just direct children."`
}

func (*LabelPropagateCmd) Run

func (c *LabelPropagateCmd) Run(r *runCtx) error

type LabelRmCmd

type LabelRmCmd struct {
	ID     string   `arg:"" help:"Issue ID."`
	Labels []string `arg:"" required:"" help:"Label(s) to remove."`
}

func (*LabelRmCmd) Run

func (c *LabelRmCmd) Run(r *runCtx) error

type LinkCmd

type LinkCmd struct {
	Child  string `arg:"" help:"Child issue (the one that depends)."`
	Parent string `arg:"" help:"Parent issue (the blocker)."`
}

LinkCmd is sugar for `clu dep add <child> <parent>`.

func (*LinkCmd) Run

func (c *LinkCmd) Run(r *runCtx) error

type ListCmd

type ListCmd struct {
	Limit         int           `short:"n" default:"50" help:"Limit results (0 = unlimited)."`
	Watch         bool          `short:"w" name:"watch" help:"Keep updating the list when matching issues change. Ctrl+C to exit."`
	WatchInterval time.Duration `name:"interval" default:"1s" help:"Poll interval when --watch is set."`
	Heartbeat     bool          `name:"heartbeat" help:"While watching, register -a <name> as a live agent in 'clu agent ls'. Requires -a. Off by default."`
	// contains filtered or unexported fields
}

func (*ListCmd) Run

func (c *ListCmd) Run(r *runCtx) error

type LockCmd

type LockCmd struct {
	Name        string        `arg:"" required:"" help:"Lock name (e.g. deploy, build, test-db)."`
	Cmd         []string      `arg:"" optional:"" passthrough:"" help:"Optional command to run while holding the lock. Pass after '--'."`
	Agent       string        `short:"a" name:"agent" default:"${user}" help:"Holder identity (defaults to $USER)."`
	TTL         time.Duration `name:"ttl" default:"5m" help:"How long the lock lives if not explicitly released. Crashed holders auto-expire after this."`
	Wait        bool          `name:"wait" help:"Block until the lock becomes free, then acquire."`
	WaitTimeout time.Duration `name:"wait-timeout" default:"0" help:"Give up waiting after this (0 = forever). Only meaningful with --wait."`
	Interval    time.Duration `name:"interval" default:"500ms" help:"Poll interval when --wait is set."`
}

LockCmd implements `clu lock <name> [-- cmd args...]`: acquire a named lock, optionally run a command while holding it.

Every acquire requires a finite --ttl so a crashed holder can't wedge the lock forever. The trailing-command form is the preferred shape — clu owns acquire+release so leakage is impossible:

clu lock deploy --ttl 1h -- ./deploy.sh production

The bare form exists for cases where the work spans multiple shell invocations; pair it with `clu unlock`.

func (*LockCmd) Run

func (c *LockCmd) Run(r *runCtx) error

type LocksCmd

type LocksCmd struct{}

LocksCmd implements `clu locks`: list current locks with live/stale annotations.

func (*LocksCmd) Run

func (c *LocksCmd) Run(r *runCtx) error

type LogCmd

type LogCmd struct {
	Actor string `name:"actor" help:"Only events by this actor."`
	Kind  string `name:"kind" help:"Only events of this kind (created, claimed, closed, …)."`
	Issue string `name:"issue" help:"Only events for this issue ID."`
	Since string `name:"since" help:"Only events newer than this ago (e.g. 24h, 7d, 2w)."`
	Limit int    `name:"limit" default:"50" help:"Max events to show."`
}

LogCmd prints the global event stream, newest first, with optional filters. The cross-issue companion to `history`.

func (*LogCmd) Run

func (c *LogCmd) Run(r *runCtx) error

type NoteAppendCmd

type NoteAppendCmd struct {
	ID   string   `arg:"" help:"Issue ID."`
	Text []string `arg:"" required:"" help:"Note text to append."`
}

func (*NoteAppendCmd) Run

func (c *NoteAppendCmd) Run(r *runCtx) error

type NoteClearCmd

type NoteClearCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

func (*NoteClearCmd) Run

func (c *NoteClearCmd) Run(r *runCtx) error

type NoteCmd

type NoteCmd struct {
	Set    NoteSetCmd    `cmd:"" help:"Replace an issue's notes."`
	Append NoteAppendCmd `cmd:"" aliases:"add" help:"Append a note to the existing notes."`
	Clear  NoteClearCmd  `cmd:"" help:"Clear an issue's notes."`
	Show   NoteShowCmd   `cmd:"" aliases:"get" help:"Print the current notes."`
}

type NoteSetCmd

type NoteSetCmd struct {
	ID   string   `arg:"" help:"Issue ID."`
	Text []string `arg:"" required:"" help:"Note text."`
}

func (*NoteSetCmd) Run

func (c *NoteSetCmd) Run(r *runCtx) error

type NoteShowCmd

type NoteShowCmd struct {
	ID string `arg:"" help:"Issue ID."`
}

func (*NoteShowCmd) Run

func (c *NoteShowCmd) Run(r *runCtx) error

type PingCmd

type PingCmd struct {
	Recipient string        `arg:"" name:"recipient" help:"Agent name or topic. Direct delivery + fan-out to matching --topic subscribers."`
	Body      []string      `arg:"" optional:"" help:"Message body. Use '-' or omit (with piped stdin) to read from stdin."`
	Agent     string        `short:"a" name:"agent" default:"${user}" help:"Sender identity (defaults to $USER)."`
	TTL       time.Duration `` /* 138-byte string literal not displayed */
}

PingCmd writes a fire-and-forget mailbox row to `Recipient`. Direct delivery always lands in `Recipient`'s mailbox; the store also fans out to every active subscription whose pattern matches the recipient name (see `clu inbox --topic`). The body can come from positional args, '-', or piped stdin (same convention as comment add).

Topic-style names are conventional: `release.urgent`, `frontend.build-broken`. They're just strings — a listener subscribed to `release.*` picks them up, otherwise the row sits in the literal-name inbox until TTL.

Distinct from `clu comment add`: doesn't attach to an issue, auto-expires, doesn't pollute the work log.

func (*PingCmd) Run

func (c *PingCmd) Run(r *runCtx) error

type PriorityCmd

type PriorityCmd struct {
	ID    string `arg:"" help:"Issue ID."`
	Level int    `arg:"" help:"New priority (0=highest)."`
}

PriorityCmd is sugar for `clu update <id> -p <N>`.

func (*PriorityCmd) Run

func (c *PriorityCmd) Run(r *runCtx) error

type ReadyCmd

type ReadyCmd struct {
	Limit     int           `short:"n" name:"limit" default:"20" help:"Limit results (0 = unlimited)."`
	Agent     string        `short:"a" help:"Lane to query (default: unassigned)."`
	Wait      bool          `help:"Block until at least one issue is ready, then print and exit."`
	Watch     bool          `short:"w" name:"watch" help:"Keep emitting the ready list as it changes. Ctrl+C to exit."`
	Interval  time.Duration `default:"250ms" help:"Poll interval when --wait or --watch is set."`
	Heartbeat bool          `name:"heartbeat" help:"While waiting/watching, register -a's name as a live agent."`
}

func (*ReadyCmd) Run

func (c *ReadyCmd) Run(r *runCtx) error

type ReopenCmd

type ReopenCmd struct {
	IDs []string `arg:"" required:"" name:"id" help:"One or more issue IDs to reopen."`
}

func (*ReopenCmd) Run

func (c *ReopenCmd) Run(r *runCtx) error

type RunCmd

type RunCmd struct {
	Template string   `arg:"" help:"Template name (in .clu/templates/) or path to a .yaml file."`
	Var      []string `short:"v" name:"var" placeholder:"KEY=VALUE" help:"Variable bindings (repeatable)."`
	DryRun   bool     `name:"dry-run" help:"Validate and print the plan without writing to the DB."`
	NoPrompt bool     `name:"no-prompt" help:"Don't prompt for missing required vars; fail instead. Default: prompt when stdin is a TTY."`
}

RunCmd instantiates a workflow template into issues + deps.

func (*RunCmd) Run

func (c *RunCmd) Run(r *runCtx) error

type ShowCmd

type ShowCmd struct {
	ID      string `arg:"" help:"Issue ID."`
	Context bool   `name:"context" help:"Also print the upstream dependency chain (descriptions, notes, comments) leading up to this issue."`
	Depth   int    `name:"context-depth" help:"Cap how far up the dependency chain --context walks (0 = unlimited)."`
}

func (*ShowCmd) Run

func (c *ShowCmd) Run(r *runCtx) error

type SqlCmd

type SqlCmd struct {
	Query string `arg:"" optional:"" help:"SQL query. Use '-' to read from stdin, or pass --file."`
	Write bool   `name:"write" help:"Allow non-SELECT statements (UPDATE/DELETE/INSERT/DDL). Default refuses anything other than read queries."`
	File  string `name:"file" short:"f" help:"Read the query from this file instead of the positional arg."`
	CSV   bool   `name:"csv" help:"Emit CSV (with header) instead of a pretty table. Mutually exclusive with --json."`
}

SqlCmd runs an ad-hoc SQL query against the project's data.sqlite.

Read-only by default — first keyword must be SELECT, WITH, EXPLAIN, or PRAGMA. Pass --write to allow arbitrary statements. There's no stricter sandbox; schema is internal and queries can break across migration versions.

func (*SqlCmd) Run

func (c *SqlCmd) Run(r *runCtx) error

type StatsCmd

type StatsCmd struct{}

func (*StatsCmd) Run

func (c *StatsCmd) Run(r *runCtx) error

type StatusesCmd

type StatusesCmd struct{}

func (*StatusesCmd) Run

func (c *StatusesCmd) Run(r *runCtx) error

type SyncCmd

type SyncCmd struct {
	Push   SyncPushCmd   `cmd:"" help:"Serialize the DB onto refs/clu/store (and optionally push it)."`
	Pull   SyncPullCmd   `cmd:"" help:"Reconcile refs/clu/store back into the DB (LWW + tombstones)."`
	Status SyncStatusCmd `cmd:"" help:"Show the synced ref and how it compares to the local DB."`
	Flush  SyncFlushCmd  `cmd:"" help:"pull then push — the full round-trip (beads-rs 'sync')."`
}

type SyncFlushCmd

type SyncFlushCmd struct {
	Remote string `name:"remote" help:"Fetch before / push after against this remote."`
}

func (*SyncFlushCmd) Run

func (c *SyncFlushCmd) Run(r *runCtx) error

type SyncPullCmd

type SyncPullCmd struct {
	Remote string `name:"remote" help:"Fetch refs/clu/store from this remote first (force; the DB is the real local state)."`
}

func (*SyncPullCmd) Run

func (c *SyncPullCmd) Run(r *runCtx) error

type SyncPushCmd

type SyncPushCmd struct {
	Remote  string `name:"remote" help:"Also push refs/clu/store to this git remote (e.g. origin)."`
	Message string `name:"message" short:"m" help:"Commit subject for the ref commit." default:"clu sync"`
}

func (*SyncPushCmd) Run

func (c *SyncPushCmd) Run(r *runCtx) error

type SyncStatusCmd

type SyncStatusCmd struct{}

func (*SyncStatusCmd) Run

func (c *SyncStatusCmd) Run(r *runCtx) error

type TagCmd

type TagCmd struct {
	ID     string   `arg:"" help:"Issue ID."`
	Labels []string `arg:"" required:"" help:"Label(s) to add."`
}

TagCmd is sugar for `clu label add <id> <labels...>`.

func (*TagCmd) Run

func (c *TagCmd) Run(r *runCtx) error

type TemplateCmd

type TemplateCmd struct {
	Ls       TemplateLsCmd       `cmd:"" aliases:"list" help:"List available templates."`
	Show     TemplateShowCmd     `cmd:"" help:"Print one template as parsed YAML."`
	Validate TemplateValidateCmd `cmd:"" help:"Validate a template's structure."`
}

TemplateCmd is the parent for `clu template ls|show|validate`.

type TemplateLsCmd

type TemplateLsCmd struct{}

func (*TemplateLsCmd) Run

func (c *TemplateLsCmd) Run(r *runCtx) error

type TemplateShowCmd

type TemplateShowCmd struct {
	Name string `arg:"" help:"Template name (in .clu/templates/) or path to a .yaml file."`
}

func (*TemplateShowCmd) Run

func (c *TemplateShowCmd) Run(r *runCtx) error

type TemplateValidateCmd

type TemplateValidateCmd struct {
	Name string `arg:"" help:"Template name (in .clu/templates/) or path to a .yaml file."`
}

func (*TemplateValidateCmd) Run

func (c *TemplateValidateCmd) Run(r *runCtx) error

type TypesCmd

type TypesCmd struct{}

func (*TypesCmd) Run

func (c *TypesCmd) Run(r *runCtx) error

type UndeferCmd

type UndeferCmd struct {
	IDs []string `arg:"" required:"" name:"id" help:"One or more issue IDs to undefer."`
}

func (*UndeferCmd) Run

func (c *UndeferCmd) Run(r *runCtx) error

type UnlockCmd

type UnlockCmd struct {
	Name  string `arg:"" required:"" help:"Lock name to release."`
	Agent string `` /* 140-byte string literal not displayed */
	Force bool   `name:"force" help:"Release the lock even if held by another holder. Use to clear a stuck lock after a crash."`
}

UnlockCmd implements `clu unlock <name>`.

func (*UnlockCmd) Run

func (c *UnlockCmd) Run(r *runCtx) error

type UpdateCmd

type UpdateCmd struct {
	ID            string  `arg:"" help:"Issue ID."`
	Priority      *int    `short:"p" help:"New priority (0=highest)."`
	Status        *string `help:"New status."`
	Assignee      *string `short:"a" name:"assignee" help:"Set assignee. The 'agent lane' is just the assignee on an open issue."`
	Unassign      bool    `help:"Clear assignee."`
	Title         *string `help:"New title."`
	Description   *string `help:"Set description."`
	NoDescription bool    `name:"no-description" help:"Clear description."`
}

func (*UpdateCmd) Run

func (c *UpdateCmd) Run(r *runCtx) error

type VersionCmd

type VersionCmd struct{}

func (*VersionCmd) Run

func (c *VersionCmd) Run(r *runCtx) error

type WebCmd

type WebCmd struct {
	APIPort   int    `name:"api-port" default:"7777" help:"Port for the REST API. 0 picks a free port."`
	WebPort   int    `` /* 156-byte string literal not displayed */
	Bind      string `name:"bind" default:"127.0.0.1" help:"Interface to bind both servers."`
	Dev       bool   `name:"dev" help:"Use 'pnpm dev' (HMR) instead of the built output. Default tries built output first, falls back to dev."`
	NoBrowser bool   `name:"no-browser" help:"Don't try to open a browser window."`
	WebDir    string `` /* 135-byte string literal not displayed */
	Install   bool   `` /* 193-byte string literal not displayed */
}

WebCmd boots the local web UI: starts the REST API in-process and spawns the TanStack Start server as a child. The two together give the user a clickable kanban / list / detail UI at a localhost URL.

In-process API rather than spawning `clu http` separately so we only manage one external child (Node) and don't need a port-discovery dance.

func (*WebCmd) Run

func (c *WebCmd) Run(r *runCtx) error

type WorktreeAddCmd

type WorktreeAddCmd struct {
	Path      string `arg:"" help:"Path to create the worktree at (relative or absolute)."`
	Ref       string `arg:"" optional:"" help:"Branch or commit-ish to check out (default: current HEAD)."`
	Branch    string `name:"branch" short:"b" help:"Create a new branch at <ref> for this worktree (passes -b to git worktree add)."`
	Bootstrap bool   `name:"bootstrap" help:"After creating the worktree, run config.yaml's worktree.copy + worktree.commands."`
}

WorktreeAddCmd wraps `git worktree add`. With --bootstrap it then runs the project's `worktree:` recipe so a single command goes from "I want a worktree for feat/foo" to "ready to work."

func (*WorktreeAddCmd) Run

func (c *WorktreeAddCmd) Run(r *runCtx) error

type WorktreeBootstrapCmd

type WorktreeBootstrapCmd struct {
	Path string `arg:"" help:"Path of the worktree to bootstrap. Must be a git worktree."`
}

WorktreeBootstrapCmd applies the config.yaml `worktree:` recipe to an already-created worktree. Idempotent enough to re-run after editing the recipe — copies overwrite, commands run again.

func (*WorktreeBootstrapCmd) Run

func (c *WorktreeBootstrapCmd) Run(r *runCtx) error

type WorktreeCmd

type WorktreeCmd struct {
	Add       WorktreeAddCmd       `cmd:"" help:"Create a new git worktree (and optionally run the bootstrap recipe)."`
	Bootstrap WorktreeBootstrapCmd `cmd:"" help:"Run the worktree.copy + worktree.commands recipe against an existing worktree."`
	Remove    WorktreeRemoveCmd    `cmd:"" aliases:"rm" help:"Remove a git worktree after checking for uncommitted/unpushed work."`
}

WorktreeCmd groups worktree lifecycle subcommands. clu wraps `git worktree` so the project's bootstrap recipe (config.yaml's `worktree:` section) runs in lock-step with the git operation — otherwise every fresh worktree starts as a half-broken filesystem tree missing .env, install state, and any per-checkout init.

type WorktreeRemoveCmd

type WorktreeRemoveCmd struct {
	Path  string `arg:"" help:"Path of the worktree to remove."`
	Force bool   `name:"force" short:"f" help:"Skip the safety checks and pass --force to git worktree remove."`
}

WorktreeRemoveCmd wraps `git worktree remove` with safety checks git itself doesn't perform: unpushed commits (the load-bearing one) plus a stash warning. Git already refuses to remove a worktree with uncommitted changes, but we run that check explicitly so the error message is uniform with the others.

func (*WorktreeRemoveCmd) Run

func (c *WorktreeRemoveCmd) Run(r *runCtx) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL