@jackwener/opencli 1.3.1 → 1.3.2

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 (217) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +44 -5
  3. package/README.zh-CN.md +44 -5
  4. package/SKILL.md +317 -5
  5. package/TESTING.md +4 -4
  6. package/dist/browser/errors.d.ts +2 -1
  7. package/dist/browser/errors.js +9 -10
  8. package/dist/build-manifest.js +1 -3
  9. package/dist/cli-manifest.json +2573 -989
  10. package/dist/cli.js +42 -2
  11. package/dist/clis/bilibili/download.js +20 -65
  12. package/dist/clis/bilibili/utils.js +2 -1
  13. package/dist/clis/chaoxing/assignments.js +2 -1
  14. package/dist/clis/doubao/ask.d.ts +1 -0
  15. package/dist/clis/doubao/ask.js +35 -0
  16. package/dist/clis/doubao/common.d.ts +23 -0
  17. package/dist/clis/doubao/common.js +564 -0
  18. package/dist/clis/doubao/new.d.ts +1 -0
  19. package/dist/clis/doubao/new.js +20 -0
  20. package/dist/clis/doubao/read.d.ts +1 -0
  21. package/dist/clis/doubao/read.js +19 -0
  22. package/dist/clis/doubao/send.d.ts +1 -0
  23. package/dist/clis/doubao/send.js +22 -0
  24. package/dist/clis/doubao/status.d.ts +1 -0
  25. package/dist/clis/doubao/status.js +24 -0
  26. package/dist/clis/doubao-app/ask.d.ts +1 -0
  27. package/dist/clis/doubao-app/ask.js +53 -0
  28. package/dist/clis/doubao-app/common.d.ts +37 -0
  29. package/dist/clis/doubao-app/common.js +110 -0
  30. package/dist/clis/doubao-app/dump.d.ts +1 -0
  31. package/dist/clis/doubao-app/dump.js +24 -0
  32. package/dist/clis/doubao-app/new.d.ts +1 -0
  33. package/dist/clis/doubao-app/new.js +20 -0
  34. package/dist/clis/doubao-app/read.d.ts +1 -0
  35. package/dist/clis/doubao-app/read.js +18 -0
  36. package/dist/clis/doubao-app/screenshot.d.ts +1 -0
  37. package/dist/clis/doubao-app/screenshot.js +18 -0
  38. package/dist/clis/doubao-app/send.d.ts +1 -0
  39. package/dist/clis/doubao-app/send.js +27 -0
  40. package/dist/clis/doubao-app/status.d.ts +1 -0
  41. package/dist/clis/doubao-app/status.js +16 -0
  42. package/dist/clis/hackernews/ask.yaml +38 -0
  43. package/dist/clis/hackernews/best.yaml +38 -0
  44. package/dist/clis/hackernews/jobs.yaml +36 -0
  45. package/dist/clis/hackernews/new.yaml +38 -0
  46. package/dist/clis/hackernews/search.yaml +44 -0
  47. package/dist/clis/hackernews/show.yaml +38 -0
  48. package/dist/clis/hackernews/top.yaml +3 -1
  49. package/dist/clis/hackernews/user.yaml +25 -0
  50. package/dist/clis/twitter/download.js +13 -97
  51. package/dist/clis/twitter/thread.js +2 -1
  52. package/dist/clis/v2ex/member.yaml +29 -0
  53. package/dist/clis/v2ex/node.yaml +34 -0
  54. package/dist/clis/v2ex/nodes.yaml +31 -0
  55. package/dist/clis/v2ex/replies.yaml +32 -0
  56. package/dist/clis/v2ex/user.yaml +34 -0
  57. package/dist/clis/weibo/search.d.ts +1 -0
  58. package/dist/clis/weibo/search.js +73 -0
  59. package/dist/clis/weixin/download.d.ts +12 -0
  60. package/dist/clis/weixin/download.js +183 -0
  61. package/dist/clis/xiaohongshu/download.js +12 -60
  62. package/dist/clis/xiaohongshu/publish.d.ts +18 -0
  63. package/dist/clis/xiaohongshu/publish.js +352 -0
  64. package/dist/clis/xiaohongshu/search.js +47 -15
  65. package/dist/clis/xiaohongshu/search.test.d.ts +1 -0
  66. package/dist/clis/xiaohongshu/search.test.js +114 -0
  67. package/dist/clis/yollomi/background.d.ts +4 -0
  68. package/dist/clis/yollomi/background.js +45 -0
  69. package/dist/clis/yollomi/edit.d.ts +5 -0
  70. package/dist/clis/yollomi/edit.js +56 -0
  71. package/dist/clis/yollomi/face-swap.d.ts +5 -0
  72. package/dist/clis/yollomi/face-swap.js +43 -0
  73. package/dist/clis/yollomi/generate.d.ts +9 -0
  74. package/dist/clis/yollomi/generate.js +100 -0
  75. package/dist/clis/yollomi/models.d.ts +1 -0
  76. package/dist/clis/yollomi/models.js +33 -0
  77. package/dist/clis/yollomi/object-remover.d.ts +4 -0
  78. package/dist/clis/yollomi/object-remover.js +42 -0
  79. package/dist/clis/yollomi/remove-bg.d.ts +4 -0
  80. package/dist/clis/yollomi/remove-bg.js +38 -0
  81. package/dist/clis/yollomi/restore.d.ts +4 -0
  82. package/dist/clis/yollomi/restore.js +38 -0
  83. package/dist/clis/yollomi/try-on.d.ts +4 -0
  84. package/dist/clis/yollomi/try-on.js +46 -0
  85. package/dist/clis/yollomi/upload.d.ts +7 -0
  86. package/dist/clis/yollomi/upload.js +71 -0
  87. package/dist/clis/yollomi/upscale.d.ts +4 -0
  88. package/dist/clis/yollomi/upscale.js +53 -0
  89. package/dist/clis/yollomi/utils.d.ts +45 -0
  90. package/dist/clis/yollomi/utils.js +180 -0
  91. package/dist/clis/yollomi/video.d.ts +5 -0
  92. package/dist/clis/yollomi/video.js +56 -0
  93. package/dist/clis/zhihu/download.d.ts +1 -5
  94. package/dist/clis/zhihu/download.js +20 -126
  95. package/dist/clis/zhihu/download.test.js +7 -5
  96. package/dist/clis/zhihu/question.js +2 -1
  97. package/dist/commanderAdapter.js +4 -6
  98. package/dist/daemon.js +5 -2
  99. package/dist/discovery.js +10 -10
  100. package/dist/download/article-download.d.ts +59 -0
  101. package/dist/download/article-download.js +178 -0
  102. package/dist/download/media-download.d.ts +49 -0
  103. package/dist/download/media-download.js +112 -0
  104. package/dist/errors.d.ts +23 -2
  105. package/dist/errors.js +58 -2
  106. package/dist/errors.test.d.ts +1 -0
  107. package/dist/errors.test.js +59 -0
  108. package/dist/execution.js +9 -10
  109. package/dist/explore.js +4 -2
  110. package/dist/external.d.ts +15 -0
  111. package/dist/external.js +48 -2
  112. package/dist/external.test.d.ts +1 -0
  113. package/dist/external.test.js +64 -0
  114. package/dist/main.js +10 -0
  115. package/dist/plugin.d.ts +4 -0
  116. package/dist/plugin.js +45 -23
  117. package/dist/plugin.test.js +6 -1
  118. package/dist/record.d.ts +47 -0
  119. package/dist/record.js +545 -0
  120. package/dist/registry.d.ts +7 -2
  121. package/dist/registry.js +2 -6
  122. package/dist/runtime.d.ts +3 -1
  123. package/dist/runtime.js +10 -3
  124. package/dist/validate.js +1 -3
  125. package/docs/.vitepress/config.mts +1 -0
  126. package/docs/adapters/browser/doubao.md +35 -0
  127. package/docs/adapters/browser/hackernews.md +20 -4
  128. package/docs/adapters/browser/tiktok.md +1 -1
  129. package/docs/adapters/browser/v2ex.md +31 -10
  130. package/docs/adapters/browser/weibo.md +4 -0
  131. package/docs/adapters/browser/weixin.md +33 -0
  132. package/docs/adapters/browser/xiaohongshu.md +8 -6
  133. package/docs/adapters/browser/yollomi.md +69 -0
  134. package/docs/adapters/desktop/doubao-app.md +35 -0
  135. package/docs/adapters/index.md +16 -5
  136. package/docs/advanced/download.md +4 -0
  137. package/package.json +3 -1
  138. package/src/browser/errors.ts +17 -11
  139. package/src/build-manifest.ts +2 -3
  140. package/src/cli.ts +45 -2
  141. package/src/clis/bilibili/download.ts +25 -83
  142. package/src/clis/bilibili/utils.ts +2 -1
  143. package/src/clis/chaoxing/assignments.ts +2 -1
  144. package/src/clis/doubao/ask.ts +40 -0
  145. package/src/clis/doubao/common.ts +619 -0
  146. package/src/clis/doubao/new.ts +22 -0
  147. package/src/clis/doubao/read.ts +20 -0
  148. package/src/clis/doubao/send.ts +25 -0
  149. package/src/clis/doubao/status.ts +27 -0
  150. package/src/clis/doubao-app/ask.ts +60 -0
  151. package/src/clis/doubao-app/common.ts +116 -0
  152. package/src/clis/doubao-app/dump.ts +28 -0
  153. package/src/clis/doubao-app/new.ts +21 -0
  154. package/src/clis/doubao-app/read.ts +21 -0
  155. package/src/clis/doubao-app/screenshot.ts +19 -0
  156. package/src/clis/doubao-app/send.ts +30 -0
  157. package/src/clis/doubao-app/status.ts +17 -0
  158. package/src/clis/hackernews/ask.yaml +38 -0
  159. package/src/clis/hackernews/best.yaml +38 -0
  160. package/src/clis/hackernews/jobs.yaml +36 -0
  161. package/src/clis/hackernews/new.yaml +38 -0
  162. package/src/clis/hackernews/search.yaml +44 -0
  163. package/src/clis/hackernews/show.yaml +38 -0
  164. package/src/clis/hackernews/top.yaml +3 -1
  165. package/src/clis/hackernews/user.yaml +25 -0
  166. package/src/clis/twitter/download.ts +13 -111
  167. package/src/clis/twitter/thread.ts +2 -1
  168. package/src/clis/v2ex/member.yaml +29 -0
  169. package/src/clis/v2ex/node.yaml +34 -0
  170. package/src/clis/v2ex/nodes.yaml +31 -0
  171. package/src/clis/v2ex/replies.yaml +32 -0
  172. package/src/clis/v2ex/user.yaml +34 -0
  173. package/src/clis/weibo/search.ts +78 -0
  174. package/src/clis/weixin/download.ts +199 -0
  175. package/src/clis/xiaohongshu/download.ts +12 -71
  176. package/src/clis/xiaohongshu/publish.ts +392 -0
  177. package/src/clis/xiaohongshu/search.test.ts +134 -0
  178. package/src/clis/xiaohongshu/search.ts +49 -15
  179. package/src/clis/yollomi/background.ts +48 -0
  180. package/src/clis/yollomi/edit.ts +58 -0
  181. package/src/clis/yollomi/face-swap.ts +45 -0
  182. package/src/clis/yollomi/generate.ts +95 -0
  183. package/src/clis/yollomi/models.ts +38 -0
  184. package/src/clis/yollomi/object-remover.ts +44 -0
  185. package/src/clis/yollomi/remove-bg.ts +40 -0
  186. package/src/clis/yollomi/restore.ts +40 -0
  187. package/src/clis/yollomi/try-on.ts +48 -0
  188. package/src/clis/yollomi/upload.ts +78 -0
  189. package/src/clis/yollomi/upscale.ts +49 -0
  190. package/src/clis/yollomi/utils.ts +202 -0
  191. package/src/clis/yollomi/video.ts +61 -0
  192. package/src/clis/zhihu/download.test.ts +7 -5
  193. package/src/clis/zhihu/download.ts +23 -158
  194. package/src/clis/zhihu/question.ts +2 -1
  195. package/src/commanderAdapter.ts +4 -7
  196. package/src/daemon.ts +5 -2
  197. package/src/discovery.ts +26 -26
  198. package/src/download/article-download.ts +272 -0
  199. package/src/download/media-download.ts +178 -0
  200. package/src/errors.test.ts +79 -0
  201. package/src/errors.ts +92 -2
  202. package/src/execution.ts +14 -10
  203. package/src/explore.ts +4 -2
  204. package/src/external.test.ts +88 -0
  205. package/src/external.ts +56 -2
  206. package/src/generate.ts +2 -1
  207. package/src/main.ts +10 -0
  208. package/src/plugin.test.ts +7 -1
  209. package/src/plugin.ts +49 -25
  210. package/src/record.ts +617 -0
  211. package/src/registry.ts +9 -5
  212. package/src/runtime.ts +16 -4
  213. package/src/validate.ts +2 -3
  214. package/tests/e2e/browser-auth.test.ts +10 -1
  215. package/tests/e2e/browser-public.test.ts +13 -8
  216. package/tests/e2e/public-commands.test.ts +209 -21
  217. package/tests/smoke/api-health.test.ts +65 -6
@@ -0,0 +1,27 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { DOUBAO_DOMAIN, DOUBAO_CHAT_URL, getDoubaoPageState } from './common.js';
4
+
5
+ export const statusCommand = cli({
6
+ site: 'doubao',
7
+ name: 'status',
8
+ description: 'Check Doubao chat page availability and login state',
9
+ domain: DOUBAO_DOMAIN,
10
+ strategy: Strategy.COOKIE,
11
+ browser: true,
12
+ navigateBefore: false,
13
+ args: [],
14
+ columns: ['Status', 'Login', 'Url', 'Title'],
15
+ func: async (page: IPage) => {
16
+ const state = await getDoubaoPageState(page);
17
+ const loggedIn = state.isLogin === null ? 'Unknown' : state.isLogin ? 'Yes' : 'No';
18
+ const status = state.isLogin === false ? 'Login Required' : 'Connected';
19
+
20
+ return [{
21
+ Status: status,
22
+ Login: loggedIn,
23
+ Url: state.url,
24
+ Title: state.title || 'Doubao',
25
+ }];
26
+ },
27
+ });
@@ -0,0 +1,60 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { SEL, injectTextScript, clickSendScript, pollResponseScript } from './common.js';
3
+
4
+ export const askCommand = cli({
5
+ site: 'doubao-app',
6
+ name: 'ask',
7
+ description: 'Send a message to Doubao desktop app and wait for the AI response',
8
+ domain: 'doubao-app',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'text', required: true, positional: true, help: 'Prompt to send' },
13
+ { name: 'timeout', type: 'int', default: 30, help: 'Max seconds to wait for response' },
14
+ ],
15
+ columns: ['Role', 'Text'],
16
+ func: async (page, kwargs) => {
17
+ const text = kwargs.text as string;
18
+ const timeout = (kwargs.timeout as number) || 30;
19
+
20
+ // Count existing messages before sending
21
+ const beforeCount = await page.evaluate(
22
+ `document.querySelectorAll('${SEL.MESSAGE}').length`
23
+ );
24
+
25
+ // Inject text + send
26
+ const injected = await page.evaluate(injectTextScript(text));
27
+ if (!injected?.ok) throw new Error('Could not find chat input.');
28
+ await page.wait(0.5);
29
+
30
+ const clicked = await page.evaluate(clickSendScript());
31
+ if (!clicked) await page.pressKey('Enter');
32
+
33
+ // Poll for response
34
+ const pollInterval = 1;
35
+ const maxPolls = Math.ceil(timeout / pollInterval);
36
+ let response = '';
37
+
38
+ for (let i = 0; i < maxPolls; i++) {
39
+ await page.wait(pollInterval);
40
+ const result = await page.evaluate(pollResponseScript(beforeCount));
41
+ if (!result) continue;
42
+ if (result.phase === 'done' && result.text) {
43
+ response = result.text;
44
+ break;
45
+ }
46
+ }
47
+
48
+ if (!response) {
49
+ return [
50
+ { Role: 'User', Text: text },
51
+ { Role: 'System', Text: `No response received within ${timeout}s.` },
52
+ ];
53
+ }
54
+
55
+ return [
56
+ { Role: 'User', Text: text },
57
+ { Role: 'Assistant', Text: response },
58
+ ];
59
+ },
60
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Shared constants and helpers for Doubao desktop app (Electron + CDP).
3
+ *
4
+ * Requires: Doubao launched with --remote-debugging-port=9226
5
+ */
6
+
7
+ /** Selectors discovered via data-testid attributes */
8
+ export const SEL = {
9
+ INPUT: '[data-testid="chat_input_input"]',
10
+ SEND_BTN: '[data-testid="chat_input_send_button"]',
11
+ MESSAGE: '[data-testid="message_content"]',
12
+ MESSAGE_TEXT: '[data-testid="message_text_content"]',
13
+ INDICATOR: '[data-testid="indicator"]',
14
+ NEW_CHAT: '[data-testid="new_chat_button"]',
15
+ NEW_CHAT_SIDEBAR: '[data-testid="app-open-newChat"]',
16
+ } as const;
17
+
18
+ /**
19
+ * Inject text into the Doubao chat textarea via React-compatible value setter.
20
+ * Returns an evaluate script string.
21
+ */
22
+ export function injectTextScript(text: string): string {
23
+ return `(function(t) {
24
+ const textarea = document.querySelector('${SEL.INPUT}');
25
+ if (!textarea) return { ok: false, error: 'No textarea found' };
26
+ textarea.focus();
27
+ const setter = Object.getOwnPropertyDescriptor(
28
+ window.HTMLTextAreaElement.prototype, 'value'
29
+ )?.set;
30
+ if (setter) setter.call(textarea, t);
31
+ else textarea.value = t;
32
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
33
+ textarea.dispatchEvent(new Event('change', { bubbles: true }));
34
+ return { ok: true };
35
+ })(${JSON.stringify(text)})`;
36
+ }
37
+
38
+ /**
39
+ * Click the send button. Returns an evaluate script string.
40
+ */
41
+ export function clickSendScript(): string {
42
+ return `(function() {
43
+ const btn = document.querySelector('${SEL.SEND_BTN}');
44
+ if (!btn) return false;
45
+ btn.click();
46
+ return true;
47
+ })()`;
48
+ }
49
+
50
+ /**
51
+ * Read all chat messages from the DOM. Returns an evaluate script string.
52
+ */
53
+ export function readMessagesScript(): string {
54
+ return `(function() {
55
+ const results = [];
56
+ const containers = document.querySelectorAll('${SEL.MESSAGE}');
57
+ for (const container of containers) {
58
+ const textEl = container.querySelector('${SEL.MESSAGE_TEXT}');
59
+ if (!textEl) continue;
60
+ // Skip streaming messages
61
+ if (textEl.querySelector('${SEL.INDICATOR}') ||
62
+ textEl.getAttribute('data-show-indicator') === 'true') continue;
63
+ const isUser = container.classList.contains('justify-end');
64
+ let text = '';
65
+ const children = textEl.querySelectorAll('div[dir]');
66
+ if (children.length > 0) {
67
+ text = Array.from(children).map(c => c.innerText || c.textContent || '').join('');
68
+ } else {
69
+ text = textEl.innerText?.trim() || textEl.textContent?.trim() || '';
70
+ }
71
+ if (!text) continue;
72
+ results.push({ role: isUser ? 'User' : 'Assistant', text: text.substring(0, 2000) });
73
+ }
74
+ return results;
75
+ })()`;
76
+ }
77
+
78
+ /**
79
+ * Click the new-chat button. Returns an evaluate script string.
80
+ */
81
+ export function clickNewChatScript(): string {
82
+ return `(function() {
83
+ let btn = document.querySelector('${SEL.NEW_CHAT}');
84
+ if (btn) { btn.click(); return true; }
85
+ btn = document.querySelector('${SEL.NEW_CHAT_SIDEBAR}');
86
+ if (btn) { btn.click(); return true; }
87
+ return false;
88
+ })()`;
89
+ }
90
+
91
+ /**
92
+ * Poll for a new assistant response after sending.
93
+ * Returns evaluate script that checks message count vs baseline.
94
+ */
95
+ export function pollResponseScript(beforeCount: number): string {
96
+ return `(function(prevCount) {
97
+ const msgs = document.querySelectorAll('${SEL.MESSAGE}');
98
+ if (msgs.length <= prevCount) return { phase: 'waiting', text: null };
99
+ const lastMsg = msgs[msgs.length - 1];
100
+ if (lastMsg.classList.contains('justify-end')) return { phase: 'waiting', text: null };
101
+ const textEl = lastMsg.querySelector('${SEL.MESSAGE_TEXT}');
102
+ if (!textEl) return { phase: 'waiting', text: null };
103
+ if (textEl.querySelector('${SEL.INDICATOR}') ||
104
+ textEl.getAttribute('data-show-indicator') === 'true') {
105
+ return { phase: 'streaming', text: null };
106
+ }
107
+ let text = '';
108
+ const children = textEl.querySelectorAll('div[dir]');
109
+ if (children.length > 0) {
110
+ text = Array.from(children).map(c => c.innerText || c.textContent || '').join('');
111
+ } else {
112
+ text = textEl.innerText?.trim() || textEl.textContent?.trim() || '';
113
+ }
114
+ return { phase: 'done', text };
115
+ })(${beforeCount})`;
116
+ }
@@ -0,0 +1,28 @@
1
+ import * as fs from 'node:fs';
2
+ import { cli, Strategy } from '../../registry.js';
3
+
4
+ export const dumpCommand = cli({
5
+ site: 'doubao-app',
6
+ name: 'dump',
7
+ description: 'Dump Doubao desktop app DOM and snapshot to /tmp for debugging',
8
+ domain: 'doubao-app',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Status', 'File'],
13
+ func: async (page) => {
14
+ const htmlPath = '/tmp/doubao-dom.html';
15
+ const snapPath = '/tmp/doubao-snapshot.json';
16
+
17
+ const html = await page.evaluate('document.documentElement.outerHTML');
18
+ const snap = await page.snapshot({ compact: true });
19
+
20
+ fs.writeFileSync(htmlPath, html);
21
+ fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
22
+
23
+ return [
24
+ { Status: 'Success', File: htmlPath },
25
+ { Status: 'Success', File: snapPath },
26
+ ];
27
+ },
28
+ });
@@ -0,0 +1,21 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { clickNewChatScript } from './common.js';
3
+
4
+ export const newCommand = cli({
5
+ site: 'doubao-app',
6
+ name: 'new',
7
+ description: 'Start a new chat in Doubao desktop app',
8
+ domain: 'doubao-app',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Status'],
13
+ func: async (page) => {
14
+ const clicked = await page.evaluate(clickNewChatScript());
15
+ if (!clicked) {
16
+ await page.pressKey('Meta+N');
17
+ }
18
+ await page.wait(3);
19
+ return [{ Status: 'Success' }];
20
+ },
21
+ });
@@ -0,0 +1,21 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { readMessagesScript } from './common.js';
3
+
4
+ export const readCommand = cli({
5
+ site: 'doubao-app',
6
+ name: 'read',
7
+ description: 'Read chat history from Doubao desktop app',
8
+ domain: 'doubao-app',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ columns: ['Role', 'Text'],
12
+ func: async (page) => {
13
+ const messages = await page.evaluate(readMessagesScript());
14
+
15
+ if (!messages || messages.length === 0) {
16
+ return [{ Role: 'System', Text: 'No conversation found' }];
17
+ }
18
+
19
+ return messages.map((m: any) => ({ Role: m.role, Text: m.text }));
20
+ },
21
+ });
@@ -0,0 +1,19 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ export const screenshotCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'screenshot',
6
+ description: 'Capture a screenshot of the Doubao desktop app window',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [
11
+ { name: 'output', required: false, help: 'Output file path (default: /tmp/doubao-screenshot.png)' },
12
+ ],
13
+ columns: ['Status', 'File'],
14
+ func: async (page, kwargs) => {
15
+ const outputPath = (kwargs.output as string) || '/tmp/doubao-screenshot.png';
16
+ await page.screenshot({ path: outputPath });
17
+ return [{ Status: 'Success', File: outputPath }];
18
+ },
19
+ });
@@ -0,0 +1,30 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { injectTextScript, clickSendScript } from './common.js';
3
+
4
+ export const sendCommand = cli({
5
+ site: 'doubao-app',
6
+ name: 'send',
7
+ description: 'Send a message to Doubao desktop app',
8
+ domain: 'doubao-app',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'text', required: true, positional: true, help: 'Message text to send' },
13
+ ],
14
+ columns: ['Status', 'Text'],
15
+ func: async (page, kwargs) => {
16
+ const text = kwargs.text as string;
17
+
18
+ const injected = await page.evaluate(injectTextScript(text));
19
+ if (!injected || !injected.ok) {
20
+ throw new Error('Could not find chat input: ' + (injected?.error || 'unknown'));
21
+ }
22
+ await page.wait(0.5);
23
+
24
+ const clicked = await page.evaluate(clickSendScript());
25
+ if (!clicked) await page.pressKey('Enter');
26
+
27
+ await page.wait(1);
28
+ return [{ Status: 'Sent', Text: text }];
29
+ },
30
+ });
@@ -0,0 +1,17 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ export const statusCommand = cli({
4
+ site: 'doubao-app',
5
+ name: 'status',
6
+ description: 'Check CDP connection to Doubao desktop app',
7
+ domain: 'doubao-app',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [],
11
+ columns: ['Status', 'Url', 'Title'],
12
+ func: async (page) => {
13
+ const url = await page.evaluate('window.location.href');
14
+ const title = await page.evaluate('document.title');
15
+ return [{ Status: 'Connected', Url: url, Title: title }];
16
+ },
17
+ });
@@ -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]