@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
@@ -0,0 +1,329 @@
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
+
13
+ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
14
+ import { CDPBridge } from '../../browser/cdp.js';
15
+ import type { IPage } from '../../types.js';
16
+
17
+ // ─── Types ───────────────────────────────────────────────────────────
18
+
19
+ interface AnthropicRequest {
20
+ model?: string;
21
+ max_tokens?: number;
22
+ system?: string | Array<{ type: string; text: string }>;
23
+ messages: Array<{ role: string; content: string | Array<{ type: string; text?: string }> }>;
24
+ stream?: boolean;
25
+ }
26
+
27
+ interface AnthropicResponse {
28
+ id: string;
29
+ type: 'message';
30
+ role: 'assistant';
31
+ content: Array<{ type: 'text'; text: string }>;
32
+ model: string;
33
+ stop_reason: 'end_turn' | 'max_tokens' | 'stop_sequence';
34
+ stop_sequence: null;
35
+ usage: { input_tokens: number; output_tokens: number };
36
+ }
37
+
38
+ // ─── Helpers ─────────────────────────────────────────────────────────
39
+
40
+ function generateMsgId(): string {
41
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
42
+ let id = 'msg_';
43
+ for (let i = 0; i < 24; i++) id += chars[Math.floor(Math.random() * chars.length)];
44
+ return id;
45
+ }
46
+
47
+ function estimateTokens(text: string): number {
48
+ // Rough approximation: ~4 chars per token for English, ~2 for CJK
49
+ return Math.max(1, Math.ceil(text.length / 3));
50
+ }
51
+
52
+ function extractTextContent(content: string | Array<{ type: string; text?: string }>): string {
53
+ if (typeof content === 'string') return content;
54
+ return content
55
+ .filter(b => b.type === 'text' && b.text)
56
+ .map(b => b.text!)
57
+ .join('\n');
58
+ }
59
+
60
+ function readBody(req: IncomingMessage): Promise<string> {
61
+ return new Promise((resolve, reject) => {
62
+ const chunks: Buffer[] = [];
63
+ req.on('data', (c: Buffer) => chunks.push(c));
64
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
65
+ req.on('error', reject);
66
+ });
67
+ }
68
+
69
+ function jsonResponse(res: ServerResponse, status: number, data: unknown): void {
70
+ const body = JSON.stringify(data);
71
+ res.writeHead(status, {
72
+ 'Content-Type': 'application/json',
73
+ 'Access-Control-Allow-Origin': '*',
74
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
75
+ 'Access-Control-Allow-Headers': 'Content-Type, x-api-key, anthropic-version, Authorization',
76
+ });
77
+ res.end(body);
78
+ }
79
+
80
+ function sleep(ms: number): Promise<void> {
81
+ return new Promise(resolve => setTimeout(resolve, ms));
82
+ }
83
+
84
+ // ─── Antigravity CDP Operations ──────────────────────────────────────
85
+
86
+ async function getConversationText(page: IPage): Promise<string> {
87
+ const text = await page.evaluate(`
88
+ (() => {
89
+ const container = document.getElementById('conversation');
90
+ return container ? container.innerText : '';
91
+ })()
92
+ `);
93
+ return String(text ?? '');
94
+ }
95
+
96
+ async function sendMessage(page: IPage, message: string): Promise<void> {
97
+ await page.evaluate(`
98
+ (async () => {
99
+ const container = document.getElementById('antigravity.agentSidePanelInputBox');
100
+ if (!container) throw new Error('Could not find antigravity.agentSidePanelInputBox');
101
+ const editor = container.querySelector('[data-lexical-editor="true"]');
102
+ if (!editor) throw new Error('Could not find Antigravity input box');
103
+
104
+ editor.focus();
105
+ document.execCommand('insertText', false, ${JSON.stringify(message)});
106
+ })()
107
+ `);
108
+ await sleep(500);
109
+ await page.pressKey('Enter');
110
+ }
111
+
112
+ async function waitForReply(
113
+ page: IPage,
114
+ beforeText: string,
115
+ opts: { timeout?: number; pollInterval?: number; stableThreshold?: number } = {},
116
+ ): Promise<string> {
117
+ const timeout = opts.timeout ?? 120_000; // 2 minutes max
118
+ const pollInterval = opts.pollInterval ?? 500; // 500ms polling
119
+ const stableThreshold = opts.stableThreshold ?? 6; // 6 × 500ms = 3s stable
120
+
121
+ const deadline = Date.now() + timeout;
122
+ let lastText = beforeText;
123
+ let stableCount = 0;
124
+
125
+ // Wait a bit for the model to start generating
126
+ await sleep(1000);
127
+
128
+ while (Date.now() < deadline) {
129
+ const current = await getConversationText(page);
130
+
131
+ if (current.length > beforeText.length) {
132
+ // New content appeared
133
+ if (current === lastText) {
134
+ stableCount++;
135
+ if (stableCount >= stableThreshold) {
136
+ // Text has been stable — reply is complete
137
+ return current.slice(beforeText.length).trim();
138
+ }
139
+ } else {
140
+ // Still generating
141
+ stableCount = 0;
142
+ lastText = current;
143
+ }
144
+ }
145
+
146
+ await sleep(pollInterval);
147
+ }
148
+
149
+ // Timeout — return whatever we have
150
+ const finalText = await getConversationText(page);
151
+ if (finalText.length > beforeText.length) {
152
+ return finalText.slice(beforeText.length).trim();
153
+ }
154
+ throw new Error('Timeout waiting for Antigravity reply');
155
+ }
156
+
157
+ // ─── Request Handlers ────────────────────────────────────────────────
158
+
159
+ async function handleMessages(
160
+ body: AnthropicRequest,
161
+ page: IPage,
162
+ ): Promise<AnthropicResponse> {
163
+ // Extract the last user message
164
+ const userMessages = body.messages.filter(m => m.role === 'user');
165
+ if (userMessages.length === 0) {
166
+ throw new Error('No user message found in request');
167
+ }
168
+ const lastUserMsg = userMessages[userMessages.length - 1];
169
+ const userText = extractTextContent(lastUserMsg.content);
170
+
171
+ if (!userText.trim()) {
172
+ throw new Error('Empty user message');
173
+ }
174
+
175
+ // Get conversation state before sending
176
+ const beforeText = await getConversationText(page);
177
+
178
+ // Send the message
179
+ console.error(`[serve] Sending: "${userText.slice(0, 80)}${userText.length > 80 ? '...' : ''}"`);
180
+ await sendMessage(page, userText);
181
+
182
+ // Poll for reply
183
+ console.error('[serve] Waiting for reply...');
184
+ const replyText = await waitForReply(page, beforeText);
185
+ console.error(`[serve] Got reply: "${replyText.slice(0, 80)}${replyText.length > 80 ? '...' : ''}"`);
186
+
187
+ return {
188
+ id: generateMsgId(),
189
+ type: 'message',
190
+ role: 'assistant',
191
+ content: [{ type: 'text', text: replyText }],
192
+ model: body.model ?? 'antigravity',
193
+ stop_reason: 'end_turn',
194
+ stop_sequence: null,
195
+ usage: {
196
+ input_tokens: estimateTokens(userText),
197
+ output_tokens: estimateTokens(replyText),
198
+ },
199
+ };
200
+ }
201
+
202
+ // ─── Server ──────────────────────────────────────────────────────────
203
+
204
+ export async function startServe(opts: { port?: number } = {}): Promise<void> {
205
+ const port = opts.port ?? 8082;
206
+
207
+ // Establish persistent CDP connection
208
+ console.error('[serve] Connecting to Antigravity via CDP...');
209
+ const cdp = new CDPBridge();
210
+ const page = await cdp.connect({ timeout: 15_000 });
211
+ console.error('[serve] CDP connected successfully.');
212
+
213
+ // Verify we can read conversation
214
+ const testText = await getConversationText(page);
215
+ console.error(`[serve] Conversation element found (${testText.length} chars).`);
216
+
217
+ let requestInFlight = false;
218
+
219
+ const server = createServer(async (req, res) => {
220
+ // CORS preflight
221
+ if (req.method === 'OPTIONS') {
222
+ res.writeHead(204, {
223
+ 'Access-Control-Allow-Origin': '*',
224
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
225
+ 'Access-Control-Allow-Headers': 'Content-Type, x-api-key, anthropic-version, Authorization',
226
+ });
227
+ res.end();
228
+ return;
229
+ }
230
+
231
+ const url = req.url ?? '/';
232
+ const pathname = url.split('?')[0];
233
+
234
+ try {
235
+ // GET /v1/models — return available models
236
+ if (req.method === 'GET' && pathname === '/v1/models') {
237
+ jsonResponse(res, 200, {
238
+ data: [
239
+ {
240
+ id: 'antigravity',
241
+ object: 'model',
242
+ created: Math.floor(Date.now() / 1000),
243
+ owned_by: 'antigravity',
244
+ },
245
+ ],
246
+ });
247
+ return;
248
+ }
249
+
250
+ // POST /v1/messages — main endpoint
251
+ if (req.method === 'POST' && pathname === '/v1/messages') {
252
+ if (requestInFlight) {
253
+ jsonResponse(res, 429, {
254
+ type: 'error',
255
+ error: {
256
+ type: 'rate_limit_error',
257
+ message: 'Another request is currently being processed. Antigravity can only handle one request at a time.',
258
+ },
259
+ });
260
+ return;
261
+ }
262
+
263
+ requestInFlight = true;
264
+ try {
265
+ const rawBody = await readBody(req);
266
+ const body = JSON.parse(rawBody) as AnthropicRequest;
267
+
268
+ if (body.stream) {
269
+ // We don't support streaming — return error
270
+ jsonResponse(res, 400, {
271
+ type: 'error',
272
+ error: {
273
+ type: 'invalid_request_error',
274
+ message: 'Streaming is not supported. Set "stream": false.',
275
+ },
276
+ });
277
+ return;
278
+ }
279
+
280
+ const response = await handleMessages(body, page);
281
+ jsonResponse(res, 200, response);
282
+ } finally {
283
+ requestInFlight = false;
284
+ }
285
+ return;
286
+ }
287
+
288
+ // Health check
289
+ if (req.method === 'GET' && (pathname === '/' || pathname === '/health')) {
290
+ jsonResponse(res, 200, { ok: true, status: 'connected' });
291
+ return;
292
+ }
293
+
294
+ jsonResponse(res, 404, {
295
+ type: 'error',
296
+ error: { type: 'not_found_error', message: `Not found: ${pathname}` },
297
+ });
298
+ } catch (err) {
299
+ console.error('[serve] Error:', err);
300
+ jsonResponse(res, 500, {
301
+ type: 'error',
302
+ error: {
303
+ type: 'api_error',
304
+ message: err instanceof Error ? err.message : 'Internal server error',
305
+ },
306
+ });
307
+ }
308
+ });
309
+
310
+ server.listen(port, '127.0.0.1', () => {
311
+ console.error(`\n[serve] ✅ Antigravity API proxy running at http://127.0.0.1:${port}`);
312
+ console.error(`[serve] Compatible with Anthropic /v1/messages API`);
313
+ console.error(`\n[serve] Usage with Claude Code:`);
314
+ console.error(` ANTHROPIC_BASE_URL=http://localhost:${port} claude\n`);
315
+ });
316
+
317
+ // Graceful shutdown
318
+ const shutdown = () => {
319
+ console.error('\n[serve] Shutting down...');
320
+ cdp.close().catch(() => {});
321
+ server.close();
322
+ process.exit(0);
323
+ };
324
+ process.on('SIGTERM', shutdown);
325
+ process.on('SIGINT', shutdown);
326
+
327
+ // Keep alive
328
+ await new Promise(() => {});
329
+ }
@@ -17,6 +17,7 @@ import {
17
17
  sanitizeFilename,
18
18
  getTempDir,
19
19
  exportCookiesToNetscape,
20
+ formatCookieHeader,
20
21
  } from '../../download/index.js';
21
22
  import { DownloadProgressTracker, formatBytes } from '../../download/progress.js';
22
23
 
@@ -63,30 +64,18 @@ cli({
63
64
  const title = sanitizeFilename(data?.title || 'video');
64
65
 
65
66
  // Extract cookies for authenticated downloads
66
- const cookieString = await page.evaluate(`(() => document.cookie)()`);
67
+ const cookies = await page.getCookies({ domain: 'bilibili.com' });
68
+ const cookieString = formatCookieHeader(cookies);
67
69
 
68
70
  // Create output directory
69
71
  fs.mkdirSync(output, { recursive: true });
70
72
 
71
73
  // Export cookies to Netscape format for yt-dlp
72
74
  let cookiesFile: string | undefined;
73
- if (typeof cookieString === 'string' && cookieString) {
75
+ if (cookies.length > 0) {
74
76
  const tempDir = getTempDir();
75
77
  fs.mkdirSync(tempDir, { recursive: true });
76
78
  cookiesFile = path.join(tempDir, `bilibili_cookies_${Date.now()}.txt`);
77
-
78
- const cookies = cookieString.split(';').map((c) => {
79
- const [name, ...rest] = c.trim().split('=');
80
- return {
81
- name: name || '',
82
- value: rest.join('=') || '',
83
- domain: '.bilibili.com',
84
- path: '/',
85
- secure: true,
86
- httpOnly: false,
87
- };
88
- }).filter((c) => c.name);
89
-
90
79
  exportCookiesToNetscape(cookies, cookiesFile);
91
80
  }
92
81
 
@@ -0,0 +1,50 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ cli({
5
+ site: 'boss',
6
+ name: 'chatlist',
7
+ description: 'BOSS直聘查看聊天列表(招聘端)',
8
+ domain: 'www.zhipin.com',
9
+ strategy: Strategy.COOKIE,
10
+ browser: true,
11
+ args: [
12
+ { name: 'page', type: 'int', default: 1, help: 'Page number' },
13
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
14
+ { name: 'job_id', default: '0', help: 'Filter by job ID (0=all)' },
15
+ ],
16
+ columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
17
+ func: async (page: IPage | null, kwargs) => {
18
+ if (!page) throw new Error('Browser page required');
19
+ await page.goto('https://www.zhipin.com/web/chat/index');
20
+ await page.wait({ time: 2 });
21
+ const jobId = kwargs.job_id || '0';
22
+ const pageNum = kwargs.page || 1;
23
+ const limit = kwargs.limit || 20;
24
+ const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
25
+ const data: any = await page.evaluate(`
26
+ async () => {
27
+ return new Promise((resolve, reject) => {
28
+ const xhr = new XMLHttpRequest();
29
+ xhr.open('GET', '${targetUrl}', true);
30
+ xhr.withCredentials = true;
31
+ xhr.timeout = 15000;
32
+ xhr.setRequestHeader('Accept', 'application/json');
33
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
34
+ xhr.onerror = () => reject(new Error('Network Error'));
35
+ xhr.send();
36
+ });
37
+ }
38
+ `);
39
+ if (data.code !== 0) throw new Error(`API error: ${data.message} (code=${data.code})`);
40
+ const friends = (data.zpData?.friendList || []).slice(0, limit);
41
+ return friends.map((f: any) => ({
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,70 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ cli({
5
+ site: 'boss',
6
+ name: 'chatmsg',
7
+ description: 'BOSS直聘查看与候选人的聊天消息',
8
+ domain: 'www.zhipin.com',
9
+ strategy: Strategy.COOKIE,
10
+ browser: true,
11
+ args: [
12
+ { name: 'uid', required: true, help: 'Encrypted UID (from chatlist)' },
13
+ { name: 'page', type: 'int', default: 1, help: 'Page number' },
14
+ ],
15
+ columns: ['from', 'type', 'text', 'time'],
16
+ func: async (page: IPage | null, kwargs) => {
17
+ if (!page) 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 uid = kwargs.uid;
21
+ const friendData: any = await page.evaluate(`
22
+ async () => {
23
+ return new Promise((resolve, reject) => {
24
+ const xhr = new XMLHttpRequest();
25
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
26
+ xhr.withCredentials = true;
27
+ xhr.timeout = 15000;
28
+ xhr.setRequestHeader('Accept', 'application/json');
29
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
30
+ xhr.onerror = () => reject(new Error('Network Error'));
31
+ xhr.send();
32
+ });
33
+ }
34
+ `);
35
+ if (friendData.code !== 0) throw new Error('获取好友列表失败');
36
+ const friend = (friendData.zpData?.friendList || []).find((f: any) => f.encryptUid === uid);
37
+ if (!friend) throw new Error('未找到该候选人');
38
+ const gid = friend.uid;
39
+ const securityId = encodeURIComponent(friend.securityId);
40
+ const msgUrl = `https://www.zhipin.com/wapi/zpchat/boss/historyMsg?gid=${gid}&securityId=${securityId}&page=${kwargs.page}&c=20&src=0`;
41
+ const msgData: any = await page.evaluate(`
42
+ async () => {
43
+ return new Promise((resolve, reject) => {
44
+ const xhr = new XMLHttpRequest();
45
+ xhr.open('GET', '${msgUrl}', true);
46
+ xhr.withCredentials = true;
47
+ xhr.timeout = 15000;
48
+ xhr.setRequestHeader('Accept', 'application/json');
49
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({raw: xhr.responseText.substring(0,500)}); } };
50
+ xhr.onerror = () => reject(new Error('Network Error'));
51
+ xhr.send();
52
+ });
53
+ }
54
+ `);
55
+ if (msgData.raw) throw new Error('Non-JSON: ' + msgData.raw);
56
+ if (msgData.code !== 0) throw new Error('API error: ' + (msgData.message || msgData.code));
57
+ const TYPE_MAP: Record<number, string> = {1: '文本', 2: '图片', 3: '招呼', 4: '简历', 5: '系统', 6: '名片', 7: '语音', 8: '视频', 9: '表情'};
58
+ const messages = msgData.zpData?.messages || msgData.zpData?.historyMsgList || [];
59
+ return messages.map((m: any) => {
60
+ const fromObj = m.from || {};
61
+ const isSelf = typeof fromObj === 'object' ? fromObj.uid !== friend.uid : false;
62
+ return {
63
+ from: isSelf ? '我' : (typeof fromObj === 'object' ? fromObj.name : friend.name),
64
+ type: TYPE_MAP[m.type] || '其他(' + m.type + ')',
65
+ text: m.text || m.body?.text || '',
66
+ time: m.time ? new Date(m.time).toLocaleString('zh-CN') : '',
67
+ };
68
+ });
69
+ },
70
+ });