@creator-notes/cnotes 0.73.0 → 0.75.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creator-notes-cli",
3
- "version": "0.73.0",
3
+ "version": "0.75.0",
4
4
  "description": "CreatorNotes CLI: create notes, build canvases, search knowledge from the terminal",
5
5
  "author": {
6
6
  "name": "CreatorNotes",
@@ -1 +1 @@
1
- {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiK5C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmxF7D"}
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAwL5C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAq9F7D"}
@@ -10,6 +10,7 @@ import { outputJson, outputTable, outputQuiet, truncate, timeAgo, formatAgo } fr
10
10
  import { readCanvasMarkdown, renderCanvasMarkdown, parseSharedCanvasLink, sameServerOrigin, readCanvasSection, renderCanvasSectionMarkdown, } from "../lib/canvas-read.js";
11
11
  import { resolveAgentSession } from "../lib/claude-session.js";
12
12
  import { WARN, dim } from "../lib/style.js";
13
+ import { findOverlaps, layoutItemsFrom, parseSize, resolveAnchor, suggestFreeSpot, } from "../lib/canvas-layout.js";
13
14
  import { canvasUrl, canvasLinkForRef } from "../lib/urls.js";
14
15
  import { canvasActionPath, commentsListPath, parseCommentStatus, } from "../lib/canvas-paths.js";
15
16
  import { registerCanvasMilestoneCommands } from "./canvas-milestone.js";
@@ -66,6 +67,18 @@ function warnLayoutListMisuse(node) {
66
67
  * below a growing frame that shifted down with it (or didn't, and why).
67
68
  * Straddlers the grown frame ran over go to stderr like every layout warning.
68
69
  */
70
+ /**
71
+ * add-node / move-node take raw x/y with no packing. Say so on every use, and
72
+ * pass on the server's overlap lint when the box lands on something (a card
73
+ * dropped on a portal or a text block can't be clicked). stderr, so --json
74
+ * stdout stays parseable.
75
+ */
76
+ function warnRawPlacement(data, verb) {
77
+ const warning = data?.layoutWarning;
78
+ if (warning?.message)
79
+ console.error(`${WARN} ${warning.message}`);
80
+ console.error(dim(`${verb} is low-level: raw x/y, no collision check. Prefer \`canvas place\` (new items, packed) or \`canvas arrange\` (reflow existing), and \`canvas status\` to see the layout first.`));
81
+ }
69
82
  function printRippleSummary(ripple, dryRun = false) {
70
83
  if (!ripple || typeof ripple !== "object")
71
84
  return;
@@ -273,6 +286,11 @@ Examples:
273
286
  .description("Get canvas details with nodes and edges")
274
287
  .argument("<canvasId>", "Canvas ID")
275
288
  .addHelpText("after", `
289
+ Positions: --json splits items by kind (nodes, richtextNodes, listNodes,
290
+ canvasLinkNodes for portals, sectionNodes). To check where things sit, read
291
+ allNodes instead: every kind in ONE list with x, y, width and height. Or run
292
+ \`cnotes canvas status <canvasId>\` for that list plus any overlaps.
293
+
276
294
  Examples:
277
295
  $ cnotes canvas get <canvasId> --json
278
296
  `)
@@ -328,6 +346,87 @@ Examples:
328
346
  console.log(` ${ln.id}: -> ${name} (${ln.positionX}, ${ln.positionY})`);
329
347
  }
330
348
  }
349
+ console.log(dim("\nEvery item with its box, and any overlaps: cnotes canvas status " + canvasId));
350
+ }
351
+ catch (error) {
352
+ handleError(error, cfg.json);
353
+ }
354
+ });
355
+ canvas
356
+ .command("status")
357
+ .description("See a canvas's whole layout before you move anything: every item (cards, text, lists, portals, " +
358
+ "sections) as one list of rectangles, the items that overlap, and with --near a free spot next to an anchor.")
359
+ .argument("<canvasId>", "Canvas ID")
360
+ .option("--near <ref>", "Suggest a free spot next to this item (display id or node id) or point (x,y)")
361
+ .option("--size <WxH>", "Box to find room for with --near (default: a note card, 280x130)", "280x130")
362
+ .addHelpText("after", `
363
+ Sections are containers: they are listed, but a card inside one is not an
364
+ overlap. Read-only.
365
+
366
+ Examples:
367
+ $ cnotes canvas status CANVAS-361
368
+ $ cnotes canvas status CANVAS-361 --near DECISION-6KJ # where can this card go?
369
+ $ cnotes canvas status CANVAS-361 --near 900,100 --size 560x240 --json
370
+ `)
371
+ .action(async (canvasId, opts) => {
372
+ const cfg = resolveConfig(program.opts());
373
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
374
+ try {
375
+ const size = parseSize(opts.size);
376
+ if (!size)
377
+ throw new ValidationError(`--size must look like 280x130 (got "${opts.size}")`);
378
+ const data = await api.get(`/api/canvas/${encodeURIComponent(canvasId)}`, {
379
+ workspaceId: cfg.workspaceId,
380
+ });
381
+ const items = layoutItemsFrom(data);
382
+ const overlaps = findOverlaps(items);
383
+ let freeSpot = null;
384
+ if (opts.near) {
385
+ const anchor = resolveAnchor(items, opts.near);
386
+ if (!anchor) {
387
+ throw new ValidationError(`No item "${opts.near}" on this canvas.`, "Pass a display id or node id from the list above, or a point as x,y.");
388
+ }
389
+ const spot = suggestFreeSpot(items, anchor, size);
390
+ if (spot)
391
+ freeSpot = { ...spot, ...size, near: opts.near };
392
+ }
393
+ if (cfg.json) {
394
+ outputJson({
395
+ items,
396
+ overlaps: overlaps.map((o) => ({ a: o.a.ref, b: o.b.ref, area: o.area })),
397
+ ...(opts.near ? { freeSpot } : {}),
398
+ });
399
+ return;
400
+ }
401
+ const canvas = data.canvas;
402
+ console.log(`Canvas: ${canvas?.title ?? canvasId} · ${items.length} items`);
403
+ outputTable(["KIND", "REF", "LABEL", "X", "Y", "W", "H"], items.map((i) => [
404
+ i.kind,
405
+ truncate(i.ref, 28),
406
+ truncate(i.label, 36),
407
+ String(Math.round(i.x)),
408
+ String(Math.round(i.y)),
409
+ String(Math.round(i.width)),
410
+ String(Math.round(i.height)),
411
+ ]));
412
+ if (overlaps.length === 0) {
413
+ console.log("\nNo overlaps.");
414
+ }
415
+ else {
416
+ console.log(`\n${WARN} ${overlaps.length} overlap${overlaps.length === 1 ? "" : "s"} (one item covers the other):`);
417
+ for (const o of overlaps) {
418
+ console.log(` ${o.a.ref} (${o.a.kind}) × ${o.b.ref} (${o.b.kind}) ${o.area}px²`);
419
+ }
420
+ console.log(" Fix with `canvas arrange`, or move one to a free spot from `--near`.");
421
+ }
422
+ if (opts.near) {
423
+ if (freeSpot) {
424
+ console.log(`\nFree ${size.width}x${size.height} spot near ${opts.near}: x=${freeSpot.x} y=${freeSpot.y}`);
425
+ }
426
+ else {
427
+ console.log(`\nNo free ${size.width}x${size.height} spot near ${opts.near}; try \`canvas place --placement auto\`.`);
428
+ }
429
+ }
331
430
  }
332
431
  catch (error) {
333
432
  handleError(error, cfg.json);
@@ -699,12 +798,17 @@ Examples:
699
798
  });
700
799
  canvas
701
800
  .command("add-node")
702
- .description("Add a note to a canvas")
801
+ .description("Low-level: add one note card at raw x/y, with no collision check. To put cards on a canvas, " +
802
+ "use `canvas place` (packs them overlap-free, --placement auto finds the space). Warns when the card lands on something.")
703
803
  .argument("<canvasId>", "Canvas ID")
704
804
  .requiredOption("--note <noteId>", "Note display ID or Convex ID")
705
805
  .option("--x <n>", "X position", "100")
706
806
  .option("--y <n>", "Y position", "100")
707
807
  .addHelpText("after", `
808
+ Prefer \`canvas place\` for new cards. If you do pick x/y yourself, check the
809
+ spot first: \`cnotes canvas status <canvasId> --near <ref>\` lists every item's
810
+ box and suggests a free one.
811
+
708
812
  Examples:
709
813
  $ cnotes canvas add-node <canvasId> --note MEETING-12 --x 200 --y 300
710
814
  `)
@@ -733,6 +837,7 @@ Examples:
733
837
  if (link)
734
838
  console.log(`Link: ${link}`);
735
839
  }
840
+ warnRawPlacement(data, "add-node");
736
841
  }
737
842
  catch (error) {
738
843
  handleError(error, cfg.json);
@@ -1816,9 +1921,92 @@ Examples:
1816
1921
  handleError(error, cfg.json);
1817
1922
  }
1818
1923
  });
1924
+ canvas
1925
+ .command("update-text")
1926
+ .description("Edit a Text card IN PLACE: same node id, position, edges and mentions pointing at it. " +
1927
+ "Replace the whole copy, swap one exact snippet (--find/--replace), or --append a paragraph. " +
1928
+ "Never delete + re-add a card to change its words: that mints a new id and breaks what points at it.")
1929
+ .argument("<canvasId>", "Canvas ID")
1930
+ .requiredOption("--node <id>", "The Text card's node id (the id on kind:richtext elements in `canvas read --json`)")
1931
+ .option("--content <markdown>", "New copy for the whole card (markdown)")
1932
+ .option("--content-file <path>", "Read the new copy from a markdown file")
1933
+ .option("--find <text>", "Exact snippet to change; must occur exactly once in the card")
1934
+ .option("--replace <text>", "What --find becomes (pass \"\" to delete it)")
1935
+ .option("--append <markdown>", "Add a paragraph at the end of the card")
1936
+ .addOption(new Option("--size <size>", "New card size").choices(["small", "medium", "large"]))
1937
+ .option("--color <hex>", "New tint hex")
1938
+ .option("--clear-color", "Remove the tint")
1939
+ .option("--no-ripple", "If the card grows, leave the content below where it is (warn only)")
1940
+ .addHelpText("after", `
1941
+ The edit attaches to the active operation (cnotes operations begin) and shows
1942
+ in \`cnotes canvas activity\`. A card that grows pushes the band below it down,
1943
+ the same as a new card does; the response names anything it still runs into.
1944
+
1945
+ Examples:
1946
+ # Add today's date to a banner without touching its mentions
1947
+ $ cnotes canvas update-text CANVAS-359 --node <nodeId> --find "picks none" --replace "picks none (10 Oct 2026)"
1948
+
1949
+ # Append a line
1950
+ $ cnotes canvas update-text CANVAS-359 --node <nodeId> --append "Answered on 10 Oct 2026."
1951
+
1952
+ # Rewrite the whole card from a file
1953
+ $ cnotes canvas update-text CANVAS-359 --node <nodeId> --content-file ./banner.md
1954
+ `)
1955
+ .action(async (canvasId, opts) => {
1956
+ const cfg = resolveConfig(program.opts());
1957
+ const api = new ApiClient(cfg.serverUrl, cfg.token);
1958
+ try {
1959
+ if (opts.content !== undefined && opts.contentFile !== undefined) {
1960
+ throw new ValidationError("Pass --content or --content-file, not both.");
1961
+ }
1962
+ const content = opts.contentFile !== undefined ? readFileSafe(opts.contentFile) : opts.content;
1963
+ if ((opts.find === undefined) !== (opts.replace === undefined)) {
1964
+ throw new ValidationError("--find and --replace go together.");
1965
+ }
1966
+ const modes = [content !== undefined, opts.find !== undefined, opts.append !== undefined].filter(Boolean).length;
1967
+ if (modes > 1) {
1968
+ throw new ValidationError("Change the copy one way per call: --content, --find/--replace, or --append.");
1969
+ }
1970
+ const body = { action: "updateRichtextNode", nodeId: opts.node };
1971
+ if (content !== undefined)
1972
+ body.content = content;
1973
+ if (opts.find !== undefined) {
1974
+ body.find = opts.find;
1975
+ body.replace = opts.replace;
1976
+ }
1977
+ if (opts.append !== undefined)
1978
+ body.append = opts.append;
1979
+ if (opts.size !== undefined)
1980
+ body.size = opts.size;
1981
+ // '' is the server's "clear the tint" sentinel.
1982
+ if (opts.clearColor)
1983
+ body.colorHex = "";
1984
+ else if (typeof opts.color === "string")
1985
+ body.colorHex = opts.color;
1986
+ if (opts.ripple === false)
1987
+ body.ripple = false;
1988
+ if (Object.keys(body).length === 2) {
1989
+ throw new ValidationError("Nothing to change: pass --content, --find/--replace, --append, --size or --color.");
1990
+ }
1991
+ const data = await api.post(canvasActionPath(canvasId, cfg.workspaceId), body);
1992
+ if (cfg.json) {
1993
+ outputJson(data);
1994
+ return;
1995
+ }
1996
+ console.log(`Updated Text card ${opts.node}`);
1997
+ printRippleSummary(data.ripple);
1998
+ const warning = data.layoutWarning;
1999
+ if (warning?.message)
2000
+ console.error(`${WARN} ${warning.message}`);
2001
+ }
2002
+ catch (error) {
2003
+ handleError(error, cfg.json);
2004
+ }
2005
+ });
1819
2006
  canvas
1820
2007
  .command("move-node")
1821
- .description("Move a node to a new position on a canvas")
2008
+ .description("Low-level: move one node to raw x/y, with no collision check. To tidy or reflow a canvas, use " +
2009
+ "`canvas arrange`; to see what's where first, `canvas status`. Warns when the node lands on something.")
1822
2010
  .argument("<canvasId>", "Canvas ID")
1823
2011
  .requiredOption("--node <nodeId>", "Canvas node ID")
1824
2012
  .requiredOption("--x <n>", "X position")
@@ -1831,7 +2019,7 @@ Examples:
1831
2019
  try {
1832
2020
  const positionX = Number(opts.x);
1833
2021
  const positionY = Number(opts.y);
1834
- await api.post(canvasActionPath(canvasId, cfg.workspaceId), {
2022
+ const data = await api.post(canvasActionPath(canvasId, cfg.workspaceId), {
1835
2023
  action: "moveNode",
1836
2024
  nodeId: opts.node,
1837
2025
  nodeType: opts.type,
@@ -1839,11 +2027,18 @@ Examples:
1839
2027
  positionY,
1840
2028
  });
1841
2029
  if (cfg.json) {
1842
- outputJson({ success: true, nodeId: opts.node, positionX, positionY });
2030
+ outputJson({
2031
+ success: true,
2032
+ nodeId: opts.node,
2033
+ positionX,
2034
+ positionY,
2035
+ ...(data?.layoutWarning ? { layoutWarning: data.layoutWarning } : {}),
2036
+ });
1843
2037
  }
1844
2038
  else {
1845
2039
  console.log(`Moved node ${opts.node} to (${positionX}, ${positionY})`);
1846
2040
  }
2041
+ warnRawPlacement(data, "move-node");
1847
2042
  }
1848
2043
  catch (error) {
1849
2044
  handleError(error, cfg.json);