@creator-notes/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/cn.d.ts +3 -0
  2. package/dist/cn.d.ts.map +1 -0
  3. package/dist/cn.js +38 -0
  4. package/dist/cn.js.map +1 -0
  5. package/dist/commands/auth.d.ts +3 -0
  6. package/dist/commands/auth.d.ts.map +1 -0
  7. package/dist/commands/auth.js +184 -0
  8. package/dist/commands/auth.js.map +1 -0
  9. package/dist/commands/canvas.d.ts +3 -0
  10. package/dist/commands/canvas.d.ts.map +1 -0
  11. package/dist/commands/canvas.js +721 -0
  12. package/dist/commands/canvas.js.map +1 -0
  13. package/dist/commands/config.d.ts +3 -0
  14. package/dist/commands/config.d.ts.map +1 -0
  15. package/dist/commands/config.js +47 -0
  16. package/dist/commands/config.js.map +1 -0
  17. package/dist/commands/mcp.d.ts +3 -0
  18. package/dist/commands/mcp.d.ts.map +1 -0
  19. package/dist/commands/mcp.js +59 -0
  20. package/dist/commands/mcp.js.map +1 -0
  21. package/dist/commands/memory.d.ts +3 -0
  22. package/dist/commands/memory.d.ts.map +1 -0
  23. package/dist/commands/memory.js +145 -0
  24. package/dist/commands/memory.js.map +1 -0
  25. package/dist/commands/notes.d.ts +3 -0
  26. package/dist/commands/notes.d.ts.map +1 -0
  27. package/dist/commands/notes.js +508 -0
  28. package/dist/commands/notes.js.map +1 -0
  29. package/dist/commands/relationships.d.ts +3 -0
  30. package/dist/commands/relationships.d.ts.map +1 -0
  31. package/dist/commands/relationships.js +47 -0
  32. package/dist/commands/relationships.js.map +1 -0
  33. package/dist/commands/search.d.ts +3 -0
  34. package/dist/commands/search.d.ts.map +1 -0
  35. package/dist/commands/search.js +52 -0
  36. package/dist/commands/search.js.map +1 -0
  37. package/dist/commands/timeline.d.ts +3 -0
  38. package/dist/commands/timeline.d.ts.map +1 -0
  39. package/dist/commands/timeline.js +92 -0
  40. package/dist/commands/timeline.js.map +1 -0
  41. package/dist/commands/types.d.ts +3 -0
  42. package/dist/commands/types.d.ts.map +1 -0
  43. package/dist/commands/types.js +124 -0
  44. package/dist/commands/types.js.map +1 -0
  45. package/dist/commands/versions.d.ts +3 -0
  46. package/dist/commands/versions.d.ts.map +1 -0
  47. package/dist/commands/versions.js +104 -0
  48. package/dist/commands/versions.js.map +1 -0
  49. package/dist/commands/workspace.d.ts +3 -0
  50. package/dist/commands/workspace.d.ts.map +1 -0
  51. package/dist/commands/workspace.js +116 -0
  52. package/dist/commands/workspace.js.map +1 -0
  53. package/dist/lib/api-client.d.ts +12 -0
  54. package/dist/lib/api-client.d.ts.map +1 -0
  55. package/dist/lib/api-client.js +76 -0
  56. package/dist/lib/api-client.js.map +1 -0
  57. package/dist/lib/auth-store.d.ts +18 -0
  58. package/dist/lib/auth-store.d.ts.map +1 -0
  59. package/dist/lib/auth-store.js +54 -0
  60. package/dist/lib/auth-store.js.map +1 -0
  61. package/dist/lib/config.d.ts +26 -0
  62. package/dist/lib/config.d.ts.map +1 -0
  63. package/dist/lib/config.js +54 -0
  64. package/dist/lib/config.js.map +1 -0
  65. package/dist/lib/errors.d.ts +20 -0
  66. package/dist/lib/errors.d.ts.map +1 -0
  67. package/dist/lib/errors.js +54 -0
  68. package/dist/lib/errors.js.map +1 -0
  69. package/dist/lib/markdown-to-tiptap.d.ts +17 -0
  70. package/dist/lib/markdown-to-tiptap.d.ts.map +1 -0
  71. package/dist/lib/markdown-to-tiptap.js +228 -0
  72. package/dist/lib/markdown-to-tiptap.js.map +1 -0
  73. package/dist/lib/output.d.ts +22 -0
  74. package/dist/lib/output.d.ts.map +1 -0
  75. package/dist/lib/output.js +56 -0
  76. package/dist/lib/output.js.map +1 -0
  77. package/dist/lib/resolve-note.d.ts +7 -0
  78. package/dist/lib/resolve-note.d.ts.map +1 -0
  79. package/dist/lib/resolve-note.js +22 -0
  80. package/dist/lib/resolve-note.js.map +1 -0
  81. package/dist/lib/stdin.d.ts +5 -0
  82. package/dist/lib/stdin.d.ts.map +1 -0
  83. package/dist/lib/stdin.js +11 -0
  84. package/dist/lib/stdin.js.map +1 -0
  85. package/dist/mcp-server.d.ts +3 -0
  86. package/dist/mcp-server.d.ts.map +1 -0
  87. package/dist/mcp-server.js +508 -0
  88. package/dist/mcp-server.js.map +1 -0
  89. package/package.json +42 -0
@@ -0,0 +1,721 @@
1
+ import { Option } from "commander";
2
+ import { ApiClient } from "../lib/api-client.js";
3
+ import { resolveConfig } from "../lib/config.js";
4
+ import { handleError, ValidationError } from "../lib/errors.js";
5
+ import { outputJson, outputTable, outputQuiet, truncate, timeAgo } from "../lib/output.js";
6
+ export function registerCanvasCommands(program) {
7
+ const canvas = program
8
+ .command("canvas")
9
+ .alias("c")
10
+ .description("Manage canvases");
11
+ canvas
12
+ .command("list")
13
+ .description("List canvases in the current workspace")
14
+ .option("--include-archived", "Include archived canvases")
15
+ .action(async (opts) => {
16
+ const cfg = resolveConfig(program.opts());
17
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
18
+ try {
19
+ const params = { workspaceId: cfg.workspaceId };
20
+ if (opts.includeArchived) {
21
+ params.includeArchived = "true";
22
+ }
23
+ const data = await api.get("/api/canvas", params);
24
+ const list = data.canvases || data;
25
+ if (cfg.json) {
26
+ outputJson(list);
27
+ return;
28
+ }
29
+ const items = Array.isArray(list) ? list : [];
30
+ if (items.length === 0) {
31
+ console.log("No canvases found.");
32
+ return;
33
+ }
34
+ const rows = items.map((c) => [
35
+ String(c._id || c.id || ""),
36
+ truncate(String(c.name || "(unnamed)"), 30),
37
+ c.isDefault ? "(default)" : c.archivedAt ? "(archived)" : "",
38
+ c.updatedAt ? timeAgo(c.updatedAt) : "",
39
+ ]);
40
+ outputTable(["ID", "NAME", "", "UPDATED"], rows);
41
+ }
42
+ catch (error) {
43
+ handleError(error, cfg.json);
44
+ }
45
+ });
46
+ canvas
47
+ .command("get")
48
+ .description("Get canvas details with nodes and edges")
49
+ .argument("<canvasId>", "Canvas ID")
50
+ .action(async (canvasId) => {
51
+ const cfg = resolveConfig(program.opts());
52
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
53
+ try {
54
+ const data = await api.get(`/api/canvas/${canvasId}`);
55
+ if (cfg.json) {
56
+ outputJson(data);
57
+ return;
58
+ }
59
+ console.log(`Canvas: ${data.name || "(unnamed)"}`);
60
+ console.log(`ID: ${canvasId}`);
61
+ const nodes = data.nodes;
62
+ const edges = data.edges;
63
+ if (nodes && nodes.length > 0) {
64
+ console.log(`\nNodes (${nodes.length}):`);
65
+ for (const node of nodes) {
66
+ const noteTitle = node.note?.title || node.content || node.noteId || "?";
67
+ console.log(` ${node._id || node.id}: ${noteTitle} (${node.positionX}, ${node.positionY})`);
68
+ }
69
+ }
70
+ if (edges && edges.length > 0) {
71
+ console.log(`\nEdges (${edges.length}):`);
72
+ for (const edge of edges) {
73
+ const label = edge.label ? ` [${edge.label}]` : "";
74
+ console.log(` ${edge.sourceNodeId} -> ${edge.targetNodeId}${label}`);
75
+ }
76
+ }
77
+ const linkNodes = data.canvasLinkNodes;
78
+ if (linkNodes && linkNodes.length > 0) {
79
+ console.log(`\nCanvas Links (${linkNodes.length}):`);
80
+ for (const ln of linkNodes) {
81
+ const linked = ln.linkedCanvas;
82
+ const name = linked?.name || ln.linkedCanvasId || "?";
83
+ console.log(` ${ln.id}: -> ${name} (${ln.positionX}, ${ln.positionY})`);
84
+ }
85
+ }
86
+ }
87
+ catch (error) {
88
+ handleError(error, cfg.json);
89
+ }
90
+ });
91
+ canvas
92
+ .command("digest")
93
+ .description("Get the AI-generated digest (summary) of a canvas")
94
+ .argument("<canvasId>", "Canvas ID")
95
+ .action(async (canvasId) => {
96
+ const cfg = resolveConfig(program.opts());
97
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
98
+ try {
99
+ const data = await api.get(`/api/canvas/${canvasId}/digest`);
100
+ if (cfg.json) {
101
+ outputJson(data);
102
+ return;
103
+ }
104
+ const status = String(data.status || "none");
105
+ if (status === "none") {
106
+ console.log("No digest available for this canvas.");
107
+ return;
108
+ }
109
+ if (status !== "ready") {
110
+ console.log(`Digest status: ${status} — try again shortly.`);
111
+ return;
112
+ }
113
+ const canvasName = data.canvasName || "(unnamed)";
114
+ console.log(`Canvas: ${canvasName}\n`);
115
+ console.log(String(data.digest || ""));
116
+ const themes = data.themes;
117
+ if (themes && themes.length > 0) {
118
+ console.log(`\nThemes:`);
119
+ for (const t of themes) {
120
+ console.log(` • ${t}`);
121
+ }
122
+ }
123
+ const noteCount = data.noteCount || 0;
124
+ console.log(`\nNotes: ${noteCount}`);
125
+ if (data.generatedAt) {
126
+ console.log(`Generated: ${timeAgo(data.generatedAt)}`);
127
+ }
128
+ }
129
+ catch (error) {
130
+ handleError(error, cfg.json);
131
+ }
132
+ });
133
+ canvas
134
+ .command("create")
135
+ .description("Create a new canvas")
136
+ .argument("<name>", "Canvas name")
137
+ .action(async (name) => {
138
+ const cfg = resolveConfig(program.opts());
139
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
140
+ try {
141
+ const data = await api.post("/api/canvas", {
142
+ workspaceId: cfg.workspaceId,
143
+ title: name,
144
+ });
145
+ if (cfg.quiet) {
146
+ outputQuiet(String(data._id || data.id || ""));
147
+ return;
148
+ }
149
+ if (cfg.json) {
150
+ outputJson(data);
151
+ }
152
+ else {
153
+ console.log(`Created canvas: ${name}`);
154
+ console.log(`ID: ${data._id || data.id}`);
155
+ }
156
+ }
157
+ catch (error) {
158
+ handleError(error, cfg.json);
159
+ }
160
+ });
161
+ canvas
162
+ .command("delete")
163
+ .description("Delete a canvas")
164
+ .argument("<canvasId>", "Canvas ID")
165
+ .action(async (canvasId) => {
166
+ const cfg = resolveConfig(program.opts());
167
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
168
+ try {
169
+ const data = await api.delete(`/api/canvas/${canvasId}`);
170
+ if (cfg.json) {
171
+ outputJson(data);
172
+ }
173
+ else {
174
+ console.log(`Deleted canvas ${canvasId}`);
175
+ }
176
+ }
177
+ catch (error) {
178
+ handleError(error, cfg.json);
179
+ }
180
+ });
181
+ canvas
182
+ .command("set-as-home")
183
+ .description("Set a canvas as the workspace home/default canvas")
184
+ .argument("<canvasId>", "Canvas ID")
185
+ .action(async (canvasId) => {
186
+ const cfg = resolveConfig(program.opts());
187
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
188
+ try {
189
+ const data = await api.post(`/api/canvas/${canvasId}`, {
190
+ action: "setAsHome",
191
+ });
192
+ if (cfg.json) {
193
+ outputJson(data);
194
+ }
195
+ else {
196
+ console.log(`Canvas ${canvasId} set as workspace home.`);
197
+ }
198
+ }
199
+ catch (error) {
200
+ handleError(error, cfg.json);
201
+ }
202
+ });
203
+ canvas
204
+ .command("archive")
205
+ .description("Archive a canvas (soft delete)")
206
+ .argument("<canvasId>", "Canvas ID")
207
+ .action(async (canvasId) => {
208
+ const cfg = resolveConfig(program.opts());
209
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
210
+ try {
211
+ const data = await api.post(`/api/canvas/${canvasId}`, {
212
+ action: "archive",
213
+ });
214
+ if (cfg.json) {
215
+ outputJson(data);
216
+ }
217
+ else {
218
+ console.log(`Archived canvas ${canvasId}`);
219
+ }
220
+ }
221
+ catch (error) {
222
+ handleError(error, cfg.json);
223
+ }
224
+ });
225
+ canvas
226
+ .command("unarchive")
227
+ .description("Unarchive (restore) an archived canvas")
228
+ .argument("<canvasId>", "Canvas ID")
229
+ .action(async (canvasId) => {
230
+ const cfg = resolveConfig(program.opts());
231
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
232
+ try {
233
+ const data = await api.post(`/api/canvas/${canvasId}`, {
234
+ action: "unarchive",
235
+ });
236
+ if (cfg.json) {
237
+ outputJson(data);
238
+ }
239
+ else {
240
+ console.log(`Unarchived canvas ${canvasId}`);
241
+ }
242
+ }
243
+ catch (error) {
244
+ handleError(error, cfg.json);
245
+ }
246
+ });
247
+ canvas
248
+ .command("add-node")
249
+ .description("Add a note to a canvas")
250
+ .argument("<canvasId>", "Canvas ID")
251
+ .requiredOption("--note <noteId>", "Note display ID or Convex ID")
252
+ .option("--x <n>", "X position", "100")
253
+ .option("--y <n>", "Y position", "100")
254
+ .action(async (canvasId, opts) => {
255
+ const cfg = resolveConfig(program.opts());
256
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
257
+ try {
258
+ const data = await api.post(`/api/canvas/${canvasId}`, {
259
+ action: "addNode",
260
+ noteId: opts.note,
261
+ positionX: parseInt(opts.x, 10),
262
+ positionY: parseInt(opts.y, 10),
263
+ });
264
+ if (cfg.quiet) {
265
+ outputQuiet(String(data.nodeId || data._id || ""));
266
+ return;
267
+ }
268
+ if (cfg.json) {
269
+ outputJson(data);
270
+ }
271
+ else {
272
+ console.log(`Added note ${opts.note} to canvas at (${opts.x}, ${opts.y})`);
273
+ }
274
+ }
275
+ catch (error) {
276
+ handleError(error, cfg.json);
277
+ }
278
+ });
279
+ canvas
280
+ .command("add-text")
281
+ .description("Add a text annotation to a canvas")
282
+ .argument("<canvasId>", "Canvas ID")
283
+ .requiredOption("--text <content>", "Text content")
284
+ .addOption(new Option("--size <size>", "Text size: heading (default, larger) or paragraph (smaller readable text)").choices(["heading", "paragraph"]).default("heading"))
285
+ .option("--x <n>", "X position", "100")
286
+ .option("--y <n>", "Y position", "100")
287
+ .action(async (canvasId, opts) => {
288
+ const cfg = resolveConfig(program.opts());
289
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
290
+ const fontSize = opts.size === "paragraph" ? 18 : 32;
291
+ try {
292
+ const data = await api.post(`/api/canvas/${canvasId}`, {
293
+ action: "addTextNode",
294
+ content: opts.text,
295
+ fontSize,
296
+ positionX: parseInt(opts.x, 10),
297
+ positionY: parseInt(opts.y, 10),
298
+ });
299
+ if (cfg.json) {
300
+ outputJson(data);
301
+ }
302
+ else {
303
+ console.log(`Added text node to canvas at (${opts.x}, ${opts.y})`);
304
+ }
305
+ }
306
+ catch (error) {
307
+ handleError(error, cfg.json);
308
+ }
309
+ });
310
+ canvas
311
+ .command("add-list")
312
+ .description("Add a list/grid node grouping notes on a canvas")
313
+ .argument("<canvasId>", "Canvas ID")
314
+ .requiredOption("--name <name>", "List name")
315
+ .option("--notes <ids>", "Note IDs (comma-separated, display IDs like TASK-6 or Convex IDs)")
316
+ .option("--view <mode>", "View mode: list or grid", "list")
317
+ .option("--x <n>", "X position", "100")
318
+ .option("--y <n>", "Y position", "100")
319
+ .action(async (canvasId, opts) => {
320
+ const cfg = resolveConfig(program.opts());
321
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
322
+ try {
323
+ if (opts.view !== "list" && opts.view !== "grid") {
324
+ throw new ValidationError(`Invalid view mode "${opts.view}". Must be "list" or "grid".`);
325
+ }
326
+ const noteIds = opts.notes ? opts.notes.split(",").map((id) => id.trim()) : [];
327
+ const data = await api.post(`/api/canvas/${canvasId}`, {
328
+ action: "addListNode",
329
+ name: opts.name,
330
+ noteIds: noteIds.length > 0 ? noteIds : undefined,
331
+ viewMode: opts.view,
332
+ positionX: parseInt(opts.x, 10),
333
+ positionY: parseInt(opts.y, 10),
334
+ });
335
+ if (cfg.json) {
336
+ outputJson(data);
337
+ }
338
+ else {
339
+ console.log(`Added "${opts.name}" (${opts.view}) with ${noteIds.length} notes to canvas`);
340
+ }
341
+ }
342
+ catch (error) {
343
+ handleError(error, cfg.json);
344
+ }
345
+ });
346
+ canvas
347
+ .command("add-link")
348
+ .description("Add a link to another canvas")
349
+ .argument("<canvasId>", "Canvas ID")
350
+ .requiredOption("--target <canvasId>", "Target canvas ID to link to")
351
+ .option("--x <n>", "X position", "100")
352
+ .option("--y <n>", "Y position", "100")
353
+ .action(async (canvasId, opts) => {
354
+ const cfg = resolveConfig(program.opts());
355
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
356
+ try {
357
+ const data = await api.post(`/api/canvas/${canvasId}`, {
358
+ action: "addCanvasLink",
359
+ linkedCanvasId: opts.target,
360
+ positionX: parseInt(opts.x, 10),
361
+ positionY: parseInt(opts.y, 10),
362
+ });
363
+ if (cfg.quiet) {
364
+ outputQuiet(String(data.nodeId || data._id || ""));
365
+ return;
366
+ }
367
+ if (cfg.json) {
368
+ outputJson(data);
369
+ }
370
+ else {
371
+ console.log(`Added link to canvas ${opts.target} at (${opts.x}, ${opts.y})`);
372
+ }
373
+ }
374
+ catch (error) {
375
+ handleError(error, cfg.json);
376
+ }
377
+ });
378
+ canvas
379
+ .command("bulk-add")
380
+ .description("Add multiple notes to a canvas in one operation with positions")
381
+ .argument("<canvasId>", "Canvas ID")
382
+ .requiredOption("--notes <json>", "JSON array of {noteId, x, y} objects")
383
+ .action(async (canvasId, opts) => {
384
+ const cfg = resolveConfig(program.opts());
385
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
386
+ try {
387
+ let items;
388
+ try {
389
+ items = JSON.parse(opts.notes);
390
+ }
391
+ catch {
392
+ throw new ValidationError("--notes must be valid JSON array, e.g. '[{\"noteId\":\"TASK-1\",\"x\":100,\"y\":200}]'");
393
+ }
394
+ if (!Array.isArray(items) || items.length === 0) {
395
+ throw new ValidationError("--notes must be a non-empty JSON array");
396
+ }
397
+ const notes = items.map((item, i) => {
398
+ const positionX = Number(item.x);
399
+ const positionY = Number(item.y);
400
+ if (isNaN(positionX) || isNaN(positionY)) {
401
+ throw new ValidationError(`Item ${i}: x and y must be numbers`);
402
+ }
403
+ return {
404
+ noteId: String(item.noteId),
405
+ positionX,
406
+ positionY,
407
+ };
408
+ });
409
+ const data = await api.post(`/api/canvas/${canvasId}`, {
410
+ action: "bulkAddNodes",
411
+ notes,
412
+ });
413
+ if (cfg.json) {
414
+ outputJson(data);
415
+ }
416
+ else {
417
+ const results = (data.results || []);
418
+ const added = results.filter((r) => r.status === "added").length;
419
+ const skipped = results.filter((r) => r.status !== "added").length;
420
+ console.log(`Added ${added} notes to canvas${skipped > 0 ? ` (${skipped} skipped/failed)` : ""}`);
421
+ }
422
+ }
423
+ catch (error) {
424
+ handleError(error, cfg.json);
425
+ }
426
+ });
427
+ canvas
428
+ .command("bulk-move")
429
+ .description("Move multiple nodes to new positions in one operation")
430
+ .argument("<canvasId>", "Canvas ID (used for routing/auth)")
431
+ .requiredOption("--moves <json>", "JSON array of {nodeId, nodeType, x, y} objects (nodeType: note|text|list|canvas)")
432
+ .action(async (canvasId, opts) => {
433
+ const cfg = resolveConfig(program.opts());
434
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
435
+ try {
436
+ let items;
437
+ try {
438
+ items = JSON.parse(opts.moves);
439
+ }
440
+ catch {
441
+ throw new ValidationError("--moves must be valid JSON array, e.g. '[{\"nodeId\":\"abc\",\"nodeType\":\"note\",\"x\":100,\"y\":200}]'");
442
+ }
443
+ if (!Array.isArray(items) || items.length === 0) {
444
+ throw new ValidationError("--moves must be a non-empty JSON array");
445
+ }
446
+ const validNodeTypes = new Set(["note", "text", "list", "canvas"]);
447
+ const moves = items.map((item, i) => {
448
+ const positionX = Number(item.x);
449
+ const positionY = Number(item.y);
450
+ if (isNaN(positionX) || isNaN(positionY)) {
451
+ throw new ValidationError(`Item ${i}: x and y must be numbers`);
452
+ }
453
+ const nodeType = item.nodeType || "note";
454
+ if (!validNodeTypes.has(nodeType)) {
455
+ throw new ValidationError(`Item ${i}: nodeType must be one of: note, text, list, canvas`);
456
+ }
457
+ return {
458
+ nodeId: String(item.nodeId),
459
+ nodeType: nodeType,
460
+ positionX,
461
+ positionY,
462
+ };
463
+ });
464
+ const data = await api.post(`/api/canvas/${canvasId}`, {
465
+ action: "bulkMoveNodes",
466
+ moves,
467
+ });
468
+ if (cfg.json) {
469
+ outputJson(data);
470
+ }
471
+ else {
472
+ const count = typeof data.count === "number" ? data.count : moves.length;
473
+ console.log(`Moved ${count} nodes`);
474
+ }
475
+ }
476
+ catch (error) {
477
+ handleError(error, cfg.json);
478
+ }
479
+ });
480
+ canvas
481
+ .command("bulk-remove")
482
+ .description("Remove multiple nodes from a canvas in one operation")
483
+ .argument("<canvasId>", "Canvas ID")
484
+ .requiredOption("--nodes <json>", "JSON array of node ID strings, e.g. '[\"nodeId1\",\"nodeId2\"]'")
485
+ .action(async (canvasId, opts) => {
486
+ const cfg = resolveConfig(program.opts());
487
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
488
+ try {
489
+ let nodeIds;
490
+ try {
491
+ nodeIds = JSON.parse(opts.nodes);
492
+ }
493
+ catch {
494
+ throw new ValidationError("--nodes must be valid JSON array, e.g. '[\"nodeId1\",\"nodeId2\"]'");
495
+ }
496
+ if (!Array.isArray(nodeIds) || nodeIds.length === 0) {
497
+ throw new ValidationError("--nodes must be a non-empty JSON array of strings");
498
+ }
499
+ nodeIds = nodeIds.map(String);
500
+ const data = await api.post(`/api/canvas/${canvasId}`, {
501
+ action: "bulkRemoveNodes",
502
+ nodeIds,
503
+ });
504
+ if (cfg.json) {
505
+ outputJson(data);
506
+ }
507
+ else {
508
+ const results = (data.results || []);
509
+ const deleted = results.filter((r) => r.status === "deleted").length;
510
+ const failed = results.filter((r) => r.status !== "deleted").length;
511
+ console.log(`Removed ${deleted} nodes from canvas${failed > 0 ? ` (${failed} failed)` : ""}`);
512
+ }
513
+ }
514
+ catch (error) {
515
+ handleError(error, cfg.json);
516
+ }
517
+ });
518
+ canvas
519
+ .command("add-edge")
520
+ .description("Connect two nodes on a canvas")
521
+ .argument("<canvasId>", "Canvas ID")
522
+ .requiredOption("--source <nodeId>", "Source node ID")
523
+ .requiredOption("--target <nodeId>", "Target node ID")
524
+ .option("--label <text>", "Edge label")
525
+ .action(async (canvasId, opts) => {
526
+ const cfg = resolveConfig(program.opts());
527
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
528
+ try {
529
+ const data = await api.post(`/api/canvas/${canvasId}`, {
530
+ action: "addEdge",
531
+ sourceNodeId: opts.source,
532
+ targetNodeId: opts.target,
533
+ label: opts.label,
534
+ });
535
+ if (cfg.json) {
536
+ outputJson(data);
537
+ }
538
+ else {
539
+ const label = opts.label ? ` [${opts.label}]` : "";
540
+ console.log(`Connected ${opts.source} -> ${opts.target}${label}`);
541
+ }
542
+ }
543
+ catch (error) {
544
+ handleError(error, cfg.json);
545
+ }
546
+ });
547
+ canvas
548
+ .command("move-node")
549
+ .description("Move a node to a new position on a canvas")
550
+ .argument("<canvasId>", "Canvas ID")
551
+ .requiredOption("--node <nodeId>", "Canvas node ID")
552
+ .requiredOption("--x <n>", "X position")
553
+ .requiredOption("--y <n>", "Y position")
554
+ .action(async (canvasId, opts) => {
555
+ const cfg = resolveConfig(program.opts());
556
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
557
+ try {
558
+ const positionX = Number(opts.x);
559
+ const positionY = Number(opts.y);
560
+ await api.post(`/api/canvas/${canvasId}`, {
561
+ action: "moveNode",
562
+ nodeId: opts.node,
563
+ positionX,
564
+ positionY,
565
+ });
566
+ if (cfg.json) {
567
+ outputJson({ success: true, nodeId: opts.node, positionX, positionY });
568
+ }
569
+ else {
570
+ console.log(`Moved node ${opts.node} to (${positionX}, ${positionY})`);
571
+ }
572
+ }
573
+ catch (error) {
574
+ handleError(error, cfg.json);
575
+ }
576
+ });
577
+ canvas
578
+ .command("remove-node")
579
+ .description("Remove a node from a canvas")
580
+ .argument("<canvasId>", "Canvas ID")
581
+ .requiredOption("--node <nodeId>", "Canvas node ID")
582
+ .action(async (canvasId, opts) => {
583
+ const cfg = resolveConfig(program.opts());
584
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
585
+ try {
586
+ await api.post(`/api/canvas/${canvasId}`, {
587
+ action: "removeNode",
588
+ nodeId: opts.node,
589
+ });
590
+ if (cfg.json) {
591
+ outputJson({ success: true, nodeId: opts.node });
592
+ }
593
+ else {
594
+ console.log(`Removed node ${opts.node} from canvas`);
595
+ }
596
+ }
597
+ catch (error) {
598
+ handleError(error, cfg.json);
599
+ }
600
+ });
601
+ // ---- Template commands ----
602
+ canvas
603
+ .command("templates")
604
+ .description("List available canvas templates")
605
+ .action(async () => {
606
+ const cfg = resolveConfig(program.opts());
607
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
608
+ try {
609
+ const data = await api.get("/api/canvas/template");
610
+ const templates = data.templates || [];
611
+ if (cfg.json) {
612
+ outputJson(templates);
613
+ return;
614
+ }
615
+ if (templates.length === 0) {
616
+ console.log("No templates available.");
617
+ return;
618
+ }
619
+ const rows = templates.map((t) => [
620
+ String(t.id || ""),
621
+ String(t.name || ""),
622
+ truncate(String(t.description || ""), 40),
623
+ String(t.category || ""),
624
+ String(t.zoneCount || 0),
625
+ ]);
626
+ outputTable(["ID", "NAME", "DESCRIPTION", "CATEGORY", "ZONES"], rows);
627
+ }
628
+ catch (error) {
629
+ handleError(error, cfg.json);
630
+ }
631
+ });
632
+ canvas
633
+ .command("from-template")
634
+ .description("Create a canvas from a predefined template")
635
+ .argument("<templateId>", "Template ID (use 'cn canvas templates' to list)")
636
+ .option("--title <title>", "Custom canvas title")
637
+ .option("--density <mode>", "Layout density: tight, medium, or spacious")
638
+ .option("--populate", "Auto-populate zones with matching notes from workspace")
639
+ .action(async (templateId, opts) => {
640
+ const cfg = resolveConfig(program.opts());
641
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
642
+ try {
643
+ // Validate template exists by fetching template list
644
+ const templateData = await api.get("/api/canvas/template");
645
+ const templates = templateData.templates || [];
646
+ const template = templates.find((t) => t.id === templateId);
647
+ if (!template) {
648
+ const available = templates.map((t) => t.id).join(", ");
649
+ throw new ValidationError(`Unknown template "${templateId}". Available: ${available}`);
650
+ }
651
+ // Validate density (must match LAYOUT_DENSITIES in lib/canvas/templates/types.ts)
652
+ const validDensities = ["tight", "medium", "spacious"];
653
+ if (opts.density && !validDensities.includes(opts.density)) {
654
+ throw new ValidationError(`Invalid density "${opts.density}". Must be: ${validDensities.join(", ")}`);
655
+ }
656
+ // If --populate, search notes for each zone
657
+ let zonePopulations;
658
+ if (opts.populate) {
659
+ zonePopulations = [];
660
+ const zones = template.zones;
661
+ if (zones) {
662
+ for (const zone of zones) {
663
+ const hint = zone.populationHint;
664
+ if (!hint || hint.startsEmpty)
665
+ continue;
666
+ const searchQuery = hint.searchQuery;
667
+ const noteTypes = hint.noteTypes;
668
+ const maxNotes = hint.maxNotes || 8;
669
+ // Search for matching notes
670
+ const searchParams = {
671
+ workspaceId: cfg.workspaceId,
672
+ limit: String(maxNotes),
673
+ };
674
+ if (searchQuery)
675
+ searchParams.search = searchQuery;
676
+ if (noteTypes && noteTypes.length > 0)
677
+ searchParams.types = noteTypes.join(",");
678
+ try {
679
+ const searchResult = await api.get("/api/notes", searchParams);
680
+ const notes = searchResult.notes || [];
681
+ if (notes.length > 0) {
682
+ zonePopulations.push({
683
+ zoneId: String(zone.id),
684
+ noteIds: notes.map((n) => String(n.displayId || n.id || n._id)),
685
+ });
686
+ }
687
+ }
688
+ catch {
689
+ // Search failed for this zone, continue without population
690
+ }
691
+ }
692
+ }
693
+ }
694
+ // Create canvas from template
695
+ const data = await api.post("/api/canvas/template", {
696
+ workspaceId: cfg.workspaceId,
697
+ templateId,
698
+ title: opts.title,
699
+ density: opts.density,
700
+ zonePopulations,
701
+ });
702
+ if (cfg.quiet) {
703
+ outputQuiet(String(data.canvasId || ""));
704
+ return;
705
+ }
706
+ if (cfg.json) {
707
+ outputJson(data);
708
+ }
709
+ else {
710
+ const name = opts.title || template.name;
711
+ console.log(`Created canvas from template "${templateId}": ${name}`);
712
+ console.log(`Canvas ID: ${data.canvasId}`);
713
+ console.log(`Zones: ${data.zoneCount}, Notes: ${data.noteCount}`);
714
+ }
715
+ }
716
+ catch (error) {
717
+ handleError(error, cfg.json);
718
+ }
719
+ });
720
+ }
721
+ //# sourceMappingURL=canvas.js.map