| 766 | } |
| 767 | |
| 768 | export function startCommand(getConfig: () => Config): Command { |
| 769 | const cmd = new Command('start') |
| 770 | .description('Start the knowledge base collab server') |
| 771 | .option('-p, --port <port>', 'Server port', undefined) |
| 772 | .option( |
| 773 | '--ui-port <port>', |
| 774 | 'Pin the ok ui sibling to <port> and connect (not exit) if a server already runs here — the worktree-preview recipe path', |
| 775 | parseUiPort, |
| 776 | ) |
| 777 | .option('-H, --host <host>', 'Server host', undefined) |
| 778 | .option('--open', 'Open browser after start') |
| 779 | .option('--mode <mode>', "Force dispatch mode: 'browser' or 'app'", parseStartMode) |
| 780 | .option('--serve-content-assets', 'Serve content assets from this server') |
| 781 | .option( |
| 782 | '--react-shell-dist-dir <path>', |
| 783 | 'Serve React shell from <path> (suppresses ok ui sibling)', |
| 784 | ) |
| 785 | .option( |
| 786 | '--single-file <path>', |
| 787 | 'No-project ephemeral single-file mode: scope the server to one markdown file (git + MCP off)', |
| 788 | ) |
| 789 | .option( |
| 790 | '--project-dir <dir>', |
| 791 | 'Throwaway project root for --single-file (where ephemeral .ok/ state lives)', |
| 792 | ) |
| 793 | .action(async (opts: StartCommandOptions) => { |
| 794 | const config = getConfig(); |
| 795 | |
| 796 | if (opts.mode === 'app') { |
| 797 | if (opts.open) { |
| 798 | process.stderr.write( |
| 799 | "error: option '--mode=app' cannot be combined with '--open' (--open opens a browser tab against the local server, which app mode does not boot)\n", |
| 800 | ); |
| 801 | process.exit(2); |
| 802 | } |
| 803 | |
| 804 | const ignored: string[] = []; |
| 805 | if (opts.port !== undefined) ignored.push('--port'); |
| 806 | if (opts.uiPort !== undefined) ignored.push('--ui-port'); |
| 807 | if (opts.host !== undefined) ignored.push('--host'); |
| 808 | if (ignored.length > 0) { |
| 809 | const logLevel = process.env.OK_LOG_LEVEL ?? 'info'; |
| 810 | if (logLevel === 'debug' || logLevel === 'trace') { |
| 811 | console.error(`--mode=app: ignoring ${ignored.join(', ')}`); |
| 812 | } |
| 813 | } |
| 814 | |
| 815 | const decision = detectDesktop(createRealDetectDeps()); |
| 816 | |
| 817 | if (decision.available) { |
| 818 | launchDesktop({ spawn: nativeSpawn }); |
| 819 | return; |
| 820 | } |
| 821 | |
| 822 | console.error(notFoundMessage(decision.reason)); |
| 823 | process.exit(1); |
| 824 | } |
| 825 | |