@jackwener/opencli 1.3.0 → 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.
- package/CHANGELOG.md +128 -0
- package/README.md +44 -5
- package/README.zh-CN.md +44 -5
- package/SKILL.md +317 -5
- package/TESTING.md +4 -4
- package/dist/browser/errors.d.ts +2 -1
- package/dist/browser/errors.js +9 -10
- 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/daemon.js +5 -2
- package/dist/discovery.js +10 -10
- 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/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/xiaohongshu.md +8 -6
- package/docs/adapters/browser/yollomi.md +69 -0
- package/docs/adapters/desktop/doubao-app.md +35 -0
- package/docs/adapters/index.md +16 -5
- package/docs/advanced/download.md +4 -0
- package/package.json +3 -1
- package/src/browser/errors.ts +17 -11
- package/src/build-manifest.ts +2 -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/daemon.ts +5 -2
- package/src/discovery.ts +26 -26
- 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 +2 -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
- package/.github/workflows/release-please.yml +0 -25
|
@@ -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]
|