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 /// Install and manage skills, hooks, and status line
198 Skills {
199 #[command(subcommand)]
200 command: SkillsCommands,
201 },
202
203 /// Configuration management
204 Config {
205 #[command(subcommand)]
206 command: ConfigCommands,
207 },
208
209 /// Time tracking (billable hours)
210 Time {
211 #[command(subcommand)]
212 command: TimeCommands,
213 },
214
215 /// Run sc commands on a remote host via SSH
216 #[command(trailing_var_arg = true)]
217 Remote {
218 /// Arguments to pass to sc on the remote host
219 #[arg(num_args = 1..)]
220 args: Vec<String>,
221 },
222}
223
224/// Supported shells for completions.
225#[derive(clap::ValueEnum, Clone, Debug)]
226pub enum Shell {
227 Bash,
228 Zsh,
229 Fish,
230 PowerShell,
231 Elvish,
232}
233
234// ============================================================================
235// Session Commands
236// ============================================================================
237
238#[derive(Subcommand, Debug)]
239pub enum SessionCommands {
240 /// Start a new session
241 Start {
242 /// Session name
243 name: String,
244
245 /// Session description
246 #[arg(short, long)]
247 description: Option<String>,
248
249 /// Project path (defaults to current directory)
250 #[arg(short, long)]
251 project: Option<String>,
252
253 /// Channel name (auto-derived from git branch if not provided)
254 #[arg(long)]
255 channel: Option<String>,
256
257 /// Force create a new session instead of resuming existing one
258 #[arg(long)]
259 force_new: bool,
260 },
261
262 /// End current session
263 End,
264
265 /// Pause current session
266 Pause,
267
268 /// Resume a paused session
269 Resume {
270 /// Session ID to resume
271 id: String,
272 },
273
274 /// List sessions
275 List {
276 /// Filter by status (active, paused, completed, all)
277 #[arg(short, long, default_value = "active")]
278 status: String,
279
280 /// Maximum sessions to return
281 #[arg(short, long, default_value = "10")]
282 limit: usize,
283
284 /// Search sessions by name or description
285 #[arg(long)]
286 search: Option<String>,
287
288 /// Filter by project path
289 #[arg(short, long)]
290 project: Option<String>,
291
292 /// Show sessions from all projects (ignore project filter)
293 #[arg(long)]
294 all_projects: bool,
295
296 /// Include completed sessions (when status is not 'all' or 'completed')
297 #[arg(long)]
298 include_completed: bool,
299 },
300
301 /// Switch to a different session
302 Switch {
303 /// Session ID to switch to
304 id: String,
305 },
306
307 /// Rename current session
308 Rename {
309 /// New session name
310 name: String,
311 },
312
313 /// Delete a session permanently
314 Delete {
315 /// Session ID
316 id: String,
317
318 /// Skip confirmation and delete
319 #[arg(short, long)]
320 force: bool,
321 },
322
323 /// Add a project path to a session
324 AddPath {
325 /// Session ID (uses current active session if not specified)
326 #[arg(short, long)]
327 id: Option<String>,
328
329 /// Project path to add (defaults to current directory)
330 path: Option<String>,
331 },
332
333 /// Remove a project path from a session
334 RemovePath {
335 /// Session ID (uses current active session if not specified)
336 #[arg(short, long)]
337 id: Option<String>,
338
339 /// Project path to remove
340 path: String,
341 },
342}
343
344// ============================================================================
345// Context Item Commands (Save/Get)
346// ============================================================================
347
348#[derive(Args, Debug)]
349pub struct SaveArgs {
350 /// Unique key for this context item
351 pub key: String,
352
353 /// Value to save
354 pub value: String,
355
356 /// Category (reminder, decision, progress, note)
357 #[arg(short, long, default_value = "note")]
358 pub category: String,
359
360 /// Priority (high, normal, low)
361 #[arg(short, long, default_value = "normal")]
362 pub priority: String,
363}
364
365#[derive(Args, Debug, Default)]
366pub struct GetArgs {
367 /// Search query (smart semantic search when embeddings enabled, keyword fallback)
368 #[arg(short = 's', long)]
369 pub query: Option<String>,
370
371 /// Get by exact key
372 #[arg(short, long)]
373 pub key: Option<String>,
374
375 /// Filter by category
376 #[arg(short, long)]
377 pub category: Option<String>,
378
379 /// Filter by priority
380 #[arg(short = 'P', long)]
381 pub priority: Option<String>,
382
383 /// Search across all sessions (not just current)
384 #[arg(long)]
385 pub search_all_sessions: bool,
386
387 /// Semantic search threshold (0.0-1.0, lower = more results)
388 #[arg(long)]
389 pub threshold: Option<f64>,
390
391 /// Semantic search mode (fast, quality, tiered)
392 ///
393 /// - fast: Instant results using Model2Vec (lower accuracy)
394 /// - quality: Slower but more accurate results using Ollama/HuggingFace
395 /// - tiered: Fast candidates, quality re-ranking (default)
396 #[arg(long, value_parser = parse_search_mode)]
397 pub search_mode: Option<crate::embeddings::SearchMode>,
398
399 /// Pagination offset
400 #[arg(long)]
401 pub offset: Option<usize>,
402
403 /// Maximum items to return
404 #[arg(short, long, default_value = "50")]
405 pub limit: usize,
406}
407
408/// Parse search mode from string
409fn parse_search_mode(s: &str) -> std::result::Result<crate::embeddings::SearchMode, String> {
410 s.parse()
411}
412
413#[derive(Args, Debug)]
414pub struct UpdateArgs {
415 /// Key of the item to update
416 pub key: String,
417
418 /// New value
419 #[arg(long)]
420 pub value: Option<String>,
421
422 /// New category (reminder, decision, progress, note)
423 #[arg(short, long)]
424 pub category: Option<String>,
425
426 /// New priority (high, normal, low)
427 #[arg(short, long)]
428 pub priority: Option<String>,
429
430 /// New channel
431 #[arg(long)]
432 pub channel: Option<String>,
433}
434
435#[derive(Subcommand, Debug)]
436pub enum TagCommands {
437 /// Add tags to context items
438 Add {
439 /// Key of the item to tag
440 key: String,
441
442 /// Tags to add (comma-separated or multiple --tag flags)
443 #[arg(short, long, value_delimiter = ',', required = true)]
444 tags: Vec<String>,
445 },
446
447 /// Remove tags from context items
448 Remove {
449 /// Key of the item to untag
450 key: String,
451
452 /// Tags to remove (comma-separated or multiple --tag flags)
453 #[arg(short, long, value_delimiter = ',', required = true)]
454 tags: Vec<String>,
455 },
456}
457
458// ============================================================================
459// Issue Commands
460// ============================================================================
461
462#[derive(Subcommand, Debug)]
463pub enum IssueCommands {
464 /// Create a new issue
465 Create(IssueCreateArgs),
466
467 /// List issues
468 List(IssueListArgs),
469
470 /// Show issue details
471 Show {
472 /// Issue ID (short or full)
473 id: String,
474 },
475
476 /// Update an issue
477 Update(IssueUpdateArgs),
478
479 /// Mark issue(s) as complete
480 Complete {
481 /// Issue IDs (one or more)
482 ids: Vec<String>,
483
484 /// Reason for closing
485 #[arg(short = 'r', long)]
486 reason: Option<String>,
487 },
488
489 /// Claim issue(s) (assign to self)
490 Claim {
491 /// Issue IDs (one or more)
492 ids: Vec<String>,
493 },
494
495 /// Release issue(s)
496 Release {
497 /// Issue IDs (one or more)
498 ids: Vec<String>,
499 },
500
501 /// Delete issue(s)
502 Delete {
503 /// Issue IDs (one or more)
504 ids: Vec<String>,
505 },
506
507 /// Manage issue labels
508 Label {
509 #[command(subcommand)]
510 command: IssueLabelCommands,
511 },
512
513 /// Manage issue dependencies
514 Dep {
515 #[command(subcommand)]
516 command: IssueDepCommands,
517 },
518
519 /// Clone an issue
520 Clone {
521 /// Issue ID to clone
522 id: String,
523
524 /// New title (defaults to "Copy of <original>")
525 #[arg(short, long)]
526 title: Option<String>,
527 },
528
529 /// Mark issue as duplicate of another
530 Duplicate {
531 /// Issue ID to mark as duplicate
532 id: String,
533
534 /// Issue ID this is a duplicate of
535 #[arg(long)]
536 of: String,
537 },
538
539 /// List issues ready to work on
540 Ready {
541 /// Maximum issues to return
542 #[arg(short, long, default_value = "10")]
543 limit: usize,
544 },
545
546 /// Get next block of issues and claim them
547 NextBlock {
548 /// Number of issues to claim
549 #[arg(short, long, default_value = "3")]
550 count: usize,
551 },
552
553 /// Create multiple issues at once with dependencies
554 Batch {
555 /// JSON input containing issues array, dependencies, and optional planId
556 #[arg(long)]
557 json_input: String,
558 },
559
560 /// Count issues grouped by a field
561 Count {
562 /// Group by: status, type, priority, assignee
563 #[arg(short, long, default_value = "status")]
564 group_by: String,
565 },
566
567 /// List stale issues (not updated recently)
568 Stale {
569 /// Issues not updated in this many days
570 #[arg(short, long, default_value = "7")]
571 days: u64,
572
573 /// Maximum issues to return
574 #[arg(short, long, default_value = "50")]
575 limit: usize,
576 },
577
578 /// List blocked issues with their blockers
579 Blocked {
580 /// Maximum issues to return
581 #[arg(short, long, default_value = "50")]
582 limit: usize,
583 },
584}
585
586#[derive(Subcommand, Debug)]
587pub enum IssueLabelCommands {
588 /// Add labels to an issue
589 Add {
590 /// Issue ID
591 id: String,
592
593 /// Labels to add (comma-separated)
594 #[arg(short, long, value_delimiter = ',', required = true)]
595 labels: Vec<String>,
596 },
597
598 /// Remove labels from an issue
599 Remove {
600 /// Issue ID
601 id: String,
602
603 /// Labels to remove (comma-separated)
604 #[arg(short, long, value_delimiter = ',', required = true)]
605 labels: Vec<String>,
606 },
607}
608
609#[derive(Subcommand, Debug)]
610pub enum IssueDepCommands {
611 /// Add a dependency to an issue
612 Add {
613 /// Issue ID
614 id: String,
615
616 /// ID of issue this depends on
617 #[arg(long)]
618 depends_on: String,
619
620 /// Dependency type (blocks, related, parent-child, discovered-from)
621 #[arg(short = 't', long, default_value = "blocks")]
622 dep_type: String,
623 },
624
625 /// Remove a dependency from an issue
626 Remove {
627 /// Issue ID
628 id: String,
629
630 /// ID of issue to remove dependency on
631 #[arg(long)]
632 depends_on: String,
633 },
634
635 /// Show dependency tree for an issue or all epics
636 Tree {
637 /// Root issue ID (omit for all epics)
638 id: Option<String>,
639 },
640}
641
642#[derive(Args, Debug)]
643pub struct IssueCreateArgs {
644 /// Issue title
645 pub title: String,
646
647 /// Issue description
648 #[arg(short, long)]
649 pub description: Option<String>,
650
651 /// Implementation details or notes
652 #[arg(long)]
653 pub details: Option<String>,
654
655 /// Issue type (task, bug, feature, epic, chore)
656 #[arg(short = 't', long, default_value = "task")]
657 pub issue_type: String,
658
659 /// Priority (0=lowest to 4=critical)
660 #[arg(short, long, default_value = "2")]
661 pub priority: i32,
662
663 /// Parent issue ID (for subtasks)
664 #[arg(long)]
665 pub parent: Option<String>,
666
667 /// Link issue to a Plan (PRD/spec)
668 #[arg(long)]
669 pub plan_id: Option<String>,
670
671 /// Labels (-l bug -l security or -l bug,security)
672 #[arg(short, long, value_delimiter = ',')]
673 pub labels: Option<Vec<String>>,
674
675 /// Import issues from a JSONL file (one JSON object per line)
676 #[arg(short, long)]
677 pub file: Option<PathBuf>,
678}
679
680#[derive(Args, Debug, Default)]
681pub struct IssueListArgs {
682 /// Filter by specific issue ID (short or full)
683 #[arg(long)]
684 pub id: Option<String>,
685
686 /// Filter by status (backlog, open, in_progress, blocked, closed, deferred, all)
687 #[arg(short, long, default_value = "open")]
688 pub status: String,
689
690 /// Filter by exact priority (0-4)
691 #[arg(short, long)]
692 pub priority: Option<i32>,
693
694 /// Filter by minimum priority
695 #[arg(long)]
696 pub priority_min: Option<i32>,
697
698 /// Filter by maximum priority
699 #[arg(long)]
700 pub priority_max: Option<i32>,
701
702 /// Filter by type
703 #[arg(short = 't', long)]
704 pub issue_type: Option<String>,
705
706 /// Filter by labels (all must match, comma-separated)
707 #[arg(long, value_delimiter = ',')]
708 pub labels: Option<Vec<String>>,
709
710 /// Filter by labels (any must match, comma-separated)
711 #[arg(long, value_delimiter = ',')]
712 pub labels_any: Option<Vec<String>>,
713
714 /// Filter by parent issue ID
715 #[arg(long)]
716 pub parent: Option<String>,
717
718 /// Filter by plan ID
719 #[arg(long)]
720 pub plan: Option<String>,
721
722 /// Filter issues with subtasks
723 #[arg(long)]
724 pub has_subtasks: bool,
725
726 /// Filter issues without subtasks
727 #[arg(long)]
728 pub no_subtasks: bool,
729
730 /// Filter issues with dependencies
731 #[arg(long)]
732 pub has_deps: bool,
733
734 /// Filter issues without dependencies
735 #[arg(long)]
736 pub no_deps: bool,
737
738 /// Sort by field (priority, createdAt, updatedAt)
739 #[arg(long, default_value = "createdAt")]
740 pub sort: String,
741
742 /// Sort order (asc, desc)
743 #[arg(long, default_value = "desc")]
744 pub order: String,
745
746 /// Filter by issues created in last N days
747 #[arg(long)]
748 pub created_days: Option<i64>,
749
750 /// Filter by issues created in last N hours
751 #[arg(long)]
752 pub created_hours: Option<i64>,
753
754 /// Filter by issues updated in last N days
755 #[arg(long)]
756 pub updated_days: Option<i64>,
757
758 /// Filter by issues updated in last N hours
759 #[arg(long)]
760 pub updated_hours: Option<i64>,
761
762 /// Search in title/description
763 #[arg(long)]
764 pub search: Option<String>,
765
766 /// Filter by assignee
767 #[arg(long)]
768 pub assignee: Option<String>,
769
770 /// Search across all projects
771 #[arg(long)]
772 pub all_projects: bool,
773
774 /// Maximum issues to return
775 #[arg(short, long, default_value = "50")]
776 pub limit: usize,
777}
778
779#[derive(Args, Debug)]
780pub struct IssueUpdateArgs {
781 /// Issue ID
782 pub id: String,
783
784 /// New title
785 #[arg(long)]
786 pub title: Option<String>,
787
788 /// New description
789 #[arg(short, long)]
790 pub description: Option<String>,
791
792 /// New details
793 #[arg(long)]
794 pub details: Option<String>,
795
796 /// New status
797 #[arg(short, long)]
798 pub status: Option<String>,
799
800 /// New priority
801 #[arg(short, long)]
802 pub priority: Option<i32>,
803
804 /// New type
805 #[arg(short = 't', long)]
806 pub issue_type: Option<String>,
807
808 /// New parent issue ID
809 #[arg(long)]
810 pub parent: Option<String>,
811
812 /// New plan ID
813 #[arg(long)]
814 pub plan: Option<String>,
815}
816
817// ============================================================================
818// Checkpoint Commands
819// ============================================================================
820
821#[derive(Subcommand, Debug)]
822pub enum CheckpointCommands {
823 /// Create a checkpoint
824 Create {
825 /// Checkpoint name
826 name: String,
827
828 /// Description
829 #[arg(short, long)]
830 description: Option<String>,
831
832 /// Include git status
833 #[arg(long)]
834 include_git: bool,
835 },
836
837 /// List checkpoints
838 List {
839 /// Search checkpoints by name or description
840 #[arg(short, long)]
841 search: Option<String>,
842
843 /// Filter by session ID
844 #[arg(long)]
845 session: Option<String>,
846
847 /// Filter by project path
848 #[arg(long)]
849 project: Option<String>,
850
851 /// Include checkpoints from all projects
852 #[arg(long)]
853 all_projects: bool,
854
855 /// Maximum checkpoints to return
856 #[arg(short, long, default_value = "20")]
857 limit: usize,
858
859 /// Pagination offset
860 #[arg(long)]
861 offset: Option<usize>,
862 },
863
864 /// Show checkpoint details
865 Show {
866 /// Checkpoint ID
867 id: String,
868 },
869
870 /// Restore from checkpoint
871 Restore {
872 /// Checkpoint ID
873 id: String,
874
875 /// Only restore items in these categories (comma-separated)
876 #[arg(long, value_delimiter = ',')]
877 categories: Option<Vec<String>>,
878
879 /// Only restore items with these tags (comma-separated)
880 #[arg(long, value_delimiter = ',')]
881 tags: Option<Vec<String>>,
882 },
883
884 /// Delete a checkpoint
885 Delete {
886 /// Checkpoint ID
887 id: String,
888 },
889
890 /// Add items to an existing checkpoint
891 AddItems {
892 /// Checkpoint ID
893 id: String,
894
895 /// Context item keys to add (comma-separated)
896 #[arg(short, long, value_delimiter = ',', required = true)]
897 keys: Vec<String>,
898 },
899
900 /// Remove items from a checkpoint
901 RemoveItems {
902 /// Checkpoint ID
903 id: String,
904
905 /// Context item keys to remove (comma-separated)
906 #[arg(short, long, value_delimiter = ',', required = true)]
907 keys: Vec<String>,
908 },
909
910 /// List items in a checkpoint
911 Items {
912 /// Checkpoint ID
913 id: String,
914 },
915}
916
917// ============================================================================
918// Memory Commands (Project-level persistent storage)
919// ============================================================================
920
921#[derive(Subcommand, Debug)]
922pub enum MemoryCommands {
923 /// Save a memory item
924 Save {
925 /// Key
926 key: String,
927
928 /// Value
929 value: String,
930
931 /// Category (command, config, note)
932 #[arg(short, long, default_value = "command")]
933 category: String,
934 },
935
936 /// Get a memory item
937 Get {
938 /// Key
939 key: String,
940 },
941
942 /// List memory items
943 List {
944 /// Filter by category
945 #[arg(short, long)]
946 category: Option<String>,
947 },
948
949 /// Delete a memory item
950 Delete {
951 /// Key
952 key: String,
953 },
954}
955
956// ============================================================================
957// Sync Commands
958// ============================================================================
959
960#[derive(Subcommand, Debug)]
961pub enum SyncCommands {
962 /// Export to JSONL
963 Export {
964 /// Force export even if JSONL is newer
965 #[arg(long)]
966 force: bool,
967 },
968
969 /// Import from JSONL
970 Import {
971 /// Force import even with conflicts
972 #[arg(long)]
973 force: bool,
974 },
975
976 /// Show sync status
977 Status,
978
979 /// Push local JSONL exports to remote host via SCP
980 Push {
981 /// Force export before push
982 #[arg(long)]
983 force: bool,
984
985 /// Remote project path (defaults to local CWD path)
986 #[arg(long)]
987 remote_path: Option<String>,
988
989 /// Full database sync (SQLite backup) instead of JSONL
990 #[arg(long)]
991 full: bool,
992 },
993
994 /// Pull JSONL exports from remote host via SCP
995 Pull {
996 /// Force import after pull
997 #[arg(long)]
998 force: bool,
999
1000 /// Remote project path (defaults to local CWD path)
1001 #[arg(long)]
1002 remote_path: Option<String>,
1003
1004 /// Full database sync (SQLite backup) instead of JSONL
1005 #[arg(long)]
1006 full: bool,
1007 },
1008
1009 /// Create a local database backup (used by full sync)
1010 Backup {
1011 /// Output path for the backup file
1012 #[arg(long)]
1013 output: Option<String>,
1014 },
1015}
1016
1017// ============================================================================
1018// Project Commands
1019// ============================================================================
1020
1021#[derive(Subcommand, Debug)]
1022pub enum ProjectCommands {
1023 /// Create a new project
1024 Create(ProjectCreateArgs),
1025
1026 /// List all projects
1027 List {
1028 /// Include session count for each project
1029 #[arg(long)]
1030 session_count: bool,
1031
1032 /// Maximum projects to return
1033 #[arg(short, long, default_value = "50")]
1034 limit: usize,
1035 },
1036
1037 /// Show project details
1038 Show {
1039 /// Project ID or path
1040 id: String,
1041 },
1042
1043 /// Update a project
1044 Update(ProjectUpdateArgs),
1045
1046 /// Delete a project
1047 Delete {
1048 /// Project ID or path
1049 id: String,
1050
1051 /// Skip confirmation and delete
1052 #[arg(short, long)]
1053 force: bool,
1054 },
1055}
1056
1057#[derive(Args, Debug)]
1058pub struct ProjectCreateArgs {
1059 /// Project path (defaults to current directory)
1060 pub path: Option<String>,
1061
1062 /// Project name (defaults to directory name)
1063 #[arg(short, long)]
1064 pub name: Option<String>,
1065
1066 /// Project description
1067 #[arg(short, long)]
1068 pub description: Option<String>,
1069
1070 /// Issue ID prefix (e.g., "SC" creates SC-1, SC-2)
1071 #[arg(short = 'p', long)]
1072 pub issue_prefix: Option<String>,
1073}
1074
1075#[derive(Args, Debug)]
1076pub struct ProjectUpdateArgs {
1077 /// Project ID or path
1078 pub id: String,
1079
1080 /// New project name
1081 #[arg(short, long)]
1082 pub name: Option<String>,
1083
1084 /// New description
1085 #[arg(short, long)]
1086 pub description: Option<String>,
1087
1088 /// New issue ID prefix
1089 #[arg(short = 'p', long)]
1090 pub issue_prefix: Option<String>,
1091}
1092
1093// ============================================================================
1094// Plan Commands
1095// ============================================================================
1096
1097#[derive(Subcommand, Debug)]
1098pub enum PlanCommands {
1099 /// Create a new plan
1100 Create(PlanCreateArgs),
1101
1102 /// List plans
1103 List {
1104 /// Filter by status (draft, active, completed, all)
1105 #[arg(short, long, default_value = "active")]
1106 status: String,
1107
1108 /// Maximum plans to return
1109 #[arg(short, long, default_value = "50")]
1110 limit: usize,
1111
1112 /// Filter by session ID (use "current" for active TTY session)
1113 #[arg(long)]
1114 session: Option<String>,
1115 },
1116
1117 /// Show plan details
1118 Show {
1119 /// Plan ID
1120 id: String,
1121 },
1122
1123 /// Update a plan
1124 Update(PlanUpdateArgs),
1125
1126 /// Capture a plan from an AI coding agent's plan file
1127 Capture {
1128 /// Only look in a specific agent's directory (claude, gemini, opencode, cursor)
1129 #[arg(long)]
1130 agent: Option<String>,
1131
1132 /// Max age in minutes (default: 30)
1133 #[arg(long, default_value = "30")]
1134 max_age: u64,
1135
1136 /// Explicit plan file path (skip discovery)
1137 #[arg(long)]
1138 file: Option<PathBuf>,
1139 },
1140}
1141
1142#[derive(Args, Debug)]
1143pub struct PlanCreateArgs {
1144 /// Plan title
1145 pub title: String,
1146
1147 /// Plan content (markdown PRD/spec)
1148 #[arg(short, long)]
1149 pub content: Option<String>,
1150
1151 /// Plan status (draft, active, completed)
1152 #[arg(short, long, default_value = "active")]
1153 pub status: String,
1154
1155 /// Success criteria
1156 #[arg(long)]
1157 pub success_criteria: Option<String>,
1158
1159 /// Bind to a specific session (default: auto-resolve from TTY)
1160 #[arg(long)]
1161 pub session: Option<String>,
1162}
1163
1164#[derive(Args, Debug)]
1165pub struct PlanUpdateArgs {
1166 /// Plan ID
1167 pub id: String,
1168
1169 /// New title
1170 #[arg(long)]
1171 pub title: Option<String>,
1172
1173 /// New content
1174 #[arg(short, long)]
1175 pub content: Option<String>,
1176
1177 /// New status (draft, active, completed)
1178 #[arg(short, long)]
1179 pub status: Option<String>,
1180
1181 /// New success criteria
1182 #[arg(long)]
1183 pub success_criteria: Option<String>,
1184}
1185
1186// ============================================================================
1187// Embeddings Commands
1188// ============================================================================
1189
1190#[derive(Subcommand, Debug, Clone)]
1191pub enum EmbeddingsCommands {
1192 /// Show embeddings status and configuration
1193 Status,
1194
1195 /// Configure embedding provider
1196 Configure {
1197 /// Provider (ollama, huggingface)
1198 #[arg(short, long)]
1199 provider: Option<String>,
1200
1201 /// Enable embeddings
1202 #[arg(long)]
1203 enable: bool,
1204
1205 /// Disable embeddings
1206 #[arg(long)]
1207 disable: bool,
1208
1209 /// Model to use (provider-specific)
1210 #[arg(short, long)]
1211 model: Option<String>,
1212
1213 /// API endpoint (for custom servers)
1214 #[arg(long)]
1215 endpoint: Option<String>,
1216
1217 /// API token (for HuggingFace)
1218 #[arg(long)]
1219 token: Option<String>,
1220 },
1221
1222 /// Backfill embeddings for existing context items
1223 Backfill {
1224 /// Maximum items to process
1225 #[arg(short, long)]
1226 limit: Option<usize>,
1227
1228 /// Session ID to backfill (defaults to current)
1229 #[arg(short, long)]
1230 session: Option<String>,
1231
1232 /// Force regeneration of existing embeddings
1233 #[arg(long)]
1234 force: bool,
1235 },
1236
1237 /// Test embedding provider connectivity
1238 Test {
1239 /// Text to generate test embedding for
1240 #[arg(default_value = "Hello world")]
1241 text: String,
1242 },
1243
1244 /// Process pending embeddings in background (internal use)
1245 #[command(hide = true)]
1246 ProcessPending {
1247 /// Maximum items to process
1248 #[arg(short, long, default_value = "10")]
1249 limit: usize,
1250
1251 /// Run silently (no output)
1252 #[arg(long)]
1253 quiet: bool,
1254 },
1255
1256 /// Upgrade items with fast embeddings to quality embeddings
1257 ///
1258 /// Items saved with the 2-tier system get instant fast embeddings (Model2Vec).
1259 /// This command generates higher-quality embeddings (Ollama/HuggingFace)
1260 /// for items that only have fast embeddings.
1261 UpgradeQuality {
1262 /// Maximum items to process
1263 #[arg(short, long)]
1264 limit: Option<usize>,
1265
1266 /// Session ID to upgrade (defaults to all sessions)
1267 #[arg(short, long)]
1268 session: Option<String>,
1269 },
1270}
1271
1272// ============================================================================
1273// Skills Commands
1274// ============================================================================
1275
1276#[derive(Subcommand, Debug)]
1277pub enum SkillsCommands {
1278 /// Download and install skills, hooks, and status line
1279 Install {
1280 /// Target tool (claude-code, codex, gemini). Auto-detects if omitted.
1281 #[arg(long)]
1282 tool: Option<String>,
1283
1284 /// Skill mode: cli, mcp, or both (default: both)
1285 #[arg(long, default_value = "both")]
1286 mode: String,
1287
1288 /// Custom skills directory (overrides auto-detected path)
1289 #[arg(long)]
1290 path: Option<PathBuf>,
1291 },
1292
1293 /// Show installed skills and versions
1294 Status,
1295
1296 /// Re-download and install latest skills
1297 Update {
1298 /// Target tool (claude-code, codex, gemini). Updates all if omitted.
1299 #[arg(long)]
1300 tool: Option<String>,
1301 },
1302}
1303
1304// ============================================================================
1305// Config Commands
1306// ============================================================================
1307
1308#[derive(Subcommand, Debug)]
1309pub enum ConfigCommands {
1310 /// Remote host configuration
1311 Remote {
1312 #[command(subcommand)]
1313 command: ConfigRemoteCommands,
1314 },
1315}
1316
1317#[derive(Subcommand, Debug)]
1318pub enum ConfigRemoteCommands {
1319 /// Set remote host configuration
1320 Set(RemoteSetArgs),
1321
1322 /// Show current remote configuration
1323 Show,
1324
1325 /// Remove remote configuration
1326 Remove,
1327}
1328
1329#[derive(Args, Debug)]
1330pub struct RemoteSetArgs {
1331 /// Remote hostname or IP
1332 #[arg(long)]
1333 pub host: String,
1334
1335 /// SSH username
1336 #[arg(long)]
1337 pub user: String,
1338
1339 /// SSH port (default: 22)
1340 #[arg(long, default_value = "22")]
1341 pub port: u16,
1342
1343 /// Path to SSH identity file (private key)
1344 #[arg(long)]
1345 pub identity_file: Option<String>,
1346
1347 /// Path to sc binary on remote host (default: sc)
1348 #[arg(long, default_value = "sc")]
1349 pub remote_sc_path: Option<String>,
1350
1351 /// Default remote project path
1352 #[arg(long)]
1353 pub remote_project_path: Option<String>,
1354
1355 /// Path to SaveContext database on remote host
1356 #[arg(long)]
1357 pub remote_db_path: Option<String>,
1358}
1359
1360// ============================================================================
1361// Time Tracking Commands
1362// ============================================================================
1363
1364#[derive(Subcommand, Debug)]
1365pub enum TimeCommands {
1366 /// Log hours worked
1367 Log(TimeLogArgs),
1368
1369 /// List time entries
1370 List(TimeListArgs),
1371
1372 /// Show grouped summary with subtotals
1373 Summary {
1374 /// Filter by billing period
1375 #[arg(long)]
1376 period: Option<String>,
1377
1378 /// Group by: period (default), date, issue, status
1379 #[arg(long, default_value = "period")]
1380 group_by: String,
1381
1382 /// Filter by status (logged, reviewed, invoiced)
1383 #[arg(long)]
1384 status: Option<String>,
1385 },
1386
1387 /// Show total hours
1388 Total {
1389 /// Filter by billing period
1390 #[arg(long)]
1391 period: Option<String>,
1392
1393 /// Filter by status
1394 #[arg(long)]
1395 status: Option<String>,
1396 },
1397
1398 /// Update a time entry
1399 Update(TimeUpdateArgs),
1400
1401 /// Delete a time entry
1402 Delete {
1403 /// Time entry ID (full or short, e.g. TE-a1b2)
1404 id: String,
1405 },
1406
1407 /// Mark entries as invoiced for a billing period
1408 Invoice {
1409 /// Billing period to invoice
1410 #[arg(long)]
1411 period: String,
1412
1413 /// Source status to transition from (default: logged)
1414 #[arg(long, default_value = "logged")]
1415 from: String,
1416 },
1417}
1418
1419#[derive(Args, Debug)]
1420pub struct TimeLogArgs {
1421 /// Hours worked (e.g. 4, 1.5, 0.25)
1422 pub hours: f64,
1423
1424 /// Description of work performed
1425 pub description: String,
1426
1427 /// Link to an issue (short ID like SC-a1b2)
1428 #[arg(long)]
1429 pub issue: Option<String>,
1430
1431 /// Billing period identifier (e.g. GL-ABG-2026-001)
1432 #[arg(long)]
1433 pub period: Option<String>,
1434
1435 /// Work date (YYYY-MM-DD, defaults to today)
1436 #[arg(long)]
1437 pub date: Option<String>,
1438}
1439
1440#[derive(Args, Debug, Default)]
1441pub struct TimeListArgs {
1442 /// Filter by billing period
1443 #[arg(long)]
1444 pub period: Option<String>,
1445
1446 /// Filter by status (logged, reviewed, invoiced)
1447 #[arg(long)]
1448 pub status: Option<String>,
1449
1450 /// Filter by issue ID
1451 #[arg(long)]
1452 pub issue: Option<String>,
1453
1454 /// Filter from date (YYYY-MM-DD)
1455 #[arg(long)]
1456 pub from: Option<String>,
1457
1458 /// Filter to date (YYYY-MM-DD)
1459 #[arg(long)]
1460 pub to: Option<String>,
1461
1462 /// Maximum entries to return
1463 #[arg(short, long, default_value = "200")]
1464 pub limit: u32,
1465}
1466
1467#[derive(Args, Debug)]
1468pub struct TimeUpdateArgs {
1469 /// Time entry ID (full or short)
1470 pub id: String,
1471
1472 /// New hours
1473 #[arg(long)]
1474 pub hours: Option<f64>,
1475
1476 /// New description
1477 #[arg(long)]
1478 pub description: Option<String>,
1479
1480 /// New billing period
1481 #[arg(long)]
1482 pub period: Option<String>,
1483
1484 /// New issue link
1485 #[arg(long)]
1486 pub issue: Option<String>,
1487
1488 /// New work date
1489 #[arg(long)]
1490 pub date: Option<String>,
1491
1492 /// New status (logged, reviewed, invoiced)
1493 #[arg(long)]
1494 pub status: Option<String>,
1495}