@monoes/monobrowse 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/package.json +71 -0
  2. package/dist/src/browser/action-builder/analyzer.d.ts +0 -1
  3. package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
  4. package/dist/src/browser/action-builder/analyzer.js +43 -21
  5. package/dist/src/browser/action-builder/analyzer.js.map +1 -1
  6. package/dist/src/browser/adapters/index.d.ts.map +1 -1
  7. package/dist/src/browser/adapters/index.js +4 -0
  8. package/dist/src/browser/adapters/index.js.map +1 -1
  9. package/dist/src/browser/browser.js +2 -2
  10. package/dist/src/browser/browser.js.map +1 -1
  11. package/dist/src/browser/cdp.d.ts +6 -0
  12. package/dist/src/browser/cdp.d.ts.map +1 -1
  13. package/dist/src/browser/cdp.js +56 -1
  14. package/dist/src/browser/cdp.js.map +1 -1
  15. package/dist/src/browser/dashboard/api-types.d.ts +1 -1
  16. package/dist/src/browser/dashboard/server.d.ts +12 -1
  17. package/dist/src/browser/dashboard/server.d.ts.map +1 -1
  18. package/dist/src/browser/dashboard/server.js +23 -28
  19. package/dist/src/browser/dashboard/server.js.map +1 -1
  20. package/dist/src/browser/dashboard/ui.html +1811 -0
  21. package/dist/src/browser/playbook/browser-handlers.d.ts +3 -0
  22. package/dist/src/browser/playbook/browser-handlers.d.ts.map +1 -0
  23. package/dist/src/browser/playbook/browser-handlers.js +377 -0
  24. package/dist/src/browser/playbook/browser-handlers.js.map +1 -0
  25. package/dist/src/browser/playbook/builtin-handlers.d.ts +3 -0
  26. package/dist/src/browser/playbook/builtin-handlers.d.ts.map +1 -0
  27. package/dist/src/browser/playbook/builtin-handlers.js +364 -0
  28. package/dist/src/browser/playbook/builtin-handlers.js.map +1 -0
  29. package/dist/src/browser/playbook/index.d.ts +3 -0
  30. package/dist/src/browser/playbook/index.d.ts.map +1 -0
  31. package/dist/src/browser/playbook/index.js +3 -0
  32. package/dist/src/browser/playbook/index.js.map +1 -0
  33. package/dist/src/browser/playbook/store.d.ts +8 -0
  34. package/dist/src/browser/playbook/store.d.ts.map +1 -0
  35. package/dist/src/browser/playbook/store.js +87 -0
  36. package/dist/src/browser/playbook/store.js.map +1 -0
  37. package/dist/src/browser/screenshot.d.ts +2 -0
  38. package/dist/src/browser/screenshot.d.ts.map +1 -1
  39. package/dist/src/browser/screenshot.js +69 -2
  40. package/dist/src/browser/screenshot.js.map +1 -1
  41. package/dist/src/browser/types.d.ts.map +1 -1
  42. package/dist/src/browser/types.js +11 -0
  43. package/dist/src/browser/types.js.map +1 -1
  44. package/dist/src/cli/action.d.ts +3 -0
  45. package/dist/src/cli/action.d.ts.map +1 -0
  46. package/dist/src/cli/action.js +142 -0
  47. package/dist/src/cli/action.js.map +1 -0
  48. package/dist/src/cli/commands.d.ts +8 -0
  49. package/dist/src/cli/commands.d.ts.map +1 -0
  50. package/dist/src/cli/commands.js +3018 -0
  51. package/dist/src/cli/commands.js.map +1 -0
  52. package/dist/src/cli/output.d.ts +11 -0
  53. package/dist/src/cli/output.d.ts.map +1 -0
  54. package/dist/src/cli/output.js +33 -0
  55. package/dist/src/cli/output.js.map +1 -0
  56. package/dist/src/cli/platform.d.ts +7 -0
  57. package/dist/src/cli/platform.d.ts.map +1 -0
  58. package/dist/src/cli/platform.js +193 -0
  59. package/dist/src/cli/platform.js.map +1 -0
  60. package/dist/src/cli/playbook.d.ts +3 -0
  61. package/dist/src/cli/playbook.d.ts.map +1 -0
  62. package/dist/src/cli/playbook.js +265 -0
  63. package/dist/src/cli/playbook.js.map +1 -0
  64. package/dist/src/cli/types.d.ts +42 -0
  65. package/dist/src/cli/types.d.ts.map +1 -0
  66. package/dist/src/cli/types.js +2 -0
  67. package/dist/src/cli/types.js.map +1 -0
  68. package/dist/src/cli/workflow.d.ts +2 -0
  69. package/dist/src/cli/workflow.d.ts.map +1 -0
  70. package/dist/src/cli/workflow.js +3 -0
  71. package/dist/src/cli/workflow.js.map +1 -0
  72. package/dist/src/cli.d.ts +7 -0
  73. package/dist/src/cli.d.ts.map +1 -0
  74. package/dist/src/cli.js +187 -0
  75. package/dist/src/cli.js.map +1 -0
  76. package/dist/src/index.d.ts +0 -1
  77. package/dist/src/index.d.ts.map +1 -1
  78. package/dist/src/index.js +0 -1
  79. package/dist/src/index.js.map +1 -1
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +21 -11
  82. package/src/browser/action-builder/analyzer.ts +41 -23
  83. package/src/browser/adapters/index.ts +4 -0
  84. package/src/browser/browser.ts +2 -2
  85. package/src/browser/cdp.ts +68 -1
  86. package/src/browser/dashboard/api-types.ts +1 -1
  87. package/src/browser/dashboard/server.ts +41 -30
  88. package/src/browser/dashboard/ui.html +1412 -45
  89. package/src/browser/screenshot.ts +78 -3
  90. package/src/browser/types.ts +11 -0
  91. package/src/cli/action.ts +149 -0
  92. package/src/cli/commands.ts +2892 -0
  93. package/src/cli/output.ts +37 -0
  94. package/src/cli/platform.ts +211 -0
  95. package/src/cli/types.ts +47 -0
  96. package/src/cli.ts +208 -0
  97. package/src/index.ts +0 -1
  98. package/src/browser/workflow/builtin-handlers.ts +0 -379
  99. package/src/browser/workflow/engine.ts +0 -202
  100. package/src/browser/workflow/expression.ts +0 -107
  101. package/src/browser/workflow/index.ts +0 -5
  102. package/src/browser/workflow/store.ts +0 -87
  103. package/src/browser/workflow/types.ts +0 -62
@@ -0,0 +1,37 @@
1
+ const COLORS = {
2
+ reset: '\x1b[0m',
3
+ bold: '\x1b[1m',
4
+ green: '\x1b[32m',
5
+ yellow: '\x1b[33m',
6
+ blue: '\x1b[34m',
7
+ red: '\x1b[31m',
8
+ cyan: '\x1b[36m',
9
+ } as const;
10
+
11
+ class OutputFormatter {
12
+ private colorEnabled = process.stdout.isTTY ?? false;
13
+
14
+ private c(color: keyof typeof COLORS, text: string): string {
15
+ if (!this.colorEnabled) return text;
16
+ return `${COLORS[color]}${text}${COLORS.reset}`;
17
+ }
18
+
19
+ printSuccess(message: string): void {
20
+ console.log(this.c('green', '✓') + ' ' + message);
21
+ }
22
+
23
+ printError(message: string, details?: string): void {
24
+ console.error(this.c('red', '✗') + ' ' + message);
25
+ if (details) console.error(this.c('red', ' ' + details));
26
+ }
27
+
28
+ printWarning(message: string): void {
29
+ console.warn(this.c('yellow', '⚠') + ' ' + message);
30
+ }
31
+
32
+ printInfo(message: string): void {
33
+ console.log(this.c('cyan', 'ℹ') + ' ' + message);
34
+ }
35
+ }
36
+
37
+ export const output = new OutputFormatter();
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Browse Platform Command — Manage platform connections (LinkedIn, Instagram, X, Gemini)
3
+ * Sessions stored as JSON at ~/.monomind/sessions.json
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
8
+ import { existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ import { getAdapter } from '../index.js';
12
+
13
+ const CDP_PORT = Number(process.env['MONOMIND_CDP_PORT'] ?? 9222);
14
+
15
+ const SESSIONS_FILE = join(homedir(), '.monomind', 'sessions.json');
16
+
17
+ interface Session {
18
+ id: string; // "platform:username"
19
+ platform: string;
20
+ username: string;
21
+ cookies: string; // JSON-serialized
22
+ userAgent: string;
23
+ createdAt: number;
24
+ lastUsedAt: number;
25
+ }
26
+
27
+ async function loadSessions(): Promise<Session[]> {
28
+ if (!existsSync(SESSIONS_FILE)) return [];
29
+ try {
30
+ const raw = await readFile(SESSIONS_FILE, 'utf-8');
31
+ return JSON.parse(raw) as Session[];
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+
37
+ async function saveSessions(sessions: Session[]): Promise<void> {
38
+ await mkdir(join(homedir(), '.monomind'), { recursive: true });
39
+ await writeFile(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
40
+ // Restrict to owner-only (rw-------) since the file contains platform session cookies.
41
+ await chmod(SESSIONS_FILE, 0o600);
42
+ }
43
+
44
+ export function createPlatformCommand(): Command {
45
+ const cmd = new Command('platform').description('Manage platform connections');
46
+
47
+ cmd
48
+ .command('connect <platform>')
49
+ .description('Connect a platform account via browser — opens Chrome, waits for login, saves session')
50
+ .option('--port <number>', 'CDP port of running Chrome instance', String(CDP_PORT))
51
+ .option('--timeout <seconds>', 'Login timeout in seconds', '300')
52
+ .action(async (platform: string, opts: { port: string; timeout: string }) => {
53
+ const SUPPORTED = ['linkedin', 'instagram', 'x', 'gemini'];
54
+ if (!SUPPORTED.includes(platform)) {
55
+ console.error(`Unknown platform: ${platform}. Supported: ${SUPPORTED.join(', ')}`);
56
+ process.exit(1);
57
+ }
58
+
59
+ let adapter: ReturnType<typeof getAdapter>;
60
+ try { adapter = getAdapter(platform); } catch (err) {
61
+ console.error(err instanceof Error ? err.message : String(err));
62
+ process.exit(1);
63
+ }
64
+
65
+ const cdpPort = Number(opts.port);
66
+ const timeoutMs = Number(opts.timeout) * 1000;
67
+
68
+ // Lazy-load browser module
69
+ let browser: typeof import('@monoes/monobrowse');
70
+ try { browser = await import('@monoes/monobrowse'); } catch (err) {
71
+ console.error('Browser module unavailable:', err);
72
+ process.exit(1);
73
+ }
74
+
75
+ // Connect to Chrome (or launch if needed)
76
+ let conn: Awaited<ReturnType<typeof browser.connectToTarget>>;
77
+ try {
78
+ conn = await browser.connectToTarget(cdpPort);
79
+ console.log(`Connected to Chrome on port ${cdpPort}`);
80
+ } catch {
81
+ console.log(`Chrome not found on port ${cdpPort} — launching managed browser...`);
82
+ try {
83
+ const port = await browser.launchBrowser({ port: cdpPort, headless: false });
84
+ conn = await browser.connectToTarget(port);
85
+ } catch (err) {
86
+ console.error('Failed to connect or launch Chrome:', err);
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ const { client, sessionId: sid } = conn;
92
+
93
+ try {
94
+ const refs = new Map();
95
+
96
+ // Restore any existing cookies first so the user might already be logged in
97
+ const sessions = await loadSessions();
98
+ const existingSession = sessions.find(s => s.platform === platform);
99
+ if (existingSession?.cookies) {
100
+ try {
101
+ const cookies = JSON.parse(existingSession.cookies);
102
+ if (cookies.length > 0) await browser.setCookies(client, sid, cookies);
103
+ } catch { /* ignore */ }
104
+ }
105
+
106
+ // Navigate to login URL
107
+ console.log(`\nOpening ${platform} login page...`);
108
+ await browser.openUrl(client, sid, adapter.loginURL());
109
+
110
+ // Check if already logged in after cookie restore
111
+ const pageIface = {
112
+ evaluate: async <T>(expr: string) => browser.evaluateJs(client, sid, expr) as Promise<T>,
113
+ url: async () => browser.getCurrentUrl(client, sid).catch(() => ''),
114
+ };
115
+ let isLoggedIn = await adapter.isLoggedIn(pageIface).catch(() => false);
116
+
117
+ if (!isLoggedIn) {
118
+ console.log('');
119
+ console.log('┌──────────────────────────────────────────────────────┐');
120
+ console.log(`│ Please sign in to ${platform.padEnd(34)}│`);
121
+ console.log('│ │');
122
+ console.log('│ Complete the login in the browser window. │');
123
+ console.log(`│ Waiting up to ${Math.round(timeoutMs / 60000)} minutes for login… │`);
124
+ console.log('└──────────────────────────────────────────────────────┘');
125
+ console.log('');
126
+
127
+ const deadline = Date.now() + timeoutMs;
128
+ while (Date.now() < deadline) {
129
+ await new Promise(r => setTimeout(r, 2000));
130
+ isLoggedIn = await adapter.isLoggedIn(pageIface).catch(() => false);
131
+ if (isLoggedIn) break;
132
+ // Navigate to app URL if user manually browsed away from login
133
+ const currentUrl = await browser.getCurrentUrl(client, sid).catch(() => '');
134
+ if (!currentUrl.includes(new URL(adapter.loginURL()).hostname) &&
135
+ !currentUrl.includes(new URL(adapter.baseURL).hostname)) {
136
+ await browser.openUrl(client, sid, adapter.loginURL());
137
+ }
138
+ }
139
+
140
+ if (!isLoggedIn) {
141
+ console.error(`\nLogin timeout after ${Math.round(timeoutMs / 1000)}s. Session not saved.`);
142
+ process.exit(1);
143
+ }
144
+ }
145
+
146
+ console.log('Login detected! Capturing session...');
147
+
148
+ // Capture cookies and user agent
149
+ const liveCookies = await browser.getCookies(client, sid).catch(() => []);
150
+ const userAgent = await browser.evaluateJs(client, sid, 'navigator.userAgent').catch(() => 'unknown') as string;
151
+ const username = await adapter.extractUsername(pageIface).catch(() => `${platform}-user`);
152
+
153
+ // Save to sessions file
154
+ const sessionEntry: Session = {
155
+ id: `${platform}:${username}`,
156
+ platform,
157
+ username: username || `${platform}-user`,
158
+ cookies: JSON.stringify(liveCookies),
159
+ userAgent: String(userAgent),
160
+ createdAt: existingSession?.createdAt ?? Date.now(),
161
+ lastUsedAt: Date.now(),
162
+ };
163
+ const idx = sessions.findIndex(s => s.platform === platform);
164
+ if (idx >= 0) sessions[idx] = sessionEntry; else sessions.push(sessionEntry);
165
+ await saveSessions(sessions);
166
+
167
+ console.log(`\n✓ Session saved: ${sessionEntry.id}`);
168
+ console.log(` Cookies: ${liveCookies.length} captured`);
169
+ console.log(` File: ${SESSIONS_FILE}`);
170
+ console.log('');
171
+ console.log(`Session will be automatically used by: monomind browse playbook run`);
172
+ } finally {
173
+ client.close();
174
+ }
175
+ });
176
+
177
+ cmd
178
+ .command('list')
179
+ .description('List connected platform accounts')
180
+ .action(async () => {
181
+ const sessions = await loadSessions();
182
+ if (sessions.length === 0) {
183
+ console.log('No connected accounts. Run: browse platform connect <platform>');
184
+ return;
185
+ }
186
+ console.log(`\n${'ID'.padEnd(40)} ${'PLATFORM'.padEnd(12)} ${'USERNAME'.padEnd(30)} LAST USED`);
187
+ console.log('─'.repeat(95));
188
+ for (const s of sessions) {
189
+ const lastUsed = new Date(s.lastUsedAt).toLocaleString();
190
+ console.log(`${s.id.padEnd(40)} ${s.platform.padEnd(12)} ${s.username.padEnd(30)} ${lastUsed}`);
191
+ }
192
+ });
193
+
194
+ cmd
195
+ .command('disconnect <id>')
196
+ .description('Remove a saved platform session')
197
+ .action(async (id: string) => {
198
+ const sessions = await loadSessions();
199
+ const idx = sessions.findIndex(s => s.id === id);
200
+ if (idx < 0) {
201
+ console.error(`Session not found: ${id}`);
202
+ console.log('Run "browse platform list" to see available sessions.');
203
+ process.exit(1);
204
+ }
205
+ sessions.splice(idx, 1);
206
+ await saveSessions(sessions);
207
+ console.log(`Disconnected: ${id}`);
208
+ });
209
+
210
+ return cmd;
211
+ }
@@ -0,0 +1,47 @@
1
+ export interface CommandContext {
2
+ args: string[];
3
+ flags: ParsedFlags;
4
+ cwd: string;
5
+ interactive: boolean;
6
+ }
7
+
8
+ export interface ParsedFlags {
9
+ [key: string]: string | boolean | number | string[];
10
+ _: string[];
11
+ }
12
+
13
+ export interface Command {
14
+ name: string;
15
+ description: string;
16
+ aliases?: string[];
17
+ subcommands?: Command[];
18
+ options?: CommandOption[];
19
+ examples?: CommandExample[];
20
+ action?: CommandAction;
21
+ hidden?: boolean;
22
+ }
23
+
24
+ export interface CommandOption {
25
+ name: string;
26
+ short?: string;
27
+ description: string;
28
+ type: 'string' | 'boolean' | 'number' | 'array';
29
+ default?: unknown;
30
+ required?: boolean;
31
+ choices?: string[];
32
+ validate?: (value: unknown) => boolean | string;
33
+ }
34
+
35
+ export interface CommandExample {
36
+ command: string;
37
+ description: string;
38
+ }
39
+
40
+ export type CommandAction = (ctx: CommandContext) => Promise<CommandResult | void>;
41
+
42
+ export interface CommandResult {
43
+ success: boolean;
44
+ message?: string;
45
+ data?: unknown;
46
+ exitCode?: number;
47
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * monobrowse — standalone CLI entry point
4
+ * Dispatches to the same command tree used by `monomind browse`
5
+ */
6
+
7
+ import { createRequire } from 'module';
8
+ import browseCommand from './cli/commands.js';
9
+ import { output } from './cli/output.js';
10
+ import type { Command, CommandContext, CommandOption, ParsedFlags } from './cli/types.js';
11
+
12
+ const _require = createRequire(import.meta.url);
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Subcommand lookup (supports aliases)
16
+ // ---------------------------------------------------------------------------
17
+
18
+ function findSub(name: string, subs: Command[]): Command | undefined {
19
+ return subs.find(s => s.name === name || s.aliases?.includes(name));
20
+ }
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Minimal argv parser — resolves short flags from the target subcommand's opts
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function buildShortMap(cmd: Command | undefined): Record<string, string> {
27
+ const m: Record<string, string> = {};
28
+ if (!cmd?.options) return m;
29
+ for (const opt of cmd.options) {
30
+ if (opt.short) m[opt.short] = opt.name;
31
+ }
32
+ return m;
33
+ }
34
+
35
+ function isBooleanOpt(name: string, cmd: Command | undefined): boolean {
36
+ const opt = cmd?.options?.find(o => o.name === name);
37
+ return opt?.type === 'boolean';
38
+ }
39
+
40
+ function parseArgv(argv: string[], sub: Command | undefined): { args: string[]; flags: ParsedFlags } {
41
+ const shortMap = buildShortMap(sub);
42
+ const flags: ParsedFlags = { _: [] };
43
+ const positional: string[] = [];
44
+ let i = 0;
45
+
46
+ while (i < argv.length) {
47
+ const arg = argv[i];
48
+
49
+ if (arg === '--') {
50
+ positional.push(...argv.slice(i + 1));
51
+ break;
52
+ }
53
+
54
+ if (arg.startsWith('--')) {
55
+ const raw = arg.slice(2);
56
+ if (raw.startsWith('no-')) {
57
+ flags[raw.slice(3)] = false;
58
+ } else if (raw.includes('=')) {
59
+ const eq = raw.indexOf('=');
60
+ flags[raw.slice(0, eq)] = raw.slice(eq + 1);
61
+ } else if (isBooleanOpt(raw, sub) || i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
62
+ flags[raw] = true;
63
+ } else {
64
+ flags[raw] = argv[++i];
65
+ }
66
+ } else if (arg.startsWith('-') && arg.length >= 2) {
67
+ const short = arg[1];
68
+ const long = shortMap[short] ?? short;
69
+ if (arg.length > 2) {
70
+ flags[long] = arg.slice(2);
71
+ } else if (isBooleanOpt(long, sub) || i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
72
+ flags[long] = true;
73
+ } else {
74
+ flags[long] = argv[++i];
75
+ }
76
+ } else {
77
+ positional.push(arg);
78
+ }
79
+
80
+ i++;
81
+ }
82
+
83
+ // Coerce number options
84
+ for (const opt of (sub?.options ?? [])) {
85
+ if (opt.type === 'number' && typeof flags[opt.name] === 'string') {
86
+ flags[opt.name] = Number(flags[opt.name]);
87
+ }
88
+ }
89
+
90
+ return { args: positional, flags };
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Help printer
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function printHelp(cmd: Command, prefix = 'monobrowse'): void {
98
+ const usage = cmd.name === 'browse'
99
+ ? `${prefix} <subcommand> [options]`
100
+ : `${prefix} ${cmd.name} [subcommand] [options]`;
101
+ console.log(`\nUsage: ${usage}\n`);
102
+ console.log(cmd.description + '\n');
103
+
104
+ if (cmd.subcommands?.length) {
105
+ console.log('Subcommands:');
106
+ for (const s of cmd.subcommands) {
107
+ if (!s.hidden) console.log(` ${s.name.padEnd(20)} ${s.description}`);
108
+ }
109
+ console.log('');
110
+ }
111
+
112
+ if (cmd.options?.length) {
113
+ console.log('Options:');
114
+ for (const o of cmd.options) {
115
+ const shortPart = o.short ? `-${o.short}, ` : ' ';
116
+ const defaultPart = o.default !== undefined ? ` (default: ${o.default})` : '';
117
+ console.log(` ${shortPart}--${o.name.padEnd(18)} ${o.description}${defaultPart}`);
118
+ }
119
+ console.log('');
120
+ }
121
+
122
+ if (cmd.examples?.length) {
123
+ console.log('Examples:');
124
+ for (const ex of cmd.examples) {
125
+ const adjusted = ex.command.replace(/^monomind browse/, prefix);
126
+ console.log(` ${adjusted} ${ex.description}`);
127
+ }
128
+ console.log('');
129
+ }
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Dispatch: find action and build context
134
+ // ---------------------------------------------------------------------------
135
+
136
+ async function dispatch(cmd: Command, argv: string[]): Promise<void> {
137
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
138
+ printHelp(cmd);
139
+ return;
140
+ }
141
+
142
+ const subName = argv[0];
143
+ const rest = argv.slice(1);
144
+
145
+ const sub = findSub(subName, cmd.subcommands ?? []);
146
+ if (!sub) {
147
+ output.printError(`Unknown subcommand: ${subName}`);
148
+ output.printInfo('Run `monobrowse --help` to see available subcommands.');
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+
153
+ if (rest[0] === '--help' || rest[0] === '-h') {
154
+ printHelp(sub, 'monobrowse');
155
+ return;
156
+ }
157
+
158
+ // Check if first remaining arg matches a nested subcommand
159
+ if (sub.subcommands?.length && rest.length > 0 && !rest[0].startsWith('-')) {
160
+ const nested = findSub(rest[0], sub.subcommands);
161
+ if (nested?.action) {
162
+ const { args, flags } = parseArgv(rest.slice(1), nested);
163
+ const ctx: CommandContext = { args, flags, cwd: process.cwd(), interactive: !!process.stdout.isTTY };
164
+ const result = await nested.action(ctx);
165
+ if (result && !result.success) process.exitCode = result.exitCode ?? 1;
166
+ return;
167
+ }
168
+ }
169
+
170
+ // No nested match — invoke the subcommand's own action
171
+ if (!sub.action) {
172
+ output.printInfo(`Run \`monobrowse ${subName} --help\` for usage.`);
173
+ return;
174
+ }
175
+
176
+ const { args, flags } = parseArgv(rest, sub);
177
+ const ctx: CommandContext = { args, flags, cwd: process.cwd(), interactive: !!process.stdout.isTTY };
178
+ const result = await sub.action(ctx);
179
+ if (result && !result.success) process.exitCode = result.exitCode ?? 1;
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Main
184
+ // ---------------------------------------------------------------------------
185
+
186
+ async function main(): Promise<void> {
187
+ const userArgs = process.argv.slice(2);
188
+
189
+ if (userArgs.length === 0 || userArgs[0] === '--help' || userArgs[0] === '-h') {
190
+ printHelp(browseCommand);
191
+ return;
192
+ }
193
+
194
+ if (userArgs[0] === '--version' || userArgs[0] === '-V') {
195
+ const pkg = _require('../package.json') as { version: string };
196
+ console.log(pkg.version);
197
+ return;
198
+ }
199
+
200
+ await dispatch(browseCommand, userArgs);
201
+ }
202
+
203
+ main().then(() => {
204
+ process.exit(process.exitCode ?? 0);
205
+ }).catch(err => {
206
+ output.printError(err instanceof Error ? err.message : String(err));
207
+ process.exit(1);
208
+ });
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from './browser/index.js';
2
- export * from './browser/workflow/index.js';
3
2
  export * from './browser/action-builder/analyzer.js';
4
3
  export * from './browser/action-builder/types.js';
5
4
  export * from './browser/adapters/index.js';