@xiaolinstar/ai-todo-cli 0.4.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.
- package/README.md +47 -0
- package/dist/index.js +1531 -0
- package/dist/index.js.map +7 -0
- package/package.json +46 -0
- package/settings.example.json +4 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1531 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// ../../packages/api-client/src/index.ts
|
|
27
|
+
var AiTodoClient = class {
|
|
28
|
+
apiUrl;
|
|
29
|
+
token;
|
|
30
|
+
source;
|
|
31
|
+
defaultIdempotencyKey;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.apiUrl = options.apiUrl.replace(/\/$/, "");
|
|
34
|
+
this.token = options.token;
|
|
35
|
+
this.source = options.source;
|
|
36
|
+
this.defaultIdempotencyKey = options.idempotencyKey;
|
|
37
|
+
}
|
|
38
|
+
async request(path2, init = {}, options = {}) {
|
|
39
|
+
const headers = new Headers(init.headers);
|
|
40
|
+
headers.set("content-type", "application/json");
|
|
41
|
+
if (this.token) {
|
|
42
|
+
headers.set("authorization", `Bearer ${this.token}`);
|
|
43
|
+
}
|
|
44
|
+
if (this.source) {
|
|
45
|
+
headers.set("x-client-source", this.source);
|
|
46
|
+
}
|
|
47
|
+
const idempotencyKey = options.idempotencyKey ?? this.defaultIdempotencyKey;
|
|
48
|
+
if (idempotencyKey) {
|
|
49
|
+
headers.set("idempotency-key", idempotencyKey);
|
|
50
|
+
}
|
|
51
|
+
const response = await fetch(`${this.apiUrl}${path2}`, {
|
|
52
|
+
...init,
|
|
53
|
+
headers
|
|
54
|
+
});
|
|
55
|
+
return await response.json();
|
|
56
|
+
}
|
|
57
|
+
createReminder(input) {
|
|
58
|
+
return this.request("/v1/reminders", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: JSON.stringify(input)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
completeReminder(reminderId) {
|
|
64
|
+
return this.request(`/v1/reminders/${reminderId}/complete`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: JSON.stringify({})
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
listReminders(params = {}) {
|
|
70
|
+
const search = new URLSearchParams();
|
|
71
|
+
if (params.status) {
|
|
72
|
+
search.set("status", params.status);
|
|
73
|
+
}
|
|
74
|
+
if (params.from) {
|
|
75
|
+
search.set("from", params.from);
|
|
76
|
+
}
|
|
77
|
+
if (params.to) {
|
|
78
|
+
search.set("to", params.to);
|
|
79
|
+
}
|
|
80
|
+
if (params.limit) {
|
|
81
|
+
search.set("limit", String(params.limit));
|
|
82
|
+
}
|
|
83
|
+
const query = search.toString();
|
|
84
|
+
return this.request(`/v1/reminders${query ? `?${query}` : ""}`);
|
|
85
|
+
}
|
|
86
|
+
listRemindersToday() {
|
|
87
|
+
return this.request("/v1/reminders/today");
|
|
88
|
+
}
|
|
89
|
+
getReminder(reminderId) {
|
|
90
|
+
return this.request(`/v1/reminders/${encodeURIComponent(reminderId)}`);
|
|
91
|
+
}
|
|
92
|
+
updateReminder(reminderId, input) {
|
|
93
|
+
return this.request(`/v1/reminders/${encodeURIComponent(reminderId)}`, {
|
|
94
|
+
method: "PATCH",
|
|
95
|
+
body: JSON.stringify(input)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
rescheduleReminder(reminderId, input) {
|
|
99
|
+
return this.request(
|
|
100
|
+
`/v1/reminders/${encodeURIComponent(reminderId)}/reschedule`,
|
|
101
|
+
{
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: JSON.stringify(input)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
deleteReminder(reminderId) {
|
|
108
|
+
return this.request(`/v1/reminders/${encodeURIComponent(reminderId)}`, {
|
|
109
|
+
method: "DELETE"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
createCalendarEvent(input) {
|
|
113
|
+
return this.request("/v1/calendar/events", {
|
|
114
|
+
method: "POST",
|
|
115
|
+
body: JSON.stringify(input)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
listCalendarEvents(params = {}) {
|
|
119
|
+
const search = new URLSearchParams();
|
|
120
|
+
if (params.from) {
|
|
121
|
+
search.set("from", params.from);
|
|
122
|
+
}
|
|
123
|
+
if (params.to) {
|
|
124
|
+
search.set("to", params.to);
|
|
125
|
+
}
|
|
126
|
+
if (params.limit) {
|
|
127
|
+
search.set("limit", String(params.limit));
|
|
128
|
+
}
|
|
129
|
+
const query = search.toString();
|
|
130
|
+
return this.request(
|
|
131
|
+
`/v1/calendar/events${query ? `?${query}` : ""}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
listCalendarToday() {
|
|
135
|
+
return this.request("/v1/calendar/today");
|
|
136
|
+
}
|
|
137
|
+
getCalendarEvent(eventId) {
|
|
138
|
+
return this.request(
|
|
139
|
+
`/v1/calendar/events/${encodeURIComponent(eventId)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
updateCalendarEvent(eventId, input) {
|
|
143
|
+
return this.request(
|
|
144
|
+
`/v1/calendar/events/${encodeURIComponent(eventId)}`,
|
|
145
|
+
{
|
|
146
|
+
method: "PATCH",
|
|
147
|
+
body: JSON.stringify(input)
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
deleteCalendarEvent(eventId) {
|
|
152
|
+
return this.request(
|
|
153
|
+
`/v1/calendar/events/${encodeURIComponent(eventId)}`,
|
|
154
|
+
{ method: "DELETE" }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
me() {
|
|
158
|
+
return this.request("/v1/me");
|
|
159
|
+
}
|
|
160
|
+
updateProfile(input) {
|
|
161
|
+
return this.request("/v1/me/profile", {
|
|
162
|
+
method: "PATCH",
|
|
163
|
+
body: JSON.stringify(input)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
today() {
|
|
167
|
+
return this.request("/v1/today");
|
|
168
|
+
}
|
|
169
|
+
createContact(input) {
|
|
170
|
+
return this.request("/v1/contacts", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: JSON.stringify(input)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
searchContacts(query) {
|
|
176
|
+
const params = query ? `?q=${encodeURIComponent(query)}` : "";
|
|
177
|
+
return this.request(`/v1/contacts${params}`);
|
|
178
|
+
}
|
|
179
|
+
getContact(contactId) {
|
|
180
|
+
return this.request(`/v1/contacts/${encodeURIComponent(contactId)}`);
|
|
181
|
+
}
|
|
182
|
+
updateContact(contactId, input) {
|
|
183
|
+
return this.request(`/v1/contacts/${encodeURIComponent(contactId)}`, {
|
|
184
|
+
method: "PATCH",
|
|
185
|
+
body: JSON.stringify(input)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
deleteContact(contactId) {
|
|
189
|
+
return this.request(`/v1/contacts/${encodeURIComponent(contactId)}`, {
|
|
190
|
+
method: "DELETE"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
createApiToken(input) {
|
|
194
|
+
return this.request("/v1/api-tokens", {
|
|
195
|
+
method: "POST",
|
|
196
|
+
body: JSON.stringify(input)
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
listApiTokens() {
|
|
200
|
+
return this.request("/v1/api-tokens");
|
|
201
|
+
}
|
|
202
|
+
revokeApiToken(tokenId) {
|
|
203
|
+
return this.request(`/v1/api-tokens/${encodeURIComponent(tokenId)}`, {
|
|
204
|
+
method: "DELETE"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
revokeAllApiTokens() {
|
|
208
|
+
return this.request("/v1/api-tokens/revoke-all", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
body: JSON.stringify({})
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
issueDevPat(input) {
|
|
214
|
+
return this.request("/v1/auth/dev/issue-pat", {
|
|
215
|
+
method: "POST",
|
|
216
|
+
body: JSON.stringify(input)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/settings.ts
|
|
222
|
+
var fs = __toESM(require("node:fs"));
|
|
223
|
+
var os = __toESM(require("node:os"));
|
|
224
|
+
var path = __toESM(require("node:path"));
|
|
225
|
+
var SETTINGS_DIR = path.join(os.homedir(), ".ai-todo");
|
|
226
|
+
var SETTINGS_PATH = path.join(SETTINGS_DIR, "settings.json");
|
|
227
|
+
var LEGACY_CONFIG_PATH = path.join(SETTINGS_DIR, "config.json");
|
|
228
|
+
function normalizeSettings(raw) {
|
|
229
|
+
const url = typeof raw.url === "string" ? raw.url : typeof raw.apiUrl === "string" ? raw.apiUrl : void 0;
|
|
230
|
+
const token = typeof raw.token === "string" ? raw.token : void 0;
|
|
231
|
+
return { url, token };
|
|
232
|
+
}
|
|
233
|
+
function readJsonFile(filePath) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function writeSettingsFile(settings) {
|
|
241
|
+
fs.mkdirSync(SETTINGS_DIR, { recursive: true });
|
|
242
|
+
fs.writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}
|
|
243
|
+
`, "utf8");
|
|
244
|
+
try {
|
|
245
|
+
fs.chmodSync(SETTINGS_PATH, 384);
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function settingsPath() {
|
|
250
|
+
return SETTINGS_PATH;
|
|
251
|
+
}
|
|
252
|
+
function loadSettings() {
|
|
253
|
+
const current = readJsonFile(SETTINGS_PATH);
|
|
254
|
+
if (current) {
|
|
255
|
+
return normalizeSettings(current);
|
|
256
|
+
}
|
|
257
|
+
const legacy = readJsonFile(LEGACY_CONFIG_PATH);
|
|
258
|
+
if (legacy) {
|
|
259
|
+
const migrated = normalizeSettings(legacy);
|
|
260
|
+
writeSettingsFile(migrated);
|
|
261
|
+
return migrated;
|
|
262
|
+
}
|
|
263
|
+
return {};
|
|
264
|
+
}
|
|
265
|
+
function saveSettings(patch) {
|
|
266
|
+
const current = loadSettings();
|
|
267
|
+
const next = { ...current, ...patch };
|
|
268
|
+
writeSettingsFile(next);
|
|
269
|
+
}
|
|
270
|
+
function clearToken() {
|
|
271
|
+
const current = loadSettings();
|
|
272
|
+
const { token: _removed, ...rest } = current;
|
|
273
|
+
writeSettingsFile(rest);
|
|
274
|
+
}
|
|
275
|
+
function resolveApiUrl(settings = loadSettings()) {
|
|
276
|
+
const fromEnv = process.env.AI_TODO_API_URL?.trim();
|
|
277
|
+
if (fromEnv) {
|
|
278
|
+
return fromEnv;
|
|
279
|
+
}
|
|
280
|
+
const fromFile = settings.url?.trim();
|
|
281
|
+
if (fromFile) {
|
|
282
|
+
return fromFile;
|
|
283
|
+
}
|
|
284
|
+
return "http://127.0.0.1:3100";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/auth.ts
|
|
288
|
+
function resolveTokenSource() {
|
|
289
|
+
const envToken = process.env.AI_TODO_TOKEN?.trim();
|
|
290
|
+
if (envToken) {
|
|
291
|
+
return { token: envToken, source: "env" };
|
|
292
|
+
}
|
|
293
|
+
const settingsToken = loadSettings().token?.trim();
|
|
294
|
+
if (settingsToken) {
|
|
295
|
+
return { token: settingsToken, source: "settings" };
|
|
296
|
+
}
|
|
297
|
+
return { source: "none" };
|
|
298
|
+
}
|
|
299
|
+
function settingsExample(apiUrl = "https://wodi.games") {
|
|
300
|
+
return JSON.stringify({ url: apiUrl, token: "aitodo_xxx" }, null, 2);
|
|
301
|
+
}
|
|
302
|
+
function printAuthHint(reason = "missing") {
|
|
303
|
+
const settingsFile = settingsPath();
|
|
304
|
+
const lines = reason === "invalid" ? [
|
|
305
|
+
"API Token \u65E0\u6548\u6216\u5DF2\u8FC7\u671F\u3002",
|
|
306
|
+
"",
|
|
307
|
+
"\u8BF7\u66F4\u65B0 Personal Access Token\uFF08PAT\uFF09\uFF1A",
|
|
308
|
+
"",
|
|
309
|
+
`\u7F16\u8F91 ${settingsFile}\uFF1A`,
|
|
310
|
+
settingsExample(),
|
|
311
|
+
"",
|
|
312
|
+
"Agent / CI \u4E5F\u53EF\u4F7F\u7528\u73AF\u5883\u53D8\u91CF\uFF08\u4F18\u5148\u7EA7\u9AD8\u4E8E\u914D\u7F6E\u6587\u4EF6\uFF09\uFF1A",
|
|
313
|
+
" export AI_TODO_TOKEN=aitodo_xxx",
|
|
314
|
+
" export AI_TODO_API_URL=https://wodi.games",
|
|
315
|
+
"",
|
|
316
|
+
"\u751F\u4EA7\u73AF\u5883\u8BF7\u5728\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F\u300C\u6211\u7684 \u2192 CLI / Agent \u8BBF\u95EE\u4EE4\u724C\u300D\u521B\u5EFA\u65B0 PAT\u3002"
|
|
317
|
+
] : [
|
|
318
|
+
"\u672A\u68C0\u6D4B\u5230 API Token\u3002",
|
|
319
|
+
"",
|
|
320
|
+
"\u9996\u6B21\u914D\u7F6E\uFF1A\u521B\u5EFA ~/.ai-todo/settings.json",
|
|
321
|
+
"",
|
|
322
|
+
settingsExample(),
|
|
323
|
+
"",
|
|
324
|
+
"1. \u5FAE\u4FE1\u5C0F\u7A0B\u5E8F \u2192 \u6211\u7684 \u2192 CLI / Agent \u8BBF\u95EE\u4EE4\u724C \u2192 \u521B\u5EFA",
|
|
325
|
+
"2. \u5C06\u5B8C\u6574 token \u586B\u5165\u4E0A\u8FF0\u6587\u4EF6\u7684 token \u5B57\u6BB5",
|
|
326
|
+
"3. \u8FD0\u884C ai-todo whoami \u9A8C\u8BC1",
|
|
327
|
+
"",
|
|
328
|
+
`\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF1A${settingsFile}`
|
|
329
|
+
];
|
|
330
|
+
for (const line of lines) {
|
|
331
|
+
console.error(line);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/context.ts
|
|
336
|
+
var GLOBAL_FLAGS = /* @__PURE__ */ new Set(["--json", "--yes", "--api-url", "--url", "--idempotency-key", "--profile"]);
|
|
337
|
+
var GLOBAL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["--api-url", "--url", "--idempotency-key", "--profile"]);
|
|
338
|
+
function commandArgv(argv2) {
|
|
339
|
+
const result = [];
|
|
340
|
+
for (let index = 0; index < argv2.length; index += 1) {
|
|
341
|
+
const arg = argv2[index];
|
|
342
|
+
if (GLOBAL_FLAGS.has(arg)) {
|
|
343
|
+
if (GLOBAL_FLAGS_WITH_VALUE.has(arg)) {
|
|
344
|
+
index += 1;
|
|
345
|
+
}
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
result.push(arg);
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function buildContext(argv2) {
|
|
353
|
+
const json = argv2.includes("--json");
|
|
354
|
+
const settings = loadSettings();
|
|
355
|
+
const apiUrl = resolveApiUrl(settings);
|
|
356
|
+
const { token } = resolveTokenSource();
|
|
357
|
+
return {
|
|
358
|
+
json,
|
|
359
|
+
apiUrl,
|
|
360
|
+
client: new AiTodoClient({
|
|
361
|
+
apiUrl,
|
|
362
|
+
token,
|
|
363
|
+
source: "cli",
|
|
364
|
+
idempotencyKey: readFlagValue(argv2, "--idempotency-key")
|
|
365
|
+
})
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function readFlagValue(argv2, flag) {
|
|
369
|
+
const index = argv2.indexOf(flag);
|
|
370
|
+
return index >= 0 ? argv2[index + 1] : void 0;
|
|
371
|
+
}
|
|
372
|
+
function readRepeatedFlag(argv2, flag) {
|
|
373
|
+
const values = [];
|
|
374
|
+
const args = commandArgv(argv2);
|
|
375
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
376
|
+
const next = args[index + 1];
|
|
377
|
+
if (args[index] === flag && next && !next.startsWith("-")) {
|
|
378
|
+
values.push(next);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return values;
|
|
382
|
+
}
|
|
383
|
+
function hasFlag(argv2, flag) {
|
|
384
|
+
return commandArgv(argv2).includes(flag);
|
|
385
|
+
}
|
|
386
|
+
function positionalAfter(argv2, ...anchors) {
|
|
387
|
+
const args = commandArgv(argv2);
|
|
388
|
+
let start = -1;
|
|
389
|
+
for (const anchor of anchors) {
|
|
390
|
+
const index = args.indexOf(anchor);
|
|
391
|
+
if (index > start) {
|
|
392
|
+
start = index;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (start < 0) {
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
const values = [];
|
|
399
|
+
for (let index = start + 1; index < args.length; index += 1) {
|
|
400
|
+
const arg = args[index];
|
|
401
|
+
if (arg === "--email" || arg === "--phone" || arg === "--alias" || arg === "--handle" || arg === "--company" || arg === "--job-title" || arg === "--contact" || arg === "--name") {
|
|
402
|
+
index += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if ([
|
|
406
|
+
"--title",
|
|
407
|
+
"--due",
|
|
408
|
+
"--remind",
|
|
409
|
+
"--notes",
|
|
410
|
+
"--start",
|
|
411
|
+
"--end",
|
|
412
|
+
"--location",
|
|
413
|
+
"--description",
|
|
414
|
+
"--status",
|
|
415
|
+
"--date",
|
|
416
|
+
"--from",
|
|
417
|
+
"--to"
|
|
418
|
+
].includes(arg)) {
|
|
419
|
+
index += 1;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (arg.startsWith("--")) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
values.push(arg);
|
|
426
|
+
}
|
|
427
|
+
return values.length > 0 ? values.join(" ") : void 0;
|
|
428
|
+
}
|
|
429
|
+
async function handleApi(ctx2, response, render) {
|
|
430
|
+
if (ctx2.json) {
|
|
431
|
+
console.log(JSON.stringify(response, null, 2));
|
|
432
|
+
process.exitCode = response.ok ? 0 : 1;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (!response.ok) {
|
|
436
|
+
const code = response.error.code ? `[${response.error.code}] ` : "";
|
|
437
|
+
console.error(`${code}${response.error.message}`);
|
|
438
|
+
if (response.error.code === "UNAUTHORIZED") {
|
|
439
|
+
printAuthHint("invalid");
|
|
440
|
+
}
|
|
441
|
+
process.exitCode = 1;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
render(response.data);
|
|
445
|
+
}
|
|
446
|
+
function persistApiUrl(apiUrl) {
|
|
447
|
+
saveSettings({ url: apiUrl });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/help.ts
|
|
451
|
+
function printHelp() {
|
|
452
|
+
console.log(`ai-todo \u2014 structured CLI for reminders, calendar, and contacts
|
|
453
|
+
|
|
454
|
+
Install:
|
|
455
|
+
npm install -g @xiaolinstar/ai-todo-cli
|
|
456
|
+
|
|
457
|
+
Configuration (~/.ai-todo/settings.json):
|
|
458
|
+
{
|
|
459
|
+
"url": "https://wodi.games",
|
|
460
|
+
"token": "aitodo_xxx"
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
Create a Personal Access Token in the WeChat miniapp (Mine \u2192 CLI / Agent tokens).
|
|
464
|
+
Example file: settings.example.json (bundled with this package).
|
|
465
|
+
|
|
466
|
+
Priority: AI_TODO_TOKEN / AI_TODO_API_URL env > settings.json > http://127.0.0.1:3100
|
|
467
|
+
|
|
468
|
+
Auth check:
|
|
469
|
+
ai-todo whoami
|
|
470
|
+
ai-todo version
|
|
471
|
+
|
|
472
|
+
Profile:
|
|
473
|
+
ai-todo profile update --name <text> [--avatar-url <url>]
|
|
474
|
+
|
|
475
|
+
Global flags (commands only):
|
|
476
|
+
--json Output API JSON (recommended for agents)
|
|
477
|
+
--idempotency-key <uuid> Recommended for agent write retries
|
|
478
|
+
|
|
479
|
+
Today:
|
|
480
|
+
ai-todo today
|
|
481
|
+
|
|
482
|
+
Reminders (aliases: add, list, done, reschedule):
|
|
483
|
+
ai-todo reminder create --title <text> [--due <iso>] [--remind <iso>] [--notes <text>] [--contact <id_or_handle> ...]
|
|
484
|
+
ai-todo reminder list [--status pending|completed|cancelled] [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
485
|
+
ai-todo reminder show <reminder_id>
|
|
486
|
+
ai-todo reminder done <reminder_id>
|
|
487
|
+
ai-todo reminder update <reminder_id> [--title <text>] [--notes <text>] [--due <iso>] [--remind <iso>] [--contact <id_or_handle> ...]
|
|
488
|
+
ai-todo reminder reschedule <reminder_id> --due <iso> [--remind <iso>]
|
|
489
|
+
ai-todo reminder delete <reminder_id>
|
|
490
|
+
ai-todo add <title> # shorthand create (title only)
|
|
491
|
+
|
|
492
|
+
Calendar:
|
|
493
|
+
ai-todo calendar today
|
|
494
|
+
ai-todo calendar list [--date YYYY-MM-DD]
|
|
495
|
+
ai-todo calendar add --title <text> --start <iso> [--end <iso>] [--location <text>]
|
|
496
|
+
ai-todo calendar show <event_id>
|
|
497
|
+
ai-todo calendar update <event_id> [--title <text>] [--start <iso>] [--end <iso>]
|
|
498
|
+
ai-todo calendar delete <event_id>
|
|
499
|
+
|
|
500
|
+
Contacts:
|
|
501
|
+
ai-todo contact add <name> [--handle <handle>] [--email <v>] [--phone <v>] [--company <text>] [--job-title <text>] [--notes <text>] [--alias <v>]
|
|
502
|
+
ai-todo contact list
|
|
503
|
+
ai-todo contact search <query>
|
|
504
|
+
ai-todo contact show <contact_id_or_handle>
|
|
505
|
+
ai-todo contact update <contact_id_or_handle> [--handle <handle>] [--name <text>] [--email <v>] [--phone <v>] [--company <text>] [--job-title <text>] [--notes <text>]
|
|
506
|
+
ai-todo contact delete <contact_id_or_handle>
|
|
507
|
+
|
|
508
|
+
Settings file: ${settingsPath()}
|
|
509
|
+
|
|
510
|
+
Agents: see docs/agent-usage.md and skills/ai-todo/SKILL.md`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/commands/calendar.ts
|
|
514
|
+
async function runCalendarToday(ctx2) {
|
|
515
|
+
await handleApi(ctx2, await ctx2.client.listCalendarToday(), (data) => {
|
|
516
|
+
if (ctx2.json) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (data.items.length === 0) {
|
|
520
|
+
console.log("\u4ECA\u65E5\u6682\u65E0\u65E5\u7A0B");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
for (const event of data.items) {
|
|
524
|
+
const end = event.endAt ? ` - ${event.endAt}` : "";
|
|
525
|
+
const place = event.location ? ` @ ${event.location}` : "";
|
|
526
|
+
console.log(`- ${event.title} (${event.startAt}${end})${place} (${event.id})`);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
async function runCalendarList(ctx2, argv2) {
|
|
531
|
+
const date = readFlagValue(argv2, "--date");
|
|
532
|
+
await handleApi(
|
|
533
|
+
ctx2,
|
|
534
|
+
await ctx2.client.listCalendarEvents(
|
|
535
|
+
date ? { from: date, to: date } : {}
|
|
536
|
+
),
|
|
537
|
+
(data) => {
|
|
538
|
+
if (ctx2.json) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (data.items.length === 0) {
|
|
542
|
+
console.log("\u6682\u65E0\u65E5\u7A0B");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
for (const event of data.items) {
|
|
546
|
+
const end = event.endAt ? ` - ${event.endAt}` : "";
|
|
547
|
+
console.log(`- ${event.title} (${event.startAt}${end}) (${event.id})`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
async function runCalendarAdd(ctx2, argv2) {
|
|
553
|
+
const title = readFlagValue(argv2, "--title");
|
|
554
|
+
const start = readFlagValue(argv2, "--start");
|
|
555
|
+
const end = readFlagValue(argv2, "--end");
|
|
556
|
+
const location = readFlagValue(argv2, "--location");
|
|
557
|
+
if (!title || !start) {
|
|
558
|
+
console.error(
|
|
559
|
+
"Usage: ai-todo calendar add --title <text> --start <iso> [--end <iso>] [--contact <contact_id> ...]"
|
|
560
|
+
);
|
|
561
|
+
process.exitCode = 1;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
await handleApi(
|
|
565
|
+
ctx2,
|
|
566
|
+
await ctx2.client.createCalendarEvent({
|
|
567
|
+
title,
|
|
568
|
+
startAt: start,
|
|
569
|
+
endAt: end,
|
|
570
|
+
location,
|
|
571
|
+
contactIds: readRepeatedFlag(argv2, "--contact")
|
|
572
|
+
}),
|
|
573
|
+
(data) => {
|
|
574
|
+
if (!ctx2.json) {
|
|
575
|
+
const event = data.calendarEvent;
|
|
576
|
+
console.log(`\u5DF2\u521B\u5EFA\u65E5\u7A0B\uFF1A${event.title}`);
|
|
577
|
+
console.log(`ID\uFF1A${event.id}`);
|
|
578
|
+
console.log(`\u5F00\u59CB\uFF1A${event.startAt}`);
|
|
579
|
+
if (event.endAt) {
|
|
580
|
+
console.log(`\u7ED3\u675F\uFF1A${event.endAt}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
async function runCalendarShow(ctx2, argv2) {
|
|
587
|
+
const id = positionalAfter(argv2, "show");
|
|
588
|
+
if (!id) {
|
|
589
|
+
console.error("Usage: ai-todo calendar show <event_id>");
|
|
590
|
+
process.exitCode = 1;
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
await handleApi(ctx2, await ctx2.client.getCalendarEvent(id), (data) => {
|
|
594
|
+
if (ctx2.json) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const e = data.calendarEvent;
|
|
598
|
+
console.log(`${e.title} (${e.id})`);
|
|
599
|
+
console.log(`${e.startAt}${e.endAt ? ` \u2192 ${e.endAt}` : ""}`);
|
|
600
|
+
if (e.location) {
|
|
601
|
+
console.log(`\u5730\u70B9\uFF1A${e.location}`);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async function runCalendarDelete(ctx2, argv2) {
|
|
606
|
+
const id = positionalAfter(argv2, "delete");
|
|
607
|
+
if (!id) {
|
|
608
|
+
console.error("Usage: ai-todo calendar delete <event_id>");
|
|
609
|
+
process.exitCode = 1;
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
await handleApi(ctx2, await ctx2.client.deleteCalendarEvent(id), (data) => {
|
|
613
|
+
if (!ctx2.json) {
|
|
614
|
+
console.log(`\u5DF2\u5220\u9664\u65E5\u7A0B\uFF1A${data.id}`);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
async function runCalendarUpdate(ctx2, argv2) {
|
|
619
|
+
const id = positionalAfter(argv2, "update");
|
|
620
|
+
const title = readFlagValue(argv2, "--title");
|
|
621
|
+
const start = readFlagValue(argv2, "--start");
|
|
622
|
+
const end = readFlagValue(argv2, "--end");
|
|
623
|
+
const location = readFlagValue(argv2, "--location");
|
|
624
|
+
const description = readFlagValue(argv2, "--description");
|
|
625
|
+
const contactIds = readRepeatedFlag(argv2, "--contact");
|
|
626
|
+
const hasContacts = argv2.includes("--contact");
|
|
627
|
+
if (!id) {
|
|
628
|
+
console.error(
|
|
629
|
+
"Usage: ai-todo calendar update <event_id> [--title <text>] [--start <iso>] [--end <iso>] [--location <text>] [--contact <contact_id> ...]"
|
|
630
|
+
);
|
|
631
|
+
process.exitCode = 1;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (!title && !start && end === void 0 && location === void 0 && !description && !hasContacts) {
|
|
635
|
+
console.error("Provide at least one field to update");
|
|
636
|
+
process.exitCode = 1;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
await handleApi(
|
|
640
|
+
ctx2,
|
|
641
|
+
await ctx2.client.updateCalendarEvent(id, {
|
|
642
|
+
title,
|
|
643
|
+
startAt: start,
|
|
644
|
+
endAt: end,
|
|
645
|
+
location,
|
|
646
|
+
description,
|
|
647
|
+
contactIds: hasContacts ? contactIds : void 0
|
|
648
|
+
}),
|
|
649
|
+
(data) => {
|
|
650
|
+
if (!ctx2.json) {
|
|
651
|
+
const event = data.calendarEvent;
|
|
652
|
+
console.log(`\u5DF2\u66F4\u65B0\u65E5\u7A0B\uFF1A${event.title} (${event.id})`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/commands/contact.ts
|
|
659
|
+
async function runContactAdd(ctx2, argv2) {
|
|
660
|
+
const displayName = positionalAfter(argv2, "contact", "add");
|
|
661
|
+
if (!displayName) {
|
|
662
|
+
console.error(
|
|
663
|
+
"Usage: ai-todo contact add <name> [--handle <handle>] [--email <v>] [--phone <v>] [--company <text>] [--job-title <text>] [--notes <text>]"
|
|
664
|
+
);
|
|
665
|
+
process.exitCode = 1;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const methods = [];
|
|
669
|
+
const handle = readFlagValue(argv2, "--handle");
|
|
670
|
+
const email = readFlagValue(argv2, "--email");
|
|
671
|
+
const phone = readFlagValue(argv2, "--phone");
|
|
672
|
+
const alias = readFlagValue(argv2, "--alias");
|
|
673
|
+
const company = readFlagValue(argv2, "--company");
|
|
674
|
+
const jobTitle = readFlagValue(argv2, "--job-title");
|
|
675
|
+
const notes = readFlagValue(argv2, "--notes");
|
|
676
|
+
if (email) {
|
|
677
|
+
methods.push({ type: "email", value: email, label: "work", isPrimary: true });
|
|
678
|
+
}
|
|
679
|
+
if (phone) {
|
|
680
|
+
methods.push({ type: "phone", value: phone, label: "mobile", isPrimary: true });
|
|
681
|
+
}
|
|
682
|
+
await handleApi(
|
|
683
|
+
ctx2,
|
|
684
|
+
await ctx2.client.createContact({
|
|
685
|
+
displayName,
|
|
686
|
+
handle,
|
|
687
|
+
company,
|
|
688
|
+
title: jobTitle,
|
|
689
|
+
notes,
|
|
690
|
+
methods,
|
|
691
|
+
aliases: alias ? [alias] : []
|
|
692
|
+
}),
|
|
693
|
+
(data) => {
|
|
694
|
+
if (!ctx2.json) {
|
|
695
|
+
console.log(`\u5DF2\u521B\u5EFA\u8054\u7CFB\u4EBA\uFF1A${data.contact.displayName}`);
|
|
696
|
+
console.log(`\u6807\u8BC6\uFF1A${data.contact.handle}`);
|
|
697
|
+
console.log(`ID\uFF1A${data.contact.id}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
async function runContactSearch(ctx2, argv2) {
|
|
703
|
+
const query = positionalAfter(argv2, "contact", "search");
|
|
704
|
+
if (!query) {
|
|
705
|
+
console.error("Usage: ai-todo contact search <query>");
|
|
706
|
+
process.exitCode = 1;
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
await handleApi(ctx2, await ctx2.client.searchContacts(query), (data) => {
|
|
710
|
+
if (ctx2.json) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
renderContactList(data.items);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async function runContactList(ctx2) {
|
|
717
|
+
await handleApi(ctx2, await ctx2.client.searchContacts(), (data) => {
|
|
718
|
+
if (ctx2.json) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
renderContactList(data.items);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
async function runContactShow(ctx2, argv2) {
|
|
725
|
+
const id = positionalAfter(argv2, "contact", "show");
|
|
726
|
+
if (!id) {
|
|
727
|
+
console.error("Usage: ai-todo contact show <contact_id_or_handle>");
|
|
728
|
+
process.exitCode = 1;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
await handleApi(ctx2, await ctx2.client.getContact(id), (data) => {
|
|
732
|
+
if (ctx2.json) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const c = data.contact;
|
|
736
|
+
console.log(`${c.displayName} (@${c.handle}, ${c.id})`);
|
|
737
|
+
console.log(`\u6807\u8BC6\u6765\u6E90\uFF1A${c.handleSource === "generated" ? "\u81EA\u52A8\u751F\u6210" : "\u624B\u52A8\u8BBE\u7F6E"}`);
|
|
738
|
+
if (c.linkedUserId) {
|
|
739
|
+
console.log(`\u5E73\u53F0\u7528\u6237\uFF1A${c.linkedUserId}`);
|
|
740
|
+
}
|
|
741
|
+
if (c.primaryEmail) {
|
|
742
|
+
console.log(`\u90AE\u7BB1\uFF1A${c.primaryEmail}`);
|
|
743
|
+
}
|
|
744
|
+
if (c.primaryPhone) {
|
|
745
|
+
console.log(`\u7535\u8BDD\uFF1A${c.primaryPhone}`);
|
|
746
|
+
}
|
|
747
|
+
if (c.company) {
|
|
748
|
+
console.log(`\u516C\u53F8\uFF1A${c.company}`);
|
|
749
|
+
}
|
|
750
|
+
if (c.title) {
|
|
751
|
+
console.log(`\u804C\u4F4D\uFF1A${c.title}`);
|
|
752
|
+
}
|
|
753
|
+
if (c.aliases.length > 0) {
|
|
754
|
+
console.log(`\u522B\u540D\uFF1A${c.aliases.join(", ")}`);
|
|
755
|
+
}
|
|
756
|
+
if (c.methods.length > 0) {
|
|
757
|
+
console.log("\u8054\u7CFB\u65B9\u5F0F\uFF1A");
|
|
758
|
+
for (const method of c.methods) {
|
|
759
|
+
const primary = method.isPrimary ? "\uFF0C\u4E3B\u8981" : "";
|
|
760
|
+
const label = method.label ? `\uFF0C${method.label}` : "";
|
|
761
|
+
console.log(`- ${method.type}${label}${primary}\uFF1A${method.value}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (c.notes) {
|
|
765
|
+
console.log(`\u5907\u6CE8\uFF1A${c.notes}`);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
async function runContactUpdate(ctx2, argv2) {
|
|
770
|
+
const id = positionalAfter(argv2, "contact", "update");
|
|
771
|
+
if (!id) {
|
|
772
|
+
console.error(
|
|
773
|
+
"Usage: ai-todo contact update <contact_id_or_handle> [--handle <handle>] [--name <text>] [--email <v>] [--phone <v>] [--company <text>] [--job-title <text>] [--alias <v>] [--notes <text>]"
|
|
774
|
+
);
|
|
775
|
+
process.exitCode = 1;
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const displayName = readFlagValue(argv2, "--name");
|
|
779
|
+
const handle = readFlagValue(argv2, "--handle");
|
|
780
|
+
const email = readFlagValue(argv2, "--email");
|
|
781
|
+
const phone = readFlagValue(argv2, "--phone");
|
|
782
|
+
const alias = readFlagValue(argv2, "--alias");
|
|
783
|
+
const company = readFlagValue(argv2, "--company");
|
|
784
|
+
const jobTitle = readFlagValue(argv2, "--job-title");
|
|
785
|
+
const notes = readFlagValue(argv2, "--notes");
|
|
786
|
+
const hasCompany = argv2.includes("--company");
|
|
787
|
+
const hasJobTitle = argv2.includes("--job-title");
|
|
788
|
+
const hasNotes = argv2.includes("--notes");
|
|
789
|
+
if (!displayName && !handle && !email && !phone && !alias && !hasCompany && !hasJobTitle && !hasNotes) {
|
|
790
|
+
console.error("Provide at least one field to update");
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const methods = [];
|
|
795
|
+
if (email) {
|
|
796
|
+
methods.push({ type: "email", value: email, label: "work", isPrimary: true });
|
|
797
|
+
}
|
|
798
|
+
if (phone) {
|
|
799
|
+
methods.push({ type: "phone", value: phone, label: "mobile", isPrimary: true });
|
|
800
|
+
}
|
|
801
|
+
await handleApi(
|
|
802
|
+
ctx2,
|
|
803
|
+
await ctx2.client.updateContact(id, {
|
|
804
|
+
displayName,
|
|
805
|
+
handle,
|
|
806
|
+
company: hasCompany ? company : void 0,
|
|
807
|
+
title: hasJobTitle ? jobTitle : void 0,
|
|
808
|
+
notes: hasNotes ? notes : void 0,
|
|
809
|
+
methods: methods.length > 0 ? methods : void 0,
|
|
810
|
+
aliases: alias ? [alias] : void 0
|
|
811
|
+
}),
|
|
812
|
+
(data) => {
|
|
813
|
+
if (!ctx2.json) {
|
|
814
|
+
console.log(
|
|
815
|
+
`\u5DF2\u66F4\u65B0\u8054\u7CFB\u4EBA\uFF1A${data.contact.displayName} (@${data.contact.handle}, ${data.contact.id})`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
async function runContactDelete(ctx2, argv2) {
|
|
822
|
+
const id = positionalAfter(argv2, "contact", "delete");
|
|
823
|
+
if (!id) {
|
|
824
|
+
console.error("Usage: ai-todo contact delete <contact_id_or_handle>");
|
|
825
|
+
process.exitCode = 1;
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await handleApi(ctx2, await ctx2.client.deleteContact(id), (data) => {
|
|
829
|
+
if (!ctx2.json) {
|
|
830
|
+
console.log(`\u5DF2\u5220\u9664\u8054\u7CFB\u4EBA\uFF1A${data.id}`);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
function renderContactList(items) {
|
|
835
|
+
if (items.length === 0) {
|
|
836
|
+
console.log("\u672A\u627E\u5230\u8054\u7CFB\u4EBA");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
for (const contact of items) {
|
|
840
|
+
const email = contact.primaryEmail ? ` <${contact.primaryEmail}>` : "";
|
|
841
|
+
const phone = contact.primaryPhone ? ` ${contact.primaryPhone}` : "";
|
|
842
|
+
const company = contact.company ? ` \xB7 ${contact.company}` : "";
|
|
843
|
+
console.log(`- ${contact.displayName}${company}${email}${phone} (@${contact.handle}, ${contact.id})`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/version.ts
|
|
848
|
+
var import_node_fs = require("node:fs");
|
|
849
|
+
var import_node_path = require("node:path");
|
|
850
|
+
var PACKAGE_JSON = (0, import_node_path.join)(__dirname, "..", "package.json");
|
|
851
|
+
function getCliVersion() {
|
|
852
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(PACKAGE_JSON, "utf8"));
|
|
853
|
+
return pkg.version;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/commands/core.ts
|
|
857
|
+
function readLoginUrl(argv2) {
|
|
858
|
+
return readFlagValue(argv2, "--url") ?? readFlagValue(argv2, "--api-url");
|
|
859
|
+
}
|
|
860
|
+
async function runVersion(ctx2) {
|
|
861
|
+
const cliVersion = getCliVersion();
|
|
862
|
+
if (ctx2.json) {
|
|
863
|
+
console.log(
|
|
864
|
+
JSON.stringify(
|
|
865
|
+
{
|
|
866
|
+
ok: true,
|
|
867
|
+
component: "cli",
|
|
868
|
+
version: cliVersion,
|
|
869
|
+
apiUrl: ctx2.apiUrl
|
|
870
|
+
},
|
|
871
|
+
null,
|
|
872
|
+
2
|
|
873
|
+
)
|
|
874
|
+
);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
console.log(`ai-todo CLI ${cliVersion}`);
|
|
878
|
+
console.log(`API: ${ctx2.apiUrl}`);
|
|
879
|
+
}
|
|
880
|
+
async function runLogin(ctx2, argv2) {
|
|
881
|
+
const apiUrl = readLoginUrl(argv2) ?? ctx2.apiUrl;
|
|
882
|
+
persistApiUrl(apiUrl);
|
|
883
|
+
const token = readFlagValue(argv2, "--token");
|
|
884
|
+
const issuePat = argv2.includes("--issue-pat");
|
|
885
|
+
const name = readFlagValue(argv2, "--name") ?? "CLI Local";
|
|
886
|
+
if (issuePat) {
|
|
887
|
+
await issuePersonalAccessToken(ctx2, apiUrl, name);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (token) {
|
|
891
|
+
saveSettings({ token, url: apiUrl });
|
|
892
|
+
}
|
|
893
|
+
const resolved = resolveTokenSource();
|
|
894
|
+
if (ctx2.json) {
|
|
895
|
+
console.log(
|
|
896
|
+
JSON.stringify(
|
|
897
|
+
{
|
|
898
|
+
ok: true,
|
|
899
|
+
apiUrl,
|
|
900
|
+
settingsPath: settingsPath(),
|
|
901
|
+
tokenSource: resolved.source,
|
|
902
|
+
hasToken: resolved.source !== "none"
|
|
903
|
+
},
|
|
904
|
+
null,
|
|
905
|
+
2
|
|
906
|
+
)
|
|
907
|
+
);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
console.log(`\u5DF2\u4FDD\u5B58 API \u5730\u5740\uFF1A${apiUrl}`);
|
|
911
|
+
if (token) {
|
|
912
|
+
console.log(`\u5DF2\u5199\u5165 ${settingsPath()}`);
|
|
913
|
+
console.log("\u540E\u7EED\u76F4\u63A5\u8FD0\u884C ai-todo whoami / ai-todo today \u7B49\u547D\u4EE4\u5373\u53EF\uFF0C\u65E0\u9700\u518D\u4F20 --url\u3002");
|
|
914
|
+
console.log("Agent \u73AF\u5883\u4ECD\u53EF\u7528 export AI_TODO_TOKEN=\u2026 \u8986\u76D6\u914D\u7F6E\u6587\u4EF6\u3002");
|
|
915
|
+
} else if (resolved.source === "env") {
|
|
916
|
+
console.log("\u68C0\u6D4B\u5230 AI_TODO_TOKEN \u73AF\u5883\u53D8\u91CF\uFF08\u5DF2\u751F\u6548\uFF09");
|
|
917
|
+
} else {
|
|
918
|
+
printAuthHint("missing");
|
|
919
|
+
}
|
|
920
|
+
console.log(`\u914D\u7F6E\u6587\u4EF6\uFF1A${settingsPath()}`);
|
|
921
|
+
}
|
|
922
|
+
async function issuePersonalAccessToken(ctx2, apiUrl, name) {
|
|
923
|
+
if (!isLocalDevApiUrl(apiUrl)) {
|
|
924
|
+
if (ctx2.json) {
|
|
925
|
+
console.log(
|
|
926
|
+
JSON.stringify(
|
|
927
|
+
{
|
|
928
|
+
ok: false,
|
|
929
|
+
error: {
|
|
930
|
+
code: "PAT_CREATE_NOT_SUPPORTED",
|
|
931
|
+
message: "Create a Personal Access Token in the WeChat miniapp Mine tab, then write ~/.ai-todo/settings.json"
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
null,
|
|
935
|
+
2
|
|
936
|
+
)
|
|
937
|
+
);
|
|
938
|
+
} else {
|
|
939
|
+
console.error("\u751F\u4EA7/\u8FDC\u7A0B API \u4E0D\u652F\u6301 CLI \u76F4\u63A5\u7B7E\u53D1 PAT\u3002");
|
|
940
|
+
console.error("\u8BF7\u5728\u5FAE\u4FE1\u5C0F\u7A0B\u5E8F\u300C\u6211\u7684 \u2192 CLI / Agent \u8BBF\u95EE\u4EE4\u724C\u300D\u4E2D\u521B\u5EFA\uFF0C\u7136\u540E\u5199\u5165 ~/.ai-todo/settings.json\u3002");
|
|
941
|
+
}
|
|
942
|
+
process.exitCode = 1;
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const response = await ctx2.client.issueDevPat({ name });
|
|
946
|
+
if (!response.ok) {
|
|
947
|
+
if (ctx2.json) {
|
|
948
|
+
console.log(JSON.stringify(response, null, 2));
|
|
949
|
+
} else {
|
|
950
|
+
console.error(`[${response.error.code}] ${response.error.message}`);
|
|
951
|
+
console.error("");
|
|
952
|
+
console.error("\u7B7E\u53D1 PAT \u5931\u8D25\u3002\u8BF7\u786E\u8BA4 API \u5DF2\u542F\u52A8\u4E14 AI_TODO_ALLOW_DEV_AUTH=true\u3002");
|
|
953
|
+
}
|
|
954
|
+
process.exitCode = 1;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const { token, id, scopes } = response.data;
|
|
958
|
+
saveSettings({ token, url: apiUrl });
|
|
959
|
+
if (ctx2.json) {
|
|
960
|
+
console.log(
|
|
961
|
+
JSON.stringify(
|
|
962
|
+
{
|
|
963
|
+
ok: true,
|
|
964
|
+
apiUrl,
|
|
965
|
+
tokenId: id,
|
|
966
|
+
token,
|
|
967
|
+
scopes,
|
|
968
|
+
savedTo: settingsPath(),
|
|
969
|
+
envHint: `export AI_TODO_TOKEN=${token}`
|
|
970
|
+
},
|
|
971
|
+
null,
|
|
972
|
+
2
|
|
973
|
+
)
|
|
974
|
+
);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
console.log("\u5DF2\u7B7E\u53D1 Personal Access Token\uFF08\u4EC5\u663E\u793A\u4E00\u6B21\uFF0C\u8BF7\u59A5\u5584\u4FDD\u7BA1\uFF09\uFF1A");
|
|
978
|
+
console.log("");
|
|
979
|
+
console.log(token);
|
|
980
|
+
console.log("");
|
|
981
|
+
console.log("\u63A8\u8350\u5199\u5165 Agent \u73AF\u5883\u53D8\u91CF\uFF1A");
|
|
982
|
+
console.log(` export AI_TODO_TOKEN=${token}`);
|
|
983
|
+
console.log("");
|
|
984
|
+
console.log(`\u5DF2\u540C\u65F6\u4FDD\u5B58\u5230 ${settingsPath()}\uFF08\u53EF\u88AB\u73AF\u5883\u53D8\u91CF\u8986\u76D6\uFF09`);
|
|
985
|
+
}
|
|
986
|
+
async function runLogout(ctx2) {
|
|
987
|
+
clearToken();
|
|
988
|
+
const resolved = resolveTokenSource();
|
|
989
|
+
if (ctx2.json) {
|
|
990
|
+
console.log(
|
|
991
|
+
JSON.stringify(
|
|
992
|
+
{
|
|
993
|
+
ok: true,
|
|
994
|
+
clearedSettings: true,
|
|
995
|
+
tokenSource: resolved.source,
|
|
996
|
+
note: resolved.source === "env" ? "AI_TODO_TOKEN \u73AF\u5883\u53D8\u91CF\u4ECD\u751F\u6548\uFF1Bunset AI_TODO_TOKEN \u53EF\u5B8C\u5168\u9000\u51FA" : "settings.json \u4E2D\u7684 token \u5DF2\u6E05\u9664"
|
|
997
|
+
},
|
|
998
|
+
null,
|
|
999
|
+
2
|
|
1000
|
+
)
|
|
1001
|
+
);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
console.log(`\u5DF2\u6E05\u9664 ${settingsPath()} \u4E2D\u7684 token`);
|
|
1005
|
+
if (resolved.source === "env") {
|
|
1006
|
+
console.log("\u6CE8\u610F\uFF1AAI_TODO_TOKEN \u73AF\u5883\u53D8\u91CF\u4ECD\u7136\u751F\u6548");
|
|
1007
|
+
console.log("\u8FD0\u884C unset AI_TODO_TOKEN \u53EF\u5B8C\u5168\u9000\u51FA\u6388\u6743");
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
async function runWhoami(ctx2) {
|
|
1011
|
+
const resolved = resolveTokenSource();
|
|
1012
|
+
if (resolved.source === "none") {
|
|
1013
|
+
if (ctx2.json) {
|
|
1014
|
+
console.log(
|
|
1015
|
+
JSON.stringify(
|
|
1016
|
+
{
|
|
1017
|
+
ok: false,
|
|
1018
|
+
error: {
|
|
1019
|
+
code: "UNAUTHORIZED",
|
|
1020
|
+
message: "No API token configured."
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
null,
|
|
1024
|
+
2
|
|
1025
|
+
)
|
|
1026
|
+
);
|
|
1027
|
+
} else {
|
|
1028
|
+
printAuthHint("missing");
|
|
1029
|
+
}
|
|
1030
|
+
process.exitCode = 1;
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
await handleApi(ctx2, await ctx2.client.me(), (data) => {
|
|
1034
|
+
if (!ctx2.json) {
|
|
1035
|
+
const sourceLabel = resolved.source === "env" ? "\u73AF\u5883\u53D8\u91CF AI_TODO_TOKEN" : `\u914D\u7F6E\u6587\u4EF6 ${settingsPath()}`;
|
|
1036
|
+
console.log(`${data.user.displayName} (${data.user.id})`);
|
|
1037
|
+
console.log(`API\uFF1A${resolveApiUrl()}`);
|
|
1038
|
+
console.log(`\u65F6\u533A\uFF1A${data.user.timezone}`);
|
|
1039
|
+
console.log(`\u6388\u6743\u6765\u6E90\uFF1A${sourceLabel}`);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
async function runProfileUpdate(ctx2, argv2) {
|
|
1044
|
+
const displayName = readFlagValue(argv2, "--name") ?? readFlagValue(argv2, "--display-name");
|
|
1045
|
+
const avatarUrl = readFlagValue(argv2, "--avatar-url");
|
|
1046
|
+
if (!displayName && avatarUrl === void 0) {
|
|
1047
|
+
console.error("Usage: ai-todo profile update --name <text> [--avatar-url <url>]");
|
|
1048
|
+
process.exitCode = 1;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
await handleApi(
|
|
1052
|
+
ctx2,
|
|
1053
|
+
await ctx2.client.updateProfile({
|
|
1054
|
+
displayName,
|
|
1055
|
+
avatarUrl
|
|
1056
|
+
}),
|
|
1057
|
+
(data) => {
|
|
1058
|
+
if (!ctx2.json) {
|
|
1059
|
+
console.log(`\u5DF2\u66F4\u65B0\u4E2A\u4EBA\u8D44\u6599\uFF1A${data.user.displayName} (${data.user.id})`);
|
|
1060
|
+
if (data.user.avatarUrl) {
|
|
1061
|
+
console.log(`\u5934\u50CF\uFF1A${data.user.avatarUrl}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
async function runToday(ctx2) {
|
|
1068
|
+
await handleApi(ctx2, await ctx2.client.today(), (data) => {
|
|
1069
|
+
if (ctx2.json) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
console.log(`${data.date} (${data.timezone})`);
|
|
1073
|
+
console.log("\u63D0\u9192\uFF1A");
|
|
1074
|
+
if (data.reminders.length === 0) {
|
|
1075
|
+
console.log(" \u6682\u65E0");
|
|
1076
|
+
} else {
|
|
1077
|
+
for (const reminder of data.reminders) {
|
|
1078
|
+
console.log(` - [${reminder.status}] ${reminder.title} (${reminder.id})`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
console.log("\u65E5\u7A0B\uFF1A");
|
|
1082
|
+
if (data.calendarEvents.length === 0) {
|
|
1083
|
+
console.log(" \u6682\u65E0");
|
|
1084
|
+
} else {
|
|
1085
|
+
for (const event of data.calendarEvents) {
|
|
1086
|
+
const end = event.endAt ? ` - ${event.endAt}` : "";
|
|
1087
|
+
const place = event.location ? ` @ ${event.location}` : "";
|
|
1088
|
+
console.log(` - ${event.title} (${event.startAt}${end})${place} (${event.id})`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function isLocalDevApiUrl(apiUrl) {
|
|
1094
|
+
try {
|
|
1095
|
+
const hostname = new URL(apiUrl).hostname;
|
|
1096
|
+
return hostname === "127.0.0.1" || hostname === "localhost";
|
|
1097
|
+
} catch {
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/commands/reminder.ts
|
|
1103
|
+
async function runReminderCreate(ctx2, argv2) {
|
|
1104
|
+
const title = readFlagValue(argv2, "--title") ?? positionalAfter(argv2, "add", "create");
|
|
1105
|
+
if (!title) {
|
|
1106
|
+
console.error(
|
|
1107
|
+
"Usage: ai-todo reminder create --title <text> [--due <iso>] [--contact <contact_id> ...]"
|
|
1108
|
+
);
|
|
1109
|
+
process.exitCode = 1;
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
await handleApi(
|
|
1113
|
+
ctx2,
|
|
1114
|
+
await ctx2.client.createReminder({
|
|
1115
|
+
title,
|
|
1116
|
+
dueAt: readFlagValue(argv2, "--due"),
|
|
1117
|
+
remindAt: readFlagValue(argv2, "--remind"),
|
|
1118
|
+
notes: readFlagValue(argv2, "--notes"),
|
|
1119
|
+
contactIds: readRepeatedFlag(argv2, "--contact")
|
|
1120
|
+
}),
|
|
1121
|
+
(data) => {
|
|
1122
|
+
if (!ctx2.json) {
|
|
1123
|
+
console.log(`\u5DF2\u521B\u5EFA\u63D0\u9192\uFF1A${data.reminder.title}`);
|
|
1124
|
+
console.log(`ID\uFF1A${data.reminder.id}`);
|
|
1125
|
+
if (data.reminder.dueAt) {
|
|
1126
|
+
console.log(`\u622A\u6B62\uFF1A${data.reminder.dueAt}`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
async function runReminderList(ctx2, argv2) {
|
|
1133
|
+
const status = readFlagValue(argv2, "--status");
|
|
1134
|
+
await handleApi(
|
|
1135
|
+
ctx2,
|
|
1136
|
+
await ctx2.client.listReminders({
|
|
1137
|
+
status,
|
|
1138
|
+
from: readFlagValue(argv2, "--from"),
|
|
1139
|
+
to: readFlagValue(argv2, "--to")
|
|
1140
|
+
}),
|
|
1141
|
+
(data) => {
|
|
1142
|
+
if (ctx2.json) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (data.items.length === 0) {
|
|
1146
|
+
console.log("\u6682\u65E0\u63D0\u9192");
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
for (const reminder of data.items) {
|
|
1150
|
+
const due = reminder.dueAt ? ` @ ${reminder.dueAt}` : "";
|
|
1151
|
+
console.log(`- [${reminder.status}] ${reminder.title}${due} (${reminder.id})`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
async function runReminderShow(ctx2, argv2) {
|
|
1157
|
+
const id = positionalAfter(argv2, "show");
|
|
1158
|
+
if (!id) {
|
|
1159
|
+
console.error("Usage: ai-todo reminder show <reminder_id>");
|
|
1160
|
+
process.exitCode = 1;
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
await handleApi(ctx2, await ctx2.client.getReminder(id), (data) => {
|
|
1164
|
+
if (ctx2.json) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const r = data.reminder;
|
|
1168
|
+
console.log(`${r.title} (${r.id})`);
|
|
1169
|
+
console.log(`\u72B6\u6001\uFF1A${r.status}`);
|
|
1170
|
+
if (r.dueAt) {
|
|
1171
|
+
console.log(`\u622A\u6B62\uFF1A${r.dueAt}`);
|
|
1172
|
+
}
|
|
1173
|
+
if (r.remindAt) {
|
|
1174
|
+
console.log(`\u901A\u77E5\uFF1A${r.remindAt}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (r.notes) {
|
|
1177
|
+
console.log(`\u5907\u6CE8\uFF1A${r.notes}`);
|
|
1178
|
+
}
|
|
1179
|
+
if (r.contacts && r.contacts.length > 0) {
|
|
1180
|
+
console.log(
|
|
1181
|
+
`\u8054\u7CFB\u4EBA\uFF1A${r.contacts.map((c) => `${c.displayName} (@${c.handle})`).join(", ")}`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
async function runReminderDone(ctx2, argv2) {
|
|
1187
|
+
const id = positionalAfter(argv2, "done", "complete");
|
|
1188
|
+
if (!id) {
|
|
1189
|
+
console.error("Usage: ai-todo reminder done <reminder_id>");
|
|
1190
|
+
process.exitCode = 1;
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
await handleApi(ctx2, await ctx2.client.completeReminder(id), (data) => {
|
|
1194
|
+
if (!ctx2.json) {
|
|
1195
|
+
console.log(`\u5DF2\u5B8C\u6210\u63D0\u9192\uFF1A${data.reminder.title}`);
|
|
1196
|
+
console.log(`ID\uFF1A${data.reminder.id}`);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
async function runReminderUpdate(ctx2, argv2) {
|
|
1201
|
+
const id = positionalAfter(argv2, "update");
|
|
1202
|
+
if (!id) {
|
|
1203
|
+
console.error(
|
|
1204
|
+
"Usage: ai-todo reminder update <reminder_id> [--title <text>] [--notes <text>] [--due <iso>] [--remind <iso>] [--contact <contact_id_or_handle> ...]"
|
|
1205
|
+
);
|
|
1206
|
+
process.exitCode = 1;
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const title = readFlagValue(argv2, "--title");
|
|
1210
|
+
const notes = readFlagValue(argv2, "--notes");
|
|
1211
|
+
const due = readFlagValue(argv2, "--due");
|
|
1212
|
+
const remind = readFlagValue(argv2, "--remind");
|
|
1213
|
+
const contactIds = readRepeatedFlag(argv2, "--contact");
|
|
1214
|
+
const hasContacts = hasFlag(argv2, "--contact");
|
|
1215
|
+
const hasNotes = hasFlag(argv2, "--notes");
|
|
1216
|
+
if (!title && notes === void 0 && due === void 0 && remind === void 0 && !hasContacts) {
|
|
1217
|
+
console.error("Provide at least one field to update");
|
|
1218
|
+
process.exitCode = 1;
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
await handleApi(
|
|
1222
|
+
ctx2,
|
|
1223
|
+
await ctx2.client.updateReminder(id, {
|
|
1224
|
+
title,
|
|
1225
|
+
notes: hasNotes ? notes : void 0,
|
|
1226
|
+
dueAt: due,
|
|
1227
|
+
remindAt: remind,
|
|
1228
|
+
contactIds: hasContacts ? contactIds : void 0
|
|
1229
|
+
}),
|
|
1230
|
+
(data) => {
|
|
1231
|
+
if (!ctx2.json) {
|
|
1232
|
+
const r = data.reminder;
|
|
1233
|
+
console.log(`\u5DF2\u66F4\u65B0\u63D0\u9192\uFF1A${r.title} (${r.id})`);
|
|
1234
|
+
if (r.dueAt) {
|
|
1235
|
+
console.log(`\u622A\u6B62\uFF1A${r.dueAt}`);
|
|
1236
|
+
}
|
|
1237
|
+
if (r.remindAt) {
|
|
1238
|
+
console.log(`\u901A\u77E5\uFF1A${r.remindAt}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
async function runReminderReschedule(ctx2, argv2) {
|
|
1245
|
+
const id = positionalAfter(argv2, "reschedule");
|
|
1246
|
+
const due = readFlagValue(argv2, "--due");
|
|
1247
|
+
const remind = readFlagValue(argv2, "--remind");
|
|
1248
|
+
if (!id || !due && !remind) {
|
|
1249
|
+
console.error("Usage: ai-todo reminder reschedule <reminder_id> --due <iso> [--remind <iso>]");
|
|
1250
|
+
process.exitCode = 1;
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
await handleApi(
|
|
1254
|
+
ctx2,
|
|
1255
|
+
await ctx2.client.rescheduleReminder(id, { dueAt: due, remindAt: remind }),
|
|
1256
|
+
(data) => {
|
|
1257
|
+
if (!ctx2.json) {
|
|
1258
|
+
console.log(`\u5DF2\u6539\u671F\uFF1A${data.reminder.title}`);
|
|
1259
|
+
if (data.reminder.dueAt) {
|
|
1260
|
+
console.log(`\u65B0\u622A\u6B62\uFF1A${data.reminder.dueAt}`);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
async function runReminderDelete(ctx2, argv2) {
|
|
1267
|
+
const id = positionalAfter(argv2, "delete");
|
|
1268
|
+
if (!id) {
|
|
1269
|
+
console.error("Usage: ai-todo reminder delete <reminder_id>");
|
|
1270
|
+
process.exitCode = 1;
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
await handleApi(ctx2, await ctx2.client.deleteReminder(id), (data) => {
|
|
1274
|
+
if (!ctx2.json) {
|
|
1275
|
+
console.log(`\u5DF2\u5220\u9664\u63D0\u9192\uFF1A${data.id}`);
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/commands/token.ts
|
|
1281
|
+
function readTokenName(argv2) {
|
|
1282
|
+
return readFlagValue(argv2, "--name");
|
|
1283
|
+
}
|
|
1284
|
+
function readMaxIdleDays(argv2) {
|
|
1285
|
+
const raw = readFlagValue(argv2, "--max-idle-days");
|
|
1286
|
+
if (!raw) {
|
|
1287
|
+
return void 0;
|
|
1288
|
+
}
|
|
1289
|
+
const value = Number(raw);
|
|
1290
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1291
|
+
throw new Error("--max-idle-days must be a positive integer.");
|
|
1292
|
+
}
|
|
1293
|
+
return value;
|
|
1294
|
+
}
|
|
1295
|
+
function readScopes(argv2) {
|
|
1296
|
+
const raw = readFlagValue(argv2, "--scopes");
|
|
1297
|
+
if (!raw) {
|
|
1298
|
+
return void 0;
|
|
1299
|
+
}
|
|
1300
|
+
return raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1301
|
+
}
|
|
1302
|
+
function statusLabel(status) {
|
|
1303
|
+
switch (status) {
|
|
1304
|
+
case "active":
|
|
1305
|
+
return "\u6709\u6548";
|
|
1306
|
+
case "expired":
|
|
1307
|
+
return "\u5DF2\u8FC7\u671F";
|
|
1308
|
+
case "idle_revoked":
|
|
1309
|
+
return "\u4E45\u672A\u4F7F\u7528\u5931\u6548";
|
|
1310
|
+
case "revoked":
|
|
1311
|
+
return "\u5DF2\u540A\u9500";
|
|
1312
|
+
default:
|
|
1313
|
+
return status;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function formatDate(value) {
|
|
1317
|
+
return value ?? "-";
|
|
1318
|
+
}
|
|
1319
|
+
async function runTokenList(ctx2) {
|
|
1320
|
+
await handleApi(ctx2, await ctx2.client.listApiTokens(), (data) => {
|
|
1321
|
+
if (ctx2.json) {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (data.items.length === 0) {
|
|
1325
|
+
console.log("\u6682\u65E0\u8BBF\u95EE\u4EE4\u724C\u3002");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
for (const item of data.items) {
|
|
1329
|
+
console.log(`${item.id} ${item.name} ${statusLabel(item.status)}`);
|
|
1330
|
+
console.log(` \u63D0\u793A\uFF1A${item.tokenHint ?? "aitodo_****"}`);
|
|
1331
|
+
console.log(` \u521B\u5EFA\uFF1A${formatDate(item.createdAt)} \u6700\u540E\u4F7F\u7528\uFF1A${formatDate(item.lastUsedAt)}`);
|
|
1332
|
+
console.log(
|
|
1333
|
+
` \u5230\u671F\uFF1A${formatDate(item.expiresAt)} \u7A7A\u95F2\u5931\u6548\uFF1A${item.maxIdleDays ? `${item.maxIdleDays} \u5929` : "-"}`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
async function runTokenCreate(ctx2, argv2) {
|
|
1339
|
+
const name = readTokenName(argv2);
|
|
1340
|
+
if (!name) {
|
|
1341
|
+
console.error("Usage: ai-todo token create --name <text> [--expires-at <iso>] [--max-idle-days <days>]");
|
|
1342
|
+
process.exitCode = 1;
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
let maxIdleDays;
|
|
1346
|
+
try {
|
|
1347
|
+
maxIdleDays = readMaxIdleDays(argv2);
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
console.error(error instanceof Error ? error.message : "Invalid --max-idle-days.");
|
|
1350
|
+
process.exitCode = 1;
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const input = {
|
|
1354
|
+
name,
|
|
1355
|
+
scopes: readScopes(argv2),
|
|
1356
|
+
expiresAt: readFlagValue(argv2, "--expires-at"),
|
|
1357
|
+
maxIdleDays
|
|
1358
|
+
};
|
|
1359
|
+
await handleApi(ctx2, await ctx2.client.createApiToken(input), (data) => {
|
|
1360
|
+
if (ctx2.json) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
console.log("\u5DF2\u521B\u5EFA\u8BBF\u95EE\u4EE4\u724C\uFF08\u4EC5\u663E\u793A\u4E00\u6B21\uFF0C\u8BF7\u59A5\u5584\u4FDD\u7BA1\uFF09\uFF1A");
|
|
1364
|
+
console.log("");
|
|
1365
|
+
console.log(data.token);
|
|
1366
|
+
console.log("");
|
|
1367
|
+
console.log(`ID\uFF1A${data.id}`);
|
|
1368
|
+
console.log(`\u540D\u79F0\uFF1A${data.name}`);
|
|
1369
|
+
console.log(`\u5230\u671F\uFF1A${data.expiresAt ?? "-"}`);
|
|
1370
|
+
console.log(`\u7A7A\u95F2\u5931\u6548\uFF1A${data.maxIdleDays ? `${data.maxIdleDays} \u5929` : "-"}`);
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
async function runTokenRevoke(ctx2, argv2) {
|
|
1374
|
+
const tokenId = argv2[2];
|
|
1375
|
+
if (!tokenId) {
|
|
1376
|
+
console.error("Usage: ai-todo token revoke <token_id>");
|
|
1377
|
+
process.exitCode = 1;
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
await handleApi(ctx2, await ctx2.client.revokeApiToken(tokenId), (data) => {
|
|
1381
|
+
if (!ctx2.json) {
|
|
1382
|
+
console.log(`\u5DF2\u540A\u9500\u8BBF\u95EE\u4EE4\u724C\uFF1A${data.id}`);
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
async function runTokenRevokeAll(ctx2) {
|
|
1387
|
+
await handleApi(ctx2, await ctx2.client.revokeAllApiTokens(), (data) => {
|
|
1388
|
+
if (!ctx2.json) {
|
|
1389
|
+
console.log(`\u5DF2\u540A\u9500 ${data.revokedCount} \u4E2A\u8BBF\u95EE\u4EE4\u724C\u3002`);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/index.ts
|
|
1395
|
+
var argv = process.argv.slice(2);
|
|
1396
|
+
var command = argv[0] ?? "help";
|
|
1397
|
+
var sub = argv[1];
|
|
1398
|
+
var ctx = buildContext(argv);
|
|
1399
|
+
async function main() {
|
|
1400
|
+
switch (command) {
|
|
1401
|
+
case "help":
|
|
1402
|
+
case "--help":
|
|
1403
|
+
case "-h":
|
|
1404
|
+
printHelp();
|
|
1405
|
+
break;
|
|
1406
|
+
case "login":
|
|
1407
|
+
await runLogin(ctx, argv);
|
|
1408
|
+
break;
|
|
1409
|
+
case "version":
|
|
1410
|
+
await runVersion(ctx);
|
|
1411
|
+
break;
|
|
1412
|
+
case "whoami":
|
|
1413
|
+
await runWhoami(ctx);
|
|
1414
|
+
break;
|
|
1415
|
+
case "profile": {
|
|
1416
|
+
const action = sub ?? "help";
|
|
1417
|
+
if (action === "update") {
|
|
1418
|
+
await runProfileUpdate(ctx, argv);
|
|
1419
|
+
} else {
|
|
1420
|
+
console.error("Usage: ai-todo profile update --name <text> [--avatar-url <url>]");
|
|
1421
|
+
process.exitCode = 1;
|
|
1422
|
+
}
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
case "logout":
|
|
1426
|
+
await runLogout(ctx);
|
|
1427
|
+
break;
|
|
1428
|
+
case "token": {
|
|
1429
|
+
const action = sub ?? "help";
|
|
1430
|
+
if (action === "list") {
|
|
1431
|
+
await runTokenList(ctx);
|
|
1432
|
+
} else if (action === "create") {
|
|
1433
|
+
await runTokenCreate(ctx, argv);
|
|
1434
|
+
} else if (action === "revoke") {
|
|
1435
|
+
await runTokenRevoke(ctx, argv);
|
|
1436
|
+
} else if (action === "revoke-all") {
|
|
1437
|
+
await runTokenRevokeAll(ctx);
|
|
1438
|
+
} else {
|
|
1439
|
+
console.error("Usage: ai-todo token <list|create|revoke|revoke-all>");
|
|
1440
|
+
process.exitCode = 1;
|
|
1441
|
+
}
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
case "today":
|
|
1445
|
+
await runToday(ctx);
|
|
1446
|
+
break;
|
|
1447
|
+
case "add":
|
|
1448
|
+
await runReminderCreate(ctx, argv);
|
|
1449
|
+
break;
|
|
1450
|
+
case "list":
|
|
1451
|
+
await runReminderList(ctx, argv);
|
|
1452
|
+
break;
|
|
1453
|
+
case "done":
|
|
1454
|
+
await runReminderDone(ctx, argv);
|
|
1455
|
+
break;
|
|
1456
|
+
case "reschedule":
|
|
1457
|
+
await runReminderReschedule(ctx, argv);
|
|
1458
|
+
break;
|
|
1459
|
+
case "reminder": {
|
|
1460
|
+
const action = sub ?? "help";
|
|
1461
|
+
if (action === "create") {
|
|
1462
|
+
await runReminderCreate(ctx, argv);
|
|
1463
|
+
} else if (action === "list") {
|
|
1464
|
+
await runReminderList(ctx, argv);
|
|
1465
|
+
} else if (action === "show") {
|
|
1466
|
+
await runReminderShow(ctx, argv);
|
|
1467
|
+
} else if (action === "done" || action === "complete") {
|
|
1468
|
+
await runReminderDone(ctx, argv);
|
|
1469
|
+
} else if (action === "update") {
|
|
1470
|
+
await runReminderUpdate(ctx, argv);
|
|
1471
|
+
} else if (action === "reschedule") {
|
|
1472
|
+
await runReminderReschedule(ctx, argv);
|
|
1473
|
+
} else if (action === "delete") {
|
|
1474
|
+
await runReminderDelete(ctx, argv);
|
|
1475
|
+
} else {
|
|
1476
|
+
console.error("Usage: ai-todo reminder <create|list|show|done|update|reschedule|delete>");
|
|
1477
|
+
process.exitCode = 1;
|
|
1478
|
+
}
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
case "calendar": {
|
|
1482
|
+
const action = sub ?? "help";
|
|
1483
|
+
if (action === "today") {
|
|
1484
|
+
await runCalendarToday(ctx);
|
|
1485
|
+
} else if (action === "list") {
|
|
1486
|
+
await runCalendarList(ctx, argv);
|
|
1487
|
+
} else if (action === "add") {
|
|
1488
|
+
await runCalendarAdd(ctx, argv);
|
|
1489
|
+
} else if (action === "show") {
|
|
1490
|
+
await runCalendarShow(ctx, argv);
|
|
1491
|
+
} else if (action === "delete") {
|
|
1492
|
+
await runCalendarDelete(ctx, argv);
|
|
1493
|
+
} else if (action === "update") {
|
|
1494
|
+
await runCalendarUpdate(ctx, argv);
|
|
1495
|
+
} else {
|
|
1496
|
+
console.error("Usage: ai-todo calendar <today|list|add|show|update|delete>");
|
|
1497
|
+
process.exitCode = 1;
|
|
1498
|
+
}
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
case "contact": {
|
|
1502
|
+
const action = sub ?? "help";
|
|
1503
|
+
if (action === "add") {
|
|
1504
|
+
await runContactAdd(ctx, argv);
|
|
1505
|
+
} else if (action === "list") {
|
|
1506
|
+
await runContactList(ctx);
|
|
1507
|
+
} else if (action === "search") {
|
|
1508
|
+
await runContactSearch(ctx, argv);
|
|
1509
|
+
} else if (action === "show") {
|
|
1510
|
+
await runContactShow(ctx, argv);
|
|
1511
|
+
} else if (action === "update") {
|
|
1512
|
+
await runContactUpdate(ctx, argv);
|
|
1513
|
+
} else if (action === "delete") {
|
|
1514
|
+
await runContactDelete(ctx, argv);
|
|
1515
|
+
} else {
|
|
1516
|
+
console.error("Usage: ai-todo contact <add|list|search|show|update|delete>");
|
|
1517
|
+
process.exitCode = 1;
|
|
1518
|
+
}
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
default:
|
|
1522
|
+
console.error(`Unknown command: ${command}`);
|
|
1523
|
+
printHelp();
|
|
1524
|
+
process.exitCode = 1;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
main().catch((error) => {
|
|
1528
|
+
console.error(error instanceof Error ? error.message : "Unexpected CLI error.");
|
|
1529
|
+
process.exitCode = 1;
|
|
1530
|
+
});
|
|
1531
|
+
//# sourceMappingURL=index.js.map
|