Skip to main content

kindling/
cli.rs

1//! `clap` command tree for the kindling CLI.
2//!
3//! Mirrors the Commander.js definitions in
4//! `packages/kindling-cli/src/index.ts` (flags + defaults). Two deliberate
5//! deviations are documented inline:
6//!
7//! * a global `--via-daemon` flag (no TS equivalent) routes the daemon-backed
8//!   verbs through `kindling-client` instead of the in-process service;
9//! * `serve` maps to the UDS daemon (`--socket`/`--idle-timeout`) rather than
10//!   the TS HTTP server (`--port`/`--host`/`--no-cors`), because the transport
11//!   changed in the Rust port (see D-005).
12//!
13//! The `sync` (GitHub) commands are intentionally out of PORT-012 scope.
14
15use clap::{Args, Parser, Subcommand};
16
17/// Local memory and continuity engine for AI-assisted development.
18#[derive(Debug, Parser)]
19#[command(name = "kindling", version, about, long_about = None)]
20pub struct Cli {
21    /// Route daemon-backed verbs (log, capsule, search, pin, unpin, forget)
22    /// through the running daemon via the UDS client instead of opening the DB
23    /// in-process.
24    #[arg(long, global = true)]
25    pub via_daemon: bool,
26
27    #[command(subcommand)]
28    pub command: Command,
29}
30
31/// Shared `--db` / `--json` flags carried by most verbs.
32#[derive(Debug, Args, Clone, Default)]
33pub struct CommonOpts {
34    /// Database path. Overrides `KINDLING_DB_PATH` and the per-project default.
35    #[arg(long, value_name = "path")]
36    pub db: Option<String>,
37
38    /// Output as JSON.
39    #[arg(long)]
40    pub json: bool,
41}
42
43#[derive(Debug, Subcommand)]
44pub enum Command {
45    /// Initialize kindling (create database and configure hooks).
46    Init(InitArgs),
47
48    /// Log an observation to memory.
49    Log(LogArgs),
50
51    /// Manage capsules (open/close).
52    #[command(subcommand)]
53    Capsule(CapsuleCommand),
54
55    /// Show database status and statistics.
56    Status(StatusArgs),
57
58    /// Search for relevant context in memory.
59    Search(SearchArgs),
60
61    /// List entities (capsules, pins, observations).
62    List(ListArgs),
63
64    /// Pin an observation or summary (type: observation|summary).
65    Pin(PinArgs),
66
67    /// Remove a pin by ID.
68    Unpin(UnpinArgs),
69
70    /// Redact (forget) an observation by ID.
71    Forget(ForgetArgs),
72
73    /// Export memory to file (default: kindling-export-<timestamp>.json).
74    Export(ExportArgs),
75
76    /// Import memory from an export file.
77    Import(ImportArgs),
78
79    /// Start the kindling daemon (HTTP/1 over a Unix domain socket).
80    Serve(ServeArgs),
81
82    /// Load sample memory for trying search and browse.
83    Demo(DemoArgs),
84
85    /// Open a local HTML viewer for memory in the database.
86    Browse(BrowseArgs),
87
88    /// Inspect the durable-emit spool (pending count and live counters).
89    #[command(subcommand)]
90    Spool(SpoolCommand),
91}
92
93#[derive(Debug, Args)]
94pub struct InitArgs {
95    /// Database path (default: per-project under ~/.kindling).
96    #[arg(long, value_name = "path")]
97    pub db: Option<String>,
98
99    /// Also configure Claude Code integration.
100    #[arg(long)]
101    pub claude_code: bool,
102
103    /// Skip database creation (only configure hooks).
104    #[arg(long)]
105    pub skip_db: bool,
106
107    /// Output as JSON.
108    #[arg(long)]
109    pub json: bool,
110}
111
112#[derive(Debug, Args)]
113pub struct LogArgs {
114    /// Content of the observation.
115    pub content: String,
116
117    /// Observation kind (default: message).
118    #[arg(long, value_name = "kind", default_value = "message")]
119    pub kind: String,
120
121    /// Session scope ID.
122    #[arg(long, value_name = "id")]
123    pub session: Option<String>,
124
125    /// Repository scope ID.
126    #[arg(long, value_name = "id")]
127    pub repo: Option<String>,
128
129    /// Attach to existing capsule.
130    #[arg(long, value_name = "id")]
131    pub capsule: Option<String>,
132
133    #[command(flatten)]
134    pub common: CommonOpts,
135}
136
137#[derive(Debug, Subcommand)]
138pub enum CapsuleCommand {
139    /// Open a new capsule.
140    Open(CapsuleOpenArgs),
141    /// Close a capsule.
142    Close(CapsuleCloseArgs),
143}
144
145#[derive(Debug, Args)]
146pub struct CapsuleOpenArgs {
147    /// Purpose of the capsule (required).
148    #[arg(long, value_name = "text")]
149    pub intent: String,
150
151    /// Capsule type (default: session).
152    #[arg(long = "type", value_name = "type", default_value = "session")]
153    pub kind: String,
154
155    /// Session scope ID.
156    #[arg(long, value_name = "id")]
157    pub session: Option<String>,
158
159    /// Repository scope ID.
160    #[arg(long, value_name = "id")]
161    pub repo: Option<String>,
162
163    #[command(flatten)]
164    pub common: CommonOpts,
165}
166
167#[derive(Debug, Args)]
168pub struct CapsuleCloseArgs {
169    /// Capsule ID to close.
170    pub id: String,
171
172    /// Summary text for the capsule.
173    #[arg(long, value_name = "text")]
174    pub summary: Option<String>,
175
176    #[command(flatten)]
177    pub common: CommonOpts,
178}
179
180#[derive(Debug, Args)]
181pub struct StatusArgs {
182    #[command(flatten)]
183    pub common: CommonOpts,
184}
185
186#[derive(Debug, Args)]
187pub struct SearchArgs {
188    /// Query string.
189    pub query: String,
190
191    /// Filter by session ID.
192    #[arg(long, value_name = "id")]
193    pub session: Option<String>,
194
195    /// Filter by repository ID.
196    #[arg(long, value_name = "id")]
197    pub repo: Option<String>,
198
199    /// Maximum results to return.
200    #[arg(long, value_name = "n", default_value_t = 10)]
201    pub max: u32,
202
203    #[command(flatten)]
204    pub common: CommonOpts,
205}
206
207#[derive(Debug, Args)]
208pub struct ListArgs {
209    /// Entity to list: capsules | pins | observations.
210    pub entity: String,
211
212    /// Filter by session ID.
213    #[arg(long, value_name = "id")]
214    pub session: Option<String>,
215
216    /// Filter by repository ID.
217    #[arg(long, value_name = "id")]
218    pub repo: Option<String>,
219
220    /// Maximum results to return.
221    #[arg(long, value_name = "n", default_value_t = 20)]
222    pub limit: u32,
223
224    #[command(flatten)]
225    pub common: CommonOpts,
226}
227
228#[derive(Debug, Args)]
229pub struct PinArgs {
230    /// Target type: observation | summary.
231    #[arg(value_name = "type")]
232    pub target_type: String,
233
234    /// Target id (observation or summary).
235    pub id: String,
236
237    /// Note describing why this is pinned.
238    #[arg(long, value_name = "text")]
239    pub note: Option<String>,
240
241    /// Time-to-live in milliseconds.
242    #[arg(long, value_name = "ms")]
243    pub ttl: Option<i64>,
244
245    #[command(flatten)]
246    pub common: CommonOpts,
247}
248
249#[derive(Debug, Args)]
250pub struct UnpinArgs {
251    /// Pin ID to remove.
252    pub id: String,
253
254    #[command(flatten)]
255    pub common: CommonOpts,
256}
257
258#[derive(Debug, Args)]
259pub struct ForgetArgs {
260    /// Observation ID to redact (exact id; no prefix matching).
261    pub id: String,
262
263    #[command(flatten)]
264    pub common: CommonOpts,
265}
266
267#[derive(Debug, Args)]
268pub struct ExportArgs {
269    /// Output file (default: kindling-export-<timestamp>.json).
270    pub output: Option<String>,
271
272    /// Export only a specific session.
273    #[arg(long, value_name = "id")]
274    pub session: Option<String>,
275
276    /// Export only a specific repository.
277    #[arg(long, value_name = "id")]
278    pub repo: Option<String>,
279
280    /// Pretty-print JSON output.
281    #[arg(long)]
282    pub pretty: bool,
283
284    /// Timestamp (epoch ms) to stamp into the bundle and the default filename.
285    /// Defaults to the current time; surfaced as a flag for deterministic tests
286    /// (the TS CLI used `Date.now()` directly with no override).
287    #[arg(long, value_name = "ms", hide = true)]
288    pub timestamp: Option<i64>,
289
290    #[command(flatten)]
291    pub common: CommonOpts,
292}
293
294#[derive(Debug, Args)]
295pub struct ImportArgs {
296    /// Bundle file to import.
297    pub file: String,
298
299    /// Validate without importing.
300    #[arg(long)]
301    pub dry_run: bool,
302
303    #[command(flatten)]
304    pub common: CommonOpts,
305}
306
307#[derive(Debug, Args)]
308pub struct ServeArgs {
309    /// Unix domain socket to bind (default: ~/.kindling/kindling.sock).
310    #[arg(long, value_name = "path")]
311    pub socket: Option<String>,
312
313    /// Idle timeout in seconds before the daemon shuts itself down.
314    #[arg(long, value_name = "secs", default_value_t = 1800)]
315    pub idle_timeout: u64,
316
317    /// kindling home (root of per-project databases). Defaults to ~/.kindling
318    /// (or the parent of `--socket` when given).
319    #[arg(long, value_name = "path")]
320    pub kindling_home: Option<String>,
321
322    /// Run as a background daemon: suppress the human-readable startup banner.
323    ///
324    /// This is the flag `kindling-client` passes when it auto-spawns the daemon
325    /// on first call (`kindling serve --daemonize`). Detachment from the calling
326    /// process (its stdio and process group) is owned by the spawner; this flag
327    /// only silences the banner so it never corrupts a caller's stdout (e.g. a
328    /// Claude Code hook writing JSON to stdout).
329    #[arg(long)]
330    pub daemonize: bool,
331}
332
333#[derive(Debug, Args)]
334pub struct DemoArgs {
335    /// Replace an existing demo database before importing sample memory.
336    #[arg(long)]
337    pub reset: bool,
338
339    #[command(flatten)]
340    pub common: CommonOpts,
341}
342
343#[derive(Debug, Subcommand)]
344pub enum SpoolCommand {
345    /// Show spool status for a spool file.
346    Status(SpoolStatusArgs),
347}
348
349#[derive(Debug, Args)]
350pub struct SpoolStatusArgs {
351    /// Path to the NDJSON spool file to inspect.
352    #[arg(long, value_name = "path")]
353    pub spool_path: String,
354
355    /// Output as JSON.
356    #[arg(long)]
357    pub json: bool,
358}
359
360#[derive(Debug, Args)]
361pub struct BrowseArgs {
362    /// Write HTML to this path instead of a temp file.
363    #[arg(long, value_name = "path")]
364    pub output: Option<String>,
365
366    /// Print the output path only; do not open a browser.
367    #[arg(long)]
368    pub no_open: bool,
369
370    #[command(flatten)]
371    pub common: CommonOpts,
372}