@jackwener/opencli 1.7.15 → 1.7.17
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.
- package/README.md +15 -13
- package/README.zh-CN.md +15 -12
- package/cli-manifest.json +165 -209
- package/clis/chatgpt/ask.js +3 -2
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +7 -2
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/image.js +38 -4
- package/clis/chatgpt/image.test.js +68 -1
- package/clis/chatgpt/new.js +1 -1
- package/clis/chatgpt/read.js +3 -2
- package/clis/chatgpt/send.js +3 -2
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +259 -25
- package/clis/chatgpt/utils.test.js +166 -2
- package/clis/claude/ask.js +23 -8
- package/clis/claude/detail.js +10 -3
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +9 -3
- package/clis/claude/read.js +3 -2
- package/clis/claude/send.js +9 -4
- package/clis/claude/status.js +1 -1
- package/clis/claude/utils.js +27 -4
- package/clis/deepseek/ask.js +22 -9
- package/clis/deepseek/detail.js +10 -2
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +14 -3
- package/clis/deepseek/read.js +3 -2
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/deepseek/utils.js +8 -1
- package/clis/doubao/ask.js +1 -1
- package/clis/doubao/detail.js +1 -1
- package/clis/doubao/history.js +1 -1
- package/clis/doubao/meeting-summary.js +1 -1
- package/clis/doubao/meeting-transcript.js +1 -1
- package/clis/doubao/new.js +1 -1
- package/clis/doubao/read.js +1 -1
- package/clis/doubao/send.js +1 -1
- package/clis/doubao/status.js +1 -1
- package/clis/gemini/ask.js +1 -1
- package/clis/gemini/deep-research-result.js +1 -1
- package/clis/gemini/deep-research.js +1 -1
- package/clis/gemini/image.js +1 -1
- package/clis/gemini/new.js +1 -1
- package/clis/grok/ask.js +1 -1
- package/clis/grok/detail.js +1 -1
- package/clis/grok/history.js +1 -1
- package/clis/grok/image.js +1 -1
- package/clis/grok/new.js +1 -1
- package/clis/grok/read.js +1 -1
- package/clis/grok/send.js +1 -1
- package/clis/grok/status.js +1 -1
- package/clis/linkedin/search.js +8 -11
- package/clis/maimai/search-talents.js +10 -6
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +2 -2
- package/clis/notebooklm/open.test.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/openreview/author.js +58 -0
- package/clis/openreview/openreview.test.js +83 -1
- package/clis/openreview/utils.js +14 -0
- package/clis/qwen/ask.js +1 -1
- package/clis/qwen/detail.js +1 -1
- package/clis/qwen/history.js +1 -1
- package/clis/qwen/image.js +1 -1
- package/clis/qwen/new.js +1 -1
- package/clis/qwen/read.js +1 -1
- package/clis/qwen/send.js +1 -1
- package/clis/qwen/status.js +1 -1
- package/clis/reddit/comment.js +1 -0
- package/clis/reddit/frontpage.js +1 -0
- package/clis/reddit/popular.js +1 -0
- package/clis/reddit/read.js +2 -0
- package/clis/reddit/read.test.js +4 -0
- package/clis/reddit/save.js +1 -0
- package/clis/reddit/saved.js +1 -0
- package/clis/reddit/search.js +1 -0
- package/clis/reddit/subreddit.js +1 -0
- package/clis/reddit/subscribe.js +1 -0
- package/clis/reddit/upvote.js +1 -0
- package/clis/reddit/upvoted.js +1 -0
- package/clis/reddit/user-comments.js +1 -0
- package/clis/reddit/user-posts.js +1 -0
- package/clis/reddit/user.js +1 -0
- package/clis/twitter/article.js +7 -4
- package/clis/twitter/bookmark-folder.js +3 -5
- package/clis/twitter/bookmark-folder.test.js +5 -2
- package/clis/twitter/bookmark-folders.js +3 -5
- package/clis/twitter/bookmark-folders.test.js +3 -1
- package/clis/twitter/bookmarks.js +3 -5
- package/clis/twitter/download.js +1 -0
- package/clis/twitter/followers.js +1 -0
- package/clis/twitter/following.js +3 -6
- package/clis/twitter/following.test.js +2 -1
- package/clis/twitter/likes.js +3 -5
- package/clis/twitter/list-add.js +4 -3
- package/clis/twitter/list-add.test.js +23 -1
- package/clis/twitter/list-remove.js +4 -3
- package/clis/twitter/list-remove.test.js +23 -1
- package/clis/twitter/list-tweets.js +3 -5
- package/clis/twitter/lists.js +3 -5
- package/clis/twitter/notifications.js +1 -0
- package/clis/twitter/profile.js +7 -4
- package/clis/twitter/search.js +1 -0
- package/clis/twitter/thread.js +5 -7
- package/clis/twitter/timeline.js +5 -7
- package/clis/twitter/trending.js +4 -4
- package/clis/twitter/tweets.js +3 -6
- package/clis/youtube/like.js +6 -2
- package/clis/youtube/subscribe.js +6 -2
- package/clis/youtube/unlike.js +6 -2
- package/clis/youtube/unsubscribe.js +6 -2
- package/clis/youtube/utils.js +19 -13
- package/clis/youtube/utils.test.js +17 -1
- package/clis/yuanbao/ask.js +1 -1
- package/clis/yuanbao/detail.js +1 -1
- package/clis/yuanbao/history.js +1 -1
- package/clis/yuanbao/new.js +1 -1
- package/clis/yuanbao/read.js +1 -1
- package/clis/yuanbao/send.js +1 -1
- package/clis/yuanbao/status.js +1 -1
- package/dist/src/browser/bridge.d.ts +4 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +4 -1
- package/dist/src/browser/daemon-client.d.ts +9 -16
- package/dist/src/browser/daemon-client.js +8 -9
- package/dist/src/browser/daemon-client.test.js +10 -0
- package/dist/src/browser/network-cache.d.ts +5 -5
- package/dist/src/browser/network-cache.js +8 -8
- package/dist/src/browser/network-cache.test.js +4 -4
- package/dist/src/browser/page.d.ts +9 -7
- package/dist/src/browser/page.js +27 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +91 -125
- package/dist/src/cli.test.js +293 -180
- package/dist/src/commanderAdapter.js +9 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +8 -72
- package/dist/src/doctor.test.js +26 -97
- package/dist/src/execution.d.ts +3 -0
- package/dist/src/execution.js +47 -23
- package/dist/src/execution.test.js +68 -45
- package/dist/src/external-clis.yaml +24 -0
- package/dist/src/help.d.ts +1 -0
- package/dist/src/help.js +36 -1
- package/dist/src/main.js +0 -29
- package/dist/src/manifest-types.d.ts +2 -4
- package/dist/src/observation/artifact.js +1 -1
- package/dist/src/observation/artifact.test.js +3 -3
- package/dist/src/observation/events.d.ts +1 -1
- package/dist/src/observation/manager.js +1 -1
- package/dist/src/observation/manager.test.js +3 -3
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +3 -12
- package/dist/src/registry.js +6 -10
- package/dist/src/runtime.d.ts +10 -2
- package/dist/src/runtime.js +4 -1
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/types.d.ts +0 -15
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -36,8 +36,6 @@ import { bindTab, BrowserCommandError, fetchDaemonStatus, sendCommand } from './
|
|
|
36
36
|
import { aliasForContextId, loadProfileConfig, renameProfile, resolveProfileContextId, setDefaultProfile } from './browser/profile.js';
|
|
37
37
|
import { formatDaemonVersion, isDaemonStale } from './browser/daemon-version.js';
|
|
38
38
|
const CLI_FILE = fileURLToPath(import.meta.url);
|
|
39
|
-
const DEFAULT_BROWSER_WORKSPACE = 'browser:default';
|
|
40
|
-
const DEFAULT_BOUND_WORKSPACE = 'bound:default';
|
|
41
39
|
const BROWSER_TAB_OPTION_DESCRIPTION = 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"';
|
|
42
40
|
const FOLLOW_POLL_MS = 1_000;
|
|
43
41
|
function parseDurationMs(raw, flagName) {
|
|
@@ -245,11 +243,11 @@ export function renderVerifyPreview(rows, opts = {}) {
|
|
|
245
243
|
function getBrowserCacheDir() {
|
|
246
244
|
return process.env.OPENCLI_CACHE_DIR || path.join(os.homedir(), '.opencli', 'cache');
|
|
247
245
|
}
|
|
248
|
-
function getBrowserTargetStatePath(scope
|
|
249
|
-
const
|
|
250
|
-
return path.join(getBrowserCacheDir(), 'browser-state', `${
|
|
246
|
+
function getBrowserTargetStatePath(scope) {
|
|
247
|
+
const safeSession = scope.replace(/[^a-zA-Z0-9_-]+/g, '_');
|
|
248
|
+
return path.join(getBrowserCacheDir(), 'browser-state', `${safeSession}.json`);
|
|
251
249
|
}
|
|
252
|
-
function loadBrowserTargetState(scope
|
|
250
|
+
function loadBrowserTargetState(scope) {
|
|
253
251
|
try {
|
|
254
252
|
const raw = fs.readFileSync(getBrowserTargetStatePath(scope), 'utf-8');
|
|
255
253
|
const parsed = JSON.parse(raw);
|
|
@@ -259,7 +257,7 @@ function loadBrowserTargetState(scope = DEFAULT_BROWSER_WORKSPACE) {
|
|
|
259
257
|
return null;
|
|
260
258
|
}
|
|
261
259
|
}
|
|
262
|
-
function saveBrowserTargetState(defaultPage, scope
|
|
260
|
+
function saveBrowserTargetState(defaultPage, scope) {
|
|
263
261
|
const target = getBrowserTargetStatePath(scope);
|
|
264
262
|
if (!defaultPage) {
|
|
265
263
|
fs.rmSync(target, { force: true });
|
|
@@ -291,7 +289,7 @@ async function resolveBrowserTargetInSession(page, targetPage, opts) {
|
|
|
291
289
|
return undefined;
|
|
292
290
|
}
|
|
293
291
|
throw new Error(`Target tab ${candidate} could not be validated in the current browser session. ` +
|
|
294
|
-
'The Browser Bridge
|
|
292
|
+
'The Browser Bridge session may have restarted; re-run "opencli browser tab list" and choose a current target.', { cause: err });
|
|
295
293
|
}
|
|
296
294
|
if (Array.isArray(tabs) && hasBrowserTabTarget(tabs, candidate)) {
|
|
297
295
|
return candidate;
|
|
@@ -301,32 +299,33 @@ async function resolveBrowserTargetInSession(page, targetPage, opts) {
|
|
|
301
299
|
return undefined;
|
|
302
300
|
}
|
|
303
301
|
throw new Error(`Target tab ${candidate} is not part of the current browser session. ` +
|
|
304
|
-
'The Browser Bridge
|
|
302
|
+
'The Browser Bridge session may have restarted; re-run "opencli browser tab list" and choose a current target.');
|
|
305
303
|
}
|
|
306
|
-
function getBrowserScope(
|
|
307
|
-
return contextId ? `${contextId}:${
|
|
304
|
+
function getBrowserScope(session, contextId) {
|
|
305
|
+
return contextId ? `${contextId}:${session}` : session;
|
|
308
306
|
}
|
|
309
|
-
async function resolveStoredBrowserTarget(page, scope
|
|
307
|
+
async function resolveStoredBrowserTarget(page, scope) {
|
|
310
308
|
const defaultPage = loadBrowserTargetState(scope)?.defaultPage?.trim();
|
|
311
309
|
if (!defaultPage)
|
|
312
310
|
return undefined;
|
|
313
311
|
return resolveBrowserTargetInSession(page, defaultPage, { scope, source: 'saved' });
|
|
314
312
|
}
|
|
315
|
-
/** Create a browser page for browser commands. Uses a
|
|
316
|
-
async function getBrowserPage(targetPage,
|
|
313
|
+
/** Create a browser page for browser commands. Uses a named browser session for continuity. */
|
|
314
|
+
async function getBrowserPage(session, targetPage, contextId, opts = {}) {
|
|
317
315
|
const { BrowserBridge } = await import('./browser/index.js');
|
|
318
316
|
const bridge = new BrowserBridge();
|
|
319
|
-
//
|
|
320
|
-
// (controls when the automation tab is released). Not the per-command runtime timeout.
|
|
317
|
+
// Internal GC timeout for browser sessions. Not the per-command runtime timeout.
|
|
321
318
|
const envTimeout = process.env.OPENCLI_BROWSER_IDLE_TIMEOUT;
|
|
322
319
|
const idleTimeout = envTimeout ? parseInt(envTimeout, 10) : undefined;
|
|
323
320
|
const page = await bridge.connect({
|
|
324
321
|
timeout: 30,
|
|
325
|
-
|
|
322
|
+
session,
|
|
323
|
+
surface: 'browser',
|
|
326
324
|
...(contextId && { contextId }),
|
|
327
325
|
...(idleTimeout && idleTimeout > 0 && { idleTimeout }),
|
|
326
|
+
windowMode: opts.windowMode ?? getBrowserWindowMode(undefined, 'foreground'),
|
|
328
327
|
});
|
|
329
|
-
const targetScope = getBrowserScope(
|
|
328
|
+
const targetScope = getBrowserScope(session, contextId);
|
|
330
329
|
const resolvedTargetPage = targetPage
|
|
331
330
|
? await resolveBrowserTargetInSession(page, targetPage, { scope: targetScope, source: 'explicit' })
|
|
332
331
|
: await resolveStoredBrowserTarget(page, targetScope);
|
|
@@ -338,6 +337,21 @@ async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE,
|
|
|
338
337
|
}
|
|
339
338
|
return page;
|
|
340
339
|
}
|
|
340
|
+
function getBrowserWindowMode(command, defaultMode) {
|
|
341
|
+
const optionRaw = getCommandOption(command, 'window');
|
|
342
|
+
if (optionRaw !== undefined && optionRaw !== '') {
|
|
343
|
+
if (optionRaw === 'foreground' || optionRaw === 'background')
|
|
344
|
+
return optionRaw;
|
|
345
|
+
throw new Error(`--window must be one of: foreground, background. Received: "${String(optionRaw)}"`);
|
|
346
|
+
}
|
|
347
|
+
const envRaw = process.env.OPENCLI_WINDOW;
|
|
348
|
+
if (envRaw !== undefined && envRaw !== '') {
|
|
349
|
+
if (envRaw === 'foreground' || envRaw === 'background')
|
|
350
|
+
return envRaw;
|
|
351
|
+
throw new Error(`OPENCLI_WINDOW must be one of: foreground, background. Received: "${envRaw}"`);
|
|
352
|
+
}
|
|
353
|
+
return defaultMode;
|
|
354
|
+
}
|
|
341
355
|
function addBrowserTabOption(command) {
|
|
342
356
|
return command.option('--tab <targetId>', BROWSER_TAB_OPTION_DESCRIPTION);
|
|
343
357
|
}
|
|
@@ -357,21 +371,25 @@ function getCommandOption(command, option) {
|
|
|
357
371
|
}
|
|
358
372
|
return undefined;
|
|
359
373
|
}
|
|
360
|
-
function
|
|
361
|
-
const raw = getCommandOption(command, '
|
|
362
|
-
|
|
374
|
+
function getBrowserSession(command) {
|
|
375
|
+
const raw = getCommandOption(command, 'session');
|
|
376
|
+
if (typeof raw === 'string' && raw.trim())
|
|
377
|
+
return raw.trim();
|
|
378
|
+
throw new Error('--session <name> is required for opencli browser commands');
|
|
363
379
|
}
|
|
364
380
|
function getBrowserContextId(command) {
|
|
365
381
|
const raw = getCommandOption(command, 'profile');
|
|
366
382
|
return resolveProfileContextId(typeof raw === 'string' && raw.trim() ? raw.trim() : undefined);
|
|
367
383
|
}
|
|
368
|
-
function
|
|
369
|
-
const
|
|
370
|
-
|
|
384
|
+
function getPageSession(page) {
|
|
385
|
+
const session = page.session;
|
|
386
|
+
if (typeof session === 'string' && session.trim())
|
|
387
|
+
return session.trim();
|
|
388
|
+
throw new Error('Browser page is missing a session');
|
|
371
389
|
}
|
|
372
390
|
function getPageScope(page) {
|
|
373
391
|
const contextId = page.contextId;
|
|
374
|
-
return getBrowserScope(
|
|
392
|
+
return getBrowserScope(getPageSession(page), typeof contextId === 'string' && contextId.trim() ? contextId.trim() : undefined);
|
|
375
393
|
}
|
|
376
394
|
function snapshotMetricText(snapshot) {
|
|
377
395
|
return typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2);
|
|
@@ -582,7 +600,8 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
582
600
|
// All commands wrapped in browserAction() for consistent error handling.
|
|
583
601
|
const browser = program
|
|
584
602
|
.command('browser')
|
|
585
|
-
.option('--
|
|
603
|
+
.option('--session <name>', 'Browser session to use')
|
|
604
|
+
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
586
605
|
.description('Browser control — navigate, click, type, extract, wait (no LLM needed)');
|
|
587
606
|
const originalBrowserDescription = browser.description();
|
|
588
607
|
/**
|
|
@@ -646,12 +665,14 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
646
665
|
/** Wrap browser actions with error handling and optional --json output */
|
|
647
666
|
function browserAction(fn) {
|
|
648
667
|
return async (...args) => {
|
|
668
|
+
let page = null;
|
|
649
669
|
try {
|
|
650
670
|
const command = args.at(-1) instanceof Command ? args.at(-1) : undefined;
|
|
651
671
|
const targetPage = getBrowserTargetId(command);
|
|
652
|
-
const
|
|
672
|
+
const session = getBrowserSession(command);
|
|
653
673
|
const contextId = getBrowserContextId(command);
|
|
654
|
-
const
|
|
674
|
+
const windowMode = getBrowserWindowMode(command, 'foreground');
|
|
675
|
+
page = await getBrowserPage(session, targetPage, contextId, { windowMode });
|
|
655
676
|
await fn(page, ...args);
|
|
656
677
|
}
|
|
657
678
|
catch (err) {
|
|
@@ -702,40 +723,19 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
702
723
|
};
|
|
703
724
|
}
|
|
704
725
|
browser.command('bind')
|
|
705
|
-
.option('--
|
|
706
|
-
.
|
|
707
|
-
.option('--workspace <name>', 'Bound workspace name (must start with bound:)')
|
|
708
|
-
.description('Bind a bound:* workspace to the current Chrome tab/window')
|
|
726
|
+
.option('--session <name>', 'Browser session name to bind')
|
|
727
|
+
.description('Bind the current Chrome tab/window to a browser session')
|
|
709
728
|
.action(async (optsOrCommand, maybeCommand) => {
|
|
710
729
|
const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
|
|
711
|
-
const
|
|
712
|
-
const rawWorkspace = getCommandOption(command, 'workspace');
|
|
713
|
-
const workspace = typeof rawWorkspace === 'string' && rawWorkspace.trim()
|
|
714
|
-
? rawWorkspace.trim()
|
|
715
|
-
: DEFAULT_BOUND_WORKSPACE;
|
|
716
|
-
if (!workspace.startsWith('bound:')) {
|
|
717
|
-
console.log(JSON.stringify({
|
|
718
|
-
error: {
|
|
719
|
-
code: 'invalid_bind_workspace',
|
|
720
|
-
message: `--workspace must start with "bound:", got "${workspace}"`,
|
|
721
|
-
hint: 'Use the default bound:default or pass --workspace bound:<name>.',
|
|
722
|
-
},
|
|
723
|
-
}, null, 2));
|
|
724
|
-
process.exitCode = EXIT_CODES.USAGE_ERROR;
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
730
|
+
const session = getBrowserSession(command);
|
|
727
731
|
try {
|
|
728
732
|
const { BrowserBridge } = await import('./browser/index.js');
|
|
729
733
|
const bridge = new BrowserBridge();
|
|
730
734
|
const contextId = getBrowserContextId(command);
|
|
731
|
-
await bridge.connect({ timeout: 30,
|
|
732
|
-
const data = await bindTab(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
...(typeof opts.pathPrefix === 'string' && opts.pathPrefix.trim() ? { matchPathPrefix: opts.pathPrefix.trim() } : {}),
|
|
736
|
-
});
|
|
737
|
-
saveBrowserTargetState(undefined, getBrowserScope(workspace, contextId));
|
|
738
|
-
console.log(JSON.stringify({ workspace, ...((data && typeof data === 'object') ? data : { data }) }, null, 2));
|
|
735
|
+
await bridge.connect({ timeout: 30, session, surface: 'browser', ...(contextId && { contextId }) });
|
|
736
|
+
const data = await bindTab(session, { ...(contextId && { contextId }) });
|
|
737
|
+
saveBrowserTargetState(undefined, getBrowserScope(session, contextId));
|
|
738
|
+
console.log(JSON.stringify({ session, ...((data && typeof data === 'object') ? data : { data }) }, null, 2));
|
|
739
739
|
}
|
|
740
740
|
catch (err) {
|
|
741
741
|
if (err instanceof BrowserCommandError && err.code) {
|
|
@@ -750,39 +750,23 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
750
750
|
log.error(err instanceof Error ? err.message : String(err));
|
|
751
751
|
if (err instanceof BrowserCommandError && err.hint)
|
|
752
752
|
log.error(`Hint: ${err.hint}`);
|
|
753
|
-
process.exitCode =
|
|
754
|
-
? EXIT_CODES.USAGE_ERROR
|
|
755
|
-
: EXIT_CODES.GENERIC_ERROR;
|
|
753
|
+
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
756
754
|
}
|
|
757
755
|
});
|
|
758
756
|
browser.command('unbind')
|
|
759
|
-
.option('--
|
|
760
|
-
.description('Detach a bound
|
|
757
|
+
.option('--session <name>', 'Browser session name to detach')
|
|
758
|
+
.description('Detach a bound browser session without closing the user tab/window')
|
|
761
759
|
.action(async (optsOrCommand, maybeCommand) => {
|
|
762
760
|
const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
|
|
763
|
-
const
|
|
764
|
-
const workspace = typeof rawWorkspace === 'string' && rawWorkspace.trim()
|
|
765
|
-
? rawWorkspace.trim()
|
|
766
|
-
: DEFAULT_BOUND_WORKSPACE;
|
|
767
|
-
if (!workspace.startsWith('bound:')) {
|
|
768
|
-
console.log(JSON.stringify({
|
|
769
|
-
error: {
|
|
770
|
-
code: 'invalid_bind_workspace',
|
|
771
|
-
message: `--workspace must start with "bound:", got "${workspace}"`,
|
|
772
|
-
hint: 'Use the default bound:default or pass --workspace bound:<name>.',
|
|
773
|
-
},
|
|
774
|
-
}, null, 2));
|
|
775
|
-
process.exitCode = EXIT_CODES.USAGE_ERROR;
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
761
|
+
const session = getBrowserSession(command);
|
|
778
762
|
try {
|
|
779
763
|
const { BrowserBridge } = await import('./browser/index.js');
|
|
780
764
|
const bridge = new BrowserBridge();
|
|
781
765
|
const contextId = getBrowserContextId(command);
|
|
782
|
-
await bridge.connect({ timeout: 30,
|
|
783
|
-
await sendCommand('close-window', {
|
|
784
|
-
saveBrowserTargetState(undefined, getBrowserScope(
|
|
785
|
-
console.log(JSON.stringify({ unbound: true,
|
|
766
|
+
await bridge.connect({ timeout: 30, session, surface: 'browser', ...(contextId && { contextId }) });
|
|
767
|
+
await sendCommand('close-window', { session, surface: 'browser', ...(contextId && { contextId }) });
|
|
768
|
+
saveBrowserTargetState(undefined, getBrowserScope(session, contextId));
|
|
769
|
+
console.log(JSON.stringify({ unbound: true, session }, null, 2));
|
|
786
770
|
}
|
|
787
771
|
catch (err) {
|
|
788
772
|
if (err instanceof BrowserCommandError && err.code) {
|
|
@@ -802,9 +786,9 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
802
786
|
});
|
|
803
787
|
const browserTab = browser
|
|
804
788
|
.command('tab')
|
|
805
|
-
.description('Tab management — list, create, and close tabs in the
|
|
789
|
+
.description('Tab management — list, create, and close tabs in the browser session');
|
|
806
790
|
browserTab.command('list')
|
|
807
|
-
.description('List tabs in the
|
|
791
|
+
.description('List tabs in the browser session with target IDs')
|
|
808
792
|
.action(browserAction(async (page) => {
|
|
809
793
|
const tabs = await page.tabs();
|
|
810
794
|
console.log(JSON.stringify(tabs, null, 2));
|
|
@@ -871,16 +855,11 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
871
855
|
* capped at 200 entries, bounding worst-case in-page memory.
|
|
872
856
|
*/
|
|
873
857
|
const NETWORK_INTERCEPTOR_JS = `(function(){if(window.__opencli_net)return;window.__opencli_net=[];var M=200,B=1048576,F=window.fetch;function capture(url,method,status,text,ct){if(window.__opencli_net.length>=M)return;var full=text?text.length:0,trunc=full>B,stored=trunc?text.slice(0,B):text,body=null;if(stored){if(trunc){body=stored}else{try{body=JSON.parse(stored)}catch(e){body=stored}}}var e={url:url,method:method||'GET',status:status,size:full,ct:ct,body:body,timestamp:Date.now()};if(trunc){e.bodyTruncated=true;e.bodyFullSize=full}window.__opencli_net.push(e)}window.fetch=async function(){var r=await F.apply(this,arguments);try{var ct=r.headers.get('content-type')||'';if(ct.includes('json')||ct.includes('text')){var c=r.clone(),t=await c.text();capture(r.url||(arguments[0]&&arguments[0].url)||String(arguments[0]),(arguments[1]&&arguments[1].method)||'GET',r.status,t,ct)}}catch(e){}return r};var X=XMLHttpRequest.prototype,O=X.open,S=X.send;X.open=function(m,u){this._om=m;this._ou=u;return O.apply(this,arguments)};X.send=function(){var x=this;x.addEventListener('load',function(){try{var ct=x.getResponseHeader('content-type')||'';if(ct.includes('json')||ct.includes('text')){capture(x._ou,x._om||'GET',x.status,x.responseText||'',ct)}}catch(e){}});return S.apply(this,arguments)}})()`;
|
|
874
|
-
addBrowserTabOption(browser.command('open').argument('<url>').
|
|
858
|
+
addBrowserTabOption(browser.command('open').argument('<url>').description('Open URL in the browser session'))
|
|
875
859
|
.action(browserAction(async (page, url, opts) => {
|
|
876
860
|
// Start session-level capture before navigation (catches initial requests)
|
|
877
861
|
const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
|
|
878
|
-
|
|
879
|
-
await page.goto(url, { allowBoundNavigation: true });
|
|
880
|
-
}
|
|
881
|
-
else {
|
|
882
|
-
await page.goto(url);
|
|
883
|
-
}
|
|
862
|
+
await page.goto(url);
|
|
884
863
|
await page.wait(2);
|
|
885
864
|
// Fallback: inject JS interceptor when session capture is unavailable
|
|
886
865
|
if (!hasSessionCapture) {
|
|
@@ -894,19 +873,8 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
894
873
|
...(page.getActivePage?.() ? { page: page.getActivePage?.() } : {}),
|
|
895
874
|
}, null, 2));
|
|
896
875
|
}));
|
|
897
|
-
addBrowserTabOption(browser.command('back').
|
|
876
|
+
addBrowserTabOption(browser.command('back').description('Go back in browser history'))
|
|
898
877
|
.action(browserAction(async (page, opts) => {
|
|
899
|
-
if (getPageWorkspace(page).startsWith('bound:') && opts.allowNavigateBound !== true) {
|
|
900
|
-
console.log(JSON.stringify({
|
|
901
|
-
error: {
|
|
902
|
-
code: 'bound_navigation_blocked',
|
|
903
|
-
message: `Workspace "${getPageWorkspace(page)}" is bound to a user tab; history navigation is blocked by default.`,
|
|
904
|
-
hint: 'Pass --allow-navigate-bound only if you intentionally want to navigate the bound tab.',
|
|
905
|
-
},
|
|
906
|
-
}, null, 2));
|
|
907
|
-
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
878
|
await page.evaluate('history.back()');
|
|
911
879
|
await page.wait(2);
|
|
912
880
|
console.log('Navigated back');
|
|
@@ -1037,7 +1005,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
1037
1005
|
}
|
|
1038
1006
|
const messages = filter(normalize(await page.consoleMessages(opts.level)));
|
|
1039
1007
|
console.log(JSON.stringify({
|
|
1040
|
-
|
|
1008
|
+
session: getPageSession(page),
|
|
1041
1009
|
captured_at: new Date().toISOString(),
|
|
1042
1010
|
count: messages.length,
|
|
1043
1011
|
messages: messages.map((message) => ({
|
|
@@ -2129,7 +2097,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2129
2097
|
// Default output is JSON (agent-native). Each entry carries a stable `key`
|
|
2130
2098
|
// (GraphQL operationName or `METHOD host+pathname`) so agents can fetch
|
|
2131
2099
|
// full bodies with `--detail <key>` even after subsequent commands.
|
|
2132
|
-
// Captures are persisted per
|
|
2100
|
+
// Captures are persisted per browser session under ~/.opencli/cache/browser-network/.
|
|
2133
2101
|
addBrowserTabOption(browser.command('network'))
|
|
2134
2102
|
.option('--detail <key>', 'Emit full body for the entry with this key')
|
|
2135
2103
|
.option('--all', 'Include static resources (js/css/images/telemetry)')
|
|
@@ -2144,7 +2112,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2144
2112
|
.description('Capture network requests as shape previews; retrieve full bodies by key')
|
|
2145
2113
|
.action(browserAction(async (page, opts) => {
|
|
2146
2114
|
const ttlMs = parsePositiveIntOption(opts.ttl, 'ttl', DEFAULT_TTL_MS);
|
|
2147
|
-
const
|
|
2115
|
+
const session = getPageSession(page);
|
|
2148
2116
|
const hasDetail = typeof opts.detail === 'string' && opts.detail.length > 0;
|
|
2149
2117
|
const hasFilter = typeof opts.filter === 'string';
|
|
2150
2118
|
const sinceMs = parseDurationMs(opts.since, 'since');
|
|
@@ -2180,9 +2148,9 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2180
2148
|
}
|
|
2181
2149
|
// --detail short-circuits: read from cache only, no live capture needed.
|
|
2182
2150
|
if (hasDetail) {
|
|
2183
|
-
const res = loadNetworkCache(
|
|
2151
|
+
const res = loadNetworkCache(session, { ttlMs });
|
|
2184
2152
|
if (res.status === 'missing') {
|
|
2185
|
-
emitNetworkError('cache_missing', `No cached capture. Run "browser network" first (in
|
|
2153
|
+
emitNetworkError('cache_missing', `No cached capture. Run "browser network" first (in session "${session}").`);
|
|
2186
2154
|
return;
|
|
2187
2155
|
}
|
|
2188
2156
|
if (res.status === 'expired') {
|
|
@@ -2303,7 +2271,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2303
2271
|
// via the output envelope rather than erroring out the whole command.
|
|
2304
2272
|
let cacheWarning = null;
|
|
2305
2273
|
try {
|
|
2306
|
-
saveNetworkCache(
|
|
2274
|
+
saveNetworkCache(session, cacheEntries);
|
|
2307
2275
|
}
|
|
2308
2276
|
catch (err) {
|
|
2309
2277
|
cacheWarning = `Could not persist capture cache: ${err.message}. --detail lookups may miss this capture.`;
|
|
@@ -2319,7 +2287,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2319
2287
|
: shaped;
|
|
2320
2288
|
const filterDropped = filterFields ? shaped.length - visible.length : 0;
|
|
2321
2289
|
const envelope = {
|
|
2322
|
-
|
|
2290
|
+
session,
|
|
2323
2291
|
captured_at: new Date().toISOString(),
|
|
2324
2292
|
count: visible.length,
|
|
2325
2293
|
filtered_out: filteredOut,
|
|
@@ -2384,19 +2352,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
2384
2352
|
console.log(`Adapter already exists: ${filePath}`);
|
|
2385
2353
|
return;
|
|
2386
2354
|
}
|
|
2387
|
-
// Try to detect domain from the last browser session
|
|
2388
2355
|
let domain = site;
|
|
2389
|
-
try {
|
|
2390
|
-
const page = await getBrowserPage();
|
|
2391
|
-
const url = await page.getCurrentUrl?.();
|
|
2392
|
-
if (url) {
|
|
2393
|
-
try {
|
|
2394
|
-
domain = new URL(url).hostname;
|
|
2395
|
-
}
|
|
2396
|
-
catch { }
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
catch { /* no active session */ }
|
|
2400
2356
|
const template = `import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2401
2357
|
|
|
2402
2358
|
cli({
|
|
@@ -2584,22 +2540,20 @@ cli({
|
|
|
2584
2540
|
}
|
|
2585
2541
|
});
|
|
2586
2542
|
// ── Session ──
|
|
2587
|
-
browser.command('close').description('Release the current
|
|
2543
|
+
browser.command('close').description('Release the current browser session tab lease')
|
|
2588
2544
|
.action(browserAction(async (page) => {
|
|
2589
2545
|
await page.closeWindow?.();
|
|
2590
|
-
console.log('
|
|
2546
|
+
console.log('Browser session tab lease released');
|
|
2591
2547
|
}));
|
|
2592
2548
|
// ── Built-in: doctor / completion ──────────────────────────────────────────
|
|
2593
2549
|
program
|
|
2594
2550
|
.command('doctor')
|
|
2595
2551
|
.description('Diagnose opencli browser bridge connectivity')
|
|
2596
|
-
.option('--no-live', 'Skip live browser connectivity test')
|
|
2597
|
-
.option('--sessions', 'Show active automation sessions', false)
|
|
2598
2552
|
.option('-v, --verbose', 'Debug output')
|
|
2599
2553
|
.action(async (opts) => {
|
|
2600
2554
|
applyVerbose(opts);
|
|
2601
2555
|
const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
|
|
2602
|
-
const report = await runBrowserDoctor({
|
|
2556
|
+
const report = await runBrowserDoctor({ cliVersion: PKG_VERSION });
|
|
2603
2557
|
console.log(renderBrowserDoctorReport(report));
|
|
2604
2558
|
});
|
|
2605
2559
|
program
|
|
@@ -2611,6 +2565,8 @@ cli({
|
|
|
2611
2565
|
});
|
|
2612
2566
|
// ── Plugin management ──────────────────────────────────────────────────────
|
|
2613
2567
|
const pluginCmd = program.command('plugin').description('Manage opencli plugins');
|
|
2568
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2569
|
+
const originalPluginDescription = pluginCmd.description();
|
|
2614
2570
|
pluginCmd
|
|
2615
2571
|
.command('install')
|
|
2616
2572
|
.description('Install a plugin from a git repository')
|
|
@@ -2797,6 +2753,8 @@ cli({
|
|
|
2797
2753
|
});
|
|
2798
2754
|
// ── Built-in: adapter management ─────────────────────────────────────────
|
|
2799
2755
|
const adapterCmd = program.command('adapter').description('Manage CLI adapters');
|
|
2756
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2757
|
+
const originalAdapterDescription = adapterCmd.description();
|
|
2800
2758
|
adapterCmd
|
|
2801
2759
|
.command('status')
|
|
2802
2760
|
.description('Show which sites have local overrides vs using official baseline')
|
|
@@ -2905,6 +2863,8 @@ cli({
|
|
|
2905
2863
|
});
|
|
2906
2864
|
// ── Built-in: browser profile selection ──────────────────────────────────
|
|
2907
2865
|
const profileCmd = program.command('profile').description('Manage Browser Bridge Chrome profiles');
|
|
2866
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2867
|
+
const originalProfileDescription = profileCmd.description();
|
|
2908
2868
|
profileCmd
|
|
2909
2869
|
.command('list')
|
|
2910
2870
|
.description('List Chrome profiles connected through the Browser Bridge extension')
|
|
@@ -2982,6 +2942,8 @@ cli({
|
|
|
2982
2942
|
});
|
|
2983
2943
|
// ── Built-in: daemon ──────────────────────────────────────────────────────
|
|
2984
2944
|
const daemonCmd = program.command('daemon').description('Manage the opencli daemon');
|
|
2945
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2946
|
+
const originalDaemonDescription = daemonCmd.description();
|
|
2985
2947
|
daemonCmd
|
|
2986
2948
|
.command('status')
|
|
2987
2949
|
.description('Show daemon status')
|
|
@@ -3107,6 +3069,10 @@ cli({
|
|
|
3107
3069
|
const adapterGroups = { external: externalNames, apps, sites };
|
|
3108
3070
|
const adapterNameSet = new Set([...externalNames, ...siteNames]);
|
|
3109
3071
|
installCommanderNamespaceStructuredHelp(browser, { globalCommand: program, description: originalBrowserDescription });
|
|
3072
|
+
installCommanderNamespaceStructuredHelp(daemonCmd, { globalCommand: program, description: originalDaemonDescription });
|
|
3073
|
+
installCommanderNamespaceStructuredHelp(pluginCmd, { globalCommand: program, description: originalPluginDescription });
|
|
3074
|
+
installCommanderNamespaceStructuredHelp(adapterCmd, { globalCommand: program, description: originalAdapterDescription });
|
|
3075
|
+
installCommanderNamespaceStructuredHelp(profileCmd, { globalCommand: program, description: originalProfileDescription });
|
|
3110
3076
|
program.configureHelp({
|
|
3111
3077
|
visibleCommands: (command) => command.commands.filter(child => command !== program || !adapterNameSet.has(child.name())),
|
|
3112
3078
|
});
|