@vcad/mcp 0.11.0-main.159 → 0.11.0-main.163
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 +1 -1
- package/index.mjs +394 -92
- package/package.json +3 -3
- package/skill/SKILL.md +22 -0
package/README.md
CHANGED
|
@@ -9,4 +9,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
|
|
|
9
9
|
Or install vcad.app, which registers this server with your agents on first launch.
|
|
10
10
|
The agent skill ships at `skill/SKILL.md`.
|
|
11
11
|
|
|
12
|
-
Built from
|
|
12
|
+
Built from 816ee12b085c12b403a35ebfedb6c276e175a3af at 2026-11-11T21:40:44.212Z. Source: https://github.com/muni-works/vcad
|
package/index.mjs
CHANGED
|
@@ -10277,6 +10277,11 @@ function evaluateOp(op, nodes, Solid2, cache, depth = 0) {
|
|
|
10277
10277
|
}
|
|
10278
10278
|
case "Sketch2D":
|
|
10279
10279
|
return Solid2.empty();
|
|
10280
|
+
// A sketch attached to a named face: like Sketch2D, it is not a solid
|
|
10281
|
+
// itself. (The native evaluator resolves the face; this legacy TS path
|
|
10282
|
+
// only needs the switch to stay exhaustive.)
|
|
10283
|
+
case "SketchOnFace":
|
|
10284
|
+
return Solid2.empty();
|
|
10280
10285
|
case "Extrude": {
|
|
10281
10286
|
const sketchNode = nodes[String(op.sketch)];
|
|
10282
10287
|
if (!sketchNode) {
|
|
@@ -11307,9 +11312,9 @@ function workerTransport(worker) {
|
|
|
11307
11312
|
pending.delete(id);
|
|
11308
11313
|
});
|
|
11309
11314
|
return {
|
|
11310
|
-
request: (req) => new Promise((
|
|
11315
|
+
request: (req) => new Promise((resolve9) => {
|
|
11311
11316
|
const id = next++;
|
|
11312
|
-
pending.set(id,
|
|
11317
|
+
pending.set(id, resolve9);
|
|
11313
11318
|
worker.postMessage({ id, req });
|
|
11314
11319
|
}),
|
|
11315
11320
|
dispose: () => worker.terminate()
|
|
@@ -12188,11 +12193,11 @@ var init_analyze = __esm({
|
|
|
12188
12193
|
type: "module"
|
|
12189
12194
|
});
|
|
12190
12195
|
this.worker = worker;
|
|
12191
|
-
this.ready = new Promise((
|
|
12196
|
+
this.ready = new Promise((resolve9, reject) => {
|
|
12192
12197
|
const onMessage = (e) => {
|
|
12193
12198
|
const { type, id, message, analysis } = e.data;
|
|
12194
12199
|
if (type === "ready") {
|
|
12195
|
-
|
|
12200
|
+
resolve9();
|
|
12196
12201
|
return;
|
|
12197
12202
|
}
|
|
12198
12203
|
if (type === "error" && id === null) {
|
|
@@ -12216,9 +12221,9 @@ var init_analyze = __esm({
|
|
|
12216
12221
|
return this.ready;
|
|
12217
12222
|
}
|
|
12218
12223
|
request(msg, transfer = []) {
|
|
12219
|
-
return this.ensureWorker().then(() => new Promise((
|
|
12224
|
+
return this.ensureWorker().then(() => new Promise((resolve9, reject) => {
|
|
12220
12225
|
const id = this.nextId++;
|
|
12221
|
-
this.pending.set(id, { resolve:
|
|
12226
|
+
this.pending.set(id, { resolve: resolve9, reject });
|
|
12222
12227
|
this.worker.postMessage({ ...msg, id }, transfer);
|
|
12223
12228
|
}));
|
|
12224
12229
|
}
|
|
@@ -12835,11 +12840,11 @@ var init_dist = __esm({
|
|
|
12835
12840
|
return;
|
|
12836
12841
|
try {
|
|
12837
12842
|
const worker = new Worker(new URL("./eval-worker.js", import.meta.url), { type: "module" });
|
|
12838
|
-
this.workerReady = new Promise((
|
|
12843
|
+
this.workerReady = new Promise((resolve9, reject) => {
|
|
12839
12844
|
const onMessage = (e) => {
|
|
12840
12845
|
if (e.data.type === "ready") {
|
|
12841
12846
|
worker.removeEventListener("message", onMessage);
|
|
12842
|
-
|
|
12847
|
+
resolve9();
|
|
12843
12848
|
} else if (e.data.type === "error" && e.data.id === null) {
|
|
12844
12849
|
worker.removeEventListener("message", onMessage);
|
|
12845
12850
|
console.warn("[ENGINE] Worker WASM init failed:", e.data.message);
|
|
@@ -13062,7 +13067,7 @@ var init_dist = __esm({
|
|
|
13062
13067
|
const worker = this.worker;
|
|
13063
13068
|
const id = Math.random().toString(36).slice(2);
|
|
13064
13069
|
const skipClash = options.skipClashDetection ?? false;
|
|
13065
|
-
return new Promise((
|
|
13070
|
+
return new Promise((resolve9, reject) => {
|
|
13066
13071
|
const onMessage = (e) => {
|
|
13067
13072
|
if (e.data.id !== id)
|
|
13068
13073
|
return;
|
|
@@ -13072,7 +13077,7 @@ var init_dist = __esm({
|
|
|
13072
13077
|
if (scene.timing && _Engine.DEV) {
|
|
13073
13078
|
_Engine.logTiming(scene.timing, e.data.workerTotalMs);
|
|
13074
13079
|
}
|
|
13075
|
-
|
|
13080
|
+
resolve9(scene);
|
|
13076
13081
|
} else if (e.data.type === "error") {
|
|
13077
13082
|
reject(new Error(e.data.message));
|
|
13078
13083
|
}
|
|
@@ -17360,7 +17365,7 @@ function isRef(value) {
|
|
|
17360
17365
|
function cloneIssues(issues) {
|
|
17361
17366
|
return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
|
|
17362
17367
|
}
|
|
17363
|
-
function isRecursive(inst, stack,
|
|
17368
|
+
function isRecursive(inst, stack, resolve9) {
|
|
17364
17369
|
const cached3 = recursive.get(inst);
|
|
17365
17370
|
if (cached3 !== void 0)
|
|
17366
17371
|
return cached3 ? PROVEN : NONE;
|
|
@@ -17370,7 +17375,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
17370
17375
|
let result = NONE;
|
|
17371
17376
|
const check = (child) => {
|
|
17372
17377
|
if (result !== PROVEN && child?._zod) {
|
|
17373
|
-
const answer = isRecursive(child, stack,
|
|
17378
|
+
const answer = isRecursive(child, stack, resolve9);
|
|
17374
17379
|
if (answer > result)
|
|
17375
17380
|
result = answer;
|
|
17376
17381
|
}
|
|
@@ -17381,7 +17386,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
17381
17386
|
const desc = Object.getOwnPropertyDescriptor(sh, key3);
|
|
17382
17387
|
if (spread && !desc.enumerable)
|
|
17383
17388
|
continue;
|
|
17384
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
17389
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve9) : NONE;
|
|
17385
17390
|
if (child > answer)
|
|
17386
17391
|
answer = child;
|
|
17387
17392
|
}
|
|
@@ -17445,7 +17450,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
17445
17450
|
break;
|
|
17446
17451
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
17447
17452
|
case "lazy": {
|
|
17448
|
-
const inner = def._cachedInner ?? (
|
|
17453
|
+
const inner = def._cachedInner ?? (resolve9 ? inst._zod.innerType : void 0);
|
|
17449
17454
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
17450
17455
|
break;
|
|
17451
17456
|
}
|
|
@@ -22199,12 +22204,12 @@ var init_stdio2 = __esm({
|
|
|
22199
22204
|
this.onclose?.();
|
|
22200
22205
|
}
|
|
22201
22206
|
send(message) {
|
|
22202
|
-
return new Promise((
|
|
22207
|
+
return new Promise((resolve9) => {
|
|
22203
22208
|
const json = serializeMessage(message);
|
|
22204
22209
|
if (this._stdout.write(json)) {
|
|
22205
|
-
|
|
22210
|
+
resolve9();
|
|
22206
22211
|
} else {
|
|
22207
|
-
this._stdout.once("drain",
|
|
22212
|
+
this._stdout.once("drain", resolve9);
|
|
22208
22213
|
}
|
|
22209
22214
|
});
|
|
22210
22215
|
}
|
|
@@ -23241,7 +23246,7 @@ var init_protocol = __esm({
|
|
|
23241
23246
|
return;
|
|
23242
23247
|
}
|
|
23243
23248
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
23244
|
-
await new Promise((
|
|
23249
|
+
await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
|
|
23245
23250
|
options?.signal?.throwIfAborted();
|
|
23246
23251
|
}
|
|
23247
23252
|
} catch (error2) {
|
|
@@ -23258,7 +23263,7 @@ var init_protocol = __esm({
|
|
|
23258
23263
|
*/
|
|
23259
23264
|
request(request, resultSchema, options) {
|
|
23260
23265
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
23261
|
-
return new Promise((
|
|
23266
|
+
return new Promise((resolve9, reject) => {
|
|
23262
23267
|
const earlyReject = (error2) => {
|
|
23263
23268
|
reject(error2);
|
|
23264
23269
|
};
|
|
@@ -23336,7 +23341,7 @@ var init_protocol = __esm({
|
|
|
23336
23341
|
if (!parseResult.success) {
|
|
23337
23342
|
reject(parseResult.error);
|
|
23338
23343
|
} else {
|
|
23339
|
-
|
|
23344
|
+
resolve9(parseResult.data);
|
|
23340
23345
|
}
|
|
23341
23346
|
} catch (error2) {
|
|
23342
23347
|
reject(error2);
|
|
@@ -23597,12 +23602,12 @@ var init_protocol = __esm({
|
|
|
23597
23602
|
}
|
|
23598
23603
|
} catch {
|
|
23599
23604
|
}
|
|
23600
|
-
return new Promise((
|
|
23605
|
+
return new Promise((resolve9, reject) => {
|
|
23601
23606
|
if (signal.aborted) {
|
|
23602
23607
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
23603
23608
|
return;
|
|
23604
23609
|
}
|
|
23605
|
-
const timeoutId = setTimeout(
|
|
23610
|
+
const timeoutId = setTimeout(resolve9, interval);
|
|
23606
23611
|
signal.addEventListener("abort", () => {
|
|
23607
23612
|
clearTimeout(timeoutId);
|
|
23608
23613
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -26629,7 +26634,7 @@ var require_compile = __commonJS({
|
|
|
26629
26634
|
const schOrFunc = root.refs[ref];
|
|
26630
26635
|
if (schOrFunc)
|
|
26631
26636
|
return schOrFunc;
|
|
26632
|
-
let _sch =
|
|
26637
|
+
let _sch = resolve9.call(this, root, ref);
|
|
26633
26638
|
if (_sch === void 0) {
|
|
26634
26639
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
26635
26640
|
const { schemaId } = this.opts;
|
|
@@ -26656,7 +26661,7 @@ var require_compile = __commonJS({
|
|
|
26656
26661
|
function sameSchemaEnv(s1, s2) {
|
|
26657
26662
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
26658
26663
|
}
|
|
26659
|
-
function
|
|
26664
|
+
function resolve9(root, ref) {
|
|
26660
26665
|
let sch;
|
|
26661
26666
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
26662
26667
|
ref = sch;
|
|
@@ -27231,7 +27236,7 @@ var require_fast_uri = __commonJS({
|
|
|
27231
27236
|
}
|
|
27232
27237
|
return uri;
|
|
27233
27238
|
}
|
|
27234
|
-
function
|
|
27239
|
+
function resolve9(baseURI, relativeURI, options) {
|
|
27235
27240
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
27236
27241
|
const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
27237
27242
|
schemelessOptions.skipEscape = true;
|
|
@@ -27458,7 +27463,7 @@ var require_fast_uri = __commonJS({
|
|
|
27458
27463
|
var fastUri = {
|
|
27459
27464
|
SCHEMES,
|
|
27460
27465
|
normalize: normalize2,
|
|
27461
|
-
resolve:
|
|
27466
|
+
resolve: resolve9,
|
|
27462
27467
|
resolveComponent,
|
|
27463
27468
|
equal,
|
|
27464
27469
|
serialize,
|
|
@@ -33630,21 +33635,21 @@ var require_react_development = __commonJS({
|
|
|
33630
33635
|
);
|
|
33631
33636
|
actScopeDepth = prevActScopeDepth;
|
|
33632
33637
|
}
|
|
33633
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
33638
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve9, reject) {
|
|
33634
33639
|
var queue = ReactSharedInternals.actQueue;
|
|
33635
33640
|
if (null !== queue)
|
|
33636
33641
|
if (0 !== queue.length)
|
|
33637
33642
|
try {
|
|
33638
33643
|
flushActQueue(queue);
|
|
33639
33644
|
enqueueTask(function() {
|
|
33640
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
33645
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve9, reject);
|
|
33641
33646
|
});
|
|
33642
33647
|
return;
|
|
33643
33648
|
} catch (error2) {
|
|
33644
33649
|
ReactSharedInternals.thrownErrors.push(error2);
|
|
33645
33650
|
}
|
|
33646
33651
|
else ReactSharedInternals.actQueue = null;
|
|
33647
|
-
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) :
|
|
33652
|
+
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve9(returnValue);
|
|
33648
33653
|
}
|
|
33649
33654
|
function flushActQueue(queue) {
|
|
33650
33655
|
if (!isFlushing) {
|
|
@@ -33831,7 +33836,7 @@ var require_react_development = __commonJS({
|
|
|
33831
33836
|
));
|
|
33832
33837
|
});
|
|
33833
33838
|
return {
|
|
33834
|
-
then: function(
|
|
33839
|
+
then: function(resolve9, reject) {
|
|
33835
33840
|
didAwaitActCall = true;
|
|
33836
33841
|
thenable.then(
|
|
33837
33842
|
function(returnValue) {
|
|
@@ -33841,7 +33846,7 @@ var require_react_development = __commonJS({
|
|
|
33841
33846
|
flushActQueue(queue), enqueueTask(function() {
|
|
33842
33847
|
return recursivelyFlushAsyncActWork(
|
|
33843
33848
|
returnValue,
|
|
33844
|
-
|
|
33849
|
+
resolve9,
|
|
33845
33850
|
reject
|
|
33846
33851
|
);
|
|
33847
33852
|
});
|
|
@@ -33855,7 +33860,7 @@ var require_react_development = __commonJS({
|
|
|
33855
33860
|
ReactSharedInternals.thrownErrors.length = 0;
|
|
33856
33861
|
reject(_thrownError);
|
|
33857
33862
|
}
|
|
33858
|
-
} else
|
|
33863
|
+
} else resolve9(returnValue);
|
|
33859
33864
|
},
|
|
33860
33865
|
function(error2) {
|
|
33861
33866
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
@@ -33877,15 +33882,15 @@ var require_react_development = __commonJS({
|
|
|
33877
33882
|
if (0 < ReactSharedInternals.thrownErrors.length)
|
|
33878
33883
|
throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
|
|
33879
33884
|
return {
|
|
33880
|
-
then: function(
|
|
33885
|
+
then: function(resolve9, reject) {
|
|
33881
33886
|
didAwaitActCall = true;
|
|
33882
33887
|
0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
|
|
33883
33888
|
return recursivelyFlushAsyncActWork(
|
|
33884
33889
|
returnValue$jscomp$0,
|
|
33885
|
-
|
|
33890
|
+
resolve9,
|
|
33886
33891
|
reject
|
|
33887
33892
|
);
|
|
33888
|
-
})) :
|
|
33893
|
+
})) : resolve9(returnValue$jscomp$0);
|
|
33889
33894
|
}
|
|
33890
33895
|
};
|
|
33891
33896
|
};
|
|
@@ -44782,6 +44787,100 @@ var init_CHANGELOG = __esm({
|
|
|
44782
44787
|
"export_cad"
|
|
44783
44788
|
]
|
|
44784
44789
|
},
|
|
44790
|
+
{
|
|
44791
|
+
id: "2026-11-11-step-interop-audit",
|
|
44792
|
+
version: "0.11.0",
|
|
44793
|
+
date: "2026-11-11",
|
|
44794
|
+
category: "fix",
|
|
44795
|
+
title: "STEP import: no hangs, colours, assembly placement",
|
|
44796
|
+
summary: "KiCad and FreeCAD STEP files no longer hang; reversed face bounds read right; colours, part names and assembly instance poses import and colours export; rational surfaces export.",
|
|
44797
|
+
features: [
|
|
44798
|
+
"step",
|
|
44799
|
+
"import",
|
|
44800
|
+
"export",
|
|
44801
|
+
"native-app"
|
|
44802
|
+
]
|
|
44803
|
+
},
|
|
44804
|
+
{
|
|
44805
|
+
id: "2026-11-11-sketch-on-face",
|
|
44806
|
+
version: "undefined",
|
|
44807
|
+
date: "2026-11-11",
|
|
44808
|
+
category: "feat",
|
|
44809
|
+
title: "Sketches drawn on a face follow that face",
|
|
44810
|
+
summary: "A sketch drawn on a named face now re-attaches to it on every rebuild, so a boss on a plate's top stays on top when the plate gets thicker. An unresolvable face fails closed.",
|
|
44811
|
+
features: [
|
|
44812
|
+
"sketch",
|
|
44813
|
+
"naming",
|
|
44814
|
+
"history"
|
|
44815
|
+
]
|
|
44816
|
+
},
|
|
44817
|
+
{
|
|
44818
|
+
id: "2026-11-11-sketch-inference-smart-dimension",
|
|
44819
|
+
version: "0.11.0",
|
|
44820
|
+
date: "2026-11-11",
|
|
44821
|
+
category: "feat",
|
|
44822
|
+
title: "Mac sketching: inference, Smart Dimension, trim and fillet",
|
|
44823
|
+
summary: "The Mac app's sketcher infers constraints while drawing, sizes or pairs with one Smart Dimension tool, colours by solve state, drags to solve, and adds construction, trim, extend, fillet, mirror, patterns and live profile shading.",
|
|
44824
|
+
features: [
|
|
44825
|
+
"sketch",
|
|
44826
|
+
"constraints",
|
|
44827
|
+
"mac-app"
|
|
44828
|
+
]
|
|
44829
|
+
},
|
|
44830
|
+
{
|
|
44831
|
+
id: "2026-11-11-revolve-profile-on-axis",
|
|
44832
|
+
version: "0.11.0",
|
|
44833
|
+
date: "2026-11-11",
|
|
44834
|
+
category: "fix",
|
|
44835
|
+
title: "Revolve profiles that touch their axis",
|
|
44836
|
+
summary: "Sketch revolve now accepts profiles with vertices on the axis: axis edges add no face, perpendicular edges become full discs, slanted edges become cones with an apex.",
|
|
44837
|
+
features: [
|
|
44838
|
+
"revolve",
|
|
44839
|
+
"sketch"
|
|
44840
|
+
]
|
|
44841
|
+
},
|
|
44842
|
+
{
|
|
44843
|
+
id: "2026-11-11-print-from-vcad",
|
|
44844
|
+
version: "0.11.0",
|
|
44845
|
+
date: "2026-11-11",
|
|
44846
|
+
category: "feat",
|
|
44847
|
+
title: "Print to a Bambu printer straight from vcad",
|
|
44848
|
+
summary: "3D Print is now Orient \u2192 Slice \u2192 Printer \u2192 Print: auto-orientation for fewest supports, LAN pairing, live printer and AMS status, a confirmed gated send, and pause/resume/stop.",
|
|
44849
|
+
features: [
|
|
44850
|
+
"3d-print",
|
|
44851
|
+
"slicer",
|
|
44852
|
+
"printer",
|
|
44853
|
+
"mac-app"
|
|
44854
|
+
]
|
|
44855
|
+
},
|
|
44856
|
+
{
|
|
44857
|
+
id: "2026-11-11-per-instance-agent-discovery",
|
|
44858
|
+
version: "undefined",
|
|
44859
|
+
date: "2026-11-11",
|
|
44860
|
+
category: "fix",
|
|
44861
|
+
title: "Agents no longer drive the wrong vcad.app window",
|
|
44862
|
+
summary: "Each vcad.app instance registers its own discovery file; clients pick one via VCAD_APP_PID/URL/DOCUMENT, every bridge result names its window, and a relaunch is reported.",
|
|
44863
|
+
features: [
|
|
44864
|
+
"agent-bridge",
|
|
44865
|
+
"mcp",
|
|
44866
|
+
"mac-app"
|
|
44867
|
+
]
|
|
44868
|
+
},
|
|
44869
|
+
{
|
|
44870
|
+
id: "2026-11-11-one-click-drawings",
|
|
44871
|
+
version: "0.11.0",
|
|
44872
|
+
date: "2026-11-11",
|
|
44873
|
+
category: "feat",
|
|
44874
|
+
title: "One-click drawings with automatic dimensions",
|
|
44875
|
+
summary: "Drawing workspace lays out front/top/right/iso on an auto-sized sheet with hole callouts, center marks and editable dimensions; exports vector PDF and true-scale DXF.",
|
|
44876
|
+
features: [
|
|
44877
|
+
"drawings",
|
|
44878
|
+
"drafting",
|
|
44879
|
+
"native-app",
|
|
44880
|
+
"gdt"
|
|
44881
|
+
],
|
|
44882
|
+
mcpTools: []
|
|
44883
|
+
},
|
|
44785
44884
|
{
|
|
44786
44885
|
id: "2026-11-11-one-breath-install",
|
|
44787
44886
|
version: "0.11.0",
|
|
@@ -44798,6 +44897,20 @@ var init_CHANGELOG = __esm({
|
|
|
44798
44897
|
"server_info"
|
|
44799
44898
|
]
|
|
44800
44899
|
},
|
|
44900
|
+
{
|
|
44901
|
+
id: "2026-11-11-local-data-trust",
|
|
44902
|
+
version: "undefined",
|
|
44903
|
+
date: "2026-11-11",
|
|
44904
|
+
category: "feat",
|
|
44905
|
+
title: "Autosave, crash recovery, and version history",
|
|
44906
|
+
summary: "The Mac app autosaves every few seconds, offers to restore unsaved work after a crash, and keeps per-document versions you can preview, restore, or duplicate.",
|
|
44907
|
+
features: [
|
|
44908
|
+
"mac-app",
|
|
44909
|
+
"autosave",
|
|
44910
|
+
"versions",
|
|
44911
|
+
"recovery"
|
|
44912
|
+
]
|
|
44913
|
+
},
|
|
44801
44914
|
{
|
|
44802
44915
|
id: "2026-11-11-live-receipt-badges",
|
|
44803
44916
|
version: "0.11.0",
|
|
@@ -44819,6 +44932,46 @@ var init_CHANGELOG = __esm({
|
|
|
44819
44932
|
"ask_user"
|
|
44820
44933
|
]
|
|
44821
44934
|
},
|
|
44935
|
+
{
|
|
44936
|
+
id: "2026-11-11-history-edits-keep-references",
|
|
44937
|
+
version: "undefined",
|
|
44938
|
+
date: "2026-11-11",
|
|
44939
|
+
category: "feat",
|
|
44940
|
+
title: "Edits to earlier features keep downstream references",
|
|
44941
|
+
summary: "Extruded faces are named after their sketch segments and fillets, chamfers and shells keep face names, so picked edges survive edits. Move Up/Down refuses moves that would drop a feature.",
|
|
44942
|
+
features: [
|
|
44943
|
+
"naming",
|
|
44944
|
+
"feature-tree",
|
|
44945
|
+
"history"
|
|
44946
|
+
]
|
|
44947
|
+
},
|
|
44948
|
+
{
|
|
44949
|
+
id: "2026-11-11-fusion-model-edges",
|
|
44950
|
+
version: "1.0.0",
|
|
44951
|
+
date: "2026-11-11",
|
|
44952
|
+
category: "feat",
|
|
44953
|
+
title: "Model edges drawn like Fusion 360",
|
|
44954
|
+
summary: "The Mac app now outlines every B-rep face boundary, tangent fillet edges included, and draws outlines on assembly instances too. Mesh-only parts keep crease edges.",
|
|
44955
|
+
features: [
|
|
44956
|
+
"viewport",
|
|
44957
|
+
"edges",
|
|
44958
|
+
"assembly"
|
|
44959
|
+
]
|
|
44960
|
+
},
|
|
44961
|
+
{
|
|
44962
|
+
id: "2026-11-11-direct-manipulation",
|
|
44963
|
+
version: "undefined",
|
|
44964
|
+
date: "2026-11-11",
|
|
44965
|
+
category: "feat",
|
|
44966
|
+
title: "Direct manipulation: press-pull, handles, live dimensions",
|
|
44967
|
+
summary: "Mac app: press-pull planar faces into real features, drag on-canvas handles for extrude/fillet/shell, type exact values into a live dimension, and a redesigned hairline gizmo.",
|
|
44968
|
+
features: [
|
|
44969
|
+
"mac-app",
|
|
44970
|
+
"viewport",
|
|
44971
|
+
"gizmo",
|
|
44972
|
+
"press-pull"
|
|
44973
|
+
]
|
|
44974
|
+
},
|
|
44822
44975
|
{
|
|
44823
44976
|
id: "2026-11-11-counterbore-breakout",
|
|
44824
44977
|
version: "0.11.0",
|
|
@@ -44831,6 +44984,32 @@ var init_CHANGELOG = __esm({
|
|
|
44831
44984
|
"kernel"
|
|
44832
44985
|
]
|
|
44833
44986
|
},
|
|
44987
|
+
{
|
|
44988
|
+
id: "2026-11-11-assembly-export-clearance-step-cache",
|
|
44989
|
+
version: "undefined",
|
|
44990
|
+
date: "2026-11-11",
|
|
44991
|
+
category: "fix",
|
|
44992
|
+
title: "Assembly export, interference and big STEP opens fixed",
|
|
44993
|
+
summary: "Assembly GLB/STL no longer duplicates every part at the origin, STEP exports posed instances, interference scales to big assemblies, and STEP assemblies parse once.",
|
|
44994
|
+
features: [
|
|
44995
|
+
"assembly",
|
|
44996
|
+
"export",
|
|
44997
|
+
"step",
|
|
44998
|
+
"clearance"
|
|
44999
|
+
]
|
|
45000
|
+
},
|
|
45001
|
+
{
|
|
45002
|
+
id: "2026-11-11-arc-extrude-boolean-tearing",
|
|
45003
|
+
version: "0.11.0",
|
|
45004
|
+
date: "2026-11-11",
|
|
45005
|
+
category: "fix",
|
|
45006
|
+
title: "No more tearing when cutting into extruded arc walls",
|
|
45007
|
+
summary: "Booleans that split the curved wall of an arc-bearing sketch extrude no longer tear the wall away from its caps; results stay watertight.",
|
|
45008
|
+
features: [
|
|
45009
|
+
"booleans",
|
|
45010
|
+
"extrude"
|
|
45011
|
+
]
|
|
45012
|
+
},
|
|
44834
45013
|
{
|
|
44835
45014
|
id: "2026-11-11-agent-cursor",
|
|
44836
45015
|
version: "0.11.0",
|
|
@@ -45116,6 +45295,22 @@ var init_CHANGELOG = __esm({
|
|
|
45116
45295
|
"inspect_cad"
|
|
45117
45296
|
]
|
|
45118
45297
|
},
|
|
45298
|
+
{
|
|
45299
|
+
id: "2026-11-10-import-from-photos",
|
|
45300
|
+
version: "0.11.0",
|
|
45301
|
+
date: "2026-11-10",
|
|
45302
|
+
category: "feat",
|
|
45303
|
+
title: "Experimental: import a part from photos",
|
|
45304
|
+
summary: "Mac app: photos of a part beside a tag36h11 AprilTag become an editable document with named, \xB1\u03C3 dimensions. Tested on synthetic renders only, not yet real photos.",
|
|
45305
|
+
features: [
|
|
45306
|
+
"native",
|
|
45307
|
+
"mac",
|
|
45308
|
+
"import",
|
|
45309
|
+
"image-to-cad",
|
|
45310
|
+
"parameters",
|
|
45311
|
+
"experimental"
|
|
45312
|
+
]
|
|
45313
|
+
},
|
|
45119
45314
|
{
|
|
45120
45315
|
id: "2026-11-10-gym-step-angle-units",
|
|
45121
45316
|
version: "0.11.0",
|
|
@@ -47192,6 +47387,20 @@ var init_CHANGELOG = __esm({
|
|
|
47192
47387
|
"manufacture"
|
|
47193
47388
|
]
|
|
47194
47389
|
},
|
|
47390
|
+
{
|
|
47391
|
+
id: "2026-10-13-cone-frustum-radii",
|
|
47392
|
+
version: "0.10.0",
|
|
47393
|
+
date: "2026-10-13",
|
|
47394
|
+
category: "fix",
|
|
47395
|
+
title: "Cone frustum keeps its radii when the top is wider",
|
|
47396
|
+
summary: "A cone/frustum whose top radius exceeds its base no longer swaps the two radii: the apex is now placed from the base radius, so the solid matches the requested geometry.",
|
|
47397
|
+
details: "ConeSurface::from_frustum placed the apex from radius_top in the radius_bottom < radius_top branch, so the lateral surface came out with the radii swapped even though the B-rep circles were correct: Solid::cone(2, 10, 10) produced radius 10 at z=0 and 18 at z=10 instead of 2 and 10, with a volume about 3x the analytic value. Volume and bounding box are symmetric in the two radii, so neither caught it; it only showed in an asymmetric operation or a per-z-band radius sample. This affected any cone with radius_top > radius_bottom, including cylinder(r1 < r2) imported from OpenSCAD and zero-radius cones such as cylinder(h=10, r1=0, r2=5), which additionally tessellated non-watertight.",
|
|
47398
|
+
features: [
|
|
47399
|
+
"kernel",
|
|
47400
|
+
"geometry",
|
|
47401
|
+
"primitives"
|
|
47402
|
+
]
|
|
47403
|
+
},
|
|
47195
47404
|
{
|
|
47196
47405
|
id: "2026-09-17-native-instant-open-contour-cam",
|
|
47197
47406
|
version: "0.10.0",
|
|
@@ -57195,10 +57404,86 @@ var init_source_provenance = __esm({
|
|
|
57195
57404
|
}
|
|
57196
57405
|
});
|
|
57197
57406
|
|
|
57198
|
-
// src/app-
|
|
57199
|
-
import { readFileSync as readFileSync5 } from "node:fs";
|
|
57407
|
+
// src/app-discovery.ts
|
|
57408
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
57200
57409
|
import { homedir } from "node:os";
|
|
57201
|
-
import {
|
|
57410
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
57411
|
+
function isAlive(pid) {
|
|
57412
|
+
if (typeof pid !== "number" || pid <= 0) return false;
|
|
57413
|
+
try {
|
|
57414
|
+
process.kill(pid, 0);
|
|
57415
|
+
return true;
|
|
57416
|
+
} catch (e) {
|
|
57417
|
+
return e.code === "EPERM";
|
|
57418
|
+
}
|
|
57419
|
+
}
|
|
57420
|
+
function describeInstance(r) {
|
|
57421
|
+
return `pid ${r.pid} (${r.document_name ?? r.document_path ?? "no document"})`;
|
|
57422
|
+
}
|
|
57423
|
+
function listInstances({ home = homedir(), alive = isAlive } = {}) {
|
|
57424
|
+
const root = join3(home, ".vcad");
|
|
57425
|
+
const read = (p) => {
|
|
57426
|
+
try {
|
|
57427
|
+
return JSON.parse(readFileSync5(p, "utf8"));
|
|
57428
|
+
} catch {
|
|
57429
|
+
return null;
|
|
57430
|
+
}
|
|
57431
|
+
};
|
|
57432
|
+
let records = [];
|
|
57433
|
+
try {
|
|
57434
|
+
const dir = join3(root, "instances");
|
|
57435
|
+
records = readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => read(join3(dir, f))).filter((r) => r !== null);
|
|
57436
|
+
} catch {
|
|
57437
|
+
}
|
|
57438
|
+
if (records.length === 0) {
|
|
57439
|
+
const legacy = read(join3(root, "app.json"));
|
|
57440
|
+
if (legacy) records = [legacy];
|
|
57441
|
+
}
|
|
57442
|
+
return records.filter((r) => alive(r.pid)).sort((a, b) => String(b.started ?? "").localeCompare(String(a.started ?? "")));
|
|
57443
|
+
}
|
|
57444
|
+
function selectInstance({
|
|
57445
|
+
env = process.env,
|
|
57446
|
+
instances,
|
|
57447
|
+
documentPath
|
|
57448
|
+
}) {
|
|
57449
|
+
if (env.VCAD_APP_URL) {
|
|
57450
|
+
const url = env.VCAD_APP_URL;
|
|
57451
|
+
return { url, instance: instances.find((r) => urlOf(r) === url) ?? null };
|
|
57452
|
+
}
|
|
57453
|
+
if (env.VCAD_APP_PID) {
|
|
57454
|
+
const pid = Number(env.VCAD_APP_PID);
|
|
57455
|
+
const instance2 = instances.find((r) => r.pid === pid);
|
|
57456
|
+
if (!instance2) {
|
|
57457
|
+
const others2 = instances.length ? ` Running: ${instances.map(describeInstance).join(", ")}.` : "";
|
|
57458
|
+
return { error: `VCAD_APP_PID=${env.VCAD_APP_PID}: no running vcad.app with that pid.${others2}` };
|
|
57459
|
+
}
|
|
57460
|
+
return { url: urlOf(instance2), instance: instance2 };
|
|
57461
|
+
}
|
|
57462
|
+
if (instances.length === 0) return { error: "no running vcad.app found (~/.vcad/instances is empty)" };
|
|
57463
|
+
const wanted = documentPath ?? env.VCAD_APP_DOCUMENT;
|
|
57464
|
+
if (wanted) {
|
|
57465
|
+
const abs = resolve3(wanted);
|
|
57466
|
+
const instance2 = instances.find((r) => r.document_path && resolve3(r.document_path) === abs);
|
|
57467
|
+
if (instance2) return { url: urlOf(instance2), instance: instance2 };
|
|
57468
|
+
}
|
|
57469
|
+
const [instance, ...others] = instances;
|
|
57470
|
+
if (!others.length) return { url: urlOf(instance), instance };
|
|
57471
|
+
return {
|
|
57472
|
+
url: urlOf(instance),
|
|
57473
|
+
instance,
|
|
57474
|
+
warning: `${instances.length} vcad.app instances are running; using the newest, ${describeInstance(instance)}. Others: ${others.map(describeInstance).join(", ")}. Set VCAD_APP_PID to choose.`
|
|
57475
|
+
};
|
|
57476
|
+
}
|
|
57477
|
+
var urlOf;
|
|
57478
|
+
var init_app_discovery = __esm({
|
|
57479
|
+
"src/app-discovery.ts"() {
|
|
57480
|
+
"use strict";
|
|
57481
|
+
urlOf = (r) => r.url ?? `http://127.0.0.1:${r.port}/mcp`;
|
|
57482
|
+
}
|
|
57483
|
+
});
|
|
57484
|
+
|
|
57485
|
+
// src/app-link.ts
|
|
57486
|
+
import { dirname, isAbsolute as isAbsolute4 } from "node:path";
|
|
57202
57487
|
function noteDocumentFolder(documentId, folder) {
|
|
57203
57488
|
folders.set(documentId, folder);
|
|
57204
57489
|
}
|
|
@@ -57210,13 +57495,27 @@ function documentFolder(documentId, document) {
|
|
|
57210
57495
|
}
|
|
57211
57496
|
function endpoint() {
|
|
57212
57497
|
if (process.env.VCAD_APP === "0") return null;
|
|
57213
|
-
|
|
57214
|
-
|
|
57215
|
-
|
|
57216
|
-
|
|
57217
|
-
|
|
57218
|
-
|
|
57498
|
+
const instances = listInstances();
|
|
57499
|
+
if (pinned && (process.env.VCAD_APP_URL || instances.some((r) => r.pid === pinned?.instance?.pid))) {
|
|
57500
|
+
return pinned.url;
|
|
57501
|
+
}
|
|
57502
|
+
const pick2 = selectInstance({ instances });
|
|
57503
|
+
if (pick2.error !== void 0) return null;
|
|
57504
|
+
if (pick2.warning && pick2.warning !== warned) {
|
|
57505
|
+
warned = pick2.warning;
|
|
57506
|
+
process.stderr.write(`[vcad-mcp] ${pick2.warning}
|
|
57507
|
+
`);
|
|
57219
57508
|
}
|
|
57509
|
+
const prev = pinned?.instance;
|
|
57510
|
+
if (prev && pick2.instance && prev.instance_id !== pick2.instance.instance_id) {
|
|
57511
|
+
appChanged(prev, pick2.instance);
|
|
57512
|
+
}
|
|
57513
|
+
pinned = { url: pick2.url, instance: pick2.instance };
|
|
57514
|
+
return pick2.url;
|
|
57515
|
+
}
|
|
57516
|
+
function appChanged(prev, next) {
|
|
57517
|
+
const why = `The vcad app changed: ${describeInstance(prev)} is gone, now linked to ${describeInstance(next)} (it relaunched, or another window took over). Edits stay in this session. To show it there, call show_in_app.`;
|
|
57518
|
+
for (const id of links.keys()) loseWindow(id, why);
|
|
57220
57519
|
}
|
|
57221
57520
|
async function reconcileBeforeWrite(documentId, documents2, call = callApp) {
|
|
57222
57521
|
const link = links.get(documentId);
|
|
@@ -57430,14 +57729,17 @@ function pushPose(documentId, joints, call = callApp, now = Date.now(), base) {
|
|
|
57430
57729
|
sendPose(frame, now);
|
|
57431
57730
|
return true;
|
|
57432
57731
|
}
|
|
57433
|
-
var links, folders, callApp, PUSH_WAIT_MS, RECONCILE_WAIT_MS, wait, pushes, lateNotes, LOST_WINDOW, OPENING_TOOLS, toolDefs3, POSE_INTERVAL_MS, poseBusy, lastPose, pendingRestart, pendingLatest, poseTimer;
|
|
57732
|
+
var links, folders, pinned, warned, callApp, PUSH_WAIT_MS, RECONCILE_WAIT_MS, wait, pushes, lateNotes, LOST_WINDOW, OPENING_TOOLS, toolDefs3, POSE_INTERVAL_MS, poseBusy, lastPose, pendingRestart, pendingLatest, poseTimer;
|
|
57434
57733
|
var init_app_link = __esm({
|
|
57435
57734
|
"src/app-link.ts"() {
|
|
57436
57735
|
"use strict";
|
|
57736
|
+
init_app_discovery();
|
|
57437
57737
|
init_session_core();
|
|
57438
57738
|
init_tool_def();
|
|
57439
57739
|
links = /* @__PURE__ */ new Map();
|
|
57440
57740
|
folders = /* @__PURE__ */ new Map();
|
|
57741
|
+
pinned = null;
|
|
57742
|
+
warned = "";
|
|
57441
57743
|
callApp = async (tool, args) => {
|
|
57442
57744
|
const url = endpoint();
|
|
57443
57745
|
if (!url) return null;
|
|
@@ -57507,7 +57809,7 @@ var init_app_link = __esm({
|
|
|
57507
57809
|
|
|
57508
57810
|
// src/tools/session.ts
|
|
57509
57811
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync6 } from "node:fs";
|
|
57510
|
-
import { dirname as dirname2, resolve as
|
|
57812
|
+
import { dirname as dirname2, resolve as resolve4 } from "node:path";
|
|
57511
57813
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
57512
57814
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
57513
57815
|
function durabilityWarning() {
|
|
@@ -57579,7 +57881,7 @@ function openDocument(args) {
|
|
|
57579
57881
|
};
|
|
57580
57882
|
}
|
|
57581
57883
|
function writeLocalSnapshot(id, json) {
|
|
57582
|
-
const dir =
|
|
57884
|
+
const dir = resolve4(stateRoot(), "get_document");
|
|
57583
57885
|
mkdirSync2(dir, { recursive: true });
|
|
57584
57886
|
const path = resolveWithinRoot(`${id}.vcad`, dir);
|
|
57585
57887
|
writeFileSync2(path, json);
|
|
@@ -57792,7 +58094,7 @@ async function loadDocumentFromPath(path, store, engine) {
|
|
|
57792
58094
|
"`path` loads are local/stdio only \u2014 this server's filesystem is ephemeral. Pass the source inline to create_cad_loon, or save with save_document and reopen by `name`."
|
|
57793
58095
|
);
|
|
57794
58096
|
}
|
|
57795
|
-
const abs =
|
|
58097
|
+
const abs = resolve4(path);
|
|
57796
58098
|
let raw;
|
|
57797
58099
|
try {
|
|
57798
58100
|
raw = readFileSync6(abs, "utf8");
|
|
@@ -61597,8 +61899,8 @@ async function openBridge(createServer2, clientInfo, clientCapabilities, hooks)
|
|
|
61597
61899
|
await server.connect(serverSide);
|
|
61598
61900
|
const rpc = (method, params) => {
|
|
61599
61901
|
const id = nextId3++;
|
|
61600
|
-
return new Promise((
|
|
61601
|
-
pending.set(id, (m) =>
|
|
61902
|
+
return new Promise((resolve9) => {
|
|
61903
|
+
pending.set(id, (m) => resolve9(m));
|
|
61602
61904
|
void clientSide.send({
|
|
61603
61905
|
jsonrpc: "2.0",
|
|
61604
61906
|
id,
|
|
@@ -61935,8 +62237,8 @@ function taskElicitation(bridge, reqParams) {
|
|
|
61935
62237
|
method: "elicitation/create",
|
|
61936
62238
|
params: cleanParams
|
|
61937
62239
|
};
|
|
61938
|
-
return new Promise((
|
|
61939
|
-
record2.pendingInputs.set(key3,
|
|
62240
|
+
return new Promise((resolve9) => {
|
|
62241
|
+
record2.pendingInputs.set(key3, resolve9);
|
|
61940
62242
|
touchTask(record2, "input_required", "waiting for client input");
|
|
61941
62243
|
});
|
|
61942
62244
|
}
|
|
@@ -61961,11 +62263,11 @@ function handleTaskMethod(id, method, params) {
|
|
|
61961
62263
|
if (method === "tasks/update") {
|
|
61962
62264
|
const responses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
|
|
61963
62265
|
for (const [key3, response] of Object.entries(responses)) {
|
|
61964
|
-
const
|
|
61965
|
-
if (!
|
|
62266
|
+
const resolve9 = record2.pendingInputs.get(key3);
|
|
62267
|
+
if (!resolve9) continue;
|
|
61966
62268
|
record2.pendingInputs.delete(key3);
|
|
61967
62269
|
delete record2.inputRequests[key3];
|
|
61968
|
-
|
|
62270
|
+
resolve9(response);
|
|
61969
62271
|
}
|
|
61970
62272
|
if (record2.status === "input_required" && Object.keys(record2.inputRequests).length === 0) {
|
|
61971
62273
|
touchTask(record2, "working", `running ${record2.toolName}`);
|
|
@@ -61977,10 +62279,10 @@ function handleTaskMethod(id, method, params) {
|
|
|
61977
62279
|
}
|
|
61978
62280
|
record2.cancelRequested = true;
|
|
61979
62281
|
if (record2.status === "working" || record2.status === "input_required") {
|
|
61980
|
-
for (const [key3,
|
|
62282
|
+
for (const [key3, resolve9] of record2.pendingInputs) {
|
|
61981
62283
|
record2.pendingInputs.delete(key3);
|
|
61982
62284
|
delete record2.inputRequests[key3];
|
|
61983
|
-
|
|
62285
|
+
resolve9({ action: "cancel" });
|
|
61984
62286
|
}
|
|
61985
62287
|
touchTask(record2, "cancelled", "cancelled by client");
|
|
61986
62288
|
}
|
|
@@ -62658,7 +62960,7 @@ function normalizeOp(op, index) {
|
|
|
62658
62960
|
return { name, args };
|
|
62659
62961
|
}
|
|
62660
62962
|
function resolveSymbolicRefs(args, createdIds, index) {
|
|
62661
|
-
const
|
|
62963
|
+
const resolve9 = (v) => {
|
|
62662
62964
|
if (typeof v !== "string") return v;
|
|
62663
62965
|
const m = SYMBOLIC_REF.exec(v);
|
|
62664
62966
|
if (!m) return v;
|
|
@@ -62678,15 +62980,15 @@ function resolveSymbolicRefs(args, createdIds, index) {
|
|
|
62678
62980
|
};
|
|
62679
62981
|
const out = { ...args };
|
|
62680
62982
|
for (const key3 of ["part_id", "node_id"]) {
|
|
62681
|
-
if (key3 in out) out[key3] =
|
|
62983
|
+
if (key3 in out) out[key3] = resolve9(out[key3]);
|
|
62682
62984
|
}
|
|
62683
62985
|
if (out.params && typeof out.params === "object" && !Array.isArray(out.params)) {
|
|
62684
62986
|
const params = { ...out.params };
|
|
62685
62987
|
for (const key3 of ["child", "left", "right", "sketch"]) {
|
|
62686
|
-
if (key3 in params) params[key3] =
|
|
62988
|
+
if (key3 in params) params[key3] = resolve9(params[key3]);
|
|
62687
62989
|
}
|
|
62688
62990
|
if (Array.isArray(params.sketches)) {
|
|
62689
|
-
params.sketches = params.sketches.map(
|
|
62991
|
+
params.sketches = params.sketches.map(resolve9);
|
|
62690
62992
|
}
|
|
62691
62993
|
out.params = params;
|
|
62692
62994
|
}
|
|
@@ -67349,7 +67651,7 @@ async function flattenSchematic(sheet, netlister) {
|
|
|
67349
67651
|
if (def.body.hierarchy) {
|
|
67350
67652
|
err9(`Sheet '${def.name}' body has its own hierarchy \u2014 nest sheets with \`instances\` instead (ignored)`);
|
|
67351
67653
|
}
|
|
67352
|
-
const
|
|
67654
|
+
const resolve9 = (name) => {
|
|
67353
67655
|
if (ports.has(name)) {
|
|
67354
67656
|
const bound = binding.get(name);
|
|
67355
67657
|
if (bound !== void 0) return bound;
|
|
@@ -67377,7 +67679,7 @@ async function flattenSchematic(sheet, netlister) {
|
|
|
67377
67679
|
if (pins.length === 0) continue;
|
|
67378
67680
|
if (isAutoName(name) && pins.length < 2) continue;
|
|
67379
67681
|
usedNames.add(name);
|
|
67380
|
-
const flat =
|
|
67682
|
+
const flat = resolve9(name);
|
|
67381
67683
|
if (!ports.has(name) && !globals.has(name) && rootNames.has(flat)) {
|
|
67382
67684
|
err9(
|
|
67383
67685
|
`Net scope collision: local net '${name}' of sheet instance '${path}' flattens to '${flat}', which the root sheet also uses`
|
|
@@ -67401,9 +67703,9 @@ async function flattenSchematic(sheet, netlister) {
|
|
|
67401
67703
|
warn(`Hierarchical port '${port}' of sheet '${def.name}' connects to nothing inside the sheet`);
|
|
67402
67704
|
}
|
|
67403
67705
|
}
|
|
67404
|
-
for (const flag of def.body.power_flags ?? []) outFlags.push({ ...flag, net:
|
|
67706
|
+
for (const flag of def.body.power_flags ?? []) outFlags.push({ ...flag, net: resolve9(flag.net) });
|
|
67405
67707
|
instances.push({ path, sheet: def.name, refs });
|
|
67406
|
-
await elaborateAll(def.instances ?? [], path, offset,
|
|
67708
|
+
await elaborateAll(def.instances ?? [], path, offset, resolve9, stack);
|
|
67407
67709
|
};
|
|
67408
67710
|
await elaborateAll(h.instances ?? [], "", { x: 0, y: 0 }, (n) => n, []);
|
|
67409
67711
|
const nets = {};
|
|
@@ -68958,12 +69260,12 @@ var init_enclosure = __esm({
|
|
|
68958
69260
|
// src/wasm/ecad-diff.ts
|
|
68959
69261
|
import { createRequire } from "node:module";
|
|
68960
69262
|
import { fileURLToPath } from "node:url";
|
|
68961
|
-
import { dirname as dirname3, resolve as
|
|
69263
|
+
import { dirname as dirname3, resolve as resolve5 } from "node:path";
|
|
68962
69264
|
function load() {
|
|
68963
69265
|
if (cached2 !== void 0) return cached2;
|
|
68964
69266
|
try {
|
|
68965
69267
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
68966
|
-
const pkg =
|
|
69268
|
+
const pkg = resolve5(here, "../../../../crates/vcad-ecad-diff-wasm/pkg/vcad_ecad_diff_wasm.js");
|
|
68967
69269
|
const req = createRequire(import.meta.url);
|
|
68968
69270
|
cached2 = req(pkg);
|
|
68969
69271
|
} catch {
|
|
@@ -82845,7 +83147,7 @@ import { execFile } from "child_process";
|
|
|
82845
83147
|
import {
|
|
82846
83148
|
existsSync as existsSync4,
|
|
82847
83149
|
mkdtempSync as mkdtempSync2,
|
|
82848
|
-
readdirSync as
|
|
83150
|
+
readdirSync as readdirSync3,
|
|
82849
83151
|
readFileSync as readFileSync8,
|
|
82850
83152
|
rmSync,
|
|
82851
83153
|
writeFileSync as writeFileSync5
|
|
@@ -82903,7 +83205,7 @@ function listEvalTasks(args) {
|
|
|
82903
83205
|
const tasksDir = join6(mechevalRoot, "tasks");
|
|
82904
83206
|
const suiteFilter = args.suite ? String(args.suite).toUpperCase() : null;
|
|
82905
83207
|
const tasks2 = [];
|
|
82906
|
-
for (const file of
|
|
83208
|
+
for (const file of readdirSync3(tasksDir).sort()) {
|
|
82907
83209
|
if (!file.endsWith(".json")) continue;
|
|
82908
83210
|
try {
|
|
82909
83211
|
const raw = JSON.parse(readFileSync8(join6(tasksDir, file), "utf8"));
|
|
@@ -87291,14 +87593,14 @@ var init_acoustics = __esm({
|
|
|
87291
87593
|
// src/tools/import.ts
|
|
87292
87594
|
import { createHash as createHash6 } from "node:crypto";
|
|
87293
87595
|
import { readFileSync as readFileSync9, existsSync as existsSync5, statSync as statSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
87294
|
-
import { basename, join as join7, resolve as
|
|
87596
|
+
import { basename, join as join7, resolve as resolve6, sep as sep3 } from "node:path";
|
|
87295
87597
|
function exportRoot() {
|
|
87296
87598
|
return process.env.VCAD_MCP_EXPORT_DIR ?? process.cwd();
|
|
87297
87599
|
}
|
|
87298
87600
|
function isWithinExportRoot(path) {
|
|
87299
87601
|
if (path.includes("\0")) return false;
|
|
87300
|
-
const root =
|
|
87301
|
-
const abs =
|
|
87602
|
+
const root = resolve6(exportRoot());
|
|
87603
|
+
const abs = resolve6(path);
|
|
87302
87604
|
return abs === root || abs.startsWith(root + sep3);
|
|
87303
87605
|
}
|
|
87304
87606
|
function stepNodePath(bytes, diskPath, label) {
|
|
@@ -87309,7 +87611,7 @@ function stepNodePath(bytes, diskPath, label) {
|
|
|
87309
87611
|
mkdirSync3(dir, { recursive: true });
|
|
87310
87612
|
const file = join7(dir, `${digest}.step`);
|
|
87311
87613
|
if (!existsSync5(file)) writeFileSync7(file, bytes);
|
|
87312
|
-
return { path:
|
|
87614
|
+
return { path: resolve6(file), portable: true };
|
|
87313
87615
|
} catch {
|
|
87314
87616
|
return { path: `step:${digest}/${label}`, portable: false };
|
|
87315
87617
|
}
|
|
@@ -87385,7 +87687,7 @@ function readStepInput(input) {
|
|
|
87385
87687
|
if (stat.size > MAX_STEP_BYTES) {
|
|
87386
87688
|
throw new Error(`STEP file exceeds ${MAX_STEP_BYTES} byte limit`);
|
|
87387
87689
|
}
|
|
87388
|
-
return { bytes: readFileSync9(filepath), diskPath:
|
|
87690
|
+
return { bytes: readFileSync9(filepath), diskPath: resolve6(filepath) };
|
|
87389
87691
|
}
|
|
87390
87692
|
function skippedFaceSummary(report, warnings) {
|
|
87391
87693
|
const skipped = report.reduce((sum, s) => sum + s.skipped_faces.length, 0);
|
|
@@ -87558,11 +87860,11 @@ var init_import = __esm({
|
|
|
87558
87860
|
|
|
87559
87861
|
// src/tools/import-urdf.ts
|
|
87560
87862
|
import { readFileSync as readFileSync10, existsSync as existsSync6, statSync as statSync3 } from "node:fs";
|
|
87561
|
-
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute5, resolve as
|
|
87863
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve7, sep as sep4 } from "node:path";
|
|
87562
87864
|
function resolveMeshPath(filename, urdfDir, packageRoots, confineRoot) {
|
|
87563
87865
|
const confined = (p) => {
|
|
87564
|
-
const abs =
|
|
87565
|
-
const root =
|
|
87866
|
+
const abs = resolve7(p);
|
|
87867
|
+
const root = resolve7(confineRoot);
|
|
87566
87868
|
const rootSep = root.endsWith(sep4) ? root : root + sep4;
|
|
87567
87869
|
if (abs !== root && !abs.startsWith(rootSep)) return null;
|
|
87568
87870
|
return existsSync6(abs) && statSync3(abs).isFile() ? abs : null;
|
|
@@ -87571,14 +87873,14 @@ function resolveMeshPath(filename, urdfDir, packageRoots, confineRoot) {
|
|
|
87571
87873
|
const pkgMatch = filename.match(/^package:\/\/([^/]+)\/?(.*)$/);
|
|
87572
87874
|
if (pkgMatch) {
|
|
87573
87875
|
for (const root of packageRoots) {
|
|
87574
|
-
const hit = confined(
|
|
87876
|
+
const hit = confined(resolve7(root, pkgMatch[1], pkgMatch[2]));
|
|
87575
87877
|
if (hit) return hit;
|
|
87576
87878
|
}
|
|
87577
87879
|
return null;
|
|
87578
87880
|
}
|
|
87579
87881
|
const stripped = filename.startsWith("file://") ? filename.slice("file://".length) : filename;
|
|
87580
87882
|
if (isAbsolute5(stripped)) return confined(stripped);
|
|
87581
|
-
if (urdfDir) return confined(
|
|
87883
|
+
if (urdfDir) return confined(resolve7(urdfDir, stripped));
|
|
87582
87884
|
return null;
|
|
87583
87885
|
}
|
|
87584
87886
|
function inlineMeshImports2(doc, engine, urdfDir, packageRoots, confineRoot) {
|
|
@@ -90821,13 +91123,13 @@ ${issues.map((i) => i.problem).join("\n")}`
|
|
|
90821
91123
|
}
|
|
90822
91124
|
async function hasFfmpeg() {
|
|
90823
91125
|
if (ffmpegAvailable !== null) return ffmpegAvailable;
|
|
90824
|
-
ffmpegAvailable = await new Promise((
|
|
91126
|
+
ffmpegAvailable = await new Promise((resolve9) => {
|
|
90825
91127
|
try {
|
|
90826
91128
|
const p = spawn("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
90827
|
-
p.on("error", () =>
|
|
90828
|
-
p.on("exit", (code) =>
|
|
91129
|
+
p.on("error", () => resolve9(false));
|
|
91130
|
+
p.on("exit", (code) => resolve9(code === 0));
|
|
90829
91131
|
} catch {
|
|
90830
|
-
|
|
91132
|
+
resolve9(false);
|
|
90831
91133
|
}
|
|
90832
91134
|
});
|
|
90833
91135
|
return ffmpegAvailable;
|
|
@@ -90866,13 +91168,13 @@ function startMp4Encoder(width, height, fps) {
|
|
|
90866
91168
|
const exitError = (code) => new Error(
|
|
90867
91169
|
`ffmpeg exited ${code}: ${Buffer.concat(errChunks).toString().slice(-400)}`
|
|
90868
91170
|
);
|
|
90869
|
-
const done = new Promise((
|
|
91171
|
+
const done = new Promise((resolve9, reject) => {
|
|
90870
91172
|
p.on("error", (e) => {
|
|
90871
91173
|
failure2 = e;
|
|
90872
91174
|
reject(e);
|
|
90873
91175
|
});
|
|
90874
91176
|
p.on("close", (code) => {
|
|
90875
|
-
if (code === 0)
|
|
91177
|
+
if (code === 0) resolve9(Buffer.concat(out));
|
|
90876
91178
|
else {
|
|
90877
91179
|
failure2 ??= exitError(code);
|
|
90878
91180
|
reject(failure2);
|
|
@@ -90883,15 +91185,15 @@ function startMp4Encoder(width, height, fps) {
|
|
|
90883
91185
|
});
|
|
90884
91186
|
return {
|
|
90885
91187
|
writeFrame(rgba) {
|
|
90886
|
-
return new Promise((
|
|
91188
|
+
return new Promise((resolve9, reject) => {
|
|
90887
91189
|
if (failure2) return reject(failure2);
|
|
90888
91190
|
if (p.exitCode !== null || p.stdin.destroyed) {
|
|
90889
91191
|
return reject(failure2 ?? exitError(p.exitCode));
|
|
90890
91192
|
}
|
|
90891
|
-
if (p.stdin.write(rgba)) return
|
|
91193
|
+
if (p.stdin.write(rgba)) return resolve9();
|
|
90892
91194
|
const onDrain = () => {
|
|
90893
91195
|
p.removeListener("close", onClose);
|
|
90894
|
-
|
|
91196
|
+
resolve9();
|
|
90895
91197
|
};
|
|
90896
91198
|
const onClose = () => {
|
|
90897
91199
|
p.stdin.removeListener("drain", onDrain);
|
|
@@ -92451,8 +92753,8 @@ var init_server3 = __esm({
|
|
|
92451
92753
|
resolveLoonAssets(doc, { base_dir }, engine);
|
|
92452
92754
|
});
|
|
92453
92755
|
PKG_VERSION = (() => {
|
|
92454
|
-
if ("0.11.0-main.
|
|
92455
|
-
return "0.11.0-main.
|
|
92756
|
+
if ("0.11.0-main.163") {
|
|
92757
|
+
return "0.11.0-main.163";
|
|
92456
92758
|
}
|
|
92457
92759
|
try {
|
|
92458
92760
|
const req = createRequire3(import.meta.url);
|
|
@@ -92461,8 +92763,8 @@ var init_server3 = __esm({
|
|
|
92461
92763
|
return "0.0.0";
|
|
92462
92764
|
}
|
|
92463
92765
|
})();
|
|
92464
|
-
BUILD_SHA = "
|
|
92465
|
-
BUILD_TIME = "2026-11-
|
|
92766
|
+
BUILD_SHA = "816ee12b085c12b403a35ebfedb6c276e175a3af";
|
|
92767
|
+
BUILD_TIME = "2026-11-11T21:40:44.212Z";
|
|
92466
92768
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
92467
92769
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
92468
92770
|
INSTANCE_ID = randomUUID5().slice(0, 8);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vcad/mcp",
|
|
3
|
-
"version": "0.11.0-main.
|
|
3
|
+
"version": "0.11.0-main.163",
|
|
4
4
|
"description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://github.com/muni-works/vcad",
|
|
21
21
|
"vcadBuild": {
|
|
22
|
-
"sha": "
|
|
23
|
-
"builtAt": "2026-11-
|
|
22
|
+
"sha": "816ee12b085c12b403a35ebfedb6c276e175a3af",
|
|
23
|
+
"builtAt": "2026-11-11T21:40:44.212Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -81,6 +81,28 @@ Call `server_info` first.
|
|
|
81
81
|
Tell the user and suggest restarting their agent (or updating vcad.app).
|
|
82
82
|
- `durable: false` → sessions won't survive a restart; keep the loon source.
|
|
83
83
|
|
|
84
|
+
## More than one vcad.app
|
|
85
|
+
|
|
86
|
+
Each running vcad.app registers itself in `~/.vcad/instances/<pid>.json`
|
|
87
|
+
(pid, port, open document, launch time); `~/.vcad/app.json` only points at
|
|
88
|
+
the newest. Every app-bridge result ends with `[vcad.app pid N · document]`
|
|
89
|
+
and carries `_meta["io.vcad/instance"]`: check it names the window you mean.
|
|
90
|
+
A `⚠ vcad.app CHANGED` notice means the app relaunched or another window took
|
|
91
|
+
over — re-check the open document before trusting earlier state.
|
|
92
|
+
|
|
93
|
+
Pick the window explicitly when more than one runs:
|
|
94
|
+
|
|
95
|
+
- `VCAD_APP_PID=<pid>` — that instance; fails loudly if it's gone.
|
|
96
|
+
- `VCAD_APP_URL=http://127.0.0.1:<port>/mcp` — that endpoint, verbatim.
|
|
97
|
+
- `VCAD_APP_DOCUMENT=<path>` — the instance with that file open.
|
|
98
|
+
- otherwise the newest, with a warning listing the others. The choice is
|
|
99
|
+
pinned for the client's lifetime; a later launch never steals it.
|
|
100
|
+
- `VCAD_APP=0` — `@vcad/mcp` doesn't link to any app.
|
|
101
|
+
|
|
102
|
+
**Never `pkill`/`killall` vcad.app by pattern.** Other sessions may be driving
|
|
103
|
+
other windows, and to them it looks like a crash. Quit only an instance you
|
|
104
|
+
launched, by its pid (`kill <pid>`, from `~/.vcad/instances/`).
|
|
105
|
+
|
|
84
106
|
## Ground rules
|
|
85
107
|
|
|
86
108
|
- vcad has no hosted services. Never send the user to a website to view,
|