@vantaloom/cli 0.15.36 → 0.15.37

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.
@@ -151,7 +151,7 @@ export async function applyPackage(packageRoot, prefix, options) {
151
151
 
152
152
  // Copy package contents to install prefix
153
153
  const optionalDirs = new Set()
154
- for (const name of ["bin", "web", "cli"]) {
154
+ for (const name of ["bin", "web", "cli", "skills"]) {
155
155
  const src = path.join(packageRoot, name)
156
156
  const dst = path.join(prefix, name)
157
157
  if (!existsSync(src)) {
@@ -243,7 +243,7 @@ export async function applyPackage(packageRoot, prefix, options) {
243
243
  }
244
244
 
245
245
  export function assertRuntimePackage(packageRoot) {
246
- for (const name of ["bin", "web", "cli", "manifest.json"]) {
246
+ for (const name of ["bin", "web", "cli", "skills", "manifest.json"]) {
247
247
  if (!existsSync(path.join(packageRoot, name))) {
248
248
  throw new Error(`invalid Vantaloom package, missing ${name}: ${packageRoot}`)
249
249
  }
@@ -352,6 +352,19 @@ export function assertSourceRoot(sourceRoot) {
352
352
  return sourceRoot
353
353
  }
354
354
 
355
+ // copyAgentSkills 把仓库里的 agent skill 打进 runtime 包。skill 是给外部 agent
356
+ // (Claude Code / Cursor / Codex)读的使用手册,"vantaloom mcp install --skill"
357
+ // 从这里取——装了 runtime 的机器上通常没有源码仓库,不随包分发就等于没有。
358
+ export async function copyAgentSkills(sourceRoot, buildSkills) {
359
+ const src = path.join(sourceRoot, "integrations", "agent-skills")
360
+ if (!existsSync(src)) {
361
+ console.error(" warning: integrations/agent-skills not found; runtime package ships without skills")
362
+ return
363
+ }
364
+ removeKnownPath(buildSkills, path.dirname(buildSkills))
365
+ await copyDir(src, buildSkills)
366
+ }
367
+
355
368
  export async function copyStaticWeb(sourceRoot, buildWeb) {
356
369
  const exportRoot = path.join(sourceRoot, "apps", "vantaloom", "out")
357
370
  if (!existsSync(exportRoot)) {
@@ -464,7 +477,7 @@ export function writeRuntimePackageMetadata(packageRoot, sourceRoot, platform) {
464
477
  type: "module",
465
478
  os: [runtimeOS],
466
479
  cpu: [cpu],
467
- files: ["bin", "web", "cli", "manifest.json", "VERSION", "README.md"],
480
+ files: ["bin", "web", "cli", "skills", "manifest.json", "VERSION", "README.md"],
468
481
  publishConfig: {
469
482
  access: "public",
470
483
  },
@@ -0,0 +1,264 @@
1
+ // vantaloom mcp —— 把跨机执行信道(vantaloom-mcp)注册给外部 agent。
2
+ //
3
+ // 为什么需要这条命令:MCP 服务端是随 runtime 分发的一个二进制,装在
4
+ // <prefix>/bin/ 下。要用它,用户(或一个 agent)得先知道那个绝对路径,再手动
5
+ // 编辑三家客户端各自格式不同的配置文件——这一步足以劝退绝大多数人,也让「让
6
+ // 另一个 agent 自己接上」变成一次寻宝游戏。这条命令把路径与配置都算好,能自动
7
+ // 写的就直接写。
8
+ //
9
+ // 三家客户端的配置位置与格式(都用「读—合并—原子写」,先备份):
10
+ // - Claude Code:~/.claude.json JSON,mcpServers 顶层键
11
+ // - Cursor: ~/.cursor/mcp.json JSON,mcpServers 顶层键
12
+ // - Codex: ~/.codex/config.toml TOML,[mcp_servers.<name>] 段
13
+
14
+ import {
15
+ copyFileSync,
16
+ cpSync,
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ renameSync,
21
+ rmSync,
22
+ writeFileSync,
23
+ } from "node:fs"
24
+ import os from "node:os"
25
+ import path from "node:path"
26
+ import { binaryName, defaultPrefix, safeDirectory } from "./platform.mjs"
27
+
28
+ // serverName 是注册进各客户端的 MCP 服务端名字,与 skill 同名以便对应。
29
+ const serverName = "vantaloom-fleet"
30
+
31
+ // mcpBinaryPath 返回本机 MCP 服务端的绝对路径(不校验存在性)。
32
+ export function mcpBinaryPath(prefix) {
33
+ return path.join(safeDirectory(prefix ?? defaultPrefix()), "bin", binaryName("vantaloom-mcp"))
34
+ }
35
+
36
+ // clientTargets 描述三家客户端的配置落点。
37
+ function clientTargets() {
38
+ const home = os.homedir()
39
+ return {
40
+ claude: { label: "Claude Code", file: path.join(home, ".claude.json"), kind: "json" },
41
+ cursor: { label: "Cursor", file: path.join(home, ".cursor", "mcp.json"), kind: "json" },
42
+ codex: { label: "Codex", file: path.join(home, ".codex", "config.toml"), kind: "toml" },
43
+ }
44
+ }
45
+
46
+ // serverEntry 是 JSON 客户端的服务端条目。env 里显式写上 VANTALOOM_API:本机
47
+ // 换过端口时用户改一处就够,不必去猜默认值。
48
+ function serverEntry(binary, apiBase) {
49
+ return { command: binary, env: { VANTALOOM_API: apiBase } }
50
+ }
51
+
52
+ // tomlBlock 是 Codex 的 TOML 段(它的配置不是 JSON,得单独拼)。
53
+ function tomlBlock(binary, apiBase) {
54
+ const escaped = binary.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
55
+ return [
56
+ `[mcp_servers.${serverName}]`,
57
+ `command = "${escaped}"`,
58
+ `args = []`,
59
+ ``,
60
+ `[mcp_servers.${serverName}.env]`,
61
+ `VANTALOOM_API = "${apiBase}"`,
62
+ ``,
63
+ ].join("\n")
64
+ }
65
+
66
+ // writeAtomic 先落临时文件再 rename,避免写到一半把用户的配置写坏。
67
+ function writeAtomic(file, content) {
68
+ mkdirSync(path.dirname(file), { recursive: true })
69
+ const tmp = `${file}.vantaloom-tmp`
70
+ writeFileSync(tmp, content, "utf8")
71
+ renameSync(tmp, file)
72
+ }
73
+
74
+ // backupOnce 在首次改动前留一份 .bak(已存在则不覆盖——保住最原始那份)。
75
+ function backupOnce(file) {
76
+ if (!existsSync(file)) return
77
+ const bak = `${file}.vantaloom-bak`
78
+ if (existsSync(bak)) return
79
+ try {
80
+ copyFileSync(file, bak)
81
+ } catch {}
82
+ }
83
+
84
+ // installJSONClient 把服务端条目合并进 JSON 配置的 mcpServers。
85
+ function installJSONClient(target, binary, apiBase, force) {
86
+ let config = {}
87
+ if (existsSync(target.file)) {
88
+ const raw = readFileSync(target.file, "utf8").trim()
89
+ if (raw) {
90
+ try {
91
+ config = JSON.parse(raw)
92
+ } catch (error) {
93
+ // 解析不了就停手:这是用户的配置文件,宁可让他自己看一眼,也不能拿一个
94
+ // 空对象覆盖掉(那会静默清空 Claude Code 的全部本地状态)。
95
+ return { ok: false, detail: `配置文件不是合法 JSON,未改动:${error.message}` }
96
+ }
97
+ }
98
+ }
99
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
100
+ return { ok: false, detail: "配置文件顶层不是对象,未改动" }
101
+ }
102
+ const servers = config.mcpServers && typeof config.mcpServers === "object" ? config.mcpServers : {}
103
+ const existing = servers[serverName]
104
+ if (existing && !force) {
105
+ const same = existing.command === binary
106
+ return {
107
+ ok: true,
108
+ detail: same ? "已注册(未改动)" : `已存在同名条目且路径不同(${existing.command})——加 --force 覆盖`,
109
+ skipped: true,
110
+ }
111
+ }
112
+ servers[serverName] = serverEntry(binary, apiBase)
113
+ config.mcpServers = servers
114
+ backupOnce(target.file)
115
+ writeAtomic(target.file, `${JSON.stringify(config, null, 2)}\n`)
116
+ return { ok: true, detail: existing ? "已更新" : "已注册" }
117
+ }
118
+
119
+ // installTOMLClient 往 Codex 的 config.toml 追加一段。TOML 只做「不存在才追加」:
120
+ // 就地改写 TOML 需要一个完整的解析器,为了一段配置引入那种复杂度不划算,而
121
+ // 追加对合法 TOML 恒安全。
122
+ function installTOMLClient(target, binary, apiBase, force) {
123
+ let existing = ""
124
+ if (existsSync(target.file)) {
125
+ existing = readFileSync(target.file, "utf8")
126
+ }
127
+ if (existing.includes(`[mcp_servers.${serverName}]`)) {
128
+ if (!force) {
129
+ return { ok: true, detail: "已注册(未改动)", skipped: true }
130
+ }
131
+ return {
132
+ ok: false,
133
+ detail: `已存在 [mcp_servers.${serverName}] 段,请手动编辑 ${target.file}(TOML 就地改写不做自动合并)`,
134
+ }
135
+ }
136
+ backupOnce(target.file)
137
+ const prefix = existing && !existing.endsWith("\n") ? "\n" : ""
138
+ writeAtomic(target.file, `${existing}${prefix}\n${tomlBlock(binary, apiBase)}`)
139
+ return { ok: true, detail: "已注册" }
140
+ }
141
+
142
+ // installSkill 把 vantaloom-fleet skill 复制到 Claude Code 的技能目录。
143
+ function installSkill(prefix) {
144
+ const src = path.join(safeDirectory(prefix ?? defaultPrefix()), "skills", serverName)
145
+ if (!existsSync(src)) {
146
+ return { ok: false, detail: `runtime 未附带 skill(缺 ${src})——需要 ≥0.15.37 的 runtime` }
147
+ }
148
+ const dst = path.join(os.homedir(), ".claude", "skills", serverName)
149
+ mkdirSync(path.dirname(dst), { recursive: true })
150
+ rmSync(dst, { recursive: true, force: true })
151
+ cpSync(src, dst, { recursive: true, force: true })
152
+ return { ok: true, detail: dst }
153
+ }
154
+
155
+ // runMcp 是 "vantaloom mcp [path|print|install]" 的入口。
156
+ export async function runMcp(subcommand, options) {
157
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
158
+ const binary = mcpBinaryPath(prefix)
159
+ const apiBase = (options.api ?? "http://127.0.0.1:8780").replace(/\/+$/, "")
160
+ const action = subcommand ?? "status"
161
+
162
+ if (action === "path") {
163
+ console.log(binary)
164
+ return
165
+ }
166
+
167
+ if (action === "print") {
168
+ printSnippets(binary, apiBase)
169
+ return
170
+ }
171
+
172
+ if (action === "status") {
173
+ printStatus(binary, apiBase, prefix)
174
+ return
175
+ }
176
+
177
+ if (action !== "install") {
178
+ throw new Error(`unknown "vantaloom mcp" subcommand "${action}" (expected: status|path|print|install)`)
179
+ }
180
+
181
+ if (!existsSync(binary)) {
182
+ throw new Error(
183
+ `未找到 MCP 服务端:${binary}\n` +
184
+ `请先安装/更新 runtime(vantaloom update),或用 --prefix 指定安装目录。`
185
+ )
186
+ }
187
+
188
+ const targets = clientTargets()
189
+ const requested = String(options.client ?? "all").toLowerCase()
190
+ const names = requested === "all" ? Object.keys(targets) : requested.split(",").map((s) => s.trim())
191
+
192
+ console.log(`MCP 服务端:${binary}`)
193
+ for (const name of names) {
194
+ const target = targets[name]
195
+ if (!target) {
196
+ console.error(` ${name}: 未知客户端(可选 claude / cursor / codex / all)`)
197
+ continue
198
+ }
199
+ let result
200
+ try {
201
+ result =
202
+ target.kind === "json"
203
+ ? installJSONClient(target, binary, apiBase, Boolean(options.force))
204
+ : installTOMLClient(target, binary, apiBase, Boolean(options.force))
205
+ } catch (error) {
206
+ result = { ok: false, detail: error.message }
207
+ }
208
+ const mark = result.ok ? (result.skipped ? "•" : "✓") : "✗"
209
+ console.log(` ${mark} ${target.label}: ${result.detail} (${target.file})`)
210
+ }
211
+
212
+ if (options.skill !== false) {
213
+ const skill = installSkill(prefix)
214
+ console.log(` ${skill.ok ? "✓" : "✗"} skill: ${skill.detail}`)
215
+ }
216
+
217
+ console.log(
218
+ `\n重启对应客户端后,让它「列出我的 Vantaloom 机器」即可验证(会调用 machines_list)。` +
219
+ `\n注意:MCP 服务端由客户端拉起,与 Vantaloom 运行时各自独立——但它需要本机运行时在跑。`
220
+ )
221
+ }
222
+
223
+ function printStatus(binary, apiBase, prefix) {
224
+ const installed = existsSync(binary)
225
+ console.log(`MCP 服务端:${binary}${installed ? "" : " (未找到——先跑 vantaloom update)"}`)
226
+ console.log(`本机 API: ${apiBase}`)
227
+ console.log(`skill: ${path.join(prefix, "skills", serverName)}`)
228
+ console.log("")
229
+ const targets = clientTargets()
230
+ for (const [, target] of Object.entries(targets)) {
231
+ let state = "未注册"
232
+ if (existsSync(target.file)) {
233
+ const raw = readFileSync(target.file, "utf8")
234
+ if (target.kind === "json") {
235
+ try {
236
+ const parsed = JSON.parse(raw || "{}")
237
+ if (parsed?.mcpServers?.[serverName]) state = "已注册"
238
+ } catch {
239
+ state = "配置文件无法解析"
240
+ }
241
+ } else if (raw.includes(`[mcp_servers.${serverName}]`)) {
242
+ state = "已注册"
243
+ }
244
+ } else {
245
+ state = "未找到配置文件"
246
+ }
247
+ console.log(` ${target.label.padEnd(12)} ${state} (${target.file})`)
248
+ }
249
+ console.log(`\n一键注册:vantaloom mcp install (全部客户端 + skill)`)
250
+ console.log(`单独注册:vantaloom mcp install --client cursor`)
251
+ }
252
+
253
+ function printSnippets(binary, apiBase) {
254
+ const jsonSnippet = JSON.stringify({ mcpServers: { [serverName]: serverEntry(binary, apiBase) } }, null, 2)
255
+ console.log("# Claude Code (~/.claude.json) / Cursor (~/.cursor/mcp.json)")
256
+ console.log(jsonSnippet)
257
+ console.log("\n# Codex (~/.codex/config.toml)")
258
+ console.log(tomlBlock(binary, apiBase))
259
+ }
260
+
261
+ // mcpHint 是 install/update 结束时打印的一行指引。
262
+ export function mcpHint(prefix) {
263
+ return `提示:让 Claude Code / Cursor / Codex 驱动这台机器与整个组网 —— vantaloom mcp install(MCP 服务端在 ${mcpBinaryPath(prefix)})`
264
+ }
@@ -1,56 +1,61 @@
1
- import { mkdirSync } from "node:fs"
2
- import path from "node:path"
3
- import { platformId, removeKnownPath, runPnpm } from "./platform.mjs"
4
- import {
5
- buildGo,
6
- copyStaticWeb,
7
- copyCliDirectory,
8
- writeBuildManifest,
9
- writeRuntimePackageMetadata,
10
- runtimeConfigFromSource,
11
- gitVersion,
12
- npmPackageVersion,
13
- } from "./install.mjs"
14
-
15
- export async function buildRuntimePackage(sourceRoot, packageRoot, options) {
16
- // Every build npm release AND local/dev — stamps VERSION/manifest with the
17
- // npm package version. There is exactly ONE product version (the npm semver);
18
- // the git hash is recorded separately as manifest `commit` for diagnostics.
19
- // History: pre-0.13.5 local builds stamped the git hash into VERSION, which
20
- // split the version universe in two (the desktop shell read VERSION, the
21
- // settings page read cli/package.json) and broke update prompts both ways.
22
- const version = npmPackageVersion(sourceRoot)
23
- const commit = gitVersion(sourceRoot)
24
- const platform = options.target ?? platformId()
25
- const buildBin = path.join(packageRoot, "bin")
26
- const buildWeb = path.join(packageRoot, "web")
27
-
28
- removeKnownPath(packageRoot, path.dirname(packageRoot))
29
- mkdirSync(buildBin, { recursive: true })
30
-
31
- buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
32
- buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
33
- buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
34
- // vantaloom-mcp:跨机执行信道的 MCP 服务端(agent-bridge)。随 runtime bin 分发,
35
- // 外部 agent(Claude Code / Codex / Cursor)配置指向它即可驱动整个组网。
36
- buildGo(sourceRoot, buildBin, "vantaloom-mcp", platform)
37
- // 0.14.26: the browser moved into the official optional PLUGIN
38
- // (@vantaloom/browser-plugin-<platform>, built by scripts/build-browser-plugin.ps1)
39
- // the runtime no longer builds the vantaloom-browser sidecar nor bundles the
40
- // Obscura engine. The Windows system-tray app (vantaloom-tray) was removed
41
- // earlier it crash-looped on some Windows 11 builds. Neither is built here.
42
-
43
- if (options.buildWeb) {
44
- runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
45
- }
46
-
47
- await copyStaticWeb(sourceRoot, buildWeb)
48
- await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
49
- writeBuildManifest(packageRoot, version, platform, commit)
50
-
51
- return { platform, version }
52
- }
53
-
54
- // copyObscura was removed in 0.14.26: the Obscura engine ships inside the
55
- // official browser PLUGIN packages (scripts/build-browser-plugin.ps1), not the
56
- // runtime.
1
+ import { mkdirSync } from "node:fs"
2
+ import path from "node:path"
3
+ import { platformId, removeKnownPath, runPnpm } from "./platform.mjs"
4
+ import {
5
+ buildGo,
6
+ copyStaticWeb,
7
+ copyAgentSkills,
8
+ copyCliDirectory,
9
+ writeBuildManifest,
10
+ writeRuntimePackageMetadata,
11
+ runtimeConfigFromSource,
12
+ gitVersion,
13
+ npmPackageVersion,
14
+ } from "./install.mjs"
15
+
16
+ export async function buildRuntimePackage(sourceRoot, packageRoot, options) {
17
+ // Every build npm release AND local/dev stamps VERSION/manifest with the
18
+ // npm package version. There is exactly ONE product version (the npm semver);
19
+ // the git hash is recorded separately as manifest `commit` for diagnostics.
20
+ // History: pre-0.13.5 local builds stamped the git hash into VERSION, which
21
+ // split the version universe in two (the desktop shell read VERSION, the
22
+ // settings page read cli/package.json) and broke update prompts both ways.
23
+ const version = npmPackageVersion(sourceRoot)
24
+ const commit = gitVersion(sourceRoot)
25
+ const platform = options.target ?? platformId()
26
+ const buildBin = path.join(packageRoot, "bin")
27
+ const buildWeb = path.join(packageRoot, "web")
28
+
29
+ removeKnownPath(packageRoot, path.dirname(packageRoot))
30
+ mkdirSync(buildBin, { recursive: true })
31
+
32
+ buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
33
+ buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
34
+ buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
35
+ // vantaloom-mcp:跨机执行信道的 MCP 服务端(agent-bridge)。随 runtime bin 分发,
36
+ // 外部 agent(Claude Code / Codex / Cursor)配置指向它即可驱动整个组网。
37
+ buildGo(sourceRoot, buildBin, "vantaloom-mcp", platform)
38
+ // 0.14.26: the browser moved into the official optional PLUGIN
39
+ // (@vantaloom/browser-plugin-<platform>, built by scripts/build-browser-plugin.ps1)
40
+ // the runtime no longer builds the vantaloom-browser sidecar nor bundles the
41
+ // Obscura engine. The Windows system-tray app (vantaloom-tray) was removed
42
+ // earlier — it crash-looped on some Windows 11 builds. Neither is built here.
43
+
44
+ if (options.buildWeb) {
45
+ runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
46
+ }
47
+
48
+ await copyStaticWeb(sourceRoot, buildWeb)
49
+ await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
50
+ // agent skill:外部 agent(Claude Code 等)用来学会怎么用 vantaloom-mcp。
51
+ // runtime 分发,"vantaloom mcp install --skill" 才有东西可装——否则用户
52
+ // 得先去仓库里找它,而装了 runtime 的机器上多半没有仓库。
53
+ await copyAgentSkills(sourceRoot, path.join(packageRoot, "skills"))
54
+ writeBuildManifest(packageRoot, version, platform, commit)
55
+
56
+ return { platform, version }
57
+ }
58
+
59
+ // copyObscura was removed in 0.14.26: the Obscura engine ships inside the
60
+ // official browser PLUGIN packages (scripts/build-browser-plugin.ps1), not the
61
+ // runtime.