nodality 1.3.15 → 1.3.17
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/bin/install-skill.mjs +3 -3
- package/bin/mcp-server.mjs +46 -1
- package/bin/nodality.mjs +103 -0
- package/dist/animator.cjs.js +1 -1
- package/dist/animator.cjs.js.LICENSE.txt +1 -1
- package/dist/animator.esm.js +1 -1
- package/dist/animator.esm.js.LICENSE.txt +1 -1
- package/dist/audionew.cjs.js +1 -1
- package/dist/audionew.cjs.js.LICENSE.txt +1 -1
- package/dist/audionew.esm.js +1 -1
- package/dist/audionew.esm.js.LICENSE.txt +1 -1
- package/dist/base.cjs.js +1 -1
- package/dist/base.cjs.js.LICENSE.txt +1 -1
- package/dist/base.esm.js +1 -1
- package/dist/base.esm.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.cjs.js +1 -1
- package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.esm.js +1 -1
- package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.cjs.js +1 -1
- package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.esm.js +1 -1
- package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
- package/dist/bundle.umd.js +1 -1
- package/dist/bundle.umd.js.LICENSE.txt +1 -1
- package/dist/button.cjs.js +1 -1
- package/dist/button.cjs.js.LICENSE.txt +1 -1
- package/dist/button.esm.js +1 -1
- package/dist/button.esm.js.LICENSE.txt +1 -1
- package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/card-getter.esm.js.LICENSE.txt +1 -1
- package/dist/center.cjs.js +1 -1
- package/dist/center.cjs.js.LICENSE.txt +1 -1
- package/dist/center.esm.js +1 -1
- package/dist/center.esm.js.LICENSE.txt +1 -1
- package/dist/checkbox.cjs.js +1 -1
- package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
- package/dist/checkbox.esm.js +1 -1
- package/dist/checkbox.esm.js.LICENSE.txt +1 -1
- package/dist/code.cjs.js +1 -1
- package/dist/code.cjs.js.LICENSE.txt +1 -1
- package/dist/code.esm.js +1 -1
- package/dist/code.esm.js.LICENSE.txt +1 -1
- package/dist/container.cjs.js +1 -1
- package/dist/container.cjs.js.LICENSE.txt +1 -1
- package/dist/container.esm.js +1 -1
- package/dist/container.esm.js.LICENSE.txt +1 -1
- package/dist/data-list.cjs.js.LICENSE.txt +1 -1
- package/dist/data-list.esm.js.LICENSE.txt +1 -1
- package/dist/designer.cjs.js +1 -1
- package/dist/designer.cjs.js.LICENSE.txt +1 -1
- package/dist/designer.esm.js +1 -1
- package/dist/designer.esm.js.LICENSE.txt +1 -1
- package/dist/element-mapper.cjs.js +1 -1
- package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
- package/dist/element-mapper.esm.js +1 -1
- package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
- package/dist/finalresult.esm.js +1 -1
- package/dist/finalresult.esm.js.LICENSE.txt +1 -1
- package/dist/flex-card.cjs.js +1 -1
- package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-card.esm.js +1 -1
- package/dist/flex-card.esm.js.LICENSE.txt +1 -1
- package/dist/flex-grid.cjs.js +1 -1
- package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-grid.esm.js +1 -1
- package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
- package/dist/flex-row.cjs.js +1 -1
- package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-row.esm.js +1 -1
- package/dist/flex-row.esm.js.LICENSE.txt +1 -1
- package/dist/floating-input.cjs.js +1 -1
- package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
- package/dist/floating-input.esm.js +1 -1
- package/dist/floating-input.esm.js.LICENSE.txt +1 -1
- package/dist/free.cjs.js +1 -1
- package/dist/free.cjs.js.LICENSE.txt +1 -1
- package/dist/free.esm.js +1 -1
- package/dist/free.esm.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
- package/dist/image-picker.cjs.js +1 -1
- package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
- package/dist/image-picker.esm.js +1 -1
- package/dist/image-picker.esm.js.LICENSE.txt +1 -1
- package/dist/image.cjs.js +1 -1
- package/dist/image.cjs.js.LICENSE.txt +1 -1
- package/dist/image.esm.js +1 -1
- package/dist/image.esm.js.LICENSE.txt +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.LICENSE.txt +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.LICENSE.txt +1 -1
- package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
- package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
- package/dist/link-getter.cjs.js +1 -1
- package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/link-getter.esm.js +1 -1
- package/dist/link-getter.esm.js.LICENSE.txt +1 -1
- package/dist/link.cjs.js +1 -1
- package/dist/link.cjs.js.LICENSE.txt +1 -1
- package/dist/link.esm.js +1 -1
- package/dist/link.esm.js.LICENSE.txt +1 -1
- package/dist/meta-adder.cjs.js +1 -1
- package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
- package/dist/meta-adder.esm.js +1 -1
- package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
- package/dist/modal-2025.cjs.js +1 -1
- package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/modal-2025.esm.js +1 -1
- package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.cjs.js +1 -1
- package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.esm.js +1 -1
- package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.cjs.js +1 -1
- package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.esm.js +1 -1
- package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/picker.cjs.js +1 -1
- package/dist/picker.cjs.js.LICENSE.txt +1 -1
- package/dist/picker.esm.js +1 -1
- package/dist/picker.esm.js.LICENSE.txt +1 -1
- package/dist/progress.cjs.js +1 -1
- package/dist/progress.cjs.js.LICENSE.txt +1 -1
- package/dist/progress.esm.js +1 -1
- package/dist/progress.esm.js.LICENSE.txt +1 -1
- package/dist/radio.cjs.js.LICENSE.txt +1 -1
- package/dist/radio.esm.js.LICENSE.txt +1 -1
- package/dist/range.cjs.js.LICENSE.txt +1 -1
- package/dist/range.esm.js.LICENSE.txt +1 -1
- package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
- package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
- package/dist/side-bar.cjs.js +1 -1
- package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-bar.esm.js +1 -1
- package/dist/side-bar.esm.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.cjs.js +1 -1
- package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.esm.js +1 -1
- package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/simple-bar.cjs.js +1 -1
- package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/simple-bar.esm.js +1 -1
- package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
- package/dist/slider-2025.cjs.js +1 -1
- package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/slider-2025.esm.js +1 -1
- package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
- package/dist/spacer.cjs.js +1 -1
- package/dist/spacer.cjs.js.LICENSE.txt +1 -1
- package/dist/spacer.esm.js +1 -1
- package/dist/spacer.esm.js.LICENSE.txt +1 -1
- package/dist/stack.cjs.js +1 -1
- package/dist/stack.cjs.js.LICENSE.txt +1 -1
- package/dist/stack.esm.js +1 -1
- package/dist/stack.esm.js.LICENSE.txt +1 -1
- package/dist/stacker.cjs.js.LICENSE.txt +1 -1
- package/dist/stacker.esm.js.LICENSE.txt +1 -1
- package/dist/table.cjs.js +1 -1
- package/dist/table.cjs.js.LICENSE.txt +1 -1
- package/dist/table.esm.js +1 -1
- package/dist/table.esm.js.LICENSE.txt +1 -1
- package/dist/text-field.cjs.js +1 -1
- package/dist/text-field.cjs.js.LICENSE.txt +1 -1
- package/dist/text-field.esm.js +1 -1
- package/dist/text-field.esm.js.LICENSE.txt +1 -1
- package/dist/text.cjs.js +1 -1
- package/dist/text.cjs.js.LICENSE.txt +1 -1
- package/dist/text.esm.js +1 -1
- package/dist/text.esm.js.LICENSE.txt +1 -1
- package/dist/theme.cjs.js.LICENSE.txt +1 -1
- package/dist/theme.esm.js.LICENSE.txt +1 -1
- package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
- package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
- package/dist/ulist.cjs.js +1 -1
- package/dist/ulist.cjs.js.LICENSE.txt +1 -1
- package/dist/ulist.esm.js +1 -1
- package/dist/ulist.esm.js.LICENSE.txt +1 -1
- package/dist/video.cjs.js +1 -1
- package/dist/video.cjs.js.LICENSE.txt +1 -1
- package/dist/video.esm.js +1 -1
- package/dist/video.esm.js.LICENSE.txt +1 -1
- package/dist/wrap.cjs.js +1 -1
- package/dist/wrap.cjs.js.LICENSE.txt +1 -1
- package/dist/wrap.esm.js +1 -1
- package/dist/wrap.esm.js.LICENSE.txt +1 -1
- package/dist/zoom-card.cjs.js +1 -1
- package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
- package/dist/zoom-card.esm.js +1 -1
- package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
- package/layout/animator.js +38 -1
- package/layout/audio.js +1 -1
- package/layout/audionew.js +1 -1
- package/layout/base.js +1 -1
- package/layout/beta-desktop-bar.js +1 -1
- package/layout/beta-mobile-bar.js +1 -1
- package/layout/button.js +1 -1
- package/layout/center.js +1 -1
- package/layout/checkbox.js +1 -1
- package/layout/circle.js +1 -1
- package/layout/code.js +1 -1
- package/layout/container.js +1 -1
- package/layout/dropdown-2025.js +1 -1
- package/layout/flex-card.js +1 -1
- package/layout/flex-grid.js +1 -1
- package/layout/flex-row.js +1 -1
- package/layout/form-components/custom.js +1 -1
- package/layout/form-components/data-list.js +1 -1
- package/layout/form-components/floating-input.js +1 -1
- package/layout/form-components/form-all.js +1 -1
- package/layout/form-components/form.js +9 -2
- package/layout/form-components/image-picker.js +1 -1
- package/layout/form-components/picker.js +1 -1
- package/layout/form-components/radio.js +1 -1
- package/layout/form-components/radiogroup.js +1 -1
- package/layout/form-components/range.js +1 -1
- package/layout/free.js +1 -1
- package/layout/grid-overlay.js +1 -1
- package/layout/grid-switcher.js +1 -1
- package/layout/grid.js +1 -1
- package/layout/horizontal-scroller.js +1 -1
- package/layout/image.js +1 -1
- package/layout/index.js +1 -1
- package/layout/link.js +6 -3
- package/layout/list.js +1 -1
- package/layout/meta-adder.js +1 -1
- package/layout/modal-2025.js +1 -1
- package/layout/morph.js +1 -1
- package/layout/multiswitcher.js +1 -1
- package/layout/nav-bar.js +1 -1
- package/layout/nav-factor/custom-div.js +1 -1
- package/layout/new-nav-bar.js +1 -1
- package/layout/polygon.js +1 -1
- package/layout/prerender-site.js +1 -1
- package/layout/prerender.js +1 -1
- package/layout/progress.js +1 -1
- package/layout/row.js +1 -1
- package/layout/scroll-video.js +1 -1
- package/layout/shop.js +1 -1
- package/layout/side-bar.js +1 -1
- package/layout/side-nav-bar.js +1 -1
- package/layout/simple-bar.js +1 -1
- package/layout/slider-2025.js +1 -1
- package/layout/spacer.js +1 -1
- package/layout/stack.js +1 -1
- package/layout/svg.js +1 -1
- package/layout/switcher.js +1 -1
- package/layout/table.js +1 -1
- package/layout/text-field.js +4 -2
- package/layout/text.js +1 -1
- package/layout/ulist.js +1 -1
- package/layout/video.js +1 -1
- package/layout/wrap.js +1 -1
- package/layout/zoom-card.js +1 -1
- package/lib/agent-surface.js +1 -1
- package/lib/card-getter.js +1 -1
- package/lib/check-page.js +37 -5
- package/lib/codegen.js +1 -1
- package/lib/data.js +1 -1
- package/lib/designer.js +1 -1
- package/lib/element-mapper.js +3 -1
- package/lib/element-params.generated.js +3 -3
- package/lib/grid-geometry.js +1 -1
- package/lib/keyframe-animation.js +1 -1
- package/lib/known-inert.js +85 -0
- package/lib/link-getter.js +1 -1
- package/lib/morph-node.js +1 -1
- package/lib/normalize-spec.js +1 -1
- package/lib/parse-html.js +1 -1
- package/lib/raster-inspect.js +1 -1
- package/lib/raster-ops.js +1 -1
- package/lib/raster-presets.js +1 -1
- package/lib/resolve-refs.js +1 -1
- package/lib/scroll-video.js +1 -1
- package/lib/seo.js +1 -1
- package/lib/stacker.js +1 -1
- package/lib/suggest.js +1 -1
- package/lib/theme.js +1 -1
- package/lib/transform-anim.js +1 -1
- package/lib/transition.js +1 -1
- package/lib/validate-nodes.js +14 -1
- package/lib/webmcp-adapter.js +1 -1
- package/package.json +1 -1
- package/schema.json +2764 -957
- package/scripts/generate-schema.mjs +16 -0
- package/skills/nodality/SKILL.md +16 -1
package/bin/install-skill.mjs
CHANGED
|
@@ -55,19 +55,19 @@ function place(src, dest, content) {
|
|
|
55
55
|
// Merge our server into an existing config without touching anything
|
|
56
56
|
// else in it. A file that exists but does not parse is left alone and
|
|
57
57
|
// reported — clobbering a hand-edited config is worse than skipping.
|
|
58
|
-
function registerMcp(configPath) {
|
|
58
|
+
export function registerMcp(configPath, entry = MCP_ENTRY) {
|
|
59
59
|
let config = {};
|
|
60
60
|
if (fs.existsSync(configPath)) {
|
|
61
61
|
try {
|
|
62
62
|
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
63
63
|
} catch {
|
|
64
64
|
log(`SKIPPED ${configPath} — existing file is not valid JSON; add this by hand:`);
|
|
65
|
-
log(` "mcpServers": { "nodality": ${JSON.stringify(
|
|
65
|
+
log(` "mcpServers": { "nodality": ${JSON.stringify(entry)} }`);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
const existed = fs.existsSync(configPath);
|
|
70
|
-
config.mcpServers = { ...config.mcpServers, nodality:
|
|
70
|
+
config.mcpServers = { ...config.mcpServers, nodality: entry };
|
|
71
71
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
72
72
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
73
73
|
log(`${existed ? "updated " : "installed"} ${configPath} (mcpServers.nodality)`);
|
package/bin/mcp-server.mjs
CHANGED
|
@@ -221,6 +221,7 @@ const TOOLS = [
|
|
|
221
221
|
inputSchema: {
|
|
222
222
|
type: "object",
|
|
223
223
|
properties: {
|
|
224
|
+
viewport: { description: "\"phone\" (default, 390×844) or \"desktop\" (1440×900), or {width, height}. Width-dependent elements render their view for it." },
|
|
224
225
|
elements: ELEMENTS_SCHEMA,
|
|
225
226
|
nodes: NODES_SCHEMA,
|
|
226
227
|
output: {
|
|
@@ -286,6 +287,25 @@ const IMPL = {
|
|
|
286
287
|
const schema = JSON.parse(await readFile(join(here, "..", "schema.json"), "utf8"));
|
|
287
288
|
const types = schema.types || {};
|
|
288
289
|
if (!type) return ok(schema);
|
|
290
|
+
if (types[type] && Array.isArray(types[type].params)) {
|
|
291
|
+
// The type's own parameters first: what its mapper reads by name, then
|
|
292
|
+
// described ones, then the rest — inherited through components. A
|
|
293
|
+
// composite lists 150+, and the dozen that matter were alphabetised
|
|
294
|
+
// among them.
|
|
295
|
+
const t = types[type];
|
|
296
|
+
const rank = (p) => (p.via === "mapper" ? 0 : p.description ? 1 : 2);
|
|
297
|
+
const params = [...t.params].sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name));
|
|
298
|
+
return ok({
|
|
299
|
+
type, ...t,
|
|
300
|
+
summary: {
|
|
301
|
+
total: params.length,
|
|
302
|
+
readByType: params.filter((p) => p.via === "mapper").length,
|
|
303
|
+
throughComponents: params.filter((p) => p.via !== "mapper").length,
|
|
304
|
+
described: params.filter((p) => p.description).length,
|
|
305
|
+
},
|
|
306
|
+
params,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
289
309
|
if (!types[type]) {
|
|
290
310
|
// Same report shape as everything else, so an agent parses one thing.
|
|
291
311
|
return ok({
|
|
@@ -312,7 +332,7 @@ const IMPL = {
|
|
|
312
332
|
return ok(parseReport(String(html ?? ""), dom.window.document));
|
|
313
333
|
},
|
|
314
334
|
|
|
315
|
-
preview: async ({ elements, nodes, output }) => {
|
|
335
|
+
preview: async ({ elements, nodes, output, viewport }) => {
|
|
316
336
|
const N = nodes || [];
|
|
317
337
|
const report = validateNodes(N, elements);
|
|
318
338
|
if (!report.ok) return reportAsError(report);
|
|
@@ -338,10 +358,23 @@ const IMPL = {
|
|
|
338
358
|
`<title>Nodality preview</title></head><body>` +
|
|
339
359
|
`<div id="mount"></div></body></html>`, "utf8");
|
|
340
360
|
|
|
361
|
+
// The prerenderer emulates a phone by default, and anything chosen by
|
|
362
|
+
// width (a nav, a multiswitcher) renders as its phone view. "desktop"
|
|
363
|
+
// shows the other side.
|
|
364
|
+
const size = viewport === "desktop" ? { width: 1440, height: 900 }
|
|
365
|
+
: (viewport && typeof viewport === "object" ? viewport : { width: 390, height: 844 });
|
|
366
|
+
// Ids that rendered as an empty box, recorded while the DOM exists.
|
|
367
|
+
const empty = [];
|
|
368
|
+
const ids = [];
|
|
369
|
+
const collect = (list) => { for (const el of Array.isArray(list) ? list : []) {
|
|
370
|
+
if (el && typeof el === "object") { if (typeof el.id === "string") ids.push(el.id); collect(el.children); }
|
|
371
|
+
} };
|
|
372
|
+
collect(elements);
|
|
341
373
|
await prerender({
|
|
342
374
|
template,
|
|
343
375
|
mount: "#mount",
|
|
344
376
|
output: out,
|
|
377
|
+
viewport: size,
|
|
345
378
|
// `build` is handed the jsdom window and is expected to render
|
|
346
379
|
// into it. The Designer is imported HERE, inside the callback,
|
|
347
380
|
// rather than at module scope: prerender installs the DOM globals
|
|
@@ -351,6 +384,13 @@ const IMPL = {
|
|
|
351
384
|
build: async () => {
|
|
352
385
|
const { Des } = await import("../lib/designer.js");
|
|
353
386
|
new Des().nodes(N).add(elements).set({ mount: "#mount", code: false });
|
|
387
|
+
for (const id of ids) {
|
|
388
|
+
const node = document.querySelector(`[id="${id}"]`);
|
|
389
|
+
if (!node) { empty.push({ id, why: "not in the output" }); continue; }
|
|
390
|
+
const hasContent = node.children.length > 0 || (node.textContent || "").trim() !== ""
|
|
391
|
+
|| /^(IMG|VIDEO|AUDIO|INPUT|SELECT|TEXTAREA|CANVAS|IFRAME|HR|BR|SVG)$/i.test(node.tagName);
|
|
392
|
+
if (!hasContent) empty.push({ id, why: "rendered empty" });
|
|
393
|
+
}
|
|
354
394
|
},
|
|
355
395
|
});
|
|
356
396
|
|
|
@@ -381,7 +421,12 @@ const IMPL = {
|
|
|
381
421
|
|
|
382
422
|
return ok({
|
|
383
423
|
path: out, bytes,
|
|
424
|
+
viewport: size,
|
|
384
425
|
...(surface ? { surface } : {}),
|
|
426
|
+
// An element that rendered nothing is said, not left to look like a
|
|
427
|
+
// finished page: a 181-byte preview of a nav was the only sign that
|
|
428
|
+
// its view is chosen at run time.
|
|
429
|
+
...(empty.length ? { empty, emptyNote: "These ids rendered no content at this viewport. A runtime choice (multiswitcher, morph state), content that loads in the browser (commerce), or an element given nothing to show. Try viewport: \"desktop\", or open the file in a browser." } : {}),
|
|
385
430
|
// Said plainly, because an agent cannot see the result and would
|
|
386
431
|
// otherwise report a shader that never ran as working.
|
|
387
432
|
note: "Prerendered DOM only. Raster effects and morph transitions " +
|
package/bin/nodality.mjs
CHANGED
|
@@ -34,6 +34,60 @@ import path from "node:path";
|
|
|
34
34
|
import { prerenderSite } from "../layout/prerender-site.js";
|
|
35
35
|
import { prerender } from "../layout/prerender.js";
|
|
36
36
|
|
|
37
|
+
// ─── link-local: test an unreleased library in a real site ─────────
|
|
38
|
+
//
|
|
39
|
+
// Pre-release testing used to be a manual copy of dist/, lib/, layout/ and the
|
|
40
|
+
// schema into the site's node_modules — easy to get wrong, and invisible once
|
|
41
|
+
// done: nothing said the site was running something other than the release.
|
|
42
|
+
// This does the copy, leaves a marker every other command warns about, and
|
|
43
|
+
// undoes itself from the lockfile.
|
|
44
|
+
async function linkLocal(rest) {
|
|
45
|
+
const fs = await import("node:fs");
|
|
46
|
+
const path = await import("node:path");
|
|
47
|
+
const { execSync } = await import("node:child_process");
|
|
48
|
+
const target = path.join(process.cwd(), "node_modules", "nodality");
|
|
49
|
+
const marker = path.join(target, ".linked-local.json");
|
|
50
|
+
|
|
51
|
+
if (rest.includes("--undo")) {
|
|
52
|
+
if (!fs.existsSync(marker)) { console.log("[nodality] link-local: nothing to undo — the installed copy is a release"); return; }
|
|
53
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
54
|
+
execSync("npm install --no-audit --no-fund", { stdio: "inherit" });
|
|
55
|
+
const v = JSON.parse(fs.readFileSync(path.join(target, "package.json"), "utf8")).version;
|
|
56
|
+
console.log(`[nodality] link-local: restored the released nodality ${v}. Rebuild your bundle (e.g. npm run build or nodality stage).`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fromArg = rest.find((a) => a.startsWith("--from="));
|
|
61
|
+
if (!fromArg) {
|
|
62
|
+
console.error("[nodality] link-local: --from=<path to the nodality repository> is required (or --undo)");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const from = path.resolve(fromArg.slice("--from=".length));
|
|
66
|
+
const pkg = path.join(from, "package.json");
|
|
67
|
+
if (!fs.existsSync(pkg) || JSON.parse(fs.readFileSync(pkg, "utf8")).name !== "nodality") {
|
|
68
|
+
console.error(`[nodality] link-local: ${from} is not the nodality repository`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
if (!fs.existsSync(target)) {
|
|
72
|
+
console.error("[nodality] link-local: install nodality in this project first (npm i nodality)");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
if (!rest.includes("--no-build")) {
|
|
76
|
+
console.log(`[nodality] link-local: building ${from}…`);
|
|
77
|
+
execSync("npm run build", { cwd: from, stdio: "inherit" });
|
|
78
|
+
}
|
|
79
|
+
for (const part of ["dist", "lib", "layout", "bin", "schema.json"]) {
|
|
80
|
+
const src = path.join(from, part);
|
|
81
|
+
if (!fs.existsSync(src)) continue;
|
|
82
|
+
fs.rmSync(path.join(target, part), { recursive: true, force: true });
|
|
83
|
+
fs.cpSync(src, path.join(target, part), { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
const version = JSON.parse(fs.readFileSync(pkg, "utf8")).version;
|
|
86
|
+
fs.writeFileSync(marker, JSON.stringify({ from, builtAt: new Date().toISOString(), baseVersion: version }, null, 2));
|
|
87
|
+
console.log(`[nodality] link-local: this project now runs the LOCAL build from ${from} (on top of ${version}).`);
|
|
88
|
+
console.log("[nodality] Rebuild your bundle (e.g. npm run build or nodality stage). Every nodality command will warn until: npx nodality link-local --undo");
|
|
89
|
+
}
|
|
90
|
+
|
|
37
91
|
// ─── Usage ──────────────────────────────────────────────────────
|
|
38
92
|
|
|
39
93
|
function showUsage() {
|
|
@@ -55,6 +109,14 @@ function showUsage() {
|
|
|
55
109
|
# { "mcpServers": { "nodality": {
|
|
56
110
|
# "command": "npx",
|
|
57
111
|
# "args": ["nodality", "mcp"] } } }
|
|
112
|
+
nodality link-local --from=<repo> [--no-build] # Build a local nodality checkout and run
|
|
113
|
+
# this project on it, for testing before a
|
|
114
|
+
# release; every command warns while linked.
|
|
115
|
+
nodality link-local --undo # Back to the released version (lockfile).
|
|
116
|
+
nodality mcp --register [--root=<dir>] # Write that entry into <dir>/.mcp.json —
|
|
117
|
+
# the folder agent sessions start in
|
|
118
|
+
# (default: here). From a parent folder
|
|
119
|
+
# it runs this project's installed copy.
|
|
58
120
|
nodality skill [flags] # Install the /nodality skill into agent
|
|
59
121
|
# IDEs. No flags: interactive (or full
|
|
60
122
|
# default install when not a TTY).
|
|
@@ -869,6 +931,20 @@ async function main() {
|
|
|
869
931
|
}
|
|
870
932
|
|
|
871
933
|
const [command, ...rest] = process.argv.slice(2);
|
|
934
|
+
|
|
935
|
+
// A site running a locally built library says so on every command, so a
|
|
936
|
+
// pre-release copy is never mistaken for the release (see link-local).
|
|
937
|
+
{
|
|
938
|
+
const fs = await import("node:fs");
|
|
939
|
+
const path = await import("node:path");
|
|
940
|
+
const marker = path.join(process.cwd(), "node_modules", "nodality", ".linked-local.json");
|
|
941
|
+
if (command !== "link-local" && fs.existsSync(marker)) {
|
|
942
|
+
try {
|
|
943
|
+
const m = JSON.parse(fs.readFileSync(marker, "utf8"));
|
|
944
|
+
console.warn(`[nodality] ⚠ using a LOCAL build of nodality from ${m.from} (built ${m.builtAt}). Undo: npx nodality link-local --undo`);
|
|
945
|
+
} catch {}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
872
948
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
873
949
|
showUsage();
|
|
874
950
|
} else if (command === "prerender") {
|
|
@@ -887,6 +963,33 @@ async function main() {
|
|
|
887
963
|
} else if (command === "skill") {
|
|
888
964
|
const { runSkillInstall } = await import("./install-skill.mjs");
|
|
889
965
|
await runSkillInstall(rest);
|
|
966
|
+
} else if (command === "link-local") {
|
|
967
|
+
await linkLocal(rest);
|
|
968
|
+
} else if (command === "mcp" && rest.includes("--register")) {
|
|
969
|
+
// Register the server where agent sessions START, which is not always
|
|
970
|
+
// the project. A session opened one folder up never saw the project's
|
|
971
|
+
// own .mcp.json, and the agent drove the server by hand instead. From a
|
|
972
|
+
// parent root the entry runs npx with --prefix, so it is still the
|
|
973
|
+
// project's installed nodality that answers, not whatever npm resolves.
|
|
974
|
+
const path = await import("node:path");
|
|
975
|
+
const { registerMcp } = await import("./install-skill.mjs");
|
|
976
|
+
const rootArg = rest.find((a) => a.startsWith("--root="));
|
|
977
|
+
// Real paths on both sides: through a symlink (macOS /var is
|
|
978
|
+
// /private/var) the two spellings of one folder looked unrelated and a
|
|
979
|
+
// correct root was refused.
|
|
980
|
+
const fsMod = await import("node:fs");
|
|
981
|
+
const real = (p) => { try { return fsMod.realpathSync(p); } catch { return path.resolve(p); } };
|
|
982
|
+
const root = real(path.resolve(rootArg ? rootArg.slice("--root=".length) : process.cwd()));
|
|
983
|
+
const project = real(process.cwd());
|
|
984
|
+
const rel = path.relative(root, project);
|
|
985
|
+
if (rel.startsWith("..")) {
|
|
986
|
+
console.error(`[nodality] mcp --register: --root must be this project or a folder above it (${root})`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
const entry = rel === "" ? { command: "npx", args: ["nodality", "mcp"] }
|
|
990
|
+
: { command: "npx", args: ["--prefix", rel, "nodality", "mcp"] };
|
|
991
|
+
registerMcp(path.join(root, ".mcp.json"), entry);
|
|
992
|
+
console.log(`[nodality] start sessions in ${root} — the nodality tools are registered there`);
|
|
890
993
|
} else if (command === "mcp") {
|
|
891
994
|
// Lazily imported so that prerender/compile/stage pay nothing for a
|
|
892
995
|
// subcommand they never touch, and so a broken morph core cannot
|