@creator-notes/cnotes 0.26.0 → 0.30.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/README.md +3 -4
- package/dist/cn.js +0 -2
- package/dist/cn.js.map +1 -1
- package/dist/commands/canvas.d.ts.map +1 -1
- package/dist/commands/canvas.js +331 -54
- package/dist/commands/canvas.js.map +1 -1
- package/dist/commands/relationships.d.ts.map +1 -1
- package/dist/commands/relationships.js +145 -14
- package/dist/commands/relationships.js.map +1 -1
- package/dist/lib/api-client.d.ts +1 -1
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/build-schema.d.ts +3 -1
- package/dist/lib/build-schema.d.ts.map +1 -1
- package/dist/lib/build-schema.js +3 -1
- package/dist/lib/build-schema.js.map +1 -1
- package/dist/lib/canvas-read.d.ts +59 -13
- package/dist/lib/canvas-read.d.ts.map +1 -1
- package/dist/lib/canvas-read.js +209 -17
- package/dist/lib/canvas-read.js.map +1 -1
- package/dist/lib/install-skill.d.ts +7 -2
- package/dist/lib/install-skill.d.ts.map +1 -1
- package/dist/lib/install-skill.js +44 -12
- package/dist/lib/install-skill.js.map +1 -1
- package/dist/mcp-server.js +98 -65
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/cnotes/SKILL.md +172 -75
- package/skills/cnotes/references/humanizer.md +267 -0
package/README.md
CHANGED
|
@@ -36,14 +36,13 @@ cnotes search semantic "onboarding flow"
|
|
|
36
36
|
| `cnotes versions list\|create` | `cnotes v` | Note version history |
|
|
37
37
|
| `cnotes canvas list\|get\|create\|delete` | `cnotes c` | Manage canvases |
|
|
38
38
|
| `cnotes canvas place` | | Declarative layout — server packs items with no overlap |
|
|
39
|
-
| `cnotes canvas add-node\|add-text\|add-
|
|
39
|
+
| `cnotes canvas add-node\|add-text\|add-list\|add-link` | | Add a single node at explicit `--x`/`--y` (`add-richtext` is a deprecated alias of `add-text`) |
|
|
40
40
|
| `cnotes canvas bulk-add\|bulk-move\|bulk-remove` | | Bulk canvas operations — requires explicit coordinates |
|
|
41
41
|
| `cnotes canvas from-template` | | Create canvas from template |
|
|
42
42
|
| `cnotes search semantic` | `cnotes s` | Semantic search across notes |
|
|
43
43
|
| `cnotes timeline` | `cnotes tl` | Recent activity feed |
|
|
44
44
|
| `cnotes relationships list` | `cnotes rel` | Note relationships |
|
|
45
45
|
| `cnotes types list\|create\|update` | `cnotes t` | Manage note types (supertags) |
|
|
46
|
-
| `cnotes memory query\|facts\|entities` | `cnotes mem` | Query workspace knowledge |
|
|
47
46
|
| `cnotes workspace list\|select\|current` | | Workspace management |
|
|
48
47
|
| `cnotes config get-server\|set-server` | | CLI configuration |
|
|
49
48
|
| `cnotes mcp setup` | | Print MCP server config for Claude |
|
|
@@ -171,7 +170,7 @@ cnotes canvas agent-run wrap \
|
|
|
171
170
|
--canvas <canvasId> \
|
|
172
171
|
--prompt "Reorganize roadmap into Q1/Q2 clusters" \
|
|
173
172
|
-- bash -c '
|
|
174
|
-
cnotes canvas add-text "$CANVAS" --
|
|
173
|
+
cnotes canvas add-text "$CANVAS" --content "# Q1" --x 100 --y 100
|
|
175
174
|
cnotes canvas add-edge "$CANVAS" --source <id1> --target <id2>
|
|
176
175
|
'
|
|
177
176
|
```
|
|
@@ -187,7 +186,7 @@ Four building blocks:
|
|
|
187
186
|
- **`stack`** — items flow along one axis (`vertical` or `horizontal`), like CSS flexbox
|
|
188
187
|
- **`grid`** — N columns, items wrap to new rows, like CSS grid auto-flow
|
|
189
188
|
- **`anchor`** — place a sub-tree relative to an existing canvas node
|
|
190
|
-
- **`item`** — a leaf: `note`, `
|
|
189
|
+
- **`item`** — a leaf: `note`, `richtext` (the Text element — a markdown heading with no tint IS a heading), `list`, `canvas` (link), or `section` (frame)
|
|
191
190
|
|
|
192
191
|
Example — a richtext header above a 3-column grid of notes:
|
|
193
192
|
|
package/dist/cn.js
CHANGED
|
@@ -17,7 +17,6 @@ import { registerRelationshipsCommands } from "./commands/relationships.js";
|
|
|
17
17
|
import { registerSearchCommands } from "./commands/search.js";
|
|
18
18
|
import { registerTimelineCommands } from "./commands/timeline.js";
|
|
19
19
|
import { registerTypesCommands } from "./commands/types.js";
|
|
20
|
-
import { registerMemoryCommands } from "./commands/memory.js";
|
|
21
20
|
import { registerFilesCommands } from "./commands/files.js";
|
|
22
21
|
import { registerMcpCommands } from "./commands/mcp.js";
|
|
23
22
|
import { registerInitCommand } from "./commands/init.js";
|
|
@@ -69,7 +68,6 @@ registerRelationshipsCommands(program);
|
|
|
69
68
|
registerSearchCommands(program);
|
|
70
69
|
registerTimelineCommands(program);
|
|
71
70
|
registerTypesCommands(program);
|
|
72
|
-
registerMemoryCommands(program);
|
|
73
71
|
registerFilesCommands(program);
|
|
74
72
|
registerMcpCommands(program);
|
|
75
73
|
registerSchemaCommand(program);
|
package/dist/cn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn.js","sourceRoot":"","sources":["../src/cn.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AACvC,MAAM,WAAW,GAAW,GAAG,CAAC,OAAO,CAAC;AACxC,OAAO,cAAc,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cn.js","sourceRoot":"","sources":["../src/cn.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/commands/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAkG5C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiwE7D"}
|
package/dist/commands/canvas.js
CHANGED
|
@@ -657,55 +657,19 @@ Examples:
|
|
|
657
657
|
handleError(error, cfg.json);
|
|
658
658
|
}
|
|
659
659
|
});
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
`)
|
|
674
|
-
.action(async (canvasId, opts) => {
|
|
675
|
-
const cfg = resolveConfig(program.opts());
|
|
676
|
-
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
677
|
-
const fontSize = opts.size === "paragraph" ? 18 : 32;
|
|
678
|
-
try {
|
|
679
|
-
const data = await api.post(`/api/canvas/${canvasId}`, {
|
|
680
|
-
action: "addTextNode",
|
|
681
|
-
content: opts.text,
|
|
682
|
-
fontSize,
|
|
683
|
-
colorVariant: opts.color,
|
|
684
|
-
positionX: parseInt(opts.x, 10),
|
|
685
|
-
positionY: parseInt(opts.y, 10),
|
|
686
|
-
});
|
|
687
|
-
if (cfg.json) {
|
|
688
|
-
outputJson(data);
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
console.log(`Added text node to canvas at (${opts.x}, ${opts.y})`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
catch (error) {
|
|
695
|
-
handleError(error, cfg.json);
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
canvas
|
|
699
|
-
.command("add-richtext")
|
|
700
|
-
.description("Add a richtext sticky to a canvas: orientation banners, emphasis quoting a note, image tiles. Sticky-sized (≤ ~3 sentences) — richtext is unversioned and unsearchable, so knowledge worth keeping goes in a typed note. Plain markdown only: NO [NOTE-X](relationship:...) mention syntax (corrupts the card)")
|
|
701
|
-
.argument("<canvasId>", "Canvas ID")
|
|
702
|
-
.option("--content <content>", "Richtext content inline (markdown; plain display IDs only, no mention syntax)")
|
|
703
|
-
.option("--content-file <path>", "Read richtext content (markdown) from a file")
|
|
704
|
-
.addOption(new Option("--size <size>", "Display size: small (560px), medium (1120px), large (1680px billboard)").choices(["small", "medium", "large"]))
|
|
705
|
-
.option("--color <hex>", "Background color hex — omit unless it encodes a role used consistently on this canvas; never pastels")
|
|
706
|
-
.option("--x <n>", "X position", "100")
|
|
707
|
-
.option("--y <n>", "Y position", "100")
|
|
708
|
-
.action(async (canvasId, opts) => {
|
|
660
|
+
// --- Text element (wire type `richtext`) ---
|
|
661
|
+
//
|
|
662
|
+
// The canvas has ONE free-form text element, called "Text", and it is a richtext
|
|
663
|
+
// node. The old `text` node kind (the "Heading": plain string + fontSize) is LEGACY:
|
|
664
|
+
// existing ones still render, move and delete, but nothing creates them any more. A
|
|
665
|
+
// Text element with no background tint and `# Foo` as its content IS a heading, which
|
|
666
|
+
// is what made the old kind redundant.
|
|
667
|
+
//
|
|
668
|
+
// `add-text` and `add-richtext` are the same command (add-richtext is the deprecated
|
|
669
|
+
// alias); both post `addRichtextNode`. The old `add-text` flags (--text, --size
|
|
670
|
+
// heading|paragraph, --color normal|muted|highlighted) are gone on purpose — an old
|
|
671
|
+
// invocation must fail loudly rather than be silently reinterpreted.
|
|
672
|
+
const addTextElementAction = async (canvasId, opts) => {
|
|
709
673
|
const cfg = resolveConfig(program.opts());
|
|
710
674
|
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
711
675
|
let content;
|
|
@@ -719,12 +683,30 @@ Examples:
|
|
|
719
683
|
handleError(new ValidationError("Either --content or --content-file is required"), cfg.json);
|
|
720
684
|
return;
|
|
721
685
|
}
|
|
686
|
+
// --color is a background tint HEX. Reject anything else so the retired heading
|
|
687
|
+
// flags (--color normal|muted|highlighted) fail loudly instead of being posted
|
|
688
|
+
// through as a nonsense colorHex.
|
|
689
|
+
//
|
|
690
|
+
// This must accept exactly what the server accepts — #RGB or #RRGGBB, per
|
|
691
|
+
// HEX_COLOR_REGEX in convex/canvas.ts. The create mutation does NOT re-validate
|
|
692
|
+
// colorHex (only the update paths do), so anything we let through is stored raw:
|
|
693
|
+
// a hex without the leading `#` is not a valid CSS color, so the tint would
|
|
694
|
+
// silently not render AND the node would then fail every later style update.
|
|
695
|
+
// Hence: allow a bare hex for convenience, but normalize the `#` on before POST.
|
|
696
|
+
let colorHex;
|
|
697
|
+
if (opts.color) {
|
|
698
|
+
if (!/^#?(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(opts.color)) {
|
|
699
|
+
handleError(new ValidationError(`--color must be a 3- or 6-digit hex color (e.g. '#c2410c'), got '${opts.color}'. There are no named color variants — a text element has no background unless you pass a hex.`), cfg.json);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
colorHex = opts.color.startsWith("#") ? opts.color : `#${opts.color}`;
|
|
703
|
+
}
|
|
722
704
|
try {
|
|
723
705
|
const data = await api.post(`/api/canvas/${canvasId}`, {
|
|
724
706
|
action: "addRichtextNode",
|
|
725
707
|
content,
|
|
726
708
|
size: opts.size,
|
|
727
|
-
colorHex
|
|
709
|
+
colorHex,
|
|
728
710
|
positionX: parseInt(opts.x, 10),
|
|
729
711
|
positionY: parseInt(opts.y, 10),
|
|
730
712
|
});
|
|
@@ -733,13 +715,32 @@ Examples:
|
|
|
733
715
|
}
|
|
734
716
|
else {
|
|
735
717
|
const heightInfo = data.estimatedHeight ? ` — estimated height: ${data.estimatedHeight}px` : '';
|
|
736
|
-
console.log(`Added
|
|
718
|
+
console.log(`Added text element to canvas at (${opts.x}, ${opts.y})${heightInfo}`);
|
|
737
719
|
}
|
|
738
720
|
}
|
|
739
721
|
catch (error) {
|
|
740
722
|
handleError(error, cfg.json);
|
|
741
723
|
}
|
|
742
|
-
}
|
|
724
|
+
};
|
|
725
|
+
const defineAddTextCommand = (name, description) => canvas
|
|
726
|
+
.command(name)
|
|
727
|
+
.description(description)
|
|
728
|
+
.argument("<canvasId>", "Canvas ID")
|
|
729
|
+
.option("--content <content>", "Text content inline (markdown — headings, bold, links; plain display IDs only, no mention syntax)")
|
|
730
|
+
.option("--content-file <path>", "Read text content (markdown) from a file")
|
|
731
|
+
.addOption(new Option("--size <size>", "Display size: small (560px), medium (1120px), large (1680px billboard)").choices(["small", "medium", "large"]))
|
|
732
|
+
.option("--color <hex>", "Background tint hex — DEFAULT IS NO BACKGROUND; omit unless the tint encodes a role used consistently on this canvas; never pastels")
|
|
733
|
+
.option("--x <n>", "X position", "100")
|
|
734
|
+
.option("--y <n>", "Y position", "100")
|
|
735
|
+
.addHelpText("after", `
|
|
736
|
+
Examples:
|
|
737
|
+
$ cnotes canvas add-text <canvasId> --content '# Q1 Goals' # a heading: markdown h1, untinted
|
|
738
|
+
$ cnotes canvas add-text <canvasId> --content-file ./banner.md --size medium # orientation banner
|
|
739
|
+
$ cnotes canvas add-text <canvasId> --content 'Read left to right.' --color '#c2410c'
|
|
740
|
+
`)
|
|
741
|
+
.action(addTextElementAction);
|
|
742
|
+
defineAddTextCommand("add-text", "Add a Text element to a canvas — free-form rich text placed on the canvas (markdown: headings, emphasis, images), with an optional background tint (none by default). A heading is just `--content '# Title'`. Keep it card-sized (≤ ~3 sentences): text elements are unversioned and unsearchable, so knowledge worth keeping goes in a typed note. Plain markdown only: NO [NOTE-X](relationship:...) mention syntax (corrupts the card)");
|
|
743
|
+
defineAddTextCommand("add-richtext", "[DEPRECATED — use `add-text`] Alias of `add-text`: adds the same Text element (richtext) with the same flags. Kept for existing scripts; prefer `add-text`.");
|
|
743
744
|
canvas
|
|
744
745
|
.command("add-section")
|
|
745
746
|
.description("Add a section frame — a named background region (Miro-style). Whatever sits inside moves with the frame when dragged; notes inside stay individually draggable. Mints a SECTION-<n> id: mention it from notes as [[SECTION-<n>]] to deep-link readers to that region, so name frames like places (\"Shaping\", \"Open Questions\"), not sentences. With --notes the frame is created WITH those notes laid out inside it (collision-aware, no coordinate math — the preferred way to build a populated section).")
|
|
@@ -1112,11 +1113,217 @@ Placement (relative to nodes already on the canvas):
|
|
|
1112
1113
|
handleError(error, cfg.json);
|
|
1113
1114
|
}
|
|
1114
1115
|
});
|
|
1116
|
+
canvas
|
|
1117
|
+
.command("arrange")
|
|
1118
|
+
.description("Reflow EXISTING canvas nodes with the layout solver (no coordinate math). " +
|
|
1119
|
+
"Unlike `place` (create-only), this MOVES nodes already on the canvas — " +
|
|
1120
|
+
"mixed kinds welcome (note, canvas-link, richtext, list). With --section, " +
|
|
1121
|
+
"packs them into that frame and auto-resizes it in place (SECTION-<n> stays stable).")
|
|
1122
|
+
.argument("<canvasId>", "Canvas ID")
|
|
1123
|
+
.option("--section <id>", "Target SECTION-<n> (or raw id) to pack into; auto-resizes the frame around the new layout")
|
|
1124
|
+
.option("--nodes <json>", 'JSON array of {nodeId, nodeType} to rearrange, e.g. \'[{"nodeId":"…","nodeType":"canvas"}]\'')
|
|
1125
|
+
.option("--nodes-stdin", "Read the --nodes JSON array from stdin")
|
|
1126
|
+
.option("--layout <mode>", "Packing mode when using --nodes: grid (default) or stack", "grid")
|
|
1127
|
+
.option("--columns <n>", "Grid columns when --layout grid", "3")
|
|
1128
|
+
.option("--gap <preset>", "Gap preset or raw px: tight | medium | spacious | <number>", "medium")
|
|
1129
|
+
.option("--stack-axis <axis>", "Axis when --layout stack: vertical | horizontal", "vertical")
|
|
1130
|
+
.option("--no-auto-resize", "With --section: move nodes but leave the frame bounds unchanged")
|
|
1131
|
+
.option("--spec <file>", "Full layout document JSON (type: \"existing\" leaves). Alternative to --nodes")
|
|
1132
|
+
.option("--spec-inline <json>", "Full layout document as a JSON string")
|
|
1133
|
+
.option("--spec-stdin", "Read a full layout document from stdin")
|
|
1134
|
+
.option("--dry-run", "Solve and preview positions without moving anything")
|
|
1135
|
+
.addHelpText("after", `
|
|
1136
|
+
Examples:
|
|
1137
|
+
$ cnotes canvas arrange CANVAS-265 --section SECTION-90 \\
|
|
1138
|
+
--nodes '[{"nodeId":"abc","nodeType":"canvas"},{"nodeId":"NOTE-3","nodeType":"note"}]' \\
|
|
1139
|
+
--layout grid --columns 4 --gap medium
|
|
1140
|
+
$ cnotes canvas arrange CANVAS-265 --spec ./reflow.json --section SECTION-90
|
|
1141
|
+
`)
|
|
1142
|
+
.action(async (canvasId, opts) => {
|
|
1143
|
+
const cfg = resolveConfig(program.opts());
|
|
1144
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
1145
|
+
try {
|
|
1146
|
+
const validNodeTypes = new Set([
|
|
1147
|
+
"note",
|
|
1148
|
+
"text",
|
|
1149
|
+
"list",
|
|
1150
|
+
"canvas",
|
|
1151
|
+
"richtext",
|
|
1152
|
+
]);
|
|
1153
|
+
const specMode = Boolean(opts.spec || opts.specInline || opts.specStdin);
|
|
1154
|
+
const nodesMode = Boolean(opts.nodes || opts.nodesStdin);
|
|
1155
|
+
if (specMode && nodesMode) {
|
|
1156
|
+
throw new ValidationError("Use either --nodes / --nodes-stdin OR a full --spec, not both");
|
|
1157
|
+
}
|
|
1158
|
+
if (!specMode && !nodesMode) {
|
|
1159
|
+
throw new ValidationError("Provide --nodes (or --nodes-stdin), or a full --spec / --spec-inline / --spec-stdin");
|
|
1160
|
+
}
|
|
1161
|
+
let doc;
|
|
1162
|
+
if (specMode) {
|
|
1163
|
+
let raw;
|
|
1164
|
+
if (opts.spec)
|
|
1165
|
+
raw = readFileSafe(opts.spec);
|
|
1166
|
+
else if (opts.specInline)
|
|
1167
|
+
raw = opts.specInline;
|
|
1168
|
+
else {
|
|
1169
|
+
raw = await new Promise((resolve, reject) => {
|
|
1170
|
+
const chunks = [];
|
|
1171
|
+
process.stdin.on("data", (c) => chunks.push(c));
|
|
1172
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
1173
|
+
process.stdin.on("error", reject);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
let parsed;
|
|
1177
|
+
try {
|
|
1178
|
+
parsed = JSON.parse(raw);
|
|
1179
|
+
}
|
|
1180
|
+
catch (e) {
|
|
1181
|
+
throw new ValidationError(`Layout spec is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
1182
|
+
}
|
|
1183
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1184
|
+
throw new ValidationError("Layout spec must be a JSON object");
|
|
1185
|
+
}
|
|
1186
|
+
doc = parsed;
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
let nodesRaw;
|
|
1190
|
+
if (opts.nodesStdin) {
|
|
1191
|
+
nodesRaw = await new Promise((resolve, reject) => {
|
|
1192
|
+
const chunks = [];
|
|
1193
|
+
process.stdin.on("data", (c) => chunks.push(c));
|
|
1194
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
1195
|
+
process.stdin.on("error", reject);
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
nodesRaw = opts.nodes;
|
|
1200
|
+
}
|
|
1201
|
+
const nodes = parseJsonOption(nodesRaw, "--nodes", '[{"nodeId":"abc","nodeType":"canvas"}]');
|
|
1202
|
+
if (!Array.isArray(nodes) || nodes.length === 0) {
|
|
1203
|
+
throw new ValidationError("--nodes must be a non-empty JSON array");
|
|
1204
|
+
}
|
|
1205
|
+
const items = nodes.map((n, i) => {
|
|
1206
|
+
if (!n || typeof n.nodeId !== "string" || !n.nodeId.trim()) {
|
|
1207
|
+
throw new ValidationError(`Item ${i}: nodeId is required`);
|
|
1208
|
+
}
|
|
1209
|
+
const nodeType = n.nodeType || "note";
|
|
1210
|
+
if (!validNodeTypes.has(nodeType)) {
|
|
1211
|
+
throw new ValidationError(`Item ${i}: nodeType must be one of: note, text, list, canvas, richtext`);
|
|
1212
|
+
}
|
|
1213
|
+
return {
|
|
1214
|
+
kind: "item",
|
|
1215
|
+
type: "existing",
|
|
1216
|
+
nodeId: n.nodeId.trim(),
|
|
1217
|
+
nodeType,
|
|
1218
|
+
};
|
|
1219
|
+
});
|
|
1220
|
+
const layout = opts.layout || "grid";
|
|
1221
|
+
if (layout !== "grid" && layout !== "stack") {
|
|
1222
|
+
throw new ValidationError('--layout must be "grid" or "stack"');
|
|
1223
|
+
}
|
|
1224
|
+
let gap = opts.gap ?? "medium";
|
|
1225
|
+
if (gap !== "tight" && gap !== "medium" && gap !== "spacious") {
|
|
1226
|
+
const asNum = Number(gap);
|
|
1227
|
+
if (!Number.isFinite(asNum) || asNum < 0) {
|
|
1228
|
+
throw new ValidationError('--gap must be tight, medium, spacious, or a non-negative number');
|
|
1229
|
+
}
|
|
1230
|
+
gap = asNum;
|
|
1231
|
+
}
|
|
1232
|
+
let child;
|
|
1233
|
+
if (layout === "stack") {
|
|
1234
|
+
const axis = opts.stackAxis || "vertical";
|
|
1235
|
+
if (axis !== "vertical" && axis !== "horizontal") {
|
|
1236
|
+
throw new ValidationError('--stack-axis must be "vertical" or "horizontal"');
|
|
1237
|
+
}
|
|
1238
|
+
child = { kind: "stack", axis, gap, items };
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
const columns = Math.max(1, parseInt(opts.columns, 10) || 3);
|
|
1242
|
+
child = { kind: "grid", columns, gap, items };
|
|
1243
|
+
}
|
|
1244
|
+
if (opts.section) {
|
|
1245
|
+
// Name is required by the section container schema; the server
|
|
1246
|
+
// updates the existing frame in place and keeps SECTION-<n>.
|
|
1247
|
+
// Fetch the current name so we don't invent a placeholder title.
|
|
1248
|
+
const canvas = await api.get(`/api/canvas/${encodeURIComponent(canvasId)}?workspaceId=${encodeURIComponent(cfg.workspaceId)}`);
|
|
1249
|
+
const want = String(opts.section);
|
|
1250
|
+
const wantUpper = want.toUpperCase();
|
|
1251
|
+
const section = (canvas.sectionNodes ?? []).find((s) => s.id === want ||
|
|
1252
|
+
(s.displayId && s.displayId.toUpperCase() === wantUpper));
|
|
1253
|
+
if (!section) {
|
|
1254
|
+
throw new ValidationError(`Section "${opts.section}" not found on this canvas`);
|
|
1255
|
+
}
|
|
1256
|
+
doc = {
|
|
1257
|
+
root: {
|
|
1258
|
+
kind: "section",
|
|
1259
|
+
name: section.name,
|
|
1260
|
+
key: "frame",
|
|
1261
|
+
child,
|
|
1262
|
+
},
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
doc = { root: child };
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const autoResize = opts.autoResize !== false;
|
|
1270
|
+
const data = await api.post(`/api/canvas/${encodeURIComponent(canvasId)}?workspaceId=${encodeURIComponent(cfg.workspaceId)}`, {
|
|
1271
|
+
action: "arrangeLayout",
|
|
1272
|
+
doc,
|
|
1273
|
+
...(opts.section ? { targetSectionId: opts.section } : {}),
|
|
1274
|
+
autoResize,
|
|
1275
|
+
...(opts.dryRun ? { dryRun: true } : {}),
|
|
1276
|
+
});
|
|
1277
|
+
const link = opts.dryRun
|
|
1278
|
+
? null
|
|
1279
|
+
: canvasLinkForRef(cfg.serverUrl, cfg.workspaceId, canvasId);
|
|
1280
|
+
if (cfg.json) {
|
|
1281
|
+
if (link)
|
|
1282
|
+
data.url = link;
|
|
1283
|
+
outputJson(data);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const items = (data.items || []);
|
|
1287
|
+
const bbox = data.bbox;
|
|
1288
|
+
const moved = items.filter((r) => r.type === "existing" && r.id && !r.error).length;
|
|
1289
|
+
const failed = items.filter((r) => !r.id || r.error).length;
|
|
1290
|
+
const sectionItem = items.find((r) => r.type === "section");
|
|
1291
|
+
if (data.dryRun) {
|
|
1292
|
+
console.log(`[dry run] Would arrange ${items.filter((i) => i.type === "existing").length} node(s)` +
|
|
1293
|
+
(opts.section ? ` into ${opts.section}` : "") +
|
|
1294
|
+
" (no changes made).");
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
console.log(`Arranged ${moved} node(s)` +
|
|
1298
|
+
(failed > 0 ? ` (${failed} failed)` : "") +
|
|
1299
|
+
(opts.section ? ` into ${data.targetSectionDisplayId || opts.section}` : ""));
|
|
1300
|
+
}
|
|
1301
|
+
if (sectionItem &&
|
|
1302
|
+
sectionItem.width != null &&
|
|
1303
|
+
sectionItem.height != null) {
|
|
1304
|
+
console.log(` Frame → ${Math.round(Number(sectionItem.width))} × ${Math.round(Number(sectionItem.height))}px` +
|
|
1305
|
+
(autoResize ? "" : " (auto-resize off)"));
|
|
1306
|
+
}
|
|
1307
|
+
else if (bbox?.width && bbox?.height) {
|
|
1308
|
+
console.log(`Layout bounding box: ${Math.round(bbox.width)} × ${Math.round(bbox.height)}px`);
|
|
1309
|
+
}
|
|
1310
|
+
for (const item of items) {
|
|
1311
|
+
if (item.error) {
|
|
1312
|
+
console.log(` ✗ item[${item.index}] (${item.type}): ${item.error}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (link)
|
|
1316
|
+
console.log(`Link: ${link}`);
|
|
1317
|
+
}
|
|
1318
|
+
catch (error) {
|
|
1319
|
+
handleError(error, cfg.json);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1115
1322
|
canvas
|
|
1116
1323
|
.command("bulk-move")
|
|
1117
1324
|
.description("Move multiple nodes to new positions in one operation")
|
|
1118
1325
|
.argument("<canvasId>", "Canvas ID (used for routing/auth)")
|
|
1119
|
-
.requiredOption("--moves <json>", "JSON array of {nodeId, nodeType, x, y} objects (nodeType: note|text|list|canvas|richtext|section)")
|
|
1326
|
+
.requiredOption("--moves <json>", "JSON array of {nodeId, nodeType, x, y} objects (nodeType: note|text|list|canvas|richtext|section; `text` is legacy — old heading nodes, no longer creatable)")
|
|
1120
1327
|
.action(async (canvasId, opts) => {
|
|
1121
1328
|
const cfg = resolveConfig(program.opts());
|
|
1122
1329
|
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
@@ -1125,6 +1332,8 @@ Placement (relative to nodes already on the canvas):
|
|
|
1125
1332
|
if (!Array.isArray(items) || items.length === 0) {
|
|
1126
1333
|
throw new ValidationError("--moves must be a non-empty JSON array");
|
|
1127
1334
|
}
|
|
1335
|
+
// `text` is a LEGACY kind (the old "Heading" node): it can no longer be created,
|
|
1336
|
+
// but existing ones must stay movable and deletable, so it stays valid here.
|
|
1128
1337
|
const validNodeTypes = new Set(["note", "text", "list", "canvas", "richtext", "section"]);
|
|
1129
1338
|
const moves = items.map((item, i) => {
|
|
1130
1339
|
const positionX = Number(item.x);
|
|
@@ -1310,6 +1519,73 @@ Placement (relative to nodes already on the canvas):
|
|
|
1310
1519
|
handleError(error, cfg.json);
|
|
1311
1520
|
}
|
|
1312
1521
|
});
|
|
1522
|
+
canvas
|
|
1523
|
+
.command("update-section")
|
|
1524
|
+
.description("Resize, rename, move, or recolor an existing section frame IN PLACE, keeping its SECTION-<n> id. Use this to grow a frame around new content — never delete + re-add, which mints a NEW id (SECTION-51 becomes SECTION-57) and breaks every [[SECTION-51]] mention pointing at it.")
|
|
1525
|
+
.argument("<canvasId>", "Canvas ID")
|
|
1526
|
+
.requiredOption("--section <id>", "Section to update — SECTION-<n> display ID or a raw node ID")
|
|
1527
|
+
.option("--name <name>", "New frame title (title case)")
|
|
1528
|
+
.option("--x <n>", "New X position (top-left)")
|
|
1529
|
+
.option("--y <n>", "New Y position (top-left)")
|
|
1530
|
+
.option("--width <n>", "New frame width in px")
|
|
1531
|
+
.option("--height <n>", "New frame height in px")
|
|
1532
|
+
.option("--color <hex>", "New frame tint hex")
|
|
1533
|
+
.option("--clear-color", "Reset the frame tint to the default")
|
|
1534
|
+
.addHelpText("after", `
|
|
1535
|
+
Examples:
|
|
1536
|
+
# Grow a frame to fit content that has spilled out of it
|
|
1537
|
+
$ cnotes canvas update-section CANVAS-250 --section SECTION-51 --height 1800
|
|
1538
|
+
|
|
1539
|
+
# Rename without touching the id or anything inside it
|
|
1540
|
+
$ cnotes canvas update-section CANVAS-250 --section SECTION-51 --name "Open Questions"
|
|
1541
|
+
`)
|
|
1542
|
+
.action(async (canvasId, opts) => {
|
|
1543
|
+
const cfg = resolveConfig(program.opts());
|
|
1544
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
1545
|
+
try {
|
|
1546
|
+
// Reject non-numeric input here. Number("wide") is NaN, which
|
|
1547
|
+
// JSON.stringify writes as null, so the server would answer "expected
|
|
1548
|
+
// number, received null" about a flag the user typed as a word.
|
|
1549
|
+
const numeric = (flag, raw) => {
|
|
1550
|
+
const n = Number(raw);
|
|
1551
|
+
if (!Number.isFinite(n)) {
|
|
1552
|
+
throw new ValidationError(`--${flag} must be a number (got "${raw}")`);
|
|
1553
|
+
}
|
|
1554
|
+
return n;
|
|
1555
|
+
};
|
|
1556
|
+
const body = {
|
|
1557
|
+
action: "updateSectionNode",
|
|
1558
|
+
nodeId: opts.section,
|
|
1559
|
+
};
|
|
1560
|
+
if (typeof opts.name === "string")
|
|
1561
|
+
body.name = opts.name;
|
|
1562
|
+
if (opts.x !== undefined)
|
|
1563
|
+
body.positionX = numeric("x", opts.x);
|
|
1564
|
+
if (opts.y !== undefined)
|
|
1565
|
+
body.positionY = numeric("y", opts.y);
|
|
1566
|
+
if (opts.width !== undefined)
|
|
1567
|
+
body.width = numeric("width", opts.width);
|
|
1568
|
+
if (opts.height !== undefined)
|
|
1569
|
+
body.height = numeric("height", opts.height);
|
|
1570
|
+
// '' is the server's "clear the tint" sentinel.
|
|
1571
|
+
if (opts.clearColor)
|
|
1572
|
+
body.colorHex = "";
|
|
1573
|
+
else if (typeof opts.color === "string")
|
|
1574
|
+
body.colorHex = opts.color;
|
|
1575
|
+
// workspaceId is what lets the server resolve a CANVAS-<n> handle (the
|
|
1576
|
+
// form every example here uses) rather than demanding a raw Convex id.
|
|
1577
|
+
const data = await api.post(`/api/canvas/${encodeURIComponent(canvasId)}?workspaceId=${encodeURIComponent(cfg.workspaceId)}`, body);
|
|
1578
|
+
if (cfg.json) {
|
|
1579
|
+
outputJson(data);
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
console.log(`Updated section ${opts.section}`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
catch (error) {
|
|
1586
|
+
handleError(error, cfg.json);
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1313
1589
|
canvas
|
|
1314
1590
|
.command("move-node")
|
|
1315
1591
|
.description("Move a node to a new position on a canvas")
|
|
@@ -1317,7 +1593,8 @@ Placement (relative to nodes already on the canvas):
|
|
|
1317
1593
|
.requiredOption("--node <nodeId>", "Canvas node ID")
|
|
1318
1594
|
.requiredOption("--x <n>", "X position")
|
|
1319
1595
|
.requiredOption("--y <n>", "Y position")
|
|
1320
|
-
|
|
1596
|
+
// `text` is a LEGACY kind (the old "Heading" node) — movable/deletable, not creatable.
|
|
1597
|
+
.addOption(new Option("--type <type>", "Node type (defaults to note; `text` is legacy — old heading nodes, no longer creatable)").choices(["note", "text", "list", "canvas", "richtext", "section"]))
|
|
1321
1598
|
.action(async (canvasId, opts) => {
|
|
1322
1599
|
const cfg = resolveConfig(program.opts());
|
|
1323
1600
|
const api = new ApiClient(cfg.serverUrl, cfg.token);
|