@jackwener/opencli 1.0.1 → 1.0.4

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 (253) hide show
  1. package/.github/workflows/build-extension.yml +80 -0
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/docs.yml +52 -0
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +2 -5
  7. package/.github/workflows/security.yml +2 -2
  8. package/CDP.md +1 -1
  9. package/CDP.zh-CN.md +1 -1
  10. package/README.md +42 -34
  11. package/README.zh-CN.md +42 -34
  12. package/SKILL.md +3 -5
  13. package/dist/browser/cdp.d.ts +42 -0
  14. package/dist/browser/cdp.js +339 -0
  15. package/dist/browser/daemon-client.d.ts +3 -1
  16. package/dist/browser/daemon-client.js +4 -0
  17. package/dist/browser/dom-helpers.d.ts +20 -0
  18. package/dist/browser/dom-helpers.js +109 -0
  19. package/dist/browser/index.d.ts +3 -0
  20. package/dist/browser/index.js +4 -0
  21. package/dist/browser/mcp.d.ts +1 -0
  22. package/dist/browser/mcp.js +10 -5
  23. package/dist/browser/page.d.ts +7 -0
  24. package/dist/browser/page.js +39 -123
  25. package/dist/browser/utils.d.ts +10 -0
  26. package/dist/browser/utils.js +27 -0
  27. package/dist/browser.test.js +49 -1
  28. package/dist/build-manifest.js +3 -1
  29. package/dist/build-manifest.test.js +34 -0
  30. package/dist/capabilityRouting.d.ts +2 -0
  31. package/dist/capabilityRouting.js +30 -0
  32. package/dist/capabilityRouting.test.d.ts +1 -0
  33. package/dist/capabilityRouting.test.js +42 -0
  34. package/dist/chaoxing.d.ts +58 -0
  35. package/dist/chaoxing.js +225 -0
  36. package/dist/chaoxing.test.d.ts +1 -0
  37. package/dist/chaoxing.test.js +45 -0
  38. package/dist/cli-manifest.json +885 -48
  39. package/dist/cli.d.ts +1 -0
  40. package/dist/cli.js +234 -0
  41. package/dist/clis/antigravity/serve.d.ts +14 -0
  42. package/dist/clis/antigravity/serve.js +263 -0
  43. package/dist/clis/bilibili/download.js +4 -14
  44. package/dist/clis/boss/chatlist.d.ts +1 -0
  45. package/dist/clis/boss/chatlist.js +50 -0
  46. package/dist/clis/boss/chatmsg.d.ts +1 -0
  47. package/dist/clis/boss/chatmsg.js +73 -0
  48. package/dist/clis/boss/resume.d.ts +1 -0
  49. package/dist/clis/boss/resume.js +249 -0
  50. package/dist/clis/boss/send.d.ts +1 -0
  51. package/dist/clis/boss/send.js +176 -0
  52. package/dist/clis/chaoxing/assignments.d.ts +1 -0
  53. package/dist/clis/chaoxing/assignments.js +74 -0
  54. package/dist/clis/chaoxing/exams.d.ts +1 -0
  55. package/dist/clis/chaoxing/exams.js +74 -0
  56. package/dist/clis/chatgpt/ask.js +15 -14
  57. package/dist/clis/chatgpt/ax.d.ts +1 -0
  58. package/dist/clis/chatgpt/ax.js +78 -0
  59. package/dist/clis/chatgpt/read.js +5 -6
  60. package/dist/clis/hf/top.d.ts +1 -0
  61. package/dist/clis/hf/top.js +119 -0
  62. package/dist/clis/jike/comment.d.ts +1 -0
  63. package/dist/clis/jike/comment.js +107 -0
  64. package/dist/clis/jike/create.d.ts +1 -0
  65. package/dist/clis/jike/create.js +106 -0
  66. package/dist/clis/jike/feed.d.ts +1 -0
  67. package/dist/clis/jike/feed.js +67 -0
  68. package/dist/clis/jike/like.d.ts +1 -0
  69. package/dist/clis/jike/like.js +61 -0
  70. package/dist/clis/jike/notifications.d.ts +1 -0
  71. package/dist/clis/jike/notifications.js +169 -0
  72. package/dist/clis/jike/post.yaml +58 -0
  73. package/dist/clis/jike/repost.d.ts +1 -0
  74. package/dist/clis/jike/repost.js +103 -0
  75. package/dist/clis/jike/search.d.ts +1 -0
  76. package/dist/clis/jike/search.js +67 -0
  77. package/dist/clis/jike/shared.d.ts +19 -0
  78. package/dist/clis/jike/shared.js +25 -0
  79. package/dist/clis/jike/topic.yaml +52 -0
  80. package/dist/clis/jike/user.yaml +51 -0
  81. package/dist/clis/smzdm/search.js +28 -39
  82. package/dist/clis/stackoverflow/bounties.yaml +29 -0
  83. package/dist/clis/stackoverflow/hot.yaml +28 -0
  84. package/dist/clis/stackoverflow/search.yaml +32 -0
  85. package/dist/clis/stackoverflow/unanswered.yaml +28 -0
  86. package/dist/clis/twitter/download.js +6 -16
  87. package/dist/clis/twitter/post.js +9 -2
  88. package/dist/clis/twitter/search.js +14 -33
  89. package/dist/clis/xiaohongshu/download.d.ts +1 -1
  90. package/dist/clis/xiaohongshu/download.js +4 -4
  91. package/dist/clis/zhihu/download.js +3 -3
  92. package/dist/doctor.d.ts +7 -0
  93. package/dist/doctor.js +16 -0
  94. package/dist/download/index.d.ts +12 -8
  95. package/dist/download/index.js +11 -3
  96. package/dist/download/index.test.d.ts +1 -0
  97. package/dist/download/index.test.js +14 -0
  98. package/dist/engine.js +25 -14
  99. package/dist/explore.d.ts +1 -0
  100. package/dist/explore.js +48 -103
  101. package/dist/generate.js +1 -0
  102. package/dist/interceptor.js +3 -2
  103. package/dist/main.js +4 -193
  104. package/dist/output.d.ts +2 -1
  105. package/dist/output.js +3 -1
  106. package/dist/pipeline/executor.test.js +1 -0
  107. package/dist/pipeline/steps/download.js +14 -18
  108. package/dist/registry.d.ts +4 -3
  109. package/dist/registry.js +5 -2
  110. package/dist/runtime.d.ts +4 -1
  111. package/dist/runtime.js +2 -2
  112. package/dist/scripts/framework.d.ts +4 -0
  113. package/dist/scripts/framework.js +21 -0
  114. package/dist/scripts/interact.d.ts +4 -0
  115. package/dist/scripts/interact.js +20 -0
  116. package/dist/scripts/store.d.ts +9 -0
  117. package/dist/scripts/store.js +44 -0
  118. package/dist/synthesize.js +1 -1
  119. package/dist/types.d.ts +12 -0
  120. package/dist/verify.d.ts +6 -1
  121. package/dist/verify.js +54 -2
  122. package/docs/.vitepress/config.mts +193 -0
  123. package/docs/adapters/browser/apple-podcasts.md +28 -0
  124. package/docs/adapters/browser/bbc.md +26 -0
  125. package/docs/adapters/browser/bilibili.md +38 -0
  126. package/docs/adapters/browser/boss.md +28 -0
  127. package/docs/adapters/browser/coupang.md +28 -0
  128. package/docs/adapters/browser/ctrip.md +27 -0
  129. package/docs/adapters/browser/github.md +26 -0
  130. package/docs/adapters/browser/hackernews.md +26 -0
  131. package/docs/adapters/browser/linkedin.md +27 -0
  132. package/docs/adapters/browser/reddit.md +41 -0
  133. package/docs/adapters/browser/reuters.md +27 -0
  134. package/docs/adapters/browser/smzdm.md +27 -0
  135. package/docs/adapters/browser/twitter.md +47 -0
  136. package/docs/adapters/browser/v2ex.md +32 -0
  137. package/docs/adapters/browser/weibo.md +27 -0
  138. package/docs/adapters/browser/xiaohongshu.md +32 -0
  139. package/docs/adapters/browser/xiaoyuzhou.md +28 -0
  140. package/docs/adapters/browser/xueqiu.md +32 -0
  141. package/docs/adapters/browser/yahoo-finance.md +26 -0
  142. package/docs/adapters/browser/youtube.md +29 -0
  143. package/docs/adapters/browser/zhihu.md +30 -0
  144. package/docs/adapters/desktop/antigravity.md +46 -0
  145. package/docs/adapters/desktop/chatgpt.md +43 -0
  146. package/docs/adapters/desktop/chatwise.md +38 -0
  147. package/docs/adapters/desktop/codex.md +32 -0
  148. package/docs/adapters/desktop/cursor.md +33 -0
  149. package/docs/adapters/desktop/discord.md +28 -0
  150. package/docs/adapters/desktop/feishu.md +20 -0
  151. package/docs/adapters/desktop/neteasemusic.md +31 -0
  152. package/docs/adapters/desktop/notion.md +29 -0
  153. package/docs/adapters/desktop/wechat.md +28 -0
  154. package/docs/adapters/index.md +49 -0
  155. package/docs/advanced/cdp.md +103 -0
  156. package/docs/advanced/download.md +63 -0
  157. package/docs/advanced/electron.md +125 -0
  158. package/docs/advanced/remote-chrome.md +72 -0
  159. package/docs/developer/ai-workflow.md +66 -0
  160. package/docs/developer/architecture.md +90 -0
  161. package/docs/developer/contributing.md +136 -0
  162. package/docs/developer/testing.md +237 -0
  163. package/docs/developer/ts-adapter.md +87 -0
  164. package/docs/developer/yaml-adapter.md +108 -0
  165. package/docs/guide/browser-bridge.md +38 -0
  166. package/docs/guide/getting-started.md +56 -0
  167. package/docs/guide/installation.md +37 -0
  168. package/docs/guide/troubleshooting.md +56 -0
  169. package/docs/index.md +35 -0
  170. package/docs/zh/adapters/index.md +5 -0
  171. package/docs/zh/advanced/cdp.md +3 -0
  172. package/docs/zh/developer/contributing.md +24 -0
  173. package/docs/zh/guide/browser-bridge.md +25 -0
  174. package/docs/zh/guide/getting-started.md +40 -0
  175. package/docs/zh/guide/installation.md +37 -0
  176. package/docs/zh/index.md +29 -0
  177. package/extension/dist/background.js +386 -438
  178. package/extension/manifest.json +2 -2
  179. package/extension/package-lock.json +1156 -0
  180. package/extension/src/background.test.ts +151 -0
  181. package/extension/src/background.ts +124 -53
  182. package/extension/src/protocol.ts +3 -1
  183. package/package.json +7 -3
  184. package/src/browser/cdp.ts +367 -0
  185. package/src/browser/daemon-client.ts +7 -1
  186. package/src/browser/dom-helpers.ts +116 -0
  187. package/src/browser/index.ts +4 -0
  188. package/src/browser/mcp.ts +14 -6
  189. package/src/browser/page.ts +47 -124
  190. package/src/browser/utils.ts +27 -0
  191. package/src/browser.test.ts +56 -0
  192. package/src/build-manifest.test.ts +36 -0
  193. package/src/build-manifest.ts +2 -1
  194. package/src/capabilityRouting.test.ts +47 -0
  195. package/src/capabilityRouting.ts +28 -0
  196. package/src/chaoxing.test.ts +53 -0
  197. package/src/chaoxing.ts +268 -0
  198. package/src/cli.ts +205 -0
  199. package/src/clis/antigravity/SKILL.md +5 -0
  200. package/src/clis/antigravity/serve.ts +329 -0
  201. package/src/clis/bilibili/download.ts +4 -15
  202. package/src/clis/boss/chatlist.ts +50 -0
  203. package/src/clis/boss/chatmsg.ts +70 -0
  204. package/src/clis/boss/resume.ts +262 -0
  205. package/src/clis/boss/send.ts +193 -0
  206. package/src/clis/chaoxing/README.md +36 -0
  207. package/src/clis/chaoxing/README.zh-CN.md +35 -0
  208. package/src/clis/chaoxing/assignments.ts +88 -0
  209. package/src/clis/chaoxing/exams.ts +88 -0
  210. package/src/clis/chatgpt/ask.ts +14 -15
  211. package/src/clis/chatgpt/ax.ts +81 -0
  212. package/src/clis/chatgpt/read.ts +5 -7
  213. package/src/clis/hf/top.ts +141 -0
  214. package/src/clis/jike/comment.ts +113 -0
  215. package/src/clis/jike/create.ts +113 -0
  216. package/src/clis/jike/feed.ts +74 -0
  217. package/src/clis/jike/like.ts +65 -0
  218. package/src/clis/jike/notifications.ts +185 -0
  219. package/src/clis/jike/post.yaml +58 -0
  220. package/src/clis/jike/repost.ts +114 -0
  221. package/src/clis/jike/search.ts +74 -0
  222. package/src/clis/jike/shared.ts +36 -0
  223. package/src/clis/jike/topic.yaml +52 -0
  224. package/src/clis/jike/user.yaml +51 -0
  225. package/src/clis/smzdm/search.ts +30 -39
  226. package/src/clis/stackoverflow/bounties.yaml +29 -0
  227. package/src/clis/stackoverflow/hot.yaml +28 -0
  228. package/src/clis/stackoverflow/search.yaml +32 -0
  229. package/src/clis/stackoverflow/unanswered.yaml +28 -0
  230. package/src/clis/twitter/download.ts +6 -17
  231. package/src/clis/twitter/post.ts +9 -2
  232. package/src/clis/twitter/search.ts +15 -33
  233. package/src/clis/xiaohongshu/download.ts +4 -4
  234. package/src/clis/zhihu/download.ts +3 -3
  235. package/src/doctor.ts +18 -2
  236. package/src/download/index.test.ts +16 -0
  237. package/src/download/index.ts +22 -4
  238. package/src/engine.ts +20 -13
  239. package/src/explore.ts +54 -103
  240. package/src/generate.ts +1 -0
  241. package/src/interceptor.ts +3 -2
  242. package/src/main.ts +4 -180
  243. package/src/output.ts +15 -13
  244. package/src/pipeline/executor.test.ts +1 -0
  245. package/src/pipeline/steps/download.ts +14 -17
  246. package/src/registry.ts +9 -5
  247. package/src/runtime.ts +3 -2
  248. package/src/scripts/framework.ts +20 -0
  249. package/src/scripts/interact.ts +22 -0
  250. package/src/scripts/store.ts +40 -0
  251. package/src/synthesize.ts +1 -1
  252. package/src/types.ts +9 -0
  253. package/src/verify.ts +64 -3
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void;
package/dist/cli.js ADDED
@@ -0,0 +1,234 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { executeCommand } from './engine.js';
4
+ import { Strategy, fullName, getRegistry, strategyLabel } from './registry.js';
5
+ import { render as renderOutput } from './output.js';
6
+ import { BrowserBridge, CDPBridge } from './browser/index.js';
7
+ import { browserSession, DEFAULT_BROWSER_COMMAND_TIMEOUT, runWithTimeout } from './runtime.js';
8
+ import { PKG_VERSION } from './version.js';
9
+ import { printCompletionScript } from './completion.js';
10
+ import { CliError } from './errors.js';
11
+ import { shouldUseBrowserSession } from './capabilityRouting.js';
12
+ export function runCli(BUILTIN_CLIS, USER_CLIS) {
13
+ const program = new Command();
14
+ program.name('opencli').description('Make any website your CLI. Zero setup. AI-powered.').version(PKG_VERSION);
15
+ // ── Built-in commands ──────────────────────────────────────────────────────
16
+ program.command('list').description('List all available CLI commands').option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('--json', 'JSON output (deprecated)')
17
+ .action((opts) => {
18
+ const registry = getRegistry();
19
+ const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
20
+ const rows = commands.map(c => ({
21
+ command: fullName(c),
22
+ site: c.site,
23
+ name: c.name,
24
+ description: c.description,
25
+ strategy: strategyLabel(c),
26
+ browser: c.browser,
27
+ args: c.args.map(a => a.name).join(', '),
28
+ }));
29
+ const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
30
+ if (fmt !== 'table') {
31
+ renderOutput(rows, {
32
+ fmt,
33
+ columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args'],
34
+ title: 'opencli/list',
35
+ source: 'opencli list',
36
+ });
37
+ return;
38
+ }
39
+ const sites = new Map();
40
+ for (const cmd of commands) {
41
+ const g = sites.get(cmd.site) ?? [];
42
+ g.push(cmd);
43
+ sites.set(cmd.site, g);
44
+ }
45
+ console.log();
46
+ console.log(chalk.bold(' opencli') + chalk.dim(' — available commands'));
47
+ console.log();
48
+ for (const [site, cmds] of sites) {
49
+ console.log(chalk.bold.cyan(` ${site}`));
50
+ for (const cmd of cmds) {
51
+ const tag = strategyLabel(cmd) === 'public' ? chalk.green('[public]') : chalk.yellow(`[${strategyLabel(cmd)}]`);
52
+ console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
53
+ }
54
+ console.log();
55
+ }
56
+ console.log(chalk.dim(` ${commands.length} commands across ${sites.size} sites`));
57
+ console.log();
58
+ });
59
+ program.command('validate').description('Validate CLI definitions').argument('[target]', 'site or site/name')
60
+ .action(async (target) => {
61
+ const { validateClisWithTarget, renderValidationReport } = await import('./validate.js');
62
+ console.log(renderValidationReport(validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target)));
63
+ });
64
+ program.command('verify').description('Validate + smoke test').argument('[target]').option('--smoke', 'Run smoke tests', false)
65
+ .action(async (target, opts) => {
66
+ const { verifyClis, renderVerifyReport } = await import('./verify.js');
67
+ const r = await verifyClis({ builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, target, smoke: opts.smoke });
68
+ console.log(renderVerifyReport(r));
69
+ process.exitCode = r.ok ? 0 : 1;
70
+ });
71
+ program.command('explore').alias('probe').description('Explore a website: discover APIs, stores, and recommend strategies').argument('<url>').option('--site <name>').option('--goal <text>').option('--wait <s>', '', '3').option('--auto', 'Enable interactive fuzzing (simulate clicks to trigger lazy APIs)').option('--click <labels>', 'Comma-separated labels to click before fuzzing (e.g. "字幕,CC,评论")')
72
+ .action(async (url, opts) => { const { exploreUrl, renderExploreSummary } = await import('./explore.js'); const clickLabels = opts.click ? opts.click.split(',').map((s) => s.trim()) : undefined; const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; const workspace = `explore:${opts.site ?? (() => { try {
73
+ return new URL(url).host;
74
+ }
75
+ catch {
76
+ return 'default';
77
+ } })()}`; console.log(renderExploreSummary(await exploreUrl(url, { BrowserFactory: BrowserFactory, site: opts.site, goal: opts.goal, waitSeconds: parseFloat(opts.wait), auto: opts.auto, clickLabels, workspace }))); });
78
+ program.command('synthesize').description('Synthesize CLIs from explore').argument('<target>').option('--top <n>', '', '3')
79
+ .action(async (target, opts) => { const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js'); console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) }))); });
80
+ program.command('generate').description('One-shot: explore → synthesize → register').argument('<url>').option('--goal <text>').option('--site <name>')
81
+ .action(async (url, opts) => { const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js'); const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; const workspace = `generate:${opts.site ?? (() => { try {
82
+ return new URL(url).host;
83
+ }
84
+ catch {
85
+ return 'default';
86
+ } })()}`; const r = await generateCliFromUrl({ url, BrowserFactory: BrowserFactory, builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, goal: opts.goal, site: opts.site, workspace }); console.log(renderGenerateSummary(r)); process.exitCode = r.ok ? 0 : 1; });
87
+ program.command('cascade').description('Strategy cascade: find simplest working strategy').argument('<url>').option('--site <name>')
88
+ .action(async (url, opts) => {
89
+ const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
90
+ const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
91
+ const result = await browserSession(BrowserFactory, async (page) => {
92
+ // Navigate to the site first for cookie context
93
+ try {
94
+ const siteUrl = new URL(url);
95
+ await page.goto(`${siteUrl.protocol}//${siteUrl.host}`);
96
+ await page.wait(2);
97
+ }
98
+ catch { }
99
+ return cascadeProbe(page, url);
100
+ }, { workspace: `cascade:${opts.site ?? (() => { try {
101
+ return new URL(url).host;
102
+ }
103
+ catch {
104
+ return 'default';
105
+ } })()}` });
106
+ console.log(renderCascadeResult(result));
107
+ });
108
+ program.command('doctor')
109
+ .description('Diagnose opencli browser bridge connectivity')
110
+ .option('--live', 'Test browser connectivity (requires Chrome running)', false)
111
+ .option('--sessions', 'Show active automation sessions', false)
112
+ .action(async (opts) => {
113
+ const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
114
+ const report = await runBrowserDoctor({ live: opts.live, sessions: opts.sessions, cliVersion: PKG_VERSION });
115
+ console.log(renderBrowserDoctorReport(report));
116
+ });
117
+ program.command('setup')
118
+ .description('Interactive setup: verify browser bridge connectivity')
119
+ .action(async () => {
120
+ const { runSetup } = await import('./setup.js');
121
+ await runSetup({ cliVersion: PKG_VERSION });
122
+ });
123
+ program.command('completion')
124
+ .description('Output shell completion script')
125
+ .argument('<shell>', 'Shell type: bash, zsh, or fish')
126
+ .action((shell) => {
127
+ printCompletionScript(shell);
128
+ });
129
+ // ── Antigravity serve (built-in, long-running) ──────────────────────────────
130
+ const antigravityCmd = program.command('antigravity').description('antigravity commands');
131
+ antigravityCmd.command('serve')
132
+ .description('Start Anthropic-compatible API proxy for Antigravity')
133
+ .option('--port <port>', 'Server port (default: 8082)', '8082')
134
+ .action(async (opts) => {
135
+ const { startServe } = await import('./clis/antigravity/serve.js');
136
+ await startServe({ port: parseInt(opts.port) });
137
+ });
138
+ // ── Dynamic site commands ──────────────────────────────────────────────────
139
+ const registry = getRegistry();
140
+ const siteGroups = new Map();
141
+ // Pre-seed with the antigravity command registered above to avoid duplicates
142
+ siteGroups.set('antigravity', antigravityCmd);
143
+ for (const [, cmd] of registry) {
144
+ let siteCmd = siteGroups.get(cmd.site);
145
+ if (!siteCmd) {
146
+ siteCmd = program.command(cmd.site).description(`${cmd.site} commands`);
147
+ siteGroups.set(cmd.site, siteCmd);
148
+ }
149
+ const subCmd = siteCmd.command(cmd.name).description(cmd.description);
150
+ // Register positional args first, then named options
151
+ const positionalArgs = [];
152
+ for (const arg of cmd.args) {
153
+ if (arg.positional) {
154
+ const bracket = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
155
+ subCmd.argument(bracket, arg.help ?? '');
156
+ positionalArgs.push(arg);
157
+ }
158
+ else {
159
+ const flag = arg.required ? `--${arg.name} <value>` : `--${arg.name} [value]`;
160
+ if (arg.required)
161
+ subCmd.requiredOption(flag, arg.help ?? '');
162
+ else if (arg.default != null)
163
+ subCmd.option(flag, arg.help ?? '', String(arg.default));
164
+ else
165
+ subCmd.option(flag, arg.help ?? '');
166
+ }
167
+ }
168
+ subCmd.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('-v, --verbose', 'Debug output', false);
169
+ subCmd.action(async (...actionArgs) => {
170
+ // Commander passes positional args first, then options object, then the Command
171
+ const actionOpts = actionArgs[positionalArgs.length] ?? {};
172
+ const startTime = Date.now();
173
+ const kwargs = {};
174
+ // Collect positional args
175
+ for (let i = 0; i < positionalArgs.length; i++) {
176
+ const arg = positionalArgs[i];
177
+ const v = actionArgs[i];
178
+ if (v !== undefined)
179
+ kwargs[arg.name] = v;
180
+ }
181
+ // Collect named options
182
+ for (const arg of cmd.args) {
183
+ if (arg.positional)
184
+ continue;
185
+ const camelName = arg.name.replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
186
+ const v = actionOpts[arg.name] ?? actionOpts[camelName];
187
+ if (v !== undefined)
188
+ kwargs[arg.name] = v;
189
+ }
190
+ try {
191
+ if (actionOpts.verbose)
192
+ process.env.OPENCLI_VERBOSE = '1';
193
+ let result;
194
+ if (shouldUseBrowserSession(cmd)) {
195
+ const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
196
+ result = await browserSession(BrowserFactory, async (page) => {
197
+ // Cookie/header strategies require same-origin context for credentialed fetch.
198
+ if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
199
+ try {
200
+ await page.goto(`https://${cmd.domain}`);
201
+ await page.wait(2);
202
+ }
203
+ catch { }
204
+ }
205
+ return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
206
+ }, { workspace: `site:${cmd.site}` });
207
+ }
208
+ else {
209
+ result = await executeCommand(cmd, null, kwargs, actionOpts.verbose);
210
+ }
211
+ if (actionOpts.verbose && (!result || (Array.isArray(result) && result.length === 0))) {
212
+ console.error(chalk.yellow(`[Verbose] Warning: Command returned an empty result. If the website structural API changed or requires authentication, check the network or update the adapter.`));
213
+ }
214
+ const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
215
+ renderOutput(result, { fmt: actionOpts.format, columns: resolved.columns, title: `${resolved.site}/${resolved.name}`, elapsed: (Date.now() - startTime) / 1000, source: fullName(resolved), footerExtra: resolved.footerExtra?.(kwargs) });
216
+ }
217
+ catch (err) {
218
+ if (err instanceof CliError) {
219
+ console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
220
+ if (err.hint)
221
+ console.error(chalk.yellow(`Hint: ${err.hint}`));
222
+ }
223
+ else if (actionOpts.verbose && err.stack) {
224
+ console.error(chalk.red(err.stack));
225
+ }
226
+ else {
227
+ console.error(chalk.red(`Error: ${err.message ?? err}`));
228
+ }
229
+ process.exitCode = 1;
230
+ }
231
+ });
232
+ }
233
+ program.parse();
234
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * antigravity serve — Anthropic-compatible `/v1/messages` proxy server.
3
+ *
4
+ * Starts an HTTP server that accepts Anthropic Messages API requests,
5
+ * forwards them to a running Antigravity app via CDP, polls for the response,
6
+ * and returns it in Anthropic format.
7
+ *
8
+ * Usage:
9
+ * OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve --port 8082
10
+ * ANTHROPIC_BASE_URL=http://localhost:8082 claude
11
+ */
12
+ export declare function startServe(opts?: {
13
+ port?: number;
14
+ }): Promise<void>;
@@ -0,0 +1,263 @@
1
+ /**
2
+ * antigravity serve — Anthropic-compatible `/v1/messages` proxy server.
3
+ *
4
+ * Starts an HTTP server that accepts Anthropic Messages API requests,
5
+ * forwards them to a running Antigravity app via CDP, polls for the response,
6
+ * and returns it in Anthropic format.
7
+ *
8
+ * Usage:
9
+ * OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve --port 8082
10
+ * ANTHROPIC_BASE_URL=http://localhost:8082 claude
11
+ */
12
+ import { createServer } from 'node:http';
13
+ import { CDPBridge } from '../../browser/cdp.js';
14
+ // ─── Helpers ─────────────────────────────────────────────────────────
15
+ function generateMsgId() {
16
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
17
+ let id = 'msg_';
18
+ for (let i = 0; i < 24; i++)
19
+ id += chars[Math.floor(Math.random() * chars.length)];
20
+ return id;
21
+ }
22
+ function estimateTokens(text) {
23
+ // Rough approximation: ~4 chars per token for English, ~2 for CJK
24
+ return Math.max(1, Math.ceil(text.length / 3));
25
+ }
26
+ function extractTextContent(content) {
27
+ if (typeof content === 'string')
28
+ return content;
29
+ return content
30
+ .filter(b => b.type === 'text' && b.text)
31
+ .map(b => b.text)
32
+ .join('\n');
33
+ }
34
+ function readBody(req) {
35
+ return new Promise((resolve, reject) => {
36
+ const chunks = [];
37
+ req.on('data', (c) => chunks.push(c));
38
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
39
+ req.on('error', reject);
40
+ });
41
+ }
42
+ function jsonResponse(res, status, data) {
43
+ const body = JSON.stringify(data);
44
+ res.writeHead(status, {
45
+ 'Content-Type': 'application/json',
46
+ 'Access-Control-Allow-Origin': '*',
47
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
48
+ 'Access-Control-Allow-Headers': 'Content-Type, x-api-key, anthropic-version, Authorization',
49
+ });
50
+ res.end(body);
51
+ }
52
+ function sleep(ms) {
53
+ return new Promise(resolve => setTimeout(resolve, ms));
54
+ }
55
+ // ─── Antigravity CDP Operations ──────────────────────────────────────
56
+ async function getConversationText(page) {
57
+ const text = await page.evaluate(`
58
+ (() => {
59
+ const container = document.getElementById('conversation');
60
+ return container ? container.innerText : '';
61
+ })()
62
+ `);
63
+ return String(text ?? '');
64
+ }
65
+ async function sendMessage(page, message) {
66
+ await page.evaluate(`
67
+ (async () => {
68
+ const container = document.getElementById('antigravity.agentSidePanelInputBox');
69
+ if (!container) throw new Error('Could not find antigravity.agentSidePanelInputBox');
70
+ const editor = container.querySelector('[data-lexical-editor="true"]');
71
+ if (!editor) throw new Error('Could not find Antigravity input box');
72
+
73
+ editor.focus();
74
+ document.execCommand('insertText', false, ${JSON.stringify(message)});
75
+ })()
76
+ `);
77
+ await sleep(500);
78
+ await page.pressKey('Enter');
79
+ }
80
+ async function waitForReply(page, beforeText, opts = {}) {
81
+ const timeout = opts.timeout ?? 120_000; // 2 minutes max
82
+ const pollInterval = opts.pollInterval ?? 500; // 500ms polling
83
+ const stableThreshold = opts.stableThreshold ?? 6; // 6 × 500ms = 3s stable
84
+ const deadline = Date.now() + timeout;
85
+ let lastText = beforeText;
86
+ let stableCount = 0;
87
+ // Wait a bit for the model to start generating
88
+ await sleep(1000);
89
+ while (Date.now() < deadline) {
90
+ const current = await getConversationText(page);
91
+ if (current.length > beforeText.length) {
92
+ // New content appeared
93
+ if (current === lastText) {
94
+ stableCount++;
95
+ if (stableCount >= stableThreshold) {
96
+ // Text has been stable — reply is complete
97
+ return current.slice(beforeText.length).trim();
98
+ }
99
+ }
100
+ else {
101
+ // Still generating
102
+ stableCount = 0;
103
+ lastText = current;
104
+ }
105
+ }
106
+ await sleep(pollInterval);
107
+ }
108
+ // Timeout — return whatever we have
109
+ const finalText = await getConversationText(page);
110
+ if (finalText.length > beforeText.length) {
111
+ return finalText.slice(beforeText.length).trim();
112
+ }
113
+ throw new Error('Timeout waiting for Antigravity reply');
114
+ }
115
+ // ─── Request Handlers ────────────────────────────────────────────────
116
+ async function handleMessages(body, page) {
117
+ // Extract the last user message
118
+ const userMessages = body.messages.filter(m => m.role === 'user');
119
+ if (userMessages.length === 0) {
120
+ throw new Error('No user message found in request');
121
+ }
122
+ const lastUserMsg = userMessages[userMessages.length - 1];
123
+ const userText = extractTextContent(lastUserMsg.content);
124
+ if (!userText.trim()) {
125
+ throw new Error('Empty user message');
126
+ }
127
+ // Get conversation state before sending
128
+ const beforeText = await getConversationText(page);
129
+ // Send the message
130
+ console.error(`[serve] Sending: "${userText.slice(0, 80)}${userText.length > 80 ? '...' : ''}"`);
131
+ await sendMessage(page, userText);
132
+ // Poll for reply
133
+ console.error('[serve] Waiting for reply...');
134
+ const replyText = await waitForReply(page, beforeText);
135
+ console.error(`[serve] Got reply: "${replyText.slice(0, 80)}${replyText.length > 80 ? '...' : ''}"`);
136
+ return {
137
+ id: generateMsgId(),
138
+ type: 'message',
139
+ role: 'assistant',
140
+ content: [{ type: 'text', text: replyText }],
141
+ model: body.model ?? 'antigravity',
142
+ stop_reason: 'end_turn',
143
+ stop_sequence: null,
144
+ usage: {
145
+ input_tokens: estimateTokens(userText),
146
+ output_tokens: estimateTokens(replyText),
147
+ },
148
+ };
149
+ }
150
+ // ─── Server ──────────────────────────────────────────────────────────
151
+ export async function startServe(opts = {}) {
152
+ const port = opts.port ?? 8082;
153
+ // Establish persistent CDP connection
154
+ console.error('[serve] Connecting to Antigravity via CDP...');
155
+ const cdp = new CDPBridge();
156
+ const page = await cdp.connect({ timeout: 15_000 });
157
+ console.error('[serve] CDP connected successfully.');
158
+ // Verify we can read conversation
159
+ const testText = await getConversationText(page);
160
+ console.error(`[serve] Conversation element found (${testText.length} chars).`);
161
+ let requestInFlight = false;
162
+ const server = createServer(async (req, res) => {
163
+ // CORS preflight
164
+ if (req.method === 'OPTIONS') {
165
+ res.writeHead(204, {
166
+ 'Access-Control-Allow-Origin': '*',
167
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
168
+ 'Access-Control-Allow-Headers': 'Content-Type, x-api-key, anthropic-version, Authorization',
169
+ });
170
+ res.end();
171
+ return;
172
+ }
173
+ const url = req.url ?? '/';
174
+ const pathname = url.split('?')[0];
175
+ try {
176
+ // GET /v1/models — return available models
177
+ if (req.method === 'GET' && pathname === '/v1/models') {
178
+ jsonResponse(res, 200, {
179
+ data: [
180
+ {
181
+ id: 'antigravity',
182
+ object: 'model',
183
+ created: Math.floor(Date.now() / 1000),
184
+ owned_by: 'antigravity',
185
+ },
186
+ ],
187
+ });
188
+ return;
189
+ }
190
+ // POST /v1/messages — main endpoint
191
+ if (req.method === 'POST' && pathname === '/v1/messages') {
192
+ if (requestInFlight) {
193
+ jsonResponse(res, 429, {
194
+ type: 'error',
195
+ error: {
196
+ type: 'rate_limit_error',
197
+ message: 'Another request is currently being processed. Antigravity can only handle one request at a time.',
198
+ },
199
+ });
200
+ return;
201
+ }
202
+ requestInFlight = true;
203
+ try {
204
+ const rawBody = await readBody(req);
205
+ const body = JSON.parse(rawBody);
206
+ if (body.stream) {
207
+ // We don't support streaming — return error
208
+ jsonResponse(res, 400, {
209
+ type: 'error',
210
+ error: {
211
+ type: 'invalid_request_error',
212
+ message: 'Streaming is not supported. Set "stream": false.',
213
+ },
214
+ });
215
+ return;
216
+ }
217
+ const response = await handleMessages(body, page);
218
+ jsonResponse(res, 200, response);
219
+ }
220
+ finally {
221
+ requestInFlight = false;
222
+ }
223
+ return;
224
+ }
225
+ // Health check
226
+ if (req.method === 'GET' && (pathname === '/' || pathname === '/health')) {
227
+ jsonResponse(res, 200, { ok: true, status: 'connected' });
228
+ return;
229
+ }
230
+ jsonResponse(res, 404, {
231
+ type: 'error',
232
+ error: { type: 'not_found_error', message: `Not found: ${pathname}` },
233
+ });
234
+ }
235
+ catch (err) {
236
+ console.error('[serve] Error:', err);
237
+ jsonResponse(res, 500, {
238
+ type: 'error',
239
+ error: {
240
+ type: 'api_error',
241
+ message: err instanceof Error ? err.message : 'Internal server error',
242
+ },
243
+ });
244
+ }
245
+ });
246
+ server.listen(port, '127.0.0.1', () => {
247
+ console.error(`\n[serve] ✅ Antigravity API proxy running at http://127.0.0.1:${port}`);
248
+ console.error(`[serve] Compatible with Anthropic /v1/messages API`);
249
+ console.error(`\n[serve] Usage with Claude Code:`);
250
+ console.error(` ANTHROPIC_BASE_URL=http://localhost:${port} claude\n`);
251
+ });
252
+ // Graceful shutdown
253
+ const shutdown = () => {
254
+ console.error('\n[serve] Shutting down...');
255
+ cdp.close().catch(() => { });
256
+ server.close();
257
+ process.exit(0);
258
+ };
259
+ process.on('SIGTERM', shutdown);
260
+ process.on('SIGINT', shutdown);
261
+ // Keep alive
262
+ await new Promise(() => { });
263
+ }
@@ -10,7 +10,7 @@
10
10
  import * as fs from 'node:fs';
11
11
  import * as path from 'node:path';
12
12
  import { cli, Strategy } from '../../registry.js';
13
- import { ytdlpDownload, checkYtdlp, sanitizeFilename, getTempDir, exportCookiesToNetscape, } from '../../download/index.js';
13
+ import { ytdlpDownload, checkYtdlp, sanitizeFilename, getTempDir, exportCookiesToNetscape, formatCookieHeader, } from '../../download/index.js';
14
14
  import { DownloadProgressTracker, formatBytes } from '../../download/progress.js';
15
15
  cli({
16
16
  site: 'bilibili',
@@ -50,26 +50,16 @@ cli({
50
50
  `);
51
51
  const title = sanitizeFilename(data?.title || 'video');
52
52
  // Extract cookies for authenticated downloads
53
- const cookieString = await page.evaluate(`(() => document.cookie)()`);
53
+ const cookies = await page.getCookies({ domain: 'bilibili.com' });
54
+ const cookieString = formatCookieHeader(cookies);
54
55
  // Create output directory
55
56
  fs.mkdirSync(output, { recursive: true });
56
57
  // Export cookies to Netscape format for yt-dlp
57
58
  let cookiesFile;
58
- if (typeof cookieString === 'string' && cookieString) {
59
+ if (cookies.length > 0) {
59
60
  const tempDir = getTempDir();
60
61
  fs.mkdirSync(tempDir, { recursive: true });
61
62
  cookiesFile = path.join(tempDir, `bilibili_cookies_${Date.now()}.txt`);
62
- const cookies = cookieString.split(';').map((c) => {
63
- const [name, ...rest] = c.trim().split('=');
64
- return {
65
- name: name || '',
66
- value: rest.join('=') || '',
67
- domain: '.bilibili.com',
68
- path: '/',
69
- secure: true,
70
- httpOnly: false,
71
- };
72
- }).filter((c) => c.name);
73
63
  exportCookiesToNetscape(cookies, cookiesFile);
74
64
  }
75
65
  // Build yt-dlp format string based on quality
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ cli({
3
+ site: 'boss',
4
+ name: 'chatlist',
5
+ description: 'BOSS直聘查看聊天列表(招聘端)',
6
+ domain: 'www.zhipin.com',
7
+ strategy: Strategy.COOKIE,
8
+ browser: true,
9
+ args: [
10
+ { name: 'page', type: 'int', default: 1, help: 'Page number' },
11
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
12
+ { name: 'job_id', default: '0', help: 'Filter by job ID (0=all)' },
13
+ ],
14
+ columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
15
+ func: async (page, kwargs) => {
16
+ if (!page)
17
+ throw new Error('Browser page required');
18
+ await page.goto('https://www.zhipin.com/web/chat/index');
19
+ await page.wait({ time: 2 });
20
+ const jobId = kwargs.job_id || '0';
21
+ const pageNum = kwargs.page || 1;
22
+ const limit = kwargs.limit || 20;
23
+ const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
24
+ const data = await page.evaluate(`
25
+ async () => {
26
+ return new Promise((resolve, reject) => {
27
+ const xhr = new XMLHttpRequest();
28
+ xhr.open('GET', '${targetUrl}', true);
29
+ xhr.withCredentials = true;
30
+ xhr.timeout = 15000;
31
+ xhr.setRequestHeader('Accept', 'application/json');
32
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
33
+ xhr.onerror = () => reject(new Error('Network Error'));
34
+ xhr.send();
35
+ });
36
+ }
37
+ `);
38
+ if (data.code !== 0)
39
+ throw new Error(`API error: ${data.message} (code=${data.code})`);
40
+ const friends = (data.zpData?.friendList || []).slice(0, limit);
41
+ return friends.map((f) => ({
42
+ name: f.name || '',
43
+ job: f.jobName || '',
44
+ last_msg: f.lastMessageInfo?.text || '',
45
+ last_time: f.lastTime || '',
46
+ uid: f.encryptUid || '',
47
+ security_id: f.securityId || '',
48
+ }));
49
+ },
50
+ });
@@ -0,0 +1 @@
1
+ export {};