rafters 0.0.82 → 0.0.84
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/index.js +901 -189
- package/dist/registry/types.d.ts +123 -1
- package/dist/registry/types.js +44 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28380,6 +28380,41 @@ var RegistryItemIntelligenceSchema = external_exports.object({
|
|
|
28380
28380
|
nevers: external_exports.array(external_exports.string()).default([])
|
|
28381
28381
|
}).optional()
|
|
28382
28382
|
});
|
|
28383
|
+
var ComponentTargetSchema = external_exports.enum(["react", "astro", "vue", "svelte", "wc"]);
|
|
28384
|
+
var PropFieldSchema = external_exports.discriminatedUnion("type", [
|
|
28385
|
+
external_exports.object({
|
|
28386
|
+
type: external_exports.literal("enum"),
|
|
28387
|
+
values: external_exports.array(external_exports.string()),
|
|
28388
|
+
// verbatim literal union members -- never "string"
|
|
28389
|
+
default: external_exports.string().optional(),
|
|
28390
|
+
required: external_exports.boolean().optional(),
|
|
28391
|
+
constraint: external_exports.object({
|
|
28392
|
+
when: external_exports.object({ prop: external_exports.string(), matches: external_exports.string() }),
|
|
28393
|
+
requires: external_exports.object({ prop: external_exports.string() })
|
|
28394
|
+
}).optional()
|
|
28395
|
+
}),
|
|
28396
|
+
external_exports.object({
|
|
28397
|
+
// Matches #2072's PropNode 'grammar' arm exactly, so graph.ts's
|
|
28398
|
+
// describe(<id>.props.<name>.vocab) drill has real shape data.
|
|
28399
|
+
type: external_exports.literal("grammar"),
|
|
28400
|
+
grammar: external_exports.array(external_exports.string()).min(1),
|
|
28401
|
+
// grammar shape tokens, e.g. ['word', 'word/alpha']
|
|
28402
|
+
vocab: external_exports.string(),
|
|
28403
|
+
// drillable addr, e.g. 'container.props.fill.vocab' -- never inlined
|
|
28404
|
+
onInvalid: external_exports.literal("silent-noop"),
|
|
28405
|
+
default: external_exports.string().optional()
|
|
28406
|
+
}),
|
|
28407
|
+
external_exports.object({
|
|
28408
|
+
type: external_exports.literal("deprecated"),
|
|
28409
|
+
deprecatedFor: external_exports.string()
|
|
28410
|
+
})
|
|
28411
|
+
]);
|
|
28412
|
+
var FacetSchema = external_exports.object({
|
|
28413
|
+
props: external_exports.record(external_exports.string(), PropFieldSchema),
|
|
28414
|
+
slots: external_exports.array(external_exports.string()).optional(),
|
|
28415
|
+
events: external_exports.array(external_exports.string()).optional(),
|
|
28416
|
+
snippet: external_exports.string()
|
|
28417
|
+
});
|
|
28383
28418
|
var RegistryItemSchema = external_exports.object({
|
|
28384
28419
|
name: external_exports.string(),
|
|
28385
28420
|
type: RegistryItemTypeSchema,
|
|
@@ -28388,7 +28423,12 @@ var RegistryItemSchema = external_exports.object({
|
|
|
28388
28423
|
files: external_exports.array(RegistryFileSchema),
|
|
28389
28424
|
rules: external_exports.array(external_exports.string()).default([]),
|
|
28390
28425
|
composites: external_exports.array(external_exports.string()).default([]),
|
|
28391
|
-
intelligence: RegistryItemIntelligenceSchema.optional()
|
|
28426
|
+
intelligence: RegistryItemIntelligenceSchema.optional(),
|
|
28427
|
+
// Per-target facets. zod v4's `z.record(enum, ...)` demands EVERY enum key be
|
|
28428
|
+
// present; a component built for only some targets must parse, so this is a
|
|
28429
|
+
// partial record (only the built targets appear).
|
|
28430
|
+
facets: external_exports.partialRecord(ComponentTargetSchema, FacetSchema).default({}),
|
|
28431
|
+
parent: external_exports.string().optional()
|
|
28392
28432
|
});
|
|
28393
28433
|
var RegistryIndexSchema = external_exports.object({
|
|
28394
28434
|
name: external_exports.string(),
|
|
@@ -28476,6 +28516,46 @@ var RegistryClient = class {
|
|
|
28476
28516
|
}
|
|
28477
28517
|
throw new Error(`"${name}" not found in registry (component, primitive, composite, or rule)`);
|
|
28478
28518
|
}
|
|
28519
|
+
/**
|
|
28520
|
+
* Fetch every registry item in one call, for the whole-catalog load the MCP
|
|
28521
|
+
* intel graph needs at startup.
|
|
28522
|
+
*
|
|
28523
|
+
* Prefers a single bulk round-trip against `/registry/items.json`. A registry
|
|
28524
|
+
* that does not serve that route -- an older deploy, or a third-party
|
|
28525
|
+
* `registryUrl` (which is user-configurable, see ConfigWiringSchema) -- returns
|
|
28526
|
+
* a non-2xx, and this falls back to looping `fetchItem` over `fetchIndex()`'s
|
|
28527
|
+
* name list, retaining today's per-item cost so no workspace's `describe` ever
|
|
28528
|
+
* hard-fails on a registry that predates the bulk endpoint.
|
|
28529
|
+
*/
|
|
28530
|
+
async fetchAllItems() {
|
|
28531
|
+
const response = await fetch(`${this.baseUrl}/registry/items.json`);
|
|
28532
|
+
if (response.ok) {
|
|
28533
|
+
return external_exports.array(RegistryItemSchema).parse(await response.json());
|
|
28534
|
+
}
|
|
28535
|
+
return this.fetchAllItemsByIndex();
|
|
28536
|
+
}
|
|
28537
|
+
/**
|
|
28538
|
+
* The fallback whole-catalog load: one `fetchItem` per name in the index,
|
|
28539
|
+
* across every describe-relevant kind. Deduped so a name appearing in more
|
|
28540
|
+
* than one index list is fetched once.
|
|
28541
|
+
*/
|
|
28542
|
+
async fetchAllItemsByIndex() {
|
|
28543
|
+
const index = await this.fetchIndex();
|
|
28544
|
+
const names = [
|
|
28545
|
+
...index.components,
|
|
28546
|
+
...index.primitives,
|
|
28547
|
+
...index.composites,
|
|
28548
|
+
...index.substrate
|
|
28549
|
+
];
|
|
28550
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28551
|
+
const items = [];
|
|
28552
|
+
for (const name of names) {
|
|
28553
|
+
if (seen.has(name)) continue;
|
|
28554
|
+
seen.add(name);
|
|
28555
|
+
items.push(await this.fetchItem(name));
|
|
28556
|
+
}
|
|
28557
|
+
return items;
|
|
28558
|
+
}
|
|
28479
28559
|
/**
|
|
28480
28560
|
* List all available components.
|
|
28481
28561
|
*/
|
|
@@ -30874,8 +30954,8 @@ function readVersion() {
|
|
|
30874
30954
|
var VERSION = readVersion();
|
|
30875
30955
|
|
|
30876
30956
|
// src/mcp/tools.ts
|
|
30877
|
-
import { readFile as readFile6 } from "fs/promises";
|
|
30878
|
-
import { join as join12 } from "path";
|
|
30957
|
+
import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
30958
|
+
import { isAbsolute as isAbsolute3, join as join12 } from "path";
|
|
30879
30959
|
|
|
30880
30960
|
// ../composites/src/built-in-rules/email.ts
|
|
30881
30961
|
var email3 = external_exports.string().email();
|
|
@@ -31025,9 +31105,6 @@ function register3(composite) {
|
|
|
31025
31105
|
}
|
|
31026
31106
|
composites.set(id, composite);
|
|
31027
31107
|
}
|
|
31028
|
-
function get2(id) {
|
|
31029
|
-
return composites.get(id);
|
|
31030
|
-
}
|
|
31031
31108
|
function getAll2() {
|
|
31032
31109
|
return Array.from(composites.values());
|
|
31033
31110
|
}
|
|
@@ -31238,7 +31315,427 @@ function resolveWorkspace(workspaces, defaultWorkspace, name) {
|
|
|
31238
31315
|
return defaultWorkspace;
|
|
31239
31316
|
}
|
|
31240
31317
|
|
|
31318
|
+
// src/mcp/graph.ts
|
|
31319
|
+
var EXPAND_OP = "*";
|
|
31320
|
+
var PROBE_OP = "?";
|
|
31321
|
+
function assembleGraph(items) {
|
|
31322
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
31323
|
+
for (const item of items) {
|
|
31324
|
+
const kind = kindOf(item.type);
|
|
31325
|
+
if (kind === null) continue;
|
|
31326
|
+
const src = item.intelligence;
|
|
31327
|
+
const intel = {
|
|
31328
|
+
dos: src?.usagePatterns?.dos ?? [],
|
|
31329
|
+
nevers: src?.usagePatterns?.nevers ?? []
|
|
31330
|
+
};
|
|
31331
|
+
if (src?.cognitiveLoad !== void 0) intel.cognitiveLoad = src.cognitiveLoad;
|
|
31332
|
+
if (src?.semanticMeaning !== void 0) intel.semanticMeaning = src.semanticMeaning;
|
|
31333
|
+
if (src?.accessibility !== void 0) intel.accessibility = src.accessibility;
|
|
31334
|
+
if (src?.attentionEconomics !== void 0) intel.attentionEconomics = src.attentionEconomics;
|
|
31335
|
+
if (src?.trustBuilding !== void 0) intel.trustBuilding = src.trustBuilding;
|
|
31336
|
+
const gn = {
|
|
31337
|
+
id: item.name,
|
|
31338
|
+
kind,
|
|
31339
|
+
intel,
|
|
31340
|
+
facets: item.facets ?? {},
|
|
31341
|
+
composesWith: item.composites,
|
|
31342
|
+
parts: []
|
|
31343
|
+
};
|
|
31344
|
+
if (item.parent !== void 0) gn.parent = item.parent;
|
|
31345
|
+
nodes.set(item.name, gn);
|
|
31346
|
+
}
|
|
31347
|
+
for (const node of nodes.values()) {
|
|
31348
|
+
if (node.parent === void 0) continue;
|
|
31349
|
+
if (node.parent === node.id) {
|
|
31350
|
+
throw new Error(`graph: node "${node.id}" parent references itself`);
|
|
31351
|
+
}
|
|
31352
|
+
if (!nodes.has(node.parent)) {
|
|
31353
|
+
throw new Error(`graph: node "${node.id}" parent names unknown id "${node.parent}"`);
|
|
31354
|
+
}
|
|
31355
|
+
}
|
|
31356
|
+
for (const node of nodes.values()) {
|
|
31357
|
+
if (node.parent === void 0) continue;
|
|
31358
|
+
const visited = /* @__PURE__ */ new Set();
|
|
31359
|
+
visited.add(node.id);
|
|
31360
|
+
let current = node.parent;
|
|
31361
|
+
while (current !== void 0) {
|
|
31362
|
+
if (visited.has(current)) {
|
|
31363
|
+
throw new Error(
|
|
31364
|
+
`graph: node "${node.id}" has a circular parent chain through "${current}"`
|
|
31365
|
+
);
|
|
31366
|
+
}
|
|
31367
|
+
visited.add(current);
|
|
31368
|
+
current = nodes.get(current)?.parent;
|
|
31369
|
+
}
|
|
31370
|
+
}
|
|
31371
|
+
for (const node of nodes.values()) {
|
|
31372
|
+
if (node.parent === void 0) continue;
|
|
31373
|
+
const parentNode = nodes.get(node.parent);
|
|
31374
|
+
if (parentNode) parentNode.parts.push(node.id);
|
|
31375
|
+
}
|
|
31376
|
+
for (const node of nodes.values()) {
|
|
31377
|
+
for (const target of node.composesWith) {
|
|
31378
|
+
if (!nodes.has(target)) {
|
|
31379
|
+
throw new Error(`graph: node "${node.id}" composesWith unknown id "${target}"`);
|
|
31380
|
+
}
|
|
31381
|
+
}
|
|
31382
|
+
}
|
|
31383
|
+
return { nodes };
|
|
31384
|
+
}
|
|
31385
|
+
function kindOf(type2) {
|
|
31386
|
+
if (type2 === "composite") return "composite";
|
|
31387
|
+
if (type2 === "ui" || type2 === "primitive") return "component";
|
|
31388
|
+
return null;
|
|
31389
|
+
}
|
|
31390
|
+
function describe(addr, graph, target) {
|
|
31391
|
+
const parts = addr === "" ? [] : addr.split(".");
|
|
31392
|
+
if (parts.length === 0) {
|
|
31393
|
+
return {
|
|
31394
|
+
kinds: [
|
|
31395
|
+
{ addr: "components", type: "edge" },
|
|
31396
|
+
{ addr: "composites", type: "edge" }
|
|
31397
|
+
],
|
|
31398
|
+
nodeCount: graph.nodes.size
|
|
31399
|
+
};
|
|
31400
|
+
}
|
|
31401
|
+
const [head, ...rest] = parts;
|
|
31402
|
+
if (head === void 0) return { error: `cannot resolve: ${addr}` };
|
|
31403
|
+
if (head === "components" || head === "composites") {
|
|
31404
|
+
if (rest.length > 0) return { error: `cannot expand: ${addr}` };
|
|
31405
|
+
const kind = head === "components" ? "component" : "composite";
|
|
31406
|
+
const roster = [];
|
|
31407
|
+
for (const node2 of graph.nodes.values()) {
|
|
31408
|
+
if (node2.kind === kind) roster.push({ id: node2.id });
|
|
31409
|
+
}
|
|
31410
|
+
return roster;
|
|
31411
|
+
}
|
|
31412
|
+
if (parts[parts.length - 1] === PROBE_OP) {
|
|
31413
|
+
const baseAddr = parts.slice(0, -1).join(".");
|
|
31414
|
+
const result = describe(baseAddr, graph, target);
|
|
31415
|
+
if (typeof result === "object" && result !== null && "error" in result) return null;
|
|
31416
|
+
return result;
|
|
31417
|
+
}
|
|
31418
|
+
if (head === EXPAND_OP || head === PROBE_OP) {
|
|
31419
|
+
return { error: `cannot expand at root: use 'components' or 'composites'` };
|
|
31420
|
+
}
|
|
31421
|
+
const node = graph.nodes.get(head);
|
|
31422
|
+
if (!node) return { error: `unknown node: ${head}` };
|
|
31423
|
+
if (rest.length === 0) return layer0(node, graph, target);
|
|
31424
|
+
if (rest[rest.length - 1] === EXPAND_OP) {
|
|
31425
|
+
const prefix = rest.slice(0, -1);
|
|
31426
|
+
if (prefix.length === 0) return expandNode(node, target);
|
|
31427
|
+
if (prefix.length === 1 && prefix[0] === "props") return expandProps(node, target);
|
|
31428
|
+
return { error: `cannot expand: ${addr}` };
|
|
31429
|
+
}
|
|
31430
|
+
if (rest.length === 1 && rest[0] === "composesWith") {
|
|
31431
|
+
return resolveEdges(node, graph, target);
|
|
31432
|
+
}
|
|
31433
|
+
if (rest[0] === "props") {
|
|
31434
|
+
const facet = target === void 0 ? void 0 : node.facets[target];
|
|
31435
|
+
const propName = rest[1];
|
|
31436
|
+
const prop = facet && propName !== void 0 ? facet.props[propName] : void 0;
|
|
31437
|
+
if (!prop) return { error: `cannot resolve: ${addr}` };
|
|
31438
|
+
if (rest.length === 2) return toAgentProp(prop);
|
|
31439
|
+
return { error: `cannot resolve: ${addr}` };
|
|
31440
|
+
}
|
|
31441
|
+
return { error: `cannot resolve: ${addr}` };
|
|
31442
|
+
}
|
|
31443
|
+
function toAgentProp(prop) {
|
|
31444
|
+
if (prop.type === "grammar") {
|
|
31445
|
+
const { vocab: _vocab, ...agentView } = prop;
|
|
31446
|
+
return agentView;
|
|
31447
|
+
}
|
|
31448
|
+
return prop;
|
|
31449
|
+
}
|
|
31450
|
+
function layer0(node, graph, target) {
|
|
31451
|
+
const facet = target === void 0 ? void 0 : node.facets[target];
|
|
31452
|
+
const children = [];
|
|
31453
|
+
if (facet) {
|
|
31454
|
+
for (const [name, prop] of Object.entries(facet.props)) {
|
|
31455
|
+
const child = { addr: `${node.id}.props.${name}`, type: prop.type };
|
|
31456
|
+
if (prop.type === "deprecated") child.deprecatedFor = prop.deprecatedFor;
|
|
31457
|
+
children.push(child);
|
|
31458
|
+
}
|
|
31459
|
+
}
|
|
31460
|
+
if (node.composesWith.length > 0) {
|
|
31461
|
+
children.push({ addr: `${node.id}.composesWith`, type: "edge" });
|
|
31462
|
+
}
|
|
31463
|
+
for (const partId of node.parts) {
|
|
31464
|
+
children.push({ addr: partId, type: "part" });
|
|
31465
|
+
}
|
|
31466
|
+
const result = { id: node.id, kind: node.kind, intel: node.intel, children };
|
|
31467
|
+
if (facet) {
|
|
31468
|
+
result.snippet = facet.snippet;
|
|
31469
|
+
if (facet.slots !== void 0) result.slots = facet.slots;
|
|
31470
|
+
if (facet.events !== void 0) result.events = facet.events;
|
|
31471
|
+
}
|
|
31472
|
+
if (node.parent !== void 0) {
|
|
31473
|
+
result.parent = node.parent;
|
|
31474
|
+
const parentNode = graph.nodes.get(node.parent);
|
|
31475
|
+
if (parentNode) {
|
|
31476
|
+
const siblings = parentNode.parts.filter((id) => id !== node.id);
|
|
31477
|
+
if (siblings.length > 0) result.siblings = siblings;
|
|
31478
|
+
}
|
|
31479
|
+
}
|
|
31480
|
+
return result;
|
|
31481
|
+
}
|
|
31482
|
+
function resolveEdges(node, graph, target) {
|
|
31483
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31484
|
+
const results = [];
|
|
31485
|
+
for (const edgeId of node.composesWith) {
|
|
31486
|
+
if (seen.has(edgeId)) continue;
|
|
31487
|
+
seen.add(edgeId);
|
|
31488
|
+
const edgeNode = graph.nodes.get(edgeId);
|
|
31489
|
+
if (edgeNode) results.push(layer0(edgeNode, graph, target));
|
|
31490
|
+
}
|
|
31491
|
+
return results;
|
|
31492
|
+
}
|
|
31493
|
+
function expandProps(node, target) {
|
|
31494
|
+
const facet = target === void 0 ? void 0 : node.facets[target];
|
|
31495
|
+
const props = {};
|
|
31496
|
+
if (facet) {
|
|
31497
|
+
for (const [name, prop] of Object.entries(facet.props)) {
|
|
31498
|
+
props[name] = toAgentProp(prop);
|
|
31499
|
+
}
|
|
31500
|
+
}
|
|
31501
|
+
return { expanded: true, props };
|
|
31502
|
+
}
|
|
31503
|
+
function expandNode(node, target) {
|
|
31504
|
+
const facet = target === void 0 ? void 0 : node.facets[target];
|
|
31505
|
+
const props = {};
|
|
31506
|
+
if (facet) {
|
|
31507
|
+
for (const [name, prop] of Object.entries(facet.props)) {
|
|
31508
|
+
props[name] = toAgentProp(prop);
|
|
31509
|
+
}
|
|
31510
|
+
}
|
|
31511
|
+
const result = {
|
|
31512
|
+
id: node.id,
|
|
31513
|
+
kind: node.kind,
|
|
31514
|
+
intel: node.intel,
|
|
31515
|
+
props
|
|
31516
|
+
};
|
|
31517
|
+
if (node.composesWith.length > 0) result.composesWith = node.composesWith;
|
|
31518
|
+
if (node.parts.length > 0) result.parts = node.parts;
|
|
31519
|
+
if (facet) {
|
|
31520
|
+
result.snippet = facet.snippet;
|
|
31521
|
+
if (facet.slots !== void 0) result.slots = facet.slots;
|
|
31522
|
+
if (facet.events !== void 0) result.events = facet.events;
|
|
31523
|
+
}
|
|
31524
|
+
return result;
|
|
31525
|
+
}
|
|
31526
|
+
|
|
31527
|
+
// src/mcp/intent.ts
|
|
31528
|
+
var INTENT_TAGS = {
|
|
31529
|
+
modal: ["attention", "blocking", "above-all", "focus-trap"],
|
|
31530
|
+
alert: ["attention", "inline", "passive"],
|
|
31531
|
+
tooltip: ["hint", "passive", "inline", "hover"]
|
|
31532
|
+
};
|
|
31533
|
+
var INTENT_AXES = [
|
|
31534
|
+
{
|
|
31535
|
+
tag: "above-all",
|
|
31536
|
+
keywords: [
|
|
31537
|
+
"above everything",
|
|
31538
|
+
"above all",
|
|
31539
|
+
"on top of everything",
|
|
31540
|
+
"sits on top",
|
|
31541
|
+
"on top",
|
|
31542
|
+
"over everything",
|
|
31543
|
+
"over all",
|
|
31544
|
+
"topmost",
|
|
31545
|
+
"highest layer"
|
|
31546
|
+
]
|
|
31547
|
+
},
|
|
31548
|
+
{
|
|
31549
|
+
tag: "blocking",
|
|
31550
|
+
keywords: [
|
|
31551
|
+
"above everything",
|
|
31552
|
+
"on top of everything",
|
|
31553
|
+
"over everything",
|
|
31554
|
+
"block",
|
|
31555
|
+
"blocking",
|
|
31556
|
+
"must dismiss",
|
|
31557
|
+
"interrupt",
|
|
31558
|
+
"take over",
|
|
31559
|
+
"stops everything",
|
|
31560
|
+
"requires a response",
|
|
31561
|
+
"can't ignore",
|
|
31562
|
+
"cannot ignore"
|
|
31563
|
+
]
|
|
31564
|
+
},
|
|
31565
|
+
{
|
|
31566
|
+
tag: "inline",
|
|
31567
|
+
keywords: ["inline", "in place", "in the flow", "within the page", "non-blocking"]
|
|
31568
|
+
},
|
|
31569
|
+
{
|
|
31570
|
+
tag: "passive",
|
|
31571
|
+
keywords: ["passive", "non-intrusive", "subtle", "quietly", "in the background"]
|
|
31572
|
+
},
|
|
31573
|
+
{
|
|
31574
|
+
tag: "attention",
|
|
31575
|
+
keywords: ["attention", "notice", "get noticed", "important message"]
|
|
31576
|
+
},
|
|
31577
|
+
{
|
|
31578
|
+
tag: "hint",
|
|
31579
|
+
keywords: ["hint", "a tip", "explain", "extra info", "more info"]
|
|
31580
|
+
},
|
|
31581
|
+
{
|
|
31582
|
+
tag: "hover",
|
|
31583
|
+
keywords: ["hover", "on hover", "mouse over"]
|
|
31584
|
+
}
|
|
31585
|
+
];
|
|
31586
|
+
function isNaturalLanguageQuery(input) {
|
|
31587
|
+
return input.includes(" ");
|
|
31588
|
+
}
|
|
31589
|
+
function matchIntent(query, graph) {
|
|
31590
|
+
const normalized = query.toLowerCase();
|
|
31591
|
+
const matchedTags = collectMatchedTags(normalized);
|
|
31592
|
+
if (matchedTags.size === 0) return noMatch();
|
|
31593
|
+
const winner = selectUse(matchedTags, graph);
|
|
31594
|
+
if (winner === void 0) return noMatch();
|
|
31595
|
+
const winningTags = winner.tags.filter((tag) => matchedTags.has(tag));
|
|
31596
|
+
const decisive = new Set(winningTags);
|
|
31597
|
+
const nearMiss = selectNearMiss(winner, decisive, matchedTags, graph);
|
|
31598
|
+
if (nearMiss === void 0) return noMatch();
|
|
31599
|
+
const use = describeNode(winner.id, graph);
|
|
31600
|
+
const not = describeNode(nearMiss.id, graph);
|
|
31601
|
+
if (use === void 0 || not === void 0) return noMatch();
|
|
31602
|
+
const divergingTags = nearMiss.tags.filter((tag) => !winner.tags.includes(tag));
|
|
31603
|
+
const because = `${winner.id} is ${winningTags.join("/")}; ${nearMiss.id} is ${divergingTags.join("/")}`;
|
|
31604
|
+
return { use, not, because };
|
|
31605
|
+
}
|
|
31606
|
+
function collectMatchedTags(normalizedQuery) {
|
|
31607
|
+
const matched = /* @__PURE__ */ new Set();
|
|
31608
|
+
for (const axis of INTENT_AXES) {
|
|
31609
|
+
for (const keyword of axis.keywords) {
|
|
31610
|
+
if (normalizedQuery.includes(keyword)) {
|
|
31611
|
+
matched.add(axis.tag);
|
|
31612
|
+
break;
|
|
31613
|
+
}
|
|
31614
|
+
}
|
|
31615
|
+
}
|
|
31616
|
+
return matched;
|
|
31617
|
+
}
|
|
31618
|
+
function selectUse(matchedTags, graph) {
|
|
31619
|
+
let best;
|
|
31620
|
+
let bestScore = 0;
|
|
31621
|
+
for (const [id, tags] of Object.entries(INTENT_TAGS)) {
|
|
31622
|
+
if (!graph.nodes.has(id)) continue;
|
|
31623
|
+
const score = tags.reduce((sum, tag) => matchedTags.has(tag) ? sum + 1 : sum, 0);
|
|
31624
|
+
if (score > bestScore) {
|
|
31625
|
+
bestScore = score;
|
|
31626
|
+
best = { id, tags };
|
|
31627
|
+
}
|
|
31628
|
+
}
|
|
31629
|
+
return best;
|
|
31630
|
+
}
|
|
31631
|
+
function selectNearMiss(use, decisive, matchedTags, graph) {
|
|
31632
|
+
let best;
|
|
31633
|
+
let bestOverlap = 0;
|
|
31634
|
+
for (const [id, tags] of Object.entries(INTENT_TAGS)) {
|
|
31635
|
+
if (id === use.id || !graph.nodes.has(id)) continue;
|
|
31636
|
+
if (tags.some((tag) => decisive.has(tag))) continue;
|
|
31637
|
+
const overlap = tags.filter((tag) => use.tags.includes(tag));
|
|
31638
|
+
const sharesOutsideMatched = overlap.some((tag) => !matchedTags.has(tag));
|
|
31639
|
+
if (!sharesOutsideMatched) continue;
|
|
31640
|
+
if (overlap.length > bestOverlap) {
|
|
31641
|
+
bestOverlap = overlap.length;
|
|
31642
|
+
best = { id, tags };
|
|
31643
|
+
}
|
|
31644
|
+
}
|
|
31645
|
+
return best;
|
|
31646
|
+
}
|
|
31647
|
+
function describeNode(id, graph) {
|
|
31648
|
+
const result = describe(id, graph);
|
|
31649
|
+
if (result !== null && !Array.isArray(result) && "children" in result) return result;
|
|
31650
|
+
return void 0;
|
|
31651
|
+
}
|
|
31652
|
+
function noMatch() {
|
|
31653
|
+
return { note: "no route matched; describe(components) or describe(composites) to browse" };
|
|
31654
|
+
}
|
|
31655
|
+
|
|
31656
|
+
// src/mcp/overlay.ts
|
|
31657
|
+
function buildInstalledSet(config2) {
|
|
31658
|
+
return {
|
|
31659
|
+
components: new Set(config2.installed?.components ?? []),
|
|
31660
|
+
composites: new Set(config2.installed?.composites ?? [])
|
|
31661
|
+
};
|
|
31662
|
+
}
|
|
31663
|
+
function describeWithOverlay(addr, graph, ctx) {
|
|
31664
|
+
const result = describe(addr, graph, ctx.target);
|
|
31665
|
+
if (addr === "components" || addr === "composites") {
|
|
31666
|
+
const set3 = addr === "components" ? ctx.installed.components : ctx.installed.composites;
|
|
31667
|
+
const roster = result;
|
|
31668
|
+
return roster.map((entry) => ({ id: entry.id, presence: presenceOf(set3, entry.id) }));
|
|
31669
|
+
}
|
|
31670
|
+
if (addr !== "" && !addr.includes(".") && isNodeResult(result)) {
|
|
31671
|
+
return { ...result, ...stampOf(result.id, result.kind, graph, ctx) };
|
|
31672
|
+
}
|
|
31673
|
+
if (isExpandedNodeResult(result)) {
|
|
31674
|
+
return { ...result, ...stampOf(result.id, result.kind, graph, ctx) };
|
|
31675
|
+
}
|
|
31676
|
+
return result;
|
|
31677
|
+
}
|
|
31678
|
+
function presenceOf(set3, id) {
|
|
31679
|
+
return set3.has(id) ? "installed" : "available";
|
|
31680
|
+
}
|
|
31681
|
+
function stampOf(id, kind, graph, ctx) {
|
|
31682
|
+
const set3 = kind === "component" ? ctx.installed.components : ctx.installed.composites;
|
|
31683
|
+
return {
|
|
31684
|
+
presence: presenceOf(set3, id),
|
|
31685
|
+
target: ctx.target,
|
|
31686
|
+
// rendersForTarget reads the node's own facets (on the universal graph):
|
|
31687
|
+
// does a facet for the resolved target exist?
|
|
31688
|
+
rendersForTarget: ctx.target !== void 0 && graph.nodes.get(id)?.facets[ctx.target] !== void 0
|
|
31689
|
+
};
|
|
31690
|
+
}
|
|
31691
|
+
function isNodeResult(result) {
|
|
31692
|
+
return typeof result === "object" && result !== null && !Array.isArray(result) && "id" in result && "kind" in result && "children" in result;
|
|
31693
|
+
}
|
|
31694
|
+
function isExpandedNodeResult(result) {
|
|
31695
|
+
return typeof result === "object" && result !== null && !Array.isArray(result) && "id" in result && "kind" in result && "props" in result && !("children" in result);
|
|
31696
|
+
}
|
|
31697
|
+
|
|
31241
31698
|
// src/mcp/tools.ts
|
|
31699
|
+
function isSafeRelPath(p2) {
|
|
31700
|
+
if (isAbsolute3(p2)) return false;
|
|
31701
|
+
return !p2.split(/[/\\]/).includes("..");
|
|
31702
|
+
}
|
|
31703
|
+
var SafePathFieldSchema = PathFieldSchema.refine(
|
|
31704
|
+
(field) => {
|
|
31705
|
+
const entries = typeof field === "string" ? [field] : field;
|
|
31706
|
+
return entries.every((e) => isSafeRelPath(typeof e === "string" ? e : e.path));
|
|
31707
|
+
},
|
|
31708
|
+
{ message: "path must be relative and stay inside the workspace (no absolute or `..` paths)" }
|
|
31709
|
+
);
|
|
31710
|
+
var ConfigWiringSchema = external_exports.object({
|
|
31711
|
+
// Closed set -- excludes 'unknown', which is a detection sentinel, not a
|
|
31712
|
+
// valid target a caller may set.
|
|
31713
|
+
framework: external_exports.enum(["next", "vite", "remix", "react-router", "astro", "wc", "vanilla"]),
|
|
31714
|
+
// Must be a valid http(s) URL. (Blocking private/link-local/metadata hosts
|
|
31715
|
+
// is tracked as a follow-up -- see the registryUrl SSRF issue.)
|
|
31716
|
+
registryUrl: external_exports.string().url().refine((u) => /^https?:$/.test(new URL(u).protocol), {
|
|
31717
|
+
message: "registryUrl must be an http(s) URL"
|
|
31718
|
+
}),
|
|
31719
|
+
// Closed set derived from the framework (see ComponentTarget in detect.ts).
|
|
31720
|
+
componentTarget: external_exports.enum(["react", "astro", "vue", "svelte", "wc"]),
|
|
31721
|
+
source: external_exports.string().min(1),
|
|
31722
|
+
cssPath: external_exports.union([external_exports.string(), external_exports.null()]).refine((v) => v === null || isSafeRelPath(v), {
|
|
31723
|
+
message: "cssPath must be relative and stay inside the workspace"
|
|
31724
|
+
}),
|
|
31725
|
+
componentsPath: SafePathFieldSchema,
|
|
31726
|
+
primitivesPath: SafePathFieldSchema,
|
|
31727
|
+
compositesPath: SafePathFieldSchema,
|
|
31728
|
+
rulesPath: SafePathFieldSchema,
|
|
31729
|
+
exports: external_exports.object({
|
|
31730
|
+
tailwind: external_exports.boolean(),
|
|
31731
|
+
typescript: external_exports.boolean(),
|
|
31732
|
+
dtcg: external_exports.boolean(),
|
|
31733
|
+
compiled: external_exports.boolean(),
|
|
31734
|
+
documentation: external_exports.boolean()
|
|
31735
|
+
}).partial()
|
|
31736
|
+
}).partial().strict();
|
|
31737
|
+
var STUDIO_OWNED_KEYS = ["intent", "darkMode", "fonts"];
|
|
31738
|
+
var ADD_MANAGED_KEYS = ["installed"];
|
|
31242
31739
|
var WORKSPACE_PARAM = {
|
|
31243
31740
|
workspace: {
|
|
31244
31741
|
type: "string",
|
|
@@ -31248,16 +31745,76 @@ var WORKSPACE_PARAM = {
|
|
|
31248
31745
|
var TOOL_DEFINITIONS = [
|
|
31249
31746
|
{
|
|
31250
31747
|
name: "rafters_workspaces",
|
|
31251
|
-
description: "List rafters workspaces
|
|
31748
|
+
description: "List rafters workspaces, or update a workspace's WIRING config. Called with no arguments (or just `workspace`): returns each workspace name, path, and which is the default for unscoped tool calls -- call this first when the project might be a monorepo. Called with any wiring field: updates that workspace's .rafters/config.rafters.json -- framework, registryUrl, componentTarget, source, cssPath, and the path fields (componentsPath, primitivesPath, compositesPath, rulesPath) and exports; only fields you pass change. Cannot set designer decisions (intent, darkMode, fonts) -- those are set in Studio -- and cannot set installed -- that is managed by `rafters add`.",
|
|
31252
31749
|
inputSchema: {
|
|
31253
31750
|
type: "object",
|
|
31254
|
-
properties: {
|
|
31751
|
+
properties: {
|
|
31752
|
+
...WORKSPACE_PARAM,
|
|
31753
|
+
framework: { type: "string", description: 'Target framework (e.g., "react", "astro")' },
|
|
31754
|
+
registryUrl: {
|
|
31755
|
+
type: "string",
|
|
31756
|
+
description: "Registry to install from / query. Point at your own internal registry."
|
|
31757
|
+
},
|
|
31758
|
+
componentTarget: { type: "string", description: "Where installed components are written" },
|
|
31759
|
+
source: { type: "string", description: "Design system this project was imported from" },
|
|
31760
|
+
cssPath: { type: ["string", "null"], description: "Path to the main CSS file, or null" },
|
|
31761
|
+
componentsPath: {
|
|
31762
|
+
oneOf: [{ type: "string" }, { type: "array" }],
|
|
31763
|
+
description: "Folder(s) to read/write components. String or array of entries."
|
|
31764
|
+
},
|
|
31765
|
+
primitivesPath: {
|
|
31766
|
+
oneOf: [{ type: "string" }, { type: "array" }],
|
|
31767
|
+
description: "Folder(s) to read/write primitives. String or array of entries."
|
|
31768
|
+
},
|
|
31769
|
+
compositesPath: {
|
|
31770
|
+
oneOf: [{ type: "string" }, { type: "array" }],
|
|
31771
|
+
description: "Folder(s) to read/write composites. String or array of entries."
|
|
31772
|
+
},
|
|
31773
|
+
rulesPath: {
|
|
31774
|
+
oneOf: [{ type: "string" }, { type: "array" }],
|
|
31775
|
+
description: "Folder(s) to read/write rules. String or array of entries."
|
|
31776
|
+
},
|
|
31777
|
+
exports: {
|
|
31778
|
+
type: "object",
|
|
31779
|
+
description: "Which output formats to emit (tailwind, typescript, dtcg, compiled, documentation)."
|
|
31780
|
+
}
|
|
31781
|
+
},
|
|
31255
31782
|
required: []
|
|
31256
31783
|
}
|
|
31257
31784
|
},
|
|
31785
|
+
{
|
|
31786
|
+
name: "rafters_describe",
|
|
31787
|
+
description: 'Recursively introspect the component/composite intel graph. describe() returns the installed surface; describe(components)/describe(composites) list the kind roster; describe(button) returns a node -- intel plus type-marked, drillable children; describe(button.props.fill) drills into a prop; describe(button.props.fill.vocab) returns the real token values. describe(button.*) expands all props inline in one call (no more drill-per-prop round trips); describe(button.props.fill.?) probes safely (null on miss, not an error). A natural-language question (e.g. "what do I use when it needs to be above everything") routes to the best-matching node plus a near-miss counter-example instead of an address.',
|
|
31788
|
+
inputSchema: {
|
|
31789
|
+
type: "object",
|
|
31790
|
+
properties: {
|
|
31791
|
+
...WORKSPACE_PARAM,
|
|
31792
|
+
address: {
|
|
31793
|
+
type: "string",
|
|
31794
|
+
description: 'A dot-address ("button.props.variant") or a natural-language question.'
|
|
31795
|
+
}
|
|
31796
|
+
},
|
|
31797
|
+
required: ["address"]
|
|
31798
|
+
}
|
|
31799
|
+
},
|
|
31800
|
+
{
|
|
31801
|
+
name: "rafters_generate",
|
|
31802
|
+
description: "STUB (Issue E). Will produce a rafters-correct composition with visible placeholders for app-specific fields/actions/copy. Currently returns a structured not-implemented result.",
|
|
31803
|
+
inputSchema: {
|
|
31804
|
+
type: "object",
|
|
31805
|
+
properties: {
|
|
31806
|
+
...WORKSPACE_PARAM,
|
|
31807
|
+
intent: { type: "string", description: "What to generate" }
|
|
31808
|
+
},
|
|
31809
|
+
required: ["intent"]
|
|
31810
|
+
}
|
|
31811
|
+
},
|
|
31812
|
+
// Deprecated aliases -- kept for one minor release, then removed (tracked as a
|
|
31813
|
+
// follow-up). Input schemas unchanged; every response carries a `deprecated`
|
|
31814
|
+
// field pointing at rafters_describe.
|
|
31258
31815
|
{
|
|
31259
31816
|
name: "rafters_composite",
|
|
31260
|
-
description: "Query composites by ID, search term, or category. Returns designer intent (solves, appliesWhen, do/never), I/O rules for chaining, and block structure.",
|
|
31817
|
+
description: "[DEPRECATED -- use rafters_describe] Query composites by ID, search term, or category. Returns designer intent (solves, appliesWhen, do/never), I/O rules for chaining, and block structure.",
|
|
31261
31818
|
inputSchema: {
|
|
31262
31819
|
type: "object",
|
|
31263
31820
|
properties: {
|
|
@@ -31271,7 +31828,7 @@ var TOOL_DEFINITIONS = [
|
|
|
31271
31828
|
},
|
|
31272
31829
|
{
|
|
31273
31830
|
name: "rafters_pattern",
|
|
31274
|
-
description: 'Get design pattern guidance by querying composites. Search by what the pattern solves (e.g., "authentication", "data entry", "navigation") to get do/never rules, cognitive load, and designer intent.',
|
|
31831
|
+
description: '[DEPRECATED -- use rafters_describe] Get design pattern guidance by querying composites. Search by what the pattern solves (e.g., "authentication", "data entry", "navigation") to get do/never rules, cognitive load, and designer intent.',
|
|
31275
31832
|
inputSchema: {
|
|
31276
31833
|
type: "object",
|
|
31277
31834
|
properties: {
|
|
@@ -31290,7 +31847,7 @@ var TOOL_DEFINITIONS = [
|
|
|
31290
31847
|
},
|
|
31291
31848
|
{
|
|
31292
31849
|
name: "rafters_component",
|
|
31293
|
-
description: "Get component intelligence: cognitive load, accessibility, do/never guidance, variants, sizes.",
|
|
31850
|
+
description: "[DEPRECATED -- use rafters_describe] Get component intelligence: cognitive load, accessibility, do/never guidance, variants, sizes.",
|
|
31294
31851
|
inputSchema: {
|
|
31295
31852
|
type: "object",
|
|
31296
31853
|
properties: {
|
|
@@ -31304,6 +31861,7 @@ var TOOL_DEFINITIONS = [
|
|
|
31304
31861
|
}
|
|
31305
31862
|
}
|
|
31306
31863
|
];
|
|
31864
|
+
var DEPRECATED_MSG = "use rafters_describe instead";
|
|
31307
31865
|
var RaftersToolHandler = class {
|
|
31308
31866
|
workspaces;
|
|
31309
31867
|
defaultWorkspace;
|
|
@@ -31311,32 +31869,52 @@ var RaftersToolHandler = class {
|
|
|
31311
31869
|
compositesLoadedFor = /* @__PURE__ */ new Set();
|
|
31312
31870
|
/** Tracks built-in composite loading separately (loaded once globally). */
|
|
31313
31871
|
builtInCompositesLoaded = false;
|
|
31314
|
-
|
|
31872
|
+
/**
|
|
31873
|
+
* Registry clients keyed by base URL, so each workspace's `registryUrl` gets
|
|
31874
|
+
* its own fetch cache. Workspaces without a configured URL share the default
|
|
31875
|
+
* singleton (which points at the public registry).
|
|
31876
|
+
*/
|
|
31877
|
+
registryClients = /* @__PURE__ */ new Map();
|
|
31878
|
+
/**
|
|
31879
|
+
* Per-workspace-root cache: the intel graph, built once, lazily, on the first
|
|
31880
|
+
* describe/generate call that touches that root. The graph is universal and
|
|
31881
|
+
* target-free (a node carries all its per-target facets); the workspace's
|
|
31882
|
+
* target is applied per query by the overlay, not baked into the cache. Same
|
|
31883
|
+
* per-workspace caching shape as `compositesLoadedFor`/`registryClients`. A
|
|
31884
|
+
* failed build is never inserted, so the next call retries rather than
|
|
31885
|
+
* permanently wedging that workspace.
|
|
31886
|
+
*/
|
|
31887
|
+
graphsByWorkspace = /* @__PURE__ */ new Map();
|
|
31888
|
+
/**
|
|
31889
|
+
* The whole-catalog item source `ensureGraph` builds from. Defaults to the
|
|
31890
|
+
* workspace's registry client `fetchAllItems()` (bulk endpoint with per-item
|
|
31891
|
+
* fallback). Injectable so the dispatch can be unit-tested against a fixture
|
|
31892
|
+
* catalog without a network round-trip.
|
|
31893
|
+
*/
|
|
31894
|
+
itemsSource;
|
|
31895
|
+
constructor(workspaces, defaultWorkspace, itemsSource) {
|
|
31315
31896
|
this.workspaces = workspaces;
|
|
31316
31897
|
this.defaultWorkspace = defaultWorkspace;
|
|
31898
|
+
this.itemsSource = itemsSource ?? (async (ws) => (await this.registryClientFor(ws)).fetchAllItems());
|
|
31317
31899
|
}
|
|
31318
31900
|
async handleToolCall(name, args) {
|
|
31319
31901
|
switch (name) {
|
|
31320
31902
|
case "rafters_workspaces":
|
|
31321
|
-
return this.handleWorkspaces();
|
|
31903
|
+
return this.handleWorkspaces(args);
|
|
31904
|
+
case "rafters_describe":
|
|
31905
|
+
return this.handleDescribe(args.address, args.workspace);
|
|
31906
|
+
case "rafters_generate":
|
|
31907
|
+
return this.handleGenerate(args.intent, args.workspace);
|
|
31322
31908
|
case "rafters_composite":
|
|
31323
31909
|
return this.handleComposite(args);
|
|
31324
31910
|
case "rafters_pattern":
|
|
31325
31911
|
return this.handlePattern(args);
|
|
31326
31912
|
case "rafters_component":
|
|
31327
|
-
return this.handleComponent(args.name);
|
|
31913
|
+
return this.handleComponent(args.name, args.workspace);
|
|
31328
31914
|
default:
|
|
31329
|
-
return {
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
type: "text",
|
|
31333
|
-
text: JSON.stringify({
|
|
31334
|
-
error: `Unknown tool: ${name}`,
|
|
31335
|
-
suggestion: "Available tools: rafters_workspaces, rafters_composite, rafters_pattern, rafters_component."
|
|
31336
|
-
})
|
|
31337
|
-
}
|
|
31338
|
-
]
|
|
31339
|
-
};
|
|
31915
|
+
return this.errorResult(`Unknown tool: ${name}`, {
|
|
31916
|
+
suggestion: "Available tools: rafters_workspaces, rafters_describe, rafters_generate. Deprecated: rafters_composite, rafters_pattern, rafters_component."
|
|
31917
|
+
});
|
|
31340
31918
|
}
|
|
31341
31919
|
}
|
|
31342
31920
|
/**
|
|
@@ -31353,42 +31931,84 @@ var RaftersToolHandler = class {
|
|
|
31353
31931
|
* Use this when a tool requires a workspace and the agent didn't pick one.
|
|
31354
31932
|
*/
|
|
31355
31933
|
workspaceRequiredError() {
|
|
31356
|
-
return {
|
|
31357
|
-
|
|
31358
|
-
|
|
31359
|
-
|
|
31360
|
-
text: JSON.stringify({
|
|
31361
|
-
error: "workspace parameter required",
|
|
31362
|
-
suggestion: "Multiple workspaces are available. Pass `workspace` with one of the names below.",
|
|
31363
|
-
workspaces: this.workspaces.map((w) => ({
|
|
31364
|
-
name: w.name,
|
|
31365
|
-
root: w.root
|
|
31366
|
-
}))
|
|
31367
|
-
})
|
|
31368
|
-
}
|
|
31369
|
-
]
|
|
31370
|
-
};
|
|
31934
|
+
return this.errorResult("workspace parameter required", {
|
|
31935
|
+
suggestion: "Multiple workspaces are available. Pass `workspace` with one of the names below.",
|
|
31936
|
+
workspaces: this.workspaces.map((w) => ({ name: w.name, root: w.root }))
|
|
31937
|
+
});
|
|
31371
31938
|
}
|
|
31372
|
-
|
|
31373
|
-
|
|
31374
|
-
|
|
31375
|
-
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
|
|
31385
|
-
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
|
|
31390
|
-
|
|
31391
|
-
|
|
31939
|
+
/** Wrap a JSON-serialisable payload as a text tool result. */
|
|
31940
|
+
jsonResult(payload) {
|
|
31941
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
31942
|
+
}
|
|
31943
|
+
/** Wrap an error string (with optional extra fields) as a tool result. */
|
|
31944
|
+
errorResult(error47, extra) {
|
|
31945
|
+
return this.jsonResult({ error: error47, ...extra });
|
|
31946
|
+
}
|
|
31947
|
+
/**
|
|
31948
|
+
* List workspaces, or -- when any wiring field is present -- update the
|
|
31949
|
+
* target workspace's config. `workspace` selects which; every other key is
|
|
31950
|
+
* treated as a wiring patch. No wiring keys means the list query.
|
|
31951
|
+
*/
|
|
31952
|
+
async handleWorkspaces(args) {
|
|
31953
|
+
const { workspace, ...patch } = args;
|
|
31954
|
+
if (Object.keys(patch).length === 0) {
|
|
31955
|
+
return this.jsonResult({
|
|
31956
|
+
workspaces: this.workspaces.map((w) => ({
|
|
31957
|
+
name: w.name,
|
|
31958
|
+
root: w.root,
|
|
31959
|
+
isDefault: w.name === this.defaultWorkspace?.name
|
|
31960
|
+
})),
|
|
31961
|
+
defaultWorkspace: this.defaultWorkspace?.name ?? null
|
|
31962
|
+
});
|
|
31963
|
+
}
|
|
31964
|
+
return this.updateWorkspaceConfig(workspace, patch);
|
|
31965
|
+
}
|
|
31966
|
+
/**
|
|
31967
|
+
* Write a WIRING patch to a workspace's config.rafters.json. Rejects the
|
|
31968
|
+
* designer-owned keys (intent, darkMode, fonts -> Studio) and add-managed
|
|
31969
|
+
* keys (installed -> `rafters add`) with a pointer to the right surface, so
|
|
31970
|
+
* this path structurally cannot remove designer choice. Only the fields in
|
|
31971
|
+
* the patch change; everything else in the config is preserved.
|
|
31972
|
+
*/
|
|
31973
|
+
async updateWorkspaceConfig(workspaceName, patch) {
|
|
31974
|
+
const resolved = this.resolve(workspaceName);
|
|
31975
|
+
if (!resolved) {
|
|
31976
|
+
return this.workspaceRequiredError();
|
|
31977
|
+
}
|
|
31978
|
+
const studioKeys = STUDIO_OWNED_KEYS.filter((k) => k in patch);
|
|
31979
|
+
if (studioKeys.length > 0) {
|
|
31980
|
+
return this.errorResult(
|
|
31981
|
+
`${studioKeys.join(", ")} ${studioKeys.length > 1 ? "are" : "is"} a designer decision owned by Rafters Studio, not the MCP. Set ${studioKeys.length > 1 ? "them" : "it"} in Studio.`
|
|
31982
|
+
);
|
|
31983
|
+
}
|
|
31984
|
+
const addKeys = ADD_MANAGED_KEYS.filter((k) => k in patch);
|
|
31985
|
+
if (addKeys.length > 0) {
|
|
31986
|
+
return this.errorResult(`${addKeys.join(", ")} is managed by \`rafters add\`, not the MCP.`);
|
|
31987
|
+
}
|
|
31988
|
+
const result = ConfigWiringSchema.safeParse(patch);
|
|
31989
|
+
if (!result.success) {
|
|
31990
|
+
return this.errorResult(`invalid wiring patch: ${result.error.message}`);
|
|
31991
|
+
}
|
|
31992
|
+
if (Object.keys(result.data).length === 0) {
|
|
31993
|
+
return this.errorResult("no writable wiring fields provided");
|
|
31994
|
+
}
|
|
31995
|
+
const config2 = await this.readConfig(resolved.root);
|
|
31996
|
+
if (!config2) {
|
|
31997
|
+
return this.errorResult(
|
|
31998
|
+
`no config at ${getRaftersPaths(resolved.root).config} -- run \`rafters init\` first`
|
|
31999
|
+
);
|
|
32000
|
+
}
|
|
32001
|
+
const updated = { ...config2, ...result.data };
|
|
32002
|
+
if (result.data.exports !== void 0) {
|
|
32003
|
+
updated.exports = { ...config2.exports, ...result.data.exports };
|
|
32004
|
+
}
|
|
32005
|
+
try {
|
|
32006
|
+
await this.writeConfig(resolved.root, updated);
|
|
32007
|
+
} catch (err) {
|
|
32008
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
32009
|
+
return this.errorResult(`failed to write config: ${message}`);
|
|
32010
|
+
}
|
|
32011
|
+
return this.jsonResult({ ok: true, workspace: resolved.name, updated: result.data });
|
|
31392
32012
|
}
|
|
31393
32013
|
/**
|
|
31394
32014
|
* Recursively discover composites under the given directories via the shared
|
|
@@ -31417,6 +32037,138 @@ var RaftersToolHandler = class {
|
|
|
31417
32037
|
this.compositesLoadedFor.add(workspace.root);
|
|
31418
32038
|
}
|
|
31419
32039
|
}
|
|
32040
|
+
/**
|
|
32041
|
+
* Read and migrate a workspace's `.rafters/config.rafters.json`. Returns null
|
|
32042
|
+
* when the file is absent or unparseable. The single config read the MCP
|
|
32043
|
+
* tools share -- composite paths and the registry URL both come through here.
|
|
32044
|
+
*/
|
|
32045
|
+
async readConfig(workspaceRoot) {
|
|
32046
|
+
const paths = getRaftersPaths(workspaceRoot);
|
|
32047
|
+
try {
|
|
32048
|
+
return migrateConfig(
|
|
32049
|
+
JSON.parse(await readFile6(paths.config, "utf-8"))
|
|
32050
|
+
);
|
|
32051
|
+
} catch {
|
|
32052
|
+
return null;
|
|
32053
|
+
}
|
|
32054
|
+
}
|
|
32055
|
+
/** Persist a workspace's config back to `.rafters/config.rafters.json`. */
|
|
32056
|
+
async writeConfig(workspaceRoot, config2) {
|
|
32057
|
+
const paths = getRaftersPaths(workspaceRoot);
|
|
32058
|
+
await writeFile4(paths.config, `${JSON.stringify(config2, null, 2)}
|
|
32059
|
+
`);
|
|
32060
|
+
}
|
|
32061
|
+
/**
|
|
32062
|
+
* Resolve the registry client for a workspace, honoring its configured
|
|
32063
|
+
* `registryUrl`. Workspaces with no config, or no `registryUrl`, share the
|
|
32064
|
+
* default singleton. Clients are cached per URL so each keeps its fetch cache.
|
|
32065
|
+
*/
|
|
32066
|
+
async registryClientFor(workspace) {
|
|
32067
|
+
if (!workspace) return registryClient;
|
|
32068
|
+
const url3 = (await this.readConfig(workspace.root))?.registryUrl;
|
|
32069
|
+
if (!url3) return registryClient;
|
|
32070
|
+
let client = this.registryClients.get(url3);
|
|
32071
|
+
if (!client) {
|
|
32072
|
+
client = new RegistryClient(url3);
|
|
32073
|
+
this.registryClients.set(url3, client);
|
|
32074
|
+
}
|
|
32075
|
+
return client;
|
|
32076
|
+
}
|
|
32077
|
+
/**
|
|
32078
|
+
* Build (once, lazily) and cache the intel graph for a workspace root.
|
|
32079
|
+
* `assembleGraph` (#2072/#2090) consumes the whole-catalog `RegistryItem[]` and
|
|
32080
|
+
* carries each node's complete intelligence and all per-target facets onto the
|
|
32081
|
+
* universal graph. Throws when the catalog can't be loaded (both the bulk
|
|
32082
|
+
* endpoint and the per-item fallback failed) or the graph is structurally
|
|
32083
|
+
* broken (a dangling `composesWith` edge -- #2072's deliberate fail-fast); the
|
|
32084
|
+
* caller converts that into a structured error result. A failed build is never
|
|
32085
|
+
* cached, so the next call retries.
|
|
32086
|
+
*/
|
|
32087
|
+
async ensureGraph(workspace) {
|
|
32088
|
+
const key = workspace?.root ?? "";
|
|
32089
|
+
const cached2 = this.graphsByWorkspace.get(key);
|
|
32090
|
+
if (cached2) return cached2;
|
|
32091
|
+
const graph = assembleGraph(await this.itemsSource(workspace));
|
|
32092
|
+
this.graphsByWorkspace.set(key, graph);
|
|
32093
|
+
return graph;
|
|
32094
|
+
}
|
|
32095
|
+
/**
|
|
32096
|
+
* Resolve a workspace's overlay context: the configured `componentTarget`
|
|
32097
|
+
* (echoed as-is, `undefined` in degraded mode) and its installed set. Per the
|
|
32098
|
+
* integration note on #2074, `installed.primitives` folds into the components
|
|
32099
|
+
* set -- a `primitive`-kind item maps to graph kind `component`, so without the
|
|
32100
|
+
* fold every installed primitive would misreport as `available`. Built here
|
|
32101
|
+
* rather than by mutating `buildInstalledSet`'s output, leaving overlay.ts
|
|
32102
|
+
* untouched.
|
|
32103
|
+
*
|
|
32104
|
+
* `componentTarget` comes off unvalidated on-disk config (`readConfig` is a raw
|
|
32105
|
+
* `JSON.parse`), so it is run through `ComponentTargetSchema` here rather than
|
|
32106
|
+
* trusted as a `ComponentTarget` -- a stale or typo'd value falls back to
|
|
32107
|
+
* degraded mode (`undefined` target) instead of silently forcing
|
|
32108
|
+
* `rendersForTarget` false for every node.
|
|
32109
|
+
*/
|
|
32110
|
+
async overlayContext(workspace) {
|
|
32111
|
+
const config2 = workspace ? await this.readConfig(workspace.root) : null;
|
|
32112
|
+
const base = buildInstalledSet(config2 ?? {});
|
|
32113
|
+
const components = /* @__PURE__ */ new Set([...base.components, ...config2?.installed?.primitives ?? []]);
|
|
32114
|
+
const parsedTarget = ComponentTargetSchema.safeParse(config2?.componentTarget);
|
|
32115
|
+
return {
|
|
32116
|
+
target: parsedTarget.success ? parsedTarget.data : void 0,
|
|
32117
|
+
installed: { components, composites: base.composites }
|
|
32118
|
+
};
|
|
32119
|
+
}
|
|
32120
|
+
/**
|
|
32121
|
+
* The `rafters_describe` dispatcher -- the one seam that composes #2072/#2074/
|
|
32122
|
+
* #2075. A natural-language question routes through the intent door
|
|
32123
|
+
* (`matchIntent`); a dot-address resolves through the workspace overlay
|
|
32124
|
+
* (`describeWithOverlay`, which delegates to #2072's `describe`).
|
|
32125
|
+
*/
|
|
32126
|
+
async handleDescribe(address, workspaceName) {
|
|
32127
|
+
const resolved = this.resolve(workspaceName);
|
|
32128
|
+
if (workspaceName && !resolved) {
|
|
32129
|
+
return this.workspaceRequiredError();
|
|
32130
|
+
}
|
|
32131
|
+
let graph;
|
|
32132
|
+
try {
|
|
32133
|
+
graph = await this.ensureGraph(resolved);
|
|
32134
|
+
} catch (err) {
|
|
32135
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
32136
|
+
return this.errorResult(`failed to build intel graph: ${message}`);
|
|
32137
|
+
}
|
|
32138
|
+
if (isNaturalLanguageQuery(address)) {
|
|
32139
|
+
return this.jsonResult(matchIntent(address, graph));
|
|
32140
|
+
}
|
|
32141
|
+
const ctx = await this.overlayContext(resolved);
|
|
32142
|
+
return this.jsonResult(describeWithOverlay(address, graph, ctx));
|
|
32143
|
+
}
|
|
32144
|
+
/**
|
|
32145
|
+
* The `rafters_generate` stub (Issue E). Registered now so the surface shape
|
|
32146
|
+
* (name, input schema) stabilizes before the generator lands; it never
|
|
32147
|
+
* attempts real composition. Deliberately does NOT build the graph: nothing
|
|
32148
|
+
* here reads it, and a build failure must not turn an honest stub into an
|
|
32149
|
+
* error result.
|
|
32150
|
+
*/
|
|
32151
|
+
async handleGenerate(intent, workspaceName) {
|
|
32152
|
+
const resolved = this.resolve(workspaceName);
|
|
32153
|
+
if (workspaceName && !resolved) {
|
|
32154
|
+
return this.workspaceRequiredError();
|
|
32155
|
+
}
|
|
32156
|
+
return this.jsonResult({
|
|
32157
|
+
implemented: false,
|
|
32158
|
+
note: `generate is a spike -- see issue E; not yet implemented (requested: ${intent})`
|
|
32159
|
+
});
|
|
32160
|
+
}
|
|
32161
|
+
/**
|
|
32162
|
+
* Stamp the deprecated marker as a top-level field. Objects gain a sibling
|
|
32163
|
+
* `deprecated` key; the rare array/leaf result is wrapped so the marker still
|
|
32164
|
+
* rides at top level.
|
|
32165
|
+
*/
|
|
32166
|
+
withDeprecated(payload) {
|
|
32167
|
+
if (typeof payload === "object" && payload !== null && !Array.isArray(payload)) {
|
|
32168
|
+
return { ...payload, deprecated: DEPRECATED_MSG };
|
|
32169
|
+
}
|
|
32170
|
+
return { result: payload, deprecated: DEPRECATED_MSG };
|
|
32171
|
+
}
|
|
31420
32172
|
/**
|
|
31421
32173
|
* Resolve the set of folders to scan for composite manifests in a workspace.
|
|
31422
32174
|
* Reads `.rafters/config.rafters.json` and applies the workspace's
|
|
@@ -31426,13 +32178,7 @@ var RaftersToolHandler = class {
|
|
|
31426
32178
|
*/
|
|
31427
32179
|
async compositeReadRoots(workspaceRoot) {
|
|
31428
32180
|
const paths = getRaftersPaths(workspaceRoot);
|
|
31429
|
-
|
|
31430
|
-
try {
|
|
31431
|
-
config2 = migrateConfig(
|
|
31432
|
-
JSON.parse(await readFile6(paths.config, "utf-8"))
|
|
31433
|
-
);
|
|
31434
|
-
} catch {
|
|
31435
|
-
}
|
|
32181
|
+
const config2 = await this.readConfig(workspaceRoot);
|
|
31436
32182
|
if (!config2?.compositesPath) {
|
|
31437
32183
|
return [join12(paths.root, "composites")];
|
|
31438
32184
|
}
|
|
@@ -31440,16 +32186,16 @@ var RaftersToolHandler = class {
|
|
|
31440
32186
|
}
|
|
31441
32187
|
async handleComposite(args) {
|
|
31442
32188
|
const { id, query, category, workspace } = args;
|
|
32189
|
+
if (id) {
|
|
32190
|
+
return this.describeById(id, workspace);
|
|
32191
|
+
}
|
|
31443
32192
|
const resolved = this.resolve(workspace);
|
|
31444
32193
|
if (workspace && !resolved) {
|
|
31445
32194
|
return this.workspaceRequiredError();
|
|
31446
32195
|
}
|
|
31447
32196
|
await this.ensureCompositesLoaded(resolved);
|
|
31448
32197
|
let composites2;
|
|
31449
|
-
if (
|
|
31450
|
-
const c4 = get2(id);
|
|
31451
|
-
composites2 = c4 ? [c4] : [];
|
|
31452
|
-
} else if (query) {
|
|
32198
|
+
if (query) {
|
|
31453
32199
|
composites2 = search(query);
|
|
31454
32200
|
} else if (category) {
|
|
31455
32201
|
composites2 = getByCategory(category);
|
|
@@ -31474,9 +32220,7 @@ var RaftersToolHandler = class {
|
|
|
31474
32220
|
// are listed -- the array is empty when nothing is constrained.
|
|
31475
32221
|
blockRules: c4.blocks.filter((b2) => b2.rules && b2.rules.length > 0).map((b2) => ({ id: b2.id, type: b2.type, rules: b2.rules }))
|
|
31476
32222
|
}));
|
|
31477
|
-
return {
|
|
31478
|
-
content: [{ type: "text", text: JSON.stringify({ composites: result }, null, 2) }]
|
|
31479
|
-
};
|
|
32223
|
+
return this.jsonResult({ composites: result, deprecated: DEPRECATED_MSG });
|
|
31480
32224
|
}
|
|
31481
32225
|
async handlePattern(args) {
|
|
31482
32226
|
const { solves, query, workspace } = args;
|
|
@@ -31503,17 +32247,10 @@ var RaftersToolHandler = class {
|
|
|
31503
32247
|
id: c4.manifest.id,
|
|
31504
32248
|
solves: c4.manifest.solves
|
|
31505
32249
|
}));
|
|
31506
|
-
return {
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
31510
|
-
text: JSON.stringify({
|
|
31511
|
-
error: "No patterns found matching query",
|
|
31512
|
-
available
|
|
31513
|
-
})
|
|
31514
|
-
}
|
|
31515
|
-
]
|
|
31516
|
-
};
|
|
32250
|
+
return this.errorResult("No patterns found matching query", {
|
|
32251
|
+
available,
|
|
32252
|
+
deprecated: DEPRECATED_MSG
|
|
32253
|
+
});
|
|
31517
32254
|
}
|
|
31518
32255
|
const patterns = composites2.map((c4) => ({
|
|
31519
32256
|
id: c4.manifest.id,
|
|
@@ -31523,51 +32260,38 @@ var RaftersToolHandler = class {
|
|
|
31523
32260
|
cognitiveLoad: c4.manifest.cognitiveLoad,
|
|
31524
32261
|
usagePatterns: c4.manifest.usagePatterns
|
|
31525
32262
|
}));
|
|
31526
|
-
return {
|
|
31527
|
-
|
|
31528
|
-
|
|
32263
|
+
return this.jsonResult({ patterns, deprecated: DEPRECATED_MSG });
|
|
32264
|
+
}
|
|
32265
|
+
/**
|
|
32266
|
+
* DEPRECATED alias for `rafters_describe`. A component resolves by id through
|
|
32267
|
+
* the overlay exactly as `describe(<id>)` does (#2072's resolver has no
|
|
32268
|
+
* separate component/composite address form -- `describe(<id>)` resolves either
|
|
32269
|
+
* kind), so this is a direct, lossless forward with the deprecated marker added.
|
|
32270
|
+
*/
|
|
32271
|
+
async handleComponent(componentName, workspaceName) {
|
|
32272
|
+
return this.describeById(componentName, workspaceName);
|
|
31529
32273
|
}
|
|
31530
|
-
|
|
32274
|
+
/**
|
|
32275
|
+
* Shared by the deprecated `rafters_component` and `rafters_composite({id})`
|
|
32276
|
+
* paths: resolve one id through the overlay, stamp the deprecated marker.
|
|
32277
|
+
*/
|
|
32278
|
+
async describeById(id, workspaceName) {
|
|
32279
|
+
const resolved = this.resolve(workspaceName);
|
|
32280
|
+
if (workspaceName && !resolved) {
|
|
32281
|
+
return this.workspaceRequiredError();
|
|
32282
|
+
}
|
|
32283
|
+
let graph;
|
|
31531
32284
|
try {
|
|
31532
|
-
|
|
31533
|
-
return {
|
|
31534
|
-
content: [
|
|
31535
|
-
{
|
|
31536
|
-
type: "text",
|
|
31537
|
-
text: JSON.stringify(
|
|
31538
|
-
{
|
|
31539
|
-
name: item.name,
|
|
31540
|
-
type: item.type,
|
|
31541
|
-
description: item.description,
|
|
31542
|
-
primitives: item.primitives,
|
|
31543
|
-
rules: item.rules,
|
|
31544
|
-
composites: item.composites,
|
|
31545
|
-
files: item.files,
|
|
31546
|
-
// The intelligence field carries the WHY of the component:
|
|
31547
|
-
// cognitive load, accessibility, do/never, semantic meaning.
|
|
31548
|
-
// Extracted from JSDoc by the registry generator and present
|
|
31549
|
-
// on every component JSON. Previously stripped by the schema
|
|
31550
|
-
// (not declared) and not referenced by the handler -- the
|
|
31551
|
-
// tool's whole reason for existing went missing somewhere.
|
|
31552
|
-
intelligence: item.intelligence
|
|
31553
|
-
},
|
|
31554
|
-
null,
|
|
31555
|
-
2
|
|
31556
|
-
)
|
|
31557
|
-
}
|
|
31558
|
-
]
|
|
31559
|
-
};
|
|
32285
|
+
graph = await this.ensureGraph(resolved);
|
|
31560
32286
|
} catch (err) {
|
|
31561
32287
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
31562
|
-
return {
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
type: "text",
|
|
31566
|
-
text: JSON.stringify({ error: message })
|
|
31567
|
-
}
|
|
31568
|
-
]
|
|
31569
|
-
};
|
|
32288
|
+
return this.errorResult(`failed to build intel graph: ${message}`, {
|
|
32289
|
+
deprecated: DEPRECATED_MSG
|
|
32290
|
+
});
|
|
31570
32291
|
}
|
|
32292
|
+
const ctx = await this.overlayContext(resolved);
|
|
32293
|
+
const out = describeWithOverlay(id, graph, ctx);
|
|
32294
|
+
return this.jsonResult(this.withDeprecated(out));
|
|
31571
32295
|
}
|
|
31572
32296
|
};
|
|
31573
32297
|
|
|
@@ -31644,8 +32368,8 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
31644
32368
|
import { resolve as resolve6 } from "path";
|
|
31645
32369
|
|
|
31646
32370
|
// ../studio/src/api/vite-plugin.ts
|
|
31647
|
-
import { readFile as readFile7, writeFile as
|
|
31648
|
-
import { isAbsolute as
|
|
32371
|
+
import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
32372
|
+
import { isAbsolute as isAbsolute4, join as join14 } from "path";
|
|
31649
32373
|
var REGISTRY_PLUGINS3 = [scalePlugin, contrastPlugin, statePlugin, invertPlugin];
|
|
31650
32374
|
var STUDIO_REASON_DEFAULT = "studio interactive edit";
|
|
31651
32375
|
var TokenResponseSchema = external_exports.object({
|
|
@@ -31706,7 +32430,7 @@ async function readRaftersConfig() {
|
|
|
31706
32430
|
}
|
|
31707
32431
|
function validateFontsPath(path) {
|
|
31708
32432
|
if (path === null || path === void 0) return null;
|
|
31709
|
-
if (
|
|
32433
|
+
if (isAbsolute4(path)) return "fonts path must be relative";
|
|
31710
32434
|
return null;
|
|
31711
32435
|
}
|
|
31712
32436
|
function validateIntent(intent) {
|
|
@@ -31725,6 +32449,17 @@ var SetTokenMessageSchema = external_exports.object({
|
|
|
31725
32449
|
persist: external_exports.boolean().optional(),
|
|
31726
32450
|
reason: external_exports.string().optional()
|
|
31727
32451
|
});
|
|
32452
|
+
var ConfigPatchSchema = external_exports.object({
|
|
32453
|
+
intent: external_exports.string().min(1).optional(),
|
|
32454
|
+
darkMode: external_exports.enum(["class", "media"]).optional(),
|
|
32455
|
+
fonts: FontsConfigSchema.optional()
|
|
32456
|
+
}).refine((patch) => Object.keys(patch).length > 0, {
|
|
32457
|
+
message: "patch must set at least one of: intent, darkMode, fonts"
|
|
32458
|
+
});
|
|
32459
|
+
function ridOf(rawData) {
|
|
32460
|
+
const rid = rawData?.__rid;
|
|
32461
|
+
return typeof rid === "string" ? rid : void 0;
|
|
32462
|
+
}
|
|
31728
32463
|
var TokenPatchSchema = TokenSchema.pick({
|
|
31729
32464
|
value: true,
|
|
31730
32465
|
trustLevel: true,
|
|
@@ -32235,83 +32970,60 @@ function studioApiPlugin() {
|
|
|
32235
32970
|
client.send("rafters:token-updated", { ok: false, error: String(error47) });
|
|
32236
32971
|
}
|
|
32237
32972
|
});
|
|
32238
|
-
server.ws.on("rafters:get-config", async (
|
|
32973
|
+
server.ws.on("rafters:get-config", async (rawData, client) => {
|
|
32974
|
+
const __rid = ridOf(rawData);
|
|
32975
|
+
const reply = (msg) => client.send("rafters:config", { ...msg, __rid });
|
|
32239
32976
|
const config2 = await readRaftersConfig();
|
|
32240
32977
|
if (!config2) {
|
|
32241
|
-
|
|
32242
|
-
ok: false,
|
|
32243
|
-
error: `config not found at ${configPath}`
|
|
32244
|
-
});
|
|
32978
|
+
reply({ ok: false, error: `config not found at ${configPath}` });
|
|
32245
32979
|
return;
|
|
32246
32980
|
}
|
|
32247
|
-
|
|
32981
|
+
reply({ ok: true, config: config2 });
|
|
32248
32982
|
});
|
|
32249
|
-
server.ws.on("rafters:set-
|
|
32250
|
-
const
|
|
32983
|
+
server.ws.on("rafters:set-config", async (rawData, client) => {
|
|
32984
|
+
const __rid = ridOf(rawData);
|
|
32985
|
+
const reply = (msg) => client.send("rafters:config", { ...msg, __rid });
|
|
32986
|
+
const parsed = ConfigPatchSchema.safeParse(rawData);
|
|
32251
32987
|
if (!parsed.success) {
|
|
32252
|
-
|
|
32253
|
-
ok: false,
|
|
32254
|
-
error: `Invalid message: ${parsed.error.message}`
|
|
32255
|
-
});
|
|
32256
|
-
return;
|
|
32257
|
-
}
|
|
32258
|
-
const { intent } = parsed.data;
|
|
32259
|
-
const intentError = validateIntent(intent);
|
|
32260
|
-
if (intentError) {
|
|
32261
|
-
client.send("rafters:intent-updated", { ok: false, error: intentError });
|
|
32262
|
-
return;
|
|
32263
|
-
}
|
|
32264
|
-
const config2 = await readRaftersConfig();
|
|
32265
|
-
if (!config2) {
|
|
32266
|
-
client.send("rafters:intent-updated", {
|
|
32267
|
-
ok: false,
|
|
32268
|
-
error: `config not found at ${configPath}`
|
|
32269
|
-
});
|
|
32988
|
+
reply({ ok: false, error: `Invalid patch: ${parsed.error.message}` });
|
|
32270
32989
|
return;
|
|
32271
32990
|
}
|
|
32272
|
-
const
|
|
32273
|
-
|
|
32274
|
-
|
|
32275
|
-
|
|
32276
|
-
|
|
32277
|
-
|
|
32278
|
-
|
|
32279
|
-
}
|
|
32280
|
-
});
|
|
32281
|
-
server.ws.on("rafters:set-fonts", async (rawData, client) => {
|
|
32282
|
-
const parsed = FontsConfigSchema.safeParse(rawData);
|
|
32283
|
-
if (!parsed.success) {
|
|
32284
|
-
client.send("rafters:fonts-updated", {
|
|
32285
|
-
ok: false,
|
|
32286
|
-
error: `Invalid message: ${parsed.error.message}`
|
|
32287
|
-
});
|
|
32288
|
-
return;
|
|
32991
|
+
const patch = parsed.data;
|
|
32992
|
+
if (patch.intent !== void 0) {
|
|
32993
|
+
const intentError = validateIntent(patch.intent);
|
|
32994
|
+
if (intentError) {
|
|
32995
|
+
reply({ ok: false, error: intentError });
|
|
32996
|
+
return;
|
|
32997
|
+
}
|
|
32289
32998
|
}
|
|
32290
|
-
|
|
32291
|
-
|
|
32292
|
-
|
|
32293
|
-
|
|
32294
|
-
|
|
32999
|
+
if (patch.fonts?.path !== void 0) {
|
|
33000
|
+
const pathError = validateFontsPath(patch.fonts.path);
|
|
33001
|
+
if (pathError) {
|
|
33002
|
+
reply({ ok: false, error: pathError });
|
|
33003
|
+
return;
|
|
33004
|
+
}
|
|
32295
33005
|
}
|
|
32296
33006
|
const config2 = await readRaftersConfig();
|
|
32297
33007
|
if (!config2) {
|
|
32298
|
-
|
|
32299
|
-
ok: false,
|
|
32300
|
-
error: `config not found at ${configPath}`
|
|
32301
|
-
});
|
|
33008
|
+
reply({ ok: false, error: `config not found at ${configPath}` });
|
|
32302
33009
|
return;
|
|
32303
33010
|
}
|
|
32304
|
-
const
|
|
32305
|
-
|
|
32306
|
-
if (
|
|
32307
|
-
if (
|
|
32308
|
-
|
|
33011
|
+
const updated = { ...config2 };
|
|
33012
|
+
if (patch.intent !== void 0) updated.intent = patch.intent;
|
|
33013
|
+
if (patch.darkMode !== void 0) updated.darkMode = patch.darkMode;
|
|
33014
|
+
if (patch.fonts !== void 0) {
|
|
33015
|
+
const merged = { ...config2.fonts };
|
|
33016
|
+
if (patch.fonts.path !== void 0) merged.path = patch.fonts.path;
|
|
33017
|
+
if (patch.fonts.imports !== void 0) merged.imports = patch.fonts.imports;
|
|
33018
|
+
updated.fonts = merged;
|
|
33019
|
+
}
|
|
32309
33020
|
try {
|
|
32310
|
-
await
|
|
32311
|
-
|
|
33021
|
+
await writeFile5(configPath, `${JSON.stringify(updated, null, 2)}
|
|
33022
|
+
`);
|
|
33023
|
+
reply({ ok: true, config: updated });
|
|
32312
33024
|
} catch (error47) {
|
|
32313
|
-
console.log(`[rafters]
|
|
32314
|
-
|
|
33025
|
+
console.log(`[rafters] Config update failed: ${error47}`);
|
|
33026
|
+
reply({ ok: false, error: String(error47) });
|
|
32315
33027
|
}
|
|
32316
33028
|
});
|
|
32317
33029
|
server.middlewares.use(async (req, res, next) => {
|