@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,53 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { SEL, injectTextScript, clickSendScript, pollResponseScript } from './common.js';
3
+ export const askCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'ask',
6
+ description: 'Send a message to Doubao desktop app and wait for the AI response',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [
11
+ { name: 'text', required: true, positional: true, help: 'Prompt to send' },
12
+ { name: 'timeout', type: 'int', default: 30, help: 'Max seconds to wait for response' },
13
+ ],
14
+ columns: ['Role', 'Text'],
15
+ func: async (page, kwargs) => {
16
+ const text = kwargs.text;
17
+ const timeout = kwargs.timeout || 30;
18
+ // Count existing messages before sending
19
+ const beforeCount = await page.evaluate(`document.querySelectorAll('${SEL.MESSAGE}').length`);
20
+ // Inject text + send
21
+ const injected = await page.evaluate(injectTextScript(text));
22
+ if (!injected?.ok)
23
+ throw new Error('Could not find chat input.');
24
+ await page.wait(0.5);
25
+ const clicked = await page.evaluate(clickSendScript());
26
+ if (!clicked)
27
+ await page.pressKey('Enter');
28
+ // Poll for response
29
+ const pollInterval = 1;
30
+ const maxPolls = Math.ceil(timeout / pollInterval);
31
+ let response = '';
32
+ for (let i = 0; i < maxPolls; i++) {
33
+ await page.wait(pollInterval);
34
+ const result = await page.evaluate(pollResponseScript(beforeCount));
35
+ if (!result)
36
+ continue;
37
+ if (result.phase === 'done' && result.text) {
38
+ response = result.text;
39
+ break;
40
+ }
41
+ }
42
+ if (!response) {
43
+ return [
44
+ { Role: 'User', Text: text },
45
+ { Role: 'System', Text: `No response received within ${timeout}s.` },
46
+ ];
47
+ }
48
+ return [
49
+ { Role: 'User', Text: text },
50
+ { Role: 'Assistant', Text: response },
51
+ ];
52
+ },
53
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared constants and helpers for Doubao desktop app (Electron + CDP).
3
+ *
4
+ * Requires: Doubao launched with --remote-debugging-port=9226
5
+ */
6
+ /** Selectors discovered via data-testid attributes */
7
+ export declare const SEL: {
8
+ readonly INPUT: "[data-testid=\"chat_input_input\"]";
9
+ readonly SEND_BTN: "[data-testid=\"chat_input_send_button\"]";
10
+ readonly MESSAGE: "[data-testid=\"message_content\"]";
11
+ readonly MESSAGE_TEXT: "[data-testid=\"message_text_content\"]";
12
+ readonly INDICATOR: "[data-testid=\"indicator\"]";
13
+ readonly NEW_CHAT: "[data-testid=\"new_chat_button\"]";
14
+ readonly NEW_CHAT_SIDEBAR: "[data-testid=\"app-open-newChat\"]";
15
+ };
16
+ /**
17
+ * Inject text into the Doubao chat textarea via React-compatible value setter.
18
+ * Returns an evaluate script string.
19
+ */
20
+ export declare function injectTextScript(text: string): string;
21
+ /**
22
+ * Click the send button. Returns an evaluate script string.
23
+ */
24
+ export declare function clickSendScript(): string;
25
+ /**
26
+ * Read all chat messages from the DOM. Returns an evaluate script string.
27
+ */
28
+ export declare function readMessagesScript(): string;
29
+ /**
30
+ * Click the new-chat button. Returns an evaluate script string.
31
+ */
32
+ export declare function clickNewChatScript(): string;
33
+ /**
34
+ * Poll for a new assistant response after sending.
35
+ * Returns evaluate script that checks message count vs baseline.
36
+ */
37
+ export declare function pollResponseScript(beforeCount: number): string;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Shared constants and helpers for Doubao desktop app (Electron + CDP).
3
+ *
4
+ * Requires: Doubao launched with --remote-debugging-port=9226
5
+ */
6
+ /** Selectors discovered via data-testid attributes */
7
+ export const SEL = {
8
+ INPUT: '[data-testid="chat_input_input"]',
9
+ SEND_BTN: '[data-testid="chat_input_send_button"]',
10
+ MESSAGE: '[data-testid="message_content"]',
11
+ MESSAGE_TEXT: '[data-testid="message_text_content"]',
12
+ INDICATOR: '[data-testid="indicator"]',
13
+ NEW_CHAT: '[data-testid="new_chat_button"]',
14
+ NEW_CHAT_SIDEBAR: '[data-testid="app-open-newChat"]',
15
+ };
16
+ /**
17
+ * Inject text into the Doubao chat textarea via React-compatible value setter.
18
+ * Returns an evaluate script string.
19
+ */
20
+ export function injectTextScript(text) {
21
+ return `(function(t) {
22
+ const textarea = document.querySelector('${SEL.INPUT}');
23
+ if (!textarea) return { ok: false, error: 'No textarea found' };
24
+ textarea.focus();
25
+ const setter = Object.getOwnPropertyDescriptor(
26
+ window.HTMLTextAreaElement.prototype, 'value'
27
+ )?.set;
28
+ if (setter) setter.call(textarea, t);
29
+ else textarea.value = t;
30
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
31
+ textarea.dispatchEvent(new Event('change', { bubbles: true }));
32
+ return { ok: true };
33
+ })(${JSON.stringify(text)})`;
34
+ }
35
+ /**
36
+ * Click the send button. Returns an evaluate script string.
37
+ */
38
+ export function clickSendScript() {
39
+ return `(function() {
40
+ const btn = document.querySelector('${SEL.SEND_BTN}');
41
+ if (!btn) return false;
42
+ btn.click();
43
+ return true;
44
+ })()`;
45
+ }
46
+ /**
47
+ * Read all chat messages from the DOM. Returns an evaluate script string.
48
+ */
49
+ export function readMessagesScript() {
50
+ return `(function() {
51
+ const results = [];
52
+ const containers = document.querySelectorAll('${SEL.MESSAGE}');
53
+ for (const container of containers) {
54
+ const textEl = container.querySelector('${SEL.MESSAGE_TEXT}');
55
+ if (!textEl) continue;
56
+ // Skip streaming messages
57
+ if (textEl.querySelector('${SEL.INDICATOR}') ||
58
+ textEl.getAttribute('data-show-indicator') === 'true') continue;
59
+ const isUser = container.classList.contains('justify-end');
60
+ let text = '';
61
+ const children = textEl.querySelectorAll('div[dir]');
62
+ if (children.length > 0) {
63
+ text = Array.from(children).map(c => c.innerText || c.textContent || '').join('');
64
+ } else {
65
+ text = textEl.innerText?.trim() || textEl.textContent?.trim() || '';
66
+ }
67
+ if (!text) continue;
68
+ results.push({ role: isUser ? 'User' : 'Assistant', text: text.substring(0, 2000) });
69
+ }
70
+ return results;
71
+ })()`;
72
+ }
73
+ /**
74
+ * Click the new-chat button. Returns an evaluate script string.
75
+ */
76
+ export function clickNewChatScript() {
77
+ return `(function() {
78
+ let btn = document.querySelector('${SEL.NEW_CHAT}');
79
+ if (btn) { btn.click(); return true; }
80
+ btn = document.querySelector('${SEL.NEW_CHAT_SIDEBAR}');
81
+ if (btn) { btn.click(); return true; }
82
+ return false;
83
+ })()`;
84
+ }
85
+ /**
86
+ * Poll for a new assistant response after sending.
87
+ * Returns evaluate script that checks message count vs baseline.
88
+ */
89
+ export function pollResponseScript(beforeCount) {
90
+ return `(function(prevCount) {
91
+ const msgs = document.querySelectorAll('${SEL.MESSAGE}');
92
+ if (msgs.length <= prevCount) return { phase: 'waiting', text: null };
93
+ const lastMsg = msgs[msgs.length - 1];
94
+ if (lastMsg.classList.contains('justify-end')) return { phase: 'waiting', text: null };
95
+ const textEl = lastMsg.querySelector('${SEL.MESSAGE_TEXT}');
96
+ if (!textEl) return { phase: 'waiting', text: null };
97
+ if (textEl.querySelector('${SEL.INDICATOR}') ||
98
+ textEl.getAttribute('data-show-indicator') === 'true') {
99
+ return { phase: 'streaming', text: null };
100
+ }
101
+ let text = '';
102
+ const children = textEl.querySelectorAll('div[dir]');
103
+ if (children.length > 0) {
104
+ text = Array.from(children).map(c => c.innerText || c.textContent || '').join('');
105
+ } else {
106
+ text = textEl.innerText?.trim() || textEl.textContent?.trim() || '';
107
+ }
108
+ return { phase: 'done', text };
109
+ })(${beforeCount})`;
110
+ }
@@ -0,0 +1 @@
1
+ export declare const dumpCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,24 @@
1
+ import * as fs from 'node:fs';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ export const dumpCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'dump',
6
+ description: 'Dump Doubao desktop app DOM and snapshot to /tmp for debugging',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [],
11
+ columns: ['Status', 'File'],
12
+ func: async (page) => {
13
+ const htmlPath = '/tmp/doubao-dom.html';
14
+ const snapPath = '/tmp/doubao-snapshot.json';
15
+ const html = await page.evaluate('document.documentElement.outerHTML');
16
+ const snap = await page.snapshot({ compact: true });
17
+ fs.writeFileSync(htmlPath, html);
18
+ fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
19
+ return [
20
+ { Status: 'Success', File: htmlPath },
21
+ { Status: 'Success', File: snapPath },
22
+ ];
23
+ },
24
+ });
@@ -0,0 +1 @@
1
+ export declare const newCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,20 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { clickNewChatScript } from './common.js';
3
+ export const newCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'new',
6
+ description: 'Start a new chat in Doubao desktop app',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [],
11
+ columns: ['Status'],
12
+ func: async (page) => {
13
+ const clicked = await page.evaluate(clickNewChatScript());
14
+ if (!clicked) {
15
+ await page.pressKey('Meta+N');
16
+ }
17
+ await page.wait(3);
18
+ return [{ Status: 'Success' }];
19
+ },
20
+ });
@@ -0,0 +1 @@
1
+ export declare const readCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { readMessagesScript } from './common.js';
3
+ export const readCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'read',
6
+ description: 'Read chat history from Doubao desktop app',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ columns: ['Role', 'Text'],
11
+ func: async (page) => {
12
+ const messages = await page.evaluate(readMessagesScript());
13
+ if (!messages || messages.length === 0) {
14
+ return [{ Role: 'System', Text: 'No conversation found' }];
15
+ }
16
+ return messages.map((m) => ({ Role: m.role, Text: m.text }));
17
+ },
18
+ });
@@ -0,0 +1 @@
1
+ export declare const screenshotCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const screenshotCommand = cli({
3
+ site: 'doubao-app',
4
+ name: 'screenshot',
5
+ description: 'Capture a screenshot of the Doubao desktop app window',
6
+ domain: 'doubao-app',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [
10
+ { name: 'output', required: false, help: 'Output file path (default: /tmp/doubao-screenshot.png)' },
11
+ ],
12
+ columns: ['Status', 'File'],
13
+ func: async (page, kwargs) => {
14
+ const outputPath = kwargs.output || '/tmp/doubao-screenshot.png';
15
+ await page.screenshot({ path: outputPath });
16
+ return [{ Status: 'Success', File: outputPath }];
17
+ },
18
+ });
@@ -0,0 +1 @@
1
+ export declare const sendCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,27 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { injectTextScript, clickSendScript } from './common.js';
3
+ export const sendCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'send',
6
+ description: 'Send a message to Doubao desktop app',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [
11
+ { name: 'text', required: true, positional: true, help: 'Message text to send' },
12
+ ],
13
+ columns: ['Status', 'Text'],
14
+ func: async (page, kwargs) => {
15
+ const text = kwargs.text;
16
+ const injected = await page.evaluate(injectTextScript(text));
17
+ if (!injected || !injected.ok) {
18
+ throw new Error('Could not find chat input: ' + (injected?.error || 'unknown'));
19
+ }
20
+ await page.wait(0.5);
21
+ const clicked = await page.evaluate(clickSendScript());
22
+ if (!clicked)
23
+ await page.pressKey('Enter');
24
+ await page.wait(1);
25
+ return [{ Status: 'Sent', Text: text }];
26
+ },
27
+ });
@@ -0,0 +1 @@
1
+ export declare const statusCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const statusCommand = cli({
3
+ site: 'doubao-app',
4
+ name: 'status',
5
+ description: 'Check CDP connection to Doubao desktop app',
6
+ domain: 'doubao-app',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Status', 'Url', 'Title'],
11
+ func: async (page) => {
12
+ const url = await page.evaluate('window.location.href');
13
+ const title = await page.evaluate('document.title');
14
+ return [{ Status: 'Connected', Url: url, Title: title }];
15
+ },
16
+ });
@@ -0,0 +1,38 @@
1
+ site: hackernews
2
+ name: ask
3
+ description: Hacker News Ask HN posts
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of stories
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/askstories.json
17
+
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
28
+ - map:
29
+ rank: ${{ index + 1 }}
30
+ title: ${{ item.title }}
31
+ score: ${{ item.score }}
32
+ author: ${{ item.by }}
33
+ comments: ${{ item.descendants }}
34
+ url: ${{ item.url }}
35
+
36
+ - limit: ${{ args.limit }}
37
+
38
+ columns: [rank, title, score, author, comments]
@@ -0,0 +1,38 @@
1
+ site: hackernews
2
+ name: best
3
+ description: Hacker News best stories
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of stories
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/beststories.json
17
+
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
28
+ - map:
29
+ rank: ${{ index + 1 }}
30
+ title: ${{ item.title }}
31
+ score: ${{ item.score }}
32
+ author: ${{ item.by }}
33
+ comments: ${{ item.descendants }}
34
+ url: ${{ item.url }}
35
+
36
+ - limit: ${{ args.limit }}
37
+
38
+ columns: [rank, title, score, author, comments]
@@ -0,0 +1,36 @@
1
+ site: hackernews
2
+ name: jobs
3
+ description: Hacker News job postings
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of job postings
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/jobstories.json
17
+
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
28
+ - map:
29
+ rank: ${{ index + 1 }}
30
+ title: ${{ item.title }}
31
+ author: ${{ item.by }}
32
+ url: ${{ item.url }}
33
+
34
+ - limit: ${{ args.limit }}
35
+
36
+ columns: [rank, title, author, url]
@@ -0,0 +1,38 @@
1
+ site: hackernews
2
+ name: new
3
+ description: Hacker News newest stories
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of stories
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/newstories.json
17
+
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
28
+ - map:
29
+ rank: ${{ index + 1 }}
30
+ title: ${{ item.title }}
31
+ score: ${{ item.score }}
32
+ author: ${{ item.by }}
33
+ comments: ${{ item.descendants }}
34
+ url: ${{ item.url }}
35
+
36
+ - limit: ${{ args.limit }}
37
+
38
+ columns: [rank, title, score, author, comments]
@@ -0,0 +1,44 @@
1
+ site: hackernews
2
+ name: search
3
+ description: Search Hacker News stories
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ query:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: Search query
14
+ limit:
15
+ type: int
16
+ default: 20
17
+ description: Number of results
18
+ sort:
19
+ type: str
20
+ default: relevance
21
+ choices: [relevance, date]
22
+ description: Sort by relevance or date
23
+
24
+ pipeline:
25
+ - fetch:
26
+ url: "https://hn.algolia.com/api/v1/${{ args.sort === 'date' ? 'search_by_date' : 'search' }}"
27
+ params:
28
+ query: ${{ args.query }}
29
+ tags: story
30
+ hitsPerPage: ${{ args.limit }}
31
+
32
+ - select: hits
33
+
34
+ - map:
35
+ rank: ${{ index + 1 }}
36
+ title: ${{ item.title }}
37
+ score: ${{ item.points }}
38
+ author: ${{ item.author }}
39
+ comments: ${{ item.num_comments }}
40
+ url: ${{ item.url }}
41
+
42
+ - limit: ${{ args.limit }}
43
+
44
+ columns: [rank, title, score, author, comments]
@@ -0,0 +1,38 @@
1
+ site: hackernews
2
+ name: show
3
+ description: Hacker News Show HN posts
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of stories
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/showstories.json
17
+
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
28
+ - map:
29
+ rank: ${{ index + 1 }}
30
+ title: ${{ item.title }}
31
+ score: ${{ item.score }}
32
+ author: ${{ item.by }}
33
+ comments: ${{ item.descendants }}
34
+ url: ${{ item.url }}
35
+
36
+ - limit: ${{ args.limit }}
37
+
38
+ columns: [rank, title, score, author, comments]
@@ -15,7 +15,7 @@ pipeline:
15
15
  - fetch:
16
16
  url: https://hacker-news.firebaseio.com/v0/topstories.json
17
17
 
18
- - limit: 30
18
+ - limit: "${{ Math.min((args.limit ? args.limit : 20) + 10, 50) }}"
19
19
 
20
20
  - map:
21
21
  id: ${{ item }}
@@ -23,6 +23,8 @@ pipeline:
23
23
  - fetch:
24
24
  url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
25
 
26
+ - filter: item.title && !item.deleted && !item.dead
27
+
26
28
  - map:
27
29
  rank: ${{ index + 1 }}
28
30
  title: ${{ item.title }}
@@ -0,0 +1,25 @@
1
+ site: hackernews
2
+ name: user
3
+ description: Hacker News user profile
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ username:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: HN username
14
+
15
+ pipeline:
16
+ - fetch:
17
+ url: https://hacker-news.firebaseio.com/v0/user/${{ args.username }}.json
18
+
19
+ - map:
20
+ username: ${{ item.id }}
21
+ karma: ${{ item.karma }}
22
+ created: "${{ item.created ? new Date(item.created * 1000).toISOString().slice(0, 10) : '' }}"
23
+ about: ${{ item.about }}
24
+
25
+ columns: [username, karma, created, about]