@jackwener/opencli 1.5.4 → 1.5.6
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/README.md +27 -2
- package/README.zh-CN.md +36 -4
- package/dist/browser/daemon-client.d.ts +5 -1
- package/dist/browser/page.d.ts +6 -0
- package/dist/browser/page.js +15 -0
- package/dist/cli-manifest.json +1284 -67
- package/dist/cli.js +14 -14
- package/dist/clis/antigravity/serve.js +2 -2
- package/dist/clis/band/bands.d.ts +1 -0
- package/dist/clis/band/bands.js +72 -0
- package/dist/clis/band/mentions.d.ts +1 -0
- package/dist/clis/band/mentions.js +127 -0
- package/dist/clis/band/post.d.ts +1 -0
- package/dist/clis/band/post.js +175 -0
- package/dist/clis/band/posts.d.ts +1 -0
- package/dist/clis/band/posts.js +94 -0
- package/dist/clis/doubao/detail.d.ts +1 -0
- package/dist/clis/doubao/detail.js +33 -0
- package/dist/clis/doubao/detail.test.d.ts +1 -0
- package/dist/clis/doubao/detail.test.js +42 -0
- package/dist/clis/doubao/history.d.ts +1 -0
- package/dist/clis/doubao/history.js +28 -0
- package/dist/clis/doubao/history.test.d.ts +1 -0
- package/dist/clis/doubao/history.test.js +37 -0
- package/dist/clis/doubao/meeting-summary.d.ts +1 -0
- package/dist/clis/doubao/meeting-summary.js +39 -0
- package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
- package/dist/clis/doubao/meeting-transcript.js +36 -0
- package/dist/clis/doubao/utils.d.ts +27 -0
- package/dist/clis/doubao/utils.js +317 -0
- package/dist/clis/doubao/utils.test.d.ts +1 -0
- package/dist/clis/doubao/utils.test.js +24 -0
- package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
- package/dist/clis/douyin/_shared/public-api.js +29 -0
- package/dist/clis/douyin/user-videos.d.ts +5 -0
- package/dist/clis/douyin/user-videos.js +74 -0
- package/dist/clis/douyin/user-videos.test.d.ts +1 -0
- package/dist/clis/douyin/user-videos.test.js +108 -0
- package/dist/clis/ones/common.d.ts +32 -0
- package/dist/clis/ones/common.js +144 -0
- package/dist/clis/ones/enrich-tasks.d.ts +5 -0
- package/dist/clis/ones/enrich-tasks.js +37 -0
- package/dist/clis/ones/login.d.ts +1 -0
- package/dist/clis/ones/login.js +80 -0
- package/dist/clis/ones/logout.d.ts +1 -0
- package/dist/clis/ones/logout.js +17 -0
- package/dist/clis/ones/me.d.ts +1 -0
- package/dist/clis/ones/me.js +30 -0
- package/dist/clis/ones/my-tasks.d.ts +1 -0
- package/dist/clis/ones/my-tasks.js +120 -0
- package/dist/clis/ones/resolve-labels.d.ts +10 -0
- package/dist/clis/ones/resolve-labels.js +64 -0
- package/dist/clis/ones/task-helpers.d.ts +29 -0
- package/dist/clis/ones/task-helpers.js +212 -0
- package/dist/clis/ones/task-helpers.test.d.ts +1 -0
- package/dist/clis/ones/task-helpers.test.js +12 -0
- package/dist/clis/ones/task.d.ts +1 -0
- package/dist/clis/ones/task.js +66 -0
- package/dist/clis/ones/tasks.d.ts +1 -0
- package/dist/clis/ones/tasks.js +79 -0
- package/dist/clis/ones/token-info.d.ts +1 -0
- package/dist/clis/ones/token-info.js +42 -0
- package/dist/clis/ones/worklog.d.ts +11 -0
- package/dist/clis/ones/worklog.js +267 -0
- package/dist/clis/ones/worklog.test.d.ts +1 -0
- package/dist/clis/ones/worklog.test.js +20 -0
- package/dist/clis/sinafinance/rolling-news.d.ts +4 -0
- package/dist/clis/sinafinance/rolling-news.js +40 -0
- package/dist/clis/sinafinance/stock.d.ts +8 -0
- package/dist/clis/sinafinance/stock.js +117 -0
- package/dist/clis/spotify/spotify.d.ts +1 -0
- package/dist/clis/spotify/spotify.js +316 -0
- package/dist/clis/spotify/utils.d.ts +21 -0
- package/dist/clis/spotify/utils.js +66 -0
- package/dist/clis/spotify/utils.test.d.ts +1 -0
- package/dist/clis/spotify/utils.test.js +67 -0
- package/dist/clis/tieba/commands.test.d.ts +4 -0
- package/dist/clis/tieba/commands.test.js +79 -0
- package/dist/clis/tieba/hot.d.ts +1 -0
- package/dist/clis/tieba/hot.js +48 -0
- package/dist/clis/tieba/posts.d.ts +1 -0
- package/dist/clis/tieba/posts.js +85 -0
- package/dist/clis/tieba/read.d.ts +1 -0
- package/dist/clis/tieba/read.js +140 -0
- package/dist/clis/tieba/search.d.ts +1 -0
- package/dist/clis/tieba/search.js +108 -0
- package/dist/clis/tieba/utils.d.ts +101 -0
- package/dist/clis/tieba/utils.js +240 -0
- package/dist/clis/tieba/utils.test.d.ts +1 -0
- package/dist/clis/tieba/utils.test.js +290 -0
- package/dist/clis/weread/book.js +100 -13
- package/dist/clis/weread/commands.test.js +221 -0
- package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
- package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
- package/dist/clis/weread/search-regression.test.d.ts +1 -0
- package/dist/clis/weread/search-regression.test.js +407 -0
- package/dist/clis/weread/search.js +143 -7
- package/dist/clis/weread/shelf.js +13 -95
- package/dist/clis/weread/utils.d.ts +46 -0
- package/dist/clis/weread/utils.js +214 -7
- package/dist/clis/weread/utils.test.js +71 -1
- package/dist/clis/xiaohongshu/publish.d.ts +1 -1
- package/dist/clis/xiaohongshu/publish.js +78 -31
- package/dist/clis/xiaohongshu/publish.test.js +66 -1
- package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.js +2 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
- package/dist/clis/xueqiu/comments.d.ts +118 -0
- package/dist/clis/xueqiu/comments.js +354 -0
- package/dist/clis/xueqiu/comments.test.d.ts +1 -0
- package/dist/clis/xueqiu/comments.test.js +696 -0
- package/dist/clis/youtube/transcript.js +2 -4
- package/dist/clis/youtube/utils.d.ts +9 -0
- package/dist/clis/youtube/utils.js +67 -3
- package/dist/clis/youtube/utils.test.d.ts +1 -0
- package/dist/clis/youtube/utils.test.js +37 -0
- package/dist/clis/youtube/video.js +16 -15
- package/dist/clis/zsxq/dynamics.d.ts +1 -0
- package/dist/clis/zsxq/dynamics.js +47 -0
- package/dist/clis/zsxq/groups.d.ts +1 -0
- package/dist/clis/zsxq/groups.js +32 -0
- package/dist/clis/zsxq/search.d.ts +1 -0
- package/dist/clis/zsxq/search.js +43 -0
- package/dist/clis/zsxq/search.test.d.ts +1 -0
- package/dist/clis/zsxq/search.test.js +24 -0
- package/dist/clis/zsxq/topic.d.ts +1 -0
- package/dist/clis/zsxq/topic.js +47 -0
- package/dist/clis/zsxq/topic.test.d.ts +1 -0
- package/dist/clis/zsxq/topic.test.js +29 -0
- package/dist/clis/zsxq/topics.d.ts +1 -0
- package/dist/clis/zsxq/topics.js +25 -0
- package/dist/clis/zsxq/topics.test.d.ts +1 -0
- package/dist/clis/zsxq/topics.test.js +24 -0
- package/dist/clis/zsxq/utils.d.ts +97 -0
- package/dist/clis/zsxq/utils.js +230 -0
- package/dist/commanderAdapter.js +27 -4
- package/dist/commanderAdapter.test.js +39 -0
- package/dist/daemon.js +5 -4
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +49 -11
- package/dist/external-clis.yaml +17 -0
- package/dist/external.js +3 -3
- package/dist/main.js +2 -1
- package/dist/tui.js +2 -1
- package/dist/types.d.ts +5 -0
- package/docs/.vitepress/config.mts +3 -0
- package/docs/adapters/browser/band.md +63 -0
- package/docs/adapters/browser/ones.md +59 -0
- package/docs/adapters/browser/sinafinance.md +56 -6
- package/docs/adapters/browser/spotify.md +62 -0
- package/docs/adapters/browser/tieba.md +45 -0
- package/docs/adapters/browser/xueqiu.md +5 -0
- package/docs/adapters/browser/zsxq.md +49 -0
- package/docs/adapters/index.md +5 -2
- package/docs/adapters-doc/ones.md +32 -0
- package/extension/dist/background.js +1 -2
- package/extension/manifest.json +1 -1
- package/extension/package.json +1 -1
- package/extension/src/background.ts +17 -1
- package/extension/src/cdp.ts +42 -0
- package/extension/src/protocol.ts +5 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +16 -0
- package/src/browser/daemon-client.ts +5 -1
- package/src/browser/page.ts +16 -0
- package/src/cli.ts +14 -14
- package/src/clis/antigravity/serve.ts +2 -2
- package/src/clis/band/bands.ts +76 -0
- package/src/clis/band/mentions.ts +134 -0
- package/src/clis/band/post.ts +187 -0
- package/src/clis/band/posts.ts +106 -0
- package/src/clis/doubao/detail.test.ts +53 -0
- package/src/clis/doubao/detail.ts +41 -0
- package/src/clis/doubao/history.test.ts +45 -0
- package/src/clis/doubao/history.ts +32 -0
- package/src/clis/doubao/meeting-summary.ts +53 -0
- package/src/clis/doubao/meeting-transcript.ts +48 -0
- package/src/clis/doubao/utils.test.ts +45 -0
- package/src/clis/doubao/utils.ts +371 -0
- package/src/clis/douyin/_shared/public-api.ts +84 -0
- package/src/clis/douyin/user-videos.test.ts +122 -0
- package/src/clis/douyin/user-videos.ts +101 -0
- package/src/clis/ones/common.ts +187 -0
- package/src/clis/ones/enrich-tasks.ts +47 -0
- package/src/clis/ones/login.ts +103 -0
- package/src/clis/ones/logout.ts +19 -0
- package/src/clis/ones/me.ts +34 -0
- package/src/clis/ones/my-tasks.ts +148 -0
- package/src/clis/ones/resolve-labels.ts +80 -0
- package/src/clis/ones/task-helpers.test.ts +14 -0
- package/src/clis/ones/task-helpers.ts +214 -0
- package/src/clis/ones/task.ts +79 -0
- package/src/clis/ones/tasks.ts +92 -0
- package/src/clis/ones/token-info.ts +46 -0
- package/src/clis/ones/worklog.test.ts +24 -0
- package/src/clis/ones/worklog.ts +306 -0
- package/src/clis/sinafinance/rolling-news.ts +42 -0
- package/src/clis/sinafinance/stock.ts +127 -0
- package/src/clis/spotify/spotify.ts +328 -0
- package/src/clis/spotify/utils.test.ts +87 -0
- package/src/clis/spotify/utils.ts +92 -0
- package/src/clis/tieba/commands.test.ts +86 -0
- package/src/clis/tieba/hot.ts +52 -0
- package/src/clis/tieba/posts.ts +108 -0
- package/src/clis/tieba/read.ts +158 -0
- package/src/clis/tieba/search.ts +119 -0
- package/src/clis/tieba/utils.test.ts +322 -0
- package/src/clis/tieba/utils.ts +348 -0
- package/src/clis/weread/book.ts +116 -13
- package/src/clis/weread/commands.test.ts +249 -0
- package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
- package/src/clis/weread/search-regression.test.ts +440 -0
- package/src/clis/weread/search.ts +189 -9
- package/src/clis/weread/shelf.ts +20 -122
- package/src/clis/weread/utils.test.ts +81 -1
- package/src/clis/weread/utils.ts +264 -7
- package/src/clis/xiaohongshu/publish.test.ts +79 -1
- package/src/clis/xiaohongshu/publish.ts +84 -30
- package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
- package/src/clis/xiaohongshu/user-helpers.ts +4 -0
- package/src/clis/xueqiu/comments.test.ts +823 -0
- package/src/clis/xueqiu/comments.ts +461 -0
- package/src/clis/youtube/transcript.ts +2 -4
- package/src/clis/youtube/utils.test.ts +43 -0
- package/src/clis/youtube/utils.ts +69 -0
- package/src/clis/youtube/video.ts +16 -15
- package/src/clis/zsxq/dynamics.ts +60 -0
- package/src/clis/zsxq/groups.ts +41 -0
- package/src/clis/zsxq/search.test.ts +29 -0
- package/src/clis/zsxq/search.ts +54 -0
- package/src/clis/zsxq/topic.test.ts +34 -0
- package/src/clis/zsxq/topic.ts +68 -0
- package/src/clis/zsxq/topics.test.ts +29 -0
- package/src/clis/zsxq/topics.ts +36 -0
- package/src/clis/zsxq/utils.ts +351 -0
- package/src/commanderAdapter.test.ts +47 -0
- package/src/commanderAdapter.ts +26 -3
- package/src/daemon.ts +5 -4
- package/src/errors.ts +71 -10
- package/src/external-clis.yaml +17 -0
- package/src/external.ts +3 -3
- package/src/main.ts +2 -1
- package/src/tui.ts +2 -1
- package/src/types.ts +5 -0
- package/tests/e2e/band-auth.test.ts +20 -0
- package/tests/e2e/browser-auth-helpers.ts +18 -0
- package/tests/e2e/browser-auth.test.ts +35 -47
- package/tests/e2e/browser-public.test.ts +288 -0
- package/tests/e2e/management.test.ts +1 -1
- package/tests/e2e/plugin-management.test.ts +1 -1
- package/vitest.config.ts +1 -0
- package/SKILL.md +0 -879
- package/dist/weread-private-api-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.js +0 -39
- package/src/weread-search-regression.test.ts +0 -44
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { ArgumentError, AuthRequiredError, CliError } from '../../errors.js';
|
|
2
|
+
const SITE_DOMAIN = 'wx.zsxq.com';
|
|
3
|
+
const SITE_URL = 'https://wx.zsxq.com';
|
|
4
|
+
function asRecord(value) {
|
|
5
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
? value
|
|
7
|
+
: null;
|
|
8
|
+
}
|
|
9
|
+
function pickArray(...values) {
|
|
10
|
+
for (const value of values) {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
export async function ensureZsxqPage(page) {
|
|
18
|
+
await page.goto(SITE_URL);
|
|
19
|
+
}
|
|
20
|
+
export async function ensureZsxqAuth(page) {
|
|
21
|
+
// zsxq uses httpOnly cookies that may be on different subdomains.
|
|
22
|
+
// Verify auth by attempting a lightweight API call instead of checking cookies.
|
|
23
|
+
try {
|
|
24
|
+
const result = await page.evaluate(`
|
|
25
|
+
(async () => {
|
|
26
|
+
try {
|
|
27
|
+
const r = await new Promise((resolve, reject) => {
|
|
28
|
+
const xhr = new XMLHttpRequest();
|
|
29
|
+
xhr.open('GET', 'https://api.zsxq.com/v2/groups', true);
|
|
30
|
+
xhr.withCredentials = true;
|
|
31
|
+
xhr.setRequestHeader('accept', 'application/json');
|
|
32
|
+
xhr.onload = () => {
|
|
33
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
34
|
+
try { resolve(JSON.parse(xhr.responseText)); }
|
|
35
|
+
catch { resolve(null); }
|
|
36
|
+
} else { resolve(null); }
|
|
37
|
+
};
|
|
38
|
+
xhr.onerror = () => resolve(null);
|
|
39
|
+
xhr.send();
|
|
40
|
+
});
|
|
41
|
+
return r !== null;
|
|
42
|
+
} catch { return false; }
|
|
43
|
+
})()
|
|
44
|
+
`);
|
|
45
|
+
if (!result) {
|
|
46
|
+
throw new AuthRequiredError('zsxq.com');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err instanceof AuthRequiredError)
|
|
51
|
+
throw err;
|
|
52
|
+
throw new AuthRequiredError('zsxq.com');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function getCookieValue(page, name) {
|
|
56
|
+
const cookies = await page.getCookies({ domain: SITE_DOMAIN });
|
|
57
|
+
return cookies.find(cookie => cookie.name === name)?.value;
|
|
58
|
+
}
|
|
59
|
+
export async function getActiveGroupId(page) {
|
|
60
|
+
const groupId = await page.evaluate(`
|
|
61
|
+
(() => {
|
|
62
|
+
const target = localStorage.getItem('target_group');
|
|
63
|
+
if (target) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(target);
|
|
66
|
+
if (parsed.group_id) return String(parsed.group_id);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
})()
|
|
71
|
+
`);
|
|
72
|
+
if (groupId)
|
|
73
|
+
return groupId;
|
|
74
|
+
throw new ArgumentError('Cannot determine active group_id', 'Pass --group_id <id> or open the target 知识星球 page in Chrome first');
|
|
75
|
+
}
|
|
76
|
+
export async function browserJsonRequest(page, path) {
|
|
77
|
+
return await page.evaluate(`
|
|
78
|
+
(async () => {
|
|
79
|
+
const path = ${JSON.stringify(path)};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await new Promise((resolve) => {
|
|
83
|
+
const xhr = new XMLHttpRequest();
|
|
84
|
+
xhr.open('GET', path, true);
|
|
85
|
+
xhr.withCredentials = true;
|
|
86
|
+
xhr.setRequestHeader('accept', 'application/json, text/plain, */*');
|
|
87
|
+
xhr.onload = () => {
|
|
88
|
+
let parsed = null;
|
|
89
|
+
if (xhr.responseText) {
|
|
90
|
+
try { parsed = JSON.parse(xhr.responseText); }
|
|
91
|
+
catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
resolve({
|
|
95
|
+
ok: xhr.status >= 200 && xhr.status < 300,
|
|
96
|
+
url: path,
|
|
97
|
+
status: xhr.status,
|
|
98
|
+
data: parsed,
|
|
99
|
+
error: xhr.status >= 200 && xhr.status < 300 ? undefined : 'HTTP ' + xhr.status,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
xhr.onerror = () => resolve({
|
|
103
|
+
ok: false,
|
|
104
|
+
url: path,
|
|
105
|
+
error: 'Network error',
|
|
106
|
+
});
|
|
107
|
+
xhr.send();
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
url: path,
|
|
113
|
+
error: error instanceof Error ? error.message : String(error),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
})()
|
|
117
|
+
`);
|
|
118
|
+
}
|
|
119
|
+
export async function fetchFirstJson(page, paths) {
|
|
120
|
+
let lastFailure = null;
|
|
121
|
+
for (const path of paths) {
|
|
122
|
+
const result = await browserJsonRequest(page, path);
|
|
123
|
+
if (result.ok) {
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
lastFailure = result;
|
|
127
|
+
}
|
|
128
|
+
if (!lastFailure) {
|
|
129
|
+
throw new CliError('FETCH_ERROR', 'No candidate endpoint returned JSON', `Checked endpoints: ${paths.join(', ')}`);
|
|
130
|
+
}
|
|
131
|
+
throw new CliError('FETCH_ERROR', lastFailure.error || 'Failed to fetch ZSXQ API', `Checked endpoints: ${paths.join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
export function unwrapRespData(payload) {
|
|
134
|
+
const record = asRecord(payload);
|
|
135
|
+
if (!record) {
|
|
136
|
+
throw new CliError('PARSE_ERROR', 'Invalid ZSXQ API response');
|
|
137
|
+
}
|
|
138
|
+
if (record.succeeded === false) {
|
|
139
|
+
const code = typeof record.code === 'number' ? String(record.code) : 'API_ERROR';
|
|
140
|
+
const message = typeof record.info === 'string'
|
|
141
|
+
? record.info
|
|
142
|
+
: typeof record.error === 'string'
|
|
143
|
+
? record.error
|
|
144
|
+
: 'ZSXQ API returned an error';
|
|
145
|
+
throw new CliError(code, message);
|
|
146
|
+
}
|
|
147
|
+
return (record.resp_data ?? record.data ?? payload);
|
|
148
|
+
}
|
|
149
|
+
export function getTopicsFromResponse(payload) {
|
|
150
|
+
const data = unwrapRespData(payload);
|
|
151
|
+
if (Array.isArray(data))
|
|
152
|
+
return data;
|
|
153
|
+
return pickArray(data.topics, data.list, data.records, data.items, data.search_result);
|
|
154
|
+
}
|
|
155
|
+
export function getCommentsFromResponse(payload) {
|
|
156
|
+
const data = unwrapRespData(payload);
|
|
157
|
+
if (Array.isArray(data))
|
|
158
|
+
return data;
|
|
159
|
+
return pickArray(data.comments, data.list, data.items);
|
|
160
|
+
}
|
|
161
|
+
export function getGroupsFromResponse(payload) {
|
|
162
|
+
const data = unwrapRespData(payload);
|
|
163
|
+
if (Array.isArray(data))
|
|
164
|
+
return data;
|
|
165
|
+
return pickArray(data.groups, data.list, data.items);
|
|
166
|
+
}
|
|
167
|
+
export function getTopicFromResponse(payload) {
|
|
168
|
+
const data = unwrapRespData(payload);
|
|
169
|
+
if (Array.isArray(data))
|
|
170
|
+
return data[0] ?? null;
|
|
171
|
+
if (typeof data.topic_id === 'number')
|
|
172
|
+
return data;
|
|
173
|
+
const record = asRecord(data);
|
|
174
|
+
if (!record)
|
|
175
|
+
return null;
|
|
176
|
+
const topic = record.topic;
|
|
177
|
+
return topic && typeof topic === 'object' ? topic : null;
|
|
178
|
+
}
|
|
179
|
+
export function getTopicAuthor(topic) {
|
|
180
|
+
return (topic.owner?.name ||
|
|
181
|
+
topic.talk?.owner?.name ||
|
|
182
|
+
topic.question?.owner?.name ||
|
|
183
|
+
topic.answer?.owner?.name ||
|
|
184
|
+
topic.task?.owner?.name ||
|
|
185
|
+
topic.solution?.owner?.name ||
|
|
186
|
+
'');
|
|
187
|
+
}
|
|
188
|
+
export function getTopicText(topic) {
|
|
189
|
+
const primary = [
|
|
190
|
+
topic.title,
|
|
191
|
+
topic.talk?.text,
|
|
192
|
+
topic.question?.text,
|
|
193
|
+
topic.answer?.text,
|
|
194
|
+
topic.task?.text,
|
|
195
|
+
topic.solution?.text,
|
|
196
|
+
].find(value => typeof value === 'string' && value.trim());
|
|
197
|
+
return (primary || '').replace(/\s+/g, ' ').trim();
|
|
198
|
+
}
|
|
199
|
+
export function getTopicUrl(topicId) {
|
|
200
|
+
return topicId ? `${SITE_URL}/topic/${topicId}` : SITE_URL;
|
|
201
|
+
}
|
|
202
|
+
export function summarizeComments(comments, limit = 3) {
|
|
203
|
+
return comments
|
|
204
|
+
.slice(0, limit)
|
|
205
|
+
.map((comment) => {
|
|
206
|
+
const author = comment.owner?.name || '匿名';
|
|
207
|
+
const target = comment.repliee?.name ? ` -> ${comment.repliee.name}` : '';
|
|
208
|
+
const text = (comment.text || '').replace(/\s+/g, ' ').trim();
|
|
209
|
+
return `${author}${target}: ${text}`;
|
|
210
|
+
})
|
|
211
|
+
.join(' | ');
|
|
212
|
+
}
|
|
213
|
+
export function toTopicRow(topic) {
|
|
214
|
+
const topicId = topic.topic_id ?? '';
|
|
215
|
+
const comments = pickArray(topic.show_comments, topic.comments);
|
|
216
|
+
return {
|
|
217
|
+
topic_id: topicId,
|
|
218
|
+
type: topic.type || '',
|
|
219
|
+
group: topic.group?.name || '',
|
|
220
|
+
author: getTopicAuthor(topic),
|
|
221
|
+
title: getTopicText(topic).slice(0, 120),
|
|
222
|
+
content: getTopicText(topic),
|
|
223
|
+
comments: topic.comments_count ?? comments.length ?? 0,
|
|
224
|
+
likes: topic.likes_count ?? 0,
|
|
225
|
+
readers: topic.readers_count ?? topic.reading_count ?? 0,
|
|
226
|
+
time: topic.create_time || '',
|
|
227
|
+
comment_preview: summarizeComments(comments),
|
|
228
|
+
url: getTopicUrl(topicId),
|
|
229
|
+
};
|
|
230
|
+
}
|
package/dist/commanderAdapter.js
CHANGED
|
@@ -14,10 +14,10 @@ import { fullName, getRegistry } from './registry.js';
|
|
|
14
14
|
import { formatRegistryHelpText } from './serialization.js';
|
|
15
15
|
import { render as renderOutput } from './output.js';
|
|
16
16
|
import { executeCommand } from './execution.js';
|
|
17
|
-
import { CliError, ERROR_ICONS, getErrorMessage, BrowserConnectError, AuthRequiredError, TimeoutError, SelectorError, EmptyResultError, ArgumentError, AdapterLoadError, CommandExecutionError, } from './errors.js';
|
|
17
|
+
import { CliError, EXIT_CODES, ERROR_ICONS, getErrorMessage, BrowserConnectError, AuthRequiredError, TimeoutError, SelectorError, EmptyResultError, ArgumentError, AdapterLoadError, CommandExecutionError, } from './errors.js';
|
|
18
18
|
import { checkDaemonStatus } from './browser/discover.js';
|
|
19
19
|
export function normalizeArgValue(argType, value, name) {
|
|
20
|
-
if (argType !== 'bool')
|
|
20
|
+
if (argType !== 'bool' && argType !== 'boolean')
|
|
21
21
|
return value;
|
|
22
22
|
if (typeof value === 'boolean')
|
|
23
23
|
return value;
|
|
@@ -28,7 +28,7 @@ export function normalizeArgValue(argType, value, name) {
|
|
|
28
28
|
return true;
|
|
29
29
|
if (normalized === 'false')
|
|
30
30
|
return false;
|
|
31
|
-
throw new
|
|
31
|
+
throw new ArgumentError(`"${name}" must be either "true" or "false".`);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Register a single CliCommand as a Commander subcommand.
|
|
@@ -106,10 +106,33 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
106
106
|
}
|
|
107
107
|
catch (err) {
|
|
108
108
|
await renderError(err, fullName(cmd), optionsRecord.verbose === true);
|
|
109
|
-
process.exitCode =
|
|
109
|
+
process.exitCode = resolveExitCode(err);
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
|
+
// ── Exit code resolution ─────────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Map any thrown value to a Unix process exit code.
|
|
116
|
+
*
|
|
117
|
+
* - CliError subclasses carry their own exitCode (set in errors.ts).
|
|
118
|
+
* - Generic Error objects are classified by message pattern so that
|
|
119
|
+
* un-typed auth / not-found errors from adapters still produce
|
|
120
|
+
* meaningful exit codes for shell scripts.
|
|
121
|
+
*/
|
|
122
|
+
function resolveExitCode(err) {
|
|
123
|
+
if (err instanceof CliError)
|
|
124
|
+
return err.exitCode;
|
|
125
|
+
// Pattern-based fallback for untyped errors thrown by third-party adapters.
|
|
126
|
+
const msg = getErrorMessage(err);
|
|
127
|
+
const kind = classifyGenericError(msg);
|
|
128
|
+
if (kind === 'auth')
|
|
129
|
+
return EXIT_CODES.NOPERM;
|
|
130
|
+
if (kind === 'not-found')
|
|
131
|
+
return EXIT_CODES.EMPTY_RESULT;
|
|
132
|
+
if (kind === 'http')
|
|
133
|
+
return EXIT_CODES.GENERIC_ERROR; // HTTP 4xx/5xx → generic; renderer shows details
|
|
134
|
+
return EXIT_CODES.GENERIC_ERROR;
|
|
135
|
+
}
|
|
113
136
|
// ── Error rendering ──────────────────────────────────────────────────────────
|
|
114
137
|
const ISSUES_URL = 'https://github.com/jackwener/opencli/issues';
|
|
115
138
|
/** Pattern-based classifier for untyped errors thrown by adapters. */
|
|
@@ -60,3 +60,42 @@ describe('commanderAdapter arg passing', () => {
|
|
|
60
60
|
expect(mockExecuteCommand).not.toHaveBeenCalled();
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
+
describe('commanderAdapter boolean alias support', () => {
|
|
64
|
+
const cmd = {
|
|
65
|
+
site: 'reddit',
|
|
66
|
+
name: 'save',
|
|
67
|
+
description: 'Save a post',
|
|
68
|
+
browser: false,
|
|
69
|
+
args: [
|
|
70
|
+
{ name: 'post-id', positional: true, required: true, help: 'Post ID' },
|
|
71
|
+
{ name: 'undo', type: 'boolean', default: false, help: 'Unsave instead of save' },
|
|
72
|
+
],
|
|
73
|
+
func: vi.fn(),
|
|
74
|
+
};
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
mockExecuteCommand.mockReset();
|
|
77
|
+
mockExecuteCommand.mockResolvedValue([]);
|
|
78
|
+
mockRenderOutput.mockReset();
|
|
79
|
+
delete process.env.OPENCLI_VERBOSE;
|
|
80
|
+
process.exitCode = undefined;
|
|
81
|
+
});
|
|
82
|
+
it('coerces default false for boolean args to a real boolean', async () => {
|
|
83
|
+
const program = new Command();
|
|
84
|
+
const siteCmd = program.command('reddit');
|
|
85
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
86
|
+
await program.parseAsync(['node', 'opencli', 'reddit', 'save', 't3_abc123']);
|
|
87
|
+
expect(mockExecuteCommand).toHaveBeenCalled();
|
|
88
|
+
const kwargs = mockExecuteCommand.mock.calls[0][1];
|
|
89
|
+
expect(kwargs['post-id']).toBe('t3_abc123');
|
|
90
|
+
expect(kwargs.undo).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
it('coerces explicit false for boolean args to a real boolean', async () => {
|
|
93
|
+
const program = new Command();
|
|
94
|
+
const siteCmd = program.command('reddit');
|
|
95
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
96
|
+
await program.parseAsync(['node', 'opencli', 'reddit', 'save', 't3_abc123', '--undo', 'false']);
|
|
97
|
+
expect(mockExecuteCommand).toHaveBeenCalled();
|
|
98
|
+
const kwargs = mockExecuteCommand.mock.calls[0][1];
|
|
99
|
+
expect(kwargs.undo).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
package/dist/daemon.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { createServer } from 'node:http';
|
|
22
22
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
23
23
|
import { DEFAULT_DAEMON_PORT } from './constants.js';
|
|
24
|
+
import { EXIT_CODES } from './errors.js';
|
|
24
25
|
const PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
25
26
|
const IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
26
27
|
// ─── State ───────────────────────────────────────────────────────────
|
|
@@ -41,7 +42,7 @@ function resetIdleTimer() {
|
|
|
41
42
|
clearTimeout(idleTimer);
|
|
42
43
|
idleTimer = setTimeout(() => {
|
|
43
44
|
console.error('[daemon] Idle timeout, shutting down');
|
|
44
|
-
process.exit(
|
|
45
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
45
46
|
}, IDLE_TIMEOUT);
|
|
46
47
|
}
|
|
47
48
|
// ─── HTTP Server ─────────────────────────────────────────────────────
|
|
@@ -269,10 +270,10 @@ httpServer.listen(PORT, '127.0.0.1', () => {
|
|
|
269
270
|
httpServer.on('error', (err) => {
|
|
270
271
|
if (err.code === 'EADDRINUSE') {
|
|
271
272
|
console.error(`[daemon] Port ${PORT} already in use — another daemon is likely running. Exiting.`);
|
|
272
|
-
process.exit(
|
|
273
|
+
process.exit(EXIT_CODES.SERVICE_UNAVAIL);
|
|
273
274
|
}
|
|
274
275
|
console.error('[daemon] Server error:', err.message);
|
|
275
|
-
process.exit(
|
|
276
|
+
process.exit(EXIT_CODES.GENERIC_ERROR);
|
|
276
277
|
});
|
|
277
278
|
// Graceful shutdown
|
|
278
279
|
function shutdown() {
|
|
@@ -285,7 +286,7 @@ function shutdown() {
|
|
|
285
286
|
if (extensionWs)
|
|
286
287
|
extensionWs.close();
|
|
287
288
|
httpServer.close();
|
|
288
|
-
process.exit(
|
|
289
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
289
290
|
}
|
|
290
291
|
process.on('SIGTERM', shutdown);
|
|
291
292
|
process.on('SIGINT', shutdown);
|
package/dist/errors.d.ts
CHANGED
|
@@ -4,13 +4,41 @@
|
|
|
4
4
|
* All errors thrown by the framework should extend CliError so that
|
|
5
5
|
* the top-level handler in commanderAdapter.ts can render consistent,
|
|
6
6
|
* helpful output with emoji-coded severity and actionable hints.
|
|
7
|
+
*
|
|
8
|
+
* ## Exit codes
|
|
9
|
+
*
|
|
10
|
+
* opencli follows Unix conventions (sysexits.h) for process exit codes:
|
|
11
|
+
*
|
|
12
|
+
* 0 Success
|
|
13
|
+
* 1 Generic / unexpected error
|
|
14
|
+
* 2 Argument / usage error (ArgumentError)
|
|
15
|
+
* 66 No input / empty result (EmptyResultError)
|
|
16
|
+
* 69 Service unavailable (BrowserConnectError, AdapterLoadError)
|
|
17
|
+
* 75 Temporary failure, retry later (TimeoutError) EX_TEMPFAIL
|
|
18
|
+
* 77 Permission denied / auth needed (AuthRequiredError)
|
|
19
|
+
* 78 Configuration error (ConfigError)
|
|
20
|
+
* 130 Interrupted by Ctrl-C (set by tui.ts SIGINT handler)
|
|
7
21
|
*/
|
|
22
|
+
export declare const EXIT_CODES: {
|
|
23
|
+
readonly SUCCESS: 0;
|
|
24
|
+
readonly GENERIC_ERROR: 1;
|
|
25
|
+
readonly USAGE_ERROR: 2;
|
|
26
|
+
readonly EMPTY_RESULT: 66;
|
|
27
|
+
readonly SERVICE_UNAVAIL: 69;
|
|
28
|
+
readonly TEMPFAIL: 75;
|
|
29
|
+
readonly NOPERM: 77;
|
|
30
|
+
readonly CONFIG_ERROR: 78;
|
|
31
|
+
readonly INTERRUPTED: 130;
|
|
32
|
+
};
|
|
33
|
+
export type ExitCode = typeof EXIT_CODES[keyof typeof EXIT_CODES];
|
|
8
34
|
export declare class CliError extends Error {
|
|
9
35
|
/** Machine-readable error code (e.g. 'BROWSER_CONNECT', 'AUTH_REQUIRED') */
|
|
10
36
|
readonly code: string;
|
|
11
37
|
/** Human-readable hint on how to fix the problem */
|
|
12
38
|
readonly hint?: string;
|
|
13
|
-
|
|
39
|
+
/** Unix process exit code — defaults to 1 (generic error) */
|
|
40
|
+
readonly exitCode: ExitCode;
|
|
41
|
+
constructor(code: string, message: string, hint?: string, exitCode?: ExitCode);
|
|
14
42
|
}
|
|
15
43
|
export type BrowserConnectKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
|
|
16
44
|
export declare class BrowserConnectError extends CliError {
|
package/dist/errors.js
CHANGED
|
@@ -4,61 +4,99 @@
|
|
|
4
4
|
* All errors thrown by the framework should extend CliError so that
|
|
5
5
|
* the top-level handler in commanderAdapter.ts can render consistent,
|
|
6
6
|
* helpful output with emoji-coded severity and actionable hints.
|
|
7
|
+
*
|
|
8
|
+
* ## Exit codes
|
|
9
|
+
*
|
|
10
|
+
* opencli follows Unix conventions (sysexits.h) for process exit codes:
|
|
11
|
+
*
|
|
12
|
+
* 0 Success
|
|
13
|
+
* 1 Generic / unexpected error
|
|
14
|
+
* 2 Argument / usage error (ArgumentError)
|
|
15
|
+
* 66 No input / empty result (EmptyResultError)
|
|
16
|
+
* 69 Service unavailable (BrowserConnectError, AdapterLoadError)
|
|
17
|
+
* 75 Temporary failure, retry later (TimeoutError) EX_TEMPFAIL
|
|
18
|
+
* 77 Permission denied / auth needed (AuthRequiredError)
|
|
19
|
+
* 78 Configuration error (ConfigError)
|
|
20
|
+
* 130 Interrupted by Ctrl-C (set by tui.ts SIGINT handler)
|
|
7
21
|
*/
|
|
22
|
+
// ── Exit code table ──────────────────────────────────────────────────────────
|
|
23
|
+
export const EXIT_CODES = {
|
|
24
|
+
SUCCESS: 0,
|
|
25
|
+
GENERIC_ERROR: 1,
|
|
26
|
+
USAGE_ERROR: 2, // Bad arguments / command misuse
|
|
27
|
+
EMPTY_RESULT: 66, // No data / not found (EX_NOINPUT)
|
|
28
|
+
SERVICE_UNAVAIL: 69, // Daemon / browser unavailable (EX_UNAVAILABLE)
|
|
29
|
+
TEMPFAIL: 75, // Timeout — try again later (EX_TEMPFAIL)
|
|
30
|
+
NOPERM: 77, // Auth required / permission (EX_NOPERM)
|
|
31
|
+
CONFIG_ERROR: 78, // Missing / invalid config (EX_CONFIG)
|
|
32
|
+
INTERRUPTED: 130, // Ctrl-C / SIGINT
|
|
33
|
+
};
|
|
34
|
+
// ── Base class ───────────────────────────────────────────────────────────────
|
|
8
35
|
export class CliError extends Error {
|
|
9
36
|
/** Machine-readable error code (e.g. 'BROWSER_CONNECT', 'AUTH_REQUIRED') */
|
|
10
37
|
code;
|
|
11
38
|
/** Human-readable hint on how to fix the problem */
|
|
12
39
|
hint;
|
|
13
|
-
|
|
40
|
+
/** Unix process exit code — defaults to 1 (generic error) */
|
|
41
|
+
exitCode;
|
|
42
|
+
constructor(code, message, hint, exitCode = EXIT_CODES.GENERIC_ERROR) {
|
|
14
43
|
super(message);
|
|
15
44
|
this.name = new.target.name;
|
|
16
45
|
this.code = code;
|
|
17
46
|
this.hint = hint;
|
|
47
|
+
this.exitCode = exitCode;
|
|
18
48
|
}
|
|
19
49
|
}
|
|
20
50
|
export class BrowserConnectError extends CliError {
|
|
21
51
|
kind;
|
|
22
52
|
constructor(message, hint, kind = 'unknown') {
|
|
23
|
-
super('BROWSER_CONNECT', message, hint);
|
|
53
|
+
super('BROWSER_CONNECT', message, hint, EXIT_CODES.SERVICE_UNAVAIL);
|
|
24
54
|
this.kind = kind;
|
|
25
55
|
}
|
|
26
56
|
}
|
|
27
57
|
export class AdapterLoadError extends CliError {
|
|
28
|
-
constructor(message, hint) {
|
|
58
|
+
constructor(message, hint) {
|
|
59
|
+
super('ADAPTER_LOAD', message, hint, EXIT_CODES.SERVICE_UNAVAIL);
|
|
60
|
+
}
|
|
29
61
|
}
|
|
30
62
|
export class CommandExecutionError extends CliError {
|
|
31
|
-
constructor(message, hint) {
|
|
63
|
+
constructor(message, hint) {
|
|
64
|
+
super('COMMAND_EXEC', message, hint, EXIT_CODES.GENERIC_ERROR);
|
|
65
|
+
}
|
|
32
66
|
}
|
|
33
67
|
export class ConfigError extends CliError {
|
|
34
|
-
constructor(message, hint) {
|
|
68
|
+
constructor(message, hint) {
|
|
69
|
+
super('CONFIG', message, hint, EXIT_CODES.CONFIG_ERROR);
|
|
70
|
+
}
|
|
35
71
|
}
|
|
36
72
|
export class AuthRequiredError extends CliError {
|
|
37
73
|
domain;
|
|
38
74
|
constructor(domain, message) {
|
|
39
|
-
super('AUTH_REQUIRED', message ?? `Not logged in to ${domain}`, `Please open Chrome and log in to https://${domain}
|
|
75
|
+
super('AUTH_REQUIRED', message ?? `Not logged in to ${domain}`, `Please open Chrome and log in to https://${domain}`, EXIT_CODES.NOPERM);
|
|
40
76
|
this.domain = domain;
|
|
41
77
|
}
|
|
42
78
|
}
|
|
43
79
|
export class TimeoutError extends CliError {
|
|
44
80
|
constructor(label, seconds, hint) {
|
|
45
|
-
super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var');
|
|
81
|
+
super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var', EXIT_CODES.TEMPFAIL);
|
|
46
82
|
}
|
|
47
83
|
}
|
|
48
84
|
export class ArgumentError extends CliError {
|
|
49
|
-
constructor(message, hint) {
|
|
85
|
+
constructor(message, hint) {
|
|
86
|
+
super('ARGUMENT', message, hint, EXIT_CODES.USAGE_ERROR);
|
|
87
|
+
}
|
|
50
88
|
}
|
|
51
89
|
export class EmptyResultError extends CliError {
|
|
52
90
|
constructor(command, hint) {
|
|
53
|
-
super('EMPTY_RESULT', `${command} returned no data`, hint ?? 'The page structure may have changed, or you may need to log in');
|
|
91
|
+
super('EMPTY_RESULT', `${command} returned no data`, hint ?? 'The page structure may have changed, or you may need to log in', EXIT_CODES.EMPTY_RESULT);
|
|
54
92
|
}
|
|
55
93
|
}
|
|
56
94
|
export class SelectorError extends CliError {
|
|
57
95
|
constructor(selector, hint) {
|
|
58
|
-
super('SELECTOR', `Could not find element: ${selector}`, hint ?? 'The page UI may have changed. Please report this issue.');
|
|
96
|
+
super('SELECTOR', `Could not find element: ${selector}`, hint ?? 'The page UI may have changed. Please report this issue.', EXIT_CODES.GENERIC_ERROR);
|
|
59
97
|
}
|
|
60
98
|
}
|
|
61
|
-
// ── Utilities
|
|
99
|
+
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
62
100
|
/** Extract a human-readable message from an unknown caught value. */
|
|
63
101
|
export function getErrorMessage(error) {
|
|
64
102
|
return error instanceof Error ? error.message : String(error);
|
package/dist/external-clis.yaml
CHANGED
|
@@ -30,6 +30,23 @@
|
|
|
30
30
|
install:
|
|
31
31
|
default: "npm install -g @larksuite/cli"
|
|
32
32
|
|
|
33
|
+
- name: dws
|
|
34
|
+
binary: dws
|
|
35
|
+
description: "DingTalk Workspace CLI — messages, docs, calendar, contacts and more for humans and AI agents"
|
|
36
|
+
homepage: "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
|
|
37
|
+
tags: [dingtalk, collaboration, productivity, ai-agent]
|
|
38
|
+
install:
|
|
39
|
+
mac: "curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh"
|
|
40
|
+
linux: "curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh"
|
|
41
|
+
|
|
42
|
+
- name: wecom-cli
|
|
43
|
+
binary: wecom-cli
|
|
44
|
+
description: "WeCom/企业微信 CLI — contacts, todos, meetings, messages, calendar, docs and smart sheets for AI agents"
|
|
45
|
+
homepage: "https://github.com/WecomTeam/wecom-cli"
|
|
46
|
+
tags: [wecom, wechat-work, collaboration, productivity, ai-agent]
|
|
47
|
+
install:
|
|
48
|
+
default: "npm install -g @wecom/cli"
|
|
49
|
+
|
|
33
50
|
- name: vercel
|
|
34
51
|
binary: vercel
|
|
35
52
|
description: "Vercel CLI — deploy projects, manage domains, env vars, logs and serverless functions"
|
package/dist/external.js
CHANGED
|
@@ -6,7 +6,7 @@ import { spawnSync, execFileSync } from 'node:child_process';
|
|
|
6
6
|
import yaml from 'js-yaml';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { log } from './logger.js';
|
|
9
|
-
import { getErrorMessage } from './errors.js';
|
|
9
|
+
import { EXIT_CODES, getErrorMessage } from './errors.js';
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
function getUserRegistryPath() {
|
|
12
12
|
const home = os.homedir();
|
|
@@ -154,7 +154,7 @@ export function executeExternalCli(name, args, preloaded) {
|
|
|
154
154
|
// 2. Try to auto install
|
|
155
155
|
const success = installExternalCli(cli);
|
|
156
156
|
if (!success) {
|
|
157
|
-
process.exitCode =
|
|
157
|
+
process.exitCode = EXIT_CODES.SERVICE_UNAVAIL;
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
}
|
|
@@ -162,7 +162,7 @@ export function executeExternalCli(name, args, preloaded) {
|
|
|
162
162
|
const result = spawnSync(cli.binary, args, { stdio: 'inherit' });
|
|
163
163
|
if (result.error) {
|
|
164
164
|
console.error(chalk.red(`Failed to execute '${cli.binary}': ${result.error.message}`));
|
|
165
|
-
process.exitCode =
|
|
165
|
+
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
168
|
if (result.status !== null) {
|
package/dist/main.js
CHANGED
|
@@ -21,6 +21,7 @@ import { runCli } from './cli.js';
|
|
|
21
21
|
import { emitHook } from './hooks.js';
|
|
22
22
|
import { installNodeNetwork } from './node-network.js';
|
|
23
23
|
import { registerUpdateNoticeOnExit, checkForUpdateBackground } from './update-check.js';
|
|
24
|
+
import { EXIT_CODES } from './errors.js';
|
|
24
25
|
installNodeNetwork();
|
|
25
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
27
|
const __dirname = path.dirname(__filename);
|
|
@@ -53,7 +54,7 @@ if (getCompIdx !== -1) {
|
|
|
53
54
|
cursor = words.length;
|
|
54
55
|
const candidates = getCompletions(words, cursor);
|
|
55
56
|
process.stdout.write(candidates.join('\n') + '\n');
|
|
56
|
-
process.exit(
|
|
57
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
57
58
|
}
|
|
58
59
|
await emitHook('onStartup', { command: '__startup__', args: {} });
|
|
59
60
|
runCli(BUILTIN_CLIS, USER_CLIS);
|
package/dist/tui.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Uses raw stdin mode + ANSI escape codes for interactive prompts.
|
|
5
5
|
*/
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
+
import { EXIT_CODES } from './errors.js';
|
|
7
8
|
/**
|
|
8
9
|
* Interactive multi-select checkbox prompt.
|
|
9
10
|
*
|
|
@@ -130,7 +131,7 @@ export async function checkboxPrompt(items, opts = {}) {
|
|
|
130
131
|
// Ctrl+C — exit process
|
|
131
132
|
if (key === '\x03') {
|
|
132
133
|
cleanup();
|
|
133
|
-
process.exit(
|
|
134
|
+
process.exit(EXIT_CODES.INTERRUPTED);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
stdin.on('data', onData);
|
package/dist/types.d.ts
CHANGED
|
@@ -70,6 +70,11 @@ export interface IPage {
|
|
|
70
70
|
getInterceptedRequests(): Promise<any[]>;
|
|
71
71
|
waitForCapture(timeout?: number): Promise<void>;
|
|
72
72
|
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
75
|
+
* Chrome reads the files directly — no base64 encoding or payload size limits.
|
|
76
|
+
*/
|
|
77
|
+
setFileInput?(files: string[], selector?: string): Promise<void>;
|
|
73
78
|
closeWindow?(): Promise<void>;
|
|
74
79
|
/** Returns the current page URL, or null if unavailable. */
|
|
75
80
|
getCurrentUrl?(): Promise<string | null>;
|
|
@@ -50,6 +50,7 @@ export default defineConfig({
|
|
|
50
50
|
items: [
|
|
51
51
|
{ text: 'Twitter / X', link: '/adapters/browser/twitter' },
|
|
52
52
|
{ text: 'Reddit', link: '/adapters/browser/reddit' },
|
|
53
|
+
{ text: 'Tieba', link: '/adapters/browser/tieba' },
|
|
53
54
|
{ text: 'Bilibili', link: '/adapters/browser/bilibili' },
|
|
54
55
|
{ text: 'Zhihu', link: '/adapters/browser/zhihu' },
|
|
55
56
|
{ text: 'Xiaohongshu', link: '/adapters/browser/xiaohongshu' },
|
|
@@ -68,6 +69,7 @@ export default defineConfig({
|
|
|
68
69
|
{ text: 'Jimeng', link: '/adapters/browser/jimeng' },
|
|
69
70
|
{ text: 'Yollomi', link: '/adapters/browser/yollomi' },
|
|
70
71
|
{ text: 'LINUX DO', link: '/adapters/browser/linux-do' },
|
|
72
|
+
{ text: 'Band', link: '/adapters/browser/band' },
|
|
71
73
|
{ text: 'Chaoxing', link: '/adapters/browser/chaoxing' },
|
|
72
74
|
{ text: 'Grok', link: '/adapters/browser/grok' },
|
|
73
75
|
{ text: 'WeRead', link: '/adapters/browser/weread' },
|
|
@@ -104,6 +106,7 @@ export default defineConfig({
|
|
|
104
106
|
{ text: 'Barchart', link: '/adapters/browser/barchart' },
|
|
105
107
|
{ text: 'Hugging Face', link: '/adapters/browser/hf' },
|
|
106
108
|
{ text: 'Sina Finance', link: '/adapters/browser/sinafinance' },
|
|
109
|
+
{ text: 'Spotify', link: '/adapters/browser/spotify' },
|
|
107
110
|
{ text: 'Stack Overflow', link: '/adapters/browser/stackoverflow' },
|
|
108
111
|
{ text: 'Wikipedia', link: '/adapters/browser/wikipedia' },
|
|
109
112
|
{ text: 'Lobsters', link: '/adapters/browser/lobsters' },
|