77package cli
88
99import (
10+ "bufio"
1011 "fmt"
1112 "os"
1213 "path/filepath"
@@ -18,8 +19,9 @@ import (
1819)
1920
2021var (
21- addPriority string
22- addSection string
22+ addPriority string
23+ addSection string
24+ addFromFile string
2325)
2426
2527// fileTypeMap maps short names to actual file names
@@ -37,7 +39,7 @@ var fileTypeMap = map[string]string{
3739// AddCmd returns the add command.
3840func AddCmd () * cobra.Command {
3941 cmd := & cobra.Command {
40- Use : "add <type> < content> " ,
42+ Use : "add <type> [ content] " ,
4143 Short : "Add a new item to a context file" ,
4244 Long : `Add a new decision, task, learning, or convention to the appropriate context file.
4345
@@ -47,24 +49,64 @@ Types:
4749 learning Add to LEARNINGS.md
4850 convention Add to CONVENTIONS.md
4951
52+ Content can be provided as:
53+ - Command argument: ctx add learning "text here"
54+ - File: ctx add learning --file /path/to/content.md
55+ - Stdin: echo "text" | ctx add learning
56+
5057Examples:
5158 ctx add decision "Use PostgreSQL for primary database"
5259 ctx add task "Implement user authentication" --priority high
5360 ctx add learning "Vitest mocks must be hoisted"
54- ctx add convention "All API routes must be versioned"` ,
55- Args : cobra .MinimumNArgs (2 ),
61+ ctx add learning --file learning-template.md
62+ cat notes.md | ctx add decision` ,
63+ Args : cobra .MinimumNArgs (1 ),
5664 RunE : runAdd ,
5765 }
5866
5967 cmd .Flags ().StringVarP (& addPriority , "priority" , "p" , "" , "Priority level for tasks (high, medium, low)" )
6068 cmd .Flags ().StringVarP (& addSection , "section" , "s" , "" , "Target section within file" )
69+ cmd .Flags ().StringVarP (& addFromFile , "file" , "f" , "" , "Read content from file instead of argument" )
6170
6271 return cmd
6372}
6473
6574func runAdd (cmd * cobra.Command , args []string ) error {
6675 fileType := strings .ToLower (args [0 ])
67- content := strings .Join (args [1 :], " " )
76+
77+ // Determine content source: args, --file, or stdin
78+ var content string
79+
80+ if addFromFile != "" {
81+ // Read from file
82+ fileContent , err := os .ReadFile (addFromFile )
83+ if err != nil {
84+ return fmt .Errorf ("failed to read file %s: %w" , addFromFile , err )
85+ }
86+ content = strings .TrimSpace (string (fileContent ))
87+ } else if len (args ) > 1 {
88+ // Content from arguments
89+ content = strings .Join (args [1 :], " " )
90+ } else {
91+ // Try reading from stdin (check if it's a pipe)
92+ stat , _ := os .Stdin .Stat ()
93+ if (stat .Mode () & os .ModeCharDevice ) == 0 {
94+ // stdin is a pipe, read from it
95+ scanner := bufio .NewScanner (os .Stdin )
96+ var lines []string
97+ for scanner .Scan () {
98+ lines = append (lines , scanner .Text ())
99+ }
100+ if err := scanner .Err (); err != nil {
101+ return fmt .Errorf ("failed to read from stdin: %w" , err )
102+ }
103+ content = strings .TrimSpace (strings .Join (lines , "\n " ))
104+ }
105+ }
106+
107+ if content == "" {
108+ return fmt .Errorf ("no content provided. Use argument, --file, or pipe from stdin" )
109+ }
68110
69111 fileName , ok := fileTypeMap [fileType ]
70112 if ! ok {
0 commit comments