@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.
- package/README.md +71 -49
- package/README.zh-CN.md +73 -60
- package/cli-manifest.json +3261 -1758
- package/clis/antigravity/serve.js +71 -25
- package/clis/baidu-scholar/search.js +87 -0
- package/clis/baidu-scholar/search.test.js +23 -0
- package/clis/deepseek/ask.js +74 -0
- package/clis/deepseek/history.js +25 -0
- package/clis/deepseek/new.js +20 -0
- package/clis/deepseek/read.js +22 -0
- package/clis/deepseek/status.js +24 -0
- package/clis/deepseek/utils.js +208 -0
- package/clis/eastmoney/_secid.js +78 -0
- package/clis/eastmoney/announcement.js +52 -0
- package/clis/eastmoney/convertible.js +73 -0
- package/clis/eastmoney/etf.js +65 -0
- package/clis/eastmoney/holders.js +78 -0
- package/clis/eastmoney/index-board.js +96 -0
- package/clis/eastmoney/kline.js +87 -0
- package/clis/eastmoney/kuaixun.js +54 -0
- package/clis/eastmoney/longhu.js +67 -0
- package/clis/eastmoney/money-flow.js +78 -0
- package/clis/eastmoney/northbound.js +57 -0
- package/clis/eastmoney/quote.js +107 -0
- package/clis/eastmoney/rank.js +94 -0
- package/clis/eastmoney/sectors.js +76 -0
- package/clis/google-scholar/search.js +58 -0
- package/clis/google-scholar/search.test.js +23 -0
- package/clis/gov-law/commands.test.js +39 -0
- package/clis/gov-law/recent.js +22 -0
- package/clis/gov-law/search.js +41 -0
- package/clis/gov-law/shared.js +51 -0
- package/clis/gov-policy/commands.test.js +27 -0
- package/clis/gov-policy/recent.js +47 -0
- package/clis/gov-policy/search.js +48 -0
- package/clis/nowcoder/companies.js +23 -0
- package/clis/nowcoder/creators.js +27 -0
- package/clis/nowcoder/detail.js +61 -0
- package/clis/nowcoder/experience.js +36 -0
- package/clis/nowcoder/hot.js +24 -0
- package/clis/nowcoder/jobs.js +21 -0
- package/clis/nowcoder/notifications.js +29 -0
- package/clis/nowcoder/papers.js +40 -0
- package/clis/nowcoder/practice.js +37 -0
- package/clis/nowcoder/recommend.js +30 -0
- package/clis/nowcoder/referral.js +39 -0
- package/clis/nowcoder/salary.js +40 -0
- package/clis/nowcoder/search.js +49 -0
- package/clis/nowcoder/suggest.js +33 -0
- package/clis/nowcoder/topics.js +27 -0
- package/clis/nowcoder/trending.js +25 -0
- package/clis/twitter/list-add.js +337 -0
- package/clis/twitter/list-add.test.js +15 -0
- package/clis/twitter/list-remove.js +297 -0
- package/clis/twitter/list-remove.test.js +14 -0
- package/clis/twitter/list-tweets.js +185 -0
- package/clis/twitter/list-tweets.test.js +108 -0
- package/clis/twitter/lists.js +134 -47
- package/clis/twitter/lists.test.js +105 -38
- package/clis/wanfang/search.js +66 -0
- package/clis/wanfang/search.test.js +23 -0
- package/clis/web/read.js +1 -1
- package/clis/weixin/download.js +3 -2
- package/clis/xiaohongshu/publish.js +149 -28
- package/clis/xiaohongshu/publish.test.js +319 -6
- package/clis/xiaoyuzhou/download.js +8 -4
- package/clis/xiaoyuzhou/download.test.js +23 -13
- package/clis/xiaoyuzhou/episode.js +9 -4
- package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
- package/clis/xiaoyuzhou/podcast.js +9 -4
- package/clis/xiaoyuzhou/utils.js +0 -40
- package/clis/xiaoyuzhou/utils.test.js +15 -75
- package/clis/zsxq/dynamics.js +1 -1
- package/clis/zsxq/utils.js +6 -3
- package/clis/zsxq/utils.test.js +31 -0
- package/dist/src/browser/base-page.d.ts +1 -1
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +1 -1
- package/dist/src/browser/cdp.js +1 -1
- package/dist/src/browser/daemon-client.d.ts +6 -4
- package/dist/src/browser/daemon-client.js +6 -1
- package/dist/src/browser/daemon-client.test.js +40 -1
- package/dist/src/browser/dom-snapshot.js +7 -2
- package/dist/src/browser/page.d.ts +14 -4
- package/dist/src/browser/page.js +48 -7
- package/dist/src/browser/page.test.js +97 -0
- package/dist/src/cli.js +227 -150
- package/dist/src/cli.test.js +167 -90
- package/dist/src/commanderAdapter.d.ts +0 -1
- package/dist/src/commanderAdapter.js +2 -16
- package/dist/src/commanderAdapter.test.js +1 -1
- package/dist/src/completion-shared.js +2 -5
- package/dist/src/daemon.js +8 -0
- package/dist/src/download/article-download.d.ts +1 -0
- package/dist/src/download/article-download.js +3 -0
- package/dist/src/download/article-download.test.js +39 -0
- package/dist/src/plugin.d.ts +1 -8
- package/dist/src/plugin.js +1 -27
- package/dist/src/plugin.test.js +1 -59
- package/dist/src/registry.d.ts +1 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/registry.test.js +22 -0
- package/dist/src/types.d.ts +14 -5
- package/package.json +1 -1
- package/clis/twitter/lists-parser.js +0 -77
- package/clis/twitter/lists.d.ts +0 -5
- package/dist/src/cascade.d.ts +0 -46
- package/dist/src/cascade.js +0 -135
- package/dist/src/explore.d.ts +0 -99
- package/dist/src/explore.js +0 -402
- package/dist/src/generate-verified.d.ts +0 -105
- package/dist/src/generate-verified.js +0 -696
- package/dist/src/generate-verified.test.js +0 -925
- package/dist/src/generate.d.ts +0 -46
- package/dist/src/generate.js +0 -117
- package/dist/src/record.d.ts +0 -96
- package/dist/src/record.js +0 -657
- package/dist/src/record.test.d.ts +0 -1
- package/dist/src/record.test.js +0 -293
- package/dist/src/skill-generate.d.ts +0 -30
- package/dist/src/skill-generate.js +0 -75
- package/dist/src/skill-generate.test.d.ts +0 -1
- package/dist/src/skill-generate.test.js +0 -173
- package/dist/src/synthesize.d.ts +0 -97
- package/dist/src/synthesize.js +0 -208
- /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
|
-
|
|
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
|
|
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(
|
|
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('
|
|
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')
|
|
464
|
-
.
|
|
465
|
-
|
|
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({
|
|
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
|
-
}
|