@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.
Files changed (256) 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 +1284 -67
  7. package/dist/cli.js +14 -14
  8. package/dist/clis/antigravity/serve.js +2 -2
  9. package/dist/clis/band/bands.d.ts +1 -0
  10. package/dist/clis/band/bands.js +72 -0
  11. package/dist/clis/band/mentions.d.ts +1 -0
  12. package/dist/clis/band/mentions.js +127 -0
  13. package/dist/clis/band/post.d.ts +1 -0
  14. package/dist/clis/band/post.js +175 -0
  15. package/dist/clis/band/posts.d.ts +1 -0
  16. package/dist/clis/band/posts.js +94 -0
  17. package/dist/clis/doubao/detail.d.ts +1 -0
  18. package/dist/clis/doubao/detail.js +33 -0
  19. package/dist/clis/doubao/detail.test.d.ts +1 -0
  20. package/dist/clis/doubao/detail.test.js +42 -0
  21. package/dist/clis/doubao/history.d.ts +1 -0
  22. package/dist/clis/doubao/history.js +28 -0
  23. package/dist/clis/doubao/history.test.d.ts +1 -0
  24. package/dist/clis/doubao/history.test.js +37 -0
  25. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  26. package/dist/clis/doubao/meeting-summary.js +39 -0
  27. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  28. package/dist/clis/doubao/meeting-transcript.js +36 -0
  29. package/dist/clis/doubao/utils.d.ts +27 -0
  30. package/dist/clis/doubao/utils.js +317 -0
  31. package/dist/clis/doubao/utils.test.d.ts +1 -0
  32. package/dist/clis/doubao/utils.test.js +24 -0
  33. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  34. package/dist/clis/douyin/_shared/public-api.js +29 -0
  35. package/dist/clis/douyin/user-videos.d.ts +5 -0
  36. package/dist/clis/douyin/user-videos.js +74 -0
  37. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  38. package/dist/clis/douyin/user-videos.test.js +108 -0
  39. package/dist/clis/ones/common.d.ts +32 -0
  40. package/dist/clis/ones/common.js +144 -0
  41. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  42. package/dist/clis/ones/enrich-tasks.js +37 -0
  43. package/dist/clis/ones/login.d.ts +1 -0
  44. package/dist/clis/ones/login.js +80 -0
  45. package/dist/clis/ones/logout.d.ts +1 -0
  46. package/dist/clis/ones/logout.js +17 -0
  47. package/dist/clis/ones/me.d.ts +1 -0
  48. package/dist/clis/ones/me.js +30 -0
  49. package/dist/clis/ones/my-tasks.d.ts +1 -0
  50. package/dist/clis/ones/my-tasks.js +120 -0
  51. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  52. package/dist/clis/ones/resolve-labels.js +64 -0
  53. package/dist/clis/ones/task-helpers.d.ts +29 -0
  54. package/dist/clis/ones/task-helpers.js +212 -0
  55. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  56. package/dist/clis/ones/task-helpers.test.js +12 -0
  57. package/dist/clis/ones/task.d.ts +1 -0
  58. package/dist/clis/ones/task.js +66 -0
  59. package/dist/clis/ones/tasks.d.ts +1 -0
  60. package/dist/clis/ones/tasks.js +79 -0
  61. package/dist/clis/ones/token-info.d.ts +1 -0
  62. package/dist/clis/ones/token-info.js +42 -0
  63. package/dist/clis/ones/worklog.d.ts +11 -0
  64. package/dist/clis/ones/worklog.js +267 -0
  65. package/dist/clis/ones/worklog.test.d.ts +1 -0
  66. package/dist/clis/ones/worklog.test.js +20 -0
  67. package/dist/clis/sinafinance/rolling-news.d.ts +4 -0
  68. package/dist/clis/sinafinance/rolling-news.js +40 -0
  69. package/dist/clis/sinafinance/stock.d.ts +8 -0
  70. package/dist/clis/sinafinance/stock.js +117 -0
  71. package/dist/clis/spotify/spotify.d.ts +1 -0
  72. package/dist/clis/spotify/spotify.js +316 -0
  73. package/dist/clis/spotify/utils.d.ts +21 -0
  74. package/dist/clis/spotify/utils.js +66 -0
  75. package/dist/clis/spotify/utils.test.d.ts +1 -0
  76. package/dist/clis/spotify/utils.test.js +67 -0
  77. package/dist/clis/tieba/commands.test.d.ts +4 -0
  78. package/dist/clis/tieba/commands.test.js +79 -0
  79. package/dist/clis/tieba/hot.d.ts +1 -0
  80. package/dist/clis/tieba/hot.js +48 -0
  81. package/dist/clis/tieba/posts.d.ts +1 -0
  82. package/dist/clis/tieba/posts.js +85 -0
  83. package/dist/clis/tieba/read.d.ts +1 -0
  84. package/dist/clis/tieba/read.js +140 -0
  85. package/dist/clis/tieba/search.d.ts +1 -0
  86. package/dist/clis/tieba/search.js +108 -0
  87. package/dist/clis/tieba/utils.d.ts +101 -0
  88. package/dist/clis/tieba/utils.js +240 -0
  89. package/dist/clis/tieba/utils.test.d.ts +1 -0
  90. package/dist/clis/tieba/utils.test.js +290 -0
  91. package/dist/clis/weread/book.js +100 -13
  92. package/dist/clis/weread/commands.test.js +221 -0
  93. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  94. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  95. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  96. package/dist/clis/weread/search-regression.test.js +407 -0
  97. package/dist/clis/weread/search.js +143 -7
  98. package/dist/clis/weread/shelf.js +13 -95
  99. package/dist/clis/weread/utils.d.ts +46 -0
  100. package/dist/clis/weread/utils.js +214 -7
  101. package/dist/clis/weread/utils.test.js +71 -1
  102. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  103. package/dist/clis/xiaohongshu/publish.js +78 -31
  104. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  105. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  106. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  107. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  108. package/dist/clis/xueqiu/comments.d.ts +118 -0
  109. package/dist/clis/xueqiu/comments.js +354 -0
  110. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  111. package/dist/clis/xueqiu/comments.test.js +696 -0
  112. package/dist/clis/youtube/transcript.js +2 -4
  113. package/dist/clis/youtube/utils.d.ts +9 -0
  114. package/dist/clis/youtube/utils.js +67 -3
  115. package/dist/clis/youtube/utils.test.d.ts +1 -0
  116. package/dist/clis/youtube/utils.test.js +37 -0
  117. package/dist/clis/youtube/video.js +16 -15
  118. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  119. package/dist/clis/zsxq/dynamics.js +47 -0
  120. package/dist/clis/zsxq/groups.d.ts +1 -0
  121. package/dist/clis/zsxq/groups.js +32 -0
  122. package/dist/clis/zsxq/search.d.ts +1 -0
  123. package/dist/clis/zsxq/search.js +43 -0
  124. package/dist/clis/zsxq/search.test.d.ts +1 -0
  125. package/dist/clis/zsxq/search.test.js +24 -0
  126. package/dist/clis/zsxq/topic.d.ts +1 -0
  127. package/dist/clis/zsxq/topic.js +47 -0
  128. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  129. package/dist/clis/zsxq/topic.test.js +29 -0
  130. package/dist/clis/zsxq/topics.d.ts +1 -0
  131. package/dist/clis/zsxq/topics.js +25 -0
  132. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  133. package/dist/clis/zsxq/topics.test.js +24 -0
  134. package/dist/clis/zsxq/utils.d.ts +97 -0
  135. package/dist/clis/zsxq/utils.js +230 -0
  136. package/dist/commanderAdapter.js +27 -4
  137. package/dist/commanderAdapter.test.js +39 -0
  138. package/dist/daemon.js +5 -4
  139. package/dist/errors.d.ts +29 -1
  140. package/dist/errors.js +49 -11
  141. package/dist/external-clis.yaml +17 -0
  142. package/dist/external.js +3 -3
  143. package/dist/main.js +2 -1
  144. package/dist/tui.js +2 -1
  145. package/dist/types.d.ts +5 -0
  146. package/docs/.vitepress/config.mts +3 -0
  147. package/docs/adapters/browser/band.md +63 -0
  148. package/docs/adapters/browser/ones.md +59 -0
  149. package/docs/adapters/browser/sinafinance.md +56 -6
  150. package/docs/adapters/browser/spotify.md +62 -0
  151. package/docs/adapters/browser/tieba.md +45 -0
  152. package/docs/adapters/browser/xueqiu.md +5 -0
  153. package/docs/adapters/browser/zsxq.md +49 -0
  154. package/docs/adapters/index.md +5 -2
  155. package/docs/adapters-doc/ones.md +32 -0
  156. package/extension/dist/background.js +1 -2
  157. package/extension/manifest.json +1 -1
  158. package/extension/package.json +1 -1
  159. package/extension/src/background.ts +17 -1
  160. package/extension/src/cdp.ts +42 -0
  161. package/extension/src/protocol.ts +5 -1
  162. package/package.json +1 -1
  163. package/scripts/postinstall.js +16 -0
  164. package/src/browser/daemon-client.ts +5 -1
  165. package/src/browser/page.ts +16 -0
  166. package/src/cli.ts +14 -14
  167. package/src/clis/antigravity/serve.ts +2 -2
  168. package/src/clis/band/bands.ts +76 -0
  169. package/src/clis/band/mentions.ts +134 -0
  170. package/src/clis/band/post.ts +187 -0
  171. package/src/clis/band/posts.ts +106 -0
  172. package/src/clis/doubao/detail.test.ts +53 -0
  173. package/src/clis/doubao/detail.ts +41 -0
  174. package/src/clis/doubao/history.test.ts +45 -0
  175. package/src/clis/doubao/history.ts +32 -0
  176. package/src/clis/doubao/meeting-summary.ts +53 -0
  177. package/src/clis/doubao/meeting-transcript.ts +48 -0
  178. package/src/clis/doubao/utils.test.ts +45 -0
  179. package/src/clis/doubao/utils.ts +371 -0
  180. package/src/clis/douyin/_shared/public-api.ts +84 -0
  181. package/src/clis/douyin/user-videos.test.ts +122 -0
  182. package/src/clis/douyin/user-videos.ts +101 -0
  183. package/src/clis/ones/common.ts +187 -0
  184. package/src/clis/ones/enrich-tasks.ts +47 -0
  185. package/src/clis/ones/login.ts +103 -0
  186. package/src/clis/ones/logout.ts +19 -0
  187. package/src/clis/ones/me.ts +34 -0
  188. package/src/clis/ones/my-tasks.ts +148 -0
  189. package/src/clis/ones/resolve-labels.ts +80 -0
  190. package/src/clis/ones/task-helpers.test.ts +14 -0
  191. package/src/clis/ones/task-helpers.ts +214 -0
  192. package/src/clis/ones/task.ts +79 -0
  193. package/src/clis/ones/tasks.ts +92 -0
  194. package/src/clis/ones/token-info.ts +46 -0
  195. package/src/clis/ones/worklog.test.ts +24 -0
  196. package/src/clis/ones/worklog.ts +306 -0
  197. package/src/clis/sinafinance/rolling-news.ts +42 -0
  198. package/src/clis/sinafinance/stock.ts +127 -0
  199. package/src/clis/spotify/spotify.ts +328 -0
  200. package/src/clis/spotify/utils.test.ts +87 -0
  201. package/src/clis/spotify/utils.ts +92 -0
  202. package/src/clis/tieba/commands.test.ts +86 -0
  203. package/src/clis/tieba/hot.ts +52 -0
  204. package/src/clis/tieba/posts.ts +108 -0
  205. package/src/clis/tieba/read.ts +158 -0
  206. package/src/clis/tieba/search.ts +119 -0
  207. package/src/clis/tieba/utils.test.ts +322 -0
  208. package/src/clis/tieba/utils.ts +348 -0
  209. package/src/clis/weread/book.ts +116 -13
  210. package/src/clis/weread/commands.test.ts +249 -0
  211. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  212. package/src/clis/weread/search-regression.test.ts +440 -0
  213. package/src/clis/weread/search.ts +189 -9
  214. package/src/clis/weread/shelf.ts +20 -122
  215. package/src/clis/weread/utils.test.ts +81 -1
  216. package/src/clis/weread/utils.ts +264 -7
  217. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  218. package/src/clis/xiaohongshu/publish.ts +84 -30
  219. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  220. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  221. package/src/clis/xueqiu/comments.test.ts +823 -0
  222. package/src/clis/xueqiu/comments.ts +461 -0
  223. package/src/clis/youtube/transcript.ts +2 -4
  224. package/src/clis/youtube/utils.test.ts +43 -0
  225. package/src/clis/youtube/utils.ts +69 -0
  226. package/src/clis/youtube/video.ts +16 -15
  227. package/src/clis/zsxq/dynamics.ts +60 -0
  228. package/src/clis/zsxq/groups.ts +41 -0
  229. package/src/clis/zsxq/search.test.ts +29 -0
  230. package/src/clis/zsxq/search.ts +54 -0
  231. package/src/clis/zsxq/topic.test.ts +34 -0
  232. package/src/clis/zsxq/topic.ts +68 -0
  233. package/src/clis/zsxq/topics.test.ts +29 -0
  234. package/src/clis/zsxq/topics.ts +36 -0
  235. package/src/clis/zsxq/utils.ts +351 -0
  236. package/src/commanderAdapter.test.ts +47 -0
  237. package/src/commanderAdapter.ts +26 -3
  238. package/src/daemon.ts +5 -4
  239. package/src/errors.ts +71 -10
  240. package/src/external-clis.yaml +17 -0
  241. package/src/external.ts +3 -3
  242. package/src/main.ts +2 -1
  243. package/src/tui.ts +2 -1
  244. package/src/types.ts +5 -0
  245. package/tests/e2e/band-auth.test.ts +20 -0
  246. package/tests/e2e/browser-auth-helpers.ts +18 -0
  247. package/tests/e2e/browser-auth.test.ts +35 -47
  248. package/tests/e2e/browser-public.test.ts +288 -0
  249. package/tests/e2e/management.test.ts +1 -1
  250. package/tests/e2e/plugin-management.test.ts +1 -1
  251. package/vitest.config.ts +1 -0
  252. package/SKILL.md +0 -879
  253. package/dist/weread-private-api-regression.test.d.ts +0 -1
  254. package/dist/weread-search-regression.test.d.ts +0 -1
  255. package/dist/weread-search-regression.test.js +0 -39
  256. package/src/weread-search-regression.test.ts +0 -44
@@ -0,0 +1,214 @@
1
+ /**
2
+ * ONES filters/peek 响应解析(tasks / my-tasks 共用)
3
+ */
4
+
5
+ import { CliError } from '../../errors.js';
6
+
7
+ /** ONES task 里 field_values 常为 [{ field_uuid, value }, ...] */
8
+ function pickTitleFromFieldValuesArray(fv: unknown): string {
9
+ if (!Array.isArray(fv)) return '';
10
+ for (const item of fv) {
11
+ if (!item || typeof item !== 'object') continue;
12
+ const row = item as Record<string, unknown>;
13
+ const fu = String(row.field_uuid ?? '');
14
+ if (!fu.startsWith('field')) continue;
15
+ const v = row.value;
16
+ if (typeof v === 'string' && v.trim()) return v.trim();
17
+ if (Array.isArray(v) && v.length && typeof v[0] === 'string' && v[0].trim()) return v[0].trim();
18
+ }
19
+ return '';
20
+ }
21
+
22
+ export function pickTaskTitle(e: Record<string, unknown>): string {
23
+ for (const k of ['summary', 'name', 'title', 'subject']) {
24
+ const v = e[k];
25
+ if (typeof v === 'string' && v.trim()) return v.trim();
26
+ }
27
+ const fromArr = pickTitleFromFieldValuesArray(e.field_values);
28
+ if (fromArr) return fromArr;
29
+ const fv = e.field_values;
30
+ if (fv && typeof fv === 'object' && !Array.isArray(fv)) {
31
+ const o = fv as Record<string, unknown>;
32
+ for (const k of ['field001', 'field002', 'field003']) {
33
+ const v = o[k];
34
+ if (typeof v === 'string' && v.trim()) return v.trim();
35
+ }
36
+ }
37
+ return '';
38
+ }
39
+
40
+ /** 表格里标题别撑爆终端 */
41
+ export function ellipsizeCell(s: string, max = 64): string {
42
+ const t = s.trim();
43
+ if (t.length <= max) return t;
44
+ return `${t.slice(0, max - 1)}…`;
45
+ }
46
+
47
+ /** 辅助列:长 uuid 缩略,完整值见 -f json */
48
+ export function briefUuid(id: string, head = 6, tail = 4): string {
49
+ if (!id) return '';
50
+ if (id.length <= head + tail + 1) return id;
51
+ return `${id.slice(0, head)}…${id.slice(-tail)}`;
52
+ }
53
+
54
+ export function formatStamp(v: unknown): string {
55
+ if (v == null || v === '') return '';
56
+ const n = Number(v);
57
+ if (Number.isNaN(n)) return String(v);
58
+ const ms = n > 1e14 ? Math.floor(n / 1000) : n > 1e12 ? n : n * 1000;
59
+ try {
60
+ return new Date(ms).toISOString().replace('T', ' ').slice(0, 19);
61
+ } catch {
62
+ return String(v);
63
+ }
64
+ }
65
+
66
+ export function flattenPeekGroups(parsed: Record<string, unknown>, limit: number): Record<string, unknown>[] {
67
+ if (!Array.isArray(parsed.groups)) {
68
+ throw new CliError(
69
+ 'FETCH_ERROR',
70
+ 'Unexpected filters/peek response (missing groups)',
71
+ 'Try -f json; check team UUID and API version.',
72
+ );
73
+ }
74
+
75
+ const groups = parsed.groups as Record<string, unknown>[];
76
+ const rows: Record<string, unknown>[] = [];
77
+
78
+ for (const g of groups) {
79
+ const entries = Array.isArray(g.entries) ? (g.entries as Record<string, unknown>[]) : [];
80
+ for (const e of entries) {
81
+ rows.push(e);
82
+ if (rows.length >= limit) break;
83
+ }
84
+ if (rows.length >= limit) break;
85
+ }
86
+
87
+ return rows.slice(0, limit);
88
+ }
89
+
90
+ function fieldArrayFirstString(fv: unknown, fieldUuid: string): string {
91
+ if (!Array.isArray(fv)) return '';
92
+ for (const item of fv) {
93
+ if (!item || typeof item !== 'object') continue;
94
+ const row = item as Record<string, unknown>;
95
+ if (String(row.field_uuid ?? '') !== fieldUuid) continue;
96
+ const v = row.value;
97
+ if (typeof v === 'string') return v;
98
+ if (Array.isArray(v) && v[0] != null) return String(v[0]);
99
+ }
100
+ return '';
101
+ }
102
+
103
+ function fvRecord(e: Record<string, unknown>): Record<string, unknown> | null {
104
+ const fv = e.field_values;
105
+ return fv && typeof fv === 'object' && !Array.isArray(fv) ? (fv as Record<string, unknown>) : null;
106
+ }
107
+
108
+ /** 工作项状态 uuid(用于查 task_statuses 得中文名) */
109
+ export function getTaskStatusRawId(e: Record<string, unknown>): string {
110
+ const fv = e.field_values;
111
+ const fvObj = fvRecord(e);
112
+ if (typeof e.status_uuid === 'string') return e.status_uuid;
113
+ return fieldArrayFirstString(fv, 'field016') || (fvObj ? String(fvObj.field016 ?? '') : '');
114
+ }
115
+
116
+ /** 项目 uuid */
117
+ export function getTaskProjectRawId(e: Record<string, unknown>): string {
118
+ const fv = e.field_values;
119
+ const fvObj = fvRecord(e);
120
+ if (typeof e.project_uuid === 'string') return e.project_uuid;
121
+ return fieldArrayFirstString(fv, 'field006') || (fvObj ? String(fvObj.field006 ?? '') : '');
122
+ }
123
+
124
+ /**
125
+ * Project API 里 assess/total/remaining_manhour 多为**定点整数**(与 Web 上「小时」不一致);
126
+ * 常见换算:raw / 1e5 ≈ 小时。若你方实例不同,可设 `ONES_MANHOUR_SCALE`(默认 100000)。
127
+ */
128
+ export function onesManhourScale(): number {
129
+ const raw = Number(process.env.ONES_MANHOUR_SCALE?.trim());
130
+ if (Number.isFinite(raw) && raw > 0) return raw;
131
+ return 1e5;
132
+ }
133
+
134
+ /** 界面/h 小数 → API 内 manhour 整数(与列表「工时」列同一刻度) */
135
+ export function hoursToOnesManhourRaw(hours: number): number {
136
+ if (!Number.isFinite(hours) || hours <= 0) return 0;
137
+ return Math.max(1, Math.round(hours * onesManhourScale()));
138
+ }
139
+
140
+ function formatHoursShort(hours: number): string {
141
+ if (!Number.isFinite(hours)) return '';
142
+ const snapped = Math.round(hours * 1e6) / 1e6;
143
+ const near = Math.round(snapped);
144
+ if (Math.abs(snapped - near) < 1e-5) return `${near}h`;
145
+ const t = Math.round(snapped * 10) / 10;
146
+ return Number.isInteger(t) ? `${t}h` : `${t.toFixed(1)}h`;
147
+ }
148
+
149
+ function formatManhourSegment(label: string, v: unknown): string | null {
150
+ if (v == null || v === '') return null;
151
+ const n = Number(v);
152
+ if (!Number.isFinite(n)) return null;
153
+ const hours = n / onesManhourScale();
154
+ return `${label}${formatHoursShort(hours)}`;
155
+ }
156
+
157
+ export function formatTaskManhourSummary(e: Record<string, unknown>): string {
158
+ const parts: string[] = [];
159
+ const a = formatManhourSegment('估', e.assess_manhour);
160
+ const t = formatManhourSegment('登', e.total_manhour);
161
+ const r = formatManhourSegment('余', e.remaining_manhour);
162
+ if (a) parts.push(a);
163
+ if (t) parts.push(t);
164
+ if (r) parts.push(r);
165
+ return parts.length ? parts.join(' ') : '—';
166
+ }
167
+
168
+ export interface TaskLabelMaps {
169
+ statusByUuid?: Map<string, string>;
170
+ projectByUuid?: Map<string, string>;
171
+ }
172
+
173
+ export function mapTaskEntry(e: Record<string, unknown>, labels?: TaskLabelMaps): Record<string, string> {
174
+ const statusId = getTaskStatusRawId(e);
175
+ const projectId = getTaskProjectRawId(e);
176
+
177
+ const fullUuid = String(e.uuid ?? '');
178
+ const title = ellipsizeCell(pickTaskTitle(e));
179
+
180
+ const briefIfLong = (s: string) => (s.length > 14 ? briefUuid(s) : s);
181
+
182
+ const statusLabel = labels?.statusByUuid?.get(statusId) ?? briefIfLong(statusId);
183
+ const projectLabel = labels?.projectByUuid?.get(projectId) ?? briefIfLong(projectId);
184
+
185
+ return {
186
+ title,
187
+ status: ellipsizeCell(statusLabel, 20),
188
+ project: ellipsizeCell(projectLabel, 40),
189
+ uuid: fullUuid,
190
+ updated: formatStamp(e.server_update_stamp),
191
+ 工时: ellipsizeCell(formatTaskManhourSummary(e), 36),
192
+ };
193
+ }
194
+
195
+ export function defaultPeekBody(query: Record<string, unknown>): Record<string, unknown> {
196
+ return {
197
+ with_boards: false,
198
+ boards: null,
199
+ query,
200
+ group_by: '',
201
+ sort: [{ create_time: { order: 'desc' } }],
202
+ include_subtasks: false,
203
+ include_status_uuid: true,
204
+ include_issue_type: false,
205
+ include_project_uuid: true,
206
+ is_show_derive: false,
207
+ };
208
+ }
209
+
210
+ export function parsePeekLimit(value: unknown, fallback: number): number {
211
+ const parsed = Number.parseInt(String(value ?? ''), 10);
212
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
213
+ return Math.max(1, Math.min(500, parsed));
214
+ }
@@ -0,0 +1,79 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { onesFetchInPage } from './common.js';
4
+ import { formatStamp } from './task-helpers.js';
5
+
6
+ /**
7
+ * 工作项详情 — 对应前端路由 …/team/<team>/filter/view/…/task/<uuid>
8
+ * API: GET team/:teamUUID/task/:taskUUIDOrNumber/info
9
+ * @see https://docs.ones.cn/project/open-api-doc/project/task.html
10
+ */
11
+ cli({
12
+ site: 'ones',
13
+ name: 'task',
14
+ description:
15
+ 'ONES — work item detail (GET team/:team/task/:id/info); id is URL segment after …/task/',
16
+ domain: 'ones.cn',
17
+ strategy: Strategy.COOKIE,
18
+ browser: true,
19
+ navigateBefore: false,
20
+ args: [
21
+ {
22
+ name: 'id',
23
+ type: 'str',
24
+ required: true,
25
+ positional: true,
26
+ help: 'Work item UUID (often 16 chars) from …/task/<id>',
27
+ },
28
+ {
29
+ name: 'team',
30
+ type: 'str',
31
+ required: false,
32
+ help: 'Team UUID (8 chars from …/team/<team>/…), or set ONES_TEAM_UUID',
33
+ },
34
+ ],
35
+ columns: ['uuid', 'summary', 'number', 'status_uuid', 'assign', 'owner', 'project_uuid', 'updated'],
36
+
37
+ func: async (page, kwargs) => {
38
+ const id = String(kwargs.id ?? '').trim();
39
+ if (!id) {
40
+ throw new CliError('CONFIG', 'task id required', 'Pass the work item uuid from the URL path …/task/<id>');
41
+ }
42
+
43
+ const team =
44
+ (kwargs.team as string | undefined)?.trim() ||
45
+ process.env.ONES_TEAM_UUID?.trim() ||
46
+ process.env.ONES_TEAM_ID?.trim();
47
+ if (!team) {
48
+ throw new CliError(
49
+ 'CONFIG',
50
+ 'team UUID required',
51
+ 'Use --team <teamUUID> or set ONES_TEAM_UUID (from …/team/<team>/…).',
52
+ );
53
+ }
54
+
55
+ const path = `team/${team}/task/${encodeURIComponent(id)}/info`;
56
+ const data = (await onesFetchInPage(page, path, { method: 'GET' })) as Record<string, unknown>;
57
+
58
+ if (typeof data.uuid !== 'string') {
59
+ const hint =
60
+ typeof data.reason === 'string'
61
+ ? data.reason
62
+ : 'Use -f json to inspect response; check id length (often 16) and team.';
63
+ throw new CliError('FETCH_ERROR', `ONES task info: ${hint}`, 'Confirm task uuid and team match the browser URL.');
64
+ }
65
+
66
+ return [
67
+ {
68
+ uuid: String(data.uuid),
69
+ summary: String(data.summary ?? ''),
70
+ number: data.number != null ? String(data.number) : '',
71
+ status_uuid: String(data.status_uuid ?? ''),
72
+ assign: String(data.assign ?? ''),
73
+ owner: String(data.owner ?? ''),
74
+ project_uuid: String(data.project_uuid ?? ''),
75
+ updated: formatStamp(data.server_update_stamp),
76
+ },
77
+ ];
78
+ },
79
+ });
@@ -0,0 +1,92 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { gotoOnesHome, onesFetchInPage } from './common.js';
4
+ import { enrichPeekEntriesWithDetails } from './enrich-tasks.js';
5
+ import { resolveTaskListLabels } from './resolve-labels.js';
6
+ import { defaultPeekBody, flattenPeekGroups, mapTaskEntry, parsePeekLimit } from './task-helpers.js';
7
+
8
+ function buildQuery(project?: string, assign?: string): Record<string, unknown> {
9
+ const must: unknown[] = [];
10
+ if (project?.trim()) {
11
+ must.push({ in: { 'field_values.field006': [project.trim()] } });
12
+ }
13
+ if (assign?.trim()) {
14
+ must.push({ equal: { assign: assign.trim() } });
15
+ }
16
+ if (must.length === 0) {
17
+ return { must: [] };
18
+ }
19
+ return { must };
20
+ }
21
+
22
+ cli({
23
+ site: 'ones',
24
+ name: 'tasks',
25
+ description:
26
+ 'ONES Project API — list work items (POST team/:team/filters/peek); use token-info -f json for team uuid',
27
+ domain: 'ones.cn',
28
+ strategy: Strategy.COOKIE,
29
+ browser: true,
30
+ navigateBefore: false,
31
+ args: [
32
+ {
33
+ name: 'team',
34
+ type: 'str',
35
+ required: false,
36
+ positional: true,
37
+ help: 'Team UUID (8 chars), or set ONES_TEAM_UUID',
38
+ },
39
+ {
40
+ name: 'project',
41
+ type: 'str',
42
+ required: false,
43
+ help: 'Filter by project UUID (field006 / 所属项目)',
44
+ },
45
+ {
46
+ name: 'assign',
47
+ type: 'str',
48
+ required: false,
49
+ help: 'Filter by assignee user UUID (负责人 assign)',
50
+ },
51
+ {
52
+ name: 'limit',
53
+ type: 'int',
54
+ default: 30,
55
+ help: 'Max rows after flattening groups (default 30)',
56
+ },
57
+ ],
58
+ columns: ['title', 'status', 'project', 'uuid', 'updated', '工时'],
59
+
60
+ func: async (page, kwargs) => {
61
+ const team =
62
+ (kwargs.team as string | undefined)?.trim() ||
63
+ process.env.ONES_TEAM_UUID?.trim() ||
64
+ process.env.ONES_TEAM_ID?.trim();
65
+ if (!team) {
66
+ throw new CliError(
67
+ 'CONFIG',
68
+ 'team UUID required',
69
+ 'Pass team as first argument or set ONES_TEAM_UUID (see `opencli ones token-info -f json` → teams[].uuid).',
70
+ );
71
+ }
72
+
73
+ const project = (kwargs.project as string | undefined)?.trim();
74
+ const assign = (kwargs.assign as string | undefined)?.trim();
75
+ const limit = parsePeekLimit(kwargs.limit, 30);
76
+
77
+ await gotoOnesHome(page);
78
+
79
+ const body = defaultPeekBody(buildQuery(project, assign));
80
+ const path = `team/${team}/filters/peek`;
81
+ const parsed = (await onesFetchInPage(page, path, {
82
+ method: 'POST',
83
+ body: JSON.stringify(body),
84
+ skipGoto: true,
85
+ })) as Record<string, unknown>;
86
+
87
+ const entries = flattenPeekGroups(parsed, limit);
88
+ const enriched = await enrichPeekEntriesWithDetails(page, team, entries, true);
89
+ const labels = await resolveTaskListLabels(page, team, enriched, true);
90
+ return enriched.map((e) => mapTaskEntry(e, labels));
91
+ },
92
+ });
@@ -0,0 +1,46 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { onesFetchInPage } from './common.js';
4
+
5
+ cli({
6
+ site: 'ones',
7
+ name: 'token-info',
8
+ description:
9
+ 'ONES Project API — session detail (GET auth/token_info) via Chrome Bridge: user, teams, org',
10
+ domain: 'ones.cn',
11
+ strategy: Strategy.COOKIE,
12
+ browser: true,
13
+ navigateBefore: false,
14
+ args: [],
15
+ columns: ['uuid', 'name', 'email', 'teams', 'org_name'],
16
+
17
+ func: async (page) => {
18
+ const root = (await onesFetchInPage(page, 'auth/token_info')) as Record<string, unknown>;
19
+ const user = root.user && typeof root.user === 'object' ? (root.user as Record<string, unknown>) : null;
20
+ if (!user?.uuid) {
21
+ throw new CliError('FETCH_ERROR', 'Unexpected auth/token_info response', 'Try `opencli ones me -f json` or check ONES_* env vars.');
22
+ }
23
+
24
+ const teamRows = Array.isArray(root.teams) ? (root.teams as Record<string, unknown>[]) : [];
25
+ const teamsHint = teamRows
26
+ .map((t) => {
27
+ const n = String(t.name ?? '').trim();
28
+ const u = String(t.uuid ?? '').trim();
29
+ if (n && u) return `${n} (${u})`;
30
+ return u || n;
31
+ })
32
+ .filter(Boolean)
33
+ .join(', ');
34
+ const org = root.org && typeof root.org === 'object' ? (root.org as Record<string, unknown>) : null;
35
+
36
+ return [
37
+ {
38
+ uuid: String(user.uuid),
39
+ name: String(user.name ?? ''),
40
+ email: String(user.email ?? ''),
41
+ teams: teamsHint,
42
+ org_name: org ? String(org.name ?? '') : '',
43
+ },
44
+ ];
45
+ },
46
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildAddManhourGraphqlBody } from './worklog.js';
3
+
4
+ describe('buildAddManhourGraphqlBody', () => {
5
+ it('inlines the addManhour arguments so the mutation is syntactically valid', () => {
6
+ const payload = JSON.parse(
7
+ buildAddManhourGraphqlBody({
8
+ ownerId: 'user-1',
9
+ taskId: 'task-1',
10
+ startTime: 1711411200,
11
+ rawManhour: 150000,
12
+ note: 'Backfill',
13
+ }),
14
+ ) as { query: string };
15
+
16
+ expect(payload.query).toContain('mutation AddManhour');
17
+ expect(payload.query).toContain('owner: "user-1"');
18
+ expect(payload.query).toContain('task: "task-1"');
19
+ expect(payload.query).toContain('start_time: 1711411200');
20
+ expect(payload.query).toContain('hours: 150000');
21
+ expect(payload.query).not.toContain('$owner');
22
+ expect(payload.query).not.toContain('$task');
23
+ });
24
+ });