@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.
- package/CHANGELOG.md +128 -0
- package/README.md +48 -9
- package/README.zh-CN.md +48 -9
- package/SKILL.md +317 -6
- package/TESTING.md +4 -4
- package/dist/browser/cdp.js +10 -1
- package/dist/browser/daemon-client.js +2 -1
- package/dist/browser/discover.js +2 -1
- package/dist/browser/errors.d.ts +2 -1
- package/dist/browser/errors.js +10 -10
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/page.js +12 -0
- package/dist/browser/stealth.d.ts +18 -0
- package/dist/browser/stealth.js +140 -0
- package/dist/browser.test.js +47 -1
- package/dist/build-manifest.js +1 -3
- package/dist/cli-manifest.json +2573 -989
- package/dist/cli.js +42 -2
- package/dist/clis/bilibili/download.js +20 -65
- package/dist/clis/bilibili/utils.js +2 -1
- package/dist/clis/chaoxing/assignments.js +2 -1
- package/dist/clis/doubao/ask.d.ts +1 -0
- package/dist/clis/doubao/ask.js +35 -0
- package/dist/clis/doubao/common.d.ts +23 -0
- package/dist/clis/doubao/common.js +564 -0
- package/dist/clis/doubao/new.d.ts +1 -0
- package/dist/clis/doubao/new.js +20 -0
- package/dist/clis/doubao/read.d.ts +1 -0
- package/dist/clis/doubao/read.js +19 -0
- package/dist/clis/doubao/send.d.ts +1 -0
- package/dist/clis/doubao/send.js +22 -0
- package/dist/clis/doubao/status.d.ts +1 -0
- package/dist/clis/doubao/status.js +24 -0
- package/dist/clis/doubao-app/ask.d.ts +1 -0
- package/dist/clis/doubao-app/ask.js +53 -0
- package/dist/clis/doubao-app/common.d.ts +37 -0
- package/dist/clis/doubao-app/common.js +110 -0
- package/dist/clis/doubao-app/dump.d.ts +1 -0
- package/dist/clis/doubao-app/dump.js +24 -0
- package/dist/clis/doubao-app/new.d.ts +1 -0
- package/dist/clis/doubao-app/new.js +20 -0
- package/dist/clis/doubao-app/read.d.ts +1 -0
- package/dist/clis/doubao-app/read.js +18 -0
- package/dist/clis/doubao-app/screenshot.d.ts +1 -0
- package/dist/clis/doubao-app/screenshot.js +18 -0
- package/dist/clis/doubao-app/send.d.ts +1 -0
- package/dist/clis/doubao-app/send.js +27 -0
- package/dist/clis/doubao-app/status.d.ts +1 -0
- package/dist/clis/doubao-app/status.js +16 -0
- package/dist/clis/hackernews/ask.yaml +38 -0
- package/dist/clis/hackernews/best.yaml +38 -0
- package/dist/clis/hackernews/jobs.yaml +36 -0
- package/dist/clis/hackernews/new.yaml +38 -0
- package/dist/clis/hackernews/search.yaml +44 -0
- package/dist/clis/hackernews/show.yaml +38 -0
- package/dist/clis/hackernews/top.yaml +3 -1
- package/dist/clis/hackernews/user.yaml +25 -0
- package/dist/clis/twitter/download.js +13 -97
- package/dist/clis/twitter/thread.js +2 -1
- package/dist/clis/v2ex/member.yaml +29 -0
- package/dist/clis/v2ex/node.yaml +34 -0
- package/dist/clis/v2ex/nodes.yaml +31 -0
- package/dist/clis/v2ex/replies.yaml +32 -0
- package/dist/clis/v2ex/user.yaml +34 -0
- package/dist/clis/weibo/search.d.ts +1 -0
- package/dist/clis/weibo/search.js +73 -0
- package/dist/clis/weixin/download.d.ts +12 -0
- package/dist/clis/weixin/download.js +183 -0
- package/dist/clis/xiaohongshu/download.js +12 -60
- package/dist/clis/xiaohongshu/publish.d.ts +18 -0
- package/dist/clis/xiaohongshu/publish.js +352 -0
- package/dist/clis/xiaohongshu/search.js +47 -15
- package/dist/clis/xiaohongshu/search.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/search.test.js +114 -0
- package/dist/clis/yollomi/background.d.ts +4 -0
- package/dist/clis/yollomi/background.js +45 -0
- package/dist/clis/yollomi/edit.d.ts +5 -0
- package/dist/clis/yollomi/edit.js +56 -0
- package/dist/clis/yollomi/face-swap.d.ts +5 -0
- package/dist/clis/yollomi/face-swap.js +43 -0
- package/dist/clis/yollomi/generate.d.ts +9 -0
- package/dist/clis/yollomi/generate.js +100 -0
- package/dist/clis/yollomi/models.d.ts +1 -0
- package/dist/clis/yollomi/models.js +33 -0
- package/dist/clis/yollomi/object-remover.d.ts +4 -0
- package/dist/clis/yollomi/object-remover.js +42 -0
- package/dist/clis/yollomi/remove-bg.d.ts +4 -0
- package/dist/clis/yollomi/remove-bg.js +38 -0
- package/dist/clis/yollomi/restore.d.ts +4 -0
- package/dist/clis/yollomi/restore.js +38 -0
- package/dist/clis/yollomi/try-on.d.ts +4 -0
- package/dist/clis/yollomi/try-on.js +46 -0
- package/dist/clis/yollomi/upload.d.ts +7 -0
- package/dist/clis/yollomi/upload.js +71 -0
- package/dist/clis/yollomi/upscale.d.ts +4 -0
- package/dist/clis/yollomi/upscale.js +53 -0
- package/dist/clis/yollomi/utils.d.ts +45 -0
- package/dist/clis/yollomi/utils.js +180 -0
- package/dist/clis/yollomi/video.d.ts +5 -0
- package/dist/clis/yollomi/video.js +56 -0
- package/dist/clis/zhihu/download.d.ts +1 -5
- package/dist/clis/zhihu/download.js +20 -126
- package/dist/clis/zhihu/download.test.js +7 -5
- package/dist/clis/zhihu/question.js +2 -1
- package/dist/commanderAdapter.js +4 -6
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.js +7 -3
- package/dist/discovery.js +10 -10
- package/dist/doctor.js +2 -1
- package/dist/download/article-download.d.ts +59 -0
- package/dist/download/article-download.js +178 -0
- package/dist/download/media-download.d.ts +49 -0
- package/dist/download/media-download.js +112 -0
- package/dist/errors.d.ts +23 -2
- package/dist/errors.js +58 -2
- package/dist/errors.test.d.ts +1 -0
- package/dist/errors.test.js +59 -0
- package/dist/execution.js +9 -10
- package/dist/explore.js +4 -2
- package/dist/external.d.ts +15 -0
- package/dist/external.js +48 -2
- package/dist/external.test.d.ts +1 -0
- package/dist/external.test.js +64 -0
- package/dist/main.js +10 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.js +45 -23
- package/dist/plugin.test.js +6 -1
- package/dist/record.d.ts +47 -0
- package/dist/record.js +545 -0
- package/dist/registry.d.ts +7 -2
- package/dist/registry.js +2 -6
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +10 -3
- package/dist/validate.js +1 -3
- package/docs/.vitepress/config.mts +1 -0
- package/docs/adapters/browser/douban.md +18 -8
- package/docs/adapters/browser/doubao.md +35 -0
- package/docs/adapters/browser/hackernews.md +20 -4
- package/docs/adapters/browser/tiktok.md +1 -1
- package/docs/adapters/browser/v2ex.md +31 -10
- package/docs/adapters/browser/weibo.md +4 -0
- package/docs/adapters/browser/weixin.md +33 -0
- package/docs/adapters/browser/wikipedia.md +0 -9
- package/docs/adapters/browser/xiaohongshu.md +8 -6
- package/docs/adapters/browser/yollomi.md +69 -0
- package/docs/adapters/desktop/antigravity.md +0 -3
- package/docs/adapters/desktop/doubao-app.md +35 -0
- package/docs/adapters/index.md +19 -8
- package/docs/advanced/download.md +4 -0
- package/package.json +3 -1
- package/src/browser/cdp.ts +9 -1
- package/src/browser/daemon-client.ts +4 -3
- package/src/browser/discover.ts +2 -1
- package/src/browser/errors.ts +18 -11
- package/src/browser/index.ts +1 -0
- package/src/browser/page.ts +11 -0
- package/src/browser/stealth.ts +142 -0
- package/src/browser.test.ts +51 -1
- package/src/build-manifest.ts +1 -3
- package/src/cli.ts +45 -2
- package/src/clis/bilibili/download.ts +25 -83
- package/src/clis/bilibili/utils.ts +2 -1
- package/src/clis/chaoxing/assignments.ts +2 -1
- package/src/clis/doubao/ask.ts +40 -0
- package/src/clis/doubao/common.ts +619 -0
- package/src/clis/doubao/new.ts +22 -0
- package/src/clis/doubao/read.ts +20 -0
- package/src/clis/doubao/send.ts +25 -0
- package/src/clis/doubao/status.ts +27 -0
- package/src/clis/doubao-app/ask.ts +60 -0
- package/src/clis/doubao-app/common.ts +116 -0
- package/src/clis/doubao-app/dump.ts +28 -0
- package/src/clis/doubao-app/new.ts +21 -0
- package/src/clis/doubao-app/read.ts +21 -0
- package/src/clis/doubao-app/screenshot.ts +19 -0
- package/src/clis/doubao-app/send.ts +30 -0
- package/src/clis/doubao-app/status.ts +17 -0
- package/src/clis/hackernews/ask.yaml +38 -0
- package/src/clis/hackernews/best.yaml +38 -0
- package/src/clis/hackernews/jobs.yaml +36 -0
- package/src/clis/hackernews/new.yaml +38 -0
- package/src/clis/hackernews/search.yaml +44 -0
- package/src/clis/hackernews/show.yaml +38 -0
- package/src/clis/hackernews/top.yaml +3 -1
- package/src/clis/hackernews/user.yaml +25 -0
- package/src/clis/twitter/download.ts +13 -111
- package/src/clis/twitter/thread.ts +2 -1
- package/src/clis/v2ex/member.yaml +29 -0
- package/src/clis/v2ex/node.yaml +34 -0
- package/src/clis/v2ex/nodes.yaml +31 -0
- package/src/clis/v2ex/replies.yaml +32 -0
- package/src/clis/v2ex/user.yaml +34 -0
- package/src/clis/weibo/search.ts +78 -0
- package/src/clis/weixin/download.ts +199 -0
- package/src/clis/xiaohongshu/download.ts +12 -71
- package/src/clis/xiaohongshu/publish.ts +392 -0
- package/src/clis/xiaohongshu/search.test.ts +134 -0
- package/src/clis/xiaohongshu/search.ts +49 -15
- package/src/clis/yollomi/background.ts +48 -0
- package/src/clis/yollomi/edit.ts +58 -0
- package/src/clis/yollomi/face-swap.ts +45 -0
- package/src/clis/yollomi/generate.ts +95 -0
- package/src/clis/yollomi/models.ts +38 -0
- package/src/clis/yollomi/object-remover.ts +44 -0
- package/src/clis/yollomi/remove-bg.ts +40 -0
- package/src/clis/yollomi/restore.ts +40 -0
- package/src/clis/yollomi/try-on.ts +48 -0
- package/src/clis/yollomi/upload.ts +78 -0
- package/src/clis/yollomi/upscale.ts +49 -0
- package/src/clis/yollomi/utils.ts +202 -0
- package/src/clis/yollomi/video.ts +61 -0
- package/src/clis/zhihu/download.test.ts +7 -5
- package/src/clis/zhihu/download.ts +23 -158
- package/src/clis/zhihu/question.ts +2 -1
- package/src/commanderAdapter.ts +4 -7
- package/src/constants.ts +3 -0
- package/src/daemon.ts +7 -3
- package/src/discovery.ts +26 -26
- package/src/doctor.ts +2 -1
- package/src/download/article-download.ts +272 -0
- package/src/download/media-download.ts +178 -0
- package/src/errors.test.ts +79 -0
- package/src/errors.ts +92 -2
- package/src/execution.ts +14 -10
- package/src/explore.ts +4 -2
- package/src/external.test.ts +88 -0
- package/src/external.ts +56 -2
- package/src/generate.ts +2 -1
- package/src/main.ts +10 -0
- package/src/plugin.test.ts +7 -1
- package/src/plugin.ts +49 -25
- package/src/record.ts +617 -0
- package/src/registry.ts +9 -5
- package/src/runtime.ts +16 -4
- package/src/validate.ts +1 -3
- package/tests/e2e/browser-auth.test.ts +10 -1
- package/tests/e2e/browser-public.test.ts +13 -8
- package/tests/e2e/public-commands.test.ts +209 -21
- 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:
|
|
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]
|