@jackwener/opencli 1.7.4 → 1.7.5

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 (126) hide show
  1. package/README.md +71 -49
  2. package/README.zh-CN.md +73 -60
  3. package/cli-manifest.json +3261 -1758
  4. package/clis/antigravity/serve.js +71 -25
  5. package/clis/baidu-scholar/search.js +87 -0
  6. package/clis/baidu-scholar/search.test.js +23 -0
  7. package/clis/deepseek/ask.js +74 -0
  8. package/clis/deepseek/history.js +25 -0
  9. package/clis/deepseek/new.js +20 -0
  10. package/clis/deepseek/read.js +22 -0
  11. package/clis/deepseek/status.js +24 -0
  12. package/clis/deepseek/utils.js +208 -0
  13. package/clis/eastmoney/_secid.js +78 -0
  14. package/clis/eastmoney/announcement.js +52 -0
  15. package/clis/eastmoney/convertible.js +73 -0
  16. package/clis/eastmoney/etf.js +65 -0
  17. package/clis/eastmoney/holders.js +78 -0
  18. package/clis/eastmoney/index-board.js +96 -0
  19. package/clis/eastmoney/kline.js +87 -0
  20. package/clis/eastmoney/kuaixun.js +54 -0
  21. package/clis/eastmoney/longhu.js +67 -0
  22. package/clis/eastmoney/money-flow.js +78 -0
  23. package/clis/eastmoney/northbound.js +57 -0
  24. package/clis/eastmoney/quote.js +107 -0
  25. package/clis/eastmoney/rank.js +94 -0
  26. package/clis/eastmoney/sectors.js +76 -0
  27. package/clis/google-scholar/search.js +58 -0
  28. package/clis/google-scholar/search.test.js +23 -0
  29. package/clis/gov-law/commands.test.js +39 -0
  30. package/clis/gov-law/recent.js +22 -0
  31. package/clis/gov-law/search.js +41 -0
  32. package/clis/gov-law/shared.js +51 -0
  33. package/clis/gov-policy/commands.test.js +27 -0
  34. package/clis/gov-policy/recent.js +47 -0
  35. package/clis/gov-policy/search.js +48 -0
  36. package/clis/nowcoder/companies.js +23 -0
  37. package/clis/nowcoder/creators.js +27 -0
  38. package/clis/nowcoder/detail.js +61 -0
  39. package/clis/nowcoder/experience.js +36 -0
  40. package/clis/nowcoder/hot.js +24 -0
  41. package/clis/nowcoder/jobs.js +21 -0
  42. package/clis/nowcoder/notifications.js +29 -0
  43. package/clis/nowcoder/papers.js +40 -0
  44. package/clis/nowcoder/practice.js +37 -0
  45. package/clis/nowcoder/recommend.js +30 -0
  46. package/clis/nowcoder/referral.js +39 -0
  47. package/clis/nowcoder/salary.js +40 -0
  48. package/clis/nowcoder/search.js +49 -0
  49. package/clis/nowcoder/suggest.js +33 -0
  50. package/clis/nowcoder/topics.js +27 -0
  51. package/clis/nowcoder/trending.js +25 -0
  52. package/clis/twitter/list-add.js +337 -0
  53. package/clis/twitter/list-add.test.js +15 -0
  54. package/clis/twitter/list-remove.js +297 -0
  55. package/clis/twitter/list-remove.test.js +14 -0
  56. package/clis/twitter/list-tweets.js +185 -0
  57. package/clis/twitter/list-tweets.test.js +108 -0
  58. package/clis/twitter/lists.js +134 -47
  59. package/clis/twitter/lists.test.js +105 -38
  60. package/clis/wanfang/search.js +66 -0
  61. package/clis/wanfang/search.test.js +23 -0
  62. package/clis/web/read.js +1 -1
  63. package/clis/weixin/download.js +3 -2
  64. package/clis/xiaohongshu/publish.js +149 -28
  65. package/clis/xiaohongshu/publish.test.js +319 -6
  66. package/clis/xiaoyuzhou/download.js +8 -4
  67. package/clis/xiaoyuzhou/download.test.js +23 -13
  68. package/clis/xiaoyuzhou/episode.js +9 -4
  69. package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
  70. package/clis/xiaoyuzhou/podcast.js +9 -4
  71. package/clis/xiaoyuzhou/utils.js +0 -40
  72. package/clis/xiaoyuzhou/utils.test.js +15 -75
  73. package/clis/zsxq/dynamics.js +1 -1
  74. package/clis/zsxq/utils.js +6 -3
  75. package/clis/zsxq/utils.test.js +31 -0
  76. package/dist/src/browser/base-page.d.ts +1 -1
  77. package/dist/src/browser/bridge.d.ts +1 -0
  78. package/dist/src/browser/bridge.js +1 -1
  79. package/dist/src/browser/cdp.js +1 -1
  80. package/dist/src/browser/daemon-client.d.ts +6 -4
  81. package/dist/src/browser/daemon-client.js +6 -1
  82. package/dist/src/browser/daemon-client.test.js +40 -1
  83. package/dist/src/browser/dom-snapshot.js +7 -2
  84. package/dist/src/browser/page.d.ts +14 -4
  85. package/dist/src/browser/page.js +48 -7
  86. package/dist/src/browser/page.test.js +97 -0
  87. package/dist/src/cli.js +227 -150
  88. package/dist/src/cli.test.js +167 -90
  89. package/dist/src/commanderAdapter.d.ts +0 -1
  90. package/dist/src/commanderAdapter.js +2 -16
  91. package/dist/src/commanderAdapter.test.js +1 -1
  92. package/dist/src/completion-shared.js +2 -5
  93. package/dist/src/daemon.js +8 -0
  94. package/dist/src/download/article-download.d.ts +1 -0
  95. package/dist/src/download/article-download.js +3 -0
  96. package/dist/src/download/article-download.test.js +39 -0
  97. package/dist/src/plugin.d.ts +1 -8
  98. package/dist/src/plugin.js +1 -27
  99. package/dist/src/plugin.test.js +1 -59
  100. package/dist/src/registry.d.ts +1 -0
  101. package/dist/src/registry.js +3 -2
  102. package/dist/src/registry.test.js +22 -0
  103. package/dist/src/types.d.ts +14 -5
  104. package/package.json +1 -1
  105. package/clis/twitter/lists-parser.js +0 -77
  106. package/clis/twitter/lists.d.ts +0 -5
  107. package/dist/src/cascade.d.ts +0 -46
  108. package/dist/src/cascade.js +0 -135
  109. package/dist/src/explore.d.ts +0 -99
  110. package/dist/src/explore.js +0 -402
  111. package/dist/src/generate-verified.d.ts +0 -105
  112. package/dist/src/generate-verified.js +0 -696
  113. package/dist/src/generate-verified.test.js +0 -925
  114. package/dist/src/generate.d.ts +0 -46
  115. package/dist/src/generate.js +0 -117
  116. package/dist/src/record.d.ts +0 -96
  117. package/dist/src/record.js +0 -657
  118. package/dist/src/record.test.d.ts +0 -1
  119. package/dist/src/record.test.js +0 -293
  120. package/dist/src/skill-generate.d.ts +0 -30
  121. package/dist/src/skill-generate.js +0 -75
  122. package/dist/src/skill-generate.test.d.ts +0 -1
  123. package/dist/src/skill-generate.test.js +0 -173
  124. package/dist/src/synthesize.d.ts +0 -97
  125. package/dist/src/synthesize.js +0 -208
  126. /package/dist/src/{generate-verified.test.d.ts → download/article-download.test.d.ts} +0 -0
package/dist/src/cli.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * Dynamic adapter commands are registered via commanderAdapter.ts.
6
6
  */
7
7
  import * as fs from 'node:fs';
8
+ import * as os from 'node:os';
8
9
  import * as path from 'node:path';
9
10
  import { fileURLToPath } from 'node:url';
10
11
  import { Command } from 'commander';
@@ -13,7 +14,6 @@ import { findPackageRoot, getBuiltEntryCandidates } from './package-paths.js';
13
14
  import { fullName, getRegistry, strategyLabel } from './registry.js';
14
15
  import { serializeCommand, formatArgSummary } from './serialization.js';
15
16
  import { render as renderOutput } from './output.js';
16
- import { getBrowserFactory, browserSession } from './runtime.js';
17
17
  import { PKG_VERSION } from './version.js';
18
18
  import { printCompletionScript } from './completion.js';
19
19
  import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
@@ -24,11 +24,122 @@ import { resolveTargetJs, getTextResolvedJs, getValueResolvedJs, getAttributesRe
24
24
  import { daemonStatus, daemonStop } from './commands/daemon.js';
25
25
  import { log } from './logger.js';
26
26
  const CLI_FILE = fileURLToPath(import.meta.url);
27
+ const DEFAULT_BROWSER_WORKSPACE = 'browser:default';
28
+ const BROWSER_TAB_OPTION_DESCRIPTION = 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"';
29
+ function getBrowserCacheDir() {
30
+ return process.env.OPENCLI_CACHE_DIR || path.join(os.homedir(), '.opencli', 'cache');
31
+ }
32
+ function getBrowserTargetStatePath(scope = DEFAULT_BROWSER_WORKSPACE) {
33
+ const safeWorkspace = scope.replace(/[^a-zA-Z0-9_-]+/g, '_');
34
+ return path.join(getBrowserCacheDir(), 'browser-state', `${safeWorkspace}.json`);
35
+ }
36
+ function loadBrowserTargetState(scope = DEFAULT_BROWSER_WORKSPACE) {
37
+ try {
38
+ const raw = fs.readFileSync(getBrowserTargetStatePath(scope), 'utf-8');
39
+ const parsed = JSON.parse(raw);
40
+ return parsed && typeof parsed === 'object' ? parsed : null;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function saveBrowserTargetState(defaultPage, scope = DEFAULT_BROWSER_WORKSPACE) {
47
+ const target = getBrowserTargetStatePath(scope);
48
+ if (!defaultPage) {
49
+ fs.rmSync(target, { force: true });
50
+ return;
51
+ }
52
+ fs.mkdirSync(path.dirname(target), { recursive: true });
53
+ fs.writeFileSync(target, JSON.stringify({ defaultPage, updatedAt: new Date().toISOString() }), 'utf-8');
54
+ }
55
+ function hasBrowserTabTarget(tabs, targetPage) {
56
+ return tabs.some((tab) => {
57
+ return typeof tab === 'object'
58
+ && tab !== null
59
+ && 'page' in tab
60
+ && typeof tab.page === 'string'
61
+ && tab.page === targetPage;
62
+ });
63
+ }
64
+ async function resolveBrowserTargetInSession(page, targetPage, opts) {
65
+ const candidate = targetPage.trim();
66
+ if (!candidate)
67
+ return undefined;
68
+ let tabs;
69
+ try {
70
+ tabs = await page.tabs();
71
+ }
72
+ catch (err) {
73
+ if (opts.source === 'saved') {
74
+ saveBrowserTargetState(undefined, opts.scope);
75
+ return undefined;
76
+ }
77
+ throw new Error(`Target tab ${candidate} could not be validated in the current browser session. ` +
78
+ 'The Browser Bridge workspace may have restarted; re-run "opencli browser tab list" and choose a current target.', { cause: err });
79
+ }
80
+ if (Array.isArray(tabs) && hasBrowserTabTarget(tabs, candidate)) {
81
+ return candidate;
82
+ }
83
+ if (opts.source === 'saved') {
84
+ saveBrowserTargetState(undefined, opts.scope);
85
+ return undefined;
86
+ }
87
+ throw new Error(`Target tab ${candidate} is not part of the current browser session. ` +
88
+ 'The Browser Bridge workspace may have restarted; re-run "opencli browser tab list" and choose a current target.');
89
+ }
90
+ async function resolveStoredBrowserTarget(page, scope = DEFAULT_BROWSER_WORKSPACE) {
91
+ const defaultPage = loadBrowserTargetState(scope)?.defaultPage?.trim();
92
+ if (!defaultPage)
93
+ return undefined;
94
+ return resolveBrowserTargetInSession(page, defaultPage, { scope, source: 'saved' });
95
+ }
27
96
  /** Create a browser page for browser commands. Uses a dedicated browser workspace for session persistence. */
28
- async function getBrowserPage() {
97
+ async function getBrowserPage(targetPage) {
29
98
  const { BrowserBridge } = await import('./browser/index.js');
30
99
  const bridge = new BrowserBridge();
31
- return bridge.connect({ timeout: 30, workspace: 'browser:default' });
100
+ const envTimeout = process.env.OPENCLI_BROWSER_TIMEOUT;
101
+ const idleTimeout = envTimeout ? parseInt(envTimeout, 10) : undefined;
102
+ const page = await bridge.connect({
103
+ timeout: 30,
104
+ workspace: DEFAULT_BROWSER_WORKSPACE,
105
+ ...(idleTimeout && idleTimeout > 0 && { idleTimeout }),
106
+ });
107
+ const resolvedTargetPage = targetPage
108
+ ? await resolveBrowserTargetInSession(page, targetPage, { scope: DEFAULT_BROWSER_WORKSPACE, source: 'explicit' })
109
+ : await resolveStoredBrowserTarget(page, DEFAULT_BROWSER_WORKSPACE);
110
+ if (resolvedTargetPage) {
111
+ if (!page.setActivePage) {
112
+ throw new Error('This browser session does not support explicit tab targeting');
113
+ }
114
+ page.setActivePage(resolvedTargetPage);
115
+ }
116
+ return page;
117
+ }
118
+ function addBrowserTabOption(command) {
119
+ return command.option('--tab <targetId>', BROWSER_TAB_OPTION_DESCRIPTION);
120
+ }
121
+ function getBrowserTargetId(command) {
122
+ if (!command)
123
+ return undefined;
124
+ const opts = command.optsWithGlobals ? command.optsWithGlobals() : command.opts();
125
+ return typeof opts.tab === 'string' && opts.tab.trim() ? opts.tab.trim() : undefined;
126
+ }
127
+ function resolveBrowserTabTarget(targetId, opts) {
128
+ if (typeof targetId === 'string' && targetId.trim())
129
+ return targetId.trim();
130
+ if (typeof opts?.tab === 'string' && opts.tab.trim())
131
+ return opts.tab.trim();
132
+ return undefined;
133
+ }
134
+ function parsePositiveIntOption(val, label, fallback) {
135
+ if (val === undefined)
136
+ return fallback;
137
+ const parsed = parseInt(val, 10);
138
+ if (Number.isNaN(parsed) || parsed <= 0) {
139
+ console.error(`[cli] Invalid ${label}="${val}", using default ${fallback}`);
140
+ return fallback;
141
+ }
142
+ return parsed;
32
143
  }
33
144
  function applyVerbose(opts) {
34
145
  if (opts.verbose)
@@ -131,119 +242,6 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
131
242
  console.log(renderVerifyReport(r));
132
243
  process.exitCode = r.ok ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR;
133
244
  });
134
- // ── Built-in: explore / synthesize / generate / cascade ───────────────────
135
- program
136
- .command('explore')
137
- .alias('probe')
138
- .description('Explore a website: discover APIs, stores, and recommend strategies')
139
- .argument('<url>')
140
- .option('--site <name>')
141
- .option('--goal <text>')
142
- .option('--wait <s>', '', '3')
143
- .option('--auto', 'Enable interactive fuzzing')
144
- .option('--click <labels>', 'Comma-separated labels to click before fuzzing')
145
- .option('-v, --verbose', 'Debug output')
146
- .action(async (url, opts) => {
147
- applyVerbose(opts);
148
- const { exploreUrl, renderExploreSummary } = await import('./explore.js');
149
- const clickLabels = opts.click
150
- ? opts.click.split(',').map((s) => s.trim())
151
- : undefined;
152
- const workspace = `explore:${inferHost(url, opts.site)}`;
153
- const result = await exploreUrl(url, {
154
- BrowserFactory: getBrowserFactory(),
155
- site: opts.site,
156
- goal: opts.goal,
157
- waitSeconds: parseFloat(opts.wait),
158
- auto: opts.auto,
159
- clickLabels,
160
- workspace,
161
- });
162
- console.log(renderExploreSummary(result));
163
- });
164
- program
165
- .command('synthesize')
166
- .description('Synthesize CLIs from explore')
167
- .argument('<target>')
168
- .option('--top <n>', '', '3')
169
- .option('-v, --verbose', 'Debug output')
170
- .action(async (target, opts) => {
171
- applyVerbose(opts);
172
- const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js');
173
- console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) })));
174
- });
175
- program
176
- .command('generate')
177
- .description('One-shot: explore → synthesize → verify → register')
178
- .argument('<url>')
179
- .option('--goal <text>')
180
- .option('--site <name>')
181
- .option('--format <fmt>', 'Output format: table, json', 'table')
182
- .option('--no-register', 'Verify the generated adapter without registering it')
183
- .option('-v, --verbose', 'Debug output')
184
- .action(async (url, opts) => {
185
- applyVerbose(opts);
186
- const { generateVerifiedFromUrl, renderGenerateVerifiedSummary } = await import('./generate-verified.js');
187
- const workspace = `generate:${inferHost(url, opts.site)}`;
188
- const r = await generateVerifiedFromUrl({
189
- url,
190
- BrowserFactory: getBrowserFactory(),
191
- goal: opts.goal,
192
- site: opts.site,
193
- workspace,
194
- noRegister: opts.register === false,
195
- });
196
- if (opts.format === 'json')
197
- console.log(JSON.stringify(r, null, 2));
198
- else
199
- console.log(renderGenerateVerifiedSummary(r));
200
- process.exitCode = r.status === 'success' ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR;
201
- });
202
- // ── Built-in: record ─────────────────────────────────────────────────────
203
- program
204
- .command('record')
205
- .description('Record API calls from a live browser session → generate YAML candidates')
206
- .argument('<url>', 'URL to open and record')
207
- .option('--site <name>', 'Site name (inferred from URL if omitted)')
208
- .option('--out <dir>', 'Output directory for candidates')
209
- .option('--poll <ms>', 'Poll interval in milliseconds', '2000')
210
- .option('--timeout <ms>', 'Auto-stop after N milliseconds (default: 60000)', '60000')
211
- .option('-v, --verbose', 'Debug output')
212
- .action(async (url, opts) => {
213
- applyVerbose(opts);
214
- const { recordSession, renderRecordSummary } = await import('./record.js');
215
- const result = await recordSession({
216
- BrowserFactory: getBrowserFactory(),
217
- url,
218
- site: opts.site,
219
- outDir: opts.out,
220
- pollMs: parseInt(opts.poll, 10),
221
- timeoutMs: parseInt(opts.timeout, 10),
222
- });
223
- console.log(renderRecordSummary(result));
224
- process.exitCode = result.candidateCount > 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.EMPTY_RESULT;
225
- });
226
- program
227
- .command('cascade')
228
- .description('Strategy cascade: find simplest working strategy')
229
- .argument('<url>')
230
- .option('--site <name>')
231
- .option('-v, --verbose', 'Debug output')
232
- .action(async (url, opts) => {
233
- applyVerbose(opts);
234
- const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
235
- const workspace = `cascade:${inferHost(url, opts.site)}`;
236
- const result = await browserSession(getBrowserFactory(), async (page) => {
237
- try {
238
- const siteUrl = new URL(url);
239
- await page.goto(`${siteUrl.protocol}//${siteUrl.host}`);
240
- await page.wait(2);
241
- }
242
- catch { }
243
- return cascadeProbe(page, url);
244
- }, { workspace });
245
- console.log(renderCascadeResult(result));
246
- });
247
245
  // ── Built-in: browser (browser control for Claude Code skill) ───────────────
248
246
  //
249
247
  // Make websites accessible for AI agents.
@@ -262,7 +260,9 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
262
260
  function browserAction(fn) {
263
261
  return async (...args) => {
264
262
  try {
265
- const page = await getBrowserPage();
263
+ const command = args.at(-1) instanceof Command ? args.at(-1) : undefined;
264
+ const targetPage = getBrowserTargetId(command);
265
+ const page = await getBrowserPage(targetPage);
266
266
  await fn(page, ...args);
267
267
  }
268
268
  catch (err) {
@@ -293,10 +293,68 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
293
293
  }
294
294
  };
295
295
  }
296
+ const browserTab = browser
297
+ .command('tab')
298
+ .description('Tab management — list, create, and close tabs in the automation window');
299
+ browserTab.command('list')
300
+ .description('List tabs in the automation window with target IDs')
301
+ .action(browserAction(async (page) => {
302
+ const tabs = await page.tabs();
303
+ console.log(JSON.stringify(tabs, null, 2));
304
+ }));
305
+ browserTab.command('new')
306
+ .argument('[url]', 'Optional URL to open in the new tab')
307
+ .description('Create a new tab and print its target ID')
308
+ .action(browserAction(async (page, url) => {
309
+ if (!page.newTab) {
310
+ throw new Error('This browser session does not support creating tabs');
311
+ }
312
+ const createdPage = await page.newTab(url);
313
+ console.log(JSON.stringify({
314
+ page: createdPage,
315
+ url: url ?? null,
316
+ }, null, 2));
317
+ }));
318
+ addBrowserTabOption(browserTab.command('select')
319
+ .argument('[targetId]', 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"')
320
+ .description('Select a tab by target ID and make it the default browser tab'))
321
+ .action(browserAction(async (page, targetId, opts) => {
322
+ const resolvedTarget = resolveBrowserTabTarget(targetId, opts);
323
+ if (!resolvedTarget) {
324
+ throw new Error('Target tab required. Pass it as an argument or --tab <targetId>.');
325
+ }
326
+ await page.selectTab(resolvedTarget);
327
+ saveBrowserTargetState(resolvedTarget, DEFAULT_BROWSER_WORKSPACE);
328
+ console.log(JSON.stringify({ selected: resolvedTarget }, null, 2));
329
+ }));
330
+ addBrowserTabOption(browserTab.command('close')
331
+ .argument('[targetId]', 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"')
332
+ .description('Close a tab by target ID'))
333
+ .action(browserAction(async (page, targetId, opts) => {
334
+ const resolvedTarget = resolveBrowserTabTarget(targetId, opts);
335
+ if (!page.closeTab) {
336
+ throw new Error('This browser session does not support closing tabs');
337
+ }
338
+ if (!resolvedTarget) {
339
+ throw new Error('Target tab required. Pass it as an argument or --tab <targetId>.');
340
+ }
341
+ const validatedTarget = await resolveBrowserTargetInSession(page, resolvedTarget, {
342
+ scope: DEFAULT_BROWSER_WORKSPACE,
343
+ source: 'explicit',
344
+ });
345
+ if (!validatedTarget) {
346
+ throw new Error(`Target tab ${resolvedTarget} is not part of the current browser session.`);
347
+ }
348
+ await page.closeTab(validatedTarget);
349
+ if (loadBrowserTargetState(DEFAULT_BROWSER_WORKSPACE)?.defaultPage === validatedTarget) {
350
+ saveBrowserTargetState(undefined, DEFAULT_BROWSER_WORKSPACE);
351
+ }
352
+ console.log(JSON.stringify({ closed: validatedTarget }, null, 2));
353
+ }));
296
354
  // ── Navigation ──
297
355
  /** Network interceptor JS — injected on every open/navigate to capture fetch/XHR */
298
356
  const NETWORK_INTERCEPTOR_JS = `(function(){if(window.__opencli_net)return;window.__opencli_net=[];var M=200,B=50000,F=window.fetch;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();if(window.__opencli_net.length<M){var b=null;if(t.length<=B)try{b=JSON.parse(t)}catch(e){b=t}window.__opencli_net.push({url:r.url||(arguments[0]&&arguments[0].url)||String(arguments[0]),method:(arguments[1]&&arguments[1].method)||'GET',status:r.status,size:t.length,ct:ct,body:b})}}}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'))&&window.__opencli_net.length<M){var t=x.responseText,b=null;if(t&&t.length<=B)try{b=JSON.parse(t)}catch(e){b=t}window.__opencli_net.push({url:x._ou,method:x._om||'GET',status:x.status,size:t?t.length:0,ct:ct,body:b})}}catch(e){}});return S.apply(this,arguments)}})()`;
299
- browser.command('open').argument('<url>').description('Open URL in automation window')
357
+ addBrowserTabOption(browser.command('open').argument('<url>').description('Open URL in automation window'))
300
358
  .action(browserAction(async (page, url) => {
301
359
  // Start session-level capture before navigation (catches initial requests)
302
360
  const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
@@ -309,15 +367,18 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
309
367
  }
310
368
  catch { /* non-fatal */ }
311
369
  }
312
- console.log(`Navigated to: ${await page.getCurrentUrl?.() ?? url}`);
370
+ console.log(JSON.stringify({
371
+ url: await page.getCurrentUrl?.() ?? url,
372
+ ...(page.getActivePage?.() ? { page: page.getActivePage?.() } : {}),
373
+ }, null, 2));
313
374
  }));
314
- browser.command('back').description('Go back in browser history')
375
+ addBrowserTabOption(browser.command('back').description('Go back in browser history'))
315
376
  .action(browserAction(async (page) => {
316
377
  await page.evaluate('history.back()');
317
378
  await page.wait(2);
318
379
  console.log('Navigated back');
319
380
  }));
320
- browser.command('scroll').argument('<direction>', 'up or down').option('--amount <pixels>', 'Pixels to scroll', '500')
381
+ addBrowserTabOption(browser.command('scroll').argument('<direction>', 'up or down').option('--amount <pixels>', 'Pixels to scroll', '500'))
321
382
  .description('Scroll page')
322
383
  .action(browserAction(async (page, direction, opts) => {
323
384
  if (direction !== 'up' && direction !== 'down') {
@@ -329,14 +390,19 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
329
390
  console.log(`Scrolled ${direction}`);
330
391
  }));
331
392
  // ── Inspect ──
332
- browser.command('state').description('Page state: URL, title, interactive elements with [N] indices')
393
+ addBrowserTabOption(browser.command('state').description('Page state: URL, title, interactive elements with [N] indices'))
333
394
  .action(browserAction(async (page) => {
334
395
  const snapshot = await page.snapshot({ viewportExpand: 2000 });
335
396
  const url = await page.getCurrentUrl?.() ?? '';
336
397
  console.log(`URL: ${url}\n`);
337
398
  console.log(typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2));
338
399
  }));
339
- browser.command('screenshot').argument('[path]', 'Save to file (base64 if omitted)')
400
+ addBrowserTabOption(browser.command('frames').description('List cross-origin iframe targets in snapshot order'))
401
+ .action(browserAction(async (page) => {
402
+ const frames = await page.frames?.() ?? [];
403
+ console.log(JSON.stringify(frames, null, 2));
404
+ }));
405
+ addBrowserTabOption(browser.command('screenshot').argument('[path]', 'Save to file (base64 if omitted)'))
340
406
  .description('Take screenshot')
341
407
  .action(browserAction(async (page, path) => {
342
408
  if (path) {
@@ -349,45 +415,45 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
349
415
  }));
350
416
  // ── Get commands (structured data extraction) ──
351
417
  const get = browser.command('get').description('Get page properties');
352
- get.command('title').description('Page title')
418
+ addBrowserTabOption(get.command('title').description('Page title'))
353
419
  .action(browserAction(async (page) => {
354
420
  console.log(await page.evaluate('document.title'));
355
421
  }));
356
- get.command('url').description('Current page URL')
422
+ addBrowserTabOption(get.command('url').description('Current page URL'))
357
423
  .action(browserAction(async (page) => {
358
424
  console.log(await page.getCurrentUrl?.() ?? await page.evaluate('location.href'));
359
425
  }));
360
- get.command('text').argument('<index>', 'Element index').description('Element text content')
426
+ addBrowserTabOption(get.command('text').argument('<index>', 'Element index').description('Element text content'))
361
427
  .action(browserAction(async (page, index) => {
362
428
  await resolveRef(page, String(index));
363
429
  const text = await page.evaluate(getTextResolvedJs());
364
430
  console.log(text ?? '(empty)');
365
431
  }));
366
- get.command('value').argument('<index>', 'Element index').description('Input/textarea value')
432
+ addBrowserTabOption(get.command('value').argument('<index>', 'Element index').description('Input/textarea value'))
367
433
  .action(browserAction(async (page, index) => {
368
434
  await resolveRef(page, String(index));
369
435
  const val = await page.evaluate(getValueResolvedJs());
370
436
  console.log(val ?? '(empty)');
371
437
  }));
372
- get.command('html').option('--selector <css>', 'CSS selector scope').description('Page HTML (or scoped)')
438
+ addBrowserTabOption(get.command('html').option('--selector <css>', 'CSS selector scope').description('Page HTML (or scoped)'))
373
439
  .action(browserAction(async (page, opts) => {
374
440
  const sel = opts.selector ? JSON.stringify(opts.selector) : 'null';
375
441
  const html = await page.evaluate(`(${sel} ? document.querySelector(${sel})?.outerHTML : document.documentElement.outerHTML)?.slice(0, 50000)`);
376
442
  console.log(html ?? '(empty)');
377
443
  }));
378
- get.command('attributes').argument('<index>', 'Element index').description('Element attributes')
444
+ addBrowserTabOption(get.command('attributes').argument('<index>', 'Element index').description('Element attributes'))
379
445
  .action(browserAction(async (page, index) => {
380
446
  await resolveRef(page, String(index));
381
447
  const attrs = await page.evaluate(getAttributesResolvedJs());
382
448
  console.log(attrs ?? '{}');
383
449
  }));
384
450
  // ── Interact ──
385
- browser.command('click').argument('<index>', 'Element index from state').description('Click element by index')
451
+ addBrowserTabOption(browser.command('click').argument('<index>', 'Element index from state').description('Click element by index'))
386
452
  .action(browserAction(async (page, index) => {
387
453
  await page.click(index);
388
454
  console.log(`Clicked element [${index}]`);
389
455
  }));
390
- browser.command('type').argument('<index>', 'Element index').argument('<text>', 'Text to type')
456
+ addBrowserTabOption(browser.command('type').argument('<index>', 'Element index').argument('<text>', 'Text to type'))
391
457
  .description('Click element, then type text')
392
458
  .action(browserAction(async (page, index, text) => {
393
459
  await page.click(index);
@@ -404,7 +470,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
404
470
  console.log(`Typed "${text}" into element [${index}]`);
405
471
  }
406
472
  }));
407
- browser.command('select').argument('<index>', 'Element index of <select>').argument('<option>', 'Option text')
473
+ addBrowserTabOption(browser.command('select').argument('<index>', 'Element index of <select>').argument('<option>', 'Option text'))
408
474
  .description('Select dropdown option')
409
475
  .action(browserAction(async (page, index, option) => {
410
476
  await resolveRef(page, String(index));
@@ -417,14 +483,14 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
417
483
  console.log(`Selected "${result?.selected}" in element [${index}]`);
418
484
  }
419
485
  }));
420
- browser.command('keys').argument('<key>', 'Key to press (Enter, Escape, Tab, Control+a)')
486
+ addBrowserTabOption(browser.command('keys').argument('<key>', 'Key to press (Enter, Escape, Tab, Control+a)'))
421
487
  .description('Press keyboard key')
422
488
  .action(browserAction(async (page, key) => {
423
489
  await page.pressKey(key);
424
490
  console.log(`Pressed: ${key}`);
425
491
  }));
426
492
  // ── Wait commands ──
427
- browser.command('wait')
493
+ addBrowserTabOption(browser.command('wait'))
428
494
  .argument('<type>', 'selector, text, or time')
429
495
  .argument('[value]', 'CSS selector, text string, or seconds')
430
496
  .option('--timeout <ms>', 'Timeout in milliseconds', '10000')
@@ -460,16 +526,34 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
460
526
  }
461
527
  }));
462
528
  // ── Extract ──
463
- browser.command('eval').argument('<js>', 'JavaScript code').description('Execute JS in page context, return result')
464
- .action(browserAction(async (page, js) => {
465
- const result = await page.evaluate(js);
529
+ addBrowserTabOption(browser.command('eval')
530
+ .argument('<js>', 'JavaScript code')
531
+ .option('--frame <index>', 'Cross-origin iframe index from "browser frames"')
532
+ .description('Execute JS in page context, return result'))
533
+ .action(browserAction(async (page, js, opts) => {
534
+ let result;
535
+ if (opts.frame !== undefined) {
536
+ const frameIndex = Number.parseInt(opts.frame, 10);
537
+ if (!Number.isInteger(frameIndex) || frameIndex < 0) {
538
+ console.error(`Invalid frame index "${opts.frame}". Use a 0-based index from "browser frames".`);
539
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
540
+ return;
541
+ }
542
+ if (!page.evaluateInFrame) {
543
+ throw new Error('This browser session does not support frame-targeted evaluation');
544
+ }
545
+ result = await page.evaluateInFrame(js, frameIndex);
546
+ }
547
+ else {
548
+ result = await page.evaluate(js);
549
+ }
466
550
  if (typeof result === 'string')
467
551
  console.log(result);
468
552
  else
469
553
  console.log(JSON.stringify(result, null, 2));
470
554
  }));
471
555
  // ── Network (API discovery) ──
472
- browser.command('network')
556
+ addBrowserTabOption(browser.command('network'))
473
557
  .option('--detail <index>', 'Show full response body of request at index')
474
558
  .option('--all', 'Show all requests including static resources')
475
559
  .description('Show captured network requests (auto-captured since last open)')
@@ -1071,10 +1155,14 @@ cli({
1071
1155
  .command('serve')
1072
1156
  .description('Start Anthropic-compatible API proxy for Antigravity')
1073
1157
  .option('--port <port>', 'Server port (default: 8082)', '8082')
1158
+ .option('--timeout <seconds>', 'Maximum time to wait for a reply (default: 120s)')
1074
1159
  .action(async (opts) => {
1075
1160
  // @ts-expect-error JS adapter — no type declarations
1076
1161
  const { startServe } = await import('../clis/antigravity/serve.js');
1077
- await startServe({ port: parseInt(opts.port) });
1162
+ await startServe({
1163
+ port: parseInt(opts.port, 10),
1164
+ timeout: opts.timeout ? parsePositiveIntOption(opts.timeout, '--timeout', 120) : undefined,
1165
+ });
1078
1166
  });
1079
1167
  // ── Dynamic adapter commands ──────────────────────────────────────────────
1080
1168
  const siteGroups = new Map();
@@ -1132,14 +1220,3 @@ export function resolveBrowserVerifyInvocation(opts = {}) {
1132
1220
  ...(platform === 'win32' ? { shell: true } : {}),
1133
1221
  };
1134
1222
  }
1135
- /** Infer a workspace-friendly hostname from a URL, with site override. */
1136
- function inferHost(url, site) {
1137
- if (site)
1138
- return site;
1139
- try {
1140
- return new URL(url).host;
1141
- }
1142
- catch {
1143
- return 'default';
1144
- }
1145
- }