@@ -13,6 +13,7 @@ import * as path from "path";
1313import type { RawData } from "ws" ;
1414import { WebSocket , WebSocketServer } from "ws" ;
1515import { Command } from "commander" ;
16+ import { validateProjectPath } from "./utils/pathUtils" ;
1617
1718// Parse command line arguments
1819const program = new Command ( ) ;
@@ -22,11 +23,16 @@ program
2223 . description ( "HTTP/WebSocket server for cmux - allows accessing cmux backend from mobile devices" )
2324 . option ( "-h, --host <host>" , "bind to specific host" , "localhost" )
2425 . option ( "-p, --port <port>" , "bind to specific port" , "3000" )
26+ . option ( "--add-project <path>" , "add and open project at the specified path (idempotent)" )
2527 . parse ( process . argv ) ;
2628
2729const options = program . opts ( ) ;
2830const HOST = options . host as string ;
2931const PORT = parseInt ( options . port as string , 10 ) ;
32+ const ADD_PROJECT_PATH = options . addProject as string | undefined ;
33+
34+ // Track the launch project path for initial navigation
35+ let launchProjectPath : string | null = null ;
3036
3137// Mock Electron's ipcMain for HTTP
3238class HttpIpcMainAdapter {
@@ -35,6 +41,13 @@ class HttpIpcMainAdapter {
3541
3642 constructor ( private readonly app : express . Application ) { }
3743
44+ // Public method to get a handler (for internal use)
45+ getHandler (
46+ channel : string
47+ ) : ( ( event : unknown , ...args : unknown [ ] ) => Promise < unknown > ) | undefined {
48+ return this . handlers . get ( channel ) ;
49+ }
50+
3851 handle ( channel : string , handler : ( event : unknown , ...args : unknown [ ] ) => Promise < unknown > ) : void {
3952 this . handlers . set ( channel , handler ) ;
4053
@@ -138,6 +151,11 @@ ipcMainService.register(
138151 mockWindow as unknown as BrowserWindow
139152) ;
140153
154+ // Add custom endpoint for launch project (only for server mode)
155+ httpIpcMain . handle ( "server:getLaunchProject" , ( ) => {
156+ return Promise . resolve ( launchProjectPath ) ;
157+ } ) ;
158+
141159// Serve static files from dist directory (built renderer)
142160app . use ( express . static ( path . join ( __dirname , "." ) ) ) ;
143161
@@ -247,6 +265,85 @@ wss.on("connection", (ws) => {
247265 } ) ;
248266} ) ;
249267
268+ /**
269+ * Initialize a project from the --add-project flag
270+ * This checks if a project exists at the given path, creates it if not, and opens it
271+ */
272+ async function initializeProject (
273+ projectPath : string ,
274+ ipcAdapter : HttpIpcMainAdapter
275+ ) : Promise < void > {
276+ try {
277+ // Trim trailing slashes to ensure proper project name extraction
278+ projectPath = projectPath . replace ( / \/ + $ / , "" ) ;
279+
280+ // Normalize path (expand tilde, make absolute) to match how PROJECT_CREATE normalizes paths
281+ const validation = await validateProjectPath ( projectPath ) ;
282+ if ( ! validation . valid ) {
283+ const errorMsg = validation . error ?? "Unknown validation error" ;
284+ console . error ( `Invalid project path: ${ errorMsg } ` ) ;
285+ return ;
286+ }
287+ projectPath = validation . expandedPath ! ;
288+
289+ // First, check if project already exists by listing all projects
290+ const handler = ipcAdapter . getHandler ( IPC_CHANNELS . PROJECT_LIST ) ;
291+ if ( ! handler ) {
292+ console . error ( "PROJECT_LIST handler not found" ) ;
293+ return ;
294+ }
295+
296+ const projectsList = await handler ( null ) ;
297+ if ( ! Array . isArray ( projectsList ) ) {
298+ console . error ( "Unexpected PROJECT_LIST response format" ) ;
299+ return ;
300+ }
301+
302+ // Check if the project already exists (projectsList is Array<[string, ProjectConfig]>)
303+ const existingProject = ( projectsList as Array < [ string , unknown ] > ) . find (
304+ ( [ path ] ) => path === projectPath
305+ ) ;
306+
307+ if ( existingProject ) {
308+ console . log ( `Project already exists at: ${ projectPath } ` ) ;
309+ launchProjectPath = projectPath ;
310+ return ;
311+ }
312+
313+ // Project doesn't exist, create it
314+ console . log ( `Creating new project at: ${ projectPath } ` ) ;
315+ const createHandler = ipcAdapter . getHandler ( IPC_CHANNELS . PROJECT_CREATE ) ;
316+ if ( ! createHandler ) {
317+ console . error ( "PROJECT_CREATE handler not found" ) ;
318+ return ;
319+ }
320+
321+ const createResult = await createHandler ( null , projectPath ) ;
322+
323+ // Check if creation was successful using the Result type
324+ if ( createResult && typeof createResult === "object" && "success" in createResult ) {
325+ if ( createResult . success ) {
326+ console . log ( `Successfully created project at: ${ projectPath } ` ) ;
327+ launchProjectPath = projectPath ;
328+ } else if ( "error" in createResult ) {
329+ const err = createResult as { error : unknown } ;
330+ const errorMsg = err . error instanceof Error ? err . error . message : String ( err . error ) ;
331+ console . error ( `Failed to create project: ${ errorMsg } ` ) ;
332+ }
333+ } else {
334+ console . error ( "Unexpected PROJECT_CREATE response format" ) ;
335+ }
336+ } catch ( error ) {
337+ console . error ( `Error initializing project:` , error ) ;
338+ }
339+ }
340+
250341server . listen ( PORT , HOST , ( ) => {
251342 console . log ( `Server is running on http://${ HOST } :${ PORT } ` ) ;
343+
344+ // Handle --add-project flag if present
345+ if ( ADD_PROJECT_PATH ) {
346+ console . log ( `Initializing project at: ${ ADD_PROJECT_PATH } ` ) ;
347+ void initializeProject ( ADD_PROJECT_PATH , httpIpcMain ) ;
348+ }
252349} ) ;
0 commit comments