@wangjs-jacky/ticktick-cli 0.1.0

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 (40) hide show
  1. package/.github/workflows/npm-publish.yml +26 -0
  2. package/CLAUDE.md +34 -0
  3. package/README.md +62 -0
  4. package/README_CN.md +62 -0
  5. package/bin/cli.ts +2 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +1490 -0
  8. package/dist/index.js.map +1 -0
  9. package/docs/oauth-credential-pre-validation.md +253 -0
  10. package/docs/reference/cli-usage-guide.md +587 -0
  11. package/docs/reference/dida365-open-api-zh.md +999 -0
  12. package/docs/reference/dida365-open-api.md +999 -0
  13. package/docs/reference/project-guide.md +63 -0
  14. package/docs/superpowers/plans/2026-04-03-tt-cli-auth.md +1110 -0
  15. package/docs/superpowers/specs/2026-04-03-tt-cli-design.md +142 -0
  16. package/package.json +45 -0
  17. package/skills/tt-cli-guide/SKILL.md +152 -0
  18. package/skills/tt-cli-guide/references/intent-mapping.md +169 -0
  19. package/src/api/client.ts +61 -0
  20. package/src/api/oauth.ts +146 -0
  21. package/src/api/resources.ts +291 -0
  22. package/src/commands/auth.ts +218 -0
  23. package/src/commands/project.ts +303 -0
  24. package/src/commands/task.ts +806 -0
  25. package/src/commands/user.ts +43 -0
  26. package/src/index.ts +46 -0
  27. package/src/types.ts +211 -0
  28. package/src/utils/config.ts +88 -0
  29. package/src/utils/endpoints.ts +22 -0
  30. package/src/utils/format.ts +71 -0
  31. package/src/utils/server.ts +81 -0
  32. package/tests/config.test.ts +87 -0
  33. package/tests/format.test.ts +56 -0
  34. package/tests/oauth.test.ts +42 -0
  35. package/tests/parity-fields.test.ts +89 -0
  36. package/tests/parity-map.ts +184 -0
  37. package/tests/parity.test.ts +101 -0
  38. package/tsconfig.json +22 -0
  39. package/tsup.config.ts +12 -0
  40. package/vitest.config.ts +7 -0
package/dist/index.js ADDED
@@ -0,0 +1,1490 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { cac } from "cac";
5
+
6
+ // src/commands/auth.ts
7
+ import * as p from "@clack/prompts";
8
+ import pc from "picocolors";
9
+
10
+ // src/utils/config.ts
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import os from "os";
14
+ function getConfigDir() {
15
+ return process.env.TT_CLI_CONFIG_DIR ?? path.join(os.homedir(), ".tt-cli");
16
+ }
17
+ var CONFIG_FILE = "config.json";
18
+ function getConfigPath() {
19
+ return path.join(getConfigDir(), CONFIG_FILE);
20
+ }
21
+ function ensureConfigDir() {
22
+ const dir = getConfigDir();
23
+ if (!fs.existsSync(dir)) {
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ }
26
+ }
27
+ function readConfig() {
28
+ const configPath = getConfigPath();
29
+ if (!fs.existsSync(configPath)) return {};
30
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
31
+ }
32
+ function writeConfig(config) {
33
+ ensureConfigDir();
34
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2));
35
+ }
36
+ function getRegion() {
37
+ return readConfig().region ?? "cn";
38
+ }
39
+ function setRegion(region) {
40
+ const config = readConfig();
41
+ config.region = region;
42
+ writeConfig(config);
43
+ }
44
+ function getOAuth() {
45
+ return readConfig().oauth;
46
+ }
47
+ function setOAuth(oauth) {
48
+ const config = readConfig();
49
+ config.oauth = { ...oauth, region: getRegion() };
50
+ writeConfig(config);
51
+ }
52
+ function getToken() {
53
+ return readConfig().token;
54
+ }
55
+ function setToken(token) {
56
+ const config = readConfig();
57
+ config.token = token;
58
+ writeConfig(config);
59
+ }
60
+ function clearToken() {
61
+ const config = readConfig();
62
+ delete config.token;
63
+ writeConfig(config);
64
+ }
65
+ function isTokenValid() {
66
+ const token = getToken();
67
+ if (!token) return false;
68
+ return Date.now() < token.expiresAt - 5 * 60 * 1e3;
69
+ }
70
+
71
+ // src/utils/endpoints.ts
72
+ var ENDPOINTS = {
73
+ cn: {
74
+ authUrl: "https://dida365.com/oauth/authorize",
75
+ tokenUrl: "https://dida365.com/oauth/token",
76
+ apiBase: "https://api.dida365.com/open/v1/",
77
+ developerUrl: "https://developer.dida365.com/app"
78
+ },
79
+ global: {
80
+ authUrl: "https://ticktick.com/oauth/authorize",
81
+ tokenUrl: "https://ticktick.com/oauth/token",
82
+ apiBase: "https://api.ticktick.com/open/v1/",
83
+ developerUrl: "https://developer.ticktick.com/app"
84
+ }
85
+ };
86
+ function getEndpoints(region) {
87
+ return ENDPOINTS[region];
88
+ }
89
+
90
+ // src/api/oauth.ts
91
+ import crypto from "crypto";
92
+ import open from "open";
93
+
94
+ // src/utils/server.ts
95
+ import http from "http";
96
+ function createCallbackServer(expectedState, port) {
97
+ return new Promise((resolve, reject) => {
98
+ let settled = false;
99
+ const server = http.createServer((req, res) => {
100
+ const url = new URL(req.url, `http://localhost:${port}`);
101
+ if (url.pathname !== "/callback") {
102
+ res.writeHead(404);
103
+ res.end("Not found");
104
+ return;
105
+ }
106
+ const code = url.searchParams.get("code");
107
+ const state = url.searchParams.get("state");
108
+ if (state !== expectedState) {
109
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
110
+ res.end("<h1>\u6388\u6743\u5931\u8D25\uFF1Astate \u4E0D\u5339\u914D</h1>");
111
+ if (!settled) {
112
+ settled = true;
113
+ reject(new Error("CSRF state \u4E0D\u5339\u914D"));
114
+ }
115
+ server.close();
116
+ return;
117
+ }
118
+ if (!code) {
119
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
120
+ res.end("<h1>\u6388\u6743\u5931\u8D25\uFF1A\u7F3A\u5C11 code \u53C2\u6570</h1>");
121
+ if (!settled) {
122
+ settled = true;
123
+ reject(new Error("\u7F3A\u5C11\u6388\u6743\u7801"));
124
+ }
125
+ server.close();
126
+ return;
127
+ }
128
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
129
+ res.end('<html><body style="display:flex;justify-content:center;align-items:center;height:100vh;font-family:sans-serif"><h1>\u2705 \u6388\u6743\u6210\u529F\uFF01\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u3002</h1></body></html>');
130
+ if (!settled) {
131
+ settled = true;
132
+ resolve({
133
+ code,
134
+ close: () => server.close()
135
+ });
136
+ }
137
+ });
138
+ server.on("error", (err) => {
139
+ if (!settled) {
140
+ settled = true;
141
+ if (err.code === "EADDRINUSE") {
142
+ reject(new Error(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u5173\u95ED\u5360\u7528\u8BE5\u7AEF\u53E3\u7684\u7A0B\u5E8F\u6216\u7B49\u5F85\u91CD\u8BD5`));
143
+ } else {
144
+ reject(err);
145
+ }
146
+ }
147
+ });
148
+ server.listen(port);
149
+ setTimeout(() => {
150
+ if (!settled) {
151
+ settled = true;
152
+ server.close();
153
+ reject(new Error("TIMEOUT"));
154
+ }
155
+ }, 12e4);
156
+ });
157
+ }
158
+
159
+ // src/api/oauth.ts
160
+ var SCOPES = "tasks:read tasks:write";
161
+ var DEFAULT_PORT = 3e3;
162
+ function generateState() {
163
+ return crypto.randomBytes(16).toString("hex");
164
+ }
165
+ function buildAuthUrl(config, state, port, region = "cn") {
166
+ const endpoints = getEndpoints(region);
167
+ const redirectUri = `http://localhost:${port}/callback`;
168
+ const params = new URLSearchParams({
169
+ client_id: config.clientId,
170
+ response_type: "code",
171
+ redirect_uri: redirectUri,
172
+ scope: SCOPES,
173
+ state
174
+ });
175
+ return `${endpoints.authUrl}?${params.toString()}`;
176
+ }
177
+ async function exchangeCode(config, code, port) {
178
+ const region = getRegion();
179
+ const endpoints = getEndpoints(region);
180
+ const redirectUri = `http://localhost:${port}/callback`;
181
+ const credentials = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
182
+ const response = await fetch(endpoints.tokenUrl, {
183
+ method: "POST",
184
+ headers: {
185
+ "Content-Type": "application/x-www-form-urlencoded",
186
+ "Authorization": `Basic ${credentials}`
187
+ },
188
+ body: new URLSearchParams({
189
+ code,
190
+ grant_type: "authorization_code",
191
+ redirect_uri: redirectUri,
192
+ scope: SCOPES
193
+ }).toString()
194
+ });
195
+ if (!response.ok) {
196
+ const text4 = await response.text();
197
+ throw new Error(`Token \u4EA4\u6362\u5931\u8D25: ${response.status} ${text4}`);
198
+ }
199
+ const data = await response.json();
200
+ return {
201
+ accessToken: data.access_token,
202
+ refreshToken: data.refresh_token,
203
+ expiresAt: Date.now() + data.expires_in * 1e3
204
+ };
205
+ }
206
+ async function refreshAccessToken() {
207
+ const oauth = getOAuth();
208
+ const token = getToken();
209
+ const region = getRegion();
210
+ const endpoints = getEndpoints(region);
211
+ if (!oauth || !token) {
212
+ throw new Error("\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C tt login");
213
+ }
214
+ const credentials = Buffer.from(`${oauth.clientId}:${oauth.clientSecret}`).toString("base64");
215
+ const response = await fetch(endpoints.tokenUrl, {
216
+ method: "POST",
217
+ headers: {
218
+ "Content-Type": "application/x-www-form-urlencoded",
219
+ "Authorization": `Basic ${credentials}`
220
+ },
221
+ body: new URLSearchParams({
222
+ grant_type: "refresh_token",
223
+ refresh_token: token.refreshToken
224
+ }).toString()
225
+ });
226
+ if (!response.ok) {
227
+ const text4 = await response.text();
228
+ throw new Error(`Token \u5237\u65B0\u5931\u8D25: ${response.status} ${text4}`);
229
+ }
230
+ const data = await response.json();
231
+ const newToken = {
232
+ accessToken: data.access_token,
233
+ refreshToken: data.refresh_token,
234
+ expiresAt: Date.now() + data.expires_in * 1e3
235
+ };
236
+ setToken(newToken);
237
+ return newToken;
238
+ }
239
+ async function loginWithBrowser(config, port = DEFAULT_PORT) {
240
+ const region = getRegion();
241
+ const state = generateState();
242
+ const authUrl = buildAuthUrl(config, state, port, region);
243
+ const codePromise = createCallbackServer(state, port);
244
+ await open(authUrl);
245
+ let callbackResult;
246
+ try {
247
+ callbackResult = await codePromise;
248
+ } catch (err) {
249
+ if (err.message === "TIMEOUT") {
250
+ throw new Error(
251
+ `\u767B\u5F55\u8D85\u65F6\uFF082 \u5206\u949F\u672A\u6536\u5230\u6388\u6743\u56DE\u8C03\uFF09\u3002
252
+ \u53EF\u80FD\u539F\u56E0\uFF1A
253
+ 1. \u6D4F\u89C8\u5668\u6388\u6743\u9875\u9762\u663E\u793A\u4E86\u9519\u8BEF\uFF08\u5982 invalid_client\uFF09
254
+ 2. \u51ED\u8BC1\u4E0E\u5F53\u524D\u533A\u57DF\u4E0D\u5339\u914D
255
+ \u5F53\u524D\u533A\u57DF\uFF1A${region === "cn" ? "\u56FD\u5185\u7248\uFF08dida365.com\uFF09" : "\u56FD\u9645\u7248\uFF08ticktick.com\uFF09"}
256
+ \u5EFA\u8BAE\uFF1A\u68C0\u67E5\u6D4F\u89C8\u5668\u9875\u9762\u9519\u8BEF\uFF0C\u6216\u5C1D\u8BD5\u5207\u6362\u533A\u57DF/\u91CD\u65B0\u914D\u7F6E\u51ED\u8BC1`
257
+ );
258
+ }
259
+ throw err;
260
+ }
261
+ const token = await exchangeCode(config, callbackResult.code, port);
262
+ setToken(token);
263
+ callbackResult.close();
264
+ return token;
265
+ }
266
+
267
+ // src/api/client.ts
268
+ async function getValidToken() {
269
+ const oauth = getOAuth();
270
+ const token = getToken();
271
+ if (!oauth || !token) {
272
+ throw new Error("\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C tt login");
273
+ }
274
+ if (isTokenValid()) {
275
+ return token.accessToken;
276
+ }
277
+ const newToken = await refreshAccessToken();
278
+ return newToken.accessToken;
279
+ }
280
+ async function apiRequest(path2, options) {
281
+ const token = await getValidToken();
282
+ const endpoints = getEndpoints(getRegion());
283
+ const response = await fetch(`${endpoints.apiBase}${path2}`, {
284
+ ...options,
285
+ headers: {
286
+ "Authorization": `Bearer ${token}`,
287
+ "Content-Type": "application/json",
288
+ ...options?.headers
289
+ }
290
+ });
291
+ if (!response.ok) {
292
+ const text5 = await response.text();
293
+ throw new Error(`API \u8BF7\u6C42\u5931\u8D25: ${response.status} ${text5}`);
294
+ }
295
+ if (response.status === 204) {
296
+ return void 0;
297
+ }
298
+ const text4 = await response.text();
299
+ if (!text4 || text4.trim() === "") {
300
+ return void 0;
301
+ }
302
+ try {
303
+ return JSON.parse(text4);
304
+ } catch {
305
+ throw new Error(
306
+ `API \u54CD\u5E94 JSON \u89E3\u6790\u5931\u8D25 (HTTP ${response.status})
307
+ \u8DEF\u5F84: ${path2}
308
+ \u539F\u59CB\u54CD\u5E94: ${text4.substring(0, 500)}
309
+ \u5EFA\u8BAE: \u8BF7\u68C0\u67E5 API \u7AEF\u70B9\u662F\u5426\u6B63\u786E\uFF0C\u6216\u5C1D\u8BD5 tt task-search \u9A8C\u8BC1\u6570\u636E`
310
+ );
311
+ }
312
+ }
313
+
314
+ // src/commands/auth.ts
315
+ var REGION_LABELS = {
316
+ cn: "\u56FD\u5185\u7248\uFF08\u6EF4\u7B54\u6E05\u5355\uFF09",
317
+ global: "\u56FD\u9645\u7248\uFF08TickTick\uFF09"
318
+ };
319
+ async function promptOAuthCredentials(developerUrl) {
320
+ p.log.info(`\u8BF7\u8BBF\u95EE ${developerUrl} \u83B7\u53D6\u51ED\u8BC1`);
321
+ p.log.info("Redirect URI \u8BBE\u7F6E\u4E3A: http://localhost:3000/callback\n");
322
+ const clientId = await p.text({
323
+ message: "\u8BF7\u8F93\u5165 Client ID",
324
+ validate: (v) => !v ? "Client ID \u4E0D\u80FD\u4E3A\u7A7A" : void 0
325
+ });
326
+ if (p.isCancel(clientId)) return void 0;
327
+ const clientSecret = await p.text({
328
+ message: "\u8BF7\u8F93\u5165 Client Secret",
329
+ validate: (v) => !v ? "Client Secret \u4E0D\u80FD\u4E3A\u7A7A" : void 0
330
+ });
331
+ if (p.isCancel(clientSecret)) return void 0;
332
+ const oauth = { clientId, clientSecret };
333
+ setOAuth(oauth);
334
+ p.log.success("\u51ED\u8BC1\u5DF2\u4FDD\u5B58");
335
+ return oauth;
336
+ }
337
+ async function loginCommand() {
338
+ const region = getRegion();
339
+ const endpoints = getEndpoints(region);
340
+ p.intro(pc.bgCyan(pc.black(` \u6EF4\u7B54\u6E05\u5355 CLI \u767B\u5F55 [${REGION_LABELS[region]}] `)));
341
+ const token = getToken();
342
+ if (token && isTokenValid()) {
343
+ p.outro(pc.green("\u5DF2\u767B\u5F55\uFF0C\u65E0\u9700\u91CD\u590D\u767B\u5F55\u3002\u4F7F\u7528 tt logout \u5148\u767B\u51FA\u3002"));
344
+ return;
345
+ }
346
+ let oauth = getOAuth();
347
+ if (oauth?.region && oauth.region !== region) {
348
+ p.log.warn(`\u5F53\u524D\u533A\u57DF\u4E3A ${REGION_LABELS[region]}\uFF0C\u4F46\u4FDD\u5B58\u7684\u51ED\u8BC1\u5C5E\u4E8E ${REGION_LABELS[oauth.region]}`);
349
+ p.log.warn("\u51ED\u8BC1\u4E0E\u533A\u57DF\u4E0D\u5339\u914D\u4F1A\u5BFC\u81F4\u767B\u5F55\u5931\u8D25\n");
350
+ const reconfigure = await p.confirm({
351
+ message: `\u662F\u5426\u4E3A ${REGION_LABELS[region]} \u91CD\u65B0\u914D\u7F6E\u51ED\u8BC1\uFF1F`
352
+ });
353
+ if (p.isCancel(reconfigure) || !reconfigure) {
354
+ p.outro("\u5DF2\u53D6\u6D88");
355
+ return;
356
+ }
357
+ oauth = void 0;
358
+ }
359
+ if (oauth && !oauth.region) {
360
+ p.log.warn("\u4FDD\u5B58\u7684\u51ED\u8BC1\u672A\u6807\u8BB0\u533A\u57DF\uFF0C\u53EF\u80FD\u4E0E\u5F53\u524D\u533A\u57DF\u4E0D\u5339\u914D");
361
+ const confirmRegion = await p.confirm({
362
+ message: `\u8FD9\u4E9B\u51ED\u8BC1\u662F\u5426\u7528\u4E8E ${REGION_LABELS[region]}\uFF1F`
363
+ });
364
+ if (p.isCancel(confirmRegion)) {
365
+ p.outro("\u5DF2\u53D6\u6D88");
366
+ return;
367
+ }
368
+ if (confirmRegion) {
369
+ setOAuth(oauth);
370
+ p.log.success(`\u5DF2\u5C06\u51ED\u8BC1\u6807\u8BB0\u4E3A ${REGION_LABELS[region]}`);
371
+ } else {
372
+ oauth = void 0;
373
+ }
374
+ }
375
+ if (!oauth) {
376
+ oauth = await promptOAuthCredentials(endpoints.developerUrl);
377
+ if (!oauth) {
378
+ p.outro("\u5DF2\u53D6\u6D88");
379
+ return;
380
+ }
381
+ }
382
+ const s2 = p.spinner();
383
+ s2.start("\u6B63\u5728\u6253\u5F00\u6D4F\u89C8\u5668\u8FDB\u884C\u6388\u6743...");
384
+ try {
385
+ await loginWithBrowser(oauth);
386
+ s2.stop("\u6388\u6743\u5B8C\u6210");
387
+ p.outro(pc.green("\u2714 \u767B\u5F55\u6210\u529F\uFF01"));
388
+ } catch (err) {
389
+ s2.stop("\u767B\u5F55\u5931\u8D25");
390
+ const msg = err.message;
391
+ if (msg.includes("invalid_client") || msg.includes("TIMEOUT")) {
392
+ p.log.error(pc.red("\u767B\u5F55\u5931\u8D25\uFF1A\u51ED\u8BC1\u65E0\u6548\u6216\u4E0E\u533A\u57DF\u4E0D\u5339\u914D"));
393
+ p.log.info(`\u5F53\u524D\u533A\u57DF\uFF1A${REGION_LABELS[region]}`);
394
+ p.log.info(`\u51ED\u8BC1\u6765\u6E90\uFF1A${oauth.region ? REGION_LABELS[oauth.region] : "\u672A\u77E5"}
395
+ `);
396
+ const action = await p.select({
397
+ message: "\u8BF7\u9009\u62E9\u4E0B\u4E00\u6B65\u64CD\u4F5C",
398
+ options: [
399
+ { value: "switch", label: `\u5207\u6362\u5230${region === "cn" ? "\u56FD\u9645\u7248" : "\u56FD\u5185\u7248"}` },
400
+ { value: "reconfig", label: "\u91CD\u65B0\u8F93\u5165\u5F53\u524D\u533A\u57DF\u7684\u51ED\u8BC1" },
401
+ { value: "exit", label: "\u9000\u51FA" }
402
+ ]
403
+ });
404
+ if (p.isCancel(action) || action === "exit") {
405
+ p.outro("\u5DF2\u9000\u51FA");
406
+ return;
407
+ }
408
+ if (action === "switch") {
409
+ const newRegion = region === "cn" ? "global" : "cn";
410
+ setRegion(newRegion);
411
+ p.log.success(`\u5DF2\u5207\u6362\u5230 ${REGION_LABELS[newRegion]}\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C tt login`);
412
+ p.outro("\u533A\u57DF\u5DF2\u5207\u6362");
413
+ return;
414
+ }
415
+ if (action === "reconfig") {
416
+ await promptOAuthCredentials(endpoints.developerUrl);
417
+ p.outro("\u51ED\u8BC1\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C tt login");
418
+ return;
419
+ }
420
+ }
421
+ p.outro(pc.red(`\u2716 ${msg}`));
422
+ process.exit(1);
423
+ }
424
+ }
425
+ async function logoutCommand() {
426
+ p.intro(pc.bgCyan(pc.black(" \u6EF4\u7B54\u6E05\u5355 CLI \u767B\u51FA ")));
427
+ clearToken();
428
+ p.outro(pc.green("\u2714 \u5DF2\u767B\u51FA"));
429
+ }
430
+ async function whoamiCommand() {
431
+ const region = getRegion();
432
+ p.intro(pc.bgCyan(pc.black(" \u6EF4\u7B54\u6E05\u5355 CLI \u72B6\u6001 ")));
433
+ const token = getToken();
434
+ if (!token) {
435
+ p.outro(pc.yellow("\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C tt login"));
436
+ return;
437
+ }
438
+ const s2 = p.spinner();
439
+ s2.start("\u6B63\u5728\u9A8C\u8BC1\u767B\u5F55\u72B6\u6001...");
440
+ try {
441
+ await apiRequest("project");
442
+ s2.stop("\u9A8C\u8BC1\u5B8C\u6210");
443
+ const expiresIn = token.expiresAt - Date.now();
444
+ if (expiresIn <= 0) {
445
+ p.outro(pc.yellow("Token \u5DF2\u5931\u6548\uFF0C\u8BF7\u8FD0\u884C tt login \u91CD\u65B0\u767B\u5F55"));
446
+ return;
447
+ }
448
+ const hours = Math.floor(expiresIn / 36e5);
449
+ const minutes = Math.floor(expiresIn % 36e5 / 6e4);
450
+ p.log.success(pc.green(`\u5DF2\u767B\u5F55 [${REGION_LABELS[region]}]`));
451
+ p.log.info(`Token \u6709\u6548\u671F: \u5269\u4F59 ${hours} \u5C0F\u65F6 ${minutes} \u5206\u949F`);
452
+ p.outro("\u4E00\u5207\u6B63\u5E38");
453
+ } catch {
454
+ s2.stop("\u9A8C\u8BC1\u5931\u8D25");
455
+ p.outro(pc.red("Token \u5DF2\u5931\u6548\uFF0C\u8BF7\u8FD0\u884C tt login \u91CD\u65B0\u767B\u5F55"));
456
+ }
457
+ }
458
+ async function configCommand(args) {
459
+ if (args?.region) {
460
+ const oldRegion = getRegion();
461
+ setRegion(args.region);
462
+ clearToken();
463
+ p.intro(pc.bgCyan(pc.black(" \u6EF4\u7B54\u6E05\u5355 CLI \u914D\u7F6E ")));
464
+ p.log.success(`\u5DF2\u5207\u6362\u5230 ${REGION_LABELS[args.region]}`);
465
+ if (oldRegion !== args.region) {
466
+ p.log.warn("\u533A\u57DF\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C tt login");
467
+ }
468
+ p.outro("\u914D\u7F6E\u5DF2\u66F4\u65B0");
469
+ return;
470
+ }
471
+ const region = getRegion();
472
+ p.intro(pc.bgCyan(pc.black(" \u6EF4\u7B54\u6E05\u5355 CLI \u914D\u7F6E ")));
473
+ p.log.info(`\u533A\u57DF: ${REGION_LABELS[region]}`);
474
+ const oauth = getOAuth();
475
+ if (oauth) {
476
+ p.log.info(`Client ID: ${oauth.clientId}`);
477
+ p.log.info(`Client Secret: ${oauth.clientSecret.substring(0, 8)}${"*".repeat(Math.max(0, oauth.clientSecret.length - 8))}`);
478
+ } else {
479
+ p.log.warn("\u5C1A\u672A\u914D\u7F6E OAuth \u51ED\u8BC1");
480
+ }
481
+ const token = getToken();
482
+ if (token) {
483
+ p.log.info(`Token: ${token.accessToken.substring(0, 8)}...`);
484
+ p.log.info(`\u8FC7\u671F\u65F6\u95F4: ${new Date(token.expiresAt).toLocaleString("zh-CN")}`);
485
+ } else {
486
+ p.log.info("Token: \u672A\u767B\u5F55");
487
+ }
488
+ p.log.info("\n\u4F7F\u7528 tt config --region cn/global \u5207\u6362\u533A\u57DF");
489
+ p.outro("\u914D\u7F6E\u4FE1\u606F\u5982\u4E0A");
490
+ }
491
+
492
+ // src/commands/project.ts
493
+ import * as p2 from "@clack/prompts";
494
+ import pc2 from "picocolors";
495
+
496
+ // src/utils/format.ts
497
+ function normalizeTickTickDate(dateStr) {
498
+ let result = dateStr;
499
+ if (!/T\d{2}:\d{2}:\d{2}\.\d{3}/.test(result)) {
500
+ result = result.replace(/(T\d{2}:\d{2}:\d{2})([+\-Z])/, "$1.000$2");
501
+ }
502
+ result = result.replace(/([+\-])(\d{2}):(\d{2})$/, "$1$2$3");
503
+ if (result.endsWith("Z")) {
504
+ result = result.slice(0, -1) + "+0000";
505
+ }
506
+ return result;
507
+ }
508
+ function extractHM(dateStr) {
509
+ try {
510
+ const d = new Date(dateStr);
511
+ if (isNaN(d.getTime())) return null;
512
+ const hours = d.getHours().toString().padStart(2, "0");
513
+ const minutes = d.getMinutes().toString().padStart(2, "0");
514
+ return `${hours}:${minutes}`;
515
+ } catch {
516
+ const match = dateStr.match(/T(\d{2}):(\d{2})/);
517
+ if (!match) return null;
518
+ return `${match[1]}:${match[2]}`;
519
+ }
520
+ }
521
+ function formatTaskTime(task) {
522
+ if (task.isAllDay && task.startDate) return "\u5168\u5929";
523
+ if (!task.startDate) return "";
524
+ const start = extractHM(task.startDate);
525
+ if (!start) return "";
526
+ if (task.dueDate) {
527
+ const end = extractHM(task.dueDate);
528
+ if (end && end !== start) return `${start}-${end}`;
529
+ }
530
+ return start;
531
+ }
532
+
533
+ // src/api/resources.ts
534
+ function getProjects() {
535
+ return apiRequest("project");
536
+ }
537
+ function getProject(projectId) {
538
+ return apiRequest(`project/${projectId}`);
539
+ }
540
+ function getProjectData(projectId) {
541
+ return apiRequest(`project/${projectId}/data`);
542
+ }
543
+ function createProject(data) {
544
+ return apiRequest("project", {
545
+ method: "POST",
546
+ body: JSON.stringify(data)
547
+ });
548
+ }
549
+ function updateProject(projectId, data) {
550
+ return apiRequest(`project/${projectId}`, {
551
+ method: "POST",
552
+ body: JSON.stringify(data)
553
+ });
554
+ }
555
+ function deleteProject(projectId) {
556
+ return apiRequest(`project/${projectId}`, { method: "DELETE" });
557
+ }
558
+ function getTask(projectId, taskId) {
559
+ return apiRequest(`project/${projectId}/task/${taskId}`);
560
+ }
561
+ function createTask(data) {
562
+ return apiRequest("task", {
563
+ method: "POST",
564
+ body: JSON.stringify(data)
565
+ });
566
+ }
567
+ function updateTask(taskId, data) {
568
+ return apiRequest(`task/${taskId}`, {
569
+ method: "POST",
570
+ body: JSON.stringify(data)
571
+ });
572
+ }
573
+ function completeTask(projectId, taskId) {
574
+ return apiRequest(`project/${projectId}/task/${taskId}/complete`, {
575
+ method: "POST"
576
+ });
577
+ }
578
+ function deleteTask(projectId, taskId) {
579
+ return apiRequest(`project/${projectId}/task/${taskId}`, {
580
+ method: "DELETE"
581
+ });
582
+ }
583
+ function moveTasks(moves) {
584
+ return apiRequest("task/move", {
585
+ method: "POST",
586
+ body: JSON.stringify(moves)
587
+ });
588
+ }
589
+ function getCompletedTasks(params) {
590
+ return apiRequest("task/completed", {
591
+ method: "POST",
592
+ body: JSON.stringify(params)
593
+ });
594
+ }
595
+ function filterTasks(params) {
596
+ return apiRequest("task/filter", {
597
+ method: "POST",
598
+ body: JSON.stringify(params)
599
+ });
600
+ }
601
+ async function batchAddTasks(tasks) {
602
+ const res = await apiRequest("task/batch", {
603
+ method: "POST",
604
+ body: JSON.stringify({ add: tasks })
605
+ });
606
+ return {
607
+ count: tasks.length,
608
+ id2etag: res?.id2etag ?? res
609
+ };
610
+ }
611
+ function batchUpdateTasks(tasks) {
612
+ return apiRequest("task/batch", {
613
+ method: "POST",
614
+ body: JSON.stringify({ update: tasks })
615
+ });
616
+ }
617
+ async function completeTasksInProject(projectId, taskIds) {
618
+ const completed = [];
619
+ const failed = [];
620
+ for (const taskId of taskIds) {
621
+ try {
622
+ await completeTask(projectId, taskId);
623
+ completed.push(taskId);
624
+ } catch {
625
+ failed.push(taskId);
626
+ }
627
+ }
628
+ return { completed, failed };
629
+ }
630
+ async function getTaskById(taskId) {
631
+ const [undoneTasks, completedTasks] = await Promise.all([
632
+ listUndoneTasksByDate({}),
633
+ getCompletedTasks({})
634
+ ]);
635
+ const allTasks = [...undoneTasks, ...completedTasks];
636
+ const task = allTasks.find((t) => t.id === taskId);
637
+ if (!task) throw new Error(`\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728`);
638
+ return task;
639
+ }
640
+ function getUserPreference() {
641
+ return apiRequest("user/info");
642
+ }
643
+ function listUndoneTasksByDate(params) {
644
+ const filterParams = { status: [0] };
645
+ if (params.projectIds) filterParams.projectIds = params.projectIds;
646
+ if (params.startDate) filterParams.startDate = params.startDate;
647
+ if (params.endDate) filterParams.endDate = params.endDate;
648
+ return filterTasks(filterParams);
649
+ }
650
+ function listUndoneTasksByTimeQuery(query) {
651
+ const now = /* @__PURE__ */ new Date();
652
+ let startDate;
653
+ let endDate;
654
+ const toISO = (d) => d.toISOString();
655
+ switch (query) {
656
+ case "today": {
657
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
658
+ const end = new Date(start);
659
+ end.setDate(end.getDate() + 1);
660
+ startDate = toISO(start);
661
+ endDate = toISO(end);
662
+ break;
663
+ }
664
+ case "tomorrow": {
665
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
666
+ const end = new Date(start);
667
+ end.setDate(end.getDate() + 1);
668
+ startDate = toISO(start);
669
+ endDate = toISO(end);
670
+ break;
671
+ }
672
+ case "last24hour": {
673
+ const start = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
674
+ startDate = toISO(start);
675
+ endDate = toISO(now);
676
+ break;
677
+ }
678
+ case "next24hour": {
679
+ const end = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
680
+ startDate = toISO(now);
681
+ endDate = toISO(end);
682
+ break;
683
+ }
684
+ case "last7day": {
685
+ const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
686
+ startDate = toISO(start);
687
+ endDate = toISO(now);
688
+ break;
689
+ }
690
+ case "next7day": {
691
+ const end = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1e3);
692
+ startDate = toISO(now);
693
+ endDate = toISO(end);
694
+ break;
695
+ }
696
+ default:
697
+ throw new Error(
698
+ `\u4E0D\u652F\u6301\u7684\u67E5\u8BE2\u9884\u8BBE: ${query}\uFF0C\u652F\u6301: today, tomorrow, last24hour, next24hour, last7day, next7day`
699
+ );
700
+ }
701
+ return listUndoneTasksByDate({ startDate, endDate });
702
+ }
703
+ async function searchTask(keyword) {
704
+ const [undoneTasks, completedTasks] = await Promise.all([
705
+ listUndoneTasksByDate({}),
706
+ getCompletedTasks({})
707
+ ]);
708
+ const taskMap = /* @__PURE__ */ new Map();
709
+ for (const t of [...undoneTasks, ...completedTasks]) {
710
+ taskMap.set(t.id, t);
711
+ }
712
+ const allTasks = Array.from(taskMap.values());
713
+ const lower = keyword.toLowerCase();
714
+ return allTasks.filter(
715
+ (t) => t.title.toLowerCase().includes(lower) || t.content && t.content.toLowerCase().includes(lower)
716
+ );
717
+ }
718
+
719
+ // src/commands/project.ts
720
+ function priorityText(priority) {
721
+ const v = priority ?? 0;
722
+ switch (v) {
723
+ case 5:
724
+ return pc2.red("\u9AD8");
725
+ case 3:
726
+ return pc2.yellow("\u4E2D");
727
+ case 1:
728
+ return pc2.blue("\u4F4E");
729
+ default:
730
+ return pc2.dim("\u65E0");
731
+ }
732
+ }
733
+ function statusIcon(status) {
734
+ return status === 2 ? pc2.green("\u2713") : "\u25CB";
735
+ }
736
+ function displayTaskList(tasks) {
737
+ if (tasks.length === 0) {
738
+ p2.log.info(pc2.dim(" \uFF08\u65E0\u4EFB\u52A1\uFF09"));
739
+ return;
740
+ }
741
+ for (const task of tasks) {
742
+ const icon = statusIcon(task.status);
743
+ const title = task.title.length > 30 ? task.title.slice(0, 30) + "\u2026" : task.title;
744
+ const time = formatTaskTime(task);
745
+ const timeStr = time ? pc2.cyan(time) : "";
746
+ console.log(
747
+ ` ${icon} ${title} ${timeStr} ${priorityText(task.priority)}`
748
+ );
749
+ if (task.content) {
750
+ const content = task.content.length > 50 ? task.content.slice(0, 50) + "\u2026" : task.content;
751
+ console.log(` ${pc2.dim(content)}`);
752
+ }
753
+ }
754
+ }
755
+ async function projectListCommand() {
756
+ const s2 = p2.spinner();
757
+ s2.start("\u6B63\u5728\u83B7\u53D6\u9879\u76EE\u5217\u8868...");
758
+ try {
759
+ const projects = await getProjects();
760
+ s2.stop(`\u627E\u5230 ${projects.length} \u4E2A\u9879\u76EE`);
761
+ if (projects.length === 0) {
762
+ p2.outro(pc2.yellow("\u6CA1\u6709\u627E\u5230\u4EFB\u4F55\u9879\u76EE"));
763
+ return;
764
+ }
765
+ console.log("");
766
+ for (const project of projects) {
767
+ const status = project.closed ? pc2.dim("\u5DF2\u5173\u95ED") : pc2.green("\u6D3B\u8DC3");
768
+ console.log(
769
+ ` ${pc2.bold(project.name)} ${pc2.dim(project.id)}`
770
+ );
771
+ console.log(
772
+ ` \u7C7B\u578B: ${project.kind || "-"} \u89C6\u56FE: ${project.viewMode || "-"} ${status}`
773
+ );
774
+ }
775
+ console.log("");
776
+ p2.outro(`\u5171 ${projects.length} \u4E2A\u9879\u76EE`);
777
+ } catch (err) {
778
+ s2.stop("\u83B7\u53D6\u5931\u8D25");
779
+ p2.outro(pc2.red(err.message));
780
+ }
781
+ }
782
+ async function projectGetCommand(id) {
783
+ const s2 = p2.spinner();
784
+ s2.start("\u6B63\u5728\u83B7\u53D6\u9879\u76EE\u8BE6\u60C5...");
785
+ try {
786
+ const project = await getProject(id);
787
+ s2.stop("\u83B7\u53D6\u6210\u529F");
788
+ console.log("");
789
+ console.log(` ${pc2.bold("\u540D\u79F0")}: ${project.name}`);
790
+ console.log(` ${pc2.bold("ID")}: ${project.id}`);
791
+ console.log(` ${pc2.bold("\u989C\u8272")}: ${project.color || "-"}`);
792
+ console.log(` ${pc2.bold("\u7C7B\u578B")}: ${project.kind || "-"}`);
793
+ console.log(` ${pc2.bold("\u89C6\u56FE")}: ${project.viewMode || "-"}`);
794
+ console.log(
795
+ ` ${pc2.bold("\u72B6\u6001")}: ${project.closed ? "\u5DF2\u5173\u95ED" : "\u6D3B\u8DC3"}`
796
+ );
797
+ if (project.groupId)
798
+ console.log(` ${pc2.bold("\u5206\u7EC4")}: ${project.groupId}`);
799
+ if (project.permission)
800
+ console.log(` ${pc2.bold("\u6743\u9650")}: ${project.permission}`);
801
+ console.log("");
802
+ } catch (err) {
803
+ s2.stop("\u83B7\u53D6\u5931\u8D25");
804
+ p2.outro(pc2.red(err.message));
805
+ }
806
+ }
807
+ async function projectTasksCommand(id, options) {
808
+ try {
809
+ const data = await getProjectData(id);
810
+ if (options.json) {
811
+ console.log(JSON.stringify(data.tasks, null, 2));
812
+ return;
813
+ }
814
+ const s2 = p2.spinner();
815
+ s2.start("\u6B63\u5728\u83B7\u53D6\u9879\u76EE\u4EFB\u52A1...");
816
+ s2.stop(
817
+ `\u9879\u76EE\u300C${data.project.name}\u300D\u4E0B\u6709 ${data.tasks.length} \u4E2A\u4EFB\u52A1`
818
+ );
819
+ if (data.tasks.length === 0) {
820
+ p2.outro(pc2.yellow("\u8BE5\u9879\u76EE\u4E0B\u6CA1\u6709\u4EFB\u52A1"));
821
+ return;
822
+ }
823
+ console.log("");
824
+ displayTaskList(data.tasks);
825
+ console.log("");
826
+ p2.outro(`\u5171 ${data.tasks.length} \u4E2A\u4EFB\u52A1`);
827
+ } catch (err) {
828
+ s.stop("\u83B7\u53D6\u5931\u8D25");
829
+ p2.outro(pc2.red(err.message));
830
+ }
831
+ }
832
+ async function projectCreateCommand(name, options) {
833
+ if (!name) {
834
+ const input = await p2.text({ message: "\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0" });
835
+ if (p2.isCancel(input)) {
836
+ p2.outro("\u5DF2\u53D6\u6D88");
837
+ return;
838
+ }
839
+ name = input;
840
+ }
841
+ const params = { name };
842
+ if (options.color) params.color = options.color;
843
+ if (options.viewMode)
844
+ params.viewMode = options.viewMode;
845
+ if (options.kind)
846
+ params.kind = options.kind;
847
+ const s2 = p2.spinner();
848
+ s2.start("\u6B63\u5728\u521B\u5EFA\u9879\u76EE...");
849
+ try {
850
+ const project = await createProject(params);
851
+ s2.stop("\u521B\u5EFA\u6210\u529F");
852
+ p2.outro(
853
+ pc2.green(`\u9879\u76EE\u300C${project.name}\u300D\u5DF2\u521B\u5EFA (ID: ${project.id})`)
854
+ );
855
+ } catch (err) {
856
+ s2.stop("\u521B\u5EFA\u5931\u8D25");
857
+ p2.outro(pc2.red(err.message));
858
+ }
859
+ }
860
+ async function projectUpdateCommand(id, options) {
861
+ const params = {};
862
+ if (options.name) params.name = options.name;
863
+ if (options.color) params.color = options.color;
864
+ if (options.viewMode)
865
+ params.viewMode = options.viewMode;
866
+ if (options.kind)
867
+ params.kind = options.kind;
868
+ if (Object.keys(params).length === 0) {
869
+ p2.outro(
870
+ pc2.yellow(
871
+ "\u672A\u6307\u5B9A\u4EFB\u4F55\u66F4\u65B0\u5185\u5BB9\uFF0C\u4F7F\u7528 --name / --color / --view-mode / --kind \u9009\u9879"
872
+ )
873
+ );
874
+ return;
875
+ }
876
+ const s2 = p2.spinner();
877
+ s2.start("\u6B63\u5728\u66F4\u65B0\u9879\u76EE...");
878
+ try {
879
+ const project = await updateProject(id, params);
880
+ s2.stop("\u66F4\u65B0\u6210\u529F");
881
+ p2.outro(pc2.green(`\u9879\u76EE\u300C${project.name}\u300D\u5DF2\u66F4\u65B0`));
882
+ } catch (err) {
883
+ s2.stop("\u66F4\u65B0\u5931\u8D25");
884
+ p2.outro(pc2.red(err.message));
885
+ }
886
+ }
887
+ async function projectDeleteCommand(id) {
888
+ const confirmed = await p2.confirm({
889
+ message: `\u786E\u8BA4\u5220\u9664\u9879\u76EE ${pc2.red(id)}\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500`
890
+ });
891
+ if (p2.isCancel(confirmed) || !confirmed) {
892
+ p2.outro("\u5DF2\u53D6\u6D88");
893
+ return;
894
+ }
895
+ const s2 = p2.spinner();
896
+ s2.start("\u6B63\u5728\u5220\u9664\u9879\u76EE...");
897
+ try {
898
+ await deleteProject(id);
899
+ s2.stop("\u5220\u9664\u6210\u529F");
900
+ p2.outro(pc2.green("\u9879\u76EE\u5DF2\u5220\u9664"));
901
+ } catch (err) {
902
+ s2.stop("\u5220\u9664\u5931\u8D25");
903
+ p2.outro(pc2.red(err.message));
904
+ }
905
+ }
906
+ function registerProjectCommands(cli2) {
907
+ cli2.command("project-list", "\u5217\u51FA\u6240\u6709\u9879\u76EE").action(projectListCommand);
908
+ cli2.command("project-get <id>", "\u83B7\u53D6\u9879\u76EE\u8BE6\u60C5").action(projectGetCommand);
909
+ cli2.command("project-tasks <id>", "\u83B7\u53D6\u9879\u76EE\u4E0B\u7684\u4EFB\u52A1").option("--json", "\u8F93\u51FA JSON \u683C\u5F0F").action(projectTasksCommand);
910
+ cli2.command("project-create [name]", "\u521B\u5EFA\u9879\u76EE").option("--color <color>", "\u9879\u76EE\u989C\u8272\uFF0C\u5982 #F18181").option("--view-mode <mode>", "\u89C6\u56FE\u6A21\u5F0F: list / kanban / timeline").option("--kind <kind>", "\u9879\u76EE\u7C7B\u578B: TASK / NOTE").action(projectCreateCommand);
911
+ cli2.command("project-update <id>", "\u66F4\u65B0\u9879\u76EE").option("--name <name>", "\u9879\u76EE\u540D\u79F0").option("--color <color>", "\u9879\u76EE\u989C\u8272").option("--view-mode <mode>", "\u89C6\u56FE\u6A21\u5F0F").option("--kind <kind>", "\u9879\u76EE\u7C7B\u578B").action(projectUpdateCommand);
912
+ cli2.command("project-delete <id>", "\u5220\u9664\u9879\u76EE").action(projectDeleteCommand);
913
+ }
914
+
915
+ // src/commands/task.ts
916
+ import * as p3 from "@clack/prompts";
917
+ import pc3 from "picocolors";
918
+ function priorityText2(priority) {
919
+ const v = priority ?? 0;
920
+ switch (v) {
921
+ case 5:
922
+ return pc3.red("\u9AD8");
923
+ case 3:
924
+ return pc3.yellow("\u4E2D");
925
+ case 1:
926
+ return pc3.blue("\u4F4E");
927
+ default:
928
+ return pc3.dim("\u65E0");
929
+ }
930
+ }
931
+ function statusIcon2(status) {
932
+ return status === 2 ? pc3.green("\u2713") : "\u25CB";
933
+ }
934
+ function displayTaskDetail(task) {
935
+ console.log("");
936
+ console.log(` ${pc3.bold(task.title)}`);
937
+ console.log(` ${pc3.dim("\u2500".repeat(40))}`);
938
+ console.log(` ID: ${task.id}`);
939
+ console.log(` \u9879\u76EE: ${task.projectId}`);
940
+ console.log(` \u4F18\u5148\u7EA7: ${priorityText2(task.priority)}`);
941
+ console.log(` \u72B6\u6001: ${task.status === 2 ? pc3.green("\u5DF2\u5B8C\u6210") : "\u5F85\u529E"}`);
942
+ if (task.isAllDay !== void 0)
943
+ console.log(` \u5168\u5929: ${task.isAllDay ? "\u662F" : "\u5426"}`);
944
+ if (task.startDate)
945
+ console.log(` \u5F00\u59CB: ${task.startDate}`);
946
+ if (task.dueDate)
947
+ console.log(` \u622A\u6B62: ${task.dueDate}`);
948
+ if (task.timeZone)
949
+ console.log(` \u65F6\u533A: ${task.timeZone}`);
950
+ if (task.kind)
951
+ console.log(` \u7C7B\u578B: ${task.kind}`);
952
+ if (task.content) {
953
+ console.log(` \u5185\u5BB9:`);
954
+ console.log(` ${task.content}`);
955
+ }
956
+ if (task.tags && task.tags.length > 0) {
957
+ console.log(` \u6807\u7B7E: ${task.tags.join(", ")}`);
958
+ }
959
+ if (task.items && task.items.length > 0) {
960
+ console.log(` \u5B50\u4EFB\u52A1:`);
961
+ for (const item of task.items) {
962
+ const icon = item.status === 1 ? pc3.green("\u2713") : "\u25CB";
963
+ console.log(` ${icon} ${item.title}`);
964
+ }
965
+ }
966
+ console.log("");
967
+ }
968
+ function displayTaskTable(tasks) {
969
+ if (tasks.length === 0) {
970
+ p3.log.info(pc3.dim(" \uFF08\u65E0\u4EFB\u52A1\uFF09"));
971
+ return;
972
+ }
973
+ for (const task of tasks) {
974
+ const icon = statusIcon2(task.status);
975
+ const title = task.title.length > 30 ? task.title.slice(0, 30) + "\u2026" : task.title;
976
+ const time = formatTaskTime(task);
977
+ const timeStr = time ? pc3.cyan(time) : "";
978
+ console.log(
979
+ ` ${icon} ${title} ${timeStr} ${pc3.dim(task.id)} ${priorityText2(task.priority)} ${pc3.dim(task.projectId)}`
980
+ );
981
+ }
982
+ }
983
+ async function selectProject() {
984
+ const s2 = p3.spinner();
985
+ s2.start("\u6B63\u5728\u83B7\u53D6\u9879\u76EE\u5217\u8868...");
986
+ const projects = await getProjects();
987
+ s2.stop("");
988
+ if (projects.length === 0) {
989
+ p3.log.error("\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\uFF0C\u8BF7\u5148\u521B\u5EFA\u9879\u76EE");
990
+ return void 0;
991
+ }
992
+ const selected = await p3.select({
993
+ message: "\u9009\u62E9\u9879\u76EE",
994
+ options: projects.map((proj) => ({
995
+ value: proj.id,
996
+ label: proj.name
997
+ }))
998
+ });
999
+ if (p3.isCancel(selected)) return void 0;
1000
+ return selected;
1001
+ }
1002
+ function parsePriority(value) {
1003
+ if (!value) return void 0;
1004
+ const n = parseInt(value, 10);
1005
+ if ([0, 1, 3, 5].includes(n)) return n;
1006
+ return void 0;
1007
+ }
1008
+ async function taskAddCommand(title, options) {
1009
+ if (!title) {
1010
+ const input = await p3.text({ message: "\u8BF7\u8F93\u5165\u4EFB\u52A1\u6807\u9898" });
1011
+ if (p3.isCancel(input)) {
1012
+ p3.outro("\u5DF2\u53D6\u6D88");
1013
+ return;
1014
+ }
1015
+ title = input;
1016
+ }
1017
+ let projectId = options.project;
1018
+ if (!projectId) {
1019
+ projectId = await selectProject();
1020
+ if (!projectId) {
1021
+ p3.outro("\u5DF2\u53D6\u6D88");
1022
+ return;
1023
+ }
1024
+ }
1025
+ const params = { title, projectId };
1026
+ if (options.content) params.content = options.content;
1027
+ if (options.priority) {
1028
+ const priority = parsePriority(options.priority);
1029
+ if (priority !== void 0) params.priority = priority;
1030
+ }
1031
+ if (options.startDate) params.startDate = normalizeTickTickDate(options.startDate);
1032
+ if (options.dueDate) params.dueDate = normalizeTickTickDate(options.dueDate);
1033
+ if (options.allDay !== void 0) params.isAllDay = options.allDay;
1034
+ const s2 = p3.spinner();
1035
+ s2.start("\u6B63\u5728\u521B\u5EFA\u4EFB\u52A1...");
1036
+ try {
1037
+ const task = await createTask(params);
1038
+ s2.stop("\u521B\u5EFA\u6210\u529F");
1039
+ p3.outro(pc3.green(`\u4EFB\u52A1\u300C${task.title}\u300D\u5DF2\u521B\u5EFA (ID: ${task.id})`));
1040
+ } catch (err) {
1041
+ s2.stop("\u521B\u5EFA\u5931\u8D25");
1042
+ p3.outro(pc3.red(err.message));
1043
+ }
1044
+ }
1045
+ async function taskGetCommand(projectId, taskId) {
1046
+ const s2 = p3.spinner();
1047
+ s2.start("\u6B63\u5728\u83B7\u53D6\u4EFB\u52A1...");
1048
+ try {
1049
+ const task = await getTask(projectId, taskId);
1050
+ s2.stop("\u83B7\u53D6\u6210\u529F");
1051
+ displayTaskDetail(task);
1052
+ p3.outro("\u4EFB\u52A1\u8BE6\u60C5\u5982\u4E0A");
1053
+ } catch (err) {
1054
+ s2.stop("\u83B7\u53D6\u5931\u8D25");
1055
+ const msg = err.message;
1056
+ p3.outro(
1057
+ pc3.red(`\u83B7\u53D6\u4EFB\u52A1\u5931\u8D25 (projectId: ${projectId}, taskId: ${taskId})
1058
+ `) + pc3.red(`${msg}
1059
+ `) + pc3.yellow("\u6392\u67E5\u5EFA\u8BAE: \u8FD0\u884C tt task-find <taskId> \u6216 tt task-search <\u5173\u952E\u8BCD> \u9A8C\u8BC1\u4EFB\u52A1")
1060
+ );
1061
+ }
1062
+ }
1063
+ async function taskDoneCommand(projectId, taskId) {
1064
+ const s2 = p3.spinner();
1065
+ s2.start("\u6B63\u5728\u5B8C\u6210\u4EFB\u52A1...");
1066
+ try {
1067
+ await completeTask(projectId, taskId);
1068
+ s2.stop("\u5B8C\u6210\u6210\u529F");
1069
+ p3.outro(pc3.green("\u4EFB\u52A1\u5DF2\u5B8C\u6210"));
1070
+ } catch (err) {
1071
+ s2.stop("\u64CD\u4F5C\u5931\u8D25");
1072
+ const msg = err.message;
1073
+ p3.outro(
1074
+ pc3.red(`\u4EFB\u52A1\u5B8C\u6210\u5931\u8D25 (projectId: ${projectId}, taskId: ${taskId})
1075
+ `) + pc3.red(`${msg}
1076
+ `) + pc3.yellow("\u6392\u67E5\u5EFA\u8BAE: \u8FD0\u884C tt task-search \u68C0\u67E5\u4EFB\u52A1\u662F\u5426\u5B58\u5728\u6216\u5DF2\u5B8C\u6210")
1077
+ );
1078
+ }
1079
+ }
1080
+ async function taskDeleteCommand(projectId, taskId) {
1081
+ const confirmed = await p3.confirm({
1082
+ message: `\u786E\u8BA4\u5220\u9664\u4EFB\u52A1 ${pc3.red(taskId)}\uFF1F`
1083
+ });
1084
+ if (p3.isCancel(confirmed) || !confirmed) {
1085
+ p3.outro("\u5DF2\u53D6\u6D88");
1086
+ return;
1087
+ }
1088
+ const s2 = p3.spinner();
1089
+ s2.start("\u6B63\u5728\u5220\u9664\u4EFB\u52A1...");
1090
+ try {
1091
+ await deleteTask(projectId, taskId);
1092
+ s2.stop("\u5220\u9664\u6210\u529F");
1093
+ p3.outro(pc3.green("\u4EFB\u52A1\u5DF2\u5220\u9664"));
1094
+ } catch (err) {
1095
+ s2.stop("\u5220\u9664\u5931\u8D25");
1096
+ const msg = err.message;
1097
+ p3.outro(
1098
+ pc3.red(`\u5220\u9664\u4EFB\u52A1\u5931\u8D25 (projectId: ${projectId}, taskId: ${taskId})
1099
+ `) + pc3.red(`${msg}
1100
+ `) + pc3.yellow("\u6392\u67E5\u5EFA\u8BAE: \u8FD0\u884C tt task-get <projectId> <taskId> \u9A8C\u8BC1\u4EFB\u52A1\u662F\u5426\u5B58\u5728")
1101
+ );
1102
+ }
1103
+ }
1104
+ async function taskUpdateCommand(taskId, options) {
1105
+ if (!options.project) {
1106
+ p3.outro(pc3.red("\u8BF7\u4F7F\u7528 -p/--project \u6307\u5B9A\u9879\u76EE ID"));
1107
+ return;
1108
+ }
1109
+ const params = {
1110
+ id: taskId,
1111
+ projectId: options.project
1112
+ };
1113
+ if (options.title) params.title = options.title;
1114
+ if (options.content) params.content = options.content;
1115
+ if (options.priority) {
1116
+ const priority = parsePriority(options.priority);
1117
+ if (priority !== void 0) params.priority = priority;
1118
+ }
1119
+ if (options.startDate) params.startDate = normalizeTickTickDate(options.startDate);
1120
+ if (options.dueDate) params.dueDate = normalizeTickTickDate(options.dueDate);
1121
+ const s2 = p3.spinner();
1122
+ s2.start("\u6B63\u5728\u66F4\u65B0\u4EFB\u52A1...");
1123
+ try {
1124
+ const task = await updateTask(taskId, params);
1125
+ s2.stop("\u66F4\u65B0\u6210\u529F");
1126
+ p3.outro(pc3.green(`\u4EFB\u52A1\u300C${task.title}\u300D\u5DF2\u66F4\u65B0`));
1127
+ } catch (err) {
1128
+ s2.stop("\u66F4\u65B0\u5931\u8D25");
1129
+ const msg = err.message;
1130
+ p3.outro(
1131
+ pc3.red(`\u66F4\u65B0\u4EFB\u52A1\u5931\u8D25 (taskId: ${taskId}, projectId: ${options.project})
1132
+ `) + pc3.red(`${msg}
1133
+ `) + pc3.yellow("\u6392\u67E5\u5EFA\u8BAE: \u8FD0\u884C tt task-find <taskId> \u9A8C\u8BC1\u4EFB\u52A1\u662F\u5426\u5B58\u5728")
1134
+ );
1135
+ }
1136
+ }
1137
+ async function taskMoveCommand(taskId, options) {
1138
+ if (!options.from || !options.to) {
1139
+ p3.outro(
1140
+ pc3.red("\u8BF7\u4F7F\u7528 -f/--from \u548C -t/--to \u6307\u5B9A\u6E90\u548C\u76EE\u6807\u9879\u76EE ID")
1141
+ );
1142
+ return;
1143
+ }
1144
+ const s2 = p3.spinner();
1145
+ s2.start("\u6B63\u5728\u79FB\u52A8\u4EFB\u52A1...");
1146
+ try {
1147
+ const result = await moveTasks([
1148
+ {
1149
+ fromProjectId: options.from,
1150
+ toProjectId: options.to,
1151
+ taskId
1152
+ }
1153
+ ]);
1154
+ s2.stop("\u79FB\u52A8\u6210\u529F");
1155
+ p3.outro(
1156
+ pc3.green(
1157
+ `\u4EFB\u52A1\u5DF2\u79FB\u52A8 (etag: ${result[0]?.etag ?? "-"})`
1158
+ )
1159
+ );
1160
+ } catch (err) {
1161
+ s2.stop("\u79FB\u52A8\u5931\u8D25");
1162
+ const msg = err.message;
1163
+ p3.outro(
1164
+ pc3.red(`\u79FB\u52A8\u4EFB\u52A1\u5931\u8D25 (taskId: ${taskId}, from: ${options.from}, to: ${options.to})
1165
+ `) + pc3.red(`${msg}
1166
+ `) + pc3.yellow("\u6392\u67E5\u5EFA\u8BAE: \u8FD0\u884C tt project-list \u9A8C\u8BC1\u9879\u76EE ID \u662F\u5426\u6709\u6548")
1167
+ );
1168
+ }
1169
+ }
1170
+ async function taskCompletedCommand(options) {
1171
+ const params = {};
1172
+ if (options.project) params.projectIds = [options.project];
1173
+ if (options.start) params.startDate = options.start;
1174
+ if (options.end) params.endDate = options.end;
1175
+ const s2 = p3.spinner();
1176
+ s2.start("\u6B63\u5728\u83B7\u53D6\u5DF2\u5B8C\u6210\u4EFB\u52A1...");
1177
+ try {
1178
+ const tasks = await getCompletedTasks(params);
1179
+ s2.stop(`\u627E\u5230 ${tasks.length} \u4E2A\u5DF2\u5B8C\u6210\u4EFB\u52A1`);
1180
+ if (tasks.length === 0) {
1181
+ p3.outro(pc3.yellow("\u6CA1\u6709\u627E\u5230\u5DF2\u5B8C\u6210\u7684\u4EFB\u52A1"));
1182
+ return;
1183
+ }
1184
+ console.log("");
1185
+ displayTaskTable(tasks);
1186
+ console.log("");
1187
+ p3.outro(`\u5171 ${tasks.length} \u4E2A\u5DF2\u5B8C\u6210\u4EFB\u52A1`);
1188
+ } catch (err) {
1189
+ s2.stop("\u83B7\u53D6\u5931\u8D25");
1190
+ p3.outro(pc3.red(err.message));
1191
+ }
1192
+ }
1193
+ async function taskListCommand(options) {
1194
+ const params = {};
1195
+ if (options.project) params.projectIds = [options.project];
1196
+ if (options.start) params.startDate = options.start;
1197
+ if (options.end) params.endDate = options.end;
1198
+ if (options.status) {
1199
+ params.status = options.status.split(",").map((s2) => parseInt(s2, 10));
1200
+ }
1201
+ if (options.priority) {
1202
+ params.priority = options.priority.split(",").map((s2) => parseInt(s2, 10));
1203
+ }
1204
+ if (options.tag) {
1205
+ params.tag = options.tag.split(",");
1206
+ }
1207
+ try {
1208
+ const tasks = await filterTasks(params);
1209
+ if (options.json) {
1210
+ console.log(JSON.stringify(tasks, null, 2));
1211
+ return;
1212
+ }
1213
+ const s2 = p3.spinner();
1214
+ s2.start("\u6B63\u5728\u7B5B\u9009\u4EFB\u52A1...");
1215
+ s2.stop(`\u627E\u5230 ${tasks.length} \u4E2A\u4EFB\u52A1`);
1216
+ if (tasks.length === 0) {
1217
+ p3.outro(pc3.yellow("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u4EFB\u52A1"));
1218
+ return;
1219
+ }
1220
+ console.log("");
1221
+ displayTaskTable(tasks);
1222
+ console.log("");
1223
+ p3.outro(`\u5171 ${tasks.length} \u4E2A\u4EFB\u52A1`);
1224
+ } catch (err) {
1225
+ p3.outro(pc3.red(err.message));
1226
+ }
1227
+ }
1228
+ async function taskBatchAddCommand(jsonFile, options) {
1229
+ let jsonStr;
1230
+ if (options.stdin || !jsonFile) {
1231
+ const chunks = [];
1232
+ for await (const chunk of process.stdin) {
1233
+ chunks.push(chunk);
1234
+ }
1235
+ jsonStr = Buffer.concat(chunks).toString("utf-8");
1236
+ } else {
1237
+ const fs2 = await import("fs/promises");
1238
+ jsonStr = await fs2.readFile(jsonFile, "utf-8");
1239
+ }
1240
+ let tasks;
1241
+ try {
1242
+ tasks = JSON.parse(jsonStr);
1243
+ if (!Array.isArray(tasks)) {
1244
+ p3.outro(pc3.red("JSON \u5FC5\u987B\u662F\u4EFB\u52A1\u6570\u7EC4"));
1245
+ return;
1246
+ }
1247
+ } catch {
1248
+ p3.outro(pc3.red("JSON \u89E3\u6790\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u683C\u5F0F"));
1249
+ return;
1250
+ }
1251
+ for (const task of tasks) {
1252
+ if (task.startDate) task.startDate = normalizeTickTickDate(task.startDate);
1253
+ if (task.dueDate) task.dueDate = normalizeTickTickDate(task.dueDate);
1254
+ }
1255
+ const s2 = p3.spinner();
1256
+ s2.start(`\u6B63\u5728\u6279\u91CF\u521B\u5EFA ${tasks.length} \u4E2A\u4EFB\u52A1...`);
1257
+ try {
1258
+ const result = await batchAddTasks(tasks);
1259
+ s2.stop(`\u6210\u529F\u521B\u5EFA ${result.count} \u4E2A\u4EFB\u52A1`);
1260
+ console.log("");
1261
+ for (const task of tasks) {
1262
+ console.log(` ${pc3.green("\u2713")} ${task.title}`);
1263
+ }
1264
+ console.log("");
1265
+ p3.outro(`\u5171\u521B\u5EFA ${result.count} \u4E2A\u4EFB\u52A1`);
1266
+ } catch (err) {
1267
+ s2.stop("\u6279\u91CF\u521B\u5EFA\u5931\u8D25");
1268
+ p3.outro(pc3.red(err.message));
1269
+ }
1270
+ }
1271
+ async function taskBatchUpdateCommand(jsonFile, options) {
1272
+ let jsonStr;
1273
+ if (options.stdin || !jsonFile) {
1274
+ const chunks = [];
1275
+ for await (const chunk of process.stdin) {
1276
+ chunks.push(chunk);
1277
+ }
1278
+ jsonStr = Buffer.concat(chunks).toString("utf-8");
1279
+ } else {
1280
+ const fs2 = await import("fs/promises");
1281
+ jsonStr = await fs2.readFile(jsonFile, "utf-8");
1282
+ }
1283
+ let tasks;
1284
+ try {
1285
+ tasks = JSON.parse(jsonStr);
1286
+ if (!Array.isArray(tasks)) {
1287
+ p3.outro(pc3.red("JSON \u5FC5\u987B\u662F\u4EFB\u52A1\u6570\u7EC4"));
1288
+ return;
1289
+ }
1290
+ } catch {
1291
+ p3.outro(pc3.red("JSON \u89E3\u6790\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u683C\u5F0F"));
1292
+ return;
1293
+ }
1294
+ const s2 = p3.spinner();
1295
+ s2.start(`\u6B63\u5728\u6279\u91CF\u66F4\u65B0 ${tasks.length} \u4E2A\u4EFB\u52A1...`);
1296
+ try {
1297
+ await batchUpdateTasks(tasks);
1298
+ s2.stop(`\u6210\u529F\u66F4\u65B0 ${tasks.length} \u4E2A\u4EFB\u52A1`);
1299
+ p3.outro(pc3.green(`\u5DF2\u66F4\u65B0 ${tasks.length} \u4E2A\u4EFB\u52A1`));
1300
+ } catch (err) {
1301
+ s2.stop("\u6279\u91CF\u66F4\u65B0\u5931\u8D25");
1302
+ p3.outro(pc3.red(err.message));
1303
+ }
1304
+ }
1305
+ async function taskBatchDoneCommand(projectId, options) {
1306
+ let ids;
1307
+ if (options.all) {
1308
+ const s3 = p3.spinner();
1309
+ s3.start("\u6B63\u5728\u83B7\u53D6\u9879\u76EE\u4EFB\u52A1...");
1310
+ const tasks = await filterTasks({ projectIds: [projectId], status: [0] });
1311
+ s3.stop(`\u627E\u5230 ${tasks.length} \u4E2A\u672A\u5B8C\u6210\u4EFB\u52A1`);
1312
+ ids = tasks.map((t) => t.id);
1313
+ } else if (options.taskIds) {
1314
+ ids = options.taskIds.split(",").map((s3) => s3.trim());
1315
+ } else {
1316
+ p3.outro(pc3.red("\u8BF7\u4F7F\u7528 --task-ids <id1,id2,...> \u6216 --all"));
1317
+ return;
1318
+ }
1319
+ if (ids.length === 0) {
1320
+ p3.outro(pc3.yellow("\u6CA1\u6709\u9700\u8981\u5B8C\u6210\u7684\u4EFB\u52A1"));
1321
+ return;
1322
+ }
1323
+ if (!options.force) {
1324
+ console.log("");
1325
+ console.log(` \u5C06\u5B8C\u6210 ${ids.length} \u4E2A\u4EFB\u52A1:`);
1326
+ for (const id of ids) {
1327
+ console.log(` ${pc3.dim(id)}`);
1328
+ }
1329
+ console.log("");
1330
+ const confirmed = await p3.confirm({
1331
+ message: `\u786E\u8BA4\u5B8C\u6210\u8FD9 ${ids.length} \u4E2A\u4EFB\u52A1\uFF1F`
1332
+ });
1333
+ if (p3.isCancel(confirmed) || !confirmed) {
1334
+ p3.outro("\u5DF2\u53D6\u6D88");
1335
+ return;
1336
+ }
1337
+ }
1338
+ const s2 = p3.spinner();
1339
+ s2.start(`\u6B63\u5728\u5B8C\u6210 ${ids.length} \u4E2A\u4EFB\u52A1...`);
1340
+ try {
1341
+ const result = await completeTasksInProject(projectId, ids);
1342
+ s2.stop("\u64CD\u4F5C\u5B8C\u6210");
1343
+ p3.outro(
1344
+ pc3.green(`\u6210\u529F ${result.completed.length} \u4E2A`) + (result.failed.length > 0 ? pc3.red(`\uFF0C\u5931\u8D25 ${result.failed.length} \u4E2A`) : "")
1345
+ );
1346
+ } catch (err) {
1347
+ s2.stop("\u64CD\u4F5C\u5931\u8D25");
1348
+ p3.outro(pc3.red(err.message));
1349
+ }
1350
+ }
1351
+ async function taskFindCommand(taskId) {
1352
+ const s2 = p3.spinner();
1353
+ s2.start("\u6B63\u5728\u67E5\u627E\u4EFB\u52A1...");
1354
+ try {
1355
+ const task = await getTaskById(taskId);
1356
+ s2.stop("\u67E5\u627E\u6210\u529F");
1357
+ displayTaskDetail(task);
1358
+ p3.outro("\u4EFB\u52A1\u8BE6\u60C5\u5982\u4E0A");
1359
+ } catch (err) {
1360
+ s2.stop("\u67E5\u627E\u5931\u8D25");
1361
+ p3.outro(pc3.red(err.message));
1362
+ }
1363
+ }
1364
+ async function taskUndoneCommand(options) {
1365
+ let tasks;
1366
+ try {
1367
+ if (options.query) {
1368
+ tasks = await listUndoneTasksByTimeQuery(options.query);
1369
+ } else {
1370
+ const params = {};
1371
+ if (options.start) params.startDate = options.start;
1372
+ if (options.end) params.endDate = options.end;
1373
+ if (options.project) params.projectIds = [options.project];
1374
+ tasks = await listUndoneTasksByDate(params);
1375
+ }
1376
+ if (options.json) {
1377
+ console.log(JSON.stringify(tasks, null, 2));
1378
+ return;
1379
+ }
1380
+ const s2 = p3.spinner();
1381
+ s2.start("\u6B63\u5728\u83B7\u53D6\u672A\u5B8C\u6210\u4EFB\u52A1...");
1382
+ s2.stop(`\u627E\u5230 ${tasks.length} \u4E2A\u672A\u5B8C\u6210\u4EFB\u52A1`);
1383
+ if (tasks.length === 0) {
1384
+ p3.outro(pc3.yellow("\u6CA1\u6709\u627E\u5230\u672A\u5B8C\u6210\u4EFB\u52A1"));
1385
+ return;
1386
+ }
1387
+ console.log("");
1388
+ displayTaskTable(tasks);
1389
+ console.log("");
1390
+ p3.outro(`\u5171 ${tasks.length} \u4E2A\u672A\u5B8C\u6210\u4EFB\u52A1`);
1391
+ } catch (err) {
1392
+ s.stop("\u83B7\u53D6\u5931\u8D25");
1393
+ p3.outro(pc3.red(err.message));
1394
+ }
1395
+ }
1396
+ async function taskSearchCommand(keyword) {
1397
+ const s2 = p3.spinner();
1398
+ s2.start(`\u6B63\u5728\u641C\u7D22 "${keyword}"...`);
1399
+ try {
1400
+ const tasks = await searchTask(keyword);
1401
+ s2.stop(`\u627E\u5230 ${tasks.length} \u4E2A\u5339\u914D\u4EFB\u52A1`);
1402
+ if (tasks.length === 0) {
1403
+ p3.outro(pc3.yellow(`\u6CA1\u6709\u627E\u5230\u5305\u542B "${keyword}" \u7684\u4EFB\u52A1`));
1404
+ return;
1405
+ }
1406
+ console.log("");
1407
+ for (const task of tasks) {
1408
+ const icon = statusIcon2(task.status);
1409
+ const title = task.title.replace(
1410
+ new RegExp(keyword, "gi"),
1411
+ (m) => pc3.bold(pc3.yellow(m))
1412
+ );
1413
+ console.log(` ${icon} ${title} ${pc3.dim(task.id)} ${pc3.dim(task.projectId)}`);
1414
+ }
1415
+ console.log("");
1416
+ p3.outro(`\u5171 ${tasks.length} \u4E2A\u5339\u914D`);
1417
+ } catch (err) {
1418
+ s2.stop("\u641C\u7D22\u5931\u8D25");
1419
+ p3.outro(pc3.red(err.message));
1420
+ }
1421
+ }
1422
+ function registerTaskCommands(cli2) {
1423
+ cli2.command("task-add [title]", "\u521B\u5EFA\u4EFB\u52A1").option("-p, --project <id>", "\u9879\u76EE ID").option("--content <text>", "\u4EFB\u52A1\u5185\u5BB9").option("--priority <n>", "\u4F18\u5148\u7EA7: 0(\u65E0) / 1(\u4F4E) / 3(\u4E2D) / 5(\u9AD8)").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--all-day", "\u5168\u5929\u4EFB\u52A1").action(taskAddCommand);
1424
+ cli2.command("task-get <projectId> <taskId>", "\u83B7\u53D6\u4EFB\u52A1\u8BE6\u60C5").action(taskGetCommand);
1425
+ cli2.command("task-done <projectId> <taskId>", "\u5B8C\u6210\u4EFB\u52A1").action(taskDoneCommand);
1426
+ cli2.command("task-delete <projectId> <taskId>", "\u5220\u9664\u4EFB\u52A1").action(taskDeleteCommand);
1427
+ cli2.command("task-update <taskId>", "\u66F4\u65B0\u4EFB\u52A1").option("-p, --project <id>", "\u9879\u76EE ID\uFF08\u5FC5\u586B\uFF09").option("--title <title>", "\u4EFB\u52A1\u6807\u9898").option("--content <text>", "\u4EFB\u52A1\u5185\u5BB9").option("--priority <n>", "\u4F18\u5148\u7EA7").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").action(taskUpdateCommand);
1428
+ cli2.command("task-move <taskId>", "\u79FB\u52A8\u4EFB\u52A1\u5230\u5176\u4ED6\u9879\u76EE").option("-f, --from <id>", "\u6E90\u9879\u76EE ID").option("-t, --to <id>", "\u76EE\u6807\u9879\u76EE ID").action(taskMoveCommand);
1429
+ cli2.command("task-completed", "\u67E5\u770B\u5DF2\u5B8C\u6210\u4EFB\u52A1").option("-p, --project <id>", "\u6309\u9879\u76EE\u7B5B\u9009").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").action(taskCompletedCommand);
1430
+ cli2.command("task-list", "\u7B5B\u9009\u4EFB\u52A1").option("-p, --project <id>", "\u6309\u9879\u76EE\u7B5B\u9009").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("--status <n>", "\u72B6\u6001\uFF0C\u9017\u53F7\u5206\u9694: 0(\u5F85\u529E),2(\u5DF2\u5B8C\u6210)").option("--priority <n>", "\u4F18\u5148\u7EA7\uFF0C\u9017\u53F7\u5206\u9694: 0,1,3,5").option("--tag <tag>", "\u6807\u7B7E\uFF0C\u9017\u53F7\u5206\u9694").option("--json", "\u8F93\u51FA JSON \u683C\u5F0F").action(taskListCommand);
1431
+ cli2.command("task-batch-add [jsonFile]", "\u6279\u91CF\u521B\u5EFA\u4EFB\u52A1").option("--stdin", "\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB\u53D6 JSON").action(taskBatchAddCommand);
1432
+ cli2.command("task-batch-update [jsonFile]", "\u6279\u91CF\u66F4\u65B0\u4EFB\u52A1").option("--stdin", "\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB\u53D6 JSON").action(taskBatchUpdateCommand);
1433
+ cli2.command("task-batch-done <projectId>", "\u6279\u91CF\u5B8C\u6210\u4EFB\u52A1").option("--task-ids <ids>", "\u4EFB\u52A1 ID \u5217\u8868\uFF0C\u9017\u53F7\u5206\u9694").option("--all", "\u5B8C\u6210\u9879\u76EE\u4E0B\u6240\u6709\u672A\u5B8C\u6210\u4EFB\u52A1").option("--force", "\u8DF3\u8FC7\u786E\u8BA4").action(taskBatchDoneCommand);
1434
+ cli2.command("task-find <taskId>", "\u6309 ID \u67E5\u627E\u4EFB\u52A1\uFF08\u65E0\u9700\u9879\u76EE ID\uFF09").action(taskFindCommand);
1435
+ cli2.command("task-undone", "\u67E5\u770B\u672A\u5B8C\u6210\u4EFB\u52A1").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("--query <preset>", "\u9884\u8BBE\u67E5\u8BE2: today|tomorrow|last24hour|next24hour|last7day|next7day").option("-p, --project <id>", "\u6309\u9879\u76EE\u7B5B\u9009").option("--json", "\u8F93\u51FA JSON \u683C\u5F0F").action(taskUndoneCommand);
1436
+ cli2.command("task-search <keyword>", "\u641C\u7D22\u4EFB\u52A1").action(taskSearchCommand);
1437
+ }
1438
+
1439
+ // src/commands/user.ts
1440
+ import * as p4 from "@clack/prompts";
1441
+ import pc4 from "picocolors";
1442
+ async function userPrefCommand() {
1443
+ const s2 = p4.spinner();
1444
+ s2.start("\u6B63\u5728\u83B7\u53D6\u7528\u6237\u504F\u597D...");
1445
+ try {
1446
+ const pref = await getUserPreference();
1447
+ s2.stop("\u83B7\u53D6\u6210\u529F");
1448
+ console.log("");
1449
+ if (pref.timeZone) console.log(` \u65F6\u533A: ${pref.timeZone}`);
1450
+ if (pref.dateFormat !== void 0)
1451
+ console.log(` \u65E5\u671F\u683C\u5F0F: ${pref.dateFormat}`);
1452
+ if (pref.language) console.log(` \u8BED\u8A00: ${pref.language}`);
1453
+ if (pref.theme) console.log(` \u4E3B\u9898: ${pref.theme}`);
1454
+ if (!pref.timeZone && pref.dateFormat === void 0 && !pref.language && !pref.theme) {
1455
+ console.log(pc4.dim(" \uFF08\u672A\u83B7\u53D6\u5230\u504F\u597D\u4FE1\u606F\uFF0CAPI \u53EF\u80FD\u4E0D\u652F\u6301\u6B64\u7AEF\u70B9\uFF09"));
1456
+ }
1457
+ console.log("");
1458
+ p4.outro("\u7528\u6237\u504F\u597D\u5982\u4E0A");
1459
+ } catch (err) {
1460
+ s2.stop("\u83B7\u53D6\u5931\u8D25");
1461
+ p4.outro(pc4.red(`\u83B7\u53D6\u7528\u6237\u504F\u597D\u5931\u8D25: ${err.message}`));
1462
+ }
1463
+ }
1464
+ function registerUserCommands(cli2) {
1465
+ cli2.command("user-pref", "\u67E5\u770B\u7528\u6237\u504F\u597D\u8BBE\u7F6E").action(userPrefCommand);
1466
+ }
1467
+
1468
+ // src/index.ts
1469
+ var SUBCOMMAND_GROUPS = ["task", "project", "user"];
1470
+ var argv = process.argv.slice(2);
1471
+ if (argv.length >= 2 && SUBCOMMAND_GROUPS.includes(argv[0]) && !argv[1].startsWith("-")) {
1472
+ argv.splice(0, 2, `${argv[0]}-${argv[1]}`);
1473
+ }
1474
+ var cli = cac("tt");
1475
+ cli.command("login", "\u767B\u5F55\u6EF4\u7B54\u6E05\u5355").action(loginCommand);
1476
+ cli.command("logout", "\u767B\u51FA").action(logoutCommand);
1477
+ cli.command("whoami", "\u67E5\u770B\u767B\u5F55\u72B6\u6001").action(whoamiCommand);
1478
+ cli.command("config", "\u67E5\u770B/\u8BBE\u7F6E\u914D\u7F6E").option("--region <region>", "\u5207\u6362\u533A\u57DF: cn (\u56FD\u5185\u7248) / global (\u56FD\u9645\u7248)").action(async (options) => {
1479
+ if (options.region && options.region !== "cn" && options.region !== "global") {
1480
+ console.error(`\u65E0\u6548\u7684\u533A\u57DF: ${options.region}\uFF0C\u8BF7\u4F7F\u7528 cn \u6216 global`);
1481
+ process.exit(1);
1482
+ }
1483
+ await configCommand({ region: options.region });
1484
+ });
1485
+ registerProjectCommands(cli);
1486
+ registerTaskCommands(cli);
1487
+ registerUserCommands(cli);
1488
+ cli.help();
1489
+ cli.parse(["", "", ...argv]);
1490
+ //# sourceMappingURL=index.js.map