autonag 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.
package/dist/cli.js ADDED
@@ -0,0 +1,1168 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli.ts
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
7
+ import JSON5 from "json5";
8
+
9
+ // apiClient.ts
10
+ var ApiError = class extends Error {
11
+ constructor(status, message) {
12
+ super(message);
13
+ this.status = status;
14
+ this.name = "ApiError";
15
+ }
16
+ status;
17
+ };
18
+ function createApiClient(baseUrl, defaultToken) {
19
+ async function apiFetch(path, options = {}, token) {
20
+ const tok = token ?? defaultToken;
21
+ const headers = {
22
+ "Content-Type": "application/json",
23
+ ...options.headers
24
+ };
25
+ if (tok) headers["Authorization"] = `Bearer ${tok}`;
26
+ const res = await fetch(`${baseUrl}${path}`, { ...options, headers });
27
+ if (!res.ok) {
28
+ let message = `HTTP ${res.status}`;
29
+ try {
30
+ const body = await res.json();
31
+ if (body.error) message = body.error;
32
+ } catch {
33
+ }
34
+ throw new ApiError(res.status, message);
35
+ }
36
+ return res.json();
37
+ }
38
+ return {
39
+ registerDevice: (nickname) => apiFetch(
40
+ "/devices/request",
41
+ { method: "POST", body: JSON.stringify({ nickname }) }
42
+ ),
43
+ getMe: (token) => apiFetch(
44
+ "/devices/me",
45
+ {},
46
+ token
47
+ ),
48
+ requestInstance: (nickname, token) => apiFetch(
49
+ "/instances/request",
50
+ { method: "POST", body: JSON.stringify({ nickname }) },
51
+ token
52
+ ),
53
+ requestInstanceAccess: (instanceId, token, inviteCode) => apiFetch(
54
+ `/instances/${instanceId}/access/request`,
55
+ { method: "POST", body: JSON.stringify(inviteCode ? { inviteCode } : {}) },
56
+ token
57
+ ),
58
+ getInvite: (code) => apiFetch(
59
+ `/invites/${code}`
60
+ ),
61
+ createInvite: (instanceId, token) => apiFetch(
62
+ `/instances/${instanceId}/invites`,
63
+ { method: "POST", body: JSON.stringify({}) },
64
+ token
65
+ ),
66
+ listInstances: (token) => apiFetch(
67
+ "/instances",
68
+ {},
69
+ token
70
+ ),
71
+ addTimer: (instanceId, token, name, expiry, recurrence, description) => apiFetch(
72
+ `/instances/${instanceId}/timers`,
73
+ { method: "POST", body: JSON.stringify({ name, expiry, ...description ? { description } : {}, ...recurrence ? { recurrence } : {} }) },
74
+ token
75
+ ),
76
+ renameTimer: (instanceId, token, timerId, name) => apiFetch(
77
+ `/instances/${instanceId}/timers/${timerId}/name`,
78
+ { method: "PATCH", body: JSON.stringify({ name }) },
79
+ token
80
+ ),
81
+ updateExpiry: (instanceId, token, timerId, expiry) => apiFetch(
82
+ `/instances/${instanceId}/timers/${timerId}/expiry`,
83
+ { method: "PATCH", body: JSON.stringify({ expiry }) },
84
+ token
85
+ ),
86
+ completeTimer: (instanceId, token, timerId, closeReason) => apiFetch(
87
+ `/instances/${instanceId}/timers/${timerId}/complete`,
88
+ { method: "POST", body: JSON.stringify({ closeReason }) },
89
+ token
90
+ ),
91
+ removeTimer: (instanceId, token, timerId) => apiFetch(
92
+ `/instances/${instanceId}/timers/${timerId}`,
93
+ { method: "DELETE" },
94
+ token
95
+ ),
96
+ setShush: (instanceId, token, expiry) => apiFetch(
97
+ `/instances/${instanceId}/shush`,
98
+ { method: "POST", body: JSON.stringify({ expiry }) },
99
+ token
100
+ ),
101
+ clearShush: (instanceId, token) => apiFetch(
102
+ `/instances/${instanceId}/shush`,
103
+ { method: "DELETE" },
104
+ token
105
+ ),
106
+ listEvents: (instanceId, token, opts = {}) => {
107
+ const { from = 1, before, limit = 200 } = opts;
108
+ const params = before !== void 0 ? `before=${before}&limit=${limit}` : `from=${from}&limit=${limit}`;
109
+ return apiFetch(
110
+ `/instances/${instanceId}/events?${params}`,
111
+ {},
112
+ token
113
+ );
114
+ },
115
+ getTimerHistory: (instanceId, token, timerId, opts = {}) => {
116
+ const params = new URLSearchParams();
117
+ if (opts.after !== void 0) params.set("after", String(opts.after));
118
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
119
+ if (opts.from !== void 0) params.set("from", String(opts.from));
120
+ if (opts.to !== void 0) params.set("to", String(opts.to));
121
+ const qs = params.toString();
122
+ return apiFetch(
123
+ `/instances/${instanceId}/timers/${timerId}/history${qs ? `?${qs}` : ""}`,
124
+ {},
125
+ token
126
+ );
127
+ },
128
+ skipEvent: (instanceId, token, eventId) => apiFetch(
129
+ `/instances/${instanceId}/events/${eventId}/skip`,
130
+ { method: "POST" },
131
+ token
132
+ ),
133
+ unskipEvent: (instanceId, token, eventId) => apiFetch(
134
+ `/instances/${instanceId}/events/${eventId}/skip`,
135
+ { method: "DELETE" },
136
+ token
137
+ ),
138
+ listComments: (instanceId, token, threadId) => apiFetch(
139
+ `/instances/${instanceId}/threads/${threadId}/comments`,
140
+ {},
141
+ token
142
+ ),
143
+ addComment: (instanceId, token, threadId, text) => apiFetch(
144
+ `/instances/${instanceId}/threads/${threadId}/comments`,
145
+ { method: "POST", body: JSON.stringify({ text }) },
146
+ token
147
+ ),
148
+ getState: (instanceId, token) => apiFetch(
149
+ `/instances/${instanceId}/state`,
150
+ {},
151
+ token
152
+ ),
153
+ batchActions: (instanceId, token, actions) => apiFetch(
154
+ `/instances/${instanceId}/actions`,
155
+ { method: "POST", body: JSON.stringify({ actions }) },
156
+ token
157
+ )
158
+ };
159
+ }
160
+
161
+ // cli.ts
162
+ var CONFIG_PATH = join(homedir(), ".config", "autonag", "client.json5");
163
+ function loadConfig() {
164
+ if (!existsSync(CONFIG_PATH)) return { server: "http://localhost:3000" };
165
+ let raw;
166
+ try {
167
+ raw = readFileSync(CONFIG_PATH, "utf8");
168
+ } catch (e) {
169
+ console.error(`Cannot read config file ${CONFIG_PATH}: ${e}`);
170
+ process.exit(1);
171
+ }
172
+ try {
173
+ return JSON5.parse(raw);
174
+ } catch (e) {
175
+ console.error(`Config file ${CONFIG_PATH} is not valid JSON5: ${e}`);
176
+ process.exit(1);
177
+ }
178
+ }
179
+ function saveConfig(config) {
180
+ mkdirSync(join(CONFIG_PATH, ".."), { recursive: true });
181
+ writeFileSync(CONFIG_PATH, JSON5.stringify(config, null, 2));
182
+ }
183
+ function parseExpiry(input) {
184
+ if (input.startsWith("unix-ms:")) {
185
+ const n = Number(input.slice(8));
186
+ if (!Number.isInteger(n) || n <= 0) {
187
+ console.error(`Invalid expiry "${input}". Use unix-ms:<positive-integer>`);
188
+ process.exit(1);
189
+ }
190
+ return n;
191
+ }
192
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/.test(input)) {
193
+ const ms = Date.parse(input);
194
+ if (isNaN(ms)) {
195
+ console.error(`Invalid datetime "${input}"`);
196
+ process.exit(1);
197
+ }
198
+ return ms;
199
+ }
200
+ const match = input.match(/^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);
201
+ if (match && (match[1] || match[2] || match[3] || match[4])) {
202
+ const d = parseInt(match[1] ?? "0");
203
+ const h = parseInt(match[2] ?? "0");
204
+ const m = parseInt(match[3] ?? "0");
205
+ const s = parseInt(match[4] ?? "0");
206
+ return Date.now() + (d * 86400 + h * 3600 + m * 60 + s) * 1e3;
207
+ }
208
+ console.error(`Cannot parse expiry "${input}". Use: 1d, 30m, 2h, 90s (relative duration), 2026-05-01T09:00:00Z (ISO 8601 with timezone), or unix-ms:<n>`);
209
+ process.exit(1);
210
+ }
211
+ function fmtCountdown(ms) {
212
+ const sign = ms < 0 ? "-" : "";
213
+ const abs = Math.abs(ms);
214
+ const totalSec = Math.floor(abs / 1e3);
215
+ const d = Math.floor(totalSec / 86400);
216
+ const h = Math.floor(totalSec % 86400 / 3600);
217
+ const m = Math.floor(totalSec % 3600 / 60);
218
+ const s = totalSec % 60;
219
+ if (d > 0) return `${sign}${d}d ${h}h`;
220
+ if (h > 0) return `${sign}${h}h ${m}m`;
221
+ if (m > 0) return `${sign}${m}m ${s}s`;
222
+ return `${sign}${s}s`;
223
+ }
224
+ function fmtDate(ms) {
225
+ return new Date(ms).toLocaleString();
226
+ }
227
+ function shortId(id) {
228
+ return id.slice(0, 8);
229
+ }
230
+ function col(s, w) {
231
+ return s.slice(0, w).padEnd(w);
232
+ }
233
+ async function resolveTimerId(prefix, cfg2) {
234
+ if (/^[0-9a-f-]{36}$/.test(prefix)) return prefix;
235
+ const api = createApiClient(cfg2.server, cfg2.token);
236
+ const state = await api.getState(cfg2.instanceId, cfg2.token);
237
+ const matches = state.timers.filter((t) => t.id.startsWith(prefix));
238
+ if (matches.length === 0) {
239
+ console.error(`Timer not found: ${prefix}`);
240
+ process.exit(1);
241
+ }
242
+ if (matches.length > 1) {
243
+ console.error(`Ambiguous prefix "${prefix}" matches: ${matches.map((t) => shortId(t.id) + "\u2026").join(", ")}`);
244
+ process.exit(1);
245
+ }
246
+ return matches[0].id;
247
+ }
248
+ async function resolveEventId(prefix, cfg2) {
249
+ if (/^[0-9a-f-]{36}$/.test(prefix)) return prefix;
250
+ const api = createApiClient(cfg2.server, cfg2.token);
251
+ const allIds = [];
252
+ let from = 1;
253
+ while (true) {
254
+ const { events, latestSeq } = await api.listEvents(cfg2.instanceId, cfg2.token, { from, limit: 200 });
255
+ allIds.push(...events.map((e) => e.id));
256
+ if (events.length < 200 || events[events.length - 1].seq >= latestSeq) break;
257
+ from = events[events.length - 1].seq + 1;
258
+ }
259
+ const matches = allIds.filter((id) => id.startsWith(prefix));
260
+ if (matches.length === 0) {
261
+ console.error(`Event not found: ${prefix}`);
262
+ process.exit(1);
263
+ }
264
+ if (matches.length > 1) {
265
+ console.error(`Ambiguous prefix "${prefix}" matches: ${matches.map((id) => shortId(id) + "\u2026").join(", ")}`);
266
+ process.exit(1);
267
+ }
268
+ return matches[0];
269
+ }
270
+ async function resolveInstanceId(prefix, cfg2) {
271
+ if (/^[0-9a-f-]{36}$/.test(prefix)) return prefix;
272
+ const api = createApiClient(cfg2.server, cfg2.token);
273
+ const { instances } = await api.listInstances(cfg2.token);
274
+ const matches = instances.filter((i) => i.id.startsWith(prefix));
275
+ if (matches.length === 0) {
276
+ console.error(`Instance not found: ${prefix}`);
277
+ process.exit(1);
278
+ }
279
+ if (matches.length > 1) {
280
+ console.error(`Ambiguous prefix "${prefix}" matches: ${matches.map((i) => `"${i.nickname}" (${shortId(i.id)}\u2026)`).join(", ")}`);
281
+ process.exit(1);
282
+ }
283
+ return matches[0].id;
284
+ }
285
+ function requireToken(cfg2) {
286
+ if (!cfg2.token) {
287
+ console.error("Not registered. Run: autonag register <nickname>");
288
+ process.exit(1);
289
+ }
290
+ }
291
+ function requireInstance(cfg2) {
292
+ if (!cfg2.instanceId) {
293
+ console.error("No instance selected. Run: autonag instances select <id>");
294
+ process.exit(1);
295
+ }
296
+ }
297
+ async function cmdRegister(nickname, cfg2) {
298
+ const api = createApiClient(cfg2.server);
299
+ const { device, token } = await api.registerDevice(nickname);
300
+ cfg2.token = token;
301
+ cfg2.deviceId = device.id;
302
+ saveConfig(cfg2);
303
+ console.log(`Registered as "${nickname}" (${shortId(device.id)}\u2026)`);
304
+ console.log(`Token saved to ${CONFIG_PATH}`);
305
+ console.log(`Waiting for admin approval \u2014 run "autonag status" to check.`);
306
+ }
307
+ async function cmdStatus(cfg2) {
308
+ console.log(`Server: ${cfg2.server}`);
309
+ console.log(`Config: ${CONFIG_PATH}`);
310
+ if (!cfg2.token) {
311
+ console.log(`Auth: not registered (run: autonag register <nickname>)`);
312
+ return;
313
+ }
314
+ const api = createApiClient(cfg2.server, cfg2.token);
315
+ try {
316
+ const { device: d } = await api.getMe(cfg2.token);
317
+ const status = d.approvedAt ? `approved ${fmtDate(d.approvedAt)}` : "PENDING APPROVAL";
318
+ console.log(`Auth: ${d.nickname} (${shortId(d.id)}\u2026) [${status}]`);
319
+ } catch (e) {
320
+ if (e instanceof ApiError && e.status === 401) {
321
+ console.log(`Auth: token invalid or expired`);
322
+ } else throw e;
323
+ }
324
+ console.log(`Instance: ${cfg2.instanceId ?? "none selected (run: autonag instances select <id>)"}`);
325
+ }
326
+ async function cmdInstancesList(cfg2) {
327
+ requireToken(cfg2);
328
+ const api = createApiClient(cfg2.server, cfg2.token);
329
+ const { instances } = await api.listInstances(cfg2.token);
330
+ if (instances.length === 0) {
331
+ console.log("No accessible instances.");
332
+ return;
333
+ }
334
+ for (const i of instances) {
335
+ const status = i.approvedAt ? "active" : "PENDING";
336
+ const marker = i.id === cfg2.instanceId ? " *" : " ";
337
+ console.log(`${marker} ${shortId(i.id)}\u2026 "${i.nickname}" [${status}]`);
338
+ }
339
+ console.log(`
340
+ * = current default`);
341
+ }
342
+ async function cmdInstancesRequest(nickname, cfg2) {
343
+ requireToken(cfg2);
344
+ const api = createApiClient(cfg2.server, cfg2.token);
345
+ const { instance } = await api.requestInstance(nickname, cfg2.token);
346
+ console.log(`Requested instance "${instance.nickname}" (${shortId(instance.id)}\u2026)`);
347
+ console.log(`Waiting for admin approval.`);
348
+ }
349
+ async function cmdInstancesSelect(id, cfg2) {
350
+ requireToken(cfg2);
351
+ const instanceId = await resolveInstanceId(id, cfg2);
352
+ cfg2.instanceId = instanceId;
353
+ saveConfig(cfg2);
354
+ console.log(`Default instance set to ${instanceId}`);
355
+ }
356
+ async function cmdInstancesJoin(code, cfg2) {
357
+ requireToken(cfg2);
358
+ const api = createApiClient(cfg2.server, cfg2.token);
359
+ const { invite } = await api.getInvite(code);
360
+ if (!invite.valid) {
361
+ console.error(`Invite is not valid: ${invite.reason ?? "expired or already used"}`);
362
+ process.exit(1);
363
+ }
364
+ await api.requestInstanceAccess(invite.instanceId, cfg2.token, code);
365
+ console.log(`Joined instance "${invite.instanceNickname}" (${shortId(invite.instanceId)}\u2026)`);
366
+ console.log(`Run: autonag instances select ${invite.instanceId}`);
367
+ }
368
+ async function cmdTimersList(cfg2) {
369
+ requireToken(cfg2);
370
+ requireInstance(cfg2);
371
+ const api = createApiClient(cfg2.server, cfg2.token);
372
+ const state = await api.getState(cfg2.instanceId, cfg2.token);
373
+ const now = Date.now();
374
+ const timers = [...state.timers].sort((a, b) => a.currentExpiry - b.currentExpiry);
375
+ if (timers.length === 0) {
376
+ console.log("No active timers.");
377
+ } else {
378
+ console.log(`${"ID".padEnd(10)} ${"Name".padEnd(24)} ${"Remaining".padEnd(16)} Recur`);
379
+ console.log("-".repeat(60));
380
+ for (const t of timers) {
381
+ const remaining = t.currentExpiry - now;
382
+ const label = remaining < 0 ? `OVERDUE ${fmtCountdown(remaining)}` : `in ${fmtCountdown(remaining)}`;
383
+ const recur = t.recurrence ? "\u21BB" : "";
384
+ console.log(`${col(shortId(t.id) + "\u2026", 10)} ${col(t.name, 24)} ${col(label, 16)} ${recur}`);
385
+ }
386
+ }
387
+ console.log();
388
+ printAlarmStatus(state, now);
389
+ }
390
+ function printAlarmStatus(state, now) {
391
+ const { alarm, shush } = state;
392
+ const shushed = !!(shush && shush.expiry > now);
393
+ const colorLabel = {
394
+ blue: "off",
395
+ green: "green",
396
+ yellow: "yellow",
397
+ red: "red",
398
+ flashing: "FLASHING"
399
+ };
400
+ if (alarm.startsAt !== null) {
401
+ const diff = alarm.startsAt - now;
402
+ const colorStr = colorLabel[alarm.color] ?? alarm.color;
403
+ const timeStr = diff <= 0 ? `overdue ${fmtCountdown(diff)}` : `in ${fmtCountdown(diff)}`;
404
+ console.log(`[${colorStr}] "${alarm.urgentName}" \u2014 ${timeStr}`);
405
+ }
406
+ if (alarm.on) {
407
+ console.log(`Alarm: ringing.`);
408
+ } else if (shushed) {
409
+ const overdue = alarm.startsAt !== null && alarm.startsAt <= now;
410
+ const shushEndsAfterTimer = alarm.startsAt !== null && shush.expiry >= alarm.startsAt;
411
+ const necessary = overdue || shushEndsAfterTimer;
412
+ if (necessary) {
413
+ const ringIn = fmtCountdown(shush.expiry - now);
414
+ console.log(`Alarm: off. Will ring in ${ringIn}: Shush expires: ${alarm.urgentName}.`);
415
+ } else if (alarm.startsAt !== null) {
416
+ const ringIn = fmtCountdown(alarm.startsAt - now);
417
+ const remaining = fmtCountdown(shush.expiry - now);
418
+ console.log(`Alarm: off. Will ring in ${ringIn}: ${alarm.urgentName}. (Shushed unnecessarily for ${remaining}.)`);
419
+ } else {
420
+ const remaining = fmtCountdown(shush.expiry - now);
421
+ console.log(`Alarm: off. Shushed (unnecessarily) for ${remaining}.`);
422
+ }
423
+ } else if (alarm.startsAt !== null) {
424
+ const ringIn = fmtCountdown(alarm.startsAt - now);
425
+ console.log(`Alarm: off. Will ring in ${ringIn}: ${alarm.urgentName}.`);
426
+ } else {
427
+ console.log(`Alarm: off.`);
428
+ }
429
+ }
430
+ async function cmdAlarm(cfg2) {
431
+ requireToken(cfg2);
432
+ requireInstance(cfg2);
433
+ const api = createApiClient(cfg2.server, cfg2.token);
434
+ const state = await api.getState(cfg2.instanceId, cfg2.token);
435
+ printAlarmStatus(state, Date.now());
436
+ }
437
+ async function cmdTimersAdd(name, expiryStr, cfg2, opts) {
438
+ requireToken(cfg2);
439
+ requireInstance(cfg2);
440
+ const api = createApiClient(cfg2.server, cfg2.token);
441
+ const expiry = parseExpiry(expiryStr);
442
+ const recurrence = opts.days !== void 0 ? {
443
+ periodDays: opts.days,
444
+ anchorTime: opts.anchor ?? "09:00",
445
+ timezone: opts.tz ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
446
+ skipWeekends: opts.skipWeekends ?? false
447
+ } : void 0;
448
+ await api.addTimer(cfg2.instanceId, cfg2.token, name, expiry, recurrence, opts.desc);
449
+ const recurNote = recurrence ? ` (recurs every ${opts.days}d)` : "";
450
+ console.log(`Timer "${name}" added, expires ${fmtDate(expiry)}${recurNote}`);
451
+ }
452
+ async function cmdTimersRename(id, name, cfg2) {
453
+ requireToken(cfg2);
454
+ requireInstance(cfg2);
455
+ const timerId = await resolveTimerId(id, cfg2);
456
+ const api = createApiClient(cfg2.server, cfg2.token);
457
+ await api.renameTimer(cfg2.instanceId, cfg2.token, timerId, name);
458
+ console.log(`Timer ${shortId(timerId)}\u2026 renamed to "${name}"`);
459
+ }
460
+ async function cmdTimersSnooze(id, expiryStr, cfg2) {
461
+ requireToken(cfg2);
462
+ requireInstance(cfg2);
463
+ const timerId = await resolveTimerId(id, cfg2);
464
+ const api = createApiClient(cfg2.server, cfg2.token);
465
+ const expiry = parseExpiry(expiryStr);
466
+ await api.updateExpiry(cfg2.instanceId, cfg2.token, timerId, expiry);
467
+ console.log(`Timer ${shortId(timerId)}\u2026 snoozed until ${fmtDate(expiry)}`);
468
+ }
469
+ async function cmdTimersComplete(id, reason, cfg2) {
470
+ requireToken(cfg2);
471
+ requireInstance(cfg2);
472
+ const api = createApiClient(cfg2.server, cfg2.token);
473
+ const state = await api.getState(cfg2.instanceId, cfg2.token);
474
+ const timerId = await resolveTimerId(id, cfg2);
475
+ const timer = state.timers.find((t) => t.id === timerId);
476
+ if (timer?.recurrence) {
477
+ await api.batchActions(cfg2.instanceId, cfg2.token, [{ type: "recurTimer", timerId, closeReason: reason }]);
478
+ const newState = await api.getState(cfg2.instanceId, cfg2.token);
479
+ const updated = newState.timers.find((t) => t.id === timerId);
480
+ if (updated) {
481
+ console.log(`Timer "${timer.name}" recurred \u2014 next: ${fmtDate(updated.currentExpiry)}`);
482
+ } else {
483
+ console.log(`Timer ${shortId(timerId)}\u2026 recurred`);
484
+ }
485
+ } else {
486
+ await api.completeTimer(cfg2.instanceId, cfg2.token, timerId, reason);
487
+ console.log(`Timer ${shortId(timerId)}\u2026 completed (${reason})`);
488
+ }
489
+ }
490
+ async function cmdTimersRemove(id, cfg2) {
491
+ requireToken(cfg2);
492
+ requireInstance(cfg2);
493
+ const timerId = await resolveTimerId(id, cfg2);
494
+ const api = createApiClient(cfg2.server, cfg2.token);
495
+ await api.removeTimer(cfg2.instanceId, cfg2.token, timerId);
496
+ console.log(`Timer ${shortId(timerId)}\u2026 removed`);
497
+ }
498
+ async function cmdTimersDescribe(id, text, cfg2) {
499
+ requireToken(cfg2);
500
+ requireInstance(cfg2);
501
+ const timerId = await resolveTimerId(id, cfg2);
502
+ const api = createApiClient(cfg2.server, cfg2.token);
503
+ await api.batchActions(cfg2.instanceId, cfg2.token, [{ type: "updateDescription", timerId, description: text }]);
504
+ console.log(`Timer ${shortId(timerId)}\u2026 description updated`);
505
+ }
506
+ async function cmdTimersRecurrenceSet(id, days, cfg2, opts) {
507
+ requireToken(cfg2);
508
+ requireInstance(cfg2);
509
+ const timerId = await resolveTimerId(id, cfg2);
510
+ const api = createApiClient(cfg2.server, cfg2.token);
511
+ const recurrence = {
512
+ periodDays: days,
513
+ anchorTime: opts.anchor ?? "09:00",
514
+ timezone: opts.tz ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
515
+ skipWeekends: opts.skipWeekends ?? false
516
+ };
517
+ await api.batchActions(cfg2.instanceId, cfg2.token, [{ type: "updateRecurrence", timerId, recurrence }]);
518
+ console.log(`Timer ${shortId(timerId)}\u2026 recurrence set to every ${days}d at ${recurrence.anchorTime} (${recurrence.timezone})`);
519
+ }
520
+ async function cmdTimersRecurrenceRemove(id, cfg2) {
521
+ requireToken(cfg2);
522
+ requireInstance(cfg2);
523
+ const timerId = await resolveTimerId(id, cfg2);
524
+ const api = createApiClient(cfg2.server, cfg2.token);
525
+ await api.batchActions(cfg2.instanceId, cfg2.token, [{ type: "removeRecurrence", timerId }]);
526
+ console.log(`Timer ${shortId(timerId)}\u2026 recurrence removed`);
527
+ }
528
+ async function cmdTimersHistory(id, cfg2) {
529
+ requireToken(cfg2);
530
+ requireInstance(cfg2);
531
+ const timerId = await resolveTimerId(id, cfg2);
532
+ const api = createApiClient(cfg2.server, cfg2.token);
533
+ const allEntries = [];
534
+ let cursor = 0;
535
+ while (cursor !== null) {
536
+ const { events, nextCursor } = await api.getTimerHistory(cfg2.instanceId, cfg2.token, timerId, { after: cursor });
537
+ for (const ev of events) {
538
+ const p = ev.payload;
539
+ const date = ev.type === "TimerRecurred" ? p["occurredAt"] ?? ev.timestamp : ev.timestamp;
540
+ allEntries.push({ date, closeReason: p["closeReason"] });
541
+ }
542
+ cursor = nextCursor;
543
+ }
544
+ if (allEntries.length === 0) {
545
+ console.log("No history yet.");
546
+ return;
547
+ }
548
+ console.log(`${"Date".padEnd(22)} Reason`);
549
+ console.log("-".repeat(36));
550
+ for (const e of allEntries) {
551
+ console.log(`${col(fmtDate(e.date), 22)} ${e.closeReason}`);
552
+ }
553
+ }
554
+ async function cmdShush(durationStr, cfg2) {
555
+ requireToken(cfg2);
556
+ requireInstance(cfg2);
557
+ const api = createApiClient(cfg2.server, cfg2.token);
558
+ const expiry = durationStr ? parseExpiry(durationStr) : Date.now() + 30 * 60 * 1e3;
559
+ await api.setShush(cfg2.instanceId, cfg2.token, expiry);
560
+ console.log(`Alarm silenced until ${fmtDate(expiry)}`);
561
+ }
562
+ async function cmdUnshush(cfg2) {
563
+ requireToken(cfg2);
564
+ requireInstance(cfg2);
565
+ const api = createApiClient(cfg2.server, cfg2.token);
566
+ await api.clearShush(cfg2.instanceId, cfg2.token);
567
+ console.log(`Shush cleared`);
568
+ }
569
+ async function cmdEventsList(fromSeq, cfg2) {
570
+ requireToken(cfg2);
571
+ requireInstance(cfg2);
572
+ const api = createApiClient(cfg2.server, cfg2.token);
573
+ const { events } = await api.listEvents(cfg2.instanceId, cfg2.token, { from: fromSeq });
574
+ if (events.length === 0) {
575
+ console.log("No events.");
576
+ return;
577
+ }
578
+ console.log(`${"seq".padEnd(6)} ${"ID".padEnd(10)} ${"Type".padEnd(28)} ${"Time".padEnd(20)} Skipped`);
579
+ console.log("-".repeat(78));
580
+ for (const e of events) {
581
+ const skipped = e.skipped ? "yes" : "";
582
+ console.log(`${String(e.seq).padEnd(6)} ${col(shortId(e.id) + "\u2026", 10)} ${col(e.type, 28)} ${col(fmtDate(e.timestamp), 20)} ${skipped}`);
583
+ }
584
+ }
585
+ async function cmdEventsSkip(id, cfg2) {
586
+ requireToken(cfg2);
587
+ requireInstance(cfg2);
588
+ const eventId = await resolveEventId(id, cfg2);
589
+ const api = createApiClient(cfg2.server, cfg2.token);
590
+ await api.skipEvent(cfg2.instanceId, cfg2.token, eventId);
591
+ console.log(`Event ${shortId(eventId)}\u2026 skipped`);
592
+ }
593
+ async function cmdEventsUnskip(id, cfg2) {
594
+ requireToken(cfg2);
595
+ requireInstance(cfg2);
596
+ const eventId = await resolveEventId(id, cfg2);
597
+ const api = createApiClient(cfg2.server, cfg2.token);
598
+ await api.unskipEvent(cfg2.instanceId, cfg2.token, eventId);
599
+ console.log(`Event ${shortId(eventId)}\u2026 unskipped`);
600
+ }
601
+ async function cmdCommentsList(id, cfg2) {
602
+ requireToken(cfg2);
603
+ requireInstance(cfg2);
604
+ const timerId = await resolveTimerId(id, cfg2);
605
+ const api = createApiClient(cfg2.server, cfg2.token);
606
+ const { comments } = await api.listComments(cfg2.instanceId, cfg2.token, timerId);
607
+ if (comments.length === 0) {
608
+ console.log("No comments.");
609
+ return;
610
+ }
611
+ for (const c of comments) {
612
+ console.log(`[${fmtDate(c.createdAt)}] ${c.text}`);
613
+ }
614
+ }
615
+ async function cmdCommentsAdd(id, text, cfg2) {
616
+ requireToken(cfg2);
617
+ requireInstance(cfg2);
618
+ const timerId = await resolveTimerId(id, cfg2);
619
+ const api = createApiClient(cfg2.server, cfg2.token);
620
+ await api.addComment(cfg2.instanceId, cfg2.token, timerId, text);
621
+ console.log(`Comment added`);
622
+ }
623
+ function cmdTutorial() {
624
+ console.log(`# autonag tutorial
625
+
626
+ autonag is a recurring-task tracker with an alarm. Timers count down; when
627
+ they expire, your alarm client starts ringing. You mark them done \u2014 if the
628
+ timer is recurring, the next occurrence is automatically scheduled.
629
+
630
+ ## Config
631
+
632
+ Your config lives at \`~/.config/autonag/client.json5\`. It is a JSON5 file
633
+ (JSON with comments and trailing commas), so you can annotate it. After
634
+ registration it looks something like:
635
+
636
+ \`\`\`json5
637
+ {
638
+ server: 'https://your-server.example',
639
+ token: '\u2026',
640
+ deviceId: '\u2026',
641
+ instanceId: '\u2026',
642
+ }
643
+ \`\`\`
644
+
645
+ Edit it directly in a text editor whenever you need to change the server
646
+ URL or switch instances. To use a different config file:
647
+
648
+ \`\`\`
649
+ autonag --config /path/to/other.json5 timers list
650
+ \`\`\`
651
+
652
+ ## First-time setup
653
+
654
+ Register this device \u2014 give it a short nickname so the admin knows who you are:
655
+
656
+ \`\`\`
657
+ autonag register myphone
658
+ \`\`\`
659
+
660
+ Your auth token and device ID are saved to the config. The device starts in
661
+ PENDING state until an admin approves it. Check your status:
662
+
663
+ \`\`\`
664
+ autonag status
665
+ \`\`\`
666
+
667
+ ## Getting an instance
668
+
669
+ Timers live in an instance. Create a new one:
670
+
671
+ \`\`\`
672
+ autonag instances request home
673
+ \`\`\`
674
+
675
+ When the admin approves it, it shows up in your list:
676
+
677
+ \`\`\`
678
+ autonag instances list
679
+ \`\`\`
680
+
681
+ Set it as your default:
682
+
683
+ \`\`\`
684
+ autonag instances select <id>
685
+ \`\`\`
686
+
687
+ To join an instance someone else owns, ask them for an invite code. They
688
+ generate one with:
689
+
690
+ \`\`\`
691
+ autonag instances invite
692
+ \`\`\`
693
+
694
+ Then you join with the code they share:
695
+
696
+ \`\`\`
697
+ autonag instances join <code>
698
+ \`\`\`
699
+
700
+ Invite codes are single-use and expire after 7 days. The instance is not
701
+ discoverable by name \u2014 you need the code.
702
+
703
+ ## Adding timers
704
+
705
+ One-shot reminder due in 2 days:
706
+
707
+ \`\`\`
708
+ autonag timers add "Doctor follow-up" 2d
709
+ \`\`\`
710
+
711
+ Recurring task every 7 days at 9 AM:
712
+
713
+ \`\`\`
714
+ autonag timers add "Weekly review" 7d --days 7 --anchor 09:00
715
+ \`\`\`
716
+
717
+ The first argument after the name is the initial expiry. \`--days\` makes it
718
+ recurring; \`--anchor\` sets the time of day; \`--tz\` overrides the timezone.
719
+
720
+ ## Checking timers
721
+
722
+ \`\`\`
723
+ autonag timers list
724
+ \`\`\`
725
+
726
+ \`\`\`
727
+ ID Name Remaining Recur
728
+ ------------------------------------------------------------
729
+ a1b2c3d4\u2026 Doctor follow-up in 1d 23h
730
+ e5f6a7b8\u2026 Weekly review in 6d 23h \u21BB
731
+ \`\`\`
732
+
733
+ The \u21BB symbol marks recurring timers.
734
+
735
+ ## Completing a timer
736
+
737
+ Mark a timer done with a reason (pass, fail, or any custom string):
738
+
739
+ \`\`\`
740
+ autonag timers complete e5f6 pass
741
+ \`\`\`
742
+
743
+ For a one-shot timer this removes it. For a recurring timer it schedules
744
+ the next occurrence and tells you when:
745
+
746
+ \`\`\`
747
+ Timer "Weekly review" recurred \u2014 next: 4/24/2026, 9:00:00 AM
748
+ \`\`\`
749
+
750
+ To permanently stop a recurring timer, remove the recurrence first:
751
+
752
+ \`\`\`
753
+ autonag timers recurrence remove e5f6
754
+ autonag timers complete e5f6 done
755
+ \`\`\`
756
+
757
+ ## Snoozing
758
+
759
+ Push a timer's deadline out:
760
+
761
+ \`\`\`
762
+ autonag timers snooze e5f6 3d
763
+ \`\`\`
764
+
765
+ ## Silencing the alarm
766
+
767
+ Silence the alarm for 30 minutes (the default):
768
+
769
+ \`\`\`
770
+ autonag shush
771
+ \`\`\`
772
+
773
+ Or for a specific duration:
774
+
775
+ \`\`\`
776
+ autonag shush 2h
777
+ \`\`\`
778
+
779
+ Resume:
780
+
781
+ \`\`\`
782
+ autonag unshush
783
+ \`\`\`
784
+
785
+ ## History
786
+
787
+ See every time a recurring timer was completed:
788
+
789
+ \`\`\`
790
+ autonag timers history e5f6
791
+ \`\`\`
792
+
793
+ ## Notes and comments
794
+
795
+ Attach a description to a timer:
796
+
797
+ \`\`\`
798
+ autonag timers describe e5f6 "Prep the agenda before this"
799
+ \`\`\`
800
+
801
+ Add timestamped comments (good for logging what happened):
802
+
803
+ \`\`\`
804
+ autonag comments add e5f6 "Completed but flagged the follow-up"
805
+ \`\`\`
806
+
807
+ ## Event log
808
+
809
+ Every action is recorded. View the log:
810
+
811
+ \`\`\`
812
+ autonag events list
813
+ \`\`\`
814
+
815
+ You can skip events to undo them \u2014 state is recomputed as if the
816
+ skipped event never happened:
817
+
818
+ \`\`\`
819
+ autonag events skip <event-id>
820
+ autonag events unskip <event-id>
821
+ \`\`\`
822
+
823
+ ---
824
+ Run \`autonag --help\` for a full command reference.`);
825
+ }
826
+ var HELP = `autonag client CLI
827
+
828
+ Usage: autonag [options] <command> [args]
829
+
830
+ Options:
831
+ -h, --help Show this help
832
+ -s, --server <url> Server URL (default: http://localhost:3000)
833
+ -i, --instance <id> Instance ID (overrides saved default)
834
+ -c, --config <path> Config file (default: ~/.config/autonag/client.json5)
835
+
836
+ tutorial Walk through setup and daily workflow
837
+
838
+ Auth:
839
+ register <nickname> Register this device and save token
840
+ status Show auth status and saved config
841
+
842
+ Instances:
843
+ instances list
844
+ instances request <nickname> Create a new instance
845
+ instances join <code> Join an existing instance via invite code
846
+ instances invite Create an invite code for this instance
847
+ instances select <id>
848
+
849
+ Timers:
850
+ timers list
851
+ timers add <name> <expiry> [--desc <text>] [--days <n>] [--anchor <HH:MM>] [--tz <tz>] [--skip-weekends]
852
+ timers rename <id> <name>
853
+ timers snooze <id> <expiry> Update expiry: 1d, 30m, 2h, 90s | 2026-05-01T09:00:00Z | unix-ms:<n>
854
+ timers complete <id> <reason> Complete or recur (reason: pass, fail, or custom)
855
+ timers remove <id>
856
+ timers describe <id> <text> Set description
857
+ timers recurrence set <id> <days> [--anchor <HH:MM>] [--tz <tz>] [--skip-weekends]
858
+ timers recurrence remove <id>
859
+ timers history <id> Show completion history
860
+
861
+ Alarm:
862
+ alarm show Show current alarm state
863
+
864
+ Shush:
865
+ shush [duration] Silence alarm (default: 30m)
866
+ unshush Clear shush
867
+
868
+ Events:
869
+ events list [--from <seq>]
870
+ events skip <id>
871
+ events unskip <id>
872
+
873
+ Comments:
874
+ comments list <timerId>
875
+ comments add <timerId> <text>
876
+
877
+ Config saved to ~/.config/autonag/client.json5`;
878
+ function parseFlags(args, schema) {
879
+ const positional = [];
880
+ const flags = {};
881
+ for (let i = 0; i < args.length; i++) {
882
+ const a = args[i];
883
+ if (a.startsWith("--")) {
884
+ const key = a.slice(2);
885
+ if (!(key in schema)) {
886
+ console.error(`Unknown flag: --${key}`);
887
+ process.exit(1);
888
+ }
889
+ const type = schema[key];
890
+ if (type === "boolean") {
891
+ flags[key] = true;
892
+ } else {
893
+ const next = args[i + 1];
894
+ if (!next || next.startsWith("--")) {
895
+ console.error(`Flag --${key} requires a value`);
896
+ process.exit(1);
897
+ }
898
+ if (type === "int") {
899
+ const n = Number(next);
900
+ if (!Number.isInteger(n)) {
901
+ console.error(`Flag --${key} must be an integer, got: "${next}"`);
902
+ process.exit(1);
903
+ }
904
+ flags[key] = n;
905
+ } else {
906
+ flags[key] = next;
907
+ }
908
+ i++;
909
+ }
910
+ } else {
911
+ positional.push(a);
912
+ }
913
+ }
914
+ return { positional, flags };
915
+ }
916
+ var rawArgs = process.argv.slice(2);
917
+ var topArgs = [];
918
+ var serverOverride;
919
+ var instanceOverride;
920
+ for (let i = 0; i < rawArgs.length; i++) {
921
+ const a = rawArgs[i];
922
+ if (a === "-h" || a === "--help") {
923
+ console.log(HELP);
924
+ process.exit(0);
925
+ } else if (a === "-s" || a === "--server") {
926
+ if (!rawArgs[i + 1] || rawArgs[i + 1].startsWith("-")) {
927
+ console.error("Flag -s/--server requires a URL");
928
+ process.exit(1);
929
+ }
930
+ serverOverride = rawArgs[++i];
931
+ } else if (a === "-i" || a === "--instance") {
932
+ if (!rawArgs[i + 1] || rawArgs[i + 1].startsWith("-")) {
933
+ console.error("Flag -i/--instance requires an ID");
934
+ process.exit(1);
935
+ }
936
+ instanceOverride = rawArgs[++i];
937
+ } else if (a === "-c" || a === "--config") {
938
+ if (!rawArgs[i + 1] || rawArgs[i + 1].startsWith("-")) {
939
+ console.error("Flag -c/--config requires a path");
940
+ process.exit(1);
941
+ }
942
+ CONFIG_PATH = rawArgs[++i];
943
+ } else topArgs.push(a);
944
+ }
945
+ var cfg = loadConfig();
946
+ if (serverOverride) cfg.server = serverOverride;
947
+ if (instanceOverride) cfg.instanceId = instanceOverride;
948
+ var [cmd, sub, ...rest] = topArgs;
949
+ try {
950
+ if (!cmd || cmd === "help") {
951
+ parseFlags(rest, {});
952
+ console.log(HELP);
953
+ } else if (cmd === "tutorial") {
954
+ parseFlags(rest, {});
955
+ cmdTutorial();
956
+ } else if (cmd === "register") {
957
+ parseFlags(rest, {});
958
+ if (!sub) {
959
+ console.error("Usage: autonag register <nickname>");
960
+ process.exit(1);
961
+ }
962
+ await cmdRegister(sub, cfg);
963
+ } else if (cmd === "status") {
964
+ parseFlags(rest, {});
965
+ await cmdStatus(cfg);
966
+ } else if (cmd === "instances") {
967
+ if (!sub || sub === "list") {
968
+ parseFlags(rest, {});
969
+ await cmdInstancesList(cfg);
970
+ } else if (sub === "request") {
971
+ const { positional } = parseFlags(rest, {});
972
+ if (!positional[0]) {
973
+ console.error("Usage: autonag instances request <nickname>");
974
+ process.exit(1);
975
+ }
976
+ await cmdInstancesRequest(positional[0], cfg);
977
+ } else if (sub === "join") {
978
+ const { positional } = parseFlags(rest, {});
979
+ if (!positional[0]) {
980
+ console.error("Usage: autonag instances join <code>");
981
+ process.exit(1);
982
+ }
983
+ await cmdInstancesJoin(positional[0], cfg);
984
+ } else if (sub === "invite") {
985
+ parseFlags(rest, {});
986
+ requireToken(cfg);
987
+ requireInstance(cfg);
988
+ const api = createApiClient(cfg.server, cfg.token);
989
+ const { invite } = await api.createInvite(cfg.instanceId, cfg.token);
990
+ console.log(`Invite code: ${invite.code}`);
991
+ console.log(`Expires: ${fmtDate(invite.expiresAt)}`);
992
+ console.log(`Share this code \u2014 it can only be used once.`);
993
+ } else if (sub === "select") {
994
+ const { positional } = parseFlags(rest, {});
995
+ if (!positional[0]) {
996
+ console.error("Usage: autonag instances select <id>");
997
+ process.exit(1);
998
+ }
999
+ await cmdInstancesSelect(positional[0], cfg);
1000
+ } else {
1001
+ console.error(`Unknown subcommand: instances ${sub}`);
1002
+ process.exit(1);
1003
+ }
1004
+ } else if (cmd === "timers") {
1005
+ if (!sub || sub === "list") {
1006
+ parseFlags(rest, {});
1007
+ await cmdTimersList(cfg);
1008
+ } else if (sub === "add") {
1009
+ const { positional, flags } = parseFlags(rest, {
1010
+ desc: "string",
1011
+ days: "int",
1012
+ anchor: "string",
1013
+ tz: "string",
1014
+ "skip-weekends": "boolean"
1015
+ });
1016
+ if (!positional[0] || !positional[1]) {
1017
+ console.error("Usage: autonag timers add <name> <expiry> [--desc <text>] [--days <n>] [--anchor <HH:MM>] [--tz <tz>] [--skip-weekends]");
1018
+ process.exit(1);
1019
+ }
1020
+ await cmdTimersAdd(positional[0], positional[1], cfg, {
1021
+ desc: flags.desc,
1022
+ days: flags.days,
1023
+ anchor: flags.anchor,
1024
+ tz: flags.tz,
1025
+ skipWeekends: flags["skip-weekends"] === true
1026
+ });
1027
+ } else if (sub === "rename") {
1028
+ const { positional } = parseFlags(rest, {});
1029
+ if (!positional[0] || !positional[1]) {
1030
+ console.error("Usage: autonag timers rename <id> <name>");
1031
+ process.exit(1);
1032
+ }
1033
+ await cmdTimersRename(positional[0], positional[1], cfg);
1034
+ } else if (sub === "snooze") {
1035
+ const { positional } = parseFlags(rest, {});
1036
+ if (!positional[0] || !positional[1]) {
1037
+ console.error("Usage: autonag timers snooze <id> <expiry>");
1038
+ process.exit(1);
1039
+ }
1040
+ await cmdTimersSnooze(positional[0], positional[1], cfg);
1041
+ } else if (sub === "complete") {
1042
+ const { positional } = parseFlags(rest, {});
1043
+ if (!positional[0] || !positional[1]) {
1044
+ console.error("Usage: autonag timers complete <id> <reason>");
1045
+ process.exit(1);
1046
+ }
1047
+ await cmdTimersComplete(positional[0], positional[1], cfg);
1048
+ } else if (sub === "remove") {
1049
+ const { positional } = parseFlags(rest, {});
1050
+ if (!positional[0]) {
1051
+ console.error("Usage: autonag timers remove <id>");
1052
+ process.exit(1);
1053
+ }
1054
+ await cmdTimersRemove(positional[0], cfg);
1055
+ } else if (sub === "describe") {
1056
+ const { positional } = parseFlags(rest, {});
1057
+ if (!positional[0] || !positional[1]) {
1058
+ console.error("Usage: autonag timers describe <id> <text>");
1059
+ process.exit(1);
1060
+ }
1061
+ await cmdTimersDescribe(positional[0], positional[1], cfg);
1062
+ } else if (sub === "recurrence") {
1063
+ const recSub = rest[0];
1064
+ if (recSub === "set") {
1065
+ const { positional, flags } = parseFlags(rest, { anchor: "string", tz: "string", "skip-weekends": "boolean" });
1066
+ if (!positional[1] || !positional[2]) {
1067
+ console.error("Usage: autonag timers recurrence set <id> <days> [--anchor <HH:MM>] [--tz <tz>] [--skip-weekends]");
1068
+ process.exit(1);
1069
+ }
1070
+ const days = Number(positional[2]);
1071
+ if (!Number.isInteger(days) || days <= 0) {
1072
+ console.error(`<days> must be a positive integer, got: "${positional[2]}"`);
1073
+ process.exit(1);
1074
+ }
1075
+ await cmdTimersRecurrenceSet(positional[1], days, cfg, {
1076
+ anchor: flags.anchor,
1077
+ tz: flags.tz,
1078
+ skipWeekends: flags["skip-weekends"] === true
1079
+ });
1080
+ } else if (recSub === "remove") {
1081
+ const { positional } = parseFlags(rest, {});
1082
+ if (!positional[1]) {
1083
+ console.error("Usage: autonag timers recurrence remove <id>");
1084
+ process.exit(1);
1085
+ }
1086
+ await cmdTimersRecurrenceRemove(positional[1], cfg);
1087
+ } else {
1088
+ console.error("Usage: autonag timers recurrence set|remove ...");
1089
+ process.exit(1);
1090
+ }
1091
+ } else if (sub === "history") {
1092
+ const { positional } = parseFlags(rest, {});
1093
+ if (!positional[0]) {
1094
+ console.error("Usage: autonag timers history <id>");
1095
+ process.exit(1);
1096
+ }
1097
+ await cmdTimersHistory(positional[0], cfg);
1098
+ } else {
1099
+ console.error(`Unknown subcommand: timers ${sub}`);
1100
+ process.exit(1);
1101
+ }
1102
+ } else if (cmd === "alarm") {
1103
+ if (sub !== "show") {
1104
+ console.error("Usage: autonag alarm show");
1105
+ process.exit(1);
1106
+ }
1107
+ parseFlags(rest, {});
1108
+ await cmdAlarm(cfg);
1109
+ } else if (cmd === "shush") {
1110
+ parseFlags(rest, {});
1111
+ await cmdShush(sub, cfg);
1112
+ } else if (cmd === "unshush") {
1113
+ parseFlags(rest, {});
1114
+ await cmdUnshush(cfg);
1115
+ } else if (cmd === "events") {
1116
+ if (!sub || sub === "list") {
1117
+ const { flags } = parseFlags(rest, { from: "int" });
1118
+ await cmdEventsList(flags.from ?? 1, cfg);
1119
+ } else if (sub === "skip") {
1120
+ const { positional } = parseFlags(rest, {});
1121
+ if (!positional[0]) {
1122
+ console.error("Usage: autonag events skip <id>");
1123
+ process.exit(1);
1124
+ }
1125
+ await cmdEventsSkip(positional[0], cfg);
1126
+ } else if (sub === "unskip") {
1127
+ const { positional } = parseFlags(rest, {});
1128
+ if (!positional[0]) {
1129
+ console.error("Usage: autonag events unskip <id>");
1130
+ process.exit(1);
1131
+ }
1132
+ await cmdEventsUnskip(positional[0], cfg);
1133
+ } else {
1134
+ console.error(`Unknown subcommand: events ${sub}`);
1135
+ process.exit(1);
1136
+ }
1137
+ } else if (cmd === "comments") {
1138
+ if (!sub || sub === "list") {
1139
+ const { positional } = parseFlags(rest, {});
1140
+ if (!positional[0]) {
1141
+ console.error("Usage: autonag comments list <timerId>");
1142
+ process.exit(1);
1143
+ }
1144
+ await cmdCommentsList(positional[0], cfg);
1145
+ } else if (sub === "add") {
1146
+ const { positional } = parseFlags(rest, {});
1147
+ if (!positional[0] || !positional[1]) {
1148
+ console.error("Usage: autonag comments add <timerId> <text>");
1149
+ process.exit(1);
1150
+ }
1151
+ await cmdCommentsAdd(positional[0], positional[1], cfg);
1152
+ } else {
1153
+ console.error(`Unknown subcommand: comments ${sub}`);
1154
+ process.exit(1);
1155
+ }
1156
+ } else {
1157
+ console.error(`Unknown command: ${cmd}`);
1158
+ console.error(`Run "autonag --help" for usage.`);
1159
+ process.exit(1);
1160
+ }
1161
+ } catch (e) {
1162
+ if (e instanceof ApiError) {
1163
+ console.error(`Error: ${e.message}`);
1164
+ process.exit(1);
1165
+ }
1166
+ throw e;
1167
+ }
1168
+ //# sourceMappingURL=cli.js.map