11import { exec , execSync } from "node:child_process" ;
2- import { existsSync , readFileSync } from "node:fs" ;
2+ import { existsSync , readFileSync , readdirSync } from "node:fs" ;
33import { homedir } from "node:os" ;
44import { promisify } from "node:util" ;
55import { join , resolve } from "node:path" ;
@@ -167,6 +167,45 @@ function isExisting0pflow(): boolean {
167167 }
168168}
169169
170+ interface ProjectInfo {
171+ name : string ;
172+ path : string ;
173+ }
174+
175+ /**
176+ * Scan ~/0pflow/ for directories that contain a package.json with 0pflow as a dependency.
177+ */
178+ function discoverProjects ( ) : ProjectInfo [ ] {
179+ const baseDir = join ( homedir ( ) , "0pflow" ) ;
180+ if ( ! existsSync ( baseDir ) ) return [ ] ;
181+
182+ const projects : ProjectInfo [ ] = [ ] ;
183+ let entries : string [ ] ;
184+ try {
185+ entries = readdirSync ( baseDir ) ;
186+ } catch {
187+ return [ ] ;
188+ }
189+
190+ for ( const entry of entries ) {
191+ const projectDir = join ( baseDir , entry ) ;
192+ const pkgPath = join ( projectDir , "package.json" ) ;
193+ try {
194+ if ( ! existsSync ( pkgPath ) ) continue ;
195+ const pkg = JSON . parse ( readFileSync ( pkgPath , "utf-8" ) ) ;
196+ const deps = { ...pkg . dependencies , ...pkg . devDependencies } ;
197+ if ( "0pflow" in deps ) {
198+ projects . push ( { name : entry , path : projectDir } ) ;
199+ }
200+ } catch {
201+ continue ;
202+ }
203+ }
204+
205+ projects . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
206+ return projects ;
207+ }
208+
170209const WELCOME_PROMPT =
171210 "Welcome to your 0pflow project! What workflow would you like to create? Here are some ideas:\n\n" +
172211 '- "Enrich leads from a CSV file with company data"\n' +
@@ -175,6 +214,53 @@ const WELCOME_PROMPT =
175214 '- "Score and route inbound leads based on firmographics"\n\n' +
176215 "Describe what you'd like to automate and I'll help you build it with /create-workflow." ;
177216
217+ async function launchExistingProject ( projectPath : string ) : Promise < void > {
218+ // Check if database is paused and start it if needed
219+ try {
220+ const { findEnvFile } = await import ( "./env.js" ) ;
221+ const envPath = findEnvFile ( projectPath ) ;
222+ if ( envPath ) {
223+ const envContent = readFileSync ( envPath , "utf-8" ) ;
224+ const parsed = dotenv . parse ( envContent ) ;
225+
226+ if ( parsed . DATABASE_URL ) {
227+ const serviceId = extractServiceIdFromUrl ( parsed . DATABASE_URL ) ;
228+ if ( serviceId ) {
229+ const status = startDatabaseIfNeeded ( serviceId , false ) ;
230+ if ( status === "started" ) {
231+ const s = p . spinner ( ) ;
232+ s . start ( "Database was paused, waiting for it to start..." ) ;
233+ const ready = await waitForDatabase ( serviceId , 3 * 60 * 1000 ) ;
234+ if ( ready ) {
235+ s . stop ( pc . green ( "Database is ready" ) ) ;
236+ } else {
237+ s . stop ( pc . yellow ( "Database is starting (taking longer than expected)" ) ) ;
238+ }
239+ }
240+ }
241+ }
242+ }
243+ } catch {
244+ // Continue without database check if env loading fails
245+ }
246+
247+ const mode = await p . select ( {
248+ message : "Launch mode" ,
249+ options : [
250+ { value : "normal" as const , label : "Launch" } ,
251+ { value : "yolo" as const , label : "Launch with --dangerously-skip-permissions" } ,
252+ ] ,
253+ } ) ;
254+
255+ if ( p . isCancel ( mode ) ) {
256+ p . cancel ( "Cancelled." ) ;
257+ process . exit ( 0 ) ;
258+ }
259+
260+ p . outro ( pc . green ( "Launching..." ) ) ;
261+ await launchDevServer ( projectPath , { yolo : mode === "yolo" } ) ;
262+ }
263+
178264async function launchDevServer ( cwd : string , { yolo = false } : { yolo ?: boolean } = { } ) : Promise < void > {
179265 // Load .env from the app directory (not process.cwd(), which may be a parent)
180266 try {
@@ -235,53 +321,42 @@ export async function runRun(): Promise<void> {
235321 }
236322 }
237323
238- // ── Existing project → launch ──────────────── ───────────────────────
324+ // ── Existing project (CWD) → launch directly ───────────────────────
239325 if ( isExisting0pflow ( ) ) {
240- // Check if database is paused and start it if needed
241- try {
242- const { findEnvFile } = await import ( "./env.js" ) ;
243- const envPath = findEnvFile ( process . cwd ( ) ) ;
244- if ( envPath ) {
245- const envContent = readFileSync ( envPath , "utf-8" ) ;
246- const parsed = dotenv . parse ( envContent ) ;
247-
248- if ( parsed . DATABASE_URL ) {
249- const serviceId = extractServiceIdFromUrl ( parsed . DATABASE_URL ) ;
250- if ( serviceId ) {
251- const status = startDatabaseIfNeeded ( serviceId , false ) ;
252- if ( status === "started" ) {
253- const s = p . spinner ( ) ;
254- s . start ( "Database was paused, waiting for it to start..." ) ;
255- const ready = await waitForDatabase ( serviceId , 3 * 60 * 1000 ) ;
256- if ( ready ) {
257- s . stop ( pc . green ( "Database is ready" ) ) ;
258- } else {
259- s . stop ( pc . yellow ( "Database is starting (taking longer than expected)" ) ) ;
260- }
261- }
262- }
263- }
264- }
265- } catch {
266- // Continue without database check if env loading fails
267- }
326+ await launchExistingProject ( process . cwd ( ) ) ;
327+ return ;
328+ }
268329
269- const mode = await p . select ( {
270- message : "Launch mode" ,
330+ // ── Discover existing projects in ~/0pflow/ ───────────────────────
331+ const projects = discoverProjects ( ) ;
332+
333+ if ( projects . length > 0 ) {
334+ const CREATE_NEW = "__create_new__" ;
335+
336+ const projectChoice = await p . select ( {
337+ message : "Select a project" ,
271338 options : [
272- { value : "normal" as const , label : "Launch" } ,
273- { value : "yolo" as const , label : "Launch with --dangerously-skip-permissions" } ,
339+ ...projects . map ( ( proj ) => ( {
340+ value : proj . path ,
341+ label : proj . name ,
342+ hint : proj . path ,
343+ } ) ) ,
344+ {
345+ value : CREATE_NEW ,
346+ label : pc . green ( "+ Create new project" ) ,
347+ } ,
274348 ] ,
275349 } ) ;
276350
277- if ( p . isCancel ( mode ) ) {
351+ if ( p . isCancel ( projectChoice ) ) {
278352 p . cancel ( "Cancelled." ) ;
279353 process . exit ( 0 ) ;
280354 }
281355
282- p . outro ( pc . green ( "Launching..." ) ) ;
283- await launchDevServer ( process . cwd ( ) , { yolo : mode === "yolo" } ) ;
284- return ;
356+ if ( projectChoice !== CREATE_NEW ) {
357+ await launchExistingProject ( projectChoice ) ;
358+ return ;
359+ }
285360 }
286361
287362 // ── Project name ────────────────────────────────────────────────────
0 commit comments