@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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/commands/canvas.d.ts.map +1 -1
- package/dist/commands/canvas.js +199 -4
- package/dist/commands/canvas.js.map +1 -1
- package/dist/commands/types.js +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/lib/canvas-layout.d.ts +62 -0
- package/dist/lib/canvas-layout.d.ts.map +1 -0
- package/dist/lib/canvas-layout.js +129 -0
- package/dist/lib/canvas-layout.js.map +1 -0
- package/dist/lib/canvas-read.d.ts +1 -0
- package/dist/lib/canvas-read.d.ts.map +1 -1
- package/dist/lib/canvas-read.js +1 -0
- package/dist/lib/canvas-read.js.map +1 -1
- package/dist/mcp-server.js +23 -2
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/cnotes/SKILL.md +19 -2
|
@@ -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;
|
|
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"}
|
package/dist/commands/canvas.js
CHANGED
|
@@ -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("
|
|
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("
|
|
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({
|
|
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);
|