Skip to main content

sc/cli/
mod.rs

1//! CLI definitions using clap.
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4use std::path::PathBuf;
5
6/// Output format for list/query commands.
7#[derive(ValueEnum, Clone, Debug, Default, PartialEq, Eq)]
8pub enum OutputFormat {
9    /// Human-readable table (default)
10    #[default]
11    Table,
12    /// JSON (same as --json)
13    Json,
14    /// Comma-separated values
15    Csv,
16}
17
18pub mod commands;
19
20/// SaveContext CLI - The OS for AI coding agents
21#[derive(Parser, Debug)]
22#[command(name = "sc", author, version, about, long_about = None)]
23pub struct Cli {
24    #[command(subcommand)]
25    pub command: Commands,
26
27    /// Database path (default: ~/.savecontext/data/savecontext.db)
28    #[arg(long, global = true, env = "SC_DB")]
29    pub db: Option<PathBuf>,
30
31    /// Actor name for audit trail
32    #[arg(long, global = true, env = "SC_ACTOR")]
33    pub actor: Option<String>,
34
35    /// Active session ID (passed by MCP server)
36    #[arg(long, global = true, env = "SC_SESSION")]
37    pub session: Option<String>,
38
39    /// Output as JSON (for agent integration)
40    #[arg(long, alias = "robot", global = true)]
41    pub json: bool,
42
43    /// Output format (table, json, csv)
44    #[arg(long, value_enum, global = true, default_value_t)]
45    pub format: OutputFormat,
46
47    /// Output only the ID/key (for agent scripting)
48    #[arg(long, global = true)]
49    pub silent: bool,
50
51    /// Preview changes without writing to the database
52    #[arg(long, global = true)]
53    pub dry_run: bool,
54
55    /// Increase logging verbosity (-v, -vv)
56    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
57    pub verbose: u8,
58
59    /// Quiet mode (no output except errors)
60    #[arg(short, long, global = true)]
61    pub quiet: bool,
62
63    /// Disable colored output
64    #[arg(long, global = true)]
65    pub no_color: bool,
66}
67
68#[derive(Subcommand, Debug)]
69pub enum Commands {
70    /// Initialize a SaveContext workspace
71    Init {
72        /// Use global location (~/.savecontext/)
73        #[arg(long)]
74        global: bool,
75
76        /// Overwrite existing database
77        #[arg(long)]
78        force: bool,
79    },
80
81    /// Print version information
82    Version,
83
84    /// Session management
85    Session {
86        #[command(subcommand)]
87        command: SessionCommands,
88    },
89
90    /// Show current session status
91    Status,
92
93    /// Save a context item
94    Save(SaveArgs),
95
96    /// Get/search context items
97    Get(GetArgs),
98
99    /// Delete a context item
100    Delete {
101        /// Key of the item to delete
102        key: String,
103    },
104
105    /// Update a context item
106    Update(UpdateArgs),
107
108    /// Tag context items
109    Tag {
110        #[command(subcommand)]
111        command: TagCommands,
112    },
113
114    /// Issue management
115    Issue {
116        #[command(subcommand)]
117        command: IssueCommands,
118    },
119
120    /// Checkpoint management
121    Checkpoint {
122        #[command(subcommand)]
123        command: CheckpointCommands,
124    },
125
126    /// Project memory (persistent across sessions)
127    Memory {
128        #[command(subcommand)]
129        command: MemoryCommands,
130    },
131
132    /// Sync with JSONL files
133    Sync {
134        #[command(subcommand)]
135        command: SyncCommands,
136    },
137
138    /// Project management
139    Project {
140        #[command(subcommand)]
141        command: ProjectCommands,
142    },
143
144    /// Plan management (PRDs, specs, feature docs)
145    Plan {
146        #[command(subcommand)]
147        command: PlanCommands,
148    },
149
150    /// Prepare context for compaction (auto-checkpoint + summary)
151    Compaction,
152
153    /// Generate context primer for agent injection
154    Prime {
155        /// Include Claude Code transcript summaries
156        #[arg(long)]
157        transcript: bool,
158
159        /// Maximum transcript entries to include
160        #[arg(long, default_value = "5")]
161        transcript_limit: usize,
162
163        /// Compact output for agent system prompt injection
164        #[arg(long)]
165        compact: bool,
166    },
167
168    /// Generate shell completions
169    Completions {
170        /// Shell to generate completions for
171        #[arg(value_enum)]
172        shell: Shell,
173    },
174
175    /// Embedding configuration and management
176    Embeddings {
177        #[command(subcommand)]
178        command: EmbeddingsCommands,
179    },
180}
181
182/// Supported shells for completions.
183#[derive(clap::ValueEnum, Clone, Debug)]
184pub enum Shell {
185    Bash,
186    Zsh,
187    Fish,
188    PowerShell,
189    Elvish,
190}
191
192// ============================================================================
193// Session Commands
194// ============================================================================
195
196#[derive(Subcommand, Debug)]
197pub enum SessionCommands {
198    /// Start a new session
199    Start {
200        /// Session name
201        name: String,
202
203        /// Session description
204        #[arg(short, long)]
205        description: Option<String>,
206
207        /// Project path (defaults to current directory)
208        #[arg(short, long)]
209        project: Option<String>,
210
211        /// Channel name (auto-derived from git branch if not provided)
212        #[arg(long)]
213        channel: Option<String>,
214
215        /// Force create a new session instead of resuming existing one
216        #[arg(long)]
217        force_new: bool,
218    },
219
220    /// End current session
221    End,
222
223    /// Pause current session
224    Pause,
225
226    /// Resume a paused session
227    Resume {
228        /// Session ID to resume
229        id: String,
230    },
231
232    /// List sessions
233    List {
234        /// Filter by status (active, paused, completed, all)
235        #[arg(short, long, default_value = "active")]
236        status: String,
237
238        /// Maximum sessions to return
239        #[arg(short, long, default_value = "10")]
240        limit: usize,
241
242        /// Search sessions by name or description
243        #[arg(long)]
244        search: Option<String>,
245
246        /// Filter by project path
247        #[arg(short, long)]
248        project: Option<String>,
249
250        /// Show sessions from all projects (ignore project filter)
251        #[arg(long)]
252        all_projects: bool,
253
254        /// Include completed sessions (when status is not 'all' or 'completed')
255        #[arg(long)]
256        include_completed: bool,
257    },
258
259    /// Switch to a different session
260    Switch {
261        /// Session ID to switch to
262        id: String,
263    },
264
265    /// Rename current session
266    Rename {
267        /// New session name
268        name: String,
269    },
270
271    /// Delete a session permanently
272    Delete {
273        /// Session ID
274        id: String,
275
276        /// Skip confirmation and delete
277        #[arg(short, long)]
278        force: bool,
279    },
280
281    /// Add a project path to a session
282    AddPath {
283        /// Session ID (uses current active session if not specified)
284        #[arg(short, long)]
285        id: Option<String>,
286
287        /// Project path to add (defaults to current directory)
288        path: Option<String>,
289    },
290
291    /// Remove a project path from a session
292    RemovePath {
293        /// Session ID (uses current active session if not specified)
294        #[arg(short, long)]
295        id: Option<String>,
296
297        /// Project path to remove
298        path: String,
299    },
300}
301
302// ============================================================================
303// Context Item Commands (Save/Get)
304// ============================================================================
305
306#[derive(Args, Debug)]
307pub struct SaveArgs {
308    /// Unique key for this context item
309    pub key: String,
310
311    /// Value to save
312    pub value: String,
313
314    /// Category (reminder, decision, progress, note)
315    #[arg(short, long, default_value = "note")]
316    pub category: String,
317
318    /// Priority (high, normal, low)
319    #[arg(short, long, default_value = "normal")]
320    pub priority: String,
321}
322
323#[derive(Args, Debug, Default)]
324pub struct GetArgs {
325    /// Search query (keyword search)
326    #[arg(short = 's', long)]
327    pub query: Option<String>,
328
329    /// Get by exact key
330    #[arg(short, long)]
331    pub key: Option<String>,
332
333    /// Filter by category
334    #[arg(short, long)]
335    pub category: Option<String>,
336
337    /// Filter by priority
338    #[arg(short = 'P', long)]
339    pub priority: Option<String>,
340
341    /// Search across all sessions (not just current)
342    #[arg(long)]
343    pub search_all_sessions: bool,
344
345    /// Semantic search threshold (0.0-1.0, lower = more results)
346    #[arg(long)]
347    pub threshold: Option<f64>,
348
349    /// Semantic search mode (fast, quality, tiered)
350    ///
351    /// - fast: Instant results using Model2Vec (lower accuracy)
352    /// - quality: Slower but more accurate results using Ollama/HuggingFace
353    /// - tiered: Fast candidates, quality re-ranking (default)
354    #[arg(long, value_parser = parse_search_mode)]
355    pub search_mode: Option<crate::embeddings::SearchMode>,
356
357    /// Pagination offset
358    #[arg(long)]
359    pub offset: Option<usize>,
360
361    /// Maximum items to return
362    #[arg(short, long, default_value = "50")]
363    pub limit: usize,
364}
365
366/// Parse search mode from string
367fn parse_search_mode(s: &str) -> std::result::Result<crate::embeddings::SearchMode, String> {
368    s.parse()
369}
370
371#[derive(Args, Debug)]
372pub struct UpdateArgs {
373    /// Key of the item to update
374    pub key: String,
375
376    /// New value
377    #[arg(long)]
378    pub value: Option<String>,
379
380    /// New category (reminder, decision, progress, note)
381    #[arg(short, long)]
382    pub category: Option<String>,
383
384    /// New priority (high, normal, low)
385    #[arg(short, long)]
386    pub priority: Option<String>,
387
388    /// New channel
389    #[arg(long)]
390    pub channel: Option<String>,
391}
392
393#[derive(Subcommand, Debug)]
394pub enum TagCommands {
395    /// Add tags to context items
396    Add {
397        /// Key of the item to tag
398        key: String,
399
400        /// Tags to add (comma-separated or multiple --tag flags)
401        #[arg(short, long, value_delimiter = ',', required = true)]
402        tags: Vec<String>,
403    },
404
405    /// Remove tags from context items
406    Remove {
407        /// Key of the item to untag
408        key: String,
409
410        /// Tags to remove (comma-separated or multiple --tag flags)
411        #[arg(short, long, value_delimiter = ',', required = true)]
412        tags: Vec<String>,
413    },
414}
415
416// ============================================================================
417// Issue Commands
418// ============================================================================
419
420#[derive(Subcommand, Debug)]
421pub enum IssueCommands {
422    /// Create a new issue
423    Create(IssueCreateArgs),
424
425    /// List issues
426    List(IssueListArgs),
427
428    /// Show issue details
429    Show {
430        /// Issue ID (short or full)
431        id: String,
432    },
433
434    /// Update an issue
435    Update(IssueUpdateArgs),
436
437    /// Mark issue(s) as complete
438    Complete {
439        /// Issue IDs (one or more)
440        ids: Vec<String>,
441    },
442
443    /// Claim issue(s) (assign to self)
444    Claim {
445        /// Issue IDs (one or more)
446        ids: Vec<String>,
447    },
448
449    /// Release issue(s)
450    Release {
451        /// Issue IDs (one or more)
452        ids: Vec<String>,
453    },
454
455    /// Delete issue(s)
456    Delete {
457        /// Issue IDs (one or more)
458        ids: Vec<String>,
459    },
460
461    /// Manage issue labels
462    Label {
463        #[command(subcommand)]
464        command: IssueLabelCommands,
465    },
466
467    /// Manage issue dependencies
468    Dep {
469        #[command(subcommand)]
470        command: IssueDepCommands,
471    },
472
473    /// Clone an issue
474    Clone {
475        /// Issue ID to clone
476        id: String,
477
478        /// New title (defaults to "Copy of <original>")
479        #[arg(short, long)]
480        title: Option<String>,
481    },
482
483    /// Mark issue as duplicate of another
484    Duplicate {
485        /// Issue ID to mark as duplicate
486        id: String,
487
488        /// Issue ID this is a duplicate of
489        #[arg(long)]
490        of: String,
491    },
492
493    /// List issues ready to work on
494    Ready {
495        /// Maximum issues to return
496        #[arg(short, long, default_value = "10")]
497        limit: usize,
498    },
499
500    /// Get next block of issues and claim them
501    NextBlock {
502        /// Number of issues to claim
503        #[arg(short, long, default_value = "3")]
504        count: usize,
505    },
506
507    /// Create multiple issues at once with dependencies
508    Batch {
509        /// JSON input containing issues array, dependencies, and optional planId
510        #[arg(long)]
511        json_input: String,
512    },
513}
514
515#[derive(Subcommand, Debug)]
516pub enum IssueLabelCommands {
517    /// Add labels to an issue
518    Add {
519        /// Issue ID
520        id: String,
521
522        /// Labels to add (comma-separated)
523        #[arg(short, long, value_delimiter = ',', required = true)]
524        labels: Vec<String>,
525    },
526
527    /// Remove labels from an issue
528    Remove {
529        /// Issue ID
530        id: String,
531
532        /// Labels to remove (comma-separated)
533        #[arg(short, long, value_delimiter = ',', required = true)]
534        labels: Vec<String>,
535    },
536}
537
538#[derive(Subcommand, Debug)]
539pub enum IssueDepCommands {
540    /// Add a dependency to an issue
541    Add {
542        /// Issue ID
543        id: String,
544
545        /// ID of issue this depends on
546        #[arg(long)]
547        depends_on: String,
548
549        /// Dependency type (blocks, related, parent-child, discovered-from)
550        #[arg(short = 't', long, default_value = "blocks")]
551        dep_type: String,
552    },
553
554    /// Remove a dependency from an issue
555    Remove {
556        /// Issue ID
557        id: String,
558
559        /// ID of issue to remove dependency on
560        #[arg(long)]
561        depends_on: String,
562    },
563}
564
565#[derive(Args, Debug)]
566pub struct IssueCreateArgs {
567    /// Issue title
568    pub title: String,
569
570    /// Issue description
571    #[arg(short, long)]
572    pub description: Option<String>,
573
574    /// Implementation details or notes
575    #[arg(long)]
576    pub details: Option<String>,
577
578    /// Issue type (task, bug, feature, epic, chore)
579    #[arg(short = 't', long, default_value = "task")]
580    pub issue_type: String,
581
582    /// Priority (0=lowest to 4=critical)
583    #[arg(short, long, default_value = "2")]
584    pub priority: i32,
585
586    /// Parent issue ID (for subtasks)
587    #[arg(long)]
588    pub parent: Option<String>,
589
590    /// Link issue to a Plan (PRD/spec)
591    #[arg(long)]
592    pub plan_id: Option<String>,
593
594    /// Labels (-l bug -l security or -l bug,security)
595    #[arg(short, long, value_delimiter = ',')]
596    pub labels: Option<Vec<String>>,
597
598    /// Import issues from a JSONL file (one JSON object per line)
599    #[arg(short, long)]
600    pub file: Option<PathBuf>,
601}
602
603#[derive(Args, Debug, Default)]
604pub struct IssueListArgs {
605    /// Filter by specific issue ID (short or full)
606    #[arg(long)]
607    pub id: Option<String>,
608
609    /// Filter by status (backlog, open, in_progress, blocked, closed, deferred, all)
610    #[arg(short, long, default_value = "open")]
611    pub status: String,
612
613    /// Filter by exact priority (0-4)
614    #[arg(short, long)]
615    pub priority: Option<i32>,
616
617    /// Filter by minimum priority
618    #[arg(long)]
619    pub priority_min: Option<i32>,
620
621    /// Filter by maximum priority
622    #[arg(long)]
623    pub priority_max: Option<i32>,
624
625    /// Filter by type
626    #[arg(short = 't', long)]
627    pub issue_type: Option<String>,
628
629    /// Filter by labels (all must match, comma-separated)
630    #[arg(long, value_delimiter = ',')]
631    pub labels: Option<Vec<String>>,
632
633    /// Filter by labels (any must match, comma-separated)
634    #[arg(long, value_delimiter = ',')]
635    pub labels_any: Option<Vec<String>>,
636
637    /// Filter by parent issue ID
638    #[arg(long)]
639    pub parent: Option<String>,
640
641    /// Filter by plan ID
642    #[arg(long)]
643    pub plan: Option<String>,
644
645    /// Filter issues with subtasks
646    #[arg(long)]
647    pub has_subtasks: bool,
648
649    /// Filter issues without subtasks
650    #[arg(long)]
651    pub no_subtasks: bool,
652
653    /// Filter issues with dependencies
654    #[arg(long)]
655    pub has_deps: bool,
656
657    /// Filter issues without dependencies
658    #[arg(long)]
659    pub no_deps: bool,
660
661    /// Sort by field (priority, createdAt, updatedAt)
662    #[arg(long, default_value = "createdAt")]
663    pub sort: String,
664
665    /// Sort order (asc, desc)
666    #[arg(long, default_value = "desc")]
667    pub order: String,
668
669    /// Filter by issues created in last N days
670    #[arg(long)]
671    pub created_days: Option<i64>,
672
673    /// Filter by issues created in last N hours
674    #[arg(long)]
675    pub created_hours: Option<i64>,
676
677    /// Filter by issues updated in last N days
678    #[arg(long)]
679    pub updated_days: Option<i64>,
680
681    /// Filter by issues updated in last N hours
682    #[arg(long)]
683    pub updated_hours: Option<i64>,
684
685    /// Search in title/description
686    #[arg(long)]
687    pub search: Option<String>,
688
689    /// Filter by assignee
690    #[arg(long)]
691    pub assignee: Option<String>,
692
693    /// Search across all projects
694    #[arg(long)]
695    pub all_projects: bool,
696
697    /// Maximum issues to return
698    #[arg(short, long, default_value = "50")]
699    pub limit: usize,
700}
701
702#[derive(Args, Debug)]
703pub struct IssueUpdateArgs {
704    /// Issue ID
705    pub id: String,
706
707    /// New title
708    #[arg(long)]
709    pub title: Option<String>,
710
711    /// New description
712    #[arg(short, long)]
713    pub description: Option<String>,
714
715    /// New details
716    #[arg(long)]
717    pub details: Option<String>,
718
719    /// New status
720    #[arg(short, long)]
721    pub status: Option<String>,
722
723    /// New priority
724    #[arg(short, long)]
725    pub priority: Option<i32>,
726
727    /// New type
728    #[arg(short = 't', long)]
729    pub issue_type: Option<String>,
730
731    /// New parent issue ID
732    #[arg(long)]
733    pub parent: Option<String>,
734
735    /// New plan ID
736    #[arg(long)]
737    pub plan: Option<String>,
738}
739
740// ============================================================================
741// Checkpoint Commands
742// ============================================================================
743
744#[derive(Subcommand, Debug)]
745pub enum CheckpointCommands {
746    /// Create a checkpoint
747    Create {
748        /// Checkpoint name
749        name: String,
750
751        /// Description
752        #[arg(short, long)]
753        description: Option<String>,
754
755        /// Include git status
756        #[arg(long)]
757        include_git: bool,
758    },
759
760    /// List checkpoints
761    List {
762        /// Search checkpoints by name or description
763        #[arg(short, long)]
764        search: Option<String>,
765
766        /// Filter by session ID
767        #[arg(long)]
768        session: Option<String>,
769
770        /// Filter by project path
771        #[arg(long)]
772        project: Option<String>,
773
774        /// Include checkpoints from all projects
775        #[arg(long)]
776        all_projects: bool,
777
778        /// Maximum checkpoints to return
779        #[arg(short, long, default_value = "20")]
780        limit: usize,
781
782        /// Pagination offset
783        #[arg(long)]
784        offset: Option<usize>,
785    },
786
787    /// Show checkpoint details
788    Show {
789        /// Checkpoint ID
790        id: String,
791    },
792
793    /// Restore from checkpoint
794    Restore {
795        /// Checkpoint ID
796        id: String,
797
798        /// Only restore items in these categories (comma-separated)
799        #[arg(long, value_delimiter = ',')]
800        categories: Option<Vec<String>>,
801
802        /// Only restore items with these tags (comma-separated)
803        #[arg(long, value_delimiter = ',')]
804        tags: Option<Vec<String>>,
805    },
806
807    /// Delete a checkpoint
808    Delete {
809        /// Checkpoint ID
810        id: String,
811    },
812
813    /// Add items to an existing checkpoint
814    AddItems {
815        /// Checkpoint ID
816        id: String,
817
818        /// Context item keys to add (comma-separated)
819        #[arg(short, long, value_delimiter = ',', required = true)]
820        keys: Vec<String>,
821    },
822
823    /// Remove items from a checkpoint
824    RemoveItems {
825        /// Checkpoint ID
826        id: String,
827
828        /// Context item keys to remove (comma-separated)
829        #[arg(short, long, value_delimiter = ',', required = true)]
830        keys: Vec<String>,
831    },
832
833    /// List items in a checkpoint
834    Items {
835        /// Checkpoint ID
836        id: String,
837    },
838}
839
840// ============================================================================
841// Memory Commands (Project-level persistent storage)
842// ============================================================================
843
844#[derive(Subcommand, Debug)]
845pub enum MemoryCommands {
846    /// Save a memory item
847    Save {
848        /// Key
849        key: String,
850
851        /// Value
852        value: String,
853
854        /// Category (command, config, note)
855        #[arg(short, long, default_value = "command")]
856        category: String,
857    },
858
859    /// Get a memory item
860    Get {
861        /// Key
862        key: String,
863    },
864
865    /// List memory items
866    List {
867        /// Filter by category
868        #[arg(short, long)]
869        category: Option<String>,
870    },
871
872    /// Delete a memory item
873    Delete {
874        /// Key
875        key: String,
876    },
877}
878
879// ============================================================================
880// Sync Commands
881// ============================================================================
882
883#[derive(Subcommand, Debug)]
884pub enum SyncCommands {
885    /// Export to JSONL
886    Export {
887        /// Force export even if JSONL is newer
888        #[arg(long)]
889        force: bool,
890    },
891
892    /// Import from JSONL
893    Import {
894        /// Force import even with conflicts
895        #[arg(long)]
896        force: bool,
897    },
898
899    /// Show sync status
900    Status,
901}
902
903// ============================================================================
904// Project Commands
905// ============================================================================
906
907#[derive(Subcommand, Debug)]
908pub enum ProjectCommands {
909    /// Create a new project
910    Create(ProjectCreateArgs),
911
912    /// List all projects
913    List {
914        /// Include session count for each project
915        #[arg(long)]
916        session_count: bool,
917
918        /// Maximum projects to return
919        #[arg(short, long, default_value = "50")]
920        limit: usize,
921    },
922
923    /// Show project details
924    Show {
925        /// Project ID or path
926        id: String,
927    },
928
929    /// Update a project
930    Update(ProjectUpdateArgs),
931
932    /// Delete a project
933    Delete {
934        /// Project ID or path
935        id: String,
936
937        /// Skip confirmation and delete
938        #[arg(short, long)]
939        force: bool,
940    },
941}
942
943#[derive(Args, Debug)]
944pub struct ProjectCreateArgs {
945    /// Project path (defaults to current directory)
946    pub path: Option<String>,
947
948    /// Project name (defaults to directory name)
949    #[arg(short, long)]
950    pub name: Option<String>,
951
952    /// Project description
953    #[arg(short, long)]
954    pub description: Option<String>,
955
956    /// Issue ID prefix (e.g., "SC" creates SC-1, SC-2)
957    #[arg(short = 'p', long)]
958    pub issue_prefix: Option<String>,
959}
960
961#[derive(Args, Debug)]
962pub struct ProjectUpdateArgs {
963    /// Project ID or path
964    pub id: String,
965
966    /// New project name
967    #[arg(short, long)]
968    pub name: Option<String>,
969
970    /// New description
971    #[arg(short, long)]
972    pub description: Option<String>,
973
974    /// New issue ID prefix
975    #[arg(short = 'p', long)]
976    pub issue_prefix: Option<String>,
977}
978
979// ============================================================================
980// Plan Commands
981// ============================================================================
982
983#[derive(Subcommand, Debug)]
984pub enum PlanCommands {
985    /// Create a new plan
986    Create(PlanCreateArgs),
987
988    /// List plans
989    List {
990        /// Filter by status (draft, active, completed, all)
991        #[arg(short, long, default_value = "active")]
992        status: String,
993
994        /// Maximum plans to return
995        #[arg(short, long, default_value = "50")]
996        limit: usize,
997    },
998
999    /// Show plan details
1000    Show {
1001        /// Plan ID
1002        id: String,
1003    },
1004
1005    /// Update a plan
1006    Update(PlanUpdateArgs),
1007}
1008
1009#[derive(Args, Debug)]
1010pub struct PlanCreateArgs {
1011    /// Plan title
1012    pub title: String,
1013
1014    /// Plan content (markdown PRD/spec)
1015    #[arg(short, long)]
1016    pub content: Option<String>,
1017
1018    /// Plan status (draft, active, completed)
1019    #[arg(short, long, default_value = "active")]
1020    pub status: String,
1021
1022    /// Success criteria
1023    #[arg(long)]
1024    pub success_criteria: Option<String>,
1025}
1026
1027#[derive(Args, Debug)]
1028pub struct PlanUpdateArgs {
1029    /// Plan ID
1030    pub id: String,
1031
1032    /// New title
1033    #[arg(long)]
1034    pub title: Option<String>,
1035
1036    /// New content
1037    #[arg(short, long)]
1038    pub content: Option<String>,
1039
1040    /// New status (draft, active, completed)
1041    #[arg(short, long)]
1042    pub status: Option<String>,
1043
1044    /// New success criteria
1045    #[arg(long)]
1046    pub success_criteria: Option<String>,
1047}
1048
1049// ============================================================================
1050// Embeddings Commands
1051// ============================================================================
1052
1053#[derive(Subcommand, Debug, Clone)]
1054pub enum EmbeddingsCommands {
1055    /// Show embeddings status and configuration
1056    Status,
1057
1058    /// Configure embedding provider
1059    Configure {
1060        /// Provider (ollama, huggingface)
1061        #[arg(short, long)]
1062        provider: Option<String>,
1063
1064        /// Enable embeddings
1065        #[arg(long)]
1066        enable: bool,
1067
1068        /// Disable embeddings
1069        #[arg(long)]
1070        disable: bool,
1071
1072        /// Model to use (provider-specific)
1073        #[arg(short, long)]
1074        model: Option<String>,
1075
1076        /// API endpoint (for custom servers)
1077        #[arg(long)]
1078        endpoint: Option<String>,
1079
1080        /// API token (for HuggingFace)
1081        #[arg(long)]
1082        token: Option<String>,
1083    },
1084
1085    /// Backfill embeddings for existing context items
1086    Backfill {
1087        /// Maximum items to process
1088        #[arg(short, long)]
1089        limit: Option<usize>,
1090
1091        /// Session ID to backfill (defaults to current)
1092        #[arg(short, long)]
1093        session: Option<String>,
1094
1095        /// Force regeneration of existing embeddings
1096        #[arg(long)]
1097        force: bool,
1098    },
1099
1100    /// Test embedding provider connectivity
1101    Test {
1102        /// Text to generate test embedding for
1103        #[arg(default_value = "Hello world")]
1104        text: String,
1105    },
1106
1107    /// Process pending embeddings in background (internal use)
1108    #[command(hide = true)]
1109    ProcessPending {
1110        /// Maximum items to process
1111        #[arg(short, long, default_value = "10")]
1112        limit: usize,
1113
1114        /// Run silently (no output)
1115        #[arg(long)]
1116        quiet: bool,
1117    },
1118
1119    /// Upgrade items with fast embeddings to quality embeddings
1120    ///
1121    /// Items saved with the 2-tier system get instant fast embeddings (Model2Vec).
1122    /// This command generates higher-quality embeddings (Ollama/HuggingFace)
1123    /// for items that only have fast embeddings.
1124    UpgradeQuality {
1125        /// Maximum items to process
1126        #[arg(short, long)]
1127        limit: Option<usize>,
1128
1129        /// Session ID to upgrade (defaults to all sessions)
1130        #[arg(short, long)]
1131        session: Option<String>,
1132    },
1133}