@jackwener/opencli 1.7.16 → 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.
Files changed (149) hide show
  1. package/README.md +8 -9
  2. package/README.zh-CN.md +8 -8
  3. package/cli-manifest.json +97 -271
  4. package/clis/chatgpt/ask.js +1 -1
  5. package/clis/chatgpt/commands.test.js +2 -2
  6. package/clis/chatgpt/detail.js +1 -1
  7. package/clis/chatgpt/history.js +1 -1
  8. package/clis/chatgpt/image.js +38 -4
  9. package/clis/chatgpt/image.test.js +68 -1
  10. package/clis/chatgpt/new.js +1 -1
  11. package/clis/chatgpt/read.js +1 -1
  12. package/clis/chatgpt/send.js +1 -1
  13. package/clis/chatgpt/status.js +1 -1
  14. package/clis/chatgpt/utils.js +208 -16
  15. package/clis/chatgpt/utils.test.js +131 -2
  16. package/clis/claude/ask.js +1 -1
  17. package/clis/claude/detail.js +1 -1
  18. package/clis/claude/history.js +1 -1
  19. package/clis/claude/new.js +1 -1
  20. package/clis/claude/read.js +1 -1
  21. package/clis/claude/send.js +1 -1
  22. package/clis/claude/status.js +1 -1
  23. package/clis/deepseek/ask.js +1 -1
  24. package/clis/deepseek/detail.js +1 -1
  25. package/clis/deepseek/history.js +1 -1
  26. package/clis/deepseek/new.js +1 -1
  27. package/clis/deepseek/read.js +1 -1
  28. package/clis/deepseek/send.js +1 -1
  29. package/clis/deepseek/status.js +1 -1
  30. package/clis/doubao/ask.js +1 -1
  31. package/clis/doubao/detail.js +1 -1
  32. package/clis/doubao/history.js +1 -1
  33. package/clis/doubao/meeting-summary.js +1 -1
  34. package/clis/doubao/meeting-transcript.js +1 -1
  35. package/clis/doubao/new.js +1 -1
  36. package/clis/doubao/read.js +1 -1
  37. package/clis/doubao/send.js +1 -1
  38. package/clis/doubao/status.js +1 -1
  39. package/clis/gemini/ask.js +1 -1
  40. package/clis/gemini/deep-research-result.js +1 -1
  41. package/clis/gemini/deep-research.js +1 -1
  42. package/clis/gemini/image.js +1 -1
  43. package/clis/gemini/new.js +1 -1
  44. package/clis/grok/ask.js +1 -1
  45. package/clis/grok/detail.js +1 -1
  46. package/clis/grok/history.js +1 -1
  47. package/clis/grok/image.js +1 -1
  48. package/clis/grok/new.js +1 -1
  49. package/clis/grok/read.js +1 -1
  50. package/clis/grok/send.js +1 -1
  51. package/clis/grok/status.js +1 -1
  52. package/clis/notebooklm/current.js +1 -1
  53. package/clis/notebooklm/get.js +1 -1
  54. package/clis/notebooklm/history.js +1 -1
  55. package/clis/notebooklm/note-list.js +1 -1
  56. package/clis/notebooklm/notes-get.js +1 -1
  57. package/clis/notebooklm/open.js +2 -2
  58. package/clis/notebooklm/open.test.js +1 -1
  59. package/clis/notebooklm/source-fulltext.js +1 -1
  60. package/clis/notebooklm/source-get.js +1 -1
  61. package/clis/notebooklm/source-guide.js +1 -1
  62. package/clis/notebooklm/source-list.js +1 -1
  63. package/clis/notebooklm/summary.js +1 -1
  64. package/clis/qwen/ask.js +1 -1
  65. package/clis/qwen/detail.js +1 -1
  66. package/clis/qwen/history.js +1 -1
  67. package/clis/qwen/image.js +1 -1
  68. package/clis/qwen/new.js +1 -1
  69. package/clis/qwen/read.js +1 -1
  70. package/clis/qwen/send.js +1 -1
  71. package/clis/qwen/status.js +1 -1
  72. package/clis/reddit/comment.js +1 -1
  73. package/clis/reddit/frontpage.js +1 -1
  74. package/clis/reddit/popular.js +1 -1
  75. package/clis/reddit/read.js +1 -1
  76. package/clis/reddit/read.test.js +2 -2
  77. package/clis/reddit/save.js +1 -1
  78. package/clis/reddit/saved.js +1 -1
  79. package/clis/reddit/search.js +1 -1
  80. package/clis/reddit/subreddit.js +1 -1
  81. package/clis/reddit/subscribe.js +1 -1
  82. package/clis/reddit/upvote.js +1 -1
  83. package/clis/reddit/upvoted.js +1 -1
  84. package/clis/reddit/user-comments.js +1 -1
  85. package/clis/reddit/user-posts.js +1 -1
  86. package/clis/reddit/user.js +1 -1
  87. package/clis/twitter/article.js +1 -1
  88. package/clis/twitter/bookmark-folder.js +1 -1
  89. package/clis/twitter/bookmark-folders.js +1 -1
  90. package/clis/twitter/bookmarks.js +1 -1
  91. package/clis/twitter/download.js +1 -1
  92. package/clis/twitter/followers.js +1 -1
  93. package/clis/twitter/following.js +1 -1
  94. package/clis/twitter/likes.js +1 -1
  95. package/clis/twitter/list-tweets.js +1 -1
  96. package/clis/twitter/lists.js +1 -1
  97. package/clis/twitter/notifications.js +1 -1
  98. package/clis/twitter/profile.js +1 -1
  99. package/clis/twitter/search.js +1 -1
  100. package/clis/twitter/thread.js +1 -1
  101. package/clis/twitter/timeline.js +1 -1
  102. package/clis/twitter/trending.js +1 -1
  103. package/clis/twitter/tweets.js +1 -1
  104. package/clis/yuanbao/ask.js +1 -1
  105. package/clis/yuanbao/detail.js +1 -1
  106. package/clis/yuanbao/history.js +1 -1
  107. package/clis/yuanbao/new.js +1 -1
  108. package/clis/yuanbao/read.js +1 -1
  109. package/clis/yuanbao/send.js +1 -1
  110. package/clis/yuanbao/status.js +1 -1
  111. package/dist/src/browser/bridge.d.ts +3 -1
  112. package/dist/src/browser/bridge.js +3 -1
  113. package/dist/src/browser/cdp.d.ts +3 -1
  114. package/dist/src/browser/daemon-client.d.ts +7 -14
  115. package/dist/src/browser/daemon-client.js +2 -6
  116. package/dist/src/browser/network-cache.d.ts +5 -5
  117. package/dist/src/browser/network-cache.js +8 -8
  118. package/dist/src/browser/network-cache.test.js +4 -4
  119. package/dist/src/browser/page.d.ts +8 -7
  120. package/dist/src/browser/page.js +23 -16
  121. package/dist/src/browser/page.test.js +60 -30
  122. package/dist/src/build-manifest.js +1 -1
  123. package/dist/src/cli.js +60 -162
  124. package/dist/src/cli.test.js +178 -197
  125. package/dist/src/commanderAdapter.js +2 -0
  126. package/dist/src/discovery.js +1 -1
  127. package/dist/src/doctor.d.ts +0 -4
  128. package/dist/src/doctor.js +8 -72
  129. package/dist/src/doctor.test.js +26 -97
  130. package/dist/src/execution.d.ts +1 -0
  131. package/dist/src/execution.js +20 -21
  132. package/dist/src/execution.test.js +27 -31
  133. package/dist/src/help.js +7 -1
  134. package/dist/src/main.js +0 -19
  135. package/dist/src/manifest-types.d.ts +2 -4
  136. package/dist/src/observation/artifact.js +1 -1
  137. package/dist/src/observation/artifact.test.js +3 -3
  138. package/dist/src/observation/events.d.ts +1 -1
  139. package/dist/src/observation/manager.js +1 -1
  140. package/dist/src/observation/manager.test.js +3 -3
  141. package/dist/src/registry-api.d.ts +1 -1
  142. package/dist/src/registry.d.ts +3 -12
  143. package/dist/src/registry.js +6 -10
  144. package/dist/src/runtime.d.ts +7 -2
  145. package/dist/src/runtime.js +3 -1
  146. package/dist/src/serialization.d.ts +1 -1
  147. package/dist/src/serialization.js +1 -1
  148. package/dist/src/types.d.ts +0 -15
  149. 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 = DEFAULT_BROWSER_WORKSPACE) {
249
- const safeWorkspace = scope.replace(/[^a-zA-Z0-9_-]+/g, '_');
250
- return path.join(getBrowserCacheDir(), 'browser-state', `${safeWorkspace}.json`);
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 = DEFAULT_BROWSER_WORKSPACE) {
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 = DEFAULT_BROWSER_WORKSPACE) {
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 workspace may have restarted; re-run "opencli browser tab list" and choose a current target.', { cause: err });
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,33 +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 workspace may have restarted; re-run "opencli browser tab list" and choose a current target.');
302
+ 'The Browser Bridge session may have restarted; re-run "opencli browser tab list" and choose a current target.');
305
303
  }
306
- function getBrowserScope(workspace, contextId) {
307
- return contextId ? `${contextId}:${workspace}` : workspace;
304
+ function getBrowserScope(session, contextId) {
305
+ return contextId ? `${contextId}:${session}` : session;
308
306
  }
309
- async function resolveStoredBrowserTarget(page, scope = DEFAULT_BROWSER_WORKSPACE) {
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 dedicated browser workspace for session persistence. */
316
- async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE, contextId, opts = {}) {
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
- // Idle timeout: how long the browser workspace lease stays alive between commands
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
- workspace,
322
+ session,
323
+ surface: 'browser',
326
324
  ...(contextId && { contextId }),
327
325
  ...(idleTimeout && idleTimeout > 0 && { idleTimeout }),
328
326
  windowMode: opts.windowMode ?? getBrowserWindowMode(undefined, 'foreground'),
329
327
  });
330
- const targetScope = getBrowserScope(workspace, contextId);
328
+ const targetScope = getBrowserScope(session, contextId);
331
329
  const resolvedTargetPage = targetPage
332
330
  ? await resolveBrowserTargetInSession(page, targetPage, { scope: targetScope, source: 'explicit' })
333
331
  : await resolveStoredBrowserTarget(page, targetScope);
@@ -354,28 +352,6 @@ function getBrowserWindowMode(command, defaultMode) {
354
352
  }
355
353
  return defaultMode;
356
354
  }
357
- function parseBrowserBoolean(name, raw) {
358
- if (raw === undefined || raw === '')
359
- return undefined;
360
- if (raw === 'true')
361
- return true;
362
- if (raw === 'false')
363
- return false;
364
- throw new Error(`${name} must be one of: true, false. Received: "${String(raw)}"`);
365
- }
366
- function getBrowserKeepTab(command, defaultValue) {
367
- return parseBrowserBoolean('--keep-tab', getCommandOption(command, 'keepTab'))
368
- ?? parseBrowserBoolean('OPENCLI_KEEP_TAB', process.env.OPENCLI_KEEP_TAB)
369
- ?? defaultValue;
370
- }
371
- function hasExplicitBrowserWindowOption(command) {
372
- const raw = getCommandOption(command, 'window');
373
- return raw !== undefined && raw !== '';
374
- }
375
- function hasExplicitBrowserKeepTabOption(command) {
376
- const raw = getCommandOption(command, 'keepTab');
377
- return raw !== undefined && raw !== '';
378
- }
379
355
  function addBrowserTabOption(command) {
380
356
  return command.option('--tab <targetId>', BROWSER_TAB_OPTION_DESCRIPTION);
381
357
  }
@@ -395,21 +371,25 @@ function getCommandOption(command, option) {
395
371
  }
396
372
  return undefined;
397
373
  }
398
- function getBrowserWorkspace(command) {
399
- const raw = getCommandOption(command, 'workspace');
400
- return typeof raw === 'string' && raw.trim() ? raw.trim() : DEFAULT_BROWSER_WORKSPACE;
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');
401
379
  }
402
380
  function getBrowserContextId(command) {
403
381
  const raw = getCommandOption(command, 'profile');
404
382
  return resolveProfileContextId(typeof raw === 'string' && raw.trim() ? raw.trim() : undefined);
405
383
  }
406
- function getPageWorkspace(page) {
407
- const workspace = page.workspace;
408
- return typeof workspace === 'string' && workspace.trim() ? workspace.trim() : DEFAULT_BROWSER_WORKSPACE;
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');
409
389
  }
410
390
  function getPageScope(page) {
411
391
  const contextId = page.contextId;
412
- return getBrowserScope(getPageWorkspace(page), typeof contextId === 'string' && contextId.trim() ? contextId.trim() : undefined);
392
+ return getBrowserScope(getPageSession(page), typeof contextId === 'string' && contextId.trim() ? contextId.trim() : undefined);
413
393
  }
414
394
  function snapshotMetricText(snapshot) {
415
395
  return typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2);
@@ -620,9 +600,8 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
620
600
  // All commands wrapped in browserAction() for consistent error handling.
621
601
  const browser = program
622
602
  .command('browser')
623
- .option('--workspace <name>', 'Browser workspace to use (default: browser:default; bound tabs use bound:<name>)')
603
+ .option('--session <name>', 'Browser session to use')
624
604
  .option('--window <mode>', 'Browser window mode: foreground or background')
625
- .option('--keep-tab <bool>', 'Keep the browser tab lease after the command finishes')
626
605
  .description('Browser control — navigate, click, type, extract, wait (no LLM needed)');
627
606
  const originalBrowserDescription = browser.description();
628
607
  /**
@@ -687,19 +666,13 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
687
666
  function browserAction(fn) {
688
667
  return async (...args) => {
689
668
  let page = null;
690
- let shouldReleasePage = false;
691
669
  try {
692
670
  const command = args.at(-1) instanceof Command ? args.at(-1) : undefined;
693
671
  const targetPage = getBrowserTargetId(command);
694
- const workspace = getBrowserWorkspace(command);
672
+ const session = getBrowserSession(command);
695
673
  const contextId = getBrowserContextId(command);
696
674
  const windowMode = getBrowserWindowMode(command, 'foreground');
697
- const keepTab = getBrowserKeepTab(command, true);
698
- shouldReleasePage = !keepTab && !workspace.startsWith('bound:');
699
- if (workspace.startsWith('bound:') && (hasExplicitBrowserWindowOption(command) || hasExplicitBrowserKeepTabOption(command))) {
700
- log.warn('--window/--keep-tab ignored for bound:* workspaces; bound tabs are user-owned.');
701
- }
702
- page = await getBrowserPage(targetPage, workspace, contextId, { windowMode });
675
+ page = await getBrowserPage(session, targetPage, contextId, { windowMode });
703
676
  await fn(page, ...args);
704
677
  }
705
678
  catch (err) {
@@ -747,51 +720,22 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
747
720
  }
748
721
  process.exitCode = EXIT_CODES.GENERIC_ERROR;
749
722
  }
750
- finally {
751
- if (shouldReleasePage && page?.closeWindow) {
752
- await page.closeWindow().catch((err) => {
753
- if (process.env.OPENCLI_VERBOSE)
754
- log.warn(`[browser] Failed to release tab lease: ${getErrorMessage(err)}`);
755
- });
756
- }
757
- }
758
723
  };
759
724
  }
760
725
  browser.command('bind')
761
- .option('--domain <host>', 'Only bind a current/visible tab whose hostname matches this domain')
762
- .option('--path-prefix <path>', 'Only bind a current/visible tab whose pathname starts with this prefix')
763
- .option('--workspace <name>', 'Bound workspace name (must start with bound:)')
764
- .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')
765
728
  .action(async (optsOrCommand, maybeCommand) => {
766
729
  const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
767
- const opts = command?.opts() ?? optsOrCommand ?? {};
768
- const rawWorkspace = getCommandOption(command, 'workspace');
769
- const workspace = typeof rawWorkspace === 'string' && rawWorkspace.trim()
770
- ? rawWorkspace.trim()
771
- : DEFAULT_BOUND_WORKSPACE;
772
- if (!workspace.startsWith('bound:')) {
773
- console.log(JSON.stringify({
774
- error: {
775
- code: 'invalid_bind_workspace',
776
- message: `--workspace must start with "bound:", got "${workspace}"`,
777
- hint: 'Use the default bound:default or pass --workspace bound:<name>.',
778
- },
779
- }, null, 2));
780
- process.exitCode = EXIT_CODES.USAGE_ERROR;
781
- return;
782
- }
730
+ const session = getBrowserSession(command);
783
731
  try {
784
732
  const { BrowserBridge } = await import('./browser/index.js');
785
733
  const bridge = new BrowserBridge();
786
734
  const contextId = getBrowserContextId(command);
787
- await bridge.connect({ timeout: 30, workspace, ...(contextId && { contextId }) });
788
- const data = await bindTab(workspace, {
789
- ...(contextId && { contextId }),
790
- ...(typeof opts.domain === 'string' && opts.domain.trim() ? { matchDomain: opts.domain.trim() } : {}),
791
- ...(typeof opts.pathPrefix === 'string' && opts.pathPrefix.trim() ? { matchPathPrefix: opts.pathPrefix.trim() } : {}),
792
- });
793
- saveBrowserTargetState(undefined, getBrowserScope(workspace, contextId));
794
- 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));
795
739
  }
796
740
  catch (err) {
797
741
  if (err instanceof BrowserCommandError && err.code) {
@@ -806,39 +750,23 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
806
750
  log.error(err instanceof Error ? err.message : String(err));
807
751
  if (err instanceof BrowserCommandError && err.hint)
808
752
  log.error(`Hint: ${err.hint}`);
809
- process.exitCode = err instanceof BrowserCommandError && err.code === 'invalid_bind_workspace'
810
- ? EXIT_CODES.USAGE_ERROR
811
- : EXIT_CODES.GENERIC_ERROR;
753
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
812
754
  }
813
755
  });
814
756
  browser.command('unbind')
815
- .option('--workspace <name>', 'Bound workspace name to detach')
816
- .description('Detach a bound:* workspace without closing the user tab/window')
757
+ .option('--session <name>', 'Browser session name to detach')
758
+ .description('Detach a bound browser session without closing the user tab/window')
817
759
  .action(async (optsOrCommand, maybeCommand) => {
818
760
  const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
819
- const rawWorkspace = getCommandOption(command, 'workspace');
820
- const workspace = typeof rawWorkspace === 'string' && rawWorkspace.trim()
821
- ? rawWorkspace.trim()
822
- : DEFAULT_BOUND_WORKSPACE;
823
- if (!workspace.startsWith('bound:')) {
824
- console.log(JSON.stringify({
825
- error: {
826
- code: 'invalid_bind_workspace',
827
- message: `--workspace must start with "bound:", got "${workspace}"`,
828
- hint: 'Use the default bound:default or pass --workspace bound:<name>.',
829
- },
830
- }, null, 2));
831
- process.exitCode = EXIT_CODES.USAGE_ERROR;
832
- return;
833
- }
761
+ const session = getBrowserSession(command);
834
762
  try {
835
763
  const { BrowserBridge } = await import('./browser/index.js');
836
764
  const bridge = new BrowserBridge();
837
765
  const contextId = getBrowserContextId(command);
838
- await bridge.connect({ timeout: 30, workspace, ...(contextId && { contextId }) });
839
- await sendCommand('close-window', { workspace, ...(contextId && { contextId }) });
840
- saveBrowserTargetState(undefined, getBrowserScope(workspace, contextId));
841
- console.log(JSON.stringify({ unbound: true, workspace }, null, 2));
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));
842
770
  }
843
771
  catch (err) {
844
772
  if (err instanceof BrowserCommandError && err.code) {
@@ -858,9 +786,9 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
858
786
  });
859
787
  const browserTab = browser
860
788
  .command('tab')
861
- .description('Tab management — list, create, and close tabs in the automation window');
789
+ .description('Tab management — list, create, and close tabs in the browser session');
862
790
  browserTab.command('list')
863
- .description('List tabs in the automation window with target IDs')
791
+ .description('List tabs in the browser session with target IDs')
864
792
  .action(browserAction(async (page) => {
865
793
  const tabs = await page.tabs();
866
794
  console.log(JSON.stringify(tabs, null, 2));
@@ -927,16 +855,11 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
927
855
  * capped at 200 entries, bounding worst-case in-page memory.
928
856
  */
929
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)}})()`;
930
- addBrowserTabOption(browser.command('open').argument('<url>').option('--allow-navigate-bound', 'Allow navigating a bound user tab', false).description('Open URL in automation window'))
858
+ addBrowserTabOption(browser.command('open').argument('<url>').description('Open URL in the browser session'))
931
859
  .action(browserAction(async (page, url, opts) => {
932
860
  // Start session-level capture before navigation (catches initial requests)
933
861
  const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
934
- if (opts.allowNavigateBound === true) {
935
- await page.goto(url, { allowBoundNavigation: true });
936
- }
937
- else {
938
- await page.goto(url);
939
- }
862
+ await page.goto(url);
940
863
  await page.wait(2);
941
864
  // Fallback: inject JS interceptor when session capture is unavailable
942
865
  if (!hasSessionCapture) {
@@ -950,19 +873,8 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
950
873
  ...(page.getActivePage?.() ? { page: page.getActivePage?.() } : {}),
951
874
  }, null, 2));
952
875
  }));
953
- addBrowserTabOption(browser.command('back').option('--allow-navigate-bound', 'Allow history navigation in a bound user tab', false).description('Go back in browser history'))
876
+ addBrowserTabOption(browser.command('back').description('Go back in browser history'))
954
877
  .action(browserAction(async (page, opts) => {
955
- if (getPageWorkspace(page).startsWith('bound:') && opts.allowNavigateBound !== true) {
956
- console.log(JSON.stringify({
957
- error: {
958
- code: 'bound_navigation_blocked',
959
- message: `Workspace "${getPageWorkspace(page)}" is bound to a user tab; history navigation is blocked by default.`,
960
- hint: 'Pass --allow-navigate-bound only if you intentionally want to navigate the bound tab.',
961
- },
962
- }, null, 2));
963
- process.exitCode = EXIT_CODES.GENERIC_ERROR;
964
- return;
965
- }
966
878
  await page.evaluate('history.back()');
967
879
  await page.wait(2);
968
880
  console.log('Navigated back');
@@ -1093,7 +1005,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
1093
1005
  }
1094
1006
  const messages = filter(normalize(await page.consoleMessages(opts.level)));
1095
1007
  console.log(JSON.stringify({
1096
- workspace: getPageWorkspace(page),
1008
+ session: getPageSession(page),
1097
1009
  captured_at: new Date().toISOString(),
1098
1010
  count: messages.length,
1099
1011
  messages: messages.map((message) => ({
@@ -2185,7 +2097,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2185
2097
  // Default output is JSON (agent-native). Each entry carries a stable `key`
2186
2098
  // (GraphQL operationName or `METHOD host+pathname`) so agents can fetch
2187
2099
  // full bodies with `--detail <key>` even after subsequent commands.
2188
- // Captures are persisted per workspace under ~/.opencli/cache/browser-network/.
2100
+ // Captures are persisted per browser session under ~/.opencli/cache/browser-network/.
2189
2101
  addBrowserTabOption(browser.command('network'))
2190
2102
  .option('--detail <key>', 'Emit full body for the entry with this key')
2191
2103
  .option('--all', 'Include static resources (js/css/images/telemetry)')
@@ -2200,7 +2112,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2200
2112
  .description('Capture network requests as shape previews; retrieve full bodies by key')
2201
2113
  .action(browserAction(async (page, opts) => {
2202
2114
  const ttlMs = parsePositiveIntOption(opts.ttl, 'ttl', DEFAULT_TTL_MS);
2203
- const workspace = getPageWorkspace(page);
2115
+ const session = getPageSession(page);
2204
2116
  const hasDetail = typeof opts.detail === 'string' && opts.detail.length > 0;
2205
2117
  const hasFilter = typeof opts.filter === 'string';
2206
2118
  const sinceMs = parseDurationMs(opts.since, 'since');
@@ -2236,9 +2148,9 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2236
2148
  }
2237
2149
  // --detail short-circuits: read from cache only, no live capture needed.
2238
2150
  if (hasDetail) {
2239
- const res = loadNetworkCache(workspace, { ttlMs });
2151
+ const res = loadNetworkCache(session, { ttlMs });
2240
2152
  if (res.status === 'missing') {
2241
- emitNetworkError('cache_missing', `No cached capture. Run "browser network" first (in workspace "${workspace}").`);
2153
+ emitNetworkError('cache_missing', `No cached capture. Run "browser network" first (in session "${session}").`);
2242
2154
  return;
2243
2155
  }
2244
2156
  if (res.status === 'expired') {
@@ -2359,7 +2271,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2359
2271
  // via the output envelope rather than erroring out the whole command.
2360
2272
  let cacheWarning = null;
2361
2273
  try {
2362
- saveNetworkCache(workspace, cacheEntries);
2274
+ saveNetworkCache(session, cacheEntries);
2363
2275
  }
2364
2276
  catch (err) {
2365
2277
  cacheWarning = `Could not persist capture cache: ${err.message}. --detail lookups may miss this capture.`;
@@ -2375,7 +2287,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2375
2287
  : shaped;
2376
2288
  const filterDropped = filterFields ? shaped.length - visible.length : 0;
2377
2289
  const envelope = {
2378
- workspace,
2290
+ session,
2379
2291
  captured_at: new Date().toISOString(),
2380
2292
  count: visible.length,
2381
2293
  filtered_out: filteredOut,
@@ -2440,19 +2352,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
2440
2352
  console.log(`Adapter already exists: ${filePath}`);
2441
2353
  return;
2442
2354
  }
2443
- // Try to detect domain from the last browser session
2444
2355
  let domain = site;
2445
- try {
2446
- const page = await getBrowserPage();
2447
- const url = await page.getCurrentUrl?.();
2448
- if (url) {
2449
- try {
2450
- domain = new URL(url).hostname;
2451
- }
2452
- catch { }
2453
- }
2454
- }
2455
- catch { /* no active session */ }
2456
2356
  const template = `import { cli, Strategy } from '@jackwener/opencli/registry';
2457
2357
 
2458
2358
  cli({
@@ -2640,22 +2540,20 @@ cli({
2640
2540
  }
2641
2541
  });
2642
2542
  // ── Session ──
2643
- browser.command('close').description('Release the current automation tab lease')
2543
+ browser.command('close').description('Release the current browser session tab lease')
2644
2544
  .action(browserAction(async (page) => {
2645
2545
  await page.closeWindow?.();
2646
- console.log('Automation tab lease released');
2546
+ console.log('Browser session tab lease released');
2647
2547
  }));
2648
2548
  // ── Built-in: doctor / completion ──────────────────────────────────────────
2649
2549
  program
2650
2550
  .command('doctor')
2651
2551
  .description('Diagnose opencli browser bridge connectivity')
2652
- .option('--no-live', 'Skip live browser connectivity test')
2653
- .option('--sessions', 'Show active automation sessions', false)
2654
2552
  .option('-v, --verbose', 'Debug output')
2655
2553
  .action(async (opts) => {
2656
2554
  applyVerbose(opts);
2657
2555
  const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
2658
- const report = await runBrowserDoctor({ live: opts.live, sessions: opts.sessions, cliVersion: PKG_VERSION });
2556
+ const report = await runBrowserDoctor({ cliVersion: PKG_VERSION });
2659
2557
  console.log(renderBrowserDoctorReport(report));
2660
2558
  });
2661
2559
  program