22 * CLI argument parsing and program setup
33 */
44
5- import { resolve } from "node:path"
5+ import { existsSync , mkdirSync } from "node:fs"
6+ import { join , resolve } from "node:path"
67import { createInterface } from "node:readline"
78import { Command } from "commander"
89import { loadConfig } from "./config.ts"
9- import { initializePaths } from "./fs.ts"
10+ import { getTimestampForFilename , initializePaths } from "./fs.ts"
11+ import { countIdeas , getIdeaSummary , loadAllIdeas } from "./ideas.ts"
1012import { runLoop } from "./loop.ts"
1113import { formatMetricsSummary , loadMetrics , resetMetrics , saveMetrics } from "./metrics.ts"
1214import type { CliOptions } from "./types.ts"
@@ -23,6 +25,51 @@ export interface ParsedCli {
2325 hint ?: string
2426}
2527
28+ /**
29+ * Handle the 'idea' subcommand: save an idea to the queue
30+ */
31+ async function handleIdeaCommand (
32+ description : string ,
33+ opts : Record < string , unknown > ,
34+ ) : Promise < void > {
35+ try {
36+ const projectDir = opts . project ? resolve ( opts . project as string ) : process . cwd ( )
37+ const paths = initializePaths ( projectDir )
38+
39+ // Ensure ideas directory exists
40+ if ( ! existsSync ( paths . ideasDir ) ) {
41+ mkdirSync ( paths . ideasDir , { recursive : true } )
42+ }
43+
44+ // Generate filename: YYYYMMDD_HHMMSS_slugified-description.md
45+ const timestamp = getTimestampForFilename ( )
46+ const slug = description
47+ . toLowerCase ( )
48+ . replace ( / [ ^ a - z 0 - 9 ] + / g, "-" )
49+ . replace ( / ^ - + | - + $ / g, "" )
50+ . slice ( 0 , 50 ) // Limit slug length
51+
52+ const filename = `${ timestamp } _${ slug } .md`
53+ const filepath = join ( paths . ideasDir , filename )
54+
55+ // Create markdown content
56+ const content = `# ${ description }
57+
58+ <!-- Add details, steps, or context here -->
59+ `
60+
61+ // Write the file
62+ await Bun . write ( filepath , content )
63+
64+ console . log ( `\n✓ Idea added to queue: ${ filename } ` )
65+ console . log ( ` Location: ${ filepath } ` )
66+ console . log ( `\nYou can edit this file to add more details before opencoder processes it.\n` )
67+ } catch ( err ) {
68+ console . error ( `Error: Failed to add idea: ${ err instanceof Error ? err . message : String ( err ) } ` )
69+ process . exit ( 1 )
70+ }
71+ }
72+
2673/**
2774 * Create and configure the CLI program
2875 */
@@ -44,6 +91,28 @@ function createProgram(): Command {
4491 . option ( "-s, --signoff" , "Add Signed-off-by line to commits" )
4592 . option ( "--status" , "Display metrics summary and exit" )
4693 . option ( "--metrics-reset" , "Reset metrics to default values (requires confirmation)" )
94+ . option ( "--ideas-list" , "List all ideas in the queue and exit" )
95+
96+ // Add 'idea' subcommand
97+ const ideaCommand = program
98+ . command ( "idea" )
99+ . description ( "Add a new idea to the queue" )
100+ . argument ( "<description>" , "Description of the idea" )
101+ . option ( "-p, --project <dir>" , "Project directory (default: current directory)" )
102+ . action ( async ( description : string , opts : Record < string , unknown > ) => {
103+ await handleIdeaCommand ( description , opts )
104+ } )
105+
106+ // Add help text for idea command
107+ ideaCommand . addHelpText (
108+ "after" ,
109+ `
110+ Examples:
111+ $ opencoder idea "Fix login bug"
112+ $ opencoder idea "Add dark mode support" -p ./myproject
113+ $ opencoder idea "Implement user authentication with JWT tokens"
114+ ` ,
115+ )
47116
48117 // Add examples to help
49118 program . addHelpText (
@@ -80,6 +149,21 @@ Examples:
80149 $ opencoder --metrics-reset
81150 Reset metrics to default values (with confirmation)
82151
152+ $ opencoder --ideas-list
153+ List all ideas in the queue without starting the loop
154+
155+ $ opencoder --ideas-list -p ./myproject
156+ List ideas for a specific project
157+
158+ $ opencoder idea "Fix login bug"
159+ Add a new idea to the queue
160+
161+ $ opencoder idea "Add dark mode support" -p ./myproject
162+ Add idea to a specific project
163+
164+ Commands:
165+ idea <description> Add a new idea to the queue
166+
83167Options:
84168 -p, --project <dir> Project directory (default: current directory)
85169 -m, --model <model> Model for both plan and build
@@ -92,6 +176,7 @@ Options:
92176 -s, --signoff Add Signed-off-by line to commits
93177 --status Display metrics summary and exit
94178 --metrics-reset Reset metrics to default values (requires confirmation)
179+ --ideas-list List all ideas in the queue and exit
95180
96181Environment variables:
97182 OPENCODER_PLAN_MODEL Default plan model
@@ -175,6 +260,7 @@ export function parseCli(argv: string[] = process.argv): ParsedCli {
175260 commitSignoff : opts . signoff as boolean | undefined ,
176261 status : opts . status as boolean | undefined ,
177262 metricsReset : opts . metricsReset as boolean | undefined ,
263+ ideasList : opts . ideasList as boolean | undefined ,
178264 } ,
179265 hint : args [ 0 ] ,
180266 }
@@ -213,6 +299,7 @@ export async function run(): Promise<void> {
213299 commitSignoff : opts . signoff as boolean | undefined ,
214300 status : opts . status as boolean | undefined ,
215301 metricsReset : opts . metricsReset as boolean | undefined ,
302+ ideasList : opts . ideasList as boolean | undefined ,
216303 }
217304
218305 // Handle --version flag: display version info and exit
@@ -266,6 +353,36 @@ export async function run(): Promise<void> {
266353 return
267354 }
268355
356+ // Handle --ideas-list flag: display ideas queue and exit
357+ if ( cliOptions . ideasList ) {
358+ const projectDir = cliOptions . project ? resolve ( cliOptions . project ) : process . cwd ( )
359+ const paths = initializePaths ( projectDir )
360+
361+ const ideas = await loadAllIdeas ( paths . ideasDir )
362+ const count = await countIdeas ( paths . ideasDir )
363+
364+ console . log ( "\nIdeas Queue" )
365+ console . log ( "===========\n" )
366+
367+ if ( count === 0 ) {
368+ console . log ( "No ideas in queue." )
369+ console . log ( `\nTo add ideas, place .md files in: ${ paths . ideasDir } ` )
370+ } else {
371+ console . log ( `Found ${ count } idea(s):\n` )
372+ for ( let i = 0 ; i < ideas . length ; i ++ ) {
373+ const idea = ideas [ i ]
374+ if ( ! idea ) continue
375+ const summary = getIdeaSummary ( idea . content )
376+ console . log ( ` ${ i + 1 } . ${ idea . filename } ` )
377+ console . log ( ` ${ summary } ` )
378+ console . log ( "" )
379+ }
380+ }
381+
382+ console . log ( "" )
383+ return
384+ }
385+
269386 const config = await loadConfig ( cliOptions , hint )
270387 await runLoop ( config )
271388 } catch ( err ) {
0 commit comments