@jackwener/opencli 0.9.6 → 0.9.8
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/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
- package/.github/dependabot.yml +27 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/ci.yml +14 -8
- package/.github/workflows/e2e-headed.yml +6 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security.yml +36 -0
- package/CLI-ELECTRON.md +89 -36
- package/CONTRIBUTING.md +167 -0
- package/README.md +98 -32
- package/README.zh-CN.md +99 -33
- package/dist/browser/discover.js +22 -7
- package/dist/browser.test.js +23 -0
- package/dist/build-manifest.d.ts +26 -0
- package/dist/build-manifest.js +132 -60
- package/dist/build-manifest.test.d.ts +1 -0
- package/dist/build-manifest.test.js +26 -0
- package/dist/cli-manifest.json +1415 -29
- package/dist/clis/bilibili/download.d.ts +10 -0
- package/dist/clis/bilibili/download.js +135 -0
- package/dist/clis/chatwise/ask.d.ts +1 -0
- package/dist/clis/chatwise/ask.js +76 -0
- package/dist/clis/chatwise/export.d.ts +1 -0
- package/dist/clis/chatwise/export.js +46 -0
- package/dist/clis/chatwise/history.d.ts +1 -0
- package/dist/clis/chatwise/history.js +43 -0
- package/dist/clis/chatwise/model.d.ts +1 -0
- package/dist/clis/chatwise/model.js +81 -0
- package/dist/clis/chatwise/new.d.ts +1 -0
- package/dist/clis/chatwise/new.js +18 -0
- package/dist/clis/chatwise/read.d.ts +1 -0
- package/dist/clis/chatwise/read.js +39 -0
- package/dist/clis/chatwise/screenshot.d.ts +1 -0
- package/dist/clis/chatwise/screenshot.js +27 -0
- package/dist/clis/chatwise/send.d.ts +1 -0
- package/dist/clis/chatwise/send.js +45 -0
- package/dist/clis/chatwise/status.d.ts +1 -0
- package/dist/clis/chatwise/status.js +22 -0
- package/dist/clis/discord-app/channels.d.ts +1 -0
- package/dist/clis/discord-app/channels.js +45 -0
- package/dist/clis/discord-app/members.d.ts +1 -0
- package/dist/clis/discord-app/members.js +38 -0
- package/dist/clis/discord-app/read.d.ts +1 -0
- package/dist/clis/discord-app/read.js +45 -0
- package/dist/clis/discord-app/search.d.ts +1 -0
- package/dist/clis/discord-app/search.js +56 -0
- package/dist/clis/discord-app/send.d.ts +1 -0
- package/dist/clis/discord-app/send.js +27 -0
- package/dist/clis/discord-app/servers.d.ts +1 -0
- package/dist/clis/discord-app/servers.js +36 -0
- package/dist/clis/discord-app/status.d.ts +1 -0
- package/dist/clis/discord-app/status.js +16 -0
- package/dist/clis/feishu/new.d.ts +1 -0
- package/dist/clis/feishu/new.js +27 -0
- package/dist/clis/feishu/read.d.ts +1 -0
- package/dist/clis/feishu/read.js +40 -0
- package/dist/clis/feishu/search.d.ts +1 -0
- package/dist/clis/feishu/search.js +30 -0
- package/dist/clis/feishu/send.d.ts +1 -0
- package/dist/clis/feishu/send.js +39 -0
- package/dist/clis/feishu/status.d.ts +1 -0
- package/dist/clis/feishu/status.js +28 -0
- package/dist/clis/grok/ask.d.ts +1 -0
- package/dist/clis/grok/ask.js +82 -0
- package/dist/clis/grok/debug.d.ts +1 -0
- package/dist/clis/grok/debug.js +45 -0
- package/dist/clis/jimeng/generate.yaml +84 -0
- package/dist/clis/jimeng/history.yaml +47 -0
- package/dist/clis/linux-do/categories.yaml +41 -0
- package/dist/clis/linux-do/category.yaml +49 -0
- package/dist/clis/linux-do/hot.yaml +50 -0
- package/dist/clis/linux-do/latest.yaml +40 -0
- package/dist/clis/linux-do/search.yaml +45 -0
- package/dist/clis/linux-do/topic.yaml +38 -0
- package/dist/clis/notion/export.d.ts +1 -0
- package/dist/clis/notion/export.js +31 -0
- package/dist/clis/notion/favorites.d.ts +1 -0
- package/dist/clis/notion/favorites.js +84 -0
- package/dist/clis/notion/new.d.ts +1 -0
- package/dist/clis/notion/new.js +34 -0
- package/dist/clis/notion/read.d.ts +1 -0
- package/dist/clis/notion/read.js +30 -0
- package/dist/clis/notion/search.d.ts +1 -0
- package/dist/clis/notion/search.js +46 -0
- package/dist/clis/notion/sidebar.d.ts +1 -0
- package/dist/clis/notion/sidebar.js +41 -0
- package/dist/clis/notion/status.d.ts +1 -0
- package/dist/clis/notion/status.js +16 -0
- package/dist/clis/notion/write.d.ts +1 -0
- package/dist/clis/notion/write.js +40 -0
- package/dist/clis/twitter/download.d.ts +8 -0
- package/dist/clis/twitter/download.js +204 -0
- package/dist/clis/wechat/chats.d.ts +1 -0
- package/dist/clis/wechat/chats.js +28 -0
- package/dist/clis/wechat/contacts.d.ts +1 -0
- package/dist/clis/wechat/contacts.js +28 -0
- package/dist/clis/wechat/read.d.ts +1 -0
- package/dist/clis/wechat/read.js +58 -0
- package/dist/clis/wechat/search.d.ts +1 -0
- package/dist/clis/wechat/search.js +31 -0
- package/dist/clis/wechat/send.d.ts +1 -0
- package/dist/clis/wechat/send.js +42 -0
- package/dist/clis/wechat/status.d.ts +1 -0
- package/dist/clis/wechat/status.js +29 -0
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
- package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
- package/dist/clis/xiaohongshu/creator-notes.js +109 -0
- package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-profile.js +54 -0
- package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-stats.js +74 -0
- package/dist/clis/xiaohongshu/download.d.ts +7 -0
- package/dist/clis/xiaohongshu/download.js +155 -0
- package/dist/clis/xiaohongshu/search.js +1 -1
- package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
- package/dist/clis/xiaohongshu/user-helpers.js +67 -0
- package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
- package/dist/clis/xiaohongshu/user.js +46 -29
- package/dist/clis/zhihu/download.d.ts +11 -0
- package/dist/clis/zhihu/download.js +186 -0
- package/dist/clis/zhihu/download.test.d.ts +1 -0
- package/dist/clis/zhihu/download.test.js +10 -0
- package/dist/download/index.d.ts +79 -0
- package/dist/download/index.js +325 -0
- package/dist/download/progress.d.ts +36 -0
- package/dist/download/progress.js +111 -0
- package/dist/engine.test.js +15 -0
- package/dist/main.js +16 -3
- package/dist/pipeline/registry.js +2 -0
- package/dist/pipeline/steps/download.d.ts +34 -0
- package/dist/pipeline/steps/download.js +251 -0
- package/dist/pipeline/template.js +28 -0
- package/package.json +4 -3
- package/scripts/test-site.mjs +70 -0
- package/src/browser/discover.ts +23 -7
- package/src/browser.test.ts +23 -0
- package/src/build-manifest.test.ts +28 -0
- package/src/build-manifest.ts +147 -57
- package/src/clis/bilibili/download.ts +161 -0
- package/src/clis/chatwise/README.md +38 -0
- package/src/clis/chatwise/README.zh-CN.md +38 -0
- package/src/clis/chatwise/ask.ts +87 -0
- package/src/clis/chatwise/export.ts +51 -0
- package/src/clis/chatwise/history.ts +47 -0
- package/src/clis/chatwise/model.ts +87 -0
- package/src/clis/chatwise/new.ts +21 -0
- package/src/clis/chatwise/read.ts +42 -0
- package/src/clis/chatwise/screenshot.ts +33 -0
- package/src/clis/chatwise/send.ts +50 -0
- package/src/clis/chatwise/status.ts +25 -0
- package/src/clis/discord-app/README.md +28 -0
- package/src/clis/discord-app/README.zh-CN.md +28 -0
- package/src/clis/discord-app/channels.ts +48 -0
- package/src/clis/discord-app/members.ts +41 -0
- package/src/clis/discord-app/read.ts +49 -0
- package/src/clis/discord-app/search.ts +64 -0
- package/src/clis/discord-app/send.ts +32 -0
- package/src/clis/discord-app/servers.ts +39 -0
- package/src/clis/discord-app/status.ts +18 -0
- package/src/clis/feishu/README.md +20 -0
- package/src/clis/feishu/README.zh-CN.md +20 -0
- package/src/clis/feishu/new.ts +32 -0
- package/src/clis/feishu/read.ts +48 -0
- package/src/clis/feishu/search.ts +35 -0
- package/src/clis/feishu/send.ts +46 -0
- package/src/clis/feishu/status.ts +34 -0
- package/src/clis/grok/ask.ts +90 -0
- package/src/clis/grok/debug.ts +49 -0
- package/src/clis/jimeng/generate.yaml +84 -0
- package/src/clis/jimeng/history.yaml +47 -0
- package/src/clis/linux-do/categories.yaml +41 -0
- package/src/clis/linux-do/category.yaml +49 -0
- package/src/clis/linux-do/hot.yaml +50 -0
- package/src/clis/linux-do/latest.yaml +40 -0
- package/src/clis/linux-do/search.yaml +45 -0
- package/src/clis/linux-do/topic.yaml +38 -0
- package/src/clis/notion/README.md +29 -0
- package/src/clis/notion/README.zh-CN.md +29 -0
- package/src/clis/notion/export.ts +36 -0
- package/src/clis/notion/favorites.ts +87 -0
- package/src/clis/notion/new.ts +39 -0
- package/src/clis/notion/read.ts +33 -0
- package/src/clis/notion/search.ts +54 -0
- package/src/clis/notion/sidebar.ts +44 -0
- package/src/clis/notion/status.ts +18 -0
- package/src/clis/notion/write.ts +45 -0
- package/src/clis/twitter/download.ts +227 -0
- package/src/clis/wechat/README.md +28 -0
- package/src/clis/wechat/README.zh-CN.md +28 -0
- package/src/clis/wechat/chats.ts +33 -0
- package/src/clis/wechat/contacts.ts +33 -0
- package/src/clis/wechat/read.ts +72 -0
- package/src/clis/wechat/search.ts +36 -0
- package/src/clis/wechat/send.ts +49 -0
- package/src/clis/wechat/status.ts +35 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
- package/src/clis/xiaohongshu/creator-notes.ts +116 -0
- package/src/clis/xiaohongshu/creator-profile.ts +60 -0
- package/src/clis/xiaohongshu/creator-stats.ts +81 -0
- package/src/clis/xiaohongshu/download.ts +173 -0
- package/src/clis/xiaohongshu/search.ts +1 -1
- package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
- package/src/clis/xiaohongshu/user-helpers.ts +85 -0
- package/src/clis/xiaohongshu/user.ts +52 -32
- package/src/clis/zhihu/download.test.ts +12 -0
- package/src/clis/zhihu/download.ts +223 -0
- package/src/download/index.ts +395 -0
- package/src/download/progress.ts +125 -0
- package/src/engine.test.ts +17 -0
- package/src/main.ts +12 -3
- package/src/pipeline/registry.ts +2 -0
- package/src/pipeline/steps/download.ts +310 -0
- package/src/pipeline/template.ts +26 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const modelCommand = cli({
|
|
5
|
+
site: 'chatwise',
|
|
6
|
+
name: 'model',
|
|
7
|
+
description: 'Get or switch the active AI model in ChatWise',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'model_name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
|
|
13
|
+
],
|
|
14
|
+
columns: ['Status', 'Model'],
|
|
15
|
+
func: async (page: IPage, kwargs: any) => {
|
|
16
|
+
const desiredModel = kwargs.model_name as string | undefined;
|
|
17
|
+
|
|
18
|
+
if (!desiredModel) {
|
|
19
|
+
// Read current model
|
|
20
|
+
const currentModel = await page.evaluate(`
|
|
21
|
+
(function() {
|
|
22
|
+
// ChatWise is a multi-LLM client, it typically shows the model name in a dropdown or header
|
|
23
|
+
const selectors = [
|
|
24
|
+
'[class*="model"] span',
|
|
25
|
+
'[class*="Model"] span',
|
|
26
|
+
'[data-testid*="model"]',
|
|
27
|
+
'button[class*="model"]',
|
|
28
|
+
'[aria-label*="Model"]',
|
|
29
|
+
'[aria-label*="model"]',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const sel of selectors) {
|
|
33
|
+
const el = document.querySelector(sel);
|
|
34
|
+
if (el) {
|
|
35
|
+
const text = (el.textContent || el.getAttribute('title') || '').trim();
|
|
36
|
+
if (text) return text;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return 'Unknown or Not Found';
|
|
41
|
+
})()
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
return [{ Status: 'Active', Model: currentModel }];
|
|
45
|
+
} else {
|
|
46
|
+
// Try to switch model
|
|
47
|
+
await page.evaluate(`
|
|
48
|
+
(function(target) {
|
|
49
|
+
const selectors = [
|
|
50
|
+
'[class*="model"]',
|
|
51
|
+
'[class*="Model"]',
|
|
52
|
+
'button[class*="model"]',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const sel of selectors) {
|
|
56
|
+
const el = document.querySelector(sel);
|
|
57
|
+
if (el) { el.click(); return; }
|
|
58
|
+
}
|
|
59
|
+
throw new Error('Could not find model selector');
|
|
60
|
+
})(${JSON.stringify(desiredModel)})
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
await page.wait(0.5);
|
|
64
|
+
|
|
65
|
+
// Find and click the target model in the dropdown
|
|
66
|
+
const found = await page.evaluate(`
|
|
67
|
+
(function(target) {
|
|
68
|
+
const options = document.querySelectorAll('[role="option"], [role="menuitem"], [class*="dropdown-item"], li');
|
|
69
|
+
for (const opt of options) {
|
|
70
|
+
if ((opt.textContent || '').toLowerCase().includes(target.toLowerCase())) {
|
|
71
|
+
opt.click();
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
})(${JSON.stringify(desiredModel)})
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
Status: found ? 'Switched' : 'Dropdown opened but model not found',
|
|
82
|
+
Model: desiredModel,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const newCommand = cli({
|
|
5
|
+
site: 'chatwise',
|
|
6
|
+
name: 'new',
|
|
7
|
+
description: 'Start a new conversation in ChatWise',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Status'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
// ChatWise uses standard Electron shortcuts
|
|
15
|
+
const isMac = process.platform === 'darwin';
|
|
16
|
+
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
|
|
17
|
+
await page.wait(1);
|
|
18
|
+
|
|
19
|
+
return [{ Status: 'Success' }];
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const readCommand = cli({
|
|
5
|
+
site: 'chatwise',
|
|
6
|
+
name: 'read',
|
|
7
|
+
description: 'Read the current ChatWise conversation history',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Content'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const content = await page.evaluate(`
|
|
15
|
+
(function() {
|
|
16
|
+
// Try common chat message selectors
|
|
17
|
+
const selectors = [
|
|
18
|
+
'[data-message-id]',
|
|
19
|
+
'[class*="message"]',
|
|
20
|
+
'[class*="chat-item"]',
|
|
21
|
+
'[class*="bubble"]',
|
|
22
|
+
'[role="log"] > *',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const sel of selectors) {
|
|
26
|
+
const nodes = document.querySelectorAll(sel);
|
|
27
|
+
if (nodes.length > 0) {
|
|
28
|
+
return Array.from(nodes).map(n => (n.innerText || n.textContent).trim()).filter(Boolean).join('\\n\\n---\\n\\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fallback: main content area
|
|
33
|
+
const main = document.querySelector('main, [role="main"], [class*="chat-container"], [class*="conversation"]');
|
|
34
|
+
if (main) return main.innerText || main.textContent;
|
|
35
|
+
|
|
36
|
+
return document.body.innerText;
|
|
37
|
+
})()
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
return [{ Content: content }];
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import type { IPage } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
export const screenshotCommand = cli({
|
|
6
|
+
site: 'chatwise',
|
|
7
|
+
name: 'screenshot',
|
|
8
|
+
description: 'Capture a snapshot of the current ChatWise window (DOM + Accessibility tree)',
|
|
9
|
+
domain: 'localhost',
|
|
10
|
+
strategy: Strategy.UI,
|
|
11
|
+
browser: true,
|
|
12
|
+
args: [
|
|
13
|
+
{ name: 'output', required: false, positional: true, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
|
|
14
|
+
],
|
|
15
|
+
columns: ['Status', 'File'],
|
|
16
|
+
func: async (page: IPage, kwargs: any) => {
|
|
17
|
+
const basePath = (kwargs.output as string) || '/tmp/chatwise-snapshot';
|
|
18
|
+
|
|
19
|
+
const snap = await page.snapshot({ compact: true });
|
|
20
|
+
const html = await page.evaluate('document.documentElement.outerHTML');
|
|
21
|
+
|
|
22
|
+
const htmlPath = basePath + '-dom.html';
|
|
23
|
+
const snapPath = basePath + '-a11y.txt';
|
|
24
|
+
|
|
25
|
+
fs.writeFileSync(htmlPath, html);
|
|
26
|
+
fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
|
|
27
|
+
|
|
28
|
+
return [
|
|
29
|
+
{ Status: 'Success', File: htmlPath },
|
|
30
|
+
{ Status: 'Success', File: snapPath },
|
|
31
|
+
];
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const sendCommand = cli({
|
|
5
|
+
site: 'chatwise',
|
|
6
|
+
name: 'send',
|
|
7
|
+
description: 'Send a message to the active ChatWise conversation',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
12
|
+
columns: ['Status', 'InjectedText'],
|
|
13
|
+
func: async (page: IPage, kwargs: any) => {
|
|
14
|
+
const text = kwargs.text as string;
|
|
15
|
+
|
|
16
|
+
await page.evaluate(`
|
|
17
|
+
(function(text) {
|
|
18
|
+
// ChatWise input can be textarea or contenteditable
|
|
19
|
+
let composer = document.querySelector('textarea');
|
|
20
|
+
if (!composer) {
|
|
21
|
+
const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
|
|
22
|
+
composer = editables.length > 0 ? editables[editables.length - 1] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!composer) throw new Error('Could not find ChatWise input element');
|
|
26
|
+
|
|
27
|
+
composer.focus();
|
|
28
|
+
|
|
29
|
+
if (composer.tagName === 'TEXTAREA') {
|
|
30
|
+
// For textarea, set value and dispatch input event
|
|
31
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
|
|
32
|
+
nativeInputValueSetter.call(composer, text);
|
|
33
|
+
composer.dispatchEvent(new Event('input', { bubbles: true }));
|
|
34
|
+
} else {
|
|
35
|
+
document.execCommand('insertText', false, text);
|
|
36
|
+
}
|
|
37
|
+
})(${JSON.stringify(text)})
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
await page.wait(0.5);
|
|
41
|
+
await page.pressKey('Enter');
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
Status: 'Success',
|
|
46
|
+
InjectedText: text,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const statusCommand = cli({
|
|
5
|
+
site: 'chatwise',
|
|
6
|
+
name: 'status',
|
|
7
|
+
description: 'Check active CDP connection to ChatWise Desktop',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Status', 'Url', 'Title'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const url = await page.evaluate('window.location.href');
|
|
15
|
+
const title = await page.evaluate('document.title');
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
Status: 'Connected',
|
|
20
|
+
Url: url,
|
|
21
|
+
Title: title,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Discord Desktop Adapter
|
|
2
|
+
|
|
3
|
+
Control the **Discord Desktop App** from the terminal via Chrome DevTools Protocol (CDP).
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Launch with remote debugging port:
|
|
8
|
+
```bash
|
|
9
|
+
/Applications/Discord.app/Contents/MacOS/Discord --remote-debugging-port=9232
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9232"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | Description |
|
|
21
|
+
|---------|-------------|
|
|
22
|
+
| `discord status` | Check CDP connection |
|
|
23
|
+
| `discord send "message"` | Send a message in the active channel |
|
|
24
|
+
| `discord read` | Read recent messages |
|
|
25
|
+
| `discord channels` | List channels in the current server |
|
|
26
|
+
| `discord servers` | List all joined servers |
|
|
27
|
+
| `discord search "query"` | Search messages (Cmd+F) |
|
|
28
|
+
| `discord members` | List online members |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Discord 桌面端适配器
|
|
2
|
+
|
|
3
|
+
通过 Chrome DevTools Protocol (CDP) 在终端中控制 **Discord 桌面应用**。
|
|
4
|
+
|
|
5
|
+
## 前置条件
|
|
6
|
+
|
|
7
|
+
通过远程调试端口启动:
|
|
8
|
+
```bash
|
|
9
|
+
/Applications/Discord.app/Contents/MacOS/Discord --remote-debugging-port=9232
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 配置
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9232"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 命令
|
|
19
|
+
|
|
20
|
+
| 命令 | 说明 |
|
|
21
|
+
|------|------|
|
|
22
|
+
| `discord status` | 检查 CDP 连接 |
|
|
23
|
+
| `discord send "消息"` | 在当前频道发送消息 |
|
|
24
|
+
| `discord read` | 读取最近消息 |
|
|
25
|
+
| `discord channels` | 列出当前服务器的频道 |
|
|
26
|
+
| `discord servers` | 列出已加入的服务器 |
|
|
27
|
+
| `discord search "关键词"` | 搜索消息(Cmd+F) |
|
|
28
|
+
| `discord members` | 列出在线成员 |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const channelsCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'channels',
|
|
7
|
+
description: 'List channels in the current Discord server',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Index', 'Channel', 'Type'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const channels = await page.evaluate(`
|
|
15
|
+
(function() {
|
|
16
|
+
const results = [];
|
|
17
|
+
// Discord channel list items
|
|
18
|
+
const items = document.querySelectorAll('[data-list-item-id*="channels___"], [class*="containerDefault_"]');
|
|
19
|
+
|
|
20
|
+
items.forEach((item, i) => {
|
|
21
|
+
const nameEl = item.querySelector('[class*="name_"], [class*="channelName"]');
|
|
22
|
+
const name = nameEl ? nameEl.textContent.trim() : (item.textContent || '').trim().substring(0, 50);
|
|
23
|
+
|
|
24
|
+
if (!name || name.length < 1) return;
|
|
25
|
+
|
|
26
|
+
// Detect channel type from icon or aria-label
|
|
27
|
+
const iconEl = item.querySelector('[class*="icon"]');
|
|
28
|
+
let type = 'Text';
|
|
29
|
+
if (iconEl) {
|
|
30
|
+
const cls = iconEl.className || '';
|
|
31
|
+
if (cls.includes('voice') || cls.includes('speaker')) type = 'Voice';
|
|
32
|
+
else if (cls.includes('forum')) type = 'Forum';
|
|
33
|
+
else if (cls.includes('announcement')) type = 'Announcement';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
results.push({ Index: i + 1, Channel: name, Type: type });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return results;
|
|
40
|
+
})()
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
if (channels.length === 0) {
|
|
44
|
+
return [{ Index: 0, Channel: 'No channels found', Type: '—' }];
|
|
45
|
+
}
|
|
46
|
+
return channels;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const membersCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'members',
|
|
7
|
+
description: 'List online members in the current Discord channel',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Index', 'Name', 'Status'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const members = await page.evaluate(`
|
|
15
|
+
(function() {
|
|
16
|
+
const results = [];
|
|
17
|
+
// Discord member list sidebar
|
|
18
|
+
const items = document.querySelectorAll('[class*="member_"], [data-list-item-id*="members"]');
|
|
19
|
+
|
|
20
|
+
items.forEach((item, i) => {
|
|
21
|
+
const nameEl = item.querySelector('[class*="username_"], [class*="nameTag"]');
|
|
22
|
+
const statusEl = item.querySelector('[class*="activity"], [class*="customStatus"]');
|
|
23
|
+
|
|
24
|
+
const name = nameEl ? nameEl.textContent.trim() : (item.textContent || '').trim().substring(0, 50);
|
|
25
|
+
const status = statusEl ? statusEl.textContent.trim() : '';
|
|
26
|
+
|
|
27
|
+
if (name && name.length > 0) {
|
|
28
|
+
results.push({ Index: i + 1, Name: name, Status: status || 'Online' });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return results.slice(0, 50); // Limit to 50
|
|
33
|
+
})()
|
|
34
|
+
`);
|
|
35
|
+
|
|
36
|
+
if (members.length === 0) {
|
|
37
|
+
return [{ Index: 0, Name: 'No members visible', Status: 'Toggle member list first' }];
|
|
38
|
+
}
|
|
39
|
+
return members;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const readCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'read',
|
|
7
|
+
description: 'Read recent messages from the active Discord channel',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'count', required: false, help: 'Number of messages to read (default: 20)', default: '20' },
|
|
13
|
+
],
|
|
14
|
+
columns: ['Author', 'Time', 'Message'],
|
|
15
|
+
func: async (page: IPage, kwargs: any) => {
|
|
16
|
+
const count = parseInt(kwargs.count as string, 10) || 20;
|
|
17
|
+
|
|
18
|
+
const messages = await page.evaluate(`
|
|
19
|
+
(function(limit) {
|
|
20
|
+
const results = [];
|
|
21
|
+
// Discord renders messages in list items with id starting with "chat-messages-"
|
|
22
|
+
const msgNodes = document.querySelectorAll('[id^="chat-messages-"] > div, [class*="messageListItem"]');
|
|
23
|
+
|
|
24
|
+
const slice = Array.from(msgNodes).slice(-limit);
|
|
25
|
+
|
|
26
|
+
slice.forEach(node => {
|
|
27
|
+
const authorEl = node.querySelector('[class*="username"], [class*="headerText"] span');
|
|
28
|
+
const timeEl = node.querySelector('time');
|
|
29
|
+
const contentEl = node.querySelector('[id^="message-content-"], [class*="messageContent"]');
|
|
30
|
+
|
|
31
|
+
if (contentEl) {
|
|
32
|
+
results.push({
|
|
33
|
+
Author: authorEl ? authorEl.textContent.trim() : '—',
|
|
34
|
+
Time: timeEl ? timeEl.getAttribute('datetime') || timeEl.textContent.trim() : '',
|
|
35
|
+
Message: (contentEl.textContent || '').trim().substring(0, 300),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return results;
|
|
41
|
+
})(${count})
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
if (messages.length === 0) {
|
|
45
|
+
return [{ Author: 'System', Time: '', Message: 'No messages found in the current channel.' }];
|
|
46
|
+
}
|
|
47
|
+
return messages;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const searchCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'search',
|
|
7
|
+
description: 'Search messages in the current Discord server/channel (Cmd+F)',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [{ name: 'query', required: true, positional: true, help: 'Search query' }],
|
|
12
|
+
columns: ['Index', 'Author', 'Message'],
|
|
13
|
+
func: async (page: IPage, kwargs: any) => {
|
|
14
|
+
const query = kwargs.query as string;
|
|
15
|
+
|
|
16
|
+
// Open search with Cmd+F
|
|
17
|
+
const isMac = process.platform === 'darwin';
|
|
18
|
+
await page.pressKey(isMac ? 'Meta+F' : 'Control+F');
|
|
19
|
+
await page.wait(0.5);
|
|
20
|
+
|
|
21
|
+
// Type query into search box
|
|
22
|
+
await page.evaluate(`
|
|
23
|
+
(function(q) {
|
|
24
|
+
const input = document.querySelector('[aria-label*="Search"], [class*="searchBar"] input, [placeholder*="Search"]');
|
|
25
|
+
if (!input) throw new Error('Search input not found');
|
|
26
|
+
input.focus();
|
|
27
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
28
|
+
setter.call(input, q);
|
|
29
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
30
|
+
})(${JSON.stringify(query)})
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
await page.pressKey('Enter');
|
|
34
|
+
await page.wait(2);
|
|
35
|
+
|
|
36
|
+
// Scrape search results
|
|
37
|
+
const results = await page.evaluate(`
|
|
38
|
+
(function() {
|
|
39
|
+
const items = [];
|
|
40
|
+
const resultNodes = document.querySelectorAll('[class*="searchResult_"], [id*="search-result"]');
|
|
41
|
+
|
|
42
|
+
resultNodes.forEach((node, i) => {
|
|
43
|
+
const author = node.querySelector('[class*="username"]')?.textContent?.trim() || '—';
|
|
44
|
+
const content = node.querySelector('[id^="message-content-"], [class*="messageContent"]')?.textContent?.trim() || node.textContent?.trim();
|
|
45
|
+
items.push({
|
|
46
|
+
Index: i + 1,
|
|
47
|
+
Author: author,
|
|
48
|
+
Message: (content || '').substring(0, 200),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return items;
|
|
53
|
+
})()
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
// Close search
|
|
57
|
+
await page.pressKey('Escape');
|
|
58
|
+
|
|
59
|
+
if (results.length === 0) {
|
|
60
|
+
return [{ Index: 0, Author: 'System', Message: `No results for "${query}"` }];
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const sendCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'send',
|
|
7
|
+
description: 'Send a message in the active Discord channel',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
12
|
+
columns: ['Status'],
|
|
13
|
+
func: async (page: IPage, kwargs: any) => {
|
|
14
|
+
const text = kwargs.text as string;
|
|
15
|
+
|
|
16
|
+
await page.evaluate(`
|
|
17
|
+
(function(text) {
|
|
18
|
+
// Discord uses a Slate-based editor with [data-slate-editor="true"] or role="textbox"
|
|
19
|
+
const editor = document.querySelector('[role="textbox"][data-slate-editor="true"], [class*="slateTextArea"]');
|
|
20
|
+
if (!editor) throw new Error('Could not find Discord message input. Make sure a channel is open.');
|
|
21
|
+
|
|
22
|
+
editor.focus();
|
|
23
|
+
document.execCommand('insertText', false, text);
|
|
24
|
+
})(${JSON.stringify(text)})
|
|
25
|
+
`);
|
|
26
|
+
|
|
27
|
+
await page.wait(0.3);
|
|
28
|
+
await page.pressKey('Enter');
|
|
29
|
+
|
|
30
|
+
return [{ Status: 'Success' }];
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const serversCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'servers',
|
|
7
|
+
description: 'List all Discord servers (guilds) in the sidebar',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Index', 'Server'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const servers = await page.evaluate(`
|
|
15
|
+
(function() {
|
|
16
|
+
const results = [];
|
|
17
|
+
// Discord guild icons in the sidebar
|
|
18
|
+
const items = document.querySelectorAll('[data-list-item-id*="guildsnav___"], [class*="listItem_"]');
|
|
19
|
+
|
|
20
|
+
items.forEach((item, i) => {
|
|
21
|
+
const nameAttr = item.querySelector('[data-dnd-name]');
|
|
22
|
+
const ariaLabel = item.getAttribute('aria-label') || (item.querySelector('[aria-label]') || {}).getAttribute?.('aria-label');
|
|
23
|
+
const name = nameAttr ? nameAttr.getAttribute('data-dnd-name') : (ariaLabel || (item.textContent || '').trim());
|
|
24
|
+
|
|
25
|
+
if (name && name.length > 0) {
|
|
26
|
+
results.push({ Index: i + 1, Server: name.substring(0, 80) });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return results;
|
|
31
|
+
})()
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
if (servers.length === 0) {
|
|
35
|
+
return [{ Index: 0, Server: 'No servers found' }];
|
|
36
|
+
}
|
|
37
|
+
return servers;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
|
|
4
|
+
export const statusCommand = cli({
|
|
5
|
+
site: 'discord-app',
|
|
6
|
+
name: 'status',
|
|
7
|
+
description: 'Check active CDP connection to Discord Desktop',
|
|
8
|
+
domain: 'localhost',
|
|
9
|
+
strategy: Strategy.UI,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [],
|
|
12
|
+
columns: ['Status', 'Url', 'Title'],
|
|
13
|
+
func: async (page: IPage) => {
|
|
14
|
+
const url = await page.evaluate('window.location.href');
|
|
15
|
+
const title = await page.evaluate('document.title');
|
|
16
|
+
return [{ Status: 'Connected', Url: url, Title: title }];
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Feishu (飞书/Lark) Desktop Adapter
|
|
2
|
+
|
|
3
|
+
Control **Feishu/Lark Desktop** from the terminal via AppleScript.
|
|
4
|
+
|
|
5
|
+
> **Note:** Feishu uses a custom `Lark Framework` (Chromium-based but NOT Electron). CDP is not available, so this adapter uses AppleScript + clipboard.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
1. Feishu/Lark must be running and logged in
|
|
10
|
+
2. Terminal must have **Accessibility permission**
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
| Command | Description |
|
|
15
|
+
|---------|-------------|
|
|
16
|
+
| `feishu status` | Check if Feishu/Lark is running |
|
|
17
|
+
| `feishu send "msg"` | Send message in active chat (paste + Enter) |
|
|
18
|
+
| `feishu read` | Read current chat (Cmd+A → Cmd+C) |
|
|
19
|
+
| `feishu search "query"` | Global search (Cmd+K) |
|
|
20
|
+
| `feishu new` | New message/document (Cmd+N) |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 飞书 (Lark) 桌面端适配器
|
|
2
|
+
|
|
3
|
+
通过 AppleScript 在终端中控制**飞书桌面端**。
|
|
4
|
+
|
|
5
|
+
> **注意:** 飞书使用自研 `Lark Framework`(基于 Chromium 但非 Electron),不支持 CDP。
|
|
6
|
+
|
|
7
|
+
## 前置条件
|
|
8
|
+
|
|
9
|
+
1. 飞书必须正在运行且已登录
|
|
10
|
+
2. Terminal 需要辅助功能权限
|
|
11
|
+
|
|
12
|
+
## 命令
|
|
13
|
+
|
|
14
|
+
| 命令 | 说明 |
|
|
15
|
+
|------|------|
|
|
16
|
+
| `feishu status` | 检查飞书是否在运行 |
|
|
17
|
+
| `feishu send "消息"` | 发送消息(粘贴 + 回车) |
|
|
18
|
+
| `feishu read` | 读取当前聊天(Cmd+A → Cmd+C) |
|
|
19
|
+
| `feishu search "关键词"` | 全局搜索(Cmd+K) |
|
|
20
|
+
| `feishu new` | 新建消息/文档(Cmd+N) |
|