ambiguous 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +3484 -0
  2. package/package.json +43 -0
package/dist/index.js ADDED
@@ -0,0 +1,3484 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/index.ts
5
+ import { Command } from "commander";
6
+
7
+ // src/lib/output.ts
8
+ import chalk from "chalk";
9
+ var _jsonMode = false;
10
+ var _quietMode = false;
11
+ function setJsonMode(on) {
12
+ _jsonMode = on;
13
+ }
14
+ function setQuietMode(on) {
15
+ _quietMode = on;
16
+ }
17
+ function isJsonMode() {
18
+ return _jsonMode || !process.stdout.isTTY;
19
+ }
20
+ function isQuietMode() {
21
+ return _quietMode;
22
+ }
23
+ function useColor() {
24
+ return !!process.stdout.isTTY && !process.env.NO_COLOR;
25
+ }
26
+ function dim(text) {
27
+ return useColor() ? chalk.dim(text) : text;
28
+ }
29
+ function bold(text) {
30
+ return useColor() ? chalk.bold(text) : text;
31
+ }
32
+ function green(text) {
33
+ return useColor() ? chalk.green(text) : text;
34
+ }
35
+ function red(text) {
36
+ return useColor() ? chalk.red(text) : text;
37
+ }
38
+ function yellow(text) {
39
+ return useColor() ? chalk.yellow(text) : text;
40
+ }
41
+ function printTable(data, columns) {
42
+ if (isJsonMode()) {
43
+ console.log(JSON.stringify(data));
44
+ return;
45
+ }
46
+ if (data.length === 0) {
47
+ if (!isQuietMode()) console.log(dim("No results."));
48
+ return;
49
+ }
50
+ const headers = columns.map((c) => c.header ?? c.key);
51
+ const formatters = columns.map((c) => c.format ?? String);
52
+ const rows = data.map(
53
+ (row) => columns.map((col, i) => {
54
+ const raw = row[col.key];
55
+ return raw == null ? "" : formatters[i](raw);
56
+ })
57
+ );
58
+ const widths = columns.map((col, i) => {
59
+ const headerLen = headers[i].length;
60
+ const maxDataLen = rows.reduce((max, row) => Math.max(max, row[i].length), 0);
61
+ return col.width ?? Math.max(headerLen, maxDataLen);
62
+ });
63
+ const pad = (s, w, align = "left") => align === "right" ? s.padStart(w) : s.padEnd(w);
64
+ const headerLine = columns.map((col, i) => pad(headers[i], widths[i], col.align)).join(" ");
65
+ console.log(bold(headerLine));
66
+ for (const row of rows) {
67
+ const line = columns.map((col, i) => pad(row[i], widths[i], col.align)).join(" ");
68
+ console.log(line);
69
+ }
70
+ }
71
+ function printOne(data, fields) {
72
+ if (isJsonMode()) {
73
+ console.log(JSON.stringify(data));
74
+ return;
75
+ }
76
+ const entries = fields ? fields.map((f) => [f.label ?? f.key, f.format ? f.format(data[f.key]) : String(data[f.key] ?? "")]) : Object.entries(data).map(([k, v]) => [k, String(v ?? "")]);
77
+ const maxLabelLen = entries.reduce((max, [label]) => Math.max(max, label.length), 0);
78
+ for (const [label, value] of entries) {
79
+ console.log(`${dim(label.padEnd(maxLabelLen))} ${value}`);
80
+ }
81
+ }
82
+ function printJson(data) {
83
+ console.log(JSON.stringify(data, null, isJsonMode() ? void 0 : 2));
84
+ }
85
+ function success(msg) {
86
+ if (isQuietMode() && isJsonMode()) return;
87
+ if (isJsonMode()) {
88
+ console.log(JSON.stringify({ ok: true, message: msg }));
89
+ return;
90
+ }
91
+ console.log(green("\u2713") + " " + msg);
92
+ }
93
+ function warn(msg) {
94
+ if (isQuietMode()) return;
95
+ console.error(yellow("\u26A0") + " " + msg);
96
+ }
97
+ function info(msg) {
98
+ if (isQuietMode()) return;
99
+ console.error(dim(msg));
100
+ }
101
+
102
+ // src/lib/client.ts
103
+ import { AmbiguousClient, AmbiguousApiError } from "@ambiguous/api-client";
104
+
105
+ // src/lib/config.ts
106
+ import Conf from "conf";
107
+ var DEFAULT_API_URL = "https://api.ambi.cc";
108
+ var config = new Conf({
109
+ projectName: "ambi",
110
+ defaults: {
111
+ apiUrl: DEFAULT_API_URL
112
+ }
113
+ });
114
+ function getConfigPath() {
115
+ return config.path;
116
+ }
117
+ function get(key) {
118
+ return config.get(key);
119
+ }
120
+ function set(key, value) {
121
+ config.set(key, value);
122
+ }
123
+ function getAll() {
124
+ return config.store;
125
+ }
126
+ function clear() {
127
+ config.clear();
128
+ }
129
+ function getAuthToken() {
130
+ return config.get("authToken");
131
+ }
132
+ function setAuth(token, userName, userEmail) {
133
+ config.set("authToken", token);
134
+ if (userName) config.set("userName", userName);
135
+ if (userEmail) config.set("userEmail", userEmail);
136
+ }
137
+ function clearAuth() {
138
+ config.delete("authToken");
139
+ config.delete("userName");
140
+ config.delete("userEmail");
141
+ }
142
+ function isAuthenticated() {
143
+ return !!config.get("authToken");
144
+ }
145
+ function getApiUrl() {
146
+ const url = config.get("apiUrl") || process.env.AMBI_API_URL || DEFAULT_API_URL;
147
+ return url.replace(/\/+$/, "");
148
+ }
149
+
150
+ // src/lib/client.ts
151
+ var _client = null;
152
+ function getClient(opts) {
153
+ const requireAuth = opts?.requireAuth ?? true;
154
+ const token = getAuthToken();
155
+ const apiUrl = getApiUrl();
156
+ if (requireAuth && !token) {
157
+ throw new Error(
158
+ "Not authenticated. Run `ambi auth login` or `ambi auth login --token <key>` first."
159
+ );
160
+ }
161
+ _client = new AmbiguousClient({
162
+ baseUrl: apiUrl,
163
+ ...token?.startsWith("ak_") ? { apiKey: token } : { token }
164
+ });
165
+ return _client;
166
+ }
167
+ async function rawRequest(method, path, data, query) {
168
+ const client = getClient();
169
+ return client.request(method, path, data, query);
170
+ }
171
+
172
+ // src/lib/errors.ts
173
+ var EXIT_ERROR = 1;
174
+ var EXIT_AUTH_ERROR = 2;
175
+ var CliError = class extends Error {
176
+ constructor(message, exitCode = EXIT_ERROR) {
177
+ super(message);
178
+ this.exitCode = exitCode;
179
+ this.name = "CliError";
180
+ }
181
+ };
182
+ function exitCodeFor(err) {
183
+ if (err instanceof CliError) return err.exitCode;
184
+ if (err instanceof AmbiguousApiError) {
185
+ return err.statusCode === 401 || err.statusCode === 403 ? EXIT_AUTH_ERROR : EXIT_ERROR;
186
+ }
187
+ return EXIT_ERROR;
188
+ }
189
+ function messageFor(err) {
190
+ if (err instanceof AmbiguousApiError) {
191
+ const bodyMsg = typeof err.body?.error === "string" ? err.body.error : void 0;
192
+ const bodyMessage = typeof err.body?.message === "string" ? err.body.message : void 0;
193
+ return bodyMsg || bodyMessage || `HTTP ${err.statusCode}`;
194
+ }
195
+ if (err instanceof Error) return err.message;
196
+ return String(err);
197
+ }
198
+ function jsonPayload(err) {
199
+ const base = {
200
+ ok: false,
201
+ error: messageFor(err)
202
+ };
203
+ if (err instanceof AmbiguousApiError) {
204
+ base.statusCode = err.statusCode;
205
+ if (err.body && typeof err.body === "object") {
206
+ const { error: _e, message: _m, ...rest } = err.body;
207
+ if (Object.keys(rest).length > 0) base.details = rest;
208
+ }
209
+ }
210
+ return base;
211
+ }
212
+ function authHint(err) {
213
+ if (err instanceof AmbiguousApiError && err.statusCode === 401) {
214
+ return "Run `ambi auth login` to authenticate.";
215
+ }
216
+ if (err instanceof Error && err.message.includes("Not authenticated")) {
217
+ return "Run `ambi auth login` or `ambi auth login --token <key>` first.";
218
+ }
219
+ return null;
220
+ }
221
+ function handleError(err) {
222
+ const code = exitCodeFor(err);
223
+ if (isJsonMode()) {
224
+ console.log(JSON.stringify(jsonPayload(err)));
225
+ } else {
226
+ const msg = messageFor(err);
227
+ console.error(red("Error:") + " " + msg);
228
+ if (err instanceof AmbiguousApiError) {
229
+ console.error(dim(` HTTP ${err.statusCode}`));
230
+ }
231
+ const hint = authHint(err);
232
+ if (hint) {
233
+ console.error(yellow(" Hint:") + " " + hint);
234
+ }
235
+ }
236
+ process.exit(code);
237
+ }
238
+ function withErrorHandler(fn) {
239
+ return async (...args) => {
240
+ try {
241
+ await fn(...args);
242
+ } catch (err) {
243
+ handleError(err);
244
+ }
245
+ };
246
+ }
247
+
248
+ // src/commands/auth.ts
249
+ import * as http from "http";
250
+ import * as crypto from "crypto";
251
+ import open from "open";
252
+ async function exchangeCode(apiUrl, code) {
253
+ const res = await fetch(`${apiUrl}/api/auth/cli/exchange`, {
254
+ method: "POST",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify({ code })
257
+ });
258
+ const data = await res.json();
259
+ if (!res.ok) {
260
+ throw new CliError(
261
+ data.error || `Code exchange failed: HTTP ${res.status}`,
262
+ EXIT_AUTH_ERROR
263
+ );
264
+ }
265
+ return {
266
+ token: data.token,
267
+ user: data.display_name ?? void 0,
268
+ email: data.email ?? void 0
269
+ };
270
+ }
271
+ async function browserLogin(timeoutMs = 12e4) {
272
+ return new Promise((resolve6, reject) => {
273
+ const state = crypto.randomUUID();
274
+ let settled = false;
275
+ const server = http.createServer(async (req, res) => {
276
+ const url = new URL(req.url ?? "/", `http://localhost`);
277
+ if (url.pathname !== "/callback") {
278
+ res.writeHead(404);
279
+ res.end("Not found");
280
+ return;
281
+ }
282
+ const receivedState = url.searchParams.get("state");
283
+ const code = url.searchParams.get("code");
284
+ if (receivedState !== state) {
285
+ res.writeHead(400);
286
+ res.end("State mismatch \u2014 possible CSRF. Please try again.");
287
+ return;
288
+ }
289
+ if (!code) {
290
+ res.writeHead(400);
291
+ res.end("No auth code received. Please try again.");
292
+ return;
293
+ }
294
+ try {
295
+ const apiUrl = getApiUrl();
296
+ const result = await exchangeCode(apiUrl, code);
297
+ res.writeHead(200, { "Content-Type": "text/html" });
298
+ res.end(`
299
+ <!DOCTYPE html>
300
+ <html>
301
+ <body style="font-family: Inter, system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #F1F3F9; color: #27272C;">
302
+ <div style="text-align: center;">
303
+ <h1 style="font-size: 24px; margin-bottom: 8px;">Authenticated!</h1>
304
+ <p style="color: #6B7280;">You can close this window and return to the terminal.</p>
305
+ </div>
306
+ </body>
307
+ </html>
308
+ `);
309
+ settled = true;
310
+ server.close();
311
+ resolve6(result);
312
+ } catch (err) {
313
+ res.writeHead(500);
314
+ res.end("Authentication failed. Please try again.");
315
+ if (!settled) {
316
+ settled = true;
317
+ server.close();
318
+ reject(err);
319
+ }
320
+ }
321
+ });
322
+ server.listen(0, "127.0.0.1", () => {
323
+ const addr = server.address();
324
+ if (!addr || typeof addr === "string") {
325
+ reject(new Error("Failed to start local server"));
326
+ return;
327
+ }
328
+ const port = addr.port;
329
+ const apiUrl = getApiUrl();
330
+ const authUrl = `${apiUrl}/api/auth/cli?port=${port}&state=${state}`;
331
+ open(authUrl).catch(() => {
332
+ warn("Could not open browser automatically.");
333
+ });
334
+ if (!isJsonMode()) {
335
+ console.error(dim(`Opening browser to authenticate...`));
336
+ console.error(dim(`If the browser didn't open, visit:`));
337
+ console.error(` ${authUrl}`);
338
+ console.error(dim(`Waiting for authentication...`));
339
+ }
340
+ });
341
+ const timer = setTimeout(() => {
342
+ if (!settled) {
343
+ settled = true;
344
+ server.close();
345
+ reject(new CliError("Login timed out. Please try again.", EXIT_AUTH_ERROR));
346
+ }
347
+ }, timeoutMs);
348
+ server.on("close", () => clearTimeout(timer));
349
+ server.on("error", (err) => {
350
+ if (!settled) {
351
+ settled = true;
352
+ reject(new CliError(`Local server error: ${err.message}`));
353
+ }
354
+ });
355
+ });
356
+ }
357
+ function registerAuthCommands(parent) {
358
+ const auth = parent.command("auth").description("Manage authentication");
359
+ auth.command("login").description("Authenticate with Ambiguous Workspace").option("--token <token>", "Authenticate directly with an API key or JWT (for agents/CI)").action(
360
+ withErrorHandler(async (opts) => {
361
+ if (opts.token) {
362
+ setAuth(opts.token);
363
+ success("Authenticated with provided token.");
364
+ return;
365
+ }
366
+ const result = await browserLogin();
367
+ setAuth(result.token, result.user, result.email);
368
+ if (isJsonMode()) {
369
+ printJson({
370
+ ok: true,
371
+ user: result.user ?? null,
372
+ email: result.email ?? null
373
+ });
374
+ } else {
375
+ const who = result.user ?? result.email ?? "unknown";
376
+ success(`Logged in as ${who}`);
377
+ }
378
+ })
379
+ );
380
+ auth.command("logout").description("Clear stored credentials").action(
381
+ withErrorHandler(async () => {
382
+ clearAuth();
383
+ success("Logged out.");
384
+ })
385
+ );
386
+ auth.command("status").description("Show current authentication status").action(
387
+ withErrorHandler(async () => {
388
+ const authenticated = isAuthenticated();
389
+ const data = {
390
+ authenticated,
391
+ apiUrl: getApiUrl()
392
+ };
393
+ if (authenticated) {
394
+ const userName = get("userName");
395
+ const userEmail = get("userEmail");
396
+ if (userName) data.user = userName;
397
+ if (userEmail) data.email = userEmail;
398
+ const token = getAuthToken();
399
+ data.tokenPreview = token.length > 12 ? token.slice(0, 8) + "..." + token.slice(-4) : "***";
400
+ }
401
+ if (isJsonMode()) {
402
+ printJson(data);
403
+ } else {
404
+ printOne(data, [
405
+ { key: "authenticated", label: "Authenticated", format: (v) => v ? green("yes") : "no" },
406
+ { key: "user", label: "User" },
407
+ { key: "email", label: "Email" },
408
+ { key: "apiUrl", label: "API URL" },
409
+ { key: "tokenPreview", label: "Token" }
410
+ ].filter((f) => data[f.key] != null));
411
+ }
412
+ })
413
+ );
414
+ auth.command("token").description("Print the raw auth token (for piping to other tools)").action(
415
+ withErrorHandler(async () => {
416
+ const token = getAuthToken();
417
+ if (!token) {
418
+ throw new CliError("Not authenticated. Run `ambi auth login` first.", EXIT_AUTH_ERROR);
419
+ }
420
+ process.stdout.write(token + "\n");
421
+ })
422
+ );
423
+ }
424
+
425
+ // src/lib/input.ts
426
+ import * as readline from "readline";
427
+ function parseFields(fields) {
428
+ const result = {};
429
+ for (const field of fields) {
430
+ const eqIndex = field.indexOf("=");
431
+ if (eqIndex === -1) {
432
+ throw new Error(`Invalid field format: "${field}". Expected key=value.`);
433
+ }
434
+ const key = field.slice(0, eqIndex).trim();
435
+ const rawValue = field.slice(eqIndex + 1);
436
+ if (!key) {
437
+ throw new Error(`Empty key in field: "${field}".`);
438
+ }
439
+ const value = coerce(rawValue);
440
+ setNested(result, key, value);
441
+ }
442
+ return result;
443
+ }
444
+ function coerce(value) {
445
+ if (value === "true") return true;
446
+ if (value === "false") return false;
447
+ if (value === "null") return null;
448
+ if (value === "") return "";
449
+ if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}")) {
450
+ try {
451
+ return JSON.parse(value);
452
+ } catch {
453
+ return value;
454
+ }
455
+ }
456
+ if (/^-?(?:0|[1-9]\d*)(\.\d+)?$/.test(value)) {
457
+ const num = Number(value);
458
+ if (Number.isFinite(num)) return num;
459
+ }
460
+ return value;
461
+ }
462
+ var BANNED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
463
+ function setNested(obj, path, value) {
464
+ const keys = path.split(".");
465
+ let current = obj;
466
+ for (const k of keys) {
467
+ if (BANNED_KEYS.has(k)) {
468
+ throw new Error(`Invalid field key: "${k}" is not allowed.`);
469
+ }
470
+ }
471
+ for (let i = 0; i < keys.length - 1; i++) {
472
+ const k = keys[i];
473
+ if (current[k] == null || typeof current[k] !== "object") {
474
+ current[k] = {};
475
+ }
476
+ current = current[k];
477
+ }
478
+ current[keys[keys.length - 1]] = value;
479
+ }
480
+ var MAX_STDIN_BYTES = 10 * 1024 * 1024;
481
+ async function readStdin() {
482
+ if (process.stdin.isTTY) {
483
+ return null;
484
+ }
485
+ const chunks = [];
486
+ let totalBytes = 0;
487
+ return new Promise((resolve6, reject) => {
488
+ process.stdin.setEncoding("utf8");
489
+ process.stdin.on("data", (chunk) => {
490
+ totalBytes += Buffer.byteLength(chunk);
491
+ if (totalBytes > MAX_STDIN_BYTES) {
492
+ process.stdin.destroy();
493
+ reject(new Error(`Stdin too large (max ${MAX_STDIN_BYTES / 1024 / 1024} MB).`));
494
+ return;
495
+ }
496
+ chunks.push(chunk);
497
+ });
498
+ process.stdin.on("end", () => {
499
+ const raw = chunks.join("").trim();
500
+ if (!raw) {
501
+ resolve6(null);
502
+ return;
503
+ }
504
+ try {
505
+ const parsed = JSON.parse(raw);
506
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
507
+ resolve6(parsed);
508
+ } else {
509
+ reject(new Error("Stdin JSON must be an object (not an array or primitive)."));
510
+ }
511
+ } catch {
512
+ reject(new Error("Invalid JSON on stdin."));
513
+ }
514
+ });
515
+ process.stdin.on("error", reject);
516
+ });
517
+ }
518
+ async function collectInput(fieldFlags) {
519
+ const stdinData = await readStdin();
520
+ const fieldData = parseFields(fieldFlags);
521
+ return { ...stdinData ?? {}, ...fieldData };
522
+ }
523
+ async function confirm(message, defaultAnswer = false) {
524
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
525
+ return defaultAnswer;
526
+ }
527
+ const suffix = defaultAnswer ? "[Y/n]" : "[y/N]";
528
+ const rl = readline.createInterface({
529
+ input: process.stdin,
530
+ output: process.stderr
531
+ // prompts go to stderr so stdout stays clean for piping
532
+ });
533
+ return new Promise((resolve6) => {
534
+ rl.question(`${message} ${suffix} `, (answer) => {
535
+ rl.close();
536
+ const trimmed = answer.trim().toLowerCase();
537
+ if (trimmed === "") resolve6(defaultAnswer);
538
+ else resolve6(trimmed === "y" || trimmed === "yes");
539
+ });
540
+ });
541
+ }
542
+
543
+ // src/commands/config.ts
544
+ var ALLOWED_KEYS = [
545
+ "apiUrl",
546
+ "workspace",
547
+ "authToken",
548
+ "userName",
549
+ "userEmail"
550
+ ];
551
+ function isValidKey(key) {
552
+ return ALLOWED_KEYS.includes(key);
553
+ }
554
+ function registerConfigCommands(parent) {
555
+ const cfg = parent.command("config").description("Manage CLI configuration");
556
+ cfg.command("get <key>").description("Get a config value").action(
557
+ withErrorHandler(async (key) => {
558
+ if (!isValidKey(key)) {
559
+ throw new CliError(
560
+ `Unknown config key: "${key}". Valid keys: ${ALLOWED_KEYS.join(", ")}`
561
+ );
562
+ }
563
+ const value = get(key);
564
+ if (isJsonMode()) {
565
+ printJson({ key, value: value ?? null });
566
+ } else {
567
+ if (value == null) {
568
+ console.log(dim("(not set)"));
569
+ } else {
570
+ if (key === "authToken" && typeof value === "string" && value.length > 12) {
571
+ console.log(value.slice(0, 8) + "..." + value.slice(-4));
572
+ } else {
573
+ console.log(String(value));
574
+ }
575
+ }
576
+ }
577
+ })
578
+ );
579
+ cfg.command("set <key> <value>").description("Set a config value").action(
580
+ withErrorHandler(async (key, value) => {
581
+ if (!isValidKey(key)) {
582
+ throw new CliError(
583
+ `Unknown config key: "${key}". Valid keys: ${ALLOWED_KEYS.join(", ")}`
584
+ );
585
+ }
586
+ set(key, value);
587
+ success(`Set ${key} = ${key === "authToken" ? "***" : value}`);
588
+ })
589
+ );
590
+ cfg.command("list").description("Show all config values").action(
591
+ withErrorHandler(async () => {
592
+ const all = getAll();
593
+ if (isJsonMode()) {
594
+ const safe = { ...all };
595
+ if (safe.authToken) {
596
+ safe.authToken = safe.authToken.length > 12 ? safe.authToken.slice(0, 8) + "..." + safe.authToken.slice(-4) : "***";
597
+ }
598
+ printJson(safe);
599
+ } else {
600
+ const entries = ALLOWED_KEYS.map((key) => ({
601
+ key,
602
+ label: key,
603
+ format: key === "authToken" ? (v) => {
604
+ if (!v) return dim("(not set)");
605
+ const s = String(v);
606
+ return s.length > 12 ? s.slice(0, 8) + "..." + s.slice(-4) : "***";
607
+ } : (v) => v == null ? dim("(not set)") : String(v)
608
+ }));
609
+ printOne(all, entries);
610
+ }
611
+ })
612
+ );
613
+ cfg.command("path").description("Print the config file path").action(
614
+ withErrorHandler(async () => {
615
+ const p = getConfigPath();
616
+ if (isJsonMode()) {
617
+ printJson({ path: p });
618
+ } else {
619
+ console.log(p);
620
+ }
621
+ })
622
+ );
623
+ cfg.command("reset").description("Reset config to defaults").option("-y, --yes", "Skip confirmation").action(
624
+ withErrorHandler(async (opts) => {
625
+ if (!opts.yes) {
626
+ const ok = await confirm("Reset all config to defaults? This will log you out.");
627
+ if (!ok) {
628
+ console.log("Cancelled.");
629
+ return;
630
+ }
631
+ }
632
+ clear();
633
+ success("Config reset to defaults.");
634
+ })
635
+ );
636
+ }
637
+
638
+ // src/commands/docs.ts
639
+ import { existsSync } from "fs";
640
+ import { writeFile } from "fs/promises";
641
+ import { basename, resolve, extname } from "path";
642
+ var docColumns = [
643
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
644
+ { key: "title", header: "Title" },
645
+ { key: "type", header: "Type", width: 6 },
646
+ {
647
+ key: "updated_at",
648
+ header: "Updated",
649
+ format: (v) => {
650
+ if (!v) return "";
651
+ const d = new Date(String(v));
652
+ return d.toLocaleDateString();
653
+ }
654
+ }
655
+ ];
656
+ function registerDocsCommands(program2) {
657
+ const docs = program2.command("docs").description("Manage documents (docs, sheets, slides)");
658
+ docs.command("list").description("List your documents").option("--type <type>", "Filter by type (doc, sheet, slide)").option("-f, --field <fields...>", "Field filters (key=value)").action(
659
+ withErrorHandler(async (opts) => {
660
+ const client = getClient();
661
+ const params = {};
662
+ if (opts.type) params.type = opts.type;
663
+ const res = await client.docs.list(params);
664
+ printTable(res.documents, docColumns);
665
+ })
666
+ );
667
+ docs.command("create").description("Create a new document").option("-f, --field <fields...>", "Set fields (title=..., type=doc|sheet|slide, content=...)").action(
668
+ withErrorHandler(async (opts) => {
669
+ const data = await collectInput(opts.field ?? []);
670
+ if (!data.type) data.type = "doc";
671
+ const client = getClient();
672
+ const doc = await client.docs.create(data);
673
+ if (isJsonMode()) {
674
+ printOne(doc);
675
+ } else {
676
+ success(`Created ${data.type} "${doc.title}" (${doc.id})`);
677
+ }
678
+ })
679
+ );
680
+ docs.command("get <id>").description("Get a document by ID").action(
681
+ withErrorHandler(async (id) => {
682
+ const client = getClient();
683
+ const doc = await client.docs.get(id);
684
+ printOne(doc);
685
+ })
686
+ );
687
+ docs.command("update <id>").description("Update a document").option("-f, --field <fields...>", "Set fields (title=..., content=...)").action(
688
+ withErrorHandler(async (id, opts) => {
689
+ const data = await collectInput(opts.field ?? []);
690
+ const client = getClient();
691
+ const doc = await client.docs.update(id, data);
692
+ if (isJsonMode()) {
693
+ printOne(doc);
694
+ } else {
695
+ success(`Updated "${doc.title}" (${doc.id})`);
696
+ }
697
+ })
698
+ );
699
+ docs.command("delete <id>").description("Delete a document").option("-y, --yes", "Skip confirmation").action(
700
+ withErrorHandler(async (id, opts) => {
701
+ if (!opts.yes && !isJsonMode()) {
702
+ const ok = await confirm(`Delete document ${id}?`);
703
+ if (!ok) {
704
+ console.error(dim("Cancelled."));
705
+ return;
706
+ }
707
+ }
708
+ const client = getClient();
709
+ await client.docs.delete(id);
710
+ success(`Deleted document ${id}`);
711
+ })
712
+ );
713
+ const perms = docs.command("permissions").description("Manage document permissions");
714
+ perms.command("list <docId>").description("List permissions for a document").action(
715
+ withErrorHandler(async (docId) => {
716
+ const client = getClient();
717
+ const res = await client.docs.listPermissions(docId);
718
+ printTable(
719
+ res.permissions.map((p) => ({
720
+ user_id: p.user_id,
721
+ name: p.user?.display_name ?? "",
722
+ email: p.user?.email ?? "",
723
+ role: p.role
724
+ })),
725
+ [
726
+ { key: "user_id", header: "User ID", width: 12, format: (v) => String(v).slice(0, 12) },
727
+ { key: "name", header: "Name" },
728
+ { key: "email", header: "Email" },
729
+ { key: "role", header: "Role", width: 10 }
730
+ ]
731
+ );
732
+ })
733
+ );
734
+ perms.command("set <docId>").description("Set permission on a document").option("-f, --field <fields...>", "Set fields (user_id=..., role=viewer|commenter|editor|owner)").action(
735
+ withErrorHandler(async (docId, opts) => {
736
+ const data = await collectInput(opts.field ?? []);
737
+ const client = getClient();
738
+ const perm = await client.docs.setPermission(docId, data);
739
+ if (isJsonMode()) {
740
+ printOne(perm);
741
+ } else {
742
+ success(`Set ${perm.role} permission for ${perm.user_id} on ${docId}`);
743
+ }
744
+ })
745
+ );
746
+ perms.command("remove <docId> <userId>").description("Remove a user's permission from a document").action(
747
+ withErrorHandler(async (docId, userId) => {
748
+ const client = getClient();
749
+ await client.docs.removePermission(docId, userId);
750
+ success(`Removed permission for ${userId} on ${docId}`);
751
+ })
752
+ );
753
+ const comments = docs.command("comments").description("Manage document comments");
754
+ comments.command("list <docId>").description("List comments on a document").action(
755
+ withErrorHandler(async (docId) => {
756
+ const client = getClient();
757
+ const res = await client.docs.listComments(docId);
758
+ printTable(
759
+ res.comments.map((c) => ({
760
+ id: c.id,
761
+ author: c.user?.display_name ?? c.user_id ?? "",
762
+ content: String(c.content ?? "").slice(0, 60),
763
+ resolved: c.resolved ? "yes" : "no"
764
+ })),
765
+ [
766
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
767
+ { key: "author", header: "Author" },
768
+ { key: "content", header: "Content" },
769
+ { key: "resolved", header: "Resolved", width: 8 }
770
+ ]
771
+ );
772
+ })
773
+ );
774
+ comments.command("add <docId>").description("Add a comment to a document").option("-f, --field <fields...>", "Set fields (content=..., parent_id=...)").action(
775
+ withErrorHandler(async (docId, opts) => {
776
+ const data = await collectInput(opts.field ?? []);
777
+ const client = getClient();
778
+ const comment = await client.docs.addComment(docId, data);
779
+ if (isJsonMode()) {
780
+ printOne(comment);
781
+ } else {
782
+ success(`Added comment ${comment.id}`);
783
+ }
784
+ })
785
+ );
786
+ comments.command("resolve <docId> <commentId>").description("Resolve a comment").action(
787
+ withErrorHandler(async (docId, commentId) => {
788
+ const client = getClient();
789
+ await client.docs.resolveComment(docId, commentId, true);
790
+ success(`Resolved comment ${commentId}`);
791
+ })
792
+ );
793
+ comments.command("unresolve <docId> <commentId>").description("Unresolve a comment").action(
794
+ withErrorHandler(async (docId, commentId) => {
795
+ const client = getClient();
796
+ await client.docs.resolveComment(docId, commentId, false);
797
+ success(`Unreresolved comment ${commentId}`);
798
+ })
799
+ );
800
+ comments.command("delete <docId> <commentId>").description("Delete a comment").option("-y, --yes", "Skip confirmation").action(
801
+ withErrorHandler(async (docId, commentId, opts) => {
802
+ if (!opts.yes && !isJsonMode()) {
803
+ const ok = await confirm(`Delete comment ${commentId}?`);
804
+ if (!ok) {
805
+ console.error(dim("Cancelled."));
806
+ return;
807
+ }
808
+ }
809
+ const client = getClient();
810
+ await client.docs.deleteComment(docId, commentId);
811
+ success(`Deleted comment ${commentId}`);
812
+ })
813
+ );
814
+ docs.command("import <file>").description("Import a file as a new document (.md, .html, .txt, .docx)").option("-f, --field <fields...>", "Set fields (title=...)").action(
815
+ withErrorHandler(async (file, opts) => {
816
+ const filePath = resolve(file);
817
+ if (!existsSync(filePath)) {
818
+ throw new CliError(`File not found: ${filePath}`);
819
+ }
820
+ const fields = await collectInput(opts.field ?? []);
821
+ const title = fields.title ?? basename(file, extname(file));
822
+ const fileContent = await import("fs/promises").then((fs) => fs.readFile(filePath));
823
+ const fileName = basename(filePath);
824
+ const formData = new FormData();
825
+ formData.append("file", new globalThis.Blob([fileContent]), fileName);
826
+ formData.append("title", title);
827
+ const apiUrl = getApiUrl();
828
+ const token = getAuthToken();
829
+ if (!token) throw new CliError("Not authenticated. Run `ambi auth login` first.", 2);
830
+ const res = await fetch(`${apiUrl}/api/documents/import`, {
831
+ method: "POST",
832
+ headers: {
833
+ "Authorization": token.startsWith("ak_") ? `Bearer ${token}` : `Bearer ${token}`
834
+ },
835
+ body: formData
836
+ });
837
+ if (!res.ok) {
838
+ const body = await res.json().catch(() => ({}));
839
+ throw new CliError(
840
+ body.error || body.message || `Import failed: HTTP ${res.status}`
841
+ );
842
+ }
843
+ const doc = await res.json();
844
+ if (isJsonMode()) {
845
+ printOne(doc);
846
+ } else {
847
+ success(`Imported "${doc.title}" (${doc.id})`);
848
+ }
849
+ })
850
+ );
851
+ docs.command("export <id>").description("Export a document to a file").requiredOption("--format <format>", "Export format (docx, pdf, html, md, xlsx, csv, pptx)").option("-o, --output <path>", "Output file path (default: ./<title>.<format>)").action(
852
+ withErrorHandler(async (id, opts) => {
853
+ const format = opts.format;
854
+ const validFormats = ["docx", "pdf", "html", "md", "xlsx", "csv", "pptx"];
855
+ if (!validFormats.includes(format)) {
856
+ throw new CliError(`Invalid format "${format}". Valid: ${validFormats.join(", ")}`);
857
+ }
858
+ const apiUrl = getApiUrl();
859
+ const token = getAuthToken();
860
+ if (!token) throw new CliError("Not authenticated. Run `ambi auth login` first.", 2);
861
+ info(`Exporting document ${id} as ${format}...`);
862
+ const res = await fetch(`${apiUrl}/api/documents/${encodeURIComponent(id)}/export`, {
863
+ method: "POST",
864
+ headers: {
865
+ "Authorization": `Bearer ${token}`,
866
+ "Content-Type": "application/json"
867
+ },
868
+ body: JSON.stringify({ format })
869
+ });
870
+ if (!res.ok) {
871
+ const body = await res.json().catch(() => ({}));
872
+ throw new CliError(
873
+ body.error || body.message || `Export failed: HTTP ${res.status}`
874
+ );
875
+ }
876
+ const contentDisp = res.headers.get("content-disposition") ?? "";
877
+ const filenameMatch = contentDisp.match(/filename="?([^";\n]+)"?/);
878
+ const serverFilename = filenameMatch?.[1];
879
+ const outputPath = opts.output ? resolve(opts.output) : resolve(serverFilename ?? `document-${id}.${format}`);
880
+ const buffer = Buffer.from(await res.arrayBuffer());
881
+ await writeFile(outputPath, buffer);
882
+ if (isJsonMode()) {
883
+ printOne({ ok: true, path: outputPath, format, size: buffer.length });
884
+ } else {
885
+ success(`Exported to ${outputPath} (${formatBytes(buffer.length)})`);
886
+ }
887
+ })
888
+ );
889
+ const versions = docs.command("versions").description("Manage document version history");
890
+ versions.command("list <docId>").description("List version history for a document").action(
891
+ withErrorHandler(async (docId) => {
892
+ const client = getClient();
893
+ const res = await client.request("GET", `/api/documents/${docId}/versions`);
894
+ printTable(
895
+ (res.versions ?? []).map((v) => ({
896
+ id: v.id,
897
+ title: v.title ?? "",
898
+ user: v.user_name ?? v.user_id ?? "",
899
+ created_at: v.created_at
900
+ })),
901
+ [
902
+ { key: "id", header: "Version ID", width: 12, format: (v) => String(v).slice(0, 12) },
903
+ { key: "title", header: "Title" },
904
+ { key: "user", header: "User" },
905
+ {
906
+ key: "created_at",
907
+ header: "Created",
908
+ format: (v) => {
909
+ if (!v) return "";
910
+ const d = new Date(String(v));
911
+ return d.toLocaleString();
912
+ }
913
+ }
914
+ ]
915
+ );
916
+ })
917
+ );
918
+ versions.command("create <docId>").description("Create a manual version snapshot").action(
919
+ withErrorHandler(async (docId) => {
920
+ const client = getClient();
921
+ const version = await client.request("POST", `/api/documents/${docId}/versions`);
922
+ if (isJsonMode()) {
923
+ printOne(version);
924
+ } else {
925
+ success(`Created version ${version.id} for document ${docId}`);
926
+ }
927
+ })
928
+ );
929
+ versions.command("get <docId> <versionId>").description("Get a specific version (includes content)").action(
930
+ withErrorHandler(async (docId, versionId) => {
931
+ const client = getClient();
932
+ const version = await client.request(
933
+ "GET",
934
+ `/api/documents/${docId}/versions/${versionId}`
935
+ );
936
+ printOne(version);
937
+ })
938
+ );
939
+ versions.command("revert <docId> <versionId>").description("Revert a document to a specific version").option("-y, --yes", "Skip confirmation").action(
940
+ withErrorHandler(async (docId, versionId, opts) => {
941
+ if (!opts.yes && !isJsonMode()) {
942
+ const ok = await confirm(`Revert document ${docId} to version ${versionId}?`);
943
+ if (!ok) {
944
+ console.error(dim("Cancelled."));
945
+ return;
946
+ }
947
+ }
948
+ const client = getClient();
949
+ await client.request("POST", `/api/documents/${docId}/versions/${versionId}/revert`);
950
+ success(`Reverted document ${docId} to version ${versionId}`);
951
+ })
952
+ );
953
+ }
954
+ function formatBytes(bytes) {
955
+ if (bytes < 1024) return `${bytes} B`;
956
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
957
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
958
+ }
959
+
960
+ // src/commands/sheets.ts
961
+ import { existsSync as existsSync2 } from "fs";
962
+ import { readFile } from "fs/promises";
963
+ import { basename as basename2, resolve as resolve2, extname as extname2 } from "path";
964
+ var sheetListColumns = [
965
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
966
+ { key: "title", header: "Title" },
967
+ {
968
+ key: "updated_at",
969
+ header: "Updated",
970
+ format: (v) => {
971
+ if (!v) return "";
972
+ return new Date(String(v)).toLocaleDateString();
973
+ }
974
+ }
975
+ ];
976
+ function registerSheetsCommands(program2) {
977
+ const sheets = program2.command("sheets").description("Manage spreadsheets");
978
+ sheets.command("list").description("List your sheets").action(
979
+ withErrorHandler(async () => {
980
+ const client = getClient();
981
+ const res = await client.docs.list({ type: "sheet" });
982
+ printTable(res.documents, sheetListColumns);
983
+ })
984
+ );
985
+ sheets.command("create").description("Create a new spreadsheet").option("-f, --field <fields...>", "Set fields (title=...)").action(
986
+ withErrorHandler(async (opts) => {
987
+ const data = await collectInput(opts.field ?? []);
988
+ data.type = "sheet";
989
+ const client = getClient();
990
+ const doc = await client.docs.create(data);
991
+ if (isJsonMode()) {
992
+ printOne(doc);
993
+ } else {
994
+ success(`Created sheet "${doc.title}" (${doc.id})`);
995
+ }
996
+ })
997
+ );
998
+ sheets.command("get <id>").description("Get a sheet document by ID").action(
999
+ withErrorHandler(async (id) => {
1000
+ const client = getClient();
1001
+ const doc = await client.docs.get(id);
1002
+ printOne(doc);
1003
+ })
1004
+ );
1005
+ sheets.command("delete <id>").description("Delete a sheet").option("-y, --yes", "Skip confirmation").action(
1006
+ withErrorHandler(async (id, opts) => {
1007
+ if (!opts.yes && !isJsonMode()) {
1008
+ const ok = await confirm(`Delete sheet ${id}?`);
1009
+ if (!ok) {
1010
+ console.error(dim("Cancelled."));
1011
+ return;
1012
+ }
1013
+ }
1014
+ const client = getClient();
1015
+ await client.docs.delete(id);
1016
+ success(`Deleted sheet ${id}`);
1017
+ })
1018
+ );
1019
+ sheets.command("data <id>").description("Get structured sheet data (columns + rows as JSON)").option("--tab <name>", "Show only a specific tab").action(
1020
+ withErrorHandler(async (id, opts) => {
1021
+ const client = getClient();
1022
+ const data = await client.sheets.getData(id);
1023
+ if (isJsonMode()) {
1024
+ if (opts.tab) {
1025
+ const tab = data.sheets.find((s) => s.name === opts.tab);
1026
+ if (!tab) throw new CliError(`Tab "${opts.tab}" not found`);
1027
+ printOne(tab);
1028
+ } else {
1029
+ printOne(data);
1030
+ }
1031
+ return;
1032
+ }
1033
+ const tabs = opts.tab ? data.sheets.filter((s) => s.name === opts.tab) : data.sheets;
1034
+ if (opts.tab && tabs.length === 0) {
1035
+ throw new CliError(`Tab "${opts.tab}" not found`);
1036
+ }
1037
+ for (const tab of tabs) {
1038
+ if (data.sheets.length > 1) {
1039
+ info(`
1040
+ --- ${tab.name} ---`);
1041
+ }
1042
+ if (tab.columns.length === 0) {
1043
+ info("(empty sheet)");
1044
+ continue;
1045
+ }
1046
+ const columns = tab.columns.map((col) => ({
1047
+ key: col.id,
1048
+ header: col.name || col.id
1049
+ }));
1050
+ printTable(tab.rows, columns);
1051
+ }
1052
+ })
1053
+ );
1054
+ sheets.command("update-cells <id>").description("Update specific cells (pass JSON array via stdin or -f)").option("-f, --field <fields...>", "Cell updates as row:col=value (e.g., 0:A=Hello)").option("--tab <name>", "Target sheet tab name").action(
1055
+ withErrorHandler(async (id, opts) => {
1056
+ let cells;
1057
+ const stdinData = await collectInput([]);
1058
+ if (stdinData.cells && Array.isArray(stdinData.cells)) {
1059
+ cells = stdinData.cells;
1060
+ } else if (opts.field && opts.field.length > 0) {
1061
+ cells = opts.field.map((f) => {
1062
+ const match = f.match(/^(\d+):([A-Za-z]+)=(.*)$/);
1063
+ if (!match) throw new CliError(`Invalid cell format "${f}". Use row:col=value (e.g., 0:A=Hello)`);
1064
+ const [, rowStr, column, value] = match;
1065
+ const cell = { row: parseInt(rowStr, 10), column, value };
1066
+ if (opts.tab) cell.sheet = opts.tab;
1067
+ return cell;
1068
+ });
1069
+ } else {
1070
+ throw new CliError('Provide cell updates via stdin JSON ({"cells": [...]}) or -f 0:A=Hello');
1071
+ }
1072
+ const client = getClient();
1073
+ const result = await client.sheets.updateCells(id, cells);
1074
+ if (isJsonMode()) {
1075
+ printOne(result);
1076
+ } else {
1077
+ success(`Updated ${cells.length} cell(s)`);
1078
+ }
1079
+ })
1080
+ );
1081
+ sheets.command("import <file>").description("Import a file as a new spreadsheet (.xlsx, .csv)").option("-f, --field <fields...>", "Set fields (title=...)").action(
1082
+ withErrorHandler(async (file, opts) => {
1083
+ const filePath = resolve2(file);
1084
+ if (!existsSync2(filePath)) {
1085
+ throw new CliError(`File not found: ${filePath}`);
1086
+ }
1087
+ const ext = extname2(filePath).toLowerCase();
1088
+ if (![".xlsx", ".csv"].includes(ext)) {
1089
+ throw new CliError(`Unsupported format "${ext}". Use .xlsx or .csv`);
1090
+ }
1091
+ const fields = await collectInput(opts.field ?? []);
1092
+ const title = fields.title ?? basename2(file, ext);
1093
+ const fileContent = await readFile(filePath);
1094
+ const fileName = basename2(filePath);
1095
+ const formData = new FormData();
1096
+ formData.append("file", new globalThis.Blob([fileContent]), fileName);
1097
+ formData.append("title", title);
1098
+ const apiUrl = getApiUrl();
1099
+ const token = getAuthToken();
1100
+ if (!token) throw new CliError("Not authenticated. Run `ambi auth login` first.", 2);
1101
+ const res = await fetch(`${apiUrl}/api/sheets/import`, {
1102
+ method: "POST",
1103
+ headers: {
1104
+ "Authorization": `Bearer ${token}`
1105
+ },
1106
+ body: formData
1107
+ });
1108
+ if (!res.ok) {
1109
+ const body = await res.json().catch(() => ({}));
1110
+ throw new CliError(
1111
+ body.error || body.message || `Import failed: HTTP ${res.status}`
1112
+ );
1113
+ }
1114
+ const doc = await res.json();
1115
+ if (isJsonMode()) {
1116
+ printOne(doc);
1117
+ } else {
1118
+ success(`Imported "${doc.title}" (${doc.id})`);
1119
+ }
1120
+ })
1121
+ );
1122
+ sheets.command("add-tab <id>").description("Add a new sheet tab").option("--name <name>", "Tab name").action(
1123
+ withErrorHandler(async (id, opts) => {
1124
+ const client = getClient();
1125
+ const result = await client.sheets.addTab(id, opts.name);
1126
+ if (isJsonMode()) {
1127
+ printOne(result);
1128
+ } else {
1129
+ const tabNames = result.sheets.map((s) => s.name).join(", ");
1130
+ success(`Added tab. Tabs: ${tabNames}`);
1131
+ }
1132
+ })
1133
+ );
1134
+ sheets.command("remove-tab <id> <tabName>").description("Remove a sheet tab").option("-y, --yes", "Skip confirmation").action(
1135
+ withErrorHandler(async (id, tabName, opts) => {
1136
+ if (!opts.yes && !isJsonMode()) {
1137
+ const ok = await confirm(`Remove tab "${tabName}" from sheet ${id}?`);
1138
+ if (!ok) {
1139
+ console.error(dim("Cancelled."));
1140
+ return;
1141
+ }
1142
+ }
1143
+ const client = getClient();
1144
+ const result = await client.sheets.removeTab(id, tabName);
1145
+ if (isJsonMode()) {
1146
+ printOne(result);
1147
+ } else {
1148
+ success(`Removed tab "${tabName}"`);
1149
+ }
1150
+ })
1151
+ );
1152
+ sheets.command("rename-tab <id> <tabName> <newName>").description("Rename a sheet tab").action(
1153
+ withErrorHandler(async (id, tabName, newName) => {
1154
+ const client = getClient();
1155
+ const result = await client.sheets.renameTab(id, tabName, newName);
1156
+ if (isJsonMode()) {
1157
+ printOne(result);
1158
+ } else {
1159
+ success(`Renamed tab "${tabName}" to "${newName}"`);
1160
+ }
1161
+ })
1162
+ );
1163
+ sheets.command("add-columns <id>").description("Add a column to a sheet").option("-f, --field <fields...>", "Column fields (name=..., type=text|number|currency|date|boolean|formula)").option("--tab <name>", "Target sheet tab name").action(
1164
+ withErrorHandler(async (id, opts) => {
1165
+ const data = await collectInput(opts.field ?? []);
1166
+ if (!data.name) throw new CliError("Column name is required. Use -f name=...");
1167
+ const params = { name: data.name };
1168
+ if (data.type) params.type = data.type;
1169
+ if (data.id) params.id = data.id;
1170
+ if (opts.tab) params.sheet = opts.tab;
1171
+ const client = getClient();
1172
+ const result = await client.sheets.addColumn(id, params);
1173
+ if (isJsonMode()) {
1174
+ printOne(result);
1175
+ } else {
1176
+ success(`Added column "${data.name}"`);
1177
+ }
1178
+ })
1179
+ );
1180
+ sheets.command("remove-column <id> <columnId>").description("Remove a column from a sheet").option("--tab <name>", "Target sheet tab name").option("-y, --yes", "Skip confirmation").action(
1181
+ withErrorHandler(async (id, columnId, opts) => {
1182
+ if (!opts.yes && !isJsonMode()) {
1183
+ const ok = await confirm(`Remove column "${columnId}"?`);
1184
+ if (!ok) {
1185
+ console.error(dim("Cancelled."));
1186
+ return;
1187
+ }
1188
+ }
1189
+ const client = getClient();
1190
+ const result = await client.sheets.removeColumn(id, columnId, opts.tab);
1191
+ if (isJsonMode()) {
1192
+ printOne(result);
1193
+ } else {
1194
+ success(`Removed column "${columnId}"`);
1195
+ }
1196
+ })
1197
+ );
1198
+ sheets.command("add-rows <id>").description("Add rows to a sheet (pass JSON array via stdin)").option("--tab <name>", "Target sheet tab name").action(
1199
+ withErrorHandler(async (id, opts) => {
1200
+ const stdinData = await collectInput([]);
1201
+ let rows;
1202
+ if (stdinData.rows && Array.isArray(stdinData.rows)) {
1203
+ rows = stdinData.rows;
1204
+ } else if (Array.isArray(stdinData)) {
1205
+ rows = stdinData;
1206
+ } else {
1207
+ throw new CliError('Provide rows via stdin JSON: {"rows": [{...}, ...]} or [{...}, ...]');
1208
+ }
1209
+ const client = getClient();
1210
+ const result = await client.sheets.addRows(id, rows, opts.tab);
1211
+ if (isJsonMode()) {
1212
+ printOne(result);
1213
+ } else {
1214
+ success(`Added ${rows.length} row(s)`);
1215
+ }
1216
+ })
1217
+ );
1218
+ sheets.command("delete-rows <id>").description("Delete rows by index (0-based)").option("-f, --field <fields...>", "Row indices (e.g., -f 0 -f 2 -f 5)").option("--tab <name>", "Target sheet tab name").option("-y, --yes", "Skip confirmation").action(
1219
+ withErrorHandler(async (id, opts) => {
1220
+ let indices;
1221
+ const stdinData = await collectInput([]);
1222
+ if (stdinData.indices && Array.isArray(stdinData.indices)) {
1223
+ indices = stdinData.indices.map(Number);
1224
+ } else if (opts.field && opts.field.length > 0) {
1225
+ indices = opts.field.map((f) => {
1226
+ const n = parseInt(f, 10);
1227
+ if (isNaN(n)) throw new CliError(`Invalid row index "${f}"`);
1228
+ return n;
1229
+ });
1230
+ } else {
1231
+ throw new CliError('Provide row indices via -f 0 -f 2 or stdin JSON: {"indices": [0, 2]}');
1232
+ }
1233
+ if (!opts.yes && !isJsonMode()) {
1234
+ const ok = await confirm(`Delete ${indices.length} row(s)?`);
1235
+ if (!ok) {
1236
+ console.error(dim("Cancelled."));
1237
+ return;
1238
+ }
1239
+ }
1240
+ const client = getClient();
1241
+ const result = await client.sheets.deleteRows(id, indices, opts.tab);
1242
+ if (isJsonMode()) {
1243
+ printOne(result);
1244
+ } else {
1245
+ success(`Deleted ${indices.length} row(s)`);
1246
+ }
1247
+ })
1248
+ );
1249
+ }
1250
+
1251
+ // src/commands/slides.ts
1252
+ import { existsSync as existsSync3 } from "fs";
1253
+ import { readFile as readFile2 } from "fs/promises";
1254
+ import { basename as basename3, resolve as resolve3, extname as extname3 } from "path";
1255
+ var slideListColumns = [
1256
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1257
+ { key: "title", header: "Title" },
1258
+ {
1259
+ key: "updated_at",
1260
+ header: "Updated",
1261
+ format: (v) => {
1262
+ if (!v) return "";
1263
+ return new Date(String(v)).toLocaleDateString();
1264
+ }
1265
+ }
1266
+ ];
1267
+ var layoutColumns = [
1268
+ { key: "name", header: "Name" },
1269
+ { key: "description", header: "Description" }
1270
+ ];
1271
+ function registerSlidesCommands(program2) {
1272
+ const slides = program2.command("slides").description("Manage presentations");
1273
+ slides.command("list").description("List your slide decks").action(
1274
+ withErrorHandler(async () => {
1275
+ const client = getClient();
1276
+ const res = await client.docs.list({ type: "slide" });
1277
+ printTable(res.documents, slideListColumns);
1278
+ })
1279
+ );
1280
+ slides.command("create").description("Create a new slide deck").option("-f, --field <fields...>", "Set fields (title=...)").action(
1281
+ withErrorHandler(async (opts) => {
1282
+ const data = await collectInput(opts.field ?? []);
1283
+ data.type = "slide";
1284
+ const client = getClient();
1285
+ const doc = await client.docs.create(data);
1286
+ if (isJsonMode()) {
1287
+ printOne(doc);
1288
+ } else {
1289
+ success(`Created deck "${doc.title}" (${doc.id})`);
1290
+ }
1291
+ })
1292
+ );
1293
+ slides.command("get <id>").description("Get a slide document by ID").action(
1294
+ withErrorHandler(async (id) => {
1295
+ const client = getClient();
1296
+ const doc = await client.docs.get(id);
1297
+ printOne(doc);
1298
+ })
1299
+ );
1300
+ slides.command("delete <id>").description("Delete a slide deck").option("-y, --yes", "Skip confirmation").action(
1301
+ withErrorHandler(async (id, opts) => {
1302
+ if (!opts.yes && !isJsonMode()) {
1303
+ const ok = await confirm(`Delete slide deck ${id}?`);
1304
+ if (!ok) {
1305
+ console.error(dim("Cancelled."));
1306
+ return;
1307
+ }
1308
+ }
1309
+ const client = getClient();
1310
+ await client.docs.delete(id);
1311
+ success(`Deleted slide deck ${id}`);
1312
+ })
1313
+ );
1314
+ slides.command("data <id>").description("Get structured slide data (slides array + settings)").action(
1315
+ withErrorHandler(async (id) => {
1316
+ const client = getClient();
1317
+ const data = await client.slides.getData(id);
1318
+ if (isJsonMode()) {
1319
+ printOne(data);
1320
+ return;
1321
+ }
1322
+ info(`Slide count: ${data.slide_count}`);
1323
+ if (data.settings && Object.keys(data.settings).length > 0) {
1324
+ info(`Settings: ${JSON.stringify(data.settings)}`);
1325
+ }
1326
+ for (let i = 0; i < data.slides.length; i++) {
1327
+ const slide = data.slides[i];
1328
+ info(`
1329
+ --- Slide ${i} ${slide.layout ? `(${slide.layout})` : ""} ---`);
1330
+ console.log(slide.content);
1331
+ }
1332
+ })
1333
+ );
1334
+ slides.command("layouts").description("List available slide layouts").action(
1335
+ withErrorHandler(async () => {
1336
+ const client = getClient();
1337
+ const res = await client.slides.listLayouts();
1338
+ printTable(res.layouts, layoutColumns);
1339
+ })
1340
+ );
1341
+ slides.command("add-slide <id>").description("Add a new slide to a deck").option("-f, --field <fields...>", "Set fields (content=..., layout=..., after_index=...)").action(
1342
+ withErrorHandler(async (id, opts) => {
1343
+ const data = await collectInput(opts.field ?? []);
1344
+ const params = {};
1345
+ if (data.content) params.content = data.content;
1346
+ if (data.layout) params.layout = data.layout;
1347
+ if (data.after_index !== void 0) params.after_index = parseInt(String(data.after_index), 10);
1348
+ const client = getClient();
1349
+ const result = await client.slides.addSlide(id, params);
1350
+ if (isJsonMode()) {
1351
+ printOne(result);
1352
+ } else {
1353
+ success(`Added slide (${result.slide_count} total)`);
1354
+ }
1355
+ })
1356
+ );
1357
+ slides.command("update-slide <id> <index>").description("Update a specific slide").option("-f, --field <fields...>", "Set fields (content=..., layout=...)").action(
1358
+ withErrorHandler(async (id, indexStr, opts) => {
1359
+ const index = parseInt(indexStr, 10);
1360
+ if (isNaN(index)) throw new CliError(`Invalid slide index "${indexStr}"`);
1361
+ const data = await collectInput(opts.field ?? []);
1362
+ const params = {};
1363
+ if (data.content) params.content = data.content;
1364
+ if (data.layout) params.layout = data.layout;
1365
+ const client = getClient();
1366
+ const result = await client.slides.updateSlide(id, index, params);
1367
+ if (isJsonMode()) {
1368
+ printOne(result);
1369
+ } else {
1370
+ success(`Updated slide ${index}`);
1371
+ }
1372
+ })
1373
+ );
1374
+ slides.command("remove-slide <id> <index>").description("Remove a slide from a deck").option("-y, --yes", "Skip confirmation").action(
1375
+ withErrorHandler(async (id, indexStr, opts) => {
1376
+ const index = parseInt(indexStr, 10);
1377
+ if (isNaN(index)) throw new CliError(`Invalid slide index "${indexStr}"`);
1378
+ if (!opts.yes && !isJsonMode()) {
1379
+ const ok = await confirm(`Remove slide ${index}?`);
1380
+ if (!ok) {
1381
+ console.error(dim("Cancelled."));
1382
+ return;
1383
+ }
1384
+ }
1385
+ const client = getClient();
1386
+ const result = await client.slides.removeSlide(id, index);
1387
+ if (isJsonMode()) {
1388
+ printOne(result);
1389
+ } else {
1390
+ success(`Removed slide ${index} (${result.slide_count} remaining)`);
1391
+ }
1392
+ })
1393
+ );
1394
+ slides.command("reorder <id>").description("Reorder slides (--order 2,0,1,3)").requiredOption("--order <indices>", "Comma-separated slide indices in new order").action(
1395
+ withErrorHandler(async (id, opts) => {
1396
+ const order = opts.order.split(",").map((s) => {
1397
+ const n = parseInt(s.trim(), 10);
1398
+ if (isNaN(n)) throw new CliError(`Invalid index "${s}" in --order`);
1399
+ return n;
1400
+ });
1401
+ const client = getClient();
1402
+ const result = await client.slides.reorder(id, order);
1403
+ if (isJsonMode()) {
1404
+ printOne(result);
1405
+ } else {
1406
+ success(`Reordered slides: ${order.join(", ")}`);
1407
+ }
1408
+ })
1409
+ );
1410
+ slides.command("settings <id>").description("Get or update deck settings").option("-f, --field <fields...>", "Set fields (theme=..., aspect_ratio=16:9, paginate=true)").action(
1411
+ withErrorHandler(async (id, opts) => {
1412
+ const client = getClient();
1413
+ if (!opts.field || opts.field.length === 0) {
1414
+ const data2 = await client.slides.getData(id);
1415
+ printOne(data2.settings);
1416
+ return;
1417
+ }
1418
+ const data = await collectInput(opts.field);
1419
+ const params = {};
1420
+ if (data.theme) params.theme = data.theme;
1421
+ if (data.aspect_ratio) params.aspect_ratio = data.aspect_ratio;
1422
+ if (data.header) params.header = data.header;
1423
+ if (data.footer) params.footer = data.footer;
1424
+ if (data.paginate !== void 0) params.paginate = data.paginate === "true" || data.paginate === true;
1425
+ const result = await client.slides.updateSettings(id, params);
1426
+ if (isJsonMode()) {
1427
+ printOne(result);
1428
+ } else {
1429
+ success("Updated deck settings");
1430
+ }
1431
+ })
1432
+ );
1433
+ slides.command("import <file>").description("Import a file as a new slide deck (.pptx)").option("-f, --field <fields...>", "Set fields (title=...)").action(
1434
+ withErrorHandler(async (file, opts) => {
1435
+ const filePath = resolve3(file);
1436
+ if (!existsSync3(filePath)) {
1437
+ throw new CliError(`File not found: ${filePath}`);
1438
+ }
1439
+ const ext = extname3(filePath).toLowerCase();
1440
+ if (ext !== ".pptx") {
1441
+ throw new CliError(`Unsupported format "${ext}". Use .pptx`);
1442
+ }
1443
+ const fields = await collectInput(opts.field ?? []);
1444
+ const title = fields.title ?? basename3(file, ext);
1445
+ const fileContent = await readFile2(filePath);
1446
+ const fileName = basename3(filePath);
1447
+ const formData = new FormData();
1448
+ formData.append("file", new globalThis.Blob([fileContent]), fileName);
1449
+ formData.append("title", title);
1450
+ const apiUrl = getApiUrl();
1451
+ const token = getAuthToken();
1452
+ if (!token) throw new CliError("Not authenticated. Run `ambi auth login` first.", 2);
1453
+ const res = await fetch(`${apiUrl}/api/slides/import`, {
1454
+ method: "POST",
1455
+ headers: {
1456
+ "Authorization": `Bearer ${token}`
1457
+ },
1458
+ body: formData
1459
+ });
1460
+ if (!res.ok) {
1461
+ const body = await res.json().catch(() => ({}));
1462
+ throw new CliError(
1463
+ body.error || body.message || `Import failed: HTTP ${res.status}`
1464
+ );
1465
+ }
1466
+ const doc = await res.json();
1467
+ if (isJsonMode()) {
1468
+ printOne(doc);
1469
+ } else {
1470
+ success(`Imported "${doc.title}" (${doc.id})`);
1471
+ }
1472
+ })
1473
+ );
1474
+ }
1475
+
1476
+ // src/commands/chat.ts
1477
+ var channelColumns = [
1478
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1479
+ { key: "name", header: "Name" },
1480
+ { key: "type", header: "Type", width: 8 },
1481
+ { key: "description", header: "Description", format: (v) => v ? String(v).slice(0, 40) : "" }
1482
+ ];
1483
+ var messageColumns = [
1484
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1485
+ {
1486
+ key: "user",
1487
+ header: "From",
1488
+ format: (v) => {
1489
+ if (v && typeof v === "object" && "display_name" in v) {
1490
+ return String(v.display_name);
1491
+ }
1492
+ return String(v ?? "");
1493
+ }
1494
+ },
1495
+ { key: "content", header: "Content", format: (v) => String(v ?? "").slice(0, 60) },
1496
+ {
1497
+ key: "created_at",
1498
+ header: "Time",
1499
+ format: (v) => {
1500
+ if (!v) return "";
1501
+ const d = new Date(String(v));
1502
+ return d.toLocaleString();
1503
+ }
1504
+ }
1505
+ ];
1506
+ function registerChatCommands(program2) {
1507
+ const chat = program2.command("chat").description("Chat \u2014 channels, messages, reactions, threads");
1508
+ const channels = chat.command("channels").description("Manage channels");
1509
+ channels.command("list").description("List your channels").action(
1510
+ withErrorHandler(async () => {
1511
+ const client = getClient();
1512
+ const res = await client.chat.listChannels();
1513
+ printTable(res.channels, channelColumns);
1514
+ })
1515
+ );
1516
+ channels.command("create").description("Create a channel").option("-f, --field <fields...>", "Set fields (name=..., type=public|private|dm, description=..., member_ids=[...])").action(
1517
+ withErrorHandler(async (opts) => {
1518
+ const data = await collectInput(opts.field ?? []);
1519
+ if (!data.type) data.type = "public";
1520
+ const client = getClient();
1521
+ const ch = await client.chat.createChannel(data);
1522
+ if (isJsonMode()) {
1523
+ printOne(ch);
1524
+ } else {
1525
+ success(`Channel created: ${ch.id}`);
1526
+ printOne(ch);
1527
+ }
1528
+ })
1529
+ );
1530
+ channels.command("get <id>").description("Get channel details").action(
1531
+ withErrorHandler(async (id) => {
1532
+ const client = getClient();
1533
+ const ch = await client.chat.getChannel(id);
1534
+ printOne(ch);
1535
+ })
1536
+ );
1537
+ channels.command("update <id>").description("Update a channel").option("-f, --field <fields...>", "Set fields (name=..., description=...)").action(
1538
+ withErrorHandler(async (id, opts) => {
1539
+ const data = await collectInput(opts.field ?? []);
1540
+ const client = getClient();
1541
+ const ch = await client.chat.updateChannel(id, data);
1542
+ if (isJsonMode()) {
1543
+ printOne(ch);
1544
+ } else {
1545
+ success(`Channel updated: ${id}`);
1546
+ }
1547
+ })
1548
+ );
1549
+ channels.command("delete <id>").description("Delete a channel").option("-y, --yes", "Skip confirmation").action(
1550
+ withErrorHandler(async (id, opts) => {
1551
+ if (!opts.yes && !isJsonMode()) {
1552
+ const ok = await confirm(`Delete channel ${id}?`);
1553
+ if (!ok) {
1554
+ console.error(dim("Cancelled."));
1555
+ return;
1556
+ }
1557
+ }
1558
+ const client = getClient();
1559
+ await client.chat.deleteChannel(id);
1560
+ success(`Channel deleted: ${id}`);
1561
+ })
1562
+ );
1563
+ const messages = chat.command("messages").description("Send and manage messages");
1564
+ messages.command("list <channelId>").description("List messages in a channel").option("--limit <n>", "Number of messages to fetch", "50").option("--before <msgId>", "Fetch messages before this ID (pagination)").action(
1565
+ withErrorHandler(async (channelId, opts) => {
1566
+ const client = getClient();
1567
+ const params = {};
1568
+ if (opts.limit) params.limit = parseInt(opts.limit, 10);
1569
+ if (opts.before) params.before = opts.before;
1570
+ const res = await client.chat.listMessages(channelId, params);
1571
+ printTable(res.messages, messageColumns);
1572
+ })
1573
+ );
1574
+ messages.command("send <channelId>").description("Send a message").option("-f, --field <fields...>", "Set fields (content=..., thread_id=...)").action(
1575
+ withErrorHandler(async (channelId, opts) => {
1576
+ const data = await collectInput(opts.field ?? []);
1577
+ if (!data.content) {
1578
+ throw new CliError('Message content is required. Use -f content="your message"');
1579
+ }
1580
+ const client = getClient();
1581
+ const msg = await client.chat.sendMessage(channelId, data);
1582
+ if (isJsonMode()) {
1583
+ printOne(msg);
1584
+ } else {
1585
+ success(`Message sent: ${msg.id}`);
1586
+ }
1587
+ })
1588
+ );
1589
+ messages.command("edit <channelId> <messageId>").description("Edit a message").option("-f, --field <fields...>", "Set fields (content=...)").action(
1590
+ withErrorHandler(async (channelId, messageId, opts) => {
1591
+ const data = await collectInput(opts.field ?? []);
1592
+ if (!data.content) {
1593
+ throw new CliError('Message content is required. Use -f content="new content"');
1594
+ }
1595
+ const client = getClient();
1596
+ const msg = await client.chat.editMessage(channelId, messageId, String(data.content));
1597
+ if (isJsonMode()) {
1598
+ printOne(msg);
1599
+ } else {
1600
+ success(`Message updated: ${messageId}`);
1601
+ }
1602
+ })
1603
+ );
1604
+ messages.command("delete <channelId> <messageId>").description("Delete a message").action(
1605
+ withErrorHandler(async (channelId, messageId) => {
1606
+ const client = getClient();
1607
+ await client.chat.deleteMessage(channelId, messageId);
1608
+ success(`Message deleted: ${messageId}`);
1609
+ })
1610
+ );
1611
+ messages.command("search").description("Search messages across channels").requiredOption("-f, --field <fields...>", "Search fields (q=..., channel_id=..., limit=...)").action(
1612
+ withErrorHandler(async (opts) => {
1613
+ const data = await collectInput(opts.field ?? []);
1614
+ if (!data.q) {
1615
+ throw new CliError('Search query is required. Use -f q="search term"');
1616
+ }
1617
+ const client = getClient();
1618
+ const query = { q: String(data.q) };
1619
+ if (data.channel_id) query.channel_id = String(data.channel_id);
1620
+ if (data.limit) query.limit = String(data.limit);
1621
+ const res = await client.request("GET", "/api/channels/messages/search", void 0, query);
1622
+ const messages2 = res.messages ?? res;
1623
+ printTable(messages2, messageColumns);
1624
+ })
1625
+ );
1626
+ chat.command("react <channelId> <messageId> <emoji>").description("Add a reaction to a message").action(
1627
+ withErrorHandler(async (channelId, messageId, emoji) => {
1628
+ const client = getClient();
1629
+ await client.chat.addReaction(channelId, messageId, emoji);
1630
+ success(`Reaction ${emoji} added to ${messageId}`);
1631
+ })
1632
+ );
1633
+ chat.command("unreact <channelId> <messageId> <emoji>").description("Remove a reaction from a message").action(
1634
+ withErrorHandler(async (channelId, messageId, emoji) => {
1635
+ const client = getClient();
1636
+ await client.chat.removeReaction(channelId, messageId, emoji);
1637
+ success(`Reaction ${emoji} removed from ${messageId}`);
1638
+ })
1639
+ );
1640
+ chat.command("thread <channelId> <messageId>").description("Get thread replies for a message").action(
1641
+ withErrorHandler(async (channelId, messageId) => {
1642
+ const client = getClient();
1643
+ const res = await client.chat.getThread(channelId, messageId);
1644
+ printTable(res.messages, messageColumns);
1645
+ })
1646
+ );
1647
+ chat.command("add-member <channelId>").description("Add member(s) to a channel").option("-f, --field <fields...>", "Set fields (user_ids=[...] or user_id=...)").action(
1648
+ withErrorHandler(async (channelId, opts) => {
1649
+ const data = await collectInput(opts.field ?? []);
1650
+ let userIds;
1651
+ if (data.user_ids && Array.isArray(data.user_ids)) {
1652
+ userIds = data.user_ids;
1653
+ } else if (data.user_id) {
1654
+ userIds = [String(data.user_id)];
1655
+ } else {
1656
+ throw new CliError('Specify user(s): -f user_id=<id> or -f user_ids=["id1","id2"]');
1657
+ }
1658
+ const client = getClient();
1659
+ const res = await client.chat.addMembers(channelId, userIds);
1660
+ success(`Added ${userIds.length} member(s) to channel ${channelId}`);
1661
+ if (isJsonMode()) printOne(res);
1662
+ })
1663
+ );
1664
+ chat.command("remove-member <channelId> <userId>").description("Remove a member from a channel").action(
1665
+ withErrorHandler(async (channelId, userId) => {
1666
+ const client = getClient();
1667
+ await client.chat.removeMember(channelId, userId);
1668
+ success(`Removed ${userId} from channel ${channelId}`);
1669
+ })
1670
+ );
1671
+ }
1672
+
1673
+ // src/commands/mail.ts
1674
+ var emailListColumns = [
1675
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1676
+ {
1677
+ key: "from",
1678
+ header: "From",
1679
+ format: (v) => {
1680
+ if (v && typeof v === "object" && "display_name" in v) {
1681
+ return String(v.display_name);
1682
+ }
1683
+ return String(v ?? "");
1684
+ }
1685
+ },
1686
+ { key: "subject", header: "Subject", format: (v) => String(v ?? "").slice(0, 50) },
1687
+ {
1688
+ key: "read",
1689
+ header: "Read",
1690
+ width: 5,
1691
+ format: (v) => v ? "yes" : "no"
1692
+ },
1693
+ {
1694
+ key: "starred",
1695
+ header: "Star",
1696
+ width: 5,
1697
+ format: (v) => v ? "*" : ""
1698
+ },
1699
+ {
1700
+ key: "sent_at",
1701
+ header: "Date",
1702
+ format: (v) => {
1703
+ if (!v) return "";
1704
+ const d = new Date(String(v));
1705
+ return d.toLocaleDateString();
1706
+ }
1707
+ }
1708
+ ];
1709
+ function buildListOpts(opts) {
1710
+ const query = {};
1711
+ if (opts.limit) query.limit = opts.limit;
1712
+ if (opts.offset) query.offset = opts.offset;
1713
+ return query;
1714
+ }
1715
+ function registerMailCommands(program2) {
1716
+ const mail = program2.command("mail").description("Mail \u2014 inbox, send, read, star, signature");
1717
+ mail.command("inbox").description("List inbox emails").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").option("--unread", "Show only unread emails").action(
1718
+ withErrorHandler(async (opts) => {
1719
+ const client = getClient();
1720
+ const res = await client.mail.inbox({
1721
+ limit: parseInt(opts.limit, 10),
1722
+ offset: parseInt(opts.offset, 10),
1723
+ ...opts.unread ? { unread: true } : {}
1724
+ });
1725
+ printTable(res.emails, emailListColumns);
1726
+ })
1727
+ );
1728
+ mail.command("sent").description("List sent emails").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(
1729
+ withErrorHandler(async (opts) => {
1730
+ const client = getClient();
1731
+ const res = await client.mail.sent({
1732
+ limit: parseInt(opts.limit, 10),
1733
+ offset: parseInt(opts.offset, 10)
1734
+ });
1735
+ printTable(res.emails, emailListColumns);
1736
+ })
1737
+ );
1738
+ mail.command("starred").description("List starred emails").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(
1739
+ withErrorHandler(async (opts) => {
1740
+ const client = getClient();
1741
+ const res = await client.request("GET", "/api/mail/starred", void 0, buildListOpts(opts));
1742
+ printTable(res.emails, emailListColumns);
1743
+ })
1744
+ );
1745
+ mail.command("drafts").description("List draft emails").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(
1746
+ withErrorHandler(async (opts) => {
1747
+ const client = getClient();
1748
+ const res = await client.request("GET", "/api/mail/drafts", void 0, buildListOpts(opts));
1749
+ printTable(res.emails, emailListColumns);
1750
+ })
1751
+ );
1752
+ mail.command("snoozed").description("List snoozed emails").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(
1753
+ withErrorHandler(async (opts) => {
1754
+ const client = getClient();
1755
+ const res = await client.request("GET", "/api/mail/snoozed", void 0, buildListOpts(opts));
1756
+ printTable(res.emails, emailListColumns);
1757
+ })
1758
+ );
1759
+ mail.command("get <id>").description("Get email details (marks as read)").action(
1760
+ withErrorHandler(async (id) => {
1761
+ const client = getClient();
1762
+ const email = await client.mail.get(id);
1763
+ printOne(email);
1764
+ })
1765
+ );
1766
+ mail.command("send").description("Send an email").option("-f, --field <fields...>", "Set fields (to=a@b.com,c@d.com subject=... body=... cc=... bcc=... from_email=...)").action(
1767
+ withErrorHandler(async (opts) => {
1768
+ const data = await collectInput(opts.field ?? []);
1769
+ for (const key of ["to", "cc", "bcc"]) {
1770
+ if (typeof data[key] === "string") {
1771
+ data[key] = data[key].split(",").map((s) => s.trim());
1772
+ }
1773
+ }
1774
+ const client = getClient();
1775
+ const email = await client.mail.send(data);
1776
+ if (isJsonMode()) {
1777
+ printOne(email);
1778
+ } else {
1779
+ success(`Email sent: ${email.id}`);
1780
+ printOne(email);
1781
+ }
1782
+ })
1783
+ );
1784
+ mail.command("delete <id>").description("Delete an email").action(
1785
+ withErrorHandler(async (id) => {
1786
+ const client = getClient();
1787
+ await client.mail.delete(id);
1788
+ success(`Email deleted: ${id}`);
1789
+ })
1790
+ );
1791
+ mail.command("star <id>").description("Star an email").action(
1792
+ withErrorHandler(async (id) => {
1793
+ const client = getClient();
1794
+ await client.request("PATCH", `/api/mail/${id}`, { starred: true });
1795
+ success(`Email starred: ${id}`);
1796
+ })
1797
+ );
1798
+ mail.command("unstar <id>").description("Unstar an email").action(
1799
+ withErrorHandler(async (id) => {
1800
+ const client = getClient();
1801
+ await client.request("PATCH", `/api/mail/${id}`, { starred: false });
1802
+ success(`Email unstarred: ${id}`);
1803
+ })
1804
+ );
1805
+ mail.command("read <id>").description("Mark an email as read").action(
1806
+ withErrorHandler(async (id) => {
1807
+ const client = getClient();
1808
+ await client.mail.markRead(id, true);
1809
+ success(`Email marked as read: ${id}`);
1810
+ })
1811
+ );
1812
+ mail.command("unread <id>").description("Mark an email as unread").action(
1813
+ withErrorHandler(async (id) => {
1814
+ const client = getClient();
1815
+ await client.mail.markRead(id, false);
1816
+ success(`Email marked as unread: ${id}`);
1817
+ })
1818
+ );
1819
+ mail.command("snooze <id> <until>").description("Snooze email until a datetime (ISO 8601)").action(
1820
+ withErrorHandler(async (id, until) => {
1821
+ const client = getClient();
1822
+ await client.request("PATCH", `/api/mail/${id}`, { snoozed_until: until });
1823
+ success(`Email snoozed until ${until}: ${id}`);
1824
+ })
1825
+ );
1826
+ mail.command("unsnooze <id>").description("Unsnooze an email").action(
1827
+ withErrorHandler(async (id) => {
1828
+ const client = getClient();
1829
+ await client.request("PATCH", `/api/mail/${id}`, { snoozed_until: null });
1830
+ success(`Email unsnoozed: ${id}`);
1831
+ })
1832
+ );
1833
+ const sendAs = mail.command("send-as").description("Manage send-as addresses");
1834
+ sendAs.command("list").description("List available send-as addresses").action(
1835
+ withErrorHandler(async () => {
1836
+ const client = getClient();
1837
+ const res = await client.request("GET", "/api/mail/send-as");
1838
+ if (isJsonMode()) {
1839
+ printOne(res);
1840
+ } else {
1841
+ const cols = [
1842
+ { key: "email", header: "Email" },
1843
+ { key: "source", header: "Source" },
1844
+ { key: "is_default", header: "Default", width: 8, format: (v) => v ? "yes" : "" }
1845
+ ];
1846
+ printTable(res.addresses ?? [], cols);
1847
+ }
1848
+ })
1849
+ );
1850
+ sendAs.command("set <email>").description("Set default send-as address").action(
1851
+ withErrorHandler(async (email) => {
1852
+ const client = getClient();
1853
+ const res = await client.request("PATCH", "/api/mail/send-as", { email });
1854
+ if (isJsonMode()) {
1855
+ printOne(res);
1856
+ } else {
1857
+ success(`Default send-as set to: ${res.default_send_as ?? email}`);
1858
+ }
1859
+ })
1860
+ );
1861
+ const signature = mail.command("signature").description("Manage email signature");
1862
+ signature.command("get").description("Get current email signature").action(
1863
+ withErrorHandler(async () => {
1864
+ const client = getClient();
1865
+ const res = await client.request("GET", "/api/mail/signature");
1866
+ if (isJsonMode()) {
1867
+ printOne(res);
1868
+ } else {
1869
+ console.log(dim("Source: ") + res.source);
1870
+ console.log(dim("Signature:"));
1871
+ console.log(res.effective || "(none)");
1872
+ }
1873
+ })
1874
+ );
1875
+ signature.command("set").description("Set personal email signature").option("-f, --field <fields...>", "Set fields (markdown=...)").action(
1876
+ withErrorHandler(async (opts) => {
1877
+ const data = await collectInput(opts.field ?? []);
1878
+ const client = getClient();
1879
+ const res = await client.request("PUT", "/api/mail/signature", { markdown: data.markdown ?? data.content ?? "" });
1880
+ if (isJsonMode()) {
1881
+ printOne(res);
1882
+ } else {
1883
+ success("Signature updated");
1884
+ console.log(res.effective || "(none)");
1885
+ }
1886
+ })
1887
+ );
1888
+ signature.command("clear").description("Clear personal signature (use workspace default)").action(
1889
+ withErrorHandler(async () => {
1890
+ const client = getClient();
1891
+ const res = await client.request("PUT", "/api/mail/signature", { markdown: null });
1892
+ if (isJsonMode()) {
1893
+ printOne(res);
1894
+ } else {
1895
+ success("Personal signature cleared. Using workspace default.");
1896
+ }
1897
+ })
1898
+ );
1899
+ }
1900
+
1901
+ // src/commands/tasks.ts
1902
+ var taskColumns = [
1903
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1904
+ { key: "title", header: "Title" },
1905
+ { key: "status", header: "Status", width: 12 },
1906
+ { key: "priority", header: "Priority", width: 8 },
1907
+ {
1908
+ key: "assignee",
1909
+ header: "Assignee",
1910
+ width: 16,
1911
+ format: (v) => {
1912
+ if (!v || typeof v !== "object") return "";
1913
+ return v.display_name ?? "";
1914
+ }
1915
+ },
1916
+ {
1917
+ key: "due_date",
1918
+ header: "Due",
1919
+ width: 12,
1920
+ format: (v) => v ? String(v) : ""
1921
+ }
1922
+ ];
1923
+ function registerTasksCommands(program2) {
1924
+ const tasks = program2.command("tasks").description("Manage tasks");
1925
+ tasks.command("list").description("List tasks").option("--assignee <userId>", "Filter by assignee").option("--status <status>", "Filter by status (todo, in_progress, done, cancelled)").option("--priority <priority>", "Filter by priority (urgent, high, medium, low)").option("--project <projectId>", "Filter by project").option("--creator <userId>", "Filter by creator").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").option("-f, --field <fields...>", "Field filters (key=value)").action(
1926
+ withErrorHandler(async (opts) => {
1927
+ const client = getClient();
1928
+ const params = {};
1929
+ if (opts.assignee) params.assignee_id = opts.assignee;
1930
+ if (opts.status) params.status = opts.status;
1931
+ if (opts.priority) params.priority = opts.priority;
1932
+ if (opts.project) params.project_id = opts.project;
1933
+ if (opts.creator) params.creator_id = opts.creator;
1934
+ if (opts.limit) params.limit = parseInt(opts.limit, 10);
1935
+ if (opts.offset && opts.offset !== "0") params.offset = parseInt(opts.offset, 10);
1936
+ const res = await client.tasks.list(params);
1937
+ printTable(res.tasks, taskColumns);
1938
+ })
1939
+ );
1940
+ tasks.command("create").description("Create a task").option("-f, --field <fields...>", "Set fields (title=..., status=..., priority=..., assignee_id=..., due_date=..., description=..., project_id=..., parent_task_id=...)").action(
1941
+ withErrorHandler(async (opts) => {
1942
+ const data = await collectInput(opts.field ?? []);
1943
+ const client = getClient();
1944
+ const task = await client.tasks.create(data);
1945
+ if (isJsonMode()) {
1946
+ printOne(task);
1947
+ } else {
1948
+ success(`Task created: ${task.id} \u2014 ${task.title}`);
1949
+ }
1950
+ })
1951
+ );
1952
+ tasks.command("get <id>").description("Get a task by ID").action(
1953
+ withErrorHandler(async (id) => {
1954
+ const client = getClient();
1955
+ const task = await client.tasks.get(id);
1956
+ printOne(task);
1957
+ })
1958
+ );
1959
+ tasks.command("update <id>").description("Update a task").option("-f, --field <fields...>", "Set fields (title=..., status=..., priority=..., assignee_id=..., due_date=..., description=...)").action(
1960
+ withErrorHandler(async (id, opts) => {
1961
+ const data = await collectInput(opts.field ?? []);
1962
+ const client = getClient();
1963
+ const task = await client.tasks.update(id, data);
1964
+ if (isJsonMode()) {
1965
+ printOne(task);
1966
+ } else {
1967
+ success(`Task updated: ${task.id}`);
1968
+ }
1969
+ })
1970
+ );
1971
+ tasks.command("delete <id>").description("Delete a task").action(
1972
+ withErrorHandler(async (id) => {
1973
+ const client = getClient();
1974
+ await client.tasks.delete(id);
1975
+ success(`Task deleted: ${id}`);
1976
+ })
1977
+ );
1978
+ tasks.command("subtasks <id>").description("List subtasks of a task").action(
1979
+ withErrorHandler(async (id) => {
1980
+ const client = getClient();
1981
+ const res = await client.tasks.listSubtasks(id);
1982
+ printTable(res.tasks, taskColumns);
1983
+ })
1984
+ );
1985
+ }
1986
+
1987
+ // src/commands/drive.ts
1988
+ import { createReadStream, statSync, existsSync as existsSync4 } from "fs";
1989
+ import { writeFile as writeFile2 } from "fs/promises";
1990
+ import { basename as basename4, resolve as resolve4 } from "path";
1991
+ import { lookup } from "mime-types";
1992
+ var fileColumns = [
1993
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
1994
+ { key: "name", header: "Name" },
1995
+ {
1996
+ key: "is_folder",
1997
+ header: "Type",
1998
+ width: 8,
1999
+ format: (v) => v ? "folder" : "file"
2000
+ },
2001
+ {
2002
+ key: "size_bytes",
2003
+ header: "Size",
2004
+ width: 10,
2005
+ align: "right",
2006
+ format: (v) => {
2007
+ if (v == null) return "\u2014";
2008
+ const n = Number(v);
2009
+ if (n < 1024) return `${n} B`;
2010
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
2011
+ if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
2012
+ return `${(n / (1024 * 1024 * 1024)).toFixed(1)} GB`;
2013
+ }
2014
+ },
2015
+ {
2016
+ key: "updated_at",
2017
+ header: "Updated",
2018
+ format: (v) => {
2019
+ if (!v) return "";
2020
+ return new Date(String(v)).toLocaleDateString();
2021
+ }
2022
+ }
2023
+ ];
2024
+ function registerDriveCommands(program2) {
2025
+ const drive = program2.command("drive").description("Manage files and folders");
2026
+ drive.command("list [folderId]").description("List files in a folder (or root)").option("-q, --query <search>", "Search query").action(
2027
+ withErrorHandler(async (folderId, opts) => {
2028
+ const client = getClient();
2029
+ const params = {};
2030
+ if (folderId) params.parent_id = folderId;
2031
+ if (opts.query) params.q = opts.query;
2032
+ const res = await client.drive.list(params);
2033
+ printTable(res.files, fileColumns);
2034
+ })
2035
+ );
2036
+ drive.command("get <id>").description("Get file/folder metadata").action(
2037
+ withErrorHandler(async (id) => {
2038
+ const client = getClient();
2039
+ const file = await client.drive.get(id);
2040
+ printOne(file);
2041
+ })
2042
+ );
2043
+ drive.command("upload <file>").description("Upload a file").option("--folder <id>", "Parent folder ID").option("--name <name>", "Override file name").action(
2044
+ withErrorHandler(async (filePath, opts) => {
2045
+ const absPath = resolve4(filePath);
2046
+ if (!existsSync4(absPath)) {
2047
+ throw new CliError(`File not found: ${absPath}`);
2048
+ }
2049
+ const stat = statSync(absPath);
2050
+ const name = opts.name ?? basename4(absPath);
2051
+ const mimeType = lookup(absPath) || "application/octet-stream";
2052
+ const client = getClient();
2053
+ const initRes = await client.drive.initiateUpload({
2054
+ name,
2055
+ mime_type: mimeType,
2056
+ size_bytes: stat.size,
2057
+ parent_id: opts.folder
2058
+ });
2059
+ const uploadRes = await fetch(initRes.upload_url, {
2060
+ method: "PUT",
2061
+ headers: { "Content-Type": mimeType, "Content-Length": String(stat.size) },
2062
+ body: createReadStream(absPath),
2063
+ // @ts-expect-error duplex is required for streaming body in Node.js but not in RequestInit types
2064
+ duplex: "half"
2065
+ });
2066
+ if (!uploadRes.ok) {
2067
+ throw new CliError(`Upload failed: HTTP ${uploadRes.status}`);
2068
+ }
2069
+ if (isJsonMode()) {
2070
+ printOne(initRes.file);
2071
+ } else {
2072
+ success(`Uploaded: ${name} (${initRes.file.id})`);
2073
+ }
2074
+ })
2075
+ );
2076
+ drive.command("download <id> [dest]").description("Download a file").action(
2077
+ withErrorHandler(async (id, dest) => {
2078
+ const client = getClient();
2079
+ const file = await client.drive.get(id);
2080
+ const destPath = dest ? resolve4(dest) : resolve4(basename4(file.name));
2081
+ const apiUrl = getApiUrl();
2082
+ const token = getAuthToken();
2083
+ const res = await fetch(`${apiUrl}/api/drive/${id}/content`, {
2084
+ headers: {
2085
+ Authorization: `Bearer ${token}`
2086
+ }
2087
+ });
2088
+ if (!res.ok) {
2089
+ throw new CliError(`Download failed: HTTP ${res.status}`);
2090
+ }
2091
+ const buffer = Buffer.from(await res.arrayBuffer());
2092
+ await writeFile2(destPath, buffer);
2093
+ if (isJsonMode()) {
2094
+ printOne({ id, name: file.name, path: destPath, size_bytes: buffer.length });
2095
+ } else {
2096
+ success(`Downloaded: ${file.name} \u2192 ${destPath} (${buffer.length} bytes)`);
2097
+ }
2098
+ })
2099
+ );
2100
+ drive.command("mkdir <name>").description("Create a folder").option("--parent <id>", "Parent folder ID").action(
2101
+ withErrorHandler(async (name, opts) => {
2102
+ const client = getClient();
2103
+ const folder = await client.drive.createFolder({
2104
+ name,
2105
+ parent_id: opts.parent
2106
+ });
2107
+ if (isJsonMode()) {
2108
+ printOne(folder);
2109
+ } else {
2110
+ success(`Folder created: ${folder.id} \u2014 ${name}`);
2111
+ }
2112
+ })
2113
+ );
2114
+ drive.command("mv <id> <parentId>").description("Move a file/folder to a new parent").action(
2115
+ withErrorHandler(async (id, parentId) => {
2116
+ const client = getClient();
2117
+ const file = await client.drive.update(id, { parent_id: parentId });
2118
+ if (isJsonMode()) {
2119
+ printOne(file);
2120
+ } else {
2121
+ success(`Moved: ${file.name} \u2192 folder ${parentId}`);
2122
+ }
2123
+ })
2124
+ );
2125
+ drive.command("rename <id> <name>").description("Rename a file/folder").action(
2126
+ withErrorHandler(async (id, name) => {
2127
+ const client = getClient();
2128
+ const file = await client.drive.update(id, { name });
2129
+ if (isJsonMode()) {
2130
+ printOne(file);
2131
+ } else {
2132
+ success(`Renamed to: ${name}`);
2133
+ }
2134
+ })
2135
+ );
2136
+ drive.command("delete <id>").description("Delete a file or folder").option("-y, --yes", "Skip confirmation").action(
2137
+ withErrorHandler(async (id, opts) => {
2138
+ if (!opts.yes && !isJsonMode()) {
2139
+ const ok = await confirm(`Delete ${id}?`);
2140
+ if (!ok) {
2141
+ console.error(dim("Cancelled."));
2142
+ return;
2143
+ }
2144
+ }
2145
+ const client = getClient();
2146
+ await client.drive.delete(id);
2147
+ success(`Deleted: ${id}`);
2148
+ })
2149
+ );
2150
+ drive.command("storage").description("Show storage usage").action(
2151
+ withErrorHandler(async () => {
2152
+ const client = getClient();
2153
+ const res = await client.request("GET", "/api/admin/usage");
2154
+ if (isJsonMode()) {
2155
+ printOne(res);
2156
+ } else {
2157
+ const used = res.storage_used_bytes ?? res.storageUsedBytes ?? 0;
2158
+ const total = res.storage_limit_bytes ?? res.storageLimitBytes ?? 0;
2159
+ const usedMB = (Number(used) / (1024 * 1024)).toFixed(1);
2160
+ const totalGB = total ? (Number(total) / (1024 * 1024 * 1024)).toFixed(1) : "\u221E";
2161
+ success(`Storage: ${usedMB} MB used of ${totalGB} GB`);
2162
+ }
2163
+ })
2164
+ );
2165
+ }
2166
+
2167
+ // src/commands/calendar.ts
2168
+ import { writeFile as writeFile3 } from "fs/promises";
2169
+ import { resolve as resolve5 } from "path";
2170
+ var calendarColumns = [
2171
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2172
+ { key: "name", header: "Name" },
2173
+ { key: "timezone", header: "Timezone", width: 24 },
2174
+ { key: "color", header: "Color", width: 8 },
2175
+ {
2176
+ key: "is_default",
2177
+ header: "Default",
2178
+ width: 8,
2179
+ format: (v) => v ? "Yes" : ""
2180
+ }
2181
+ ];
2182
+ var eventColumns = [
2183
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2184
+ { key: "title", header: "Title" },
2185
+ {
2186
+ key: "start_at",
2187
+ header: "Start",
2188
+ width: 18,
2189
+ format: (v) => {
2190
+ if (!v) return "";
2191
+ const d = new Date(String(v));
2192
+ return d.toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
2193
+ }
2194
+ },
2195
+ {
2196
+ key: "end_at",
2197
+ header: "End",
2198
+ width: 18,
2199
+ format: (v) => {
2200
+ if (!v) return "";
2201
+ const d = new Date(String(v));
2202
+ return d.toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
2203
+ }
2204
+ },
2205
+ { key: "status", header: "Status", width: 12 },
2206
+ { key: "location", header: "Location", width: 20, format: (v) => String(v ?? "") }
2207
+ ];
2208
+ var busyColumns = [
2209
+ { key: "user_id", header: "User", width: 12, format: (v) => String(v).slice(0, 12) },
2210
+ {
2211
+ key: "start",
2212
+ header: "Busy Start",
2213
+ width: 20,
2214
+ format: (v) => v ? new Date(String(v)).toLocaleString() : ""
2215
+ },
2216
+ {
2217
+ key: "end",
2218
+ header: "Busy End",
2219
+ width: 20,
2220
+ format: (v) => v ? new Date(String(v)).toLocaleString() : ""
2221
+ },
2222
+ { key: "title", header: "Title", format: (v) => String(v ?? "") }
2223
+ ];
2224
+ function registerCalendarCommands(program2) {
2225
+ const cal = program2.command("calendar").description("Manage calendars and events");
2226
+ cal.command("list").description("List your calendars").action(
2227
+ withErrorHandler(async () => {
2228
+ const client = getClient();
2229
+ const res = await client.calendar.listCalendars();
2230
+ printTable(res.calendars, calendarColumns);
2231
+ })
2232
+ );
2233
+ cal.command("create").description("Create a calendar").option("-f, --field <fields...>", "Set fields (name=..., color=..., timezone=...)").action(
2234
+ withErrorHandler(async (opts) => {
2235
+ const data = await collectInput(opts.field ?? []);
2236
+ const client = getClient();
2237
+ const calendar = await client.calendar.createCalendar(data);
2238
+ if (isJsonMode()) {
2239
+ printOne(calendar);
2240
+ } else {
2241
+ success(`Calendar created: ${calendar.id} \u2014 ${calendar.name}`);
2242
+ }
2243
+ })
2244
+ );
2245
+ cal.command("update <id>").description("Update a calendar").option("-f, --field <fields...>", "Set fields (name=..., color=..., timezone=...)").action(
2246
+ withErrorHandler(async (id, opts) => {
2247
+ const data = await collectInput(opts.field ?? []);
2248
+ const client = getClient();
2249
+ const calendar = await client.calendar.updateCalendar(id, data);
2250
+ if (isJsonMode()) {
2251
+ printOne(calendar);
2252
+ } else {
2253
+ success(`Calendar updated: ${calendar.id}`);
2254
+ }
2255
+ })
2256
+ );
2257
+ cal.command("delete <id>").description("Delete a calendar").option("-y, --yes", "Skip confirmation").action(
2258
+ withErrorHandler(async (id, opts) => {
2259
+ if (!opts.yes && !isJsonMode()) {
2260
+ const ok = await confirm(`Delete calendar ${id}?`);
2261
+ if (!ok) {
2262
+ console.error(dim("Cancelled."));
2263
+ return;
2264
+ }
2265
+ }
2266
+ const client = getClient();
2267
+ await client.calendar.deleteCalendar(id);
2268
+ success(`Calendar deleted: ${id}`);
2269
+ })
2270
+ );
2271
+ const events = cal.command("events").description("Manage calendar events");
2272
+ events.command("list").description("List events").option("--start <datetime>", "Start of date range (ISO 8601)").option("--end <datetime>", "End of date range (ISO 8601)").option("--calendar <id>", "Filter by calendar ID").action(
2273
+ withErrorHandler(async (opts) => {
2274
+ const client = getClient();
2275
+ const params = {};
2276
+ if (opts.start) params.start = opts.start;
2277
+ if (opts.end) params.end = opts.end;
2278
+ if (opts.calendar) params.calendar_id = opts.calendar;
2279
+ const res = await client.calendar.listEvents(params);
2280
+ printTable(res.events, eventColumns);
2281
+ })
2282
+ );
2283
+ events.command("create <calendarId>").description("Create an event").option("-f, --field <fields...>", "Set fields (title=..., start_at=..., end_at=..., description=..., location=..., all_day=..., recurrence_rule=..., attendees=[...])").action(
2284
+ withErrorHandler(async (calendarId, opts) => {
2285
+ const data = await collectInput(opts.field ?? []);
2286
+ const client = getClient();
2287
+ const event = await client.calendar.createEvent(calendarId, data);
2288
+ if (isJsonMode()) {
2289
+ printOne(event);
2290
+ } else {
2291
+ success(`Event created: ${event.id} \u2014 ${event.title}`);
2292
+ }
2293
+ })
2294
+ );
2295
+ events.command("get <id>").description("Get event details").action(
2296
+ withErrorHandler(async (id) => {
2297
+ const client = getClient();
2298
+ const event = await client.calendar.getEvent(id);
2299
+ printOne(event);
2300
+ })
2301
+ );
2302
+ events.command("update <id>").description("Update an event").option("-f, --field <fields...>", "Set fields (title=..., start_at=..., end_at=..., description=..., location=..., attendees=[...])").action(
2303
+ withErrorHandler(async (id, opts) => {
2304
+ const data = await collectInput(opts.field ?? []);
2305
+ const client = getClient();
2306
+ const event = await client.calendar.updateEvent(id, data);
2307
+ if (isJsonMode()) {
2308
+ printOne(event);
2309
+ } else {
2310
+ success(`Event updated: ${event.id}`);
2311
+ }
2312
+ })
2313
+ );
2314
+ events.command("delete <id>").description("Delete an event").option("-y, --yes", "Skip confirmation").action(
2315
+ withErrorHandler(async (id, opts) => {
2316
+ if (!opts.yes && !isJsonMode()) {
2317
+ const ok = await confirm(`Delete event ${id}?`);
2318
+ if (!ok) {
2319
+ console.error(dim("Cancelled."));
2320
+ return;
2321
+ }
2322
+ }
2323
+ const client = getClient();
2324
+ await client.calendar.deleteEvent(id);
2325
+ success(`Event deleted: ${id}`);
2326
+ })
2327
+ );
2328
+ events.command("rsvp <id>").description("RSVP to an event").option("--accept", "Accept the invitation").option("--decline", "Decline the invitation").option("--tentative", "Mark as tentative").action(
2329
+ withErrorHandler(async (id, opts) => {
2330
+ let status;
2331
+ if (opts.accept) status = "accepted";
2332
+ else if (opts.decline) status = "declined";
2333
+ else if (opts.tentative) status = "tentative";
2334
+ else throw new CliError("Specify --accept, --decline, or --tentative");
2335
+ const client = getClient();
2336
+ const res = await client.calendar.rsvp(id, status);
2337
+ if (isJsonMode()) {
2338
+ printOne(res);
2339
+ } else {
2340
+ success(`RSVP: ${status} for event ${id}`);
2341
+ }
2342
+ })
2343
+ );
2344
+ cal.command("availability").description("Check free/busy availability").requiredOption("--users <userIds>", "Comma-separated user IDs").requiredOption("--start <datetime>", "Start of time range (ISO 8601)").requiredOption("--end <datetime>", "End of time range (ISO 8601)").action(
2345
+ withErrorHandler(async (opts) => {
2346
+ const client = getClient();
2347
+ const userIds = opts.users.split(",").map((s) => s.trim());
2348
+ const res = await client.calendar.checkAvailability({
2349
+ user_ids: userIds,
2350
+ start: opts.start,
2351
+ end: opts.end
2352
+ });
2353
+ printTable(res.busy, busyColumns);
2354
+ })
2355
+ );
2356
+ cal.command("export <id>").description("Export event as .ics").option("-o, --output <file>", "Output file path").action(
2357
+ withErrorHandler(async (id, opts) => {
2358
+ const apiUrl = getApiUrl();
2359
+ const token = getAuthToken();
2360
+ const res = await fetch(`${apiUrl}/api/calendars/events/${id}/export`, {
2361
+ headers: { Authorization: `Bearer ${token}` }
2362
+ });
2363
+ if (!res.ok) {
2364
+ throw new CliError(`Export failed: HTTP ${res.status}`);
2365
+ }
2366
+ const icsContent = await res.text();
2367
+ const outPath = opts.output ? resolve5(opts.output) : resolve5(`event-${id}.ics`);
2368
+ await writeFile3(outPath, icsContent, "utf-8");
2369
+ if (isJsonMode()) {
2370
+ printOne({ id, path: outPath, format: "ics" });
2371
+ } else {
2372
+ success(`Exported: ${outPath}`);
2373
+ }
2374
+ })
2375
+ );
2376
+ }
2377
+
2378
+ // src/commands/crm.ts
2379
+ var contactColumns = [
2380
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2381
+ { key: "name", header: "Name" },
2382
+ { key: "type", header: "Type", width: 8 },
2383
+ { key: "email", header: "Email" },
2384
+ { key: "lifecycle_stage", header: "Stage", width: 12 }
2385
+ ];
2386
+ var dealColumns = [
2387
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2388
+ { key: "title", header: "Title" },
2389
+ { key: "amount", header: "Amount", width: 10, format: (v) => v != null ? `$${Number(v).toLocaleString()}` : "" },
2390
+ { key: "status", header: "Status", width: 8 },
2391
+ { key: "stage", header: "Stage", format: (v) => v && typeof v === "object" ? v.name ?? "" : String(v ?? "") }
2392
+ ];
2393
+ var pipelineColumns = [
2394
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2395
+ { key: "name", header: "Name" },
2396
+ { key: "is_default", header: "Default", width: 8, format: (v) => v ? "yes" : "no" }
2397
+ ];
2398
+ var activityColumns = [
2399
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2400
+ { key: "type", header: "Type", width: 8 },
2401
+ { key: "subject", header: "Subject" },
2402
+ { key: "occurred_at", header: "Date", format: (v) => v ? new Date(String(v)).toLocaleDateString() : "" }
2403
+ ];
2404
+ function registerCrmCommands(program2) {
2405
+ const crm = program2.command("crm").description("CRM \u2014 contacts, deals, pipelines, activities");
2406
+ const contacts = crm.command("contacts").description("Manage contacts");
2407
+ contacts.command("list").description("List contacts").option("--type <type>", "Filter by type (person, company)").option("--stage <stage>", "Filter by lifecycle stage").option("--owner <userId>", "Filter by owner").option("-q, --query <q>", "Search query").option("--limit <n>", "Limit results").option("--offset <n>", "Offset results").action(
2408
+ withErrorHandler(async (opts) => {
2409
+ const client = getClient();
2410
+ const params = {};
2411
+ if (opts.type) params.type = opts.type;
2412
+ if (opts.stage) params.lifecycle_stage = opts.stage;
2413
+ if (opts.owner) params.owner_id = opts.owner;
2414
+ if (opts.query) params.q = opts.query;
2415
+ if (opts.limit) params.limit = Number(opts.limit);
2416
+ if (opts.offset) params.offset = Number(opts.offset);
2417
+ const res = await client.crm.listContacts(params);
2418
+ printTable(res.contacts, contactColumns);
2419
+ })
2420
+ );
2421
+ contacts.command("create").description("Create a contact").option("-f, --field <fields...>", "Set fields (name=..., type=person|company, email=..., phone=..., lifecycle_stage=...)").action(
2422
+ withErrorHandler(async (opts) => {
2423
+ const data = await collectInput(opts.field ?? []);
2424
+ if (!data.type) data.type = "person";
2425
+ const client = getClient();
2426
+ const contact = await client.crm.createContact(data);
2427
+ if (isJsonMode()) {
2428
+ printOne(contact);
2429
+ } else {
2430
+ success(`Created ${data.type} "${contact.name}" (${contact.id})`);
2431
+ }
2432
+ })
2433
+ );
2434
+ contacts.command("get <id>").description("Get a contact by ID").action(
2435
+ withErrorHandler(async (id) => {
2436
+ const client = getClient();
2437
+ const contact = await client.crm.getContact(id);
2438
+ printOne(contact);
2439
+ })
2440
+ );
2441
+ contacts.command("update <id>").description("Update a contact").option("-f, --field <fields...>", "Set fields (name=..., email=..., lifecycle_stage=...)").action(
2442
+ withErrorHandler(async (id, opts) => {
2443
+ const data = await collectInput(opts.field ?? []);
2444
+ const client = getClient();
2445
+ const contact = await client.crm.updateContact(id, data);
2446
+ if (isJsonMode()) {
2447
+ printOne(contact);
2448
+ } else {
2449
+ success(`Updated "${contact.name}" (${contact.id})`);
2450
+ }
2451
+ })
2452
+ );
2453
+ contacts.command("delete <id>").description("Delete a contact").option("-y, --yes", "Skip confirmation").action(
2454
+ withErrorHandler(async (id, opts) => {
2455
+ if (!opts.yes && !isJsonMode()) {
2456
+ const ok = await confirm(`Delete contact ${id}?`);
2457
+ if (!ok) {
2458
+ console.error(dim("Cancelled."));
2459
+ return;
2460
+ }
2461
+ }
2462
+ const client = getClient();
2463
+ await client.crm.deleteContact(id);
2464
+ success(`Deleted contact ${id}`);
2465
+ })
2466
+ );
2467
+ contacts.command("activities <contactId>").description("List activities for a contact").option("--type <type>", "Filter by activity type (note, call, email, meeting, task)").action(
2468
+ withErrorHandler(async (contactId, opts) => {
2469
+ const client = getClient();
2470
+ const params = {};
2471
+ if (opts.type) params.type = opts.type;
2472
+ const res = await client.crm.listContactActivities(contactId, params);
2473
+ printTable(res.activities, activityColumns);
2474
+ })
2475
+ );
2476
+ const deals = crm.command("deals").description("Manage deals");
2477
+ deals.command("list").description("List deals").option("--pipeline <id>", "Filter by pipeline").option("--stage <id>", "Filter by stage").option("--status <status>", "Filter by status (open, won, lost)").option("--limit <n>", "Limit results").option("--offset <n>", "Offset results").action(
2478
+ withErrorHandler(async (opts) => {
2479
+ const client = getClient();
2480
+ const params = {};
2481
+ if (opts.pipeline) params.pipeline_id = opts.pipeline;
2482
+ if (opts.stage) params.stage_id = opts.stage;
2483
+ if (opts.status) params.status = opts.status;
2484
+ if (opts.limit) params.limit = Number(opts.limit);
2485
+ if (opts.offset) params.offset = Number(opts.offset);
2486
+ const res = await client.crm.listDeals(params);
2487
+ printTable(res.deals, dealColumns);
2488
+ })
2489
+ );
2490
+ deals.command("create").description("Create a deal").option("-f, --field <fields...>", "Set fields (title=..., amount=..., pipeline_id=..., stage_id=..., contact_id=...)").action(
2491
+ withErrorHandler(async (opts) => {
2492
+ const data = await collectInput(opts.field ?? []);
2493
+ if (data.amount) data.amount = Number(data.amount);
2494
+ const client = getClient();
2495
+ const deal = await client.crm.createDeal(data);
2496
+ if (isJsonMode()) {
2497
+ printOne(deal);
2498
+ } else {
2499
+ success(`Created deal "${deal.title}" (${deal.id})`);
2500
+ }
2501
+ })
2502
+ );
2503
+ deals.command("get <id>").description("Get a deal by ID").action(
2504
+ withErrorHandler(async (id) => {
2505
+ const client = getClient();
2506
+ const deal = await client.crm.getDeal(id);
2507
+ printOne(deal);
2508
+ })
2509
+ );
2510
+ deals.command("update <id>").description("Update a deal").option("-f, --field <fields...>", "Set fields (title=..., amount=..., stage_id=..., status=...)").action(
2511
+ withErrorHandler(async (id, opts) => {
2512
+ const data = await collectInput(opts.field ?? []);
2513
+ if (data.amount) data.amount = Number(data.amount);
2514
+ const client = getClient();
2515
+ const deal = await client.crm.updateDeal(id, data);
2516
+ if (isJsonMode()) {
2517
+ printOne(deal);
2518
+ } else {
2519
+ success(`Updated deal "${deal.title}" (${deal.id})`);
2520
+ }
2521
+ })
2522
+ );
2523
+ deals.command("delete <id>").description("Delete a deal").option("-y, --yes", "Skip confirmation").action(
2524
+ withErrorHandler(async (id, opts) => {
2525
+ if (!opts.yes && !isJsonMode()) {
2526
+ const ok = await confirm(`Delete deal ${id}?`);
2527
+ if (!ok) {
2528
+ console.error(dim("Cancelled."));
2529
+ return;
2530
+ }
2531
+ }
2532
+ const client = getClient();
2533
+ await client.crm.deleteDeal(id);
2534
+ success(`Deleted deal ${id}`);
2535
+ })
2536
+ );
2537
+ const pipelines = crm.command("pipelines").description("Manage pipelines");
2538
+ pipelines.command("list").description("List pipelines with stages").action(
2539
+ withErrorHandler(async () => {
2540
+ const client = getClient();
2541
+ const res = await client.crm.listPipelines();
2542
+ printTable(res.pipelines, pipelineColumns);
2543
+ })
2544
+ );
2545
+ pipelines.command("create").description("Create a pipeline").option("-f, --field <fields...>", 'Set fields (name=..., stages=[{"name":"Lead","probability":10}])').action(
2546
+ withErrorHandler(async (opts) => {
2547
+ const data = await collectInput(opts.field ?? []);
2548
+ if (typeof data.stages === "string") {
2549
+ try {
2550
+ data.stages = JSON.parse(data.stages);
2551
+ } catch {
2552
+ }
2553
+ }
2554
+ const client = getClient();
2555
+ const pipeline = await client.crm.createPipeline(data);
2556
+ if (isJsonMode()) {
2557
+ printOne(pipeline);
2558
+ } else {
2559
+ success(`Created pipeline "${pipeline.name}" (${pipeline.id})`);
2560
+ }
2561
+ })
2562
+ );
2563
+ pipelines.command("update <id>").description("Update a pipeline").option("-f, --field <fields...>", "Set fields (name=..., stages=[...])").action(
2564
+ withErrorHandler(async (id, opts) => {
2565
+ const data = await collectInput(opts.field ?? []);
2566
+ if (typeof data.stages === "string") {
2567
+ try {
2568
+ data.stages = JSON.parse(data.stages);
2569
+ } catch {
2570
+ }
2571
+ }
2572
+ const client = getClient();
2573
+ const pipeline = await client.crm.updatePipeline(id, data);
2574
+ if (isJsonMode()) {
2575
+ printOne(pipeline);
2576
+ } else {
2577
+ success(`Updated pipeline "${pipeline.name}" (${pipeline.id})`);
2578
+ }
2579
+ })
2580
+ );
2581
+ pipelines.command("delete <id>").description("Delete a pipeline").option("-y, --yes", "Skip confirmation").action(
2582
+ withErrorHandler(async (id, opts) => {
2583
+ if (!opts.yes && !isJsonMode()) {
2584
+ const ok = await confirm(`Delete pipeline ${id}?`);
2585
+ if (!ok) {
2586
+ console.error(dim("Cancelled."));
2587
+ return;
2588
+ }
2589
+ }
2590
+ const client = getClient();
2591
+ await client.crm.deletePipeline(id);
2592
+ success(`Deleted pipeline ${id}`);
2593
+ })
2594
+ );
2595
+ const activities = crm.command("activities").description("Log and manage activities");
2596
+ activities.command("log").description("Log an activity (note, call, email, meeting, task)").option("-f, --field <fields...>", "Set fields (type=call, subject=..., body=..., contact_id=..., deal_id=..., duration_minutes=30)").action(
2597
+ withErrorHandler(async (opts) => {
2598
+ const data = await collectInput(opts.field ?? []);
2599
+ if (data.duration_minutes) data.duration_minutes = Number(data.duration_minutes);
2600
+ const client = getClient();
2601
+ const activity = await client.crm.logActivity(data);
2602
+ if (isJsonMode()) {
2603
+ printOne(activity);
2604
+ } else {
2605
+ success(`Logged ${activity.type} activity (${activity.id})`);
2606
+ }
2607
+ })
2608
+ );
2609
+ activities.command("delete <id>").description("Delete an activity").option("-y, --yes", "Skip confirmation").action(
2610
+ withErrorHandler(async (id, opts) => {
2611
+ if (!opts.yes && !isJsonMode()) {
2612
+ const ok = await confirm(`Delete activity ${id}?`);
2613
+ if (!ok) {
2614
+ console.error(dim("Cancelled."));
2615
+ return;
2616
+ }
2617
+ }
2618
+ const client = getClient();
2619
+ await client.crm.deleteActivity(id);
2620
+ success(`Deleted activity ${id}`);
2621
+ })
2622
+ );
2623
+ }
2624
+
2625
+ // src/commands/forms.ts
2626
+ var formColumns = [
2627
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2628
+ { key: "title", header: "Title" },
2629
+ { key: "slug", header: "Slug", width: 20 },
2630
+ { key: "is_published", header: "Published", width: 10, format: (v) => v ? "yes" : "no" }
2631
+ ];
2632
+ var responseColumns = [
2633
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2634
+ { key: "respondent_email", header: "Email" },
2635
+ { key: "submitted_at", header: "Submitted", format: (v) => v ? new Date(String(v)).toLocaleDateString() : "" }
2636
+ ];
2637
+ function registerFormsCommands(program2) {
2638
+ const forms = program2.command("forms").description("Manage forms and responses");
2639
+ forms.command("list").description("List forms").option("-q, --query <q>", "Search query").option("--published", "Only published forms").action(
2640
+ withErrorHandler(async (opts) => {
2641
+ const client = getClient();
2642
+ const params = {};
2643
+ if (opts.query) params.q = opts.query;
2644
+ if (opts.published) params.published = true;
2645
+ const res = await client.forms.list(params);
2646
+ printTable(res.forms, formColumns);
2647
+ })
2648
+ );
2649
+ forms.command("create").description("Create a form").option("-f, --field <fields...>", "Set fields (title=..., slug=..., description=...)").action(
2650
+ withErrorHandler(async (opts) => {
2651
+ const data = await collectInput(opts.field ?? []);
2652
+ if (typeof data.fields === "string") {
2653
+ try {
2654
+ data.fields = JSON.parse(data.fields);
2655
+ } catch {
2656
+ }
2657
+ }
2658
+ if (typeof data.settings === "string") {
2659
+ try {
2660
+ data.settings = JSON.parse(data.settings);
2661
+ } catch {
2662
+ }
2663
+ }
2664
+ const client = getClient();
2665
+ const form = await client.forms.create(data);
2666
+ if (isJsonMode()) {
2667
+ printOne(form);
2668
+ } else {
2669
+ success(`Created form "${form.title}" (${form.id})`);
2670
+ }
2671
+ })
2672
+ );
2673
+ forms.command("get <id>").description("Get a form by ID").action(
2674
+ withErrorHandler(async (id) => {
2675
+ const client = getClient();
2676
+ const form = await client.forms.get(id);
2677
+ printOne(form);
2678
+ })
2679
+ );
2680
+ forms.command("update <id>").description("Update a form").option("-f, --field <fields...>", "Set fields (title=..., is_published=true)").action(
2681
+ withErrorHandler(async (id, opts) => {
2682
+ const data = await collectInput(opts.field ?? []);
2683
+ if (typeof data.fields === "string") {
2684
+ try {
2685
+ data.fields = JSON.parse(data.fields);
2686
+ } catch {
2687
+ }
2688
+ }
2689
+ if (typeof data.settings === "string") {
2690
+ try {
2691
+ data.settings = JSON.parse(data.settings);
2692
+ } catch {
2693
+ }
2694
+ }
2695
+ if (data.is_published === "true") data.is_published = true;
2696
+ if (data.is_published === "false") data.is_published = false;
2697
+ const client = getClient();
2698
+ const form = await client.forms.update(id, data);
2699
+ if (isJsonMode()) {
2700
+ printOne(form);
2701
+ } else {
2702
+ success(`Updated form "${form.title}" (${form.id})`);
2703
+ }
2704
+ })
2705
+ );
2706
+ forms.command("delete <id>").description("Delete a form").option("-y, --yes", "Skip confirmation").action(
2707
+ withErrorHandler(async (id, opts) => {
2708
+ if (!opts.yes && !isJsonMode()) {
2709
+ const ok = await confirm(`Delete form ${id}?`);
2710
+ if (!ok) {
2711
+ console.error(dim("Cancelled."));
2712
+ return;
2713
+ }
2714
+ }
2715
+ const client = getClient();
2716
+ await client.forms.delete(id);
2717
+ success(`Deleted form ${id}`);
2718
+ })
2719
+ );
2720
+ forms.command("publish <id>").description("Publish a form").action(
2721
+ withErrorHandler(async (id) => {
2722
+ const client = getClient();
2723
+ await client.forms.update(id, { is_published: true });
2724
+ success(`Published form ${id}`);
2725
+ })
2726
+ );
2727
+ forms.command("unpublish <id>").description("Unpublish a form").action(
2728
+ withErrorHandler(async (id) => {
2729
+ const client = getClient();
2730
+ await client.forms.update(id, { is_published: false });
2731
+ success(`Unpublished form ${id}`);
2732
+ })
2733
+ );
2734
+ forms.command("submit <slug>").description("Submit a response to a form (by slug)").option("-f, --field <fields...>", "Set response data (fieldId=value)").action(
2735
+ withErrorHandler(async (slug, opts) => {
2736
+ const data = await collectInput(opts.field ?? []);
2737
+ const client = getClient();
2738
+ const response = await client.forms.submit(slug, data);
2739
+ if (isJsonMode()) {
2740
+ printOne(response);
2741
+ } else {
2742
+ success(`Submitted response (${response.id})`);
2743
+ }
2744
+ })
2745
+ );
2746
+ const responses = forms.command("responses").description("Manage form responses");
2747
+ responses.command("list <formId>").description("List responses for a form").option("--limit <n>", "Limit results").option("--offset <n>", "Offset results").action(
2748
+ withErrorHandler(async (formId, opts) => {
2749
+ const client = getClient();
2750
+ const params = {};
2751
+ if (opts.limit) params.limit = Number(opts.limit);
2752
+ if (opts.offset) params.offset = Number(opts.offset);
2753
+ const res = await client.forms.listResponses(formId, params);
2754
+ printTable(res.responses, responseColumns);
2755
+ })
2756
+ );
2757
+ responses.command("get <formId> <responseId>").description("Get a specific response").action(
2758
+ withErrorHandler(async (formId, responseId) => {
2759
+ const client = getClient();
2760
+ const response = await client.forms.getResponse(formId, responseId);
2761
+ printOne(response);
2762
+ })
2763
+ );
2764
+ responses.command("delete <formId> <responseId>").description("Delete a response").option("-y, --yes", "Skip confirmation").action(
2765
+ withErrorHandler(async (formId, responseId, opts) => {
2766
+ if (!opts.yes && !isJsonMode()) {
2767
+ const ok = await confirm(`Delete response ${responseId}?`);
2768
+ if (!ok) {
2769
+ console.error(dim("Cancelled."));
2770
+ return;
2771
+ }
2772
+ }
2773
+ const client = getClient();
2774
+ await client.forms.deleteResponse(formId, responseId);
2775
+ success(`Deleted response ${responseId}`);
2776
+ })
2777
+ );
2778
+ }
2779
+
2780
+ // src/commands/wiki.ts
2781
+ var spaceColumns = [
2782
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2783
+ { key: "name", header: "Name" },
2784
+ { key: "slug", header: "Slug", width: 20 },
2785
+ { key: "is_private", header: "Private", width: 8, format: (v) => v ? "yes" : "no" }
2786
+ ];
2787
+ var pageColumns = [
2788
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2789
+ { key: "title", header: "Title" },
2790
+ { key: "icon", header: "Icon", width: 4 },
2791
+ { key: "slug", header: "Slug", width: 20 }
2792
+ ];
2793
+ function registerWikiCommands(program2) {
2794
+ const wiki = program2.command("wiki").description("Wiki \u2014 knowledge base with spaces, pages, databases");
2795
+ const spaces = wiki.command("spaces").description("Manage wiki spaces");
2796
+ spaces.command("list").description("List wiki spaces").action(
2797
+ withErrorHandler(async () => {
2798
+ const client = getClient();
2799
+ const res = await client.wiki.listSpaces();
2800
+ printTable(res.spaces, spaceColumns);
2801
+ })
2802
+ );
2803
+ spaces.command("create").description("Create a wiki space").option("-f, --field <fields...>", "Set fields (name=..., slug=..., description=..., is_private=false)").action(
2804
+ withErrorHandler(async (opts) => {
2805
+ const data = await collectInput(opts.field ?? []);
2806
+ if (data.is_private === "true") data.is_private = true;
2807
+ if (data.is_private === "false") data.is_private = false;
2808
+ const client = getClient();
2809
+ const space = await client.wiki.createSpace(data);
2810
+ if (isJsonMode()) {
2811
+ printOne(space);
2812
+ } else {
2813
+ success(`Created space "${space.name}" (${space.id})`);
2814
+ }
2815
+ })
2816
+ );
2817
+ spaces.command("get <slug>").description("Get a space by slug").action(
2818
+ withErrorHandler(async (slug) => {
2819
+ const client = getClient();
2820
+ const space = await client.wiki.getSpace(slug);
2821
+ printOne(space);
2822
+ })
2823
+ );
2824
+ spaces.command("update <id>").description("Update a wiki space").option("-f, --field <fields...>", "Set fields (name=..., description=...)").action(
2825
+ withErrorHandler(async (id, opts) => {
2826
+ const data = await collectInput(opts.field ?? []);
2827
+ if (data.is_private === "true") data.is_private = true;
2828
+ if (data.is_private === "false") data.is_private = false;
2829
+ const client = getClient();
2830
+ const space = await client.wiki.updateSpace(id, data);
2831
+ if (isJsonMode()) {
2832
+ printOne(space);
2833
+ } else {
2834
+ success(`Updated space "${space.name}" (${space.id})`);
2835
+ }
2836
+ })
2837
+ );
2838
+ spaces.command("delete <id>").description("Delete a wiki space").option("-y, --yes", "Skip confirmation").action(
2839
+ withErrorHandler(async (id, opts) => {
2840
+ if (!opts.yes && !isJsonMode()) {
2841
+ const ok = await confirm(`Delete space ${id} and all its pages?`);
2842
+ if (!ok) {
2843
+ console.error(dim("Cancelled."));
2844
+ return;
2845
+ }
2846
+ }
2847
+ const client = getClient();
2848
+ await client.wiki.deleteSpace(id);
2849
+ success(`Deleted space ${id}`);
2850
+ })
2851
+ );
2852
+ const pages = wiki.command("pages").description("Manage wiki pages");
2853
+ pages.command("create <spaceId>").description("Create a page in a space").option("-f, --field <fields...>", "Set fields (title=..., slug=..., content=..., icon=..., parent_page_id=...)").action(
2854
+ withErrorHandler(async (spaceId, opts) => {
2855
+ const data = await collectInput(opts.field ?? []);
2856
+ if (data.is_template === "true") data.is_template = true;
2857
+ const client = getClient();
2858
+ const page = await client.wiki.createPage(spaceId, data);
2859
+ if (isJsonMode()) {
2860
+ printOne(page);
2861
+ } else {
2862
+ success(`Created page "${page.title}" (${page.id})`);
2863
+ }
2864
+ })
2865
+ );
2866
+ pages.command("get <id>").description("Get a page by ID").action(
2867
+ withErrorHandler(async (id) => {
2868
+ const client = getClient();
2869
+ const page = await client.wiki.getPage(id);
2870
+ printOne(page);
2871
+ })
2872
+ );
2873
+ pages.command("update <id>").description("Update a page").option("-f, --field <fields...>", "Set fields (title=..., content=..., icon=..., parent_page_id=...)").action(
2874
+ withErrorHandler(async (id, opts) => {
2875
+ const data = await collectInput(opts.field ?? []);
2876
+ const client = getClient();
2877
+ const page = await client.wiki.updatePage(id, data);
2878
+ if (isJsonMode()) {
2879
+ printOne(page);
2880
+ } else {
2881
+ success(`Updated page "${page.title}" (${page.id})`);
2882
+ }
2883
+ })
2884
+ );
2885
+ pages.command("delete <id>").description("Delete a page and its children").option("-y, --yes", "Skip confirmation").action(
2886
+ withErrorHandler(async (id, opts) => {
2887
+ if (!opts.yes && !isJsonMode()) {
2888
+ const ok = await confirm(`Delete page ${id} and all children?`);
2889
+ if (!ok) {
2890
+ console.error(dim("Cancelled."));
2891
+ return;
2892
+ }
2893
+ }
2894
+ const client = getClient();
2895
+ await client.wiki.deletePage(id);
2896
+ success(`Deleted page ${id}`);
2897
+ })
2898
+ );
2899
+ pages.command("children <pageId>").description("List child pages").action(
2900
+ withErrorHandler(async (pageId) => {
2901
+ const client = getClient();
2902
+ const res = await client.wiki.listChildren(pageId);
2903
+ printTable(res.pages, pageColumns);
2904
+ })
2905
+ );
2906
+ pages.command("backlinks <pageId>").description("List pages that link to this page").action(
2907
+ withErrorHandler(async (pageId) => {
2908
+ const client = getClient();
2909
+ const res = await client.wiki.listBacklinks(pageId);
2910
+ printTable(
2911
+ res.backlinks,
2912
+ [
2913
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
2914
+ { key: "title", header: "Title" }
2915
+ ]
2916
+ );
2917
+ })
2918
+ );
2919
+ pages.command("duplicate <pageId>").description("Duplicate a page").action(
2920
+ withErrorHandler(async (pageId) => {
2921
+ const client = getClient();
2922
+ const page = await client.wiki.duplicatePage(pageId);
2923
+ if (isJsonMode()) {
2924
+ printOne(page);
2925
+ } else {
2926
+ success(`Duplicated page \u2192 "${page.title}" (${page.id})`);
2927
+ }
2928
+ })
2929
+ );
2930
+ wiki.command("search").description("Search across all wiki content").requiredOption("-q, --query <q>", "Search query").option("--space <slug>", "Limit to a specific space").action(
2931
+ withErrorHandler(async (opts) => {
2932
+ const client = getClient();
2933
+ const params = { q: opts.query };
2934
+ if (opts.space) params.space = opts.space;
2935
+ const res = await client.wiki.search(params);
2936
+ printTable(res.results, pageColumns);
2937
+ })
2938
+ );
2939
+ const db = wiki.command("db").description("Manage wiki databases");
2940
+ db.command("get <id>").description("Get a database (schema + rows)").action(
2941
+ withErrorHandler(async (id) => {
2942
+ const client = getClient();
2943
+ const database = await client.wiki.getDatabase(id);
2944
+ printOne(database);
2945
+ })
2946
+ );
2947
+ db.command("create <pageId>").description("Create a database on a page").option("-f, --field <fields...>", 'Set fields (name=..., schema={"properties":[...]})').action(
2948
+ withErrorHandler(async (pageId, opts) => {
2949
+ const data = await collectInput(opts.field ?? []);
2950
+ if (typeof data.schema === "string") {
2951
+ try {
2952
+ data.schema = JSON.parse(data.schema);
2953
+ } catch {
2954
+ }
2955
+ }
2956
+ const client = getClient();
2957
+ const database = await client.wiki.createDatabase(pageId, data);
2958
+ if (isJsonMode()) {
2959
+ printOne(database);
2960
+ } else {
2961
+ success(`Created database "${database.name}" (${database.id})`);
2962
+ }
2963
+ })
2964
+ );
2965
+ db.command("update <id>").description("Update a database schema or views").option("-f, --field <fields...>", "Set fields (schema=..., views=...)").action(
2966
+ withErrorHandler(async (id, opts) => {
2967
+ const data = await collectInput(opts.field ?? []);
2968
+ if (typeof data.schema === "string") {
2969
+ try {
2970
+ data.schema = JSON.parse(data.schema);
2971
+ } catch {
2972
+ }
2973
+ }
2974
+ if (typeof data.views === "string") {
2975
+ try {
2976
+ data.views = JSON.parse(data.views);
2977
+ } catch {
2978
+ }
2979
+ }
2980
+ const client = getClient();
2981
+ const database = await client.wiki.updateDatabase(id, data);
2982
+ if (isJsonMode()) {
2983
+ printOne(database);
2984
+ } else {
2985
+ success(`Updated database (${database.id})`);
2986
+ }
2987
+ })
2988
+ );
2989
+ db.command("query <databaseId>").description("Query database rows").option("--filter <json>", "Filter as JSON").option("--sort <json>", "Sort as JSON").option("--limit <n>", "Page size").option("--offset <n>", "Offset").action(
2990
+ withErrorHandler(async (databaseId, opts) => {
2991
+ const client = getClient();
2992
+ const params = {};
2993
+ if (opts.filter) {
2994
+ try {
2995
+ params.filter = JSON.parse(opts.filter);
2996
+ } catch {
2997
+ params.filter = opts.filter;
2998
+ }
2999
+ }
3000
+ if (opts.sort) {
3001
+ try {
3002
+ params.sorts = JSON.parse(opts.sort);
3003
+ } catch {
3004
+ params.sorts = opts.sort;
3005
+ }
3006
+ }
3007
+ if (opts.limit) params.page_size = Number(opts.limit);
3008
+ if (opts.offset) params.offset = Number(opts.offset);
3009
+ const res = await client.wiki.queryRows(databaseId, params);
3010
+ if (isJsonMode()) {
3011
+ printOne(res);
3012
+ } else {
3013
+ printTable(
3014
+ res.rows.map((r) => ({ id: r.id, ...r.properties })),
3015
+ [
3016
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) }
3017
+ ]
3018
+ );
3019
+ }
3020
+ })
3021
+ );
3022
+ db.command("add-row <databaseId>").description("Add a row to a database").option("-f, --field <fields...>", "Set properties (propName=value)").action(
3023
+ withErrorHandler(async (databaseId, opts) => {
3024
+ const data = await collectInput(opts.field ?? []);
3025
+ const client = getClient();
3026
+ const row = await client.wiki.createRow(databaseId, data);
3027
+ if (isJsonMode()) {
3028
+ printOne(row);
3029
+ } else {
3030
+ success(`Added row (${row.id})`);
3031
+ }
3032
+ })
3033
+ );
3034
+ db.command("update-row <databaseId> <rowId>").description("Update a row in a database").option("-f, --field <fields...>", "Set properties (propName=value)").action(
3035
+ withErrorHandler(async (databaseId, rowId, opts) => {
3036
+ const data = await collectInput(opts.field ?? []);
3037
+ const client = getClient();
3038
+ const row = await client.wiki.updateRow(databaseId, rowId, data);
3039
+ if (isJsonMode()) {
3040
+ printOne(row);
3041
+ } else {
3042
+ success(`Updated row ${rowId}`);
3043
+ }
3044
+ })
3045
+ );
3046
+ db.command("delete-row <databaseId> <rowId>").description("Delete a row from a database").option("-y, --yes", "Skip confirmation").action(
3047
+ withErrorHandler(async (databaseId, rowId, opts) => {
3048
+ if (!opts.yes && !isJsonMode()) {
3049
+ const ok = await confirm(`Delete row ${rowId}?`);
3050
+ if (!ok) {
3051
+ console.error(dim("Cancelled."));
3052
+ return;
3053
+ }
3054
+ }
3055
+ const client = getClient();
3056
+ await client.wiki.deleteRow(databaseId, rowId);
3057
+ success(`Deleted row ${rowId}`);
3058
+ })
3059
+ );
3060
+ }
3061
+
3062
+ // src/commands/admin.ts
3063
+ var userColumns = [
3064
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3065
+ { key: "display_name", header: "Name" },
3066
+ { key: "email", header: "Email" },
3067
+ { key: "type", header: "Type", width: 8 }
3068
+ ];
3069
+ var roleColumns = [
3070
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3071
+ { key: "name", header: "Name" },
3072
+ { key: "is_system", header: "System", width: 8, format: (v) => v ? "yes" : "no" }
3073
+ ];
3074
+ var auditColumns = [
3075
+ { key: "id", header: "ID", width: 10 },
3076
+ { key: "action", header: "Action", width: 20 },
3077
+ { key: "user_id", header: "User", width: 12, format: (v) => String(v ?? "").slice(0, 12) },
3078
+ { key: "resource_type", header: "Resource", width: 12 },
3079
+ { key: "created_at", header: "Date", format: (v) => v ? new Date(String(v)).toLocaleString() : "" }
3080
+ ];
3081
+ var apiKeyColumns = [
3082
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3083
+ { key: "name", header: "Name" },
3084
+ { key: "user_id", header: "User", width: 12, format: (v) => String(v ?? "").slice(0, 12) },
3085
+ { key: "last_used_at", header: "Last Used", format: (v) => v ? new Date(String(v)).toLocaleDateString() : "never" }
3086
+ ];
3087
+ function registerAdminCommands(program2) {
3088
+ const admin = program2.command("admin").description("Workspace administration");
3089
+ const users = admin.command("users").description("Manage users and agents");
3090
+ users.command("list").description("List all users").option("--type <type>", "Filter by type (human, agent)").option("--role <role>", "Filter by role").option("-q, --query <q>", "Search query").option("--limit <n>", "Limit results").option("--offset <n>", "Offset results").action(
3091
+ withErrorHandler(async (opts) => {
3092
+ const client = getClient();
3093
+ const params = {};
3094
+ if (opts.type) params.type = opts.type;
3095
+ if (opts.role) params.role = opts.role;
3096
+ if (opts.query) params.q = opts.query;
3097
+ if (opts.limit) params.limit = Number(opts.limit);
3098
+ if (opts.offset) params.offset = Number(opts.offset);
3099
+ const res = await client.admin.listUsers(params);
3100
+ printTable(res.users, userColumns);
3101
+ })
3102
+ );
3103
+ users.command("get <id>").description("Get user details").action(
3104
+ withErrorHandler(async (id) => {
3105
+ const client = getClient();
3106
+ const user = await client.admin.getUser(id);
3107
+ printOne(user);
3108
+ })
3109
+ );
3110
+ users.command("update <id>").description("Update a user").option("-f, --field <fields...>", "Set fields (display_name=..., role=...)").action(
3111
+ withErrorHandler(async (id, opts) => {
3112
+ const data = await collectInput(opts.field ?? []);
3113
+ const client = getClient();
3114
+ const user = await client.admin.updateUser(id, data);
3115
+ if (isJsonMode()) {
3116
+ printOne(user);
3117
+ } else {
3118
+ success(`Updated user ${user.display_name} (${user.id})`);
3119
+ }
3120
+ })
3121
+ );
3122
+ users.command("delete <id>").description("Deactivate/delete a user").option("-y, --yes", "Skip confirmation").action(
3123
+ withErrorHandler(async (id, opts) => {
3124
+ if (!opts.yes && !isJsonMode()) {
3125
+ const ok = await confirm(`Delete user ${id}? This cannot be undone.`);
3126
+ if (!ok) {
3127
+ console.error(dim("Cancelled."));
3128
+ return;
3129
+ }
3130
+ }
3131
+ const client = getClient();
3132
+ await client.admin.deleteUser(id);
3133
+ success(`Deleted user ${id}`);
3134
+ })
3135
+ );
3136
+ const roles = admin.command("roles").description("Manage roles and permissions");
3137
+ roles.command("list").description("List all roles").action(
3138
+ withErrorHandler(async () => {
3139
+ const client = getClient();
3140
+ const res = await client.admin.listRoles();
3141
+ printTable(res.roles, roleColumns);
3142
+ })
3143
+ );
3144
+ roles.command("create").description("Create a custom role").option("-f, --field <fields...>", 'Set fields (name=..., permissions=["users.view","docs.manage"])').action(
3145
+ withErrorHandler(async (opts) => {
3146
+ const data = await collectInput(opts.field ?? []);
3147
+ if (typeof data.permissions === "string") {
3148
+ try {
3149
+ data.permissions = JSON.parse(data.permissions);
3150
+ } catch {
3151
+ }
3152
+ }
3153
+ const client = getClient();
3154
+ const role = await client.admin.createRole(data);
3155
+ if (isJsonMode()) {
3156
+ printOne(role);
3157
+ } else {
3158
+ success(`Created role "${role.name}" (${role.id})`);
3159
+ }
3160
+ })
3161
+ );
3162
+ roles.command("update <id>").description("Update a role").option("-f, --field <fields...>", "Set fields (name=..., permissions=[...])").action(
3163
+ withErrorHandler(async (id, opts) => {
3164
+ const data = await collectInput(opts.field ?? []);
3165
+ if (typeof data.permissions === "string") {
3166
+ try {
3167
+ data.permissions = JSON.parse(data.permissions);
3168
+ } catch {
3169
+ }
3170
+ }
3171
+ const client = getClient();
3172
+ const role = await client.admin.updateRole(id, data);
3173
+ if (isJsonMode()) {
3174
+ printOne(role);
3175
+ } else {
3176
+ success(`Updated role "${role.name}" (${role.id})`);
3177
+ }
3178
+ })
3179
+ );
3180
+ roles.command("delete <id>").description("Delete a custom role").option("-y, --yes", "Skip confirmation").action(
3181
+ withErrorHandler(async (id, opts) => {
3182
+ if (!opts.yes && !isJsonMode()) {
3183
+ const ok = await confirm(`Delete role ${id}?`);
3184
+ if (!ok) {
3185
+ console.error(dim("Cancelled."));
3186
+ return;
3187
+ }
3188
+ }
3189
+ const client = getClient();
3190
+ await client.admin.deleteRole(id);
3191
+ success(`Deleted role ${id}`);
3192
+ })
3193
+ );
3194
+ roles.command("assign <userId> <roleName>").description("Assign a role to a user").action(
3195
+ withErrorHandler(async (userId, roleName) => {
3196
+ const client = getClient();
3197
+ await client.admin.assignRole(userId, roleName);
3198
+ success(`Assigned role "${roleName}" to user ${userId}`);
3199
+ })
3200
+ );
3201
+ roles.command("remove <userId> <roleName>").description("Remove a role from a user").action(
3202
+ withErrorHandler(async (userId, roleName) => {
3203
+ const client = getClient();
3204
+ await client.admin.removeRole(userId, roleName);
3205
+ success(`Removed role "${roleName}" from user ${userId}`);
3206
+ })
3207
+ );
3208
+ const settings = admin.command("settings").description("Workspace settings");
3209
+ settings.command("get").description("Get all workspace settings").action(
3210
+ withErrorHandler(async () => {
3211
+ const client = getClient();
3212
+ const res = await client.admin.getSettings();
3213
+ printOne(res);
3214
+ })
3215
+ );
3216
+ settings.command("set").description("Update workspace settings").option("-f, --field <fields...>", "Set settings (key=value)").action(
3217
+ withErrorHandler(async (opts) => {
3218
+ const data = await collectInput(opts.field ?? []);
3219
+ const client = getClient();
3220
+ await client.admin.updateSettings(data);
3221
+ success("Settings updated");
3222
+ })
3223
+ );
3224
+ admin.command("audit-log").description("Query the audit log").option("--user <userId>", "Filter by user").option("--action <action>", "Filter by action (e.g. user.login)").option("--resource <type>", "Filter by resource type").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").option("--limit <n>", "Limit results").option("--offset <n>", "Offset results").action(
3225
+ withErrorHandler(async (opts) => {
3226
+ const client = getClient();
3227
+ const params = {};
3228
+ if (opts.user) params.user_id = opts.user;
3229
+ if (opts.action) params.action = opts.action;
3230
+ if (opts.resource) params.resource_type = opts.resource;
3231
+ if (opts.start) params.start = opts.start;
3232
+ if (opts.end) params.end = opts.end;
3233
+ if (opts.limit) params.limit = Number(opts.limit);
3234
+ if (opts.offset) params.offset = Number(opts.offset);
3235
+ const res = await client.admin.queryAuditLog(params);
3236
+ printTable(res.entries, auditColumns);
3237
+ })
3238
+ );
3239
+ const apiKeys = admin.command("api-keys").description("Manage API keys");
3240
+ apiKeys.command("list").description("List all API keys").option("--user <userId>", "Filter by user").action(
3241
+ withErrorHandler(async (opts) => {
3242
+ const client = getClient();
3243
+ const params = {};
3244
+ if (opts.user) params.user_id = opts.user;
3245
+ const res = await client.admin.listApiKeys(params);
3246
+ printTable(res.api_keys, apiKeyColumns);
3247
+ })
3248
+ );
3249
+ apiKeys.command("create").description("Create an API key").option("-f, --field <fields...>", 'Set fields (user_id=..., name=..., scopes=["*"], expires_at=...)').action(
3250
+ withErrorHandler(async (opts) => {
3251
+ const data = await collectInput(opts.field ?? []);
3252
+ if (typeof data.scopes === "string") {
3253
+ try {
3254
+ data.scopes = JSON.parse(data.scopes);
3255
+ } catch {
3256
+ }
3257
+ }
3258
+ const client = getClient();
3259
+ const key = await client.admin.createApiKey(data);
3260
+ if (isJsonMode()) {
3261
+ printOne(key);
3262
+ } else {
3263
+ success(`Created API key "${key.name}"
3264
+ Key: ${key.key}
3265
+ (Save this \u2014 it won't be shown again)`);
3266
+ }
3267
+ })
3268
+ );
3269
+ apiKeys.command("revoke <id>").description("Revoke an API key").option("-y, --yes", "Skip confirmation").action(
3270
+ withErrorHandler(async (id, opts) => {
3271
+ if (!opts.yes && !isJsonMode()) {
3272
+ const ok = await confirm(`Revoke API key ${id}? This cannot be undone.`);
3273
+ if (!ok) {
3274
+ console.error(dim("Cancelled."));
3275
+ return;
3276
+ }
3277
+ }
3278
+ const client = getClient();
3279
+ await client.admin.revokeApiKey(id);
3280
+ success(`Revoked API key ${id}`);
3281
+ })
3282
+ );
3283
+ admin.command("usage").description("Get workspace usage statistics").action(
3284
+ withErrorHandler(async () => {
3285
+ const client = getClient();
3286
+ const usage = await client.admin.getUsage();
3287
+ printOne(usage);
3288
+ })
3289
+ );
3290
+ }
3291
+
3292
+ // src/commands/api.ts
3293
+ function registerApiCommands(program2) {
3294
+ program2.command("api <method> <path>").description("Make a raw API request (escape hatch)").option("-d, --data <json>", "Request body as JSON").option("--query <params>", "Query params as key=value,key=value").action(
3295
+ withErrorHandler(async (method, path, opts) => {
3296
+ let data;
3297
+ if (opts.data) {
3298
+ try {
3299
+ data = JSON.parse(opts.data);
3300
+ } catch {
3301
+ data = opts.data;
3302
+ }
3303
+ }
3304
+ let query;
3305
+ if (opts.query) {
3306
+ query = {};
3307
+ for (const pair of opts.query.split(",")) {
3308
+ const eq = pair.indexOf("=");
3309
+ if (eq > 0) {
3310
+ query[pair.slice(0, eq)] = pair.slice(eq + 1);
3311
+ }
3312
+ }
3313
+ }
3314
+ const result = await rawRequest(method.toUpperCase(), path, data, query);
3315
+ printOne(result);
3316
+ })
3317
+ );
3318
+ }
3319
+
3320
+ // src/commands/search.ts
3321
+ var searchColumns = [
3322
+ { key: "type", header: "Type", width: 10 },
3323
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3324
+ { key: "title", header: "Title" }
3325
+ ];
3326
+ function registerSearchCommands(program2) {
3327
+ program2.command("search <query>").description("Search across all modules").option("--module <module>", "Limit to a specific module").option("--limit <n>", "Limit results").action(
3328
+ withErrorHandler(async (query, opts) => {
3329
+ const client = getClient();
3330
+ const params = { q: query };
3331
+ if (opts.module) params.module = opts.module;
3332
+ if (opts.limit) params.limit = Number(opts.limit);
3333
+ const res = await client.request("POST", "/api/search", params);
3334
+ const results = res.results ?? [];
3335
+ printTable(
3336
+ results.map((r) => ({
3337
+ type: r.type ?? r.module ?? "",
3338
+ id: r.id ?? "",
3339
+ title: r.title ?? r.name ?? r.subject ?? ""
3340
+ })),
3341
+ searchColumns
3342
+ );
3343
+ })
3344
+ );
3345
+ }
3346
+
3347
+ // src/commands/notifications.ts
3348
+ var notifColumns = [
3349
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3350
+ { key: "event_type", header: "Event", width: 20 },
3351
+ { key: "read", header: "Read", width: 6, format: (v) => v ? "yes" : "no" },
3352
+ { key: "created_at", header: "Date", format: (v) => v ? new Date(String(v)).toLocaleDateString() : "" }
3353
+ ];
3354
+ function registerNotificationsCommands(program2) {
3355
+ const notifs = program2.command("notifications").alias("notifs").description("Manage notifications");
3356
+ notifs.command("list").description("List notifications").action(
3357
+ withErrorHandler(async () => {
3358
+ const client = getClient();
3359
+ const res = await client.request("GET", "/api/notifications");
3360
+ printTable(res.notifications ?? [], notifColumns);
3361
+ })
3362
+ );
3363
+ notifs.command("read <id>").description("Mark a notification as read").action(
3364
+ withErrorHandler(async (id) => {
3365
+ const client = getClient();
3366
+ await client.request("PATCH", `/api/notifications/${id}`, { read: true });
3367
+ success(`Marked notification ${id} as read`);
3368
+ })
3369
+ );
3370
+ notifs.command("unread-counts").description("Get unread counts per module").action(
3371
+ withErrorHandler(async () => {
3372
+ const client = getClient();
3373
+ const res = await client.request("GET", "/api/unread-counts");
3374
+ printOne(res);
3375
+ })
3376
+ );
3377
+ }
3378
+
3379
+ // src/commands/webhooks.ts
3380
+ var webhookColumns = [
3381
+ { key: "id", header: "ID", width: 12, format: (v) => String(v).slice(0, 12) },
3382
+ { key: "url", header: "URL" },
3383
+ { key: "active", header: "Active", width: 8, format: (v) => v ? "yes" : "no" }
3384
+ ];
3385
+ function registerWebhooksCommands(program2) {
3386
+ const webhooks = program2.command("webhooks").description("Manage webhook registrations");
3387
+ webhooks.command("list").description("List webhook registrations").action(
3388
+ withErrorHandler(async () => {
3389
+ const client = getClient();
3390
+ const res = await client.webhooks.list();
3391
+ printTable(res.registrations, webhookColumns);
3392
+ })
3393
+ );
3394
+ webhooks.command("create").description("Create a webhook registration").option("-f, --field <fields...>", 'Set fields (url=..., events=["*"], secret=...)').action(
3395
+ withErrorHandler(async (opts) => {
3396
+ const data = await collectInput(opts.field ?? []);
3397
+ if (typeof data.events === "string") {
3398
+ try {
3399
+ data.events = JSON.parse(data.events);
3400
+ } catch {
3401
+ }
3402
+ }
3403
+ const client = getClient();
3404
+ const webhook = await client.webhooks.create(data);
3405
+ if (isJsonMode()) {
3406
+ printOne(webhook);
3407
+ } else {
3408
+ success(`Created webhook (${webhook.id}) \u2192 ${webhook.url}`);
3409
+ }
3410
+ })
3411
+ );
3412
+ webhooks.command("delete <id>").description("Delete a webhook registration").option("-y, --yes", "Skip confirmation").action(
3413
+ withErrorHandler(async (id, opts) => {
3414
+ if (!opts.yes && !isJsonMode()) {
3415
+ const ok = await confirm(`Delete webhook ${id}?`);
3416
+ if (!ok) {
3417
+ console.error(dim("Cancelled."));
3418
+ return;
3419
+ }
3420
+ }
3421
+ const client = getClient();
3422
+ await client.webhooks.delete(id);
3423
+ success(`Deleted webhook ${id}`);
3424
+ })
3425
+ );
3426
+ }
3427
+
3428
+ // src/commands/assistant.ts
3429
+ function registerAssistantCommands(program2) {
3430
+ const assistant = program2.command("assistant").alias("ai").description("AI assistant");
3431
+ assistant.command("chat <message>").description("Send a message to the AI assistant").option("--context <json>", "Context object as JSON (e.g. current page)").action(
3432
+ withErrorHandler(async (message, opts) => {
3433
+ const client = getClient();
3434
+ const body = { message };
3435
+ if (opts.context) {
3436
+ try {
3437
+ body.context = JSON.parse(opts.context);
3438
+ } catch {
3439
+ body.context = opts.context;
3440
+ }
3441
+ }
3442
+ const res = await client.request("POST", "/api/assistant/chat", body);
3443
+ if (isJsonMode()) {
3444
+ printOne(res);
3445
+ } else {
3446
+ const text = res.response ?? res.message ?? res.content ?? JSON.stringify(res);
3447
+ console.log(text);
3448
+ }
3449
+ })
3450
+ );
3451
+ }
3452
+
3453
+ // src/index.ts
3454
+ var program = new Command();
3455
+ program.name("ambi").description("CLI for Ambiguous Workspaces \u2014 built for humans and LLMs").version("0.1.0").option("--json", "Output as JSON (auto-enabled when stdout is piped)").option("-q, --quiet", "Suppress non-essential output");
3456
+ program.hook("preAction", (_thisCommand, actionCommand) => {
3457
+ const root = actionCommand.optsWithGlobals();
3458
+ if (root.json) setJsonMode(true);
3459
+ if (root.quiet) setQuietMode(true);
3460
+ });
3461
+ registerAuthCommands(program);
3462
+ registerConfigCommands(program);
3463
+ registerDocsCommands(program);
3464
+ registerSheetsCommands(program);
3465
+ registerSlidesCommands(program);
3466
+ registerChatCommands(program);
3467
+ registerMailCommands(program);
3468
+ registerTasksCommands(program);
3469
+ registerDriveCommands(program);
3470
+ registerCalendarCommands(program);
3471
+ registerCrmCommands(program);
3472
+ registerFormsCommands(program);
3473
+ registerWikiCommands(program);
3474
+ registerAdminCommands(program);
3475
+ registerApiCommands(program);
3476
+ registerSearchCommands(program);
3477
+ registerNotificationsCommands(program);
3478
+ registerWebhooksCommands(program);
3479
+ registerAssistantCommands(program);
3480
+ try {
3481
+ await program.parseAsync();
3482
+ } catch (err) {
3483
+ handleError(err);
3484
+ }