@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.
- package/dist/cn.d.ts +3 -0
- package/dist/cn.d.ts.map +1 -0
- package/dist/cn.js +38 -0
- package/dist/cn.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +184 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/canvas.d.ts +3 -0
- package/dist/commands/canvas.d.ts.map +1 -0
- package/dist/commands/canvas.js +721 -0
- package/dist/commands/canvas.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +47 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +59 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +145 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/notes.d.ts +3 -0
- package/dist/commands/notes.d.ts.map +1 -0
- package/dist/commands/notes.js +508 -0
- package/dist/commands/notes.js.map +1 -0
- package/dist/commands/relationships.d.ts +3 -0
- package/dist/commands/relationships.d.ts.map +1 -0
- package/dist/commands/relationships.js +47 -0
- package/dist/commands/relationships.js.map +1 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +52 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/timeline.d.ts +3 -0
- package/dist/commands/timeline.d.ts.map +1 -0
- package/dist/commands/timeline.js +92 -0
- package/dist/commands/timeline.js.map +1 -0
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +124 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/versions.d.ts +3 -0
- package/dist/commands/versions.d.ts.map +1 -0
- package/dist/commands/versions.js +104 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +116 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/lib/api-client.d.ts +12 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +76 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth-store.d.ts +18 -0
- package/dist/lib/auth-store.d.ts.map +1 -0
- package/dist/lib/auth-store.js +54 -0
- package/dist/lib/auth-store.js.map +1 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +54 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +20 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +54 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/markdown-to-tiptap.d.ts +17 -0
- package/dist/lib/markdown-to-tiptap.d.ts.map +1 -0
- package/dist/lib/markdown-to-tiptap.js +228 -0
- package/dist/lib/markdown-to-tiptap.js.map +1 -0
- package/dist/lib/output.d.ts +22 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +56 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/resolve-note.d.ts +7 -0
- package/dist/lib/resolve-note.d.ts.map +1 -0
- package/dist/lib/resolve-note.js +22 -0
- package/dist/lib/resolve-note.js.map +1 -0
- package/dist/lib/stdin.d.ts +5 -0
- package/dist/lib/stdin.d.ts.map +1 -0
- package/dist/lib/stdin.js +11 -0
- package/dist/lib/stdin.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +508 -0
- package/dist/mcp-server.js.map +1 -0
- 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
|