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        /// Smart relevance-ranked context selection
168        #[arg(long)]
169        smart: bool,
170
171        /// Token budget for smart mode (default: 4000)
172        #[arg(long, default_value = "4000")]
173        budget: usize,
174
175        /// Topic query for semantic boosting in smart mode
176        #[arg(long)]
177        query: Option<String>,
178
179        /// Temporal decay half-life in days for smart mode (default: 14)
180        #[arg(long, default_value = "14")]
181        decay_days: u32,
182    },
183
184    /// Generate shell completions
185    Completions {
186        /// Shell to generate completions for
187        #[arg(value_enum)]
188        shell: Shell,
189    },
190
191    /// Embedding configuration and management
192    Embeddings {
193        #[command(subcommand)]
194        command: EmbeddingsCommands,
195    },
196}
197
198/// Supported shells for completions.
199#[derive(clap::ValueEnum, Clone, Debug)]
200pub enum Shell {
201    Bash,
202    Zsh,
203    Fish,
204    PowerShell,
205    Elvish,
206}
207
208// ============================================================================
209// Session Commands
210// ============================================================================
211
212#[derive(Subcommand, Debug)]
213pub enum SessionCommands {
214    /// Start a new session
215    Start {
216        /// Session name
217        name: String,
218
219        /// Session description
220        #[arg(short, long)]
221        description: Option<String>,
222
223        /// Project path (defaults to current directory)
224        #[arg(short, long)]
225        project: Option<String>,
226
227        /// Channel name (auto-derived from git branch if not provided)
228        #[arg(long)]
229        channel: Option<String>,
230
231        /// Force create a new session instead of resuming existing one
232        #[arg(long)]
233        force_new: bool,
234    },
235
236    /// End current session
237    End,
238
239    /// Pause current session
240    Pause,
241
242    /// Resume a paused session
243    Resume {
244        /// Session ID to resume
245        id: String,
246    },
247
248    /// List sessions
249    List {
250        /// Filter by status (active, paused, completed, all)
251        #[arg(short, long, default_value = "active")]
252        status: String,
253
254        /// Maximum sessions to return
255        #[arg(short, long, default_value = "10")]
256        limit: usize,
257
258        /// Search sessions by name or description
259        #[arg(long)]
260        search: Option<String>,
261
262        /// Filter by project path
263        #[arg(short, long)]
264        project: Option<String>,
265
266        /// Show sessions from all projects (ignore project filter)
267        #[arg(long)]
268        all_projects: bool,
269
270        /// Include completed sessions (when status is not 'all' or 'completed')
271        #[arg(long)]
272        include_completed: bool,
273    },
274
275    /// Switch to a different session
276    Switch {
277        /// Session ID to switch to
278        id: String,
279    },
280
281    /// Rename current session
282    Rename {
283        /// New session name
284        name: String,
285    },
286
287    /// Delete a session permanently
288    Delete {
289        /// Session ID
290        id: String,
291
292        /// Skip confirmation and delete
293        #[arg(short, long)]
294        force: bool,
295    },
296
297    /// Add a project path to a session
298    AddPath {
299        /// Session ID (uses current active session if not specified)
300        #[arg(short, long)]
301        id: Option<String>,
302
303        /// Project path to add (defaults to current directory)
304        path: Option<String>,
305    },
306
307    /// Remove a project path from a session
308    RemovePath {
309        /// Session ID (uses current active session if not specified)
310        #[arg(short, long)]
311        id: Option<String>,
312
313        /// Project path to remove
314        path: String,
315    },
316}
317
318// ============================================================================
319// Context Item Commands (Save/Get)
320// ============================================================================
321
322#[derive(Args, Debug)]
323pub struct SaveArgs {
324    /// Unique key for this context item
325    pub key: String,
326
327    /// Value to save
328    pub value: String,
329
330    /// Category (reminder, decision, progress, note)
331    #[arg(short, long, default_value = "note")]
332    pub category: String,
333
334    /// Priority (high, normal, low)
335    #[arg(short, long, default_value = "normal")]
336    pub priority: String,
337}
338
339#[derive(Args, Debug, Default)]
340pub struct GetArgs {
341    /// Search query (smart semantic search when embeddings enabled, keyword fallback)
342    #[arg(short = 's', long)]
343    pub query: Option<String>,
344
345    /// Get by exact key
346    #[arg(short, long)]
347    pub key: Option<String>,
348
349    /// Filter by category
350    #[arg(short, long)]
351    pub category: Option<String>,
352
353    /// Filter by priority
354    #[arg(short = 'P', long)]
355    pub priority: Option<String>,
356
357    /// Search across all sessions (not just current)
358    #[arg(long)]
359    pub search_all_sessions: bool,
360
361    /// Semantic search threshold (0.0-1.0, lower = more results)
362    #[arg(long)]
363    pub threshold: Option<f64>,
364
365    /// Semantic search mode (fast, quality, tiered)
366    ///
367    /// - fast: Instant results using Model2Vec (lower accuracy)
368    /// - quality: Slower but more accurate results using Ollama/HuggingFace
369    /// - tiered: Fast candidates, quality re-ranking (default)
370    #[arg(long, value_parser = parse_search_mode)]
371    pub search_mode: Option<crate::embeddings::SearchMode>,
372
373    /// Pagination offset
374    #[arg(long)]
375    pub offset: Option<usize>,
376
377    /// Maximum items to return
378    #[arg(short, long, default_value = "50")]
379    pub limit: usize,
380}
381
382/// Parse search mode from string
383fn parse_search_mode(s: &str) -> std::result::Result<crate::embeddings::SearchMode, String> {
384    s.parse()
385}
386
387#[derive(Args, Debug)]
388pub struct UpdateArgs {
389    /// Key of the item to update
390    pub key: String,
391
392    /// New value
393    #[arg(long)]
394    pub value: Option<String>,
395
396    /// New category (reminder, decision, progress, note)
397    #[arg(short, long)]
398    pub category: Option<String>,
399
400    /// New priority (high, normal, low)
401    #[arg(short, long)]
402    pub priority: Option<String>,
403
404    /// New channel
405    #[arg(long)]
406    pub channel: Option<String>,
407}
408
409#[derive(Subcommand, Debug)]
410pub enum TagCommands {
411    /// Add tags to context items
412    Add {
413        /// Key of the item to tag
414        key: String,
415
416        /// Tags to add (comma-separated or multiple --tag flags)
417        #[arg(short, long, value_delimiter = ',', required = true)]
418        tags: Vec<String>,
419    },
420
421    /// Remove tags from context items
422    Remove {
423        /// Key of the item to untag
424        key: String,
425
426        /// Tags to remove (comma-separated or multiple --tag flags)
427        #[arg(short, long, value_delimiter = ',', required = true)]
428        tags: Vec<String>,
429    },
430}
431
432// ============================================================================
433// Issue Commands
434// ============================================================================
435
436#[derive(Subcommand, Debug)]
437pub enum IssueCommands {
438    /// Create a new issue
439    Create(IssueCreateArgs),
440
441    /// List issues
442    List(IssueListArgs),
443
444    /// Show issue details
445    Show {
446        /// Issue ID (short or full)
447        id: String,
448    },
449
450    /// Update an issue
451    Update(IssueUpdateArgs),
452
453    /// Mark issue(s) as complete
454    Complete {
455        /// Issue IDs (one or more)
456        ids: Vec<String>,
457
458        /// Reason for closing
459        #[arg(short = 'r', long)]
460        reason: Option<String>,
461    },
462
463    /// Claim issue(s) (assign to self)
464    Claim {
465        /// Issue IDs (one or more)
466        ids: Vec<String>,
467    },
468
469    /// Release issue(s)
470    Release {
471        /// Issue IDs (one or more)
472        ids: Vec<String>,
473    },
474
475    /// Delete issue(s)
476    Delete {
477        /// Issue IDs (one or more)
478        ids: Vec<String>,
479    },
480
481    /// Manage issue labels
482    Label {
483        #[command(subcommand)]
484        command: IssueLabelCommands,
485    },
486
487    /// Manage issue dependencies
488    Dep {
489        #[command(subcommand)]
490        command: IssueDepCommands,
491    },
492
493    /// Clone an issue
494    Clone {
495        /// Issue ID to clone
496        id: String,
497
498        /// New title (defaults to "Copy of <original>")
499        #[arg(short, long)]
500        title: Option<String>,
501    },
502
503    /// Mark issue as duplicate of another
504    Duplicate {
505        /// Issue ID to mark as duplicate
506        id: String,
507
508        /// Issue ID this is a duplicate of
509        #[arg(long)]
510        of: String,
511    },
512
513    /// List issues ready to work on
514    Ready {
515        /// Maximum issues to return
516        #[arg(short, long, default_value = "10")]
517        limit: usize,
518    },
519
520    /// Get next block of issues and claim them
521    NextBlock {
522        /// Number of issues to claim
523        #[arg(short, long, default_value = "3")]
524        count: usize,
525    },
526
527    /// Create multiple issues at once with dependencies
528    Batch {
529        /// JSON input containing issues array, dependencies, and optional planId
530        #[arg(long)]
531        json_input: String,
532    },
533
534    /// Count issues grouped by a field
535    Count {
536        /// Group by: status, type, priority, assignee
537        #[arg(short, long, default_value = "status")]
538        group_by: String,
539    },
540
541    /// List stale issues (not updated recently)
542    Stale {
543        /// Issues not updated in this many days
544        #[arg(short, long, default_value = "7")]
545        days: u64,
546
547        /// Maximum issues to return
548        #[arg(short, long, default_value = "50")]
549        limit: usize,
550    },
551
552    /// List blocked issues with their blockers
553    Blocked {
554        /// Maximum issues to return
555        #[arg(short, long, default_value = "50")]
556        limit: usize,
557    },
558}
559
560#[derive(Subcommand, Debug)]
561pub enum IssueLabelCommands {
562    /// Add labels to an issue
563    Add {
564        /// Issue ID
565        id: String,
566
567        /// Labels to add (comma-separated)
568        #[arg(short, long, value_delimiter = ',', required = true)]
569        labels: Vec<String>,
570    },
571
572    /// Remove labels from an issue
573    Remove {
574        /// Issue ID
575        id: String,
576
577        /// Labels to remove (comma-separated)
578        #[arg(short, long, value_delimiter = ',', required = true)]
579        labels: Vec<String>,
580    },
581}
582
583#[derive(Subcommand, Debug)]
584pub enum IssueDepCommands {
585    /// Add a dependency to an issue
586    Add {
587        /// Issue ID
588        id: String,
589
590        /// ID of issue this depends on
591        #[arg(long)]
592        depends_on: String,
593
594        /// Dependency type (blocks, related, parent-child, discovered-from)
595        #[arg(short = 't', long, default_value = "blocks")]
596        dep_type: String,
597    },
598
599    /// Remove a dependency from an issue
600    Remove {
601        /// Issue ID
602        id: String,
603
604        /// ID of issue to remove dependency on
605        #[arg(long)]
606        depends_on: String,
607    },
608
609    /// Show dependency tree for an issue or all epics
610    Tree {
611        /// Root issue ID (omit for all epics)
612        id: Option<String>,
613    },
614}
615
616#[derive(Args, Debug)]
617pub struct IssueCreateArgs {
618    /// Issue title
619    pub title: String,
620
621    /// Issue description
622    #[arg(short, long)]
623    pub description: Option<String>,
624
625    /// Implementation details or notes
626    #[arg(long)]
627    pub details: Option<String>,
628
629    /// Issue type (task, bug, feature, epic, chore)
630    #[arg(short = 't', long, default_value = "task")]
631    pub issue_type: String,
632
633    /// Priority (0=lowest to 4=critical)
634    #[arg(short, long, default_value = "2")]
635    pub priority: i32,
636
637    /// Parent issue ID (for subtasks)
638    #[arg(long)]
639    pub parent: Option<String>,
640
641    /// Link issue to a Plan (PRD/spec)
642    #[arg(long)]
643    pub plan_id: Option<String>,
644
645    /// Labels (-l bug -l security or -l bug,security)
646    #[arg(short, long, value_delimiter = ',')]
647    pub labels: Option<Vec<String>>,
648
649    /// Import issues from a JSONL file (one JSON object per line)
650    #[arg(short, long)]
651    pub file: Option<PathBuf>,
652}
653
654#[derive(Args, Debug, Default)]
655pub struct IssueListArgs {
656    /// Filter by specific issue ID (short or full)
657    #[arg(long)]
658    pub id: Option<String>,
659
660    /// Filter by status (backlog, open, in_progress, blocked, closed, deferred, all)
661    #[arg(short, long, default_value = "open")]
662    pub status: String,
663
664    /// Filter by exact priority (0-4)
665    #[arg(short, long)]
666    pub priority: Option<i32>,
667
668    /// Filter by minimum priority
669    #[arg(long)]
670    pub priority_min: Option<i32>,
671
672    /// Filter by maximum priority
673    #[arg(long)]
674    pub priority_max: Option<i32>,
675
676    /// Filter by type
677    #[arg(short = 't', long)]
678    pub issue_type: Option<String>,
679
680    /// Filter by labels (all must match, comma-separated)
681    #[arg(long, value_delimiter = ',')]
682    pub labels: Option<Vec<String>>,
683
684    /// Filter by labels (any must match, comma-separated)
685    #[arg(long, value_delimiter = ',')]
686    pub labels_any: Option<Vec<String>>,
687
688    /// Filter by parent issue ID
689    #[arg(long)]
690    pub parent: Option<String>,
691
692    /// Filter by plan ID
693    #[arg(long)]
694    pub plan: Option<String>,
695
696    /// Filter issues with subtasks
697    #[arg(long)]
698    pub has_subtasks: bool,
699
700    /// Filter issues without subtasks
701    #[arg(long)]
702    pub no_subtasks: bool,
703
704    /// Filter issues with dependencies
705    #[arg(long)]
706    pub has_deps: bool,
707
708    /// Filter issues without dependencies
709    #[arg(long)]
710    pub no_deps: bool,
711
712    /// Sort by field (priority, createdAt, updatedAt)
713    #[arg(long, default_value = "createdAt")]
714    pub sort: String,
715
716    /// Sort order (asc, desc)
717    #[arg(long, default_value = "desc")]
718    pub order: String,
719
720    /// Filter by issues created in last N days
721    #[arg(long)]
722    pub created_days: Option<i64>,
723
724    /// Filter by issues created in last N hours
725    #[arg(long)]
726    pub created_hours: Option<i64>,
727
728    /// Filter by issues updated in last N days
729    #[arg(long)]
730    pub updated_days: Option<i64>,
731
732    /// Filter by issues updated in last N hours
733    #[arg(long)]
734    pub updated_hours: Option<i64>,
735
736    /// Search in title/description
737    #[arg(long)]
738    pub search: Option<String>,
739
740    /// Filter by assignee
741    #[arg(long)]
742    pub assignee: Option<String>,
743
744    /// Search across all projects
745    #[arg(long)]
746    pub all_projects: bool,
747
748    /// Maximum issues to return
749    #[arg(short, long, default_value = "50")]
750    pub limit: usize,
751}
752
753#[derive(Args, Debug)]
754pub struct IssueUpdateArgs {
755    /// Issue ID
756    pub id: String,
757
758    /// New title
759    #[arg(long)]
760    pub title: Option<String>,
761
762    /// New description
763    #[arg(short, long)]
764    pub description: Option<String>,
765
766    /// New details
767    #[arg(long)]
768    pub details: Option<String>,
769
770    /// New status
771    #[arg(short, long)]
772    pub status: Option<String>,
773
774    /// New priority
775    #[arg(short, long)]
776    pub priority: Option<i32>,
777
778    /// New type
779    #[arg(short = 't', long)]
780    pub issue_type: Option<String>,
781
782    /// New parent issue ID
783    #[arg(long)]
784    pub parent: Option<String>,
785
786    /// New plan ID
787    #[arg(long)]
788    pub plan: Option<String>,
789}
790
791// ============================================================================
792// Checkpoint Commands
793// ============================================================================
794
795#[derive(Subcommand, Debug)]
796pub enum CheckpointCommands {
797    /// Create a checkpoint
798    Create {
799        /// Checkpoint name
800        name: String,
801
802        /// Description
803        #[arg(short, long)]
804        description: Option<String>,
805
806        /// Include git status
807        #[arg(long)]
808        include_git: bool,
809    },
810
811    /// List checkpoints
812    List {
813        /// Search checkpoints by name or description
814        #[arg(short, long)]
815        search: Option<String>,
816
817        /// Filter by session ID
818        #[arg(long)]
819        session: Option<String>,
820
821        /// Filter by project path
822        #[arg(long)]
823        project: Option<String>,
824
825        /// Include checkpoints from all projects
826        #[arg(long)]
827        all_projects: bool,
828
829        /// Maximum checkpoints to return
830        #[arg(short, long, default_value = "20")]
831        limit: usize,
832
833        /// Pagination offset
834        #[arg(long)]
835        offset: Option<usize>,
836    },
837
838    /// Show checkpoint details
839    Show {
840        /// Checkpoint ID
841        id: String,
842    },
843
844    /// Restore from checkpoint
845    Restore {
846        /// Checkpoint ID
847        id: String,
848
849        /// Only restore items in these categories (comma-separated)
850        #[arg(long, value_delimiter = ',')]
851        categories: Option<Vec<String>>,
852
853        /// Only restore items with these tags (comma-separated)
854        #[arg(long, value_delimiter = ',')]
855        tags: Option<Vec<String>>,
856    },
857
858    /// Delete a checkpoint
859    Delete {
860        /// Checkpoint ID
861        id: String,
862    },
863
864    /// Add items to an existing checkpoint
865    AddItems {
866        /// Checkpoint ID
867        id: String,
868
869        /// Context item keys to add (comma-separated)
870        #[arg(short, long, value_delimiter = ',', required = true)]
871        keys: Vec<String>,
872    },
873
874    /// Remove items from a checkpoint
875    RemoveItems {
876        /// Checkpoint ID
877        id: String,
878
879        /// Context item keys to remove (comma-separated)
880        #[arg(short, long, value_delimiter = ',', required = true)]
881        keys: Vec<String>,
882    },
883
884    /// List items in a checkpoint
885    Items {
886        /// Checkpoint ID
887        id: String,
888    },
889}
890
891// ============================================================================
892// Memory Commands (Project-level persistent storage)
893// ============================================================================
894
895#[derive(Subcommand, Debug)]
896pub enum MemoryCommands {
897    /// Save a memory item
898    Save {
899        /// Key
900        key: String,
901
902        /// Value
903        value: String,
904
905        /// Category (command, config, note)
906        #[arg(short, long, default_value = "command")]
907        category: String,
908    },
909
910    /// Get a memory item
911    Get {
912        /// Key
913        key: String,
914    },
915
916    /// List memory items
917    List {
918        /// Filter by category
919        #[arg(short, long)]
920        category: Option<String>,
921    },
922
923    /// Delete a memory item
924    Delete {
925        /// Key
926        key: String,
927    },
928}
929
930// ============================================================================
931// Sync Commands
932// ============================================================================
933
934#[derive(Subcommand, Debug)]
935pub enum SyncCommands {
936    /// Export to JSONL
937    Export {
938        /// Force export even if JSONL is newer
939        #[arg(long)]
940        force: bool,
941    },
942
943    /// Import from JSONL
944    Import {
945        /// Force import even with conflicts
946        #[arg(long)]
947        force: bool,
948    },
949
950    /// Show sync status
951    Status,
952}
953
954// ============================================================================
955// Project Commands
956// ============================================================================
957
958#[derive(Subcommand, Debug)]
959pub enum ProjectCommands {
960    /// Create a new project
961    Create(ProjectCreateArgs),
962
963    /// List all projects
964    List {
965        /// Include session count for each project
966        #[arg(long)]
967        session_count: bool,
968
969        /// Maximum projects to return
970        #[arg(short, long, default_value = "50")]
971        limit: usize,
972    },
973
974    /// Show project details
975    Show {
976        /// Project ID or path
977        id: String,
978    },
979
980    /// Update a project
981    Update(ProjectUpdateArgs),
982
983    /// Delete a project
984    Delete {
985        /// Project ID or path
986        id: String,
987
988        /// Skip confirmation and delete
989        #[arg(short, long)]
990        force: bool,
991    },
992}
993
994#[derive(Args, Debug)]
995pub struct ProjectCreateArgs {
996    /// Project path (defaults to current directory)
997    pub path: Option<String>,
998
999    /// Project name (defaults to directory name)
1000    #[arg(short, long)]
1001    pub name: Option<String>,
1002
1003    /// Project description
1004    #[arg(short, long)]
1005    pub description: Option<String>,
1006
1007    /// Issue ID prefix (e.g., "SC" creates SC-1, SC-2)
1008    #[arg(short = 'p', long)]
1009    pub issue_prefix: Option<String>,
1010}
1011
1012#[derive(Args, Debug)]
1013pub struct ProjectUpdateArgs {
1014    /// Project ID or path
1015    pub id: String,
1016
1017    /// New project name
1018    #[arg(short, long)]
1019    pub name: Option<String>,
1020
1021    /// New description
1022    #[arg(short, long)]
1023    pub description: Option<String>,
1024
1025    /// New issue ID prefix
1026    #[arg(short = 'p', long)]
1027    pub issue_prefix: Option<String>,
1028}
1029
1030// ============================================================================
1031// Plan Commands
1032// ============================================================================
1033
1034#[derive(Subcommand, Debug)]
1035pub enum PlanCommands {
1036    /// Create a new plan
1037    Create(PlanCreateArgs),
1038
1039    /// List plans
1040    List {
1041        /// Filter by status (draft, active, completed, all)
1042        #[arg(short, long, default_value = "active")]
1043        status: String,
1044
1045        /// Maximum plans to return
1046        #[arg(short, long, default_value = "50")]
1047        limit: usize,
1048
1049        /// Filter by session ID (use "current" for active TTY session)
1050        #[arg(long)]
1051        session: Option<String>,
1052    },
1053
1054    /// Show plan details
1055    Show {
1056        /// Plan ID
1057        id: String,
1058    },
1059
1060    /// Update a plan
1061    Update(PlanUpdateArgs),
1062
1063    /// Capture a plan from an AI coding agent's plan file
1064    Capture {
1065        /// Only look in a specific agent's directory (claude, gemini, opencode, cursor)
1066        #[arg(long)]
1067        agent: Option<String>,
1068
1069        /// Max age in minutes (default: 30)
1070        #[arg(long, default_value = "30")]
1071        max_age: u64,
1072
1073        /// Explicit plan file path (skip discovery)
1074        #[arg(long)]
1075        file: Option<PathBuf>,
1076    },
1077}
1078
1079#[derive(Args, Debug)]
1080pub struct PlanCreateArgs {
1081    /// Plan title
1082    pub title: String,
1083
1084    /// Plan content (markdown PRD/spec)
1085    #[arg(short, long)]
1086    pub content: Option<String>,
1087
1088    /// Plan status (draft, active, completed)
1089    #[arg(short, long, default_value = "active")]
1090    pub status: String,
1091
1092    /// Success criteria
1093    #[arg(long)]
1094    pub success_criteria: Option<String>,
1095
1096    /// Bind to a specific session (default: auto-resolve from TTY)
1097    #[arg(long)]
1098    pub session: Option<String>,
1099}
1100
1101#[derive(Args, Debug)]
1102pub struct PlanUpdateArgs {
1103    /// Plan ID
1104    pub id: String,
1105
1106    /// New title
1107    #[arg(long)]
1108    pub title: Option<String>,
1109
1110    /// New content
1111    #[arg(short, long)]
1112    pub content: Option<String>,
1113
1114    /// New status (draft, active, completed)
1115    #[arg(short, long)]
1116    pub status: Option<String>,
1117
1118    /// New success criteria
1119    #[arg(long)]
1120    pub success_criteria: Option<String>,
1121}
1122
1123// ============================================================================
1124// Embeddings Commands
1125// ============================================================================
1126
1127#[derive(Subcommand, Debug, Clone)]
1128pub enum EmbeddingsCommands {
1129    /// Show embeddings status and configuration
1130    Status,
1131
1132    /// Configure embedding provider
1133    Configure {
1134        /// Provider (ollama, huggingface)
1135        #[arg(short, long)]
1136        provider: Option<String>,
1137
1138        /// Enable embeddings
1139        #[arg(long)]
1140        enable: bool,
1141
1142        /// Disable embeddings
1143        #[arg(long)]
1144        disable: bool,
1145
1146        /// Model to use (provider-specific)
1147        #[arg(short, long)]
1148        model: Option<String>,
1149
1150        /// API endpoint (for custom servers)
1151        #[arg(long)]
1152        endpoint: Option<String>,
1153
1154        /// API token (for HuggingFace)
1155        #[arg(long)]
1156        token: Option<String>,
1157    },
1158
1159    /// Backfill embeddings for existing context items
1160    Backfill {
1161        /// Maximum items to process
1162        #[arg(short, long)]
1163        limit: Option<usize>,
1164
1165        /// Session ID to backfill (defaults to current)
1166        #[arg(short, long)]
1167        session: Option<String>,
1168
1169        /// Force regeneration of existing embeddings
1170        #[arg(long)]
1171        force: bool,
1172    },
1173
1174    /// Test embedding provider connectivity
1175    Test {
1176        /// Text to generate test embedding for
1177        #[arg(default_value = "Hello world")]
1178        text: String,
1179    },
1180
1181    /// Process pending embeddings in background (internal use)
1182    #[command(hide = true)]
1183    ProcessPending {
1184        /// Maximum items to process
1185        #[arg(short, long, default_value = "10")]
1186        limit: usize,
1187
1188        /// Run silently (no output)
1189        #[arg(long)]
1190        quiet: bool,
1191    },
1192
1193    /// Upgrade items with fast embeddings to quality embeddings
1194    ///
1195    /// Items saved with the 2-tier system get instant fast embeddings (Model2Vec).
1196    /// This command generates higher-quality embeddings (Ollama/HuggingFace)
1197    /// for items that only have fast embeddings.
1198    UpgradeQuality {
1199        /// Maximum items to process
1200        #[arg(short, long)]
1201        limit: Option<usize>,
1202
1203        /// Session ID to upgrade (defaults to all sessions)
1204        #[arg(short, long)]
1205        session: Option<String>,
1206    },
1207}