@jackwener/opencli 1.5.5 → 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 +1229 -67
- 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/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 +1 -1
- package/dist/commanderAdapter.test.js +39 -0
- package/dist/external-clis.yaml +17 -0
- 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/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/src/background.ts +15 -0
- 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/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/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 +1 -1
- package/src/external-clis.yaml +17 -0
- 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
|
@@ -17,7 +17,7 @@ import { executeCommand } from './execution.js';
|
|
|
17
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;
|
|
@@ -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/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/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' },
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Band
|
|
2
|
+
|
|
3
|
+
**Mode**: 🔐 Browser · **Domain**: `www.band.us`
|
|
4
|
+
|
|
5
|
+
Read posts, comments, and notifications from [Band](https://www.band.us), a private community platform. Authentication uses your logged-in Chrome session (cookie-based).
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | Description |
|
|
10
|
+
|---------|-------------|
|
|
11
|
+
| `opencli band bands` | List all Bands you belong to |
|
|
12
|
+
| `opencli band posts <band_no>` | List posts from a Band |
|
|
13
|
+
| `opencli band post <band_no> <post_no>` | Export full post content including nested comments |
|
|
14
|
+
| `opencli band mentions` | Show notifications where you were @mentioned |
|
|
15
|
+
|
|
16
|
+
## Usage Examples
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# List all your bands (get band_no from here)
|
|
20
|
+
opencli band bands
|
|
21
|
+
|
|
22
|
+
# List recent posts in a band
|
|
23
|
+
opencli band posts 12345678 --limit 10
|
|
24
|
+
|
|
25
|
+
# Export a post with comments
|
|
26
|
+
opencli band post 12345678 987654321
|
|
27
|
+
|
|
28
|
+
# Export post body only (skip comments)
|
|
29
|
+
opencli band post 12345678 987654321 --comments false
|
|
30
|
+
|
|
31
|
+
# Export post and download attached photos
|
|
32
|
+
opencli band post 12345678 987654321 --output ./band-photos
|
|
33
|
+
|
|
34
|
+
# Show recent @mention notifications
|
|
35
|
+
opencli band mentions --limit 20
|
|
36
|
+
|
|
37
|
+
# Show only unread mentions
|
|
38
|
+
opencli band mentions --unread true
|
|
39
|
+
|
|
40
|
+
# Show all notification types
|
|
41
|
+
opencli band mentions --filter all
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `band mentions` filter options
|
|
45
|
+
|
|
46
|
+
| Filter | Description |
|
|
47
|
+
|--------|-------------|
|
|
48
|
+
| `mentioned` | Only notifications where you were @mentioned (default) |
|
|
49
|
+
| `all` | All notifications |
|
|
50
|
+
| `post` | Post-related notifications |
|
|
51
|
+
| `comment` | Comment-related notifications |
|
|
52
|
+
|
|
53
|
+
## Prerequisites
|
|
54
|
+
|
|
55
|
+
- Chrome running and **logged into** [band.us](https://www.band.us)
|
|
56
|
+
- [Browser Bridge extension](/guide/browser-bridge) installed
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
- `band_no` is the numeric ID in the Band URL: `band.us/band/{band_no}/post`
|
|
61
|
+
- `band bands` lists all your bands with their `band_no` values
|
|
62
|
+
- `band post` output rows: `type=post` (the post itself), `type=comment` (top-level comment), `type=reply` (nested reply)
|
|
63
|
+
- Photo downloads use the full-resolution URL (thumbnail query params are stripped automatically)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ONES
|
|
2
|
+
|
|
3
|
+
**Mode**: 🔐 Browser Bridge · **Domain**: `ones.cn` (self-hosted via `ONES_BASE_URL`)
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `opencli ones login` | Login via Project API (`auth/login`) |
|
|
10
|
+
| `opencli ones me` | Current user profile (`users/me`) |
|
|
11
|
+
| `opencli ones token-info` | Token/user/team summary (`auth/token_info`) |
|
|
12
|
+
| `opencli ones tasks` | Team task list with status/project labels and hours |
|
|
13
|
+
| `opencli ones my-tasks` | My tasks (`assign`/`field004`/`owner`/`both`) |
|
|
14
|
+
| `opencli ones task` | Task detail by UUID (`team/:team/task/:id/info`) |
|
|
15
|
+
| `opencli ones worklog` | Log/backfill hours (GraphQL `addManhour` first, then REST fallbacks) |
|
|
16
|
+
| `opencli ones logout` | Logout (`auth/logout`) |
|
|
17
|
+
|
|
18
|
+
## Usage Examples
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Required: your ONES base URL
|
|
22
|
+
export ONES_BASE_URL=https://your-instance.example.com
|
|
23
|
+
|
|
24
|
+
# Optional if your deployment requires auth headers
|
|
25
|
+
# export ONES_USER_ID=...
|
|
26
|
+
# export ONES_AUTH_TOKEN=...
|
|
27
|
+
|
|
28
|
+
# Login/profile
|
|
29
|
+
opencli ones login --email you@company.com --password 'your-password'
|
|
30
|
+
opencli ones me
|
|
31
|
+
opencli ones token-info
|
|
32
|
+
|
|
33
|
+
# Task lists
|
|
34
|
+
opencli ones tasks <teamUUID> --limit 20
|
|
35
|
+
opencli ones tasks <teamUUID> --project <projectUUID> --assign <userUUID>
|
|
36
|
+
opencli ones my-tasks <teamUUID> --limit 100
|
|
37
|
+
opencli ones my-tasks <teamUUID> --mode both
|
|
38
|
+
|
|
39
|
+
# Task detail
|
|
40
|
+
opencli ones task <taskUUID> --team <teamUUID>
|
|
41
|
+
|
|
42
|
+
# Worklog: today / backfill
|
|
43
|
+
opencli ones worklog <taskUUID> 2 --team <teamUUID>
|
|
44
|
+
opencli ones worklog <taskUUID> 1.5 --team <teamUUID> --date 2026-03-23 --note "integration"
|
|
45
|
+
|
|
46
|
+
opencli ones logout
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Prerequisites
|
|
50
|
+
|
|
51
|
+
- Chrome running and logged into your ONES instance
|
|
52
|
+
- [Browser Bridge extension](/guide/browser-bridge) installed
|
|
53
|
+
- `ONES_BASE_URL` set to the same origin opened in Chrome
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- This adapter targets legacy ONES Project API deployments.
|
|
58
|
+
- `ONES_TEAM_UUID` can be set to omit `--team` in `tasks` / `my-tasks` / `task`.
|
|
59
|
+
- Hours display and input use `ONES_MANHOUR_SCALE` (default `100000`).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Spotify
|
|
2
|
+
|
|
3
|
+
**Mode**: 🔑 OAuth API · **Domains**: `accounts.spotify.com`, `api.spotify.com`
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `opencli spotify auth` | Authenticate with Spotify and store tokens locally |
|
|
10
|
+
| `opencli spotify status` | Show current playback status |
|
|
11
|
+
| `opencli spotify play [query]` | Resume playback or search-and-play a track |
|
|
12
|
+
| `opencli spotify pause` | Pause playback |
|
|
13
|
+
| `opencli spotify next` | Skip to the next track |
|
|
14
|
+
| `opencli spotify prev` | Skip to the previous track |
|
|
15
|
+
| `opencli spotify volume <0-100>` | Set playback volume |
|
|
16
|
+
| `opencli spotify search <query>` | Search Spotify tracks |
|
|
17
|
+
| `opencli spotify queue <query>` | Add a track to the playback queue |
|
|
18
|
+
| `opencli spotify shuffle <on|off>` | Toggle shuffle |
|
|
19
|
+
| `opencli spotify repeat <off|track|context>` | Set repeat mode |
|
|
20
|
+
|
|
21
|
+
## Usage Examples
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# First-time setup
|
|
25
|
+
opencli spotify auth
|
|
26
|
+
|
|
27
|
+
# What is playing right now?
|
|
28
|
+
opencli spotify status
|
|
29
|
+
|
|
30
|
+
# Resume playback
|
|
31
|
+
opencli spotify play
|
|
32
|
+
|
|
33
|
+
# Search and immediately play a track
|
|
34
|
+
opencli spotify play "Numb Linkin Park"
|
|
35
|
+
|
|
36
|
+
# Search without playing
|
|
37
|
+
opencli spotify search "Daft Punk" --limit 5 -f json
|
|
38
|
+
|
|
39
|
+
# Queue a track
|
|
40
|
+
opencli spotify queue "Get Lucky"
|
|
41
|
+
|
|
42
|
+
# Playback controls
|
|
43
|
+
opencli spotify pause
|
|
44
|
+
opencli spotify next
|
|
45
|
+
opencli spotify prev
|
|
46
|
+
opencli spotify volume 35
|
|
47
|
+
opencli spotify shuffle on
|
|
48
|
+
opencli spotify repeat track
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Setup
|
|
52
|
+
|
|
53
|
+
1. Create a Spotify app at <https://developer.spotify.com/dashboard>
|
|
54
|
+
2. Add `http://127.0.0.1:8888/callback` to the app's Redirect URIs
|
|
55
|
+
3. Fill in `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` in `~/.opencli/spotify.env`
|
|
56
|
+
4. Run `opencli spotify auth`
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
- Browser Bridge is not required.
|
|
61
|
+
- Tokens are stored locally at `~/.opencli/spotify-tokens.json`.
|
|
62
|
+
- Playback commands work best when you already have an active Spotify device/session.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Tieba
|
|
2
|
+
|
|
3
|
+
**Mode**: 🔐 Browser · **Domain**: `tieba.baidu.com`
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `opencli tieba hot` | Read Tieba trending topics |
|
|
10
|
+
| `opencli tieba posts <forum>` | List threads in one forum |
|
|
11
|
+
| `opencli tieba search <keyword>` | Search threads across Tieba |
|
|
12
|
+
| `opencli tieba read <thread-id>` | Read one thread page |
|
|
13
|
+
|
|
14
|
+
## Usage Examples
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Trending topics
|
|
18
|
+
opencli tieba hot --limit 5
|
|
19
|
+
|
|
20
|
+
# List forum threads
|
|
21
|
+
opencli tieba posts 李毅 --limit 10
|
|
22
|
+
|
|
23
|
+
# Search Tieba
|
|
24
|
+
opencli tieba search 编程 --limit 10
|
|
25
|
+
|
|
26
|
+
# Read one thread
|
|
27
|
+
opencli tieba read 10163164720 --limit 10
|
|
28
|
+
|
|
29
|
+
# Read page 2 of a thread
|
|
30
|
+
opencli tieba read 10163164720 --page 2 --limit 10
|
|
31
|
+
|
|
32
|
+
# JSON output
|
|
33
|
+
opencli tieba hot -f json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Notes
|
|
37
|
+
|
|
38
|
+
- `tieba search` currently supports only `--page 1`
|
|
39
|
+
- `tieba read --limit` counts reply rows; page 1 may also include the main post
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- Chrome running and able to open `tieba.baidu.com`
|
|
44
|
+
- [Browser Bridge extension](/guide/browser-bridge) installed
|
|
45
|
+
- For `posts`, `search`, and `read`, a valid Tieba login session in Chrome is recommended
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
| `opencli xueqiu hot` | 获取雪球热门动态 |
|
|
13
13
|
| `opencli xueqiu search` | 搜索雪球股票(代码或名称) |
|
|
14
14
|
| `opencli xueqiu stock` | 获取雪球股票实时行情 |
|
|
15
|
+
| `opencli xueqiu comments` | 获取单只股票的讨论动态(按时间排序) |
|
|
15
16
|
| `opencli xueqiu watchlist` | 获取雪球自选股列表 |
|
|
16
17
|
| `opencli xueqiu fund-holdings` | 获取蛋卷基金持仓明细(可用 `--account` 按子账户过滤) |
|
|
17
18
|
| `opencli xueqiu fund-snapshot` | 获取蛋卷基金快照(总资产、子账户、持仓,推荐 `-f json`) |
|
|
@@ -28,6 +29,9 @@ opencli xueqiu search 茅台
|
|
|
28
29
|
# View one stock
|
|
29
30
|
opencli xueqiu stock SH600519
|
|
30
31
|
|
|
32
|
+
# View recent discussions for one stock
|
|
33
|
+
opencli xueqiu comments SH600519 --limit 5
|
|
34
|
+
|
|
31
35
|
# Upcoming earnings dates
|
|
32
36
|
opencli xueqiu earnings-date SH600519 --next
|
|
33
37
|
|
|
@@ -57,4 +61,5 @@ opencli xueqiu feed -v
|
|
|
57
61
|
|
|
58
62
|
- `fund-holdings` exposes both market value and share fields (`volume`, `usableRemainShare`)
|
|
59
63
|
- `fund-snapshot -f json` is the easiest way to persist a full account snapshot for later analysis or diffing
|
|
64
|
+
- `comments` returns stock-scoped discussion posts from the symbol page, not reply threads under one parent post
|
|
60
65
|
- If the commands return empty data, first confirm the logged-in browser can directly see the Danjuan asset page
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 知识星球 (ZSXQ)
|
|
2
|
+
|
|
3
|
+
**Mode**: 🔐 Browser · **Domain**: `wx.zsxq.com`
|
|
4
|
+
|
|
5
|
+
Read groups, topics, search results, dynamics, and single-topic details from [知识星球](https://wx.zsxq.com) using your logged-in Chrome session.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | Description |
|
|
10
|
+
|---------|-------------|
|
|
11
|
+
| `opencli zsxq groups` | List the groups your account has joined |
|
|
12
|
+
| `opencli zsxq topics` | List topics in the active group |
|
|
13
|
+
| `opencli zsxq topic <id>` | Fetch a single topic with comments |
|
|
14
|
+
| `opencli zsxq search <keyword>` | Search topics inside a group |
|
|
15
|
+
| `opencli zsxq dynamics` | List recent dynamics across groups |
|
|
16
|
+
|
|
17
|
+
## Usage Examples
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# List your groups
|
|
21
|
+
opencli zsxq groups
|
|
22
|
+
|
|
23
|
+
# List topics from the active group in Chrome
|
|
24
|
+
opencli zsxq topics --limit 20
|
|
25
|
+
|
|
26
|
+
# Search inside the active group
|
|
27
|
+
opencli zsxq search "opencli"
|
|
28
|
+
|
|
29
|
+
# Search inside a specific group explicitly
|
|
30
|
+
opencli zsxq search "opencli" --group_id 123456789
|
|
31
|
+
|
|
32
|
+
# Export a single topic with comments
|
|
33
|
+
opencli zsxq topic 987654321 --comment_limit 20
|
|
34
|
+
|
|
35
|
+
# Read recent dynamics across all joined groups
|
|
36
|
+
opencli zsxq dynamics --limit 20
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Prerequisites
|
|
40
|
+
|
|
41
|
+
- Chrome running and **logged into** [wx.zsxq.com](https://wx.zsxq.com)
|
|
42
|
+
- [Browser Bridge extension](/guide/browser-bridge) installed
|
|
43
|
+
|
|
44
|
+
## Notes
|
|
45
|
+
|
|
46
|
+
- `zsxq topics` and `zsxq search` use the current active group context from Chrome by default
|
|
47
|
+
- If there is no active group context, pass `--group_id <id>` or open the target group in Chrome first
|
|
48
|
+
- `zsxq groups` returns `group_id`, which you can reuse with `--group_id`
|
|
49
|
+
- `zsxq topic` surfaces a missing topic as `NOT_FOUND` instead of a generic fetch error
|