@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.
Files changed (231) hide show
  1. package/README.md +27 -2
  2. package/README.zh-CN.md +36 -4
  3. package/dist/browser/daemon-client.d.ts +5 -1
  4. package/dist/browser/page.d.ts +6 -0
  5. package/dist/browser/page.js +15 -0
  6. package/dist/cli-manifest.json +1229 -67
  7. package/dist/clis/band/bands.d.ts +1 -0
  8. package/dist/clis/band/bands.js +72 -0
  9. package/dist/clis/band/mentions.d.ts +1 -0
  10. package/dist/clis/band/mentions.js +127 -0
  11. package/dist/clis/band/post.d.ts +1 -0
  12. package/dist/clis/band/post.js +175 -0
  13. package/dist/clis/band/posts.d.ts +1 -0
  14. package/dist/clis/band/posts.js +94 -0
  15. package/dist/clis/doubao/detail.d.ts +1 -0
  16. package/dist/clis/doubao/detail.js +33 -0
  17. package/dist/clis/doubao/detail.test.d.ts +1 -0
  18. package/dist/clis/doubao/detail.test.js +42 -0
  19. package/dist/clis/doubao/history.d.ts +1 -0
  20. package/dist/clis/doubao/history.js +28 -0
  21. package/dist/clis/doubao/history.test.d.ts +1 -0
  22. package/dist/clis/doubao/history.test.js +37 -0
  23. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  24. package/dist/clis/doubao/meeting-summary.js +39 -0
  25. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  26. package/dist/clis/doubao/meeting-transcript.js +36 -0
  27. package/dist/clis/doubao/utils.d.ts +27 -0
  28. package/dist/clis/doubao/utils.js +317 -0
  29. package/dist/clis/doubao/utils.test.d.ts +1 -0
  30. package/dist/clis/doubao/utils.test.js +24 -0
  31. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  32. package/dist/clis/douyin/_shared/public-api.js +29 -0
  33. package/dist/clis/douyin/user-videos.d.ts +5 -0
  34. package/dist/clis/douyin/user-videos.js +74 -0
  35. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  36. package/dist/clis/douyin/user-videos.test.js +108 -0
  37. package/dist/clis/ones/common.d.ts +32 -0
  38. package/dist/clis/ones/common.js +144 -0
  39. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  40. package/dist/clis/ones/enrich-tasks.js +37 -0
  41. package/dist/clis/ones/login.d.ts +1 -0
  42. package/dist/clis/ones/login.js +80 -0
  43. package/dist/clis/ones/logout.d.ts +1 -0
  44. package/dist/clis/ones/logout.js +17 -0
  45. package/dist/clis/ones/me.d.ts +1 -0
  46. package/dist/clis/ones/me.js +30 -0
  47. package/dist/clis/ones/my-tasks.d.ts +1 -0
  48. package/dist/clis/ones/my-tasks.js +120 -0
  49. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  50. package/dist/clis/ones/resolve-labels.js +64 -0
  51. package/dist/clis/ones/task-helpers.d.ts +29 -0
  52. package/dist/clis/ones/task-helpers.js +212 -0
  53. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  54. package/dist/clis/ones/task-helpers.test.js +12 -0
  55. package/dist/clis/ones/task.d.ts +1 -0
  56. package/dist/clis/ones/task.js +66 -0
  57. package/dist/clis/ones/tasks.d.ts +1 -0
  58. package/dist/clis/ones/tasks.js +79 -0
  59. package/dist/clis/ones/token-info.d.ts +1 -0
  60. package/dist/clis/ones/token-info.js +42 -0
  61. package/dist/clis/ones/worklog.d.ts +11 -0
  62. package/dist/clis/ones/worklog.js +267 -0
  63. package/dist/clis/ones/worklog.test.d.ts +1 -0
  64. package/dist/clis/ones/worklog.test.js +20 -0
  65. package/dist/clis/spotify/spotify.d.ts +1 -0
  66. package/dist/clis/spotify/spotify.js +316 -0
  67. package/dist/clis/spotify/utils.d.ts +21 -0
  68. package/dist/clis/spotify/utils.js +66 -0
  69. package/dist/clis/spotify/utils.test.d.ts +1 -0
  70. package/dist/clis/spotify/utils.test.js +67 -0
  71. package/dist/clis/tieba/commands.test.d.ts +4 -0
  72. package/dist/clis/tieba/commands.test.js +79 -0
  73. package/dist/clis/tieba/hot.d.ts +1 -0
  74. package/dist/clis/tieba/hot.js +48 -0
  75. package/dist/clis/tieba/posts.d.ts +1 -0
  76. package/dist/clis/tieba/posts.js +85 -0
  77. package/dist/clis/tieba/read.d.ts +1 -0
  78. package/dist/clis/tieba/read.js +140 -0
  79. package/dist/clis/tieba/search.d.ts +1 -0
  80. package/dist/clis/tieba/search.js +108 -0
  81. package/dist/clis/tieba/utils.d.ts +101 -0
  82. package/dist/clis/tieba/utils.js +240 -0
  83. package/dist/clis/tieba/utils.test.d.ts +1 -0
  84. package/dist/clis/tieba/utils.test.js +290 -0
  85. package/dist/clis/weread/book.js +100 -13
  86. package/dist/clis/weread/commands.test.js +221 -0
  87. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  88. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  89. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  90. package/dist/clis/weread/search-regression.test.js +407 -0
  91. package/dist/clis/weread/search.js +143 -7
  92. package/dist/clis/weread/shelf.js +13 -95
  93. package/dist/clis/weread/utils.d.ts +46 -0
  94. package/dist/clis/weread/utils.js +214 -7
  95. package/dist/clis/weread/utils.test.js +71 -1
  96. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  97. package/dist/clis/xiaohongshu/publish.js +78 -31
  98. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  99. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  100. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  101. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  102. package/dist/clis/xueqiu/comments.d.ts +118 -0
  103. package/dist/clis/xueqiu/comments.js +354 -0
  104. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  105. package/dist/clis/xueqiu/comments.test.js +696 -0
  106. package/dist/clis/youtube/transcript.js +2 -4
  107. package/dist/clis/youtube/utils.d.ts +9 -0
  108. package/dist/clis/youtube/utils.js +67 -3
  109. package/dist/clis/youtube/utils.test.d.ts +1 -0
  110. package/dist/clis/youtube/utils.test.js +37 -0
  111. package/dist/clis/youtube/video.js +16 -15
  112. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  113. package/dist/clis/zsxq/dynamics.js +47 -0
  114. package/dist/clis/zsxq/groups.d.ts +1 -0
  115. package/dist/clis/zsxq/groups.js +32 -0
  116. package/dist/clis/zsxq/search.d.ts +1 -0
  117. package/dist/clis/zsxq/search.js +43 -0
  118. package/dist/clis/zsxq/search.test.d.ts +1 -0
  119. package/dist/clis/zsxq/search.test.js +24 -0
  120. package/dist/clis/zsxq/topic.d.ts +1 -0
  121. package/dist/clis/zsxq/topic.js +47 -0
  122. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  123. package/dist/clis/zsxq/topic.test.js +29 -0
  124. package/dist/clis/zsxq/topics.d.ts +1 -0
  125. package/dist/clis/zsxq/topics.js +25 -0
  126. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  127. package/dist/clis/zsxq/topics.test.js +24 -0
  128. package/dist/clis/zsxq/utils.d.ts +97 -0
  129. package/dist/clis/zsxq/utils.js +230 -0
  130. package/dist/commanderAdapter.js +1 -1
  131. package/dist/commanderAdapter.test.js +39 -0
  132. package/dist/external-clis.yaml +17 -0
  133. package/dist/types.d.ts +5 -0
  134. package/docs/.vitepress/config.mts +3 -0
  135. package/docs/adapters/browser/band.md +63 -0
  136. package/docs/adapters/browser/ones.md +59 -0
  137. package/docs/adapters/browser/spotify.md +62 -0
  138. package/docs/adapters/browser/tieba.md +45 -0
  139. package/docs/adapters/browser/xueqiu.md +5 -0
  140. package/docs/adapters/browser/zsxq.md +49 -0
  141. package/docs/adapters/index.md +5 -2
  142. package/docs/adapters-doc/ones.md +32 -0
  143. package/extension/src/background.ts +15 -0
  144. package/extension/src/cdp.ts +42 -0
  145. package/extension/src/protocol.ts +5 -1
  146. package/package.json +1 -1
  147. package/scripts/postinstall.js +16 -0
  148. package/src/browser/daemon-client.ts +5 -1
  149. package/src/browser/page.ts +16 -0
  150. package/src/clis/band/bands.ts +76 -0
  151. package/src/clis/band/mentions.ts +134 -0
  152. package/src/clis/band/post.ts +187 -0
  153. package/src/clis/band/posts.ts +106 -0
  154. package/src/clis/doubao/detail.test.ts +53 -0
  155. package/src/clis/doubao/detail.ts +41 -0
  156. package/src/clis/doubao/history.test.ts +45 -0
  157. package/src/clis/doubao/history.ts +32 -0
  158. package/src/clis/doubao/meeting-summary.ts +53 -0
  159. package/src/clis/doubao/meeting-transcript.ts +48 -0
  160. package/src/clis/doubao/utils.test.ts +45 -0
  161. package/src/clis/doubao/utils.ts +371 -0
  162. package/src/clis/douyin/_shared/public-api.ts +84 -0
  163. package/src/clis/douyin/user-videos.test.ts +122 -0
  164. package/src/clis/douyin/user-videos.ts +101 -0
  165. package/src/clis/ones/common.ts +187 -0
  166. package/src/clis/ones/enrich-tasks.ts +47 -0
  167. package/src/clis/ones/login.ts +103 -0
  168. package/src/clis/ones/logout.ts +19 -0
  169. package/src/clis/ones/me.ts +34 -0
  170. package/src/clis/ones/my-tasks.ts +148 -0
  171. package/src/clis/ones/resolve-labels.ts +80 -0
  172. package/src/clis/ones/task-helpers.test.ts +14 -0
  173. package/src/clis/ones/task-helpers.ts +214 -0
  174. package/src/clis/ones/task.ts +79 -0
  175. package/src/clis/ones/tasks.ts +92 -0
  176. package/src/clis/ones/token-info.ts +46 -0
  177. package/src/clis/ones/worklog.test.ts +24 -0
  178. package/src/clis/ones/worklog.ts +306 -0
  179. package/src/clis/spotify/spotify.ts +328 -0
  180. package/src/clis/spotify/utils.test.ts +87 -0
  181. package/src/clis/spotify/utils.ts +92 -0
  182. package/src/clis/tieba/commands.test.ts +86 -0
  183. package/src/clis/tieba/hot.ts +52 -0
  184. package/src/clis/tieba/posts.ts +108 -0
  185. package/src/clis/tieba/read.ts +158 -0
  186. package/src/clis/tieba/search.ts +119 -0
  187. package/src/clis/tieba/utils.test.ts +322 -0
  188. package/src/clis/tieba/utils.ts +348 -0
  189. package/src/clis/weread/book.ts +116 -13
  190. package/src/clis/weread/commands.test.ts +249 -0
  191. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  192. package/src/clis/weread/search-regression.test.ts +440 -0
  193. package/src/clis/weread/search.ts +189 -9
  194. package/src/clis/weread/shelf.ts +20 -122
  195. package/src/clis/weread/utils.test.ts +81 -1
  196. package/src/clis/weread/utils.ts +264 -7
  197. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  198. package/src/clis/xiaohongshu/publish.ts +84 -30
  199. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  200. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  201. package/src/clis/xueqiu/comments.test.ts +823 -0
  202. package/src/clis/xueqiu/comments.ts +461 -0
  203. package/src/clis/youtube/transcript.ts +2 -4
  204. package/src/clis/youtube/utils.test.ts +43 -0
  205. package/src/clis/youtube/utils.ts +69 -0
  206. package/src/clis/youtube/video.ts +16 -15
  207. package/src/clis/zsxq/dynamics.ts +60 -0
  208. package/src/clis/zsxq/groups.ts +41 -0
  209. package/src/clis/zsxq/search.test.ts +29 -0
  210. package/src/clis/zsxq/search.ts +54 -0
  211. package/src/clis/zsxq/topic.test.ts +34 -0
  212. package/src/clis/zsxq/topic.ts +68 -0
  213. package/src/clis/zsxq/topics.test.ts +29 -0
  214. package/src/clis/zsxq/topics.ts +36 -0
  215. package/src/clis/zsxq/utils.ts +351 -0
  216. package/src/commanderAdapter.test.ts +47 -0
  217. package/src/commanderAdapter.ts +1 -1
  218. package/src/external-clis.yaml +17 -0
  219. package/src/types.ts +5 -0
  220. package/tests/e2e/band-auth.test.ts +20 -0
  221. package/tests/e2e/browser-auth-helpers.ts +18 -0
  222. package/tests/e2e/browser-auth.test.ts +35 -47
  223. package/tests/e2e/browser-public.test.ts +288 -0
  224. package/tests/e2e/management.test.ts +1 -1
  225. package/tests/e2e/plugin-management.test.ts +1 -1
  226. package/vitest.config.ts +1 -0
  227. package/SKILL.md +0 -879
  228. package/dist/weread-private-api-regression.test.d.ts +0 -1
  229. package/dist/weread-search-regression.test.d.ts +0 -1
  230. package/dist/weread-search-regression.test.js +0 -39
  231. 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
+ }
@@ -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
+ });
@@ -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