@dmytro-prototypes/cadbridge-mcp 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/plugin.js +67 -5
- package/package.json +4 -4
package/bin/plugin.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// com.apple.quarantine nor Mark-of-the-Web, so nothing here is ever gated by
|
|
15
15
|
// Gatekeeper or SmartScreen.
|
|
16
16
|
import { createHash } from "node:crypto"
|
|
17
|
-
import { cpSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs"
|
|
17
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs"
|
|
18
18
|
import { homedir } from "node:os"
|
|
19
19
|
import { dirname, join, sep } from "node:path"
|
|
20
20
|
|
|
@@ -65,6 +65,46 @@ export function hashTree(root) {
|
|
|
65
65
|
return digest.digest("hex")
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// AutoCAD releases installed on this machine, newest first. LT is excluded
|
|
69
|
+
// deliberately: it shares the naming but cannot load ObjectARX modules at all,
|
|
70
|
+
// so counting it would report coverage that can never happen.
|
|
71
|
+
//
|
|
72
|
+
// Detection only ever adds information. The bundle is installed either way —
|
|
73
|
+
// it is inert without AutoCAD, and a false negative here (an install in a
|
|
74
|
+
// non-standard location) must not be able to withhold the plugin from someone
|
|
75
|
+
// who does have AutoCAD.
|
|
76
|
+
export function installedAutoCAD({ platform = process.platform, roots } = {}) {
|
|
77
|
+
const search = roots ?? (platform === "win32"
|
|
78
|
+
? [process.env["ProgramFiles"] || "C:\\Program Files", process.env["ProgramW6432"]].filter(Boolean).map(p => join(p, "Autodesk"))
|
|
79
|
+
: ["/Applications/Autodesk"])
|
|
80
|
+
|
|
81
|
+
const found = new Set()
|
|
82
|
+
for (const root of search) {
|
|
83
|
+
let entries
|
|
84
|
+
try {
|
|
85
|
+
entries = readdirSync(root, { withFileTypes: true })
|
|
86
|
+
} catch {
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
// "AutoCAD 2026" yes, "AutoCAD LT 2026" no.
|
|
91
|
+
const match = /^AutoCAD (\d{4})$/.exec(entry.name)
|
|
92
|
+
if (match) found.add(match[1])
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...found].sort().reverse()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Which AutoCAD releases the bundle about to be installed actually serves.
|
|
99
|
+
// Read out of the manifest itself rather than from plugin/series.json, which
|
|
100
|
+
// is a build-time file and is not shipped: the manifest is both what AutoCAD
|
|
101
|
+
// obeys and the only copy that reaches a user's machine. Its per-release
|
|
102
|
+
// Components blocks are generated by scripts/package-contents.mjs, which names
|
|
103
|
+
// the release in each Description.
|
|
104
|
+
export function coveredReleases(manifest) {
|
|
105
|
+
return [...manifest.matchAll(/<Components Description="CADBridge for AutoCAD (\d{4})"/g)].map(([, release]) => release)
|
|
106
|
+
}
|
|
107
|
+
|
|
68
108
|
// Beside PackageContents.xml rather than inside Contents/: the signed bundle
|
|
69
109
|
// is nested one level down, and adding a file inside it would invalidate the
|
|
70
110
|
// seal that Gatekeeper and AutoCAD both check.
|
|
@@ -89,6 +129,9 @@ export function installPlugin(binary, {
|
|
|
89
129
|
home = homedir(),
|
|
90
130
|
appData = process.env.APPDATA || join(home, "AppData", "Roaming"),
|
|
91
131
|
env = process.env,
|
|
132
|
+
// Injectable so a test does not depend on what AutoCAD happens to be
|
|
133
|
+
// installed on the machine running it.
|
|
134
|
+
autocad,
|
|
92
135
|
} = {}) {
|
|
93
136
|
if (env.CADBRIDGE_SKIP_PLUGIN_INSTALL) return { status: "skipped" }
|
|
94
137
|
|
|
@@ -97,10 +140,12 @@ export function installPlugin(binary, {
|
|
|
97
140
|
|
|
98
141
|
const source = pluginSource(binary)
|
|
99
142
|
let version
|
|
143
|
+
let manifest
|
|
100
144
|
try {
|
|
101
145
|
// PackageContents.xml is the file AutoCAD itself reads, so its absence
|
|
102
146
|
// means there is no loadable bundle here whatever else was shipped.
|
|
103
|
-
|
|
147
|
+
manifest = readFileSync(join(source, "PackageContents.xml"), "utf8")
|
|
148
|
+
version = /AppVersion="([^"]*)"/.exec(manifest)?.[1] ?? "unknown"
|
|
104
149
|
} catch {
|
|
105
150
|
// Older platform packages shipped the server alone. Nothing to install,
|
|
106
151
|
// and nothing worth saying about it.
|
|
@@ -109,7 +154,8 @@ export function installPlugin(binary, {
|
|
|
109
154
|
|
|
110
155
|
const destination = join(root, "CADBridge.bundle")
|
|
111
156
|
const digest = hashTree(source)
|
|
112
|
-
|
|
157
|
+
const covers = { manifest, installedAutoCAD: autocad ?? installedAutoCAD({ platform }) }
|
|
158
|
+
if (installed(destination)?.sha256 === digest) return { status: "current", version, destination, ...covers }
|
|
113
159
|
|
|
114
160
|
mkdirSync(root, { recursive: true })
|
|
115
161
|
// One temp directory holds both the new tree being assembled and the old one
|
|
@@ -151,14 +197,14 @@ export function installPlugin(binary, {
|
|
|
151
197
|
}
|
|
152
198
|
throw error
|
|
153
199
|
}
|
|
154
|
-
return { status: "installed", version, destination }
|
|
200
|
+
return { status: "installed", version, destination, ...covers }
|
|
155
201
|
} catch (error) {
|
|
156
202
|
// AutoCAD maps CADBridge.arx for the whole session (PackageContents.xml
|
|
157
203
|
// sets LoadOnAutoCADStartup), and Windows will not let a mapped module be
|
|
158
204
|
// renamed or deleted. That is the ordinary case of "the user started
|
|
159
205
|
// AutoCAD first", not a failure worth refusing to start the server over.
|
|
160
206
|
const status = !stranded && ["EBUSY", "EPERM", "EACCES", "ENOTEMPTY"].includes(error.code) ? "locked" : "failed"
|
|
161
|
-
return { status, version, destination, stranded, error }
|
|
207
|
+
return { status, version, destination, stranded, error, ...covers }
|
|
162
208
|
} finally {
|
|
163
209
|
if (!stranded) rmSync(staging, { recursive: true, force: true })
|
|
164
210
|
}
|
|
@@ -167,6 +213,22 @@ export function installPlugin(binary, {
|
|
|
167
213
|
// stdout belongs to the MCP protocol; every byte here goes to stderr, which
|
|
168
214
|
// clients surface in their server logs.
|
|
169
215
|
export function reportPluginInstall(result, write = message => process.stderr.write(`${message}\n`)) {
|
|
216
|
+
// An AutoCAD that is installed but that the bundle does not serve is the one
|
|
217
|
+
// thing the user cannot discover for themselves: AutoCAD ignores a
|
|
218
|
+
// non-matching Components block in silence, so CADBridge would simply never
|
|
219
|
+
// appear there with no clue why. Reported on any status, because the bundle
|
|
220
|
+
// being already current does not make the gap go away.
|
|
221
|
+
if (result.manifest && result.installedAutoCAD?.length) {
|
|
222
|
+
const covered = coveredReleases(result.manifest)
|
|
223
|
+
const missing = result.installedAutoCAD.filter(release => !covered.includes(release))
|
|
224
|
+
if (missing.length) {
|
|
225
|
+
write(
|
|
226
|
+
`CADBridge: no plugin build for AutoCAD ${missing.join(", ")} — ` +
|
|
227
|
+
`this bundle serves ${covered.join(", ") || "no installed release"}. ` +
|
|
228
|
+
`CADBridge will not load there.`,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
170
232
|
if (result.status === "installed") {
|
|
171
233
|
// The plugin's own version, which is not the server's and not this npm
|
|
172
234
|
// package's — the three are released independently and only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmytro-prototypes/cadbridge-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"mcpName": "net.dmytro-prototypes/cadbridge-mcp-autocad",
|
|
5
5
|
"description": "Drive a live AutoCAD session from an AI client through MCP",
|
|
6
6
|
"type": "module",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"LICENSE.txt"
|
|
31
31
|
],
|
|
32
32
|
"optionalDependencies": {
|
|
33
|
-
"@dmytro-prototypes/cadbridge-mcp-darwin-arm64": "0.1.
|
|
34
|
-
"@dmytro-prototypes/cadbridge-mcp-win32-x64": "0.1.
|
|
33
|
+
"@dmytro-prototypes/cadbridge-mcp-darwin-arm64": "0.1.14",
|
|
34
|
+
"@dmytro-prototypes/cadbridge-mcp-win32-x64": "0.1.14"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"cadbridge": {
|
|
40
40
|
"server": "0.1.11",
|
|
41
|
-
"plugin": "0.1.
|
|
41
|
+
"plugin": "0.1.12"
|
|
42
42
|
}
|
|
43
43
|
}
|