@jackwener/opencli 1.7.3 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +81 -59
  2. package/README.zh-CN.md +93 -67
  3. package/cli-manifest.json +5015 -2975
  4. package/clis/antigravity/serve.js +71 -25
  5. package/clis/baidu-scholar/search.js +87 -0
  6. package/clis/baidu-scholar/search.test.js +23 -0
  7. package/clis/bilibili/favorite.js +18 -13
  8. package/clis/binance/depth.js +3 -4
  9. package/clis/boss/utils.js +2 -3
  10. package/clis/chatgpt-app/ax.js +6 -3
  11. package/clis/deepseek/ask.js +74 -0
  12. package/clis/deepseek/history.js +25 -0
  13. package/clis/deepseek/new.js +20 -0
  14. package/clis/deepseek/read.js +22 -0
  15. package/clis/deepseek/status.js +24 -0
  16. package/clis/deepseek/utils.js +208 -0
  17. package/clis/douban/search.js +1 -0
  18. package/clis/douban/search.test.js +11 -0
  19. package/clis/douban/subject.js +20 -93
  20. package/clis/douban/subject.test.js +11 -0
  21. package/clis/douban/utils.js +250 -8
  22. package/clis/douban/utils.test.js +179 -4
  23. package/clis/doubao/utils.js +319 -130
  24. package/clis/doubao/utils.test.js +241 -2
  25. package/clis/eastmoney/_secid.js +78 -0
  26. package/clis/eastmoney/announcement.js +52 -0
  27. package/clis/eastmoney/convertible.js +73 -0
  28. package/clis/eastmoney/etf.js +65 -0
  29. package/clis/eastmoney/holders.js +78 -0
  30. package/clis/eastmoney/hot-rank.js +50 -0
  31. package/clis/eastmoney/hot-rank.test.js +59 -0
  32. package/clis/eastmoney/index-board.js +96 -0
  33. package/clis/eastmoney/kline.js +87 -0
  34. package/clis/eastmoney/kuaixun.js +54 -0
  35. package/clis/eastmoney/longhu.js +67 -0
  36. package/clis/eastmoney/money-flow.js +78 -0
  37. package/clis/eastmoney/northbound.js +57 -0
  38. package/clis/eastmoney/quote.js +107 -0
  39. package/clis/eastmoney/rank.js +94 -0
  40. package/clis/eastmoney/sectors.js +76 -0
  41. package/clis/google-scholar/search.js +58 -0
  42. package/clis/google-scholar/search.test.js +23 -0
  43. package/clis/gov-law/commands.test.js +39 -0
  44. package/clis/gov-law/recent.js +22 -0
  45. package/clis/gov-law/search.js +41 -0
  46. package/clis/gov-law/shared.js +51 -0
  47. package/clis/gov-policy/commands.test.js +27 -0
  48. package/clis/gov-policy/recent.js +47 -0
  49. package/clis/gov-policy/search.js +48 -0
  50. package/clis/grok/image.test.ts +107 -0
  51. package/clis/grok/image.ts +356 -0
  52. package/clis/nowcoder/companies.js +23 -0
  53. package/clis/nowcoder/creators.js +27 -0
  54. package/clis/nowcoder/detail.js +61 -0
  55. package/clis/nowcoder/experience.js +36 -0
  56. package/clis/nowcoder/hot.js +24 -0
  57. package/clis/nowcoder/jobs.js +21 -0
  58. package/clis/nowcoder/notifications.js +29 -0
  59. package/clis/nowcoder/papers.js +40 -0
  60. package/clis/nowcoder/practice.js +37 -0
  61. package/clis/nowcoder/recommend.js +30 -0
  62. package/clis/nowcoder/referral.js +39 -0
  63. package/clis/nowcoder/salary.js +40 -0
  64. package/clis/nowcoder/search.js +49 -0
  65. package/clis/nowcoder/suggest.js +33 -0
  66. package/clis/nowcoder/topics.js +27 -0
  67. package/clis/nowcoder/trending.js +25 -0
  68. package/clis/tdx/hot-rank.js +47 -0
  69. package/clis/tdx/hot-rank.test.js +59 -0
  70. package/clis/ths/hot-rank.js +49 -0
  71. package/clis/ths/hot-rank.test.js +64 -0
  72. package/clis/twitter/bookmarks.js +2 -1
  73. package/clis/twitter/list-add.js +337 -0
  74. package/clis/twitter/list-add.test.js +15 -0
  75. package/clis/twitter/list-remove.js +297 -0
  76. package/clis/twitter/list-remove.test.js +14 -0
  77. package/clis/twitter/list-tweets.js +185 -0
  78. package/clis/twitter/list-tweets.test.js +108 -0
  79. package/clis/twitter/lists.js +134 -47
  80. package/clis/twitter/lists.test.js +105 -38
  81. package/clis/uiverse/_shared.js +368 -0
  82. package/clis/uiverse/_shared.test.js +55 -0
  83. package/clis/uiverse/code.js +47 -0
  84. package/clis/uiverse/preview.js +71 -0
  85. package/clis/wanfang/search.js +66 -0
  86. package/clis/wanfang/search.test.js +23 -0
  87. package/clis/web/read.js +1 -1
  88. package/clis/weixin/download.js +3 -2
  89. package/clis/xiaohongshu/comments.js +2 -2
  90. package/clis/xiaohongshu/comments.test.js +46 -25
  91. package/clis/xiaohongshu/download.js +6 -7
  92. package/clis/xiaohongshu/download.test.js +17 -5
  93. package/clis/xiaohongshu/note-helpers.js +46 -12
  94. package/clis/xiaohongshu/note.js +3 -5
  95. package/clis/xiaohongshu/note.test.js +52 -25
  96. package/clis/xiaohongshu/publish.js +149 -28
  97. package/clis/xiaohongshu/publish.test.js +319 -6
  98. package/clis/xiaoyuzhou/auth.js +303 -0
  99. package/clis/xiaoyuzhou/auth.test.js +124 -0
  100. package/clis/xiaoyuzhou/download.js +53 -0
  101. package/clis/xiaoyuzhou/download.test.js +135 -0
  102. package/clis/xiaoyuzhou/episode.js +9 -4
  103. package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
  104. package/clis/xiaoyuzhou/podcast.js +9 -4
  105. package/clis/xiaoyuzhou/transcript.js +76 -0
  106. package/clis/xiaoyuzhou/transcript.test.js +195 -0
  107. package/clis/xiaoyuzhou/utils.js +0 -40
  108. package/clis/xiaoyuzhou/utils.test.js +15 -75
  109. package/clis/youtube/feed.js +120 -0
  110. package/clis/youtube/history.js +118 -0
  111. package/clis/youtube/like.js +62 -0
  112. package/clis/youtube/playlist.js +97 -0
  113. package/clis/youtube/subscribe.js +71 -0
  114. package/clis/youtube/subscriptions.js +57 -0
  115. package/clis/youtube/unlike.js +62 -0
  116. package/clis/youtube/unsubscribe.js +71 -0
  117. package/clis/youtube/utils.js +122 -0
  118. package/clis/youtube/utils.test.js +32 -1
  119. package/clis/youtube/watch-later.js +76 -0
  120. package/clis/zsxq/dynamics.js +1 -1
  121. package/clis/zsxq/utils.js +6 -3
  122. package/clis/zsxq/utils.test.js +31 -0
  123. package/dist/src/browser/base-page.d.ts +1 -1
  124. package/dist/src/browser/base-page.js +25 -5
  125. package/dist/src/browser/bridge.d.ts +3 -0
  126. package/dist/src/browser/bridge.js +52 -15
  127. package/dist/src/browser/cdp.js +2 -1
  128. package/dist/src/browser/daemon-client.d.ts +7 -4
  129. package/dist/src/browser/daemon-client.js +6 -1
  130. package/dist/src/browser/daemon-client.test.js +40 -1
  131. package/dist/src/browser/dom-snapshot.js +20 -3
  132. package/dist/src/browser/page.d.ts +18 -5
  133. package/dist/src/browser/page.js +96 -15
  134. package/dist/src/browser/page.test.js +158 -1
  135. package/dist/src/browser/target-errors.d.ts +23 -0
  136. package/dist/src/browser/target-errors.js +29 -0
  137. package/dist/src/browser/target-errors.test.js +61 -0
  138. package/dist/src/browser/target-resolver.d.ts +57 -0
  139. package/dist/src/browser/target-resolver.js +298 -0
  140. package/dist/src/browser/target-resolver.test.js +43 -0
  141. package/dist/src/browser.test.js +38 -1
  142. package/dist/src/cli.js +272 -187
  143. package/dist/src/cli.test.js +167 -90
  144. package/dist/src/commanderAdapter.d.ts +0 -1
  145. package/dist/src/commanderAdapter.js +2 -16
  146. package/dist/src/commanderAdapter.test.js +1 -1
  147. package/dist/src/commands/daemon.d.ts +4 -2
  148. package/dist/src/commands/daemon.js +22 -2
  149. package/dist/src/commands/daemon.test.js +65 -2
  150. package/dist/src/completion-shared.js +2 -5
  151. package/dist/src/daemon.js +10 -0
  152. package/dist/src/doctor.d.ts +1 -0
  153. package/dist/src/doctor.js +32 -9
  154. package/dist/src/doctor.test.js +28 -12
  155. package/dist/src/download/article-download.d.ts +1 -0
  156. package/dist/src/download/article-download.js +3 -0
  157. package/dist/src/download/article-download.test.js +39 -0
  158. package/dist/src/external-clis.yaml +2 -2
  159. package/dist/src/logger.d.ts +2 -2
  160. package/dist/src/logger.js +3 -3
  161. package/dist/src/output.js +1 -5
  162. package/dist/src/output.test.js +0 -21
  163. package/dist/src/pipeline/steps/transform.js +1 -1
  164. package/dist/src/pipeline/template.d.ts +1 -0
  165. package/dist/src/pipeline/template.js +11 -3
  166. package/dist/src/pipeline/template.test.js +3 -0
  167. package/dist/src/pipeline/transform.test.js +14 -0
  168. package/dist/src/plugin.d.ts +8 -9
  169. package/dist/src/plugin.js +24 -28
  170. package/dist/src/plugin.test.js +16 -60
  171. package/dist/src/registry.d.ts +1 -0
  172. package/dist/src/registry.js +3 -2
  173. package/dist/src/registry.test.js +22 -0
  174. package/dist/src/types.d.ts +15 -6
  175. package/package.json +1 -1
  176. package/clis/twitter/lists-parser.js +0 -77
  177. package/clis/twitter/lists.d.ts +0 -5
  178. package/dist/src/cascade.d.ts +0 -46
  179. package/dist/src/cascade.js +0 -135
  180. package/dist/src/explore.d.ts +0 -99
  181. package/dist/src/explore.js +0 -402
  182. package/dist/src/generate-verified.d.ts +0 -105
  183. package/dist/src/generate-verified.js +0 -696
  184. package/dist/src/generate-verified.test.js +0 -925
  185. package/dist/src/generate.d.ts +0 -46
  186. package/dist/src/generate.js +0 -117
  187. package/dist/src/record.d.ts +0 -96
  188. package/dist/src/record.js +0 -657
  189. package/dist/src/record.test.js +0 -293
  190. package/dist/src/skill-generate.d.ts +0 -30
  191. package/dist/src/skill-generate.js +0 -75
  192. package/dist/src/skill-generate.test.js +0 -173
  193. package/dist/src/synthesize.d.ts +0 -97
  194. package/dist/src/synthesize.js +0 -208
  195. /package/dist/src/{generate-verified.test.d.ts → browser/target-errors.test.d.ts} +0 -0
  196. /package/dist/src/{record.test.d.ts → browser/target-resolver.test.d.ts} +0 -0
  197. /package/dist/src/{skill-generate.test.d.ts → download/article-download.test.d.ts} +0 -0
@@ -1,293 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { analyzeRecordedRequests, buildWriteRecordedYaml, createRecordedEntry, generateFullCaptureInterceptorJs, generateRecordedCandidates, } from './record.js';
3
- import { render } from './pipeline/template.js';
4
- describe('record request-body capture', () => {
5
- it('captures a JSON fetch request body alongside the JSON response body', () => {
6
- const entry = createRecordedEntry({
7
- url: 'https://api.example.com/tasks',
8
- method: 'POST',
9
- requestContentType: 'application/json',
10
- requestBodyText: '{"title":"Ship #601","priority":"high"}',
11
- responseBody: { id: 'task_123', ok: true },
12
- });
13
- expect(entry).toMatchObject({
14
- method: 'POST',
15
- requestContentType: 'application/json',
16
- requestBody: { title: 'Ship #601', priority: 'high' },
17
- responseBody: { id: 'task_123', ok: true },
18
- });
19
- });
20
- it('captures a JSON request body from fetch(Request)', async () => {
21
- class MockXMLHttpRequest {
22
- open() { }
23
- send() { }
24
- setRequestHeader() { }
25
- addEventListener() { }
26
- getResponseHeader() { return null; }
27
- responseText = '';
28
- }
29
- const mockFetch = vi.fn(async () => new Response(JSON.stringify({ id: 'task_123', ok: true }), { headers: { 'content-type': 'application/json' } }));
30
- vi.stubGlobal('fetch', mockFetch);
31
- vi.stubGlobal('XMLHttpRequest', MockXMLHttpRequest);
32
- vi.stubGlobal('window', globalThis);
33
- // eslint-disable-next-line no-eval
34
- eval(generateFullCaptureInterceptorJs());
35
- const request = new Request('https://api.example.com/tasks', {
36
- method: 'POST',
37
- headers: { 'content-type': 'application/json' },
38
- body: JSON.stringify({ title: 'Ship #601' }),
39
- });
40
- await globalThis.fetch(request);
41
- const recorded = globalThis.__opencli_record;
42
- expect(recorded).toHaveLength(1);
43
- expect(recorded?.[0]?.requestBody).toEqual({ title: 'Ship #601' });
44
- });
45
- it('captures a JSON request body from XHR send()', async () => {
46
- class MockXMLHttpRequest {
47
- __listeners = {};
48
- __rec_url;
49
- __rec_method;
50
- __rec_request_content_type;
51
- responseText = JSON.stringify({ id: 'task_456', ok: true });
52
- open(method, url) {
53
- this.__rec_method = method;
54
- this.__rec_url = url;
55
- }
56
- send() {
57
- for (const listener of this.__listeners.load ?? [])
58
- listener.call(this);
59
- }
60
- setRequestHeader(name, value) {
61
- if (name.toLowerCase() === 'content-type')
62
- this.__rec_request_content_type = value;
63
- }
64
- addEventListener(event, listener) {
65
- this.__listeners[event] ??= [];
66
- this.__listeners[event].push(listener);
67
- }
68
- getResponseHeader(name) {
69
- return name.toLowerCase() === 'content-type' ? 'application/json' : null;
70
- }
71
- }
72
- const mockFetch = vi.fn(async () => new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } }));
73
- vi.stubGlobal('fetch', mockFetch);
74
- vi.stubGlobal('XMLHttpRequest', MockXMLHttpRequest);
75
- vi.stubGlobal('window', globalThis);
76
- // eslint-disable-next-line no-eval
77
- eval(generateFullCaptureInterceptorJs());
78
- const xhr = new XMLHttpRequest();
79
- xhr.open('PATCH', 'https://api.example.com/tasks/submit');
80
- xhr.setRequestHeader('content-type', 'application/json;charset=utf-8');
81
- xhr.send('{"done":true}');
82
- const recorded = globalThis.__opencli_record;
83
- expect(recorded).toHaveLength(1);
84
- expect(recorded?.[0]?.requestBody).toEqual({ done: true });
85
- });
86
- it('does not interrupt fetch when reading a Request body fails', async () => {
87
- class MockXMLHttpRequest {
88
- open() { }
89
- send() { }
90
- setRequestHeader() { }
91
- addEventListener() { }
92
- getResponseHeader() { return null; }
93
- responseText = '';
94
- }
95
- class BrokenRequest extends Request {
96
- clone() {
97
- throw new Error('clone failed');
98
- }
99
- }
100
- const mockFetch = vi.fn(async () => new Response(JSON.stringify({ id: 'task_123', ok: true }), { headers: { 'content-type': 'application/json' } }));
101
- vi.stubGlobal('fetch', mockFetch);
102
- vi.stubGlobal('XMLHttpRequest', MockXMLHttpRequest);
103
- vi.stubGlobal('window', globalThis);
104
- // eslint-disable-next-line no-eval
105
- eval(generateFullCaptureInterceptorJs());
106
- const request = new BrokenRequest('https://api.example.com/tasks', {
107
- method: 'POST',
108
- headers: { 'content-type': 'application/json' },
109
- body: JSON.stringify({ title: 'Ship #601' }),
110
- });
111
- await expect(globalThis.fetch(request)).resolves.toBeInstanceOf(Response);
112
- expect(mockFetch).toHaveBeenCalledTimes(1);
113
- });
114
- });
115
- describe('record write candidates', () => {
116
- it('keeps a POST request with JSON request body and object response as a write candidate', () => {
117
- const result = analyzeRecordedRequests([
118
- createRecordedEntry({
119
- url: 'https://api.example.com/tasks/create',
120
- method: 'POST',
121
- requestContentType: 'application/json',
122
- requestBodyText: '{"title":"Ship #601"}',
123
- responseBody: { id: 'task_123', ok: true },
124
- }),
125
- ]);
126
- expect(result.candidates).toHaveLength(1);
127
- expect(result.candidates[0]).toMatchObject({
128
- kind: 'write',
129
- req: { method: 'POST' },
130
- });
131
- });
132
- it('accepts vendor JSON content types for write candidates', () => {
133
- const result = analyzeRecordedRequests([
134
- createRecordedEntry({
135
- url: 'https://api.example.com/tasks',
136
- method: 'POST',
137
- requestContentType: 'application/vnd.api+json',
138
- requestBodyText: '{"title":"Ship #601"}',
139
- responseBody: { id: 'task_123', ok: true },
140
- }),
141
- ]);
142
- expect(result.candidates).toHaveLength(1);
143
- expect(result.candidates[0]).toMatchObject({
144
- kind: 'write',
145
- req: { method: 'POST' },
146
- });
147
- });
148
- it('rejects a POST request that has no usable JSON request body', () => {
149
- const result = analyzeRecordedRequests([
150
- createRecordedEntry({
151
- url: 'https://api.example.com/tasks/create',
152
- method: 'POST',
153
- requestContentType: 'application/json',
154
- requestBodyText: '',
155
- responseBody: { id: 'task_123', ok: true },
156
- }),
157
- ]);
158
- expect(result.candidates).toEqual([]);
159
- });
160
- it('rejects array request and response bodies for first-version write candidates', () => {
161
- const result = analyzeRecordedRequests([
162
- createRecordedEntry({
163
- url: 'https://api.example.com/tasks/batch',
164
- method: 'POST',
165
- requestContentType: 'application/json',
166
- requestBodyText: '[{"title":"Ship #601"}]',
167
- responseBody: [{ id: 'task_123' }],
168
- }),
169
- ]);
170
- expect(result.candidates).toEqual([]);
171
- });
172
- it('generates a write YAML candidate from a replayable JSON write request', () => {
173
- const candidates = generateRecordedCandidates('demo', 'https://demo.example.com/app', [
174
- createRecordedEntry({
175
- url: 'https://api.example.com/tasks/create',
176
- method: 'POST',
177
- requestContentType: 'application/json',
178
- requestBodyText: '{"title":"Ship #601"}',
179
- responseBody: { id: 'task_123', ok: true },
180
- }),
181
- ]);
182
- expect(candidates).toHaveLength(1);
183
- expect(candidates[0]).toMatchObject({
184
- kind: 'write',
185
- name: 'create',
186
- strategy: 'cookie',
187
- });
188
- expect(JSON.stringify(candidates[0].yaml)).toContain('Ship #601');
189
- });
190
- it('builds a write template that replays the recorded JSON body with application/json', () => {
191
- const candidate = buildWriteRecordedYaml('demo', 'https://demo.example.com/app', createRecordedEntry({
192
- url: 'https://api.example.com/tasks/create',
193
- method: 'POST',
194
- requestContentType: 'application/json',
195
- requestBodyText: '{"title":"Ship #601"}',
196
- responseBody: { id: 'task_123', ok: true },
197
- }), 'create');
198
- expect(candidate.name).toBe('create');
199
- expect(JSON.stringify(candidate.yaml)).toContain('method: \\"POST\\"');
200
- expect(JSON.stringify(candidate.yaml)).toContain('content-type');
201
- expect(JSON.stringify(candidate.yaml)).toContain('Ship #601');
202
- });
203
- });
204
- describe('record read candidates', () => {
205
- it('keeps existing read candidates for array responses', () => {
206
- const result = analyzeRecordedRequests([
207
- {
208
- url: 'https://api.example.com/feed',
209
- method: 'GET',
210
- status: null,
211
- requestContentType: null,
212
- responseContentType: 'application/json',
213
- requestBody: null,
214
- responseBody: { items: [{ title: 'A' }, { title: 'B' }] },
215
- contentType: 'application/json',
216
- body: { items: [{ title: 'A' }, { title: 'B' }] },
217
- capturedAt: 1,
218
- },
219
- ]);
220
- expect(result.candidates).toHaveLength(1);
221
- expect(result.candidates[0]).toMatchObject({ kind: 'read' });
222
- });
223
- it('keeps read YAML generation on the baseline fetch path', () => {
224
- const candidates = generateRecordedCandidates('demo', 'https://demo.example.com/app', [
225
- createRecordedEntry({
226
- url: 'https://api.example.com/search?q=test',
227
- method: 'GET',
228
- responseBody: { items: [{ title: 'A' }, { title: 'B' }] },
229
- }),
230
- ]);
231
- const yaml = candidates[0].yaml;
232
- expect(yaml.pipeline[1]?.evaluate).toContain(`fetch("https://api.example.com/search?q=`);
233
- expect(yaml.pipeline[1]?.evaluate).toContain(`{ credentials: 'include' }`);
234
- expect(yaml.pipeline[1]?.evaluate).not.toContain('method: "POST"');
235
- expect(yaml.pipeline[1]?.evaluate).not.toContain('body: JSON.stringify');
236
- });
237
- it('renders search and page args into the read YAML fetch URL', () => {
238
- const candidates = generateRecordedCandidates('demo', 'https://demo.example.com/app', [
239
- createRecordedEntry({
240
- url: 'https://api.example.com/search?q=test&page=2',
241
- method: 'GET',
242
- responseBody: { items: [{ title: 'A' }, { title: 'B' }] },
243
- }),
244
- ]);
245
- const yaml = candidates[0].yaml;
246
- const renderedEvaluate = render(yaml.pipeline[1]?.evaluate, {
247
- args: { keyword: 'desk', page: 3 },
248
- });
249
- expect(renderedEvaluate).toContain('https://api.example.com/search?q=desk&page=3');
250
- });
251
- it('keeps GET and POST candidates separate when they share the same URL pattern', () => {
252
- const candidates = generateRecordedCandidates('demo', 'https://demo.example.com/app', [
253
- createRecordedEntry({
254
- url: 'https://api.example.com/tasks',
255
- method: 'GET',
256
- responseBody: { items: [{ title: 'A' }, { title: 'B' }] },
257
- }),
258
- createRecordedEntry({
259
- url: 'https://api.example.com/tasks',
260
- method: 'POST',
261
- requestContentType: 'application/json',
262
- requestBodyText: '{"title":"Ship #601"}',
263
- responseBody: { id: 'task_123', ok: true },
264
- }),
265
- ]);
266
- expect(candidates).toHaveLength(2);
267
- expect(candidates.some((candidate) => candidate.kind === 'read')).toBe(true);
268
- expect(candidates.some((candidate) => candidate.kind === 'write')).toBe(true);
269
- });
270
- });
271
- describe('record noise filtering', () => {
272
- it('filters analytics POST noise from write candidates', () => {
273
- const result = analyzeRecordedRequests([
274
- createRecordedEntry({
275
- url: 'https://api.example.com/analytics/event',
276
- method: 'POST',
277
- requestContentType: 'application/json',
278
- requestBodyText: '{"event":"click"}',
279
- responseBody: { ok: true, accepted: 1 },
280
- }),
281
- ]);
282
- expect(result.candidates).toEqual([]);
283
- });
284
- });
285
- afterEach(() => {
286
- vi.unstubAllGlobals();
287
- Reflect.deleteProperty(globalThis, '__opencli_record');
288
- Reflect.deleteProperty(globalThis, '__opencli_record_patched');
289
- Reflect.deleteProperty(globalThis, '__opencli_orig_fetch');
290
- Reflect.deleteProperty(globalThis, '__opencli_orig_xhr_open');
291
- Reflect.deleteProperty(globalThis, '__opencli_orig_xhr_send');
292
- Reflect.deleteProperty(globalThis, '__opencli_orig_xhr_set_request_header');
293
- });
@@ -1,30 +0,0 @@
1
- /**
2
- * Generate skill: thin wrapper over generateVerifiedFromUrl.
3
- *
4
- * Maps GenerateOutcome → SkillOutput.
5
- * Used by `opencli generate <url>` (automated path in opencli-explorer workflow).
6
- *
7
- * Design:
8
- * - Input: url + goal? (user intent, not execution strategy)
9
- * - Output: machine-readable decision fields + human-readable message
10
- * - Single source of truth: P1 GenerateOutcome
11
- * - No re-orchestration, no auto-escalation to browser
12
- */
13
- import { type GenerateOutcome, type StopReason, type EscalationReason, type SuggestedAction, type Reusability } from './generate-verified.js';
14
- import type { IBrowserFactory } from './runtime.js';
15
- export interface SkillInput {
16
- url: string;
17
- goal?: string;
18
- }
19
- export interface SkillOutput {
20
- conclusion: 'success' | 'blocked' | 'needs-human-check';
21
- reason?: StopReason | EscalationReason;
22
- suggested_action?: SuggestedAction;
23
- reusability?: Reusability;
24
- command?: string;
25
- strategy?: string;
26
- path?: string;
27
- message: string;
28
- }
29
- export declare function mapOutcomeToSkillOutput(outcome: GenerateOutcome): SkillOutput;
30
- export declare function executeGenerateSkill(input: SkillInput, BrowserFactory: new () => IBrowserFactory): Promise<SkillOutput>;
@@ -1,75 +0,0 @@
1
- /**
2
- * Generate skill: thin wrapper over generateVerifiedFromUrl.
3
- *
4
- * Maps GenerateOutcome → SkillOutput.
5
- * Used by `opencli generate <url>` (automated path in opencli-explorer workflow).
6
- *
7
- * Design:
8
- * - Input: url + goal? (user intent, not execution strategy)
9
- * - Output: machine-readable decision fields + human-readable message
10
- * - Single source of truth: P1 GenerateOutcome
11
- * - No re-orchestration, no auto-escalation to browser
12
- */
13
- import { generateVerifiedFromUrl, } from './generate-verified.js';
14
- // ── Message Templates ────────────────────────────────────────────────────────
15
- const BLOCKED_MESSAGES = {
16
- 'no-viable-api-surface': '该站点没有发现可用的 JSON API 接口,无法自动生成 CLI',
17
- 'auth-too-complex': '所有接口都需要超出自动化能力的认证方式(如 signature/bearer),无法自动生成',
18
- 'no-viable-candidate': '发现了 API 接口,但未能合成有效的 CLI 候选',
19
- 'execution-environment-unavailable': '浏览器未连接,请先运行 opencli doctor 检查环境',
20
- };
21
- const ESCALATION_MESSAGES = {
22
- 'unsupported-required-args': () => '候选需要用户提供必填参数的示例值后重试',
23
- 'empty-result': () => '候选验证返回空结果,建议用 opencli-browser 检查',
24
- 'sparse-fields': () => '候选验证结果字段不足,建议人工检查',
25
- 'non-array-result': () => '返回结果不是数组格式,建议用 opencli-browser 检查接口返回结构',
26
- 'timeout': () => '验证超时,建议用 opencli-browser 手动检查接口响应',
27
- 'selector-mismatch': () => '数据路径不匹配,建议用 opencli-browser 检查实际返回结构',
28
- 'verify-inconclusive': (ctx) => ctx?.path
29
- ? `验证结果不确定,候选已保存在 ${ctx.path},需要人工审查`
30
- : '验证结果不确定,需要人工审查',
31
- };
32
- // ── Core Mapping ─────────────────────────────────────────────────────────────
33
- export function mapOutcomeToSkillOutput(outcome) {
34
- switch (outcome.status) {
35
- case 'success':
36
- return {
37
- conclusion: 'success',
38
- reusability: outcome.reusability ?? 'verified-artifact',
39
- command: outcome.adapter?.command,
40
- strategy: outcome.adapter?.strategy,
41
- path: outcome.adapter?.path,
42
- message: `已生成 ${outcome.adapter?.command ?? 'unknown'},可直接使用。策略: ${outcome.adapter?.strategy ?? 'unknown'}`,
43
- };
44
- case 'blocked':
45
- return {
46
- conclusion: 'blocked',
47
- reason: outcome.reason,
48
- message: BLOCKED_MESSAGES[outcome.reason] ?? outcome.message ?? '生成被阻断',
49
- };
50
- case 'needs-human-check': {
51
- const escalation = outcome.escalation;
52
- const reason = escalation?.reason;
53
- const candidatePath = escalation?.candidate?.path ?? undefined;
54
- const messageFn = reason ? ESCALATION_MESSAGES[reason] : undefined;
55
- return {
56
- conclusion: 'needs-human-check',
57
- reason,
58
- suggested_action: escalation?.suggested_action,
59
- reusability: outcome.reusability,
60
- path: candidatePath ?? undefined,
61
- message: outcome.message ?? messageFn?.({ path: candidatePath ?? undefined }) ?? '需要人工检查',
62
- };
63
- }
64
- }
65
- }
66
- // ── Skill Entry Point ────────────────────────────────────────────────────────
67
- export async function executeGenerateSkill(input, BrowserFactory) {
68
- const opts = {
69
- url: input.url,
70
- BrowserFactory,
71
- goal: input.goal ?? null,
72
- };
73
- const outcome = await generateVerifiedFromUrl(opts);
74
- return mapOutcomeToSkillOutput(outcome);
75
- }
@@ -1,173 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { mapOutcomeToSkillOutput } from './skill-generate.js';
3
- import { Strategy } from './registry.js';
4
- describe('mapOutcomeToSkillOutput', () => {
5
- const baseStats = {
6
- endpoint_count: 1,
7
- api_endpoint_count: 1,
8
- candidate_count: 1,
9
- verified: false,
10
- repair_attempted: false,
11
- explore_dir: '/tmp/test',
12
- };
13
- it('maps success outcome correctly', () => {
14
- const outcome = {
15
- status: 'success',
16
- adapter: {
17
- site: 'demo',
18
- name: 'hot',
19
- command: 'demo/hot',
20
- strategy: Strategy.PUBLIC,
21
- path: '/tmp/demo/hot.verified.ts',
22
- metadata_path: '/tmp/demo/hot.meta.json',
23
- reusability: 'verified-artifact',
24
- },
25
- reusability: 'verified-artifact',
26
- stats: { ...baseStats, verified: true },
27
- };
28
- const result = mapOutcomeToSkillOutput(outcome);
29
- expect(result.conclusion).toBe('success');
30
- expect(result.reusability).toBe('verified-artifact');
31
- expect(result.command).toBe('demo/hot');
32
- expect(result.strategy).toBe('public');
33
- expect(result.path).toBe('/tmp/demo/hot.verified.ts');
34
- expect(result.message).toContain('demo/hot');
35
- expect(result.reason).toBeUndefined();
36
- expect(result.suggested_action).toBeUndefined();
37
- });
38
- it('maps blocked outcome with no-viable-api-surface', () => {
39
- const outcome = {
40
- status: 'blocked',
41
- reason: 'no-viable-api-surface',
42
- stage: 'explore',
43
- confidence: 'high',
44
- message: 'No API endpoints',
45
- stats: { ...baseStats, api_endpoint_count: 0 },
46
- };
47
- const result = mapOutcomeToSkillOutput(outcome);
48
- expect(result.conclusion).toBe('blocked');
49
- expect(result.reason).toBe('no-viable-api-surface');
50
- expect(result.message).toContain('JSON API');
51
- expect(result.command).toBeUndefined();
52
- expect(result.suggested_action).toBeUndefined();
53
- });
54
- it('maps blocked outcome with auth-too-complex', () => {
55
- const outcome = {
56
- status: 'blocked',
57
- reason: 'auth-too-complex',
58
- stage: 'cascade',
59
- confidence: 'high',
60
- stats: baseStats,
61
- };
62
- const result = mapOutcomeToSkillOutput(outcome);
63
- expect(result.conclusion).toBe('blocked');
64
- expect(result.reason).toBe('auth-too-complex');
65
- expect(result.message).toContain('认证');
66
- });
67
- it('maps blocked outcome with execution-environment-unavailable', () => {
68
- const outcome = {
69
- status: 'blocked',
70
- reason: 'execution-environment-unavailable',
71
- stage: 'verify',
72
- confidence: 'high',
73
- stats: baseStats,
74
- };
75
- const result = mapOutcomeToSkillOutput(outcome);
76
- expect(result.conclusion).toBe('blocked');
77
- expect(result.reason).toBe('execution-environment-unavailable');
78
- expect(result.message).toContain('doctor');
79
- });
80
- it('maps needs-human-check with unsupported-required-args', () => {
81
- const outcome = {
82
- status: 'needs-human-check',
83
- escalation: {
84
- stage: 'synthesize',
85
- reason: 'unsupported-required-args',
86
- confidence: 'high',
87
- suggested_action: 'ask-for-sample-arg',
88
- candidate: {
89
- name: 'detail',
90
- command: 'demo/detail',
91
- path: '/tmp/demo/detail.verified.ts',
92
- reusability: 'unverified-candidate',
93
- },
94
- },
95
- reusability: 'unverified-candidate',
96
- message: 'required args: id',
97
- stats: baseStats,
98
- };
99
- const result = mapOutcomeToSkillOutput(outcome);
100
- expect(result.conclusion).toBe('needs-human-check');
101
- expect(result.reason).toBe('unsupported-required-args');
102
- expect(result.suggested_action).toBe('ask-for-sample-arg');
103
- expect(result.reusability).toBe('unverified-candidate');
104
- expect(result.path).toBe('/tmp/demo/detail.verified.ts');
105
- expect(result.message).toContain('required args: id');
106
- });
107
- it('maps needs-human-check with empty-result (inspect-with-browser)', () => {
108
- const outcome = {
109
- status: 'needs-human-check',
110
- escalation: {
111
- stage: 'fallback',
112
- reason: 'empty-result',
113
- confidence: 'low',
114
- suggested_action: 'inspect-with-browser',
115
- candidate: {
116
- name: 'hot',
117
- command: 'demo/hot',
118
- path: null,
119
- reusability: 'unverified-candidate',
120
- },
121
- },
122
- reusability: 'unverified-candidate',
123
- stats: { ...baseStats, repair_attempted: true },
124
- };
125
- const result = mapOutcomeToSkillOutput(outcome);
126
- expect(result.conclusion).toBe('needs-human-check');
127
- expect(result.reason).toBe('empty-result');
128
- expect(result.suggested_action).toBe('inspect-with-browser');
129
- expect(result.path).toBeUndefined();
130
- expect(result.message).toContain('空结果');
131
- });
132
- it('maps needs-human-check with verify-inconclusive and path', () => {
133
- const outcome = {
134
- status: 'needs-human-check',
135
- escalation: {
136
- stage: 'verify',
137
- reason: 'verify-inconclusive',
138
- confidence: 'low',
139
- suggested_action: 'manual-review',
140
- candidate: {
141
- name: 'hot',
142
- command: 'demo/hot',
143
- path: '/tmp/demo/hot.verified.ts',
144
- reusability: 'unverified-candidate',
145
- },
146
- },
147
- reusability: 'unverified-candidate',
148
- stats: baseStats,
149
- };
150
- const result = mapOutcomeToSkillOutput(outcome);
151
- expect(result.conclusion).toBe('needs-human-check');
152
- expect(result.reason).toBe('verify-inconclusive');
153
- expect(result.suggested_action).toBe('manual-review');
154
- expect(result.path).toBe('/tmp/demo/hot.verified.ts');
155
- expect(result.message).toContain('/tmp/demo/hot.verified.ts');
156
- });
157
- it('output satisfies SkillOutput contract shape', () => {
158
- const outcome = {
159
- status: 'blocked',
160
- reason: 'no-viable-candidate',
161
- stage: 'synthesize',
162
- confidence: 'medium',
163
- stats: baseStats,
164
- };
165
- const result = mapOutcomeToSkillOutput(outcome);
166
- // Every SkillOutput must have conclusion + message
167
- expect(result).toHaveProperty('conclusion');
168
- expect(result).toHaveProperty('message');
169
- expect(['success', 'blocked', 'needs-human-check']).toContain(result.conclusion);
170
- expect(typeof result.message).toBe('string');
171
- expect(result.message.length).toBeGreaterThan(0);
172
- });
173
- });
@@ -1,97 +0,0 @@
1
- /**
2
- * Synthesize candidate CLIs from explore artifacts.
3
- * Generates evaluate-based pipelines (matching hand-written adapter patterns).
4
- */
5
- import type { ExploreAuthSummary, ExploreEndpointArtifact, ExploreManifest } from './explore.js';
6
- interface RecommendedArg {
7
- name: string;
8
- type?: string;
9
- required?: boolean;
10
- default?: unknown;
11
- }
12
- interface StoreHint {
13
- store: string;
14
- action: string;
15
- }
16
- export interface SynthesizeCapability {
17
- name: string;
18
- description: string;
19
- strategy: string;
20
- endpoint?: string;
21
- itemPath?: string | null;
22
- recommendedColumns?: string[];
23
- recommendedArgs?: RecommendedArg[];
24
- storeHint?: StoreHint;
25
- }
26
- export interface GeneratedArgDefinition {
27
- type: string;
28
- required?: boolean;
29
- default?: unknown;
30
- description?: string;
31
- }
32
- type CandidatePipelineStep = {
33
- navigate: string;
34
- } | {
35
- wait: number;
36
- } | {
37
- evaluate: string;
38
- } | {
39
- select: string;
40
- } | {
41
- map: Record<string, string>;
42
- } | {
43
- limit: string;
44
- } | {
45
- fetch: {
46
- url: string;
47
- };
48
- } | {
49
- tap: {
50
- store: string;
51
- action: string;
52
- timeout: number;
53
- capture?: string;
54
- select?: string | null;
55
- };
56
- };
57
- export interface CandidateYaml {
58
- site: string;
59
- name: string;
60
- description: string;
61
- domain: string;
62
- strategy: string;
63
- browser: boolean;
64
- args: Record<string, GeneratedArgDefinition>;
65
- pipeline: CandidatePipelineStep[];
66
- columns: string[];
67
- }
68
- export interface SynthesizeCandidateSummary {
69
- name: string;
70
- path: string;
71
- strategy: string;
72
- }
73
- export interface SynthesizeResult {
74
- site: string;
75
- explore_dir: string;
76
- out_dir: string;
77
- candidate_count: number;
78
- candidates: SynthesizeCandidateSummary[];
79
- }
80
- interface LoadedExploreBundle {
81
- manifest: ExploreManifest;
82
- endpoints: ExploreEndpointArtifact[];
83
- capabilities: SynthesizeCapability[];
84
- auth: ExploreAuthSummary;
85
- }
86
- export declare function synthesizeFromExplore(target: string, opts?: {
87
- outDir?: string;
88
- top?: number;
89
- }): SynthesizeResult;
90
- export declare function renderSynthesizeSummary(result: SynthesizeResult): string;
91
- export declare function resolveExploreDir(target: string): string;
92
- export declare function loadExploreBundle(exploreDir: string): LoadedExploreBundle;
93
- export declare function buildCandidate(site: string, targetUrl: string, cap: SynthesizeCapability, endpoint: ExploreEndpointArtifact): {
94
- name: string;
95
- yaml: CandidateYaml;
96
- };
97
- export {};