@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
@@ -23,6 +23,7 @@ import {
23
23
  generateFilename,
24
24
  exportCookiesToNetscape,
25
25
  getTempDir,
26
+ formatCookieHeader,
26
27
  } from '../../download/index.js';
27
28
  import { DownloadProgressTracker, formatBytes } from '../../download/progress.js';
28
29
 
@@ -62,9 +63,8 @@ async function mapConcurrent<T, R>(
62
63
  */
63
64
  async function extractBrowserCookies(page: IPage, domain?: string): Promise<string> {
64
65
  try {
65
- // Use browser evaluate to get document.cookie
66
- const cookieString = await page.evaluate(`(() => document.cookie)()`);
67
- return typeof cookieString === 'string' ? cookieString : '';
66
+ const cookies = await page.getCookies(domain ? { domain } : {});
67
+ return formatCookieHeader(cookies);
68
68
  } catch {
69
69
  return '';
70
70
  }
@@ -78,20 +78,17 @@ async function extractCookiesArray(
78
78
  domain: string,
79
79
  ): Promise<Array<{ name: string; value: string; domain: string; path: string; secure: boolean; httpOnly: boolean }>> {
80
80
  try {
81
- const cookieString = await extractBrowserCookies(page);
82
- if (!cookieString) return [];
83
-
84
- return cookieString.split(';').map((c) => {
85
- const [name, ...rest] = c.trim().split('=');
86
- return {
87
- name: name || '',
88
- value: rest.join('=') || '',
89
- domain,
90
- path: '/',
91
- secure: true,
92
- httpOnly: false,
93
- };
94
- }).filter((c) => c.name);
81
+ const cookies = await page.getCookies({ domain });
82
+ return cookies
83
+ .filter((cookie) => cookie.name)
84
+ .map((cookie) => ({
85
+ name: cookie.name,
86
+ value: cookie.value,
87
+ domain: cookie.domain,
88
+ path: cookie.path ?? '/',
89
+ secure: cookie.secure ?? false,
90
+ httpOnly: cookie.httpOnly ?? false,
91
+ }));
95
92
  } catch {
96
93
  return [];
97
94
  }
package/src/registry.ts CHANGED
@@ -15,7 +15,7 @@ export enum Strategy {
15
15
  export interface Arg {
16
16
  name: string;
17
17
  type?: string;
18
- default?: any;
18
+ default?: unknown;
19
19
  required?: boolean;
20
20
  positional?: boolean;
21
21
  help?: string;
@@ -31,10 +31,11 @@ export interface CliCommand {
31
31
  browser?: boolean;
32
32
  args: Arg[];
33
33
  columns?: string[];
34
- func?: (page: IPage, kwargs: Record<string, any>, debug?: boolean) => Promise<any>;
35
- pipeline?: any[];
34
+ func?: (page: IPage, kwargs: Record<string, any>, debug?: boolean) => Promise<unknown>;
35
+ pipeline?: Record<string, unknown>[];
36
36
  timeoutSeconds?: number;
37
37
  source?: string;
38
+ footerExtra?: (kwargs: Record<string, any>) => string | undefined;
38
39
  }
39
40
 
40
41
  /** Internal extension for lazy-loaded TS modules (not exposed in public API) */
@@ -51,18 +52,21 @@ export interface CliOptions extends Partial<Omit<CliCommand, 'args' | 'descripti
51
52
  const _registry = new Map<string, CliCommand>();
52
53
 
53
54
  export function cli(opts: CliOptions): CliCommand {
55
+ const strategy = opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
56
+ const browser = opts.browser ?? (strategy !== Strategy.PUBLIC);
54
57
  const cmd: CliCommand = {
55
58
  site: opts.site,
56
59
  name: opts.name,
57
60
  description: opts.description ?? '',
58
61
  domain: opts.domain,
59
- strategy: opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE),
60
- browser: opts.browser ?? (opts.strategy === Strategy.PUBLIC ? false : true),
62
+ strategy,
63
+ browser,
61
64
  args: opts.args ?? [],
62
65
  columns: opts.columns,
63
66
  func: opts.func,
64
67
  pipeline: opts.pipeline,
65
68
  timeoutSeconds: opts.timeoutSeconds,
69
+ footerExtra: opts.footerExtra,
66
70
  };
67
71
 
68
72
  const key = fullName(cmd);
package/src/runtime.ts CHANGED
@@ -30,17 +30,18 @@ export function withTimeoutMs<T>(promise: Promise<T>, timeoutMs: number, message
30
30
 
31
31
  /** Interface for browser factory (BrowserBridge or test mocks) */
32
32
  export interface IBrowserFactory {
33
- connect(opts?: { timeout?: number }): Promise<IPage>;
33
+ connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage>;
34
34
  close(): Promise<void>;
35
35
  }
36
36
 
37
37
  export async function browserSession<T>(
38
38
  BrowserFactory: new () => IBrowserFactory,
39
39
  fn: (page: IPage) => Promise<T>,
40
+ opts: { workspace?: string } = {},
40
41
  ): Promise<T> {
41
42
  const mcp = new BrowserFactory();
42
43
  try {
43
- const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
44
+ const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT, workspace: opts.workspace });
44
45
  return await fn(page);
45
46
  } finally {
46
47
  await mcp.close().catch(() => {});
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Injected script for detecting frontend frameworks (Vue, React, Next, Nuxt, etc.)
3
+ */
4
+ export function detectFramework() {
5
+ const r: Record<string, boolean> = {};
6
+ try {
7
+ const app = document.querySelector('#app') as any;
8
+ r.vue3 = !!(app && app.__vue_app__);
9
+ r.vue2 = !!(app && app.__vue__);
10
+ r.react = !!(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ || !!document.querySelector('[data-reactroot]');
11
+ r.nextjs = !!(window as any).__NEXT_DATA__;
12
+ r.nuxt = !!(window as any).__NUXT__;
13
+ if (r.vue3 && app.__vue_app__) {
14
+ const gp = app.__vue_app__.config?.globalProperties;
15
+ r.pinia = !!(gp && gp.$pinia);
16
+ r.vuex = !!(gp && gp.$store);
17
+ }
18
+ } catch {}
19
+ return r;
20
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Injected script for interactive fuzzing (clicking elements to trigger lazy loading)
3
+ */
4
+ export async function interactFuzz() {
5
+ const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
6
+ const clickables = Array.from(document.querySelectorAll(
7
+ 'button, [role="button"], [role="tab"], .tab, .btn, a[href="javascript:void(0)"], a[href="#"]'
8
+ )).slice(0, 15); // limit to a small number to avoid endless loops
9
+
10
+ let clicked = 0;
11
+ for (const el of clickables) {
12
+ try {
13
+ const rect = el.getBoundingClientRect();
14
+ if (rect.width > 0 && rect.height > 0) {
15
+ el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
16
+ clicked++;
17
+ await sleep(300); // give it time to trigger network
18
+ }
19
+ } catch {}
20
+ }
21
+ return clicked;
22
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Injected script for discovering Pinia or Vuex stores and their actions/state representations
3
+ */
4
+ export function discoverStores() {
5
+ const stores: Array<{ type: string; id: string; actions: string[]; stateKeys: string[] }> = [];
6
+ try {
7
+ const app = document.querySelector('#app') as any;
8
+ if (!app?.__vue_app__) return stores;
9
+ const gp = app.__vue_app__.config?.globalProperties;
10
+
11
+ // Pinia stores
12
+ const pinia = gp?.$pinia;
13
+ if (pinia?._s) {
14
+ pinia._s.forEach((store: any, id: string) => {
15
+ const actions: string[] = [];
16
+ const stateKeys: string[] = [];
17
+ for (const k in store) {
18
+ try {
19
+ if (k.startsWith('$') || k.startsWith('_')) continue;
20
+ if (typeof store[k] === 'function') actions.push(k);
21
+ else stateKeys.push(k);
22
+ } catch {}
23
+ }
24
+ stores.push({ type: 'pinia', id, actions: actions.slice(0, 20), stateKeys: stateKeys.slice(0, 15) });
25
+ });
26
+ }
27
+
28
+ // Vuex store modules
29
+ const vuex = gp?.$store;
30
+ if (vuex?._modules?.root?._children) {
31
+ const children = vuex._modules.root._children;
32
+ for (const [modName, mod] of Object.entries<any>(children)) {
33
+ const actions = Object.keys(mod._rawModule?.actions ?? {}).slice(0, 20);
34
+ const stateKeys = Object.keys(mod.state ?? {}).slice(0, 15);
35
+ stores.push({ type: 'vuex', id: modName, actions, stateKeys });
36
+ }
37
+ }
38
+ } catch {}
39
+ return stores;
40
+ }
package/src/synthesize.ts CHANGED
@@ -116,7 +116,7 @@ function buildEvaluateScript(url: string, itemPath: string, endpoint: any): stri
116
116
 
117
117
  return [
118
118
  '(async () => {',
119
- ` const res = await fetch('${url}', {`,
119
+ ` const res = await fetch(${JSON.stringify(url)}, {`,
120
120
  ` credentials: 'include'`,
121
121
  ' });',
122
122
  ' const data = await res.json();',
package/src/types.ts CHANGED
@@ -8,6 +8,15 @@
8
8
  export interface IPage {
9
9
  goto(url: string): Promise<void>;
10
10
  evaluate(js: string): Promise<any>;
11
+ getCookies(opts?: { domain?: string; url?: string }): Promise<Array<{
12
+ name: string;
13
+ value: string;
14
+ domain: string;
15
+ path?: string;
16
+ secure?: boolean;
17
+ httpOnly?: boolean;
18
+ expirationDate?: number;
19
+ }>>;
11
20
  snapshot(opts?: { interactive?: boolean; compact?: boolean; maxDepth?: number; raw?: boolean }): Promise<any>;
12
21
  click(ref: string): Promise<void>;
13
22
  typeText(ref: string, text: string): Promise<void>;
package/src/verify.ts CHANGED
@@ -7,6 +7,9 @@
7
7
  */
8
8
 
9
9
  import { validateClisWithTarget, renderValidationReport, type ValidationReport } from './validate.js';
10
+ import { spawn } from 'node:child_process';
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
10
13
 
11
14
  export interface VerifyOptions {
12
15
  builtinClis: string;
@@ -18,15 +21,73 @@ export interface VerifyOptions {
18
21
  export interface VerifyReport {
19
22
  ok: boolean;
20
23
  validation: ValidationReport;
21
- smoke: null;
24
+ smoke: null | {
25
+ requested: boolean;
26
+ executed: boolean;
27
+ ok: boolean;
28
+ summary: string;
29
+ };
22
30
  }
23
31
 
24
32
  export async function verifyClis(opts: VerifyOptions): Promise<VerifyReport> {
25
33
  const report = validateClisWithTarget([opts.builtinClis, opts.userClis], opts.target);
26
- return { ok: report.ok, validation: report, smoke: null };
34
+ let smoke: VerifyReport['smoke'] = null;
35
+ if (opts.smoke) {
36
+ smoke = await runSmokeTests(opts.builtinClis);
37
+ }
38
+ return { ok: report.ok && (smoke?.ok ?? true), validation: report, smoke };
27
39
  }
28
40
 
29
41
  export function renderVerifyReport(report: VerifyReport): string {
30
- return renderValidationReport(report.validation);
42
+ const base = renderValidationReport(report.validation);
43
+ if (!report.smoke) return base;
44
+ const status = report.smoke.ok ? 'PASS' : 'FAIL';
45
+ const mode = report.smoke.executed ? 'executed' : 'skipped';
46
+ return `${base}\nSmoke: ${status} (${mode}) — ${report.smoke.summary}`;
31
47
  }
32
48
 
49
+ async function runSmokeTests(builtinClis: string): Promise<NonNullable<VerifyReport['smoke']>> {
50
+ const projectRoot = path.resolve(builtinClis, '..', '..');
51
+ const smokeDir = path.join(projectRoot, 'tests', 'smoke');
52
+
53
+ if (!fs.existsSync(smokeDir)) {
54
+ return {
55
+ requested: true,
56
+ executed: false,
57
+ ok: false,
58
+ summary: 'Smoke tests are unavailable in this package/environment.',
59
+ };
60
+ }
61
+
62
+ const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
63
+ return new Promise((resolve) => {
64
+ const child = spawn(npx, ['vitest', 'run', 'tests/smoke/', '--reporter=dot'], {
65
+ cwd: projectRoot,
66
+ env: { ...process.env },
67
+ stdio: ['ignore', 'pipe', 'pipe'],
68
+ });
69
+
70
+ let stderr = '';
71
+ child.stderr.on('data', (chunk) => {
72
+ stderr += chunk.toString();
73
+ });
74
+
75
+ child.on('error', (error) => {
76
+ resolve({
77
+ requested: true,
78
+ executed: false,
79
+ ok: false,
80
+ summary: `Failed to start smoke tests: ${error.message}`,
81
+ });
82
+ });
83
+
84
+ child.on('close', (code) => {
85
+ resolve({
86
+ requested: true,
87
+ executed: true,
88
+ ok: code === 0,
89
+ summary: code === 0 ? 'tests/smoke passed' : (stderr.trim() || `vitest exited with code ${code}`),
90
+ });
91
+ });
92
+ });
93
+ }