@jackwener/opencli 1.3.1 → 1.3.3

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 (241) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +48 -9
  3. package/README.zh-CN.md +48 -9
  4. package/SKILL.md +317 -6
  5. package/TESTING.md +4 -4
  6. package/dist/browser/cdp.js +10 -1
  7. package/dist/browser/daemon-client.js +2 -1
  8. package/dist/browser/discover.js +2 -1
  9. package/dist/browser/errors.d.ts +2 -1
  10. package/dist/browser/errors.js +10 -10
  11. package/dist/browser/index.d.ts +1 -0
  12. package/dist/browser/index.js +1 -0
  13. package/dist/browser/page.js +12 -0
  14. package/dist/browser/stealth.d.ts +18 -0
  15. package/dist/browser/stealth.js +140 -0
  16. package/dist/browser.test.js +47 -1
  17. package/dist/build-manifest.js +1 -3
  18. package/dist/cli-manifest.json +2573 -989
  19. package/dist/cli.js +42 -2
  20. package/dist/clis/bilibili/download.js +20 -65
  21. package/dist/clis/bilibili/utils.js +2 -1
  22. package/dist/clis/chaoxing/assignments.js +2 -1
  23. package/dist/clis/doubao/ask.d.ts +1 -0
  24. package/dist/clis/doubao/ask.js +35 -0
  25. package/dist/clis/doubao/common.d.ts +23 -0
  26. package/dist/clis/doubao/common.js +564 -0
  27. package/dist/clis/doubao/new.d.ts +1 -0
  28. package/dist/clis/doubao/new.js +20 -0
  29. package/dist/clis/doubao/read.d.ts +1 -0
  30. package/dist/clis/doubao/read.js +19 -0
  31. package/dist/clis/doubao/send.d.ts +1 -0
  32. package/dist/clis/doubao/send.js +22 -0
  33. package/dist/clis/doubao/status.d.ts +1 -0
  34. package/dist/clis/doubao/status.js +24 -0
  35. package/dist/clis/doubao-app/ask.d.ts +1 -0
  36. package/dist/clis/doubao-app/ask.js +53 -0
  37. package/dist/clis/doubao-app/common.d.ts +37 -0
  38. package/dist/clis/doubao-app/common.js +110 -0
  39. package/dist/clis/doubao-app/dump.d.ts +1 -0
  40. package/dist/clis/doubao-app/dump.js +24 -0
  41. package/dist/clis/doubao-app/new.d.ts +1 -0
  42. package/dist/clis/doubao-app/new.js +20 -0
  43. package/dist/clis/doubao-app/read.d.ts +1 -0
  44. package/dist/clis/doubao-app/read.js +18 -0
  45. package/dist/clis/doubao-app/screenshot.d.ts +1 -0
  46. package/dist/clis/doubao-app/screenshot.js +18 -0
  47. package/dist/clis/doubao-app/send.d.ts +1 -0
  48. package/dist/clis/doubao-app/send.js +27 -0
  49. package/dist/clis/doubao-app/status.d.ts +1 -0
  50. package/dist/clis/doubao-app/status.js +16 -0
  51. package/dist/clis/hackernews/ask.yaml +38 -0
  52. package/dist/clis/hackernews/best.yaml +38 -0
  53. package/dist/clis/hackernews/jobs.yaml +36 -0
  54. package/dist/clis/hackernews/new.yaml +38 -0
  55. package/dist/clis/hackernews/search.yaml +44 -0
  56. package/dist/clis/hackernews/show.yaml +38 -0
  57. package/dist/clis/hackernews/top.yaml +3 -1
  58. package/dist/clis/hackernews/user.yaml +25 -0
  59. package/dist/clis/twitter/download.js +13 -97
  60. package/dist/clis/twitter/thread.js +2 -1
  61. package/dist/clis/v2ex/member.yaml +29 -0
  62. package/dist/clis/v2ex/node.yaml +34 -0
  63. package/dist/clis/v2ex/nodes.yaml +31 -0
  64. package/dist/clis/v2ex/replies.yaml +32 -0
  65. package/dist/clis/v2ex/user.yaml +34 -0
  66. package/dist/clis/weibo/search.d.ts +1 -0
  67. package/dist/clis/weibo/search.js +73 -0
  68. package/dist/clis/weixin/download.d.ts +12 -0
  69. package/dist/clis/weixin/download.js +183 -0
  70. package/dist/clis/xiaohongshu/download.js +12 -60
  71. package/dist/clis/xiaohongshu/publish.d.ts +18 -0
  72. package/dist/clis/xiaohongshu/publish.js +352 -0
  73. package/dist/clis/xiaohongshu/search.js +47 -15
  74. package/dist/clis/xiaohongshu/search.test.d.ts +1 -0
  75. package/dist/clis/xiaohongshu/search.test.js +114 -0
  76. package/dist/clis/yollomi/background.d.ts +4 -0
  77. package/dist/clis/yollomi/background.js +45 -0
  78. package/dist/clis/yollomi/edit.d.ts +5 -0
  79. package/dist/clis/yollomi/edit.js +56 -0
  80. package/dist/clis/yollomi/face-swap.d.ts +5 -0
  81. package/dist/clis/yollomi/face-swap.js +43 -0
  82. package/dist/clis/yollomi/generate.d.ts +9 -0
  83. package/dist/clis/yollomi/generate.js +100 -0
  84. package/dist/clis/yollomi/models.d.ts +1 -0
  85. package/dist/clis/yollomi/models.js +33 -0
  86. package/dist/clis/yollomi/object-remover.d.ts +4 -0
  87. package/dist/clis/yollomi/object-remover.js +42 -0
  88. package/dist/clis/yollomi/remove-bg.d.ts +4 -0
  89. package/dist/clis/yollomi/remove-bg.js +38 -0
  90. package/dist/clis/yollomi/restore.d.ts +4 -0
  91. package/dist/clis/yollomi/restore.js +38 -0
  92. package/dist/clis/yollomi/try-on.d.ts +4 -0
  93. package/dist/clis/yollomi/try-on.js +46 -0
  94. package/dist/clis/yollomi/upload.d.ts +7 -0
  95. package/dist/clis/yollomi/upload.js +71 -0
  96. package/dist/clis/yollomi/upscale.d.ts +4 -0
  97. package/dist/clis/yollomi/upscale.js +53 -0
  98. package/dist/clis/yollomi/utils.d.ts +45 -0
  99. package/dist/clis/yollomi/utils.js +180 -0
  100. package/dist/clis/yollomi/video.d.ts +5 -0
  101. package/dist/clis/yollomi/video.js +56 -0
  102. package/dist/clis/zhihu/download.d.ts +1 -5
  103. package/dist/clis/zhihu/download.js +20 -126
  104. package/dist/clis/zhihu/download.test.js +7 -5
  105. package/dist/clis/zhihu/question.js +2 -1
  106. package/dist/commanderAdapter.js +4 -6
  107. package/dist/constants.d.ts +2 -0
  108. package/dist/constants.js +2 -0
  109. package/dist/daemon.js +7 -3
  110. package/dist/discovery.js +10 -10
  111. package/dist/doctor.js +2 -1
  112. package/dist/download/article-download.d.ts +59 -0
  113. package/dist/download/article-download.js +178 -0
  114. package/dist/download/media-download.d.ts +49 -0
  115. package/dist/download/media-download.js +112 -0
  116. package/dist/errors.d.ts +23 -2
  117. package/dist/errors.js +58 -2
  118. package/dist/errors.test.d.ts +1 -0
  119. package/dist/errors.test.js +59 -0
  120. package/dist/execution.js +9 -10
  121. package/dist/explore.js +4 -2
  122. package/dist/external.d.ts +15 -0
  123. package/dist/external.js +48 -2
  124. package/dist/external.test.d.ts +1 -0
  125. package/dist/external.test.js +64 -0
  126. package/dist/main.js +10 -0
  127. package/dist/plugin.d.ts +4 -0
  128. package/dist/plugin.js +45 -23
  129. package/dist/plugin.test.js +6 -1
  130. package/dist/record.d.ts +47 -0
  131. package/dist/record.js +545 -0
  132. package/dist/registry.d.ts +7 -2
  133. package/dist/registry.js +2 -6
  134. package/dist/runtime.d.ts +3 -1
  135. package/dist/runtime.js +10 -3
  136. package/dist/validate.js +1 -3
  137. package/docs/.vitepress/config.mts +1 -0
  138. package/docs/adapters/browser/douban.md +18 -8
  139. package/docs/adapters/browser/doubao.md +35 -0
  140. package/docs/adapters/browser/hackernews.md +20 -4
  141. package/docs/adapters/browser/tiktok.md +1 -1
  142. package/docs/adapters/browser/v2ex.md +31 -10
  143. package/docs/adapters/browser/weibo.md +4 -0
  144. package/docs/adapters/browser/weixin.md +33 -0
  145. package/docs/adapters/browser/wikipedia.md +0 -9
  146. package/docs/adapters/browser/xiaohongshu.md +8 -6
  147. package/docs/adapters/browser/yollomi.md +69 -0
  148. package/docs/adapters/desktop/antigravity.md +0 -3
  149. package/docs/adapters/desktop/doubao-app.md +35 -0
  150. package/docs/adapters/index.md +19 -8
  151. package/docs/advanced/download.md +4 -0
  152. package/package.json +3 -1
  153. package/src/browser/cdp.ts +9 -1
  154. package/src/browser/daemon-client.ts +4 -3
  155. package/src/browser/discover.ts +2 -1
  156. package/src/browser/errors.ts +18 -11
  157. package/src/browser/index.ts +1 -0
  158. package/src/browser/page.ts +11 -0
  159. package/src/browser/stealth.ts +142 -0
  160. package/src/browser.test.ts +51 -1
  161. package/src/build-manifest.ts +1 -3
  162. package/src/cli.ts +45 -2
  163. package/src/clis/bilibili/download.ts +25 -83
  164. package/src/clis/bilibili/utils.ts +2 -1
  165. package/src/clis/chaoxing/assignments.ts +2 -1
  166. package/src/clis/doubao/ask.ts +40 -0
  167. package/src/clis/doubao/common.ts +619 -0
  168. package/src/clis/doubao/new.ts +22 -0
  169. package/src/clis/doubao/read.ts +20 -0
  170. package/src/clis/doubao/send.ts +25 -0
  171. package/src/clis/doubao/status.ts +27 -0
  172. package/src/clis/doubao-app/ask.ts +60 -0
  173. package/src/clis/doubao-app/common.ts +116 -0
  174. package/src/clis/doubao-app/dump.ts +28 -0
  175. package/src/clis/doubao-app/new.ts +21 -0
  176. package/src/clis/doubao-app/read.ts +21 -0
  177. package/src/clis/doubao-app/screenshot.ts +19 -0
  178. package/src/clis/doubao-app/send.ts +30 -0
  179. package/src/clis/doubao-app/status.ts +17 -0
  180. package/src/clis/hackernews/ask.yaml +38 -0
  181. package/src/clis/hackernews/best.yaml +38 -0
  182. package/src/clis/hackernews/jobs.yaml +36 -0
  183. package/src/clis/hackernews/new.yaml +38 -0
  184. package/src/clis/hackernews/search.yaml +44 -0
  185. package/src/clis/hackernews/show.yaml +38 -0
  186. package/src/clis/hackernews/top.yaml +3 -1
  187. package/src/clis/hackernews/user.yaml +25 -0
  188. package/src/clis/twitter/download.ts +13 -111
  189. package/src/clis/twitter/thread.ts +2 -1
  190. package/src/clis/v2ex/member.yaml +29 -0
  191. package/src/clis/v2ex/node.yaml +34 -0
  192. package/src/clis/v2ex/nodes.yaml +31 -0
  193. package/src/clis/v2ex/replies.yaml +32 -0
  194. package/src/clis/v2ex/user.yaml +34 -0
  195. package/src/clis/weibo/search.ts +78 -0
  196. package/src/clis/weixin/download.ts +199 -0
  197. package/src/clis/xiaohongshu/download.ts +12 -71
  198. package/src/clis/xiaohongshu/publish.ts +392 -0
  199. package/src/clis/xiaohongshu/search.test.ts +134 -0
  200. package/src/clis/xiaohongshu/search.ts +49 -15
  201. package/src/clis/yollomi/background.ts +48 -0
  202. package/src/clis/yollomi/edit.ts +58 -0
  203. package/src/clis/yollomi/face-swap.ts +45 -0
  204. package/src/clis/yollomi/generate.ts +95 -0
  205. package/src/clis/yollomi/models.ts +38 -0
  206. package/src/clis/yollomi/object-remover.ts +44 -0
  207. package/src/clis/yollomi/remove-bg.ts +40 -0
  208. package/src/clis/yollomi/restore.ts +40 -0
  209. package/src/clis/yollomi/try-on.ts +48 -0
  210. package/src/clis/yollomi/upload.ts +78 -0
  211. package/src/clis/yollomi/upscale.ts +49 -0
  212. package/src/clis/yollomi/utils.ts +202 -0
  213. package/src/clis/yollomi/video.ts +61 -0
  214. package/src/clis/zhihu/download.test.ts +7 -5
  215. package/src/clis/zhihu/download.ts +23 -158
  216. package/src/clis/zhihu/question.ts +2 -1
  217. package/src/commanderAdapter.ts +4 -7
  218. package/src/constants.ts +3 -0
  219. package/src/daemon.ts +7 -3
  220. package/src/discovery.ts +26 -26
  221. package/src/doctor.ts +2 -1
  222. package/src/download/article-download.ts +272 -0
  223. package/src/download/media-download.ts +178 -0
  224. package/src/errors.test.ts +79 -0
  225. package/src/errors.ts +92 -2
  226. package/src/execution.ts +14 -10
  227. package/src/explore.ts +4 -2
  228. package/src/external.test.ts +88 -0
  229. package/src/external.ts +56 -2
  230. package/src/generate.ts +2 -1
  231. package/src/main.ts +10 -0
  232. package/src/plugin.test.ts +7 -1
  233. package/src/plugin.ts +49 -25
  234. package/src/record.ts +617 -0
  235. package/src/registry.ts +9 -5
  236. package/src/runtime.ts +16 -4
  237. package/src/validate.ts +1 -3
  238. package/tests/e2e/browser-auth.test.ts +10 -1
  239. package/tests/e2e/browser-public.test.ts +13 -8
  240. package/tests/e2e/public-commands.test.ts +209 -21
  241. package/tests/smoke/api-health.test.ts +65 -6
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Stealth anti-detection module.
3
+ *
4
+ * Generates JS code that patches browser globals to hide automation
5
+ * fingerprints (e.g. navigator.webdriver, missing chrome object, empty
6
+ * plugin list). Injected before page scripts run so that websites cannot
7
+ * detect CDP / extension-based control.
8
+ *
9
+ * Inspired by puppeteer-extra-plugin-stealth.
10
+ */
11
+ /** Guard flag set on `window` to prevent double-injection. */
12
+ export const STEALTH_GUARD = '__opencli_stealth_applied';
13
+ /**
14
+ * Return a self-contained JS string that, when evaluated in a page context,
15
+ * applies all stealth patches. Safe to call multiple times — the guard flag
16
+ * ensures patches are applied only once.
17
+ */
18
+ export function generateStealthJs() {
19
+ return `
20
+ (() => {
21
+ // Guard: skip if already applied
22
+ if (window.${STEALTH_GUARD}) return 'skipped';
23
+ // Use defineProperty so the guard flag is non-enumerable (not a detection vector).
24
+ Object.defineProperty(window, '${STEALTH_GUARD}', { value: true, configurable: true });
25
+
26
+ // 1. navigator.webdriver → undefined
27
+ // Most common check; Playwright/Puppeteer/CDP set this to true.
28
+ try {
29
+ Object.defineProperty(navigator, 'webdriver', {
30
+ get: () => undefined,
31
+ configurable: true,
32
+ });
33
+ } catch {}
34
+
35
+ // 2. window.chrome stub
36
+ // Real Chrome exposes window.chrome with runtime, loadTimes, csi.
37
+ // Headless/automated Chrome may not have it.
38
+ try {
39
+ if (!window.chrome) {
40
+ window.chrome = {
41
+ runtime: {
42
+ onConnect: { addListener: () => {}, removeListener: () => {} },
43
+ onMessage: { addListener: () => {}, removeListener: () => {} },
44
+ },
45
+ loadTimes: () => ({}),
46
+ csi: () => ({}),
47
+ };
48
+ }
49
+ } catch {}
50
+
51
+ // 3. navigator.plugins — fake population only if empty
52
+ // Real user browser already has plugins; only patch in automated/headless
53
+ // contexts where the list is empty (overwriting real plugins with fakes
54
+ // would be counterproductive and detectable).
55
+ try {
56
+ if (!navigator.plugins || navigator.plugins.length === 0) {
57
+ const fakePlugins = [
58
+ { name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
59
+ { name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
60
+ { name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
61
+ { name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
62
+ { name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: '' },
63
+ ];
64
+ fakePlugins.item = (i) => fakePlugins[i] || null;
65
+ fakePlugins.namedItem = (n) => fakePlugins.find(p => p.name === n) || null;
66
+ fakePlugins.refresh = () => {};
67
+ Object.defineProperty(navigator, 'plugins', {
68
+ get: () => fakePlugins,
69
+ configurable: true,
70
+ });
71
+ }
72
+ } catch {}
73
+
74
+ // 4. navigator.languages — guarantee non-empty
75
+ // Some automated contexts return undefined or empty array.
76
+ try {
77
+ if (!navigator.languages || navigator.languages.length === 0) {
78
+ Object.defineProperty(navigator, 'languages', {
79
+ get: () => ['en-US', 'en'],
80
+ configurable: true,
81
+ });
82
+ }
83
+ } catch {}
84
+
85
+ // 5. Permissions.query — normalize notification permission
86
+ // Headless Chrome throws on Permissions.query({ name: 'notifications' }).
87
+ try {
88
+ const origQuery = window.Permissions?.prototype?.query;
89
+ if (origQuery) {
90
+ window.Permissions.prototype.query = function (parameters) {
91
+ if (parameters?.name === 'notifications') {
92
+ return Promise.resolve({ state: Notification.permission, onchange: null });
93
+ }
94
+ return origQuery.call(this, parameters);
95
+ };
96
+ }
97
+ } catch {}
98
+
99
+ // 6. Clean automation artifacts
100
+ // Remove properties left by Playwright, Puppeteer, or CDP injection.
101
+ try {
102
+ delete window.__playwright;
103
+ delete window.__puppeteer;
104
+ // ChromeDriver injects cdc_ prefixed globals; the suffix varies by version,
105
+ // so scan window for any matching property rather than hardcoding names.
106
+ for (const prop of Object.getOwnPropertyNames(window)) {
107
+ if (prop.startsWith('cdc_') || prop.startsWith('__cdc_')) {
108
+ try { delete window[prop]; } catch {}
109
+ }
110
+ }
111
+ } catch {}
112
+
113
+ // 7. CDP stack trace cleanup
114
+ // Runtime.evaluate injects scripts whose source URLs appear in Error
115
+ // stack traces (e.g. __puppeteer_evaluation_script__, pptr:, debugger://).
116
+ // Websites detect automation by doing: new Error().stack and inspecting it.
117
+ // We override the stack property getter on Error.prototype to filter them.
118
+ // Note: Error.prepareStackTrace is V8/Node-only and not available in
119
+ // browser page context, so we use a property descriptor approach instead.
120
+ try {
121
+ const _origDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
122
+ const _cdpPatterns = ['puppeteer_evaluation_script', 'pptr:', 'debugger://', '__opencli'];
123
+ if (_origDescriptor && _origDescriptor.get) {
124
+ Object.defineProperty(Error.prototype, 'stack', {
125
+ get: function () {
126
+ const raw = _origDescriptor.get.call(this);
127
+ if (typeof raw !== 'string') return raw;
128
+ return raw.split('\\n').filter(line =>
129
+ !_cdpPatterns.some(p => line.includes(p))
130
+ ).join('\\n');
131
+ },
132
+ configurable: true,
133
+ });
134
+ }
135
+ } catch {}
136
+
137
+ return 'applied';
138
+ })()
139
+ `;
140
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
- import { BrowserBridge, __test__ } from './browser/index.js';
2
+ import { BrowserBridge, __test__, generateStealthJs } from './browser/index.js';
3
+ import { STEALTH_GUARD } from './browser/stealth.js';
3
4
  import * as daemonClient from './browser/daemon-client.js';
4
5
  describe('browser helpers', () => {
5
6
  it('extracts tab entries from string snapshots', () => {
@@ -102,3 +103,48 @@ describe('BrowserBridge state', () => {
102
103
  await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
103
104
  });
104
105
  });
106
+ describe('stealth anti-detection', () => {
107
+ it('generates non-empty JS string', () => {
108
+ const js = generateStealthJs();
109
+ expect(typeof js).toBe('string');
110
+ expect(js.length).toBeGreaterThan(100);
111
+ });
112
+ it('contains all 7 anti-detection patches', () => {
113
+ const js = generateStealthJs();
114
+ // 1. webdriver
115
+ expect(js).toContain('navigator');
116
+ expect(js).toContain('webdriver');
117
+ // 2. chrome stub
118
+ expect(js).toContain('window.chrome');
119
+ // 3. plugins
120
+ expect(js).toContain('plugins');
121
+ expect(js).toContain('PDF Viewer');
122
+ // 4. languages
123
+ expect(js).toContain('languages');
124
+ // 5. permissions
125
+ expect(js).toContain('Permissions');
126
+ expect(js).toContain('notifications');
127
+ // 6. automation artifacts (dynamic cdc_ scan)
128
+ expect(js).toContain('__playwright');
129
+ expect(js).toContain('__puppeteer');
130
+ expect(js).toContain('getOwnPropertyNames');
131
+ expect(js).toContain('cdc_');
132
+ // 7. CDP stack trace cleanup
133
+ expect(js).toContain('Error.prototype');
134
+ expect(js).toContain('puppeteer_evaluation_script');
135
+ expect(js).toContain('getOwnPropertyDescriptor');
136
+ });
137
+ it('includes guard flag to prevent double-injection', () => {
138
+ const js = generateStealthJs();
139
+ expect(js).toContain(STEALTH_GUARD);
140
+ // Guard should check early and return 'skipped'
141
+ expect(js).toContain("return 'skipped'");
142
+ // Normal path returns 'applied'
143
+ expect(js).toContain("return 'applied'");
144
+ });
145
+ it('generates syntactically valid JS', () => {
146
+ const js = generateStealthJs();
147
+ // Should not throw when parsed
148
+ expect(() => new Function(js)).not.toThrow();
149
+ });
150
+ });
@@ -12,15 +12,13 @@ import * as fs from 'node:fs';
12
12
  import * as path from 'node:path';
13
13
  import { fileURLToPath, pathToFileURL } from 'node:url';
14
14
  import yaml from 'js-yaml';
15
+ import { getErrorMessage } from './errors.js';
15
16
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
17
  const CLIS_DIR = path.resolve(__dirname, 'clis');
17
18
  const OUTPUT = path.resolve(__dirname, '..', 'dist', 'cli-manifest.json');
18
19
  function isRecord(value) {
19
20
  return typeof value === 'object' && value !== null && !Array.isArray(value);
20
21
  }
21
- function getErrorMessage(error) {
22
- return error instanceof Error ? error.message : String(error);
23
- }
24
22
  function extractBalancedBlock(source, startIndex, openChar, closeChar) {
25
23
  let depth = 0;
26
24
  let quote = null;