@vantaloom/runtime-win32-x64 0.1.24 → 0.2.0

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.
Files changed (35) hide show
  1. package/VERSION +1 -1
  2. package/bin/vantaloom-agent.exe +0 -0
  3. package/bin/vantaloom-api.exe +0 -0
  4. package/bin/vantaloom-tray.exe +0 -0
  5. package/bin/vantaloomctl.exe +0 -0
  6. package/cli/README.md +12 -12
  7. package/cli/bin/vantaloom.mjs +9 -9
  8. package/cli/config.json +2 -1
  9. package/cli/package.json +22 -22
  10. package/cli/src/cli.mjs +917 -878
  11. package/manifest.json +4 -3
  12. package/package.json +1 -1
  13. package/web/404.html +1 -1
  14. package/web/__next.__PAGE__.txt +2 -2
  15. package/web/__next._full.txt +3 -3
  16. package/web/__next._head.txt +1 -1
  17. package/web/__next._index.txt +2 -2
  18. package/web/__next._tree.txt +2 -2
  19. package/web/_next/static/chunks/1da7f8d27cd96c86.js +52 -0
  20. package/web/_next/static/chunks/c0c4db6d45c7ff65.css +2 -0
  21. package/web/_not-found/__next._full.txt +2 -2
  22. package/web/_not-found/__next._head.txt +1 -1
  23. package/web/_not-found/__next._index.txt +2 -2
  24. package/web/_not-found/__next._not-found/__PAGE__.txt +1 -1
  25. package/web/_not-found/__next._not-found.txt +1 -1
  26. package/web/_not-found/__next._tree.txt +2 -2
  27. package/web/_not-found.html +1 -1
  28. package/web/_not-found.txt +2 -2
  29. package/web/index.html +2 -2
  30. package/web/index.txt +3 -3
  31. package/web/_next/static/chunks/2743615d9a466b92.css +0 -2
  32. package/web/_next/static/chunks/bb819ea11a2befeb.js +0 -49
  33. /package/web/_next/static/{xJ0OUDbsX5zVbwpnlZg9k → qDb937Fsr8zR_VHDxrNf_}/_buildManifest.js +0 -0
  34. /package/web/_next/static/{xJ0OUDbsX5zVbwpnlZg9k → qDb937Fsr8zR_VHDxrNf_}/_clientMiddlewareManifest.json +0 -0
  35. /package/web/_next/static/{xJ0OUDbsX5zVbwpnlZg9k → qDb937Fsr8zR_VHDxrNf_}/_ssgManifest.js +0 -0
package/cli/src/cli.mjs CHANGED
@@ -1,878 +1,917 @@
1
- import { execFileSync, spawnSync } from "node:child_process"
2
- import {
3
- chmodSync,
4
- existsSync,
5
- mkdirSync,
6
- mkdtempSync,
7
- readdirSync,
8
- readFileSync,
9
- rmSync,
10
- writeFileSync,
11
- } from "node:fs"
12
- import { cp, writeFile } from "node:fs/promises"
13
- import os from "node:os"
14
- import path from "node:path"
15
- import { fileURLToPath } from "node:url"
16
-
17
- const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..")
18
- const repoCandidate = path.resolve(cliRoot, "..", "..")
19
- const installedConfigPath = path.join(cliRoot, "config.json")
20
- const defaultReleaseTag = "runtime-latest"
21
- const defaultRepo = "Timefiles404/Vantaloom-next"
22
- const defaultNpmRegistry = "https://registry.npmjs.org"
23
-
24
- export async function main(argv) {
25
- const command = argv[0] ?? "help"
26
- const options = parseOptions(argv.slice(1))
27
-
28
- switch (command) {
29
- case "install":
30
- if (options.package) {
31
- await installFromPackage({ ...options, update: false })
32
- } else if (shouldUseSourceInstall(options)) {
33
- await installFromSource(options)
34
- } else if (shouldUseReleaseSync(options)) {
35
- await syncFromRelease(options, "install")
36
- } else {
37
- await syncFromNpmRegistry(options, "install")
38
- }
39
- return
40
- case "update":
41
- if (options.package) {
42
- await installFromPackage({ ...options, update: true })
43
- } else if (shouldUseSourceInstall(options)) {
44
- await installFromSource({ ...options, update: true })
45
- } else if (shouldUseReleaseSync(options)) {
46
- await syncFromRelease(options, "update")
47
- } else {
48
- await syncFromNpmRegistry(options, "update")
49
- }
50
- return
51
- case "package":
52
- await packageRuntime(options)
53
- return
54
- case "platform":
55
- console.log(platformId())
56
- return
57
- case "start":
58
- case "stop":
59
- case "restart":
60
- case "status":
61
- case "ports":
62
- runCtl(command, options)
63
- return
64
- case "path":
65
- printPaths(options)
66
- return
67
- case "help":
68
- case "-h":
69
- case "--help":
70
- printHelp()
71
- return
72
- default:
73
- throw new Error(`unknown command "${command}"`)
74
- }
75
- }
76
-
77
- async function installFromSource(options) {
78
- const sourceRoot = findSourceRoot(options.source)
79
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
80
- const buildRoot = path.join(sourceRoot, "artifacts", "local-install", platformId())
81
-
82
- const { version } = await buildRuntimePackage(sourceRoot, buildRoot, {
83
- buildWeb: options.buildWeb,
84
- })
85
-
86
- await applyPackage(buildRoot, prefix, {
87
- noStart: options.noStart,
88
- sourceRoot,
89
- update: options.update,
90
- })
91
-
92
- console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
93
- console.log(`version: ${version}`)
94
- console.log(`run: ${displayCommand(prefix)} status`)
95
- }
96
-
97
- async function installFromPackage(options) {
98
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
99
- const packageRoot = safeDirectory(options.package)
100
- const version = await applyPackage(packageRoot, prefix, {
101
- noStart: options.noStart,
102
- update: options.update,
103
- })
104
-
105
- console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
106
- console.log(`version: ${version}`)
107
- console.log(`run: ${displayCommand(prefix)} status`)
108
- }
109
-
110
- async function packageRuntime(options) {
111
- const sourceRoot = findSourceRoot(options.source)
112
- const packageRoot = safeDirectory(
113
- options.output ?? path.join(sourceRoot, "artifacts", "packages", `vantaloom-${platformId()}`)
114
- )
115
- const { platform: builtPlatform, version } = await buildRuntimePackage(sourceRoot, packageRoot, {
116
- buildWeb: options.buildWeb,
117
- })
118
-
119
- if (options.npmPackage) {
120
- writeRuntimePackageMetadata(packageRoot, sourceRoot, builtPlatform)
121
- }
122
-
123
- if (options.archive) {
124
- const archivePath = `${packageRoot}.tar.gz`
125
- removeKnownPath(archivePath, path.dirname(archivePath))
126
- run("tar", [
127
- "-czf",
128
- archivePath,
129
- "-C",
130
- path.dirname(packageRoot),
131
- path.basename(packageRoot),
132
- ])
133
- console.log(`archive: ${archivePath}`)
134
- }
135
-
136
- console.log(`packaged Vantaloom: ${packageRoot}`)
137
- console.log(`platform: ${builtPlatform}`)
138
- console.log(`version: ${version}`)
139
- }
140
-
141
- async function buildRuntimePackage(sourceRoot, packageRoot, options) {
142
- const version = gitVersion(sourceRoot)
143
- const platform = platformId()
144
- const buildBin = path.join(packageRoot, "bin")
145
- const buildWeb = path.join(packageRoot, "web")
146
-
147
- removeKnownPath(packageRoot, path.dirname(packageRoot))
148
- mkdirSync(buildBin, { recursive: true })
149
-
150
- buildGo(sourceRoot, buildBin, "vantaloom-api")
151
- buildGo(sourceRoot, buildBin, "vantaloom-agent")
152
- buildGo(sourceRoot, buildBin, "vantaloomctl")
153
-
154
- if (options.buildWeb) {
155
- runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
156
- }
157
-
158
- await copyStaticWeb(sourceRoot, buildWeb)
159
- await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
160
- writeBuildManifest(packageRoot, version, platform)
161
-
162
- return { platform, version }
163
- }
164
-
165
- async function applyPackage(packageRoot, prefix, options) {
166
- assertRuntimePackage(packageRoot)
167
-
168
- const existingConfig = readInstalledConfig(prefix)
169
- const packageConfig = readJSONIfExists(path.join(packageRoot, "cli", "config.json"))
170
- const mergedConfig = mergeRuntimeConfig(packageConfig, existingConfig, {
171
- sourceRoot: options.sourceRoot,
172
- })
173
- const version = readVersion(packageRoot)
174
-
175
- mkdirSync(prefix, { recursive: true })
176
- const existingCtl = path.join(prefix, "bin", binaryName("vantaloomctl"))
177
- if (existsSync(existingCtl)) {
178
- spawnSync(existingCtl, ["stop", "--prefix", prefix], { stdio: "inherit" })
179
- }
180
-
181
- for (const name of ["bin", "web", "cli"]) {
182
- removeKnownPath(path.join(prefix, name), prefix)
183
- await cp(path.join(packageRoot, name), path.join(prefix, name), {
184
- recursive: true,
185
- force: true,
186
- dereference: false,
187
- })
188
- }
189
-
190
- await writeLauncher(prefix)
191
- await writeText(path.join(prefix, "cli", "config.json"), `${JSON.stringify(mergedConfig, null, 2)}\n`)
192
- await writeText(path.join(prefix, "VERSION"), `${version}\n`)
193
- await cp(path.join(packageRoot, "manifest.json"), path.join(prefix, "manifest.json"), {
194
- force: true,
195
- })
196
-
197
- run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
198
- "install",
199
- "--prefix",
200
- prefix,
201
- "--version",
202
- version,
203
- ])
204
-
205
- if (!options.noStart) {
206
- run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
207
- "start",
208
- "--prefix",
209
- prefix,
210
- ])
211
- }
212
-
213
- return version
214
- }
215
-
216
- async function syncFromNpmRegistry(options, action) {
217
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
218
- const installedConfig = readInstalledConfig(prefix)
219
- const runtimePackage = options.runtimePackage || installedConfig.runtimePackage || runtimePackageName(platformId())
220
- const runtimeVersion = options.runtimeVersion || installedConfig.runtimeVersion || "latest"
221
- const registry = normalizeRegistry(options.npmRegistry || installedConfig.npmRegistry || defaultNpmRegistry)
222
- const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-npm-"))
223
-
224
- try {
225
- const extractRoot = path.join(tempRoot, "extract")
226
- const archive = path.join(tempRoot, `${packageBasename(runtimePackage)}-${runtimeVersion}.tgz`)
227
- mkdirSync(extractRoot, { recursive: true })
228
-
229
- const resolved = await resolveNpmPackage({ registry, name: runtimePackage, version: runtimeVersion })
230
- await downloadNpmTarball({
231
- tarballUrl: resolved.tarball,
232
- target: archive,
233
- packageName: runtimePackage,
234
- version: resolved.version,
235
- })
236
- run("tar", ["-xzf", archive, "-C", extractRoot])
237
-
238
- const packageRoot = findExtractedNpmPackage(extractRoot)
239
- const version = await applyPackage(packageRoot, prefix, {
240
- noStart: options.noStart,
241
- runtimePackage,
242
- runtimeVersion: options.runtimeVersion ? resolved.version : "latest",
243
- npmRegistry: registry,
244
- update: action === "update",
245
- })
246
-
247
- console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
248
- console.log(`version: ${version}`)
249
- console.log(`source: ${runtimePackage}@${resolved.version}`)
250
- console.log(`run: ${displayCommand(prefix)} status`)
251
- } finally {
252
- removeKnownPath(tempRoot, os.tmpdir())
253
- }
254
- }
255
-
256
- async function syncFromRelease(options, action) {
257
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
258
- const installedConfig = readInstalledConfig(prefix)
259
- const sourceRoot = installedConfig.sourceRoot
260
- ? tryAssertSourceRoot(installedConfig.sourceRoot)
261
- : tryFindSourceRoot()
262
- const sourceRemote = sourceRoot ? gitRemoteUrl(sourceRoot) : ""
263
- const repo = options.repo || installedConfig.repo || inferGitHubRepo(options.remote ?? installedConfig.remote ?? sourceRemote) || defaultRepo
264
- const releaseTag = options.releaseTag || installedConfig.releaseTag || defaultReleaseTag
265
- const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-update-"))
266
-
267
- try {
268
- const extractRoot = path.join(tempRoot, "extract")
269
- const archive = path.join(tempRoot, `vantaloom-${platformId()}.tar.gz`)
270
- mkdirSync(extractRoot, { recursive: true })
271
-
272
- await downloadReleaseAsset({
273
- repo,
274
- releaseTag,
275
- assetName: `vantaloom-${platformId()}.tar.gz`,
276
- target: archive,
277
- token: options.githubToken,
278
- })
279
- run("tar", ["-xzf", archive, "-C", extractRoot])
280
-
281
- const packageRoot = findExtractedPackage(extractRoot, platformId())
282
- const version = await applyPackage(packageRoot, prefix, {
283
- noStart: options.noStart,
284
- sourceRoot: installedConfig.sourceRoot,
285
- update: action === "update",
286
- })
287
-
288
- console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
289
- console.log(`version: ${version}`)
290
- console.log(`source: ${repo}@${releaseTag}`)
291
- console.log(`run: ${displayCommand(prefix)} status`)
292
- } finally {
293
- removeKnownPath(tempRoot, os.tmpdir())
294
- }
295
- }
296
-
297
- async function downloadReleaseAsset({ repo, releaseTag, assetName, target, token }) {
298
- const releaseUrl = `https://api.github.com/repos/${repo}/releases/tags/${releaseTag}`
299
- const headers = {
300
- "User-Agent": "vantaloom-cli",
301
- }
302
- const githubToken = token || process.env.VANTALOOM_GITHUB_TOKEN || process.env.GITHUB_TOKEN
303
- if (githubToken) {
304
- headers.Authorization = `Bearer ${githubToken}`
305
- }
306
-
307
- const releaseResponse = await fetch(releaseUrl, { headers })
308
- if (!releaseResponse.ok) {
309
- const detail = await releaseResponse.text().catch(() => "")
310
- const authHint = releaseResponse.status === 404 || releaseResponse.status === 403
311
- ? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
312
- : ""
313
- throw new Error(`failed to inspect ${repo}@${releaseTag}: HTTP ${releaseResponse.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
314
- }
315
-
316
- const release = await releaseResponse.json()
317
- const asset = release.assets?.find((asset) => asset.name === assetName)
318
- if (!asset) {
319
- const names = release.assets?.map((asset) => asset.name).join(", ") || "none"
320
- throw new Error(`missing release asset ${assetName} in ${repo}@${releaseTag}; available assets: ${names}`)
321
- }
322
-
323
- const response = await fetch(asset.url, {
324
- headers: {
325
- ...headers,
326
- Accept: "application/octet-stream",
327
- },
328
- })
329
- if (!response.ok) {
330
- const detail = await response.text().catch(() => "")
331
- const authHint = response.status === 404 || response.status === 403
332
- ? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
333
- : ""
334
- throw new Error(`failed to download ${assetName} from ${repo}@${releaseTag}: HTTP ${response.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
335
- }
336
-
337
- const buffer = Buffer.from(await response.arrayBuffer())
338
- await writeFile(target, buffer)
339
- }
340
-
341
- async function resolveNpmPackage({ registry, name, version }) {
342
- const metadataUrl = `${registry}/${encodeURIComponent(name).replace("%2F", "%2f")}`
343
- const response = await fetch(metadataUrl, {
344
- headers: {
345
- Accept: "application/vnd.npm.install-v1+json",
346
- "User-Agent": "vantaloom-cli",
347
- },
348
- })
349
- if (!response.ok) {
350
- const detail = await response.text().catch(() => "")
351
- throw new Error(`failed to inspect npm package ${name}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
352
- }
353
-
354
- const metadata = await response.json()
355
- const selectedVersion = metadata["dist-tags"]?.[version] ?? version
356
- const packageVersion = metadata.versions?.[selectedVersion]
357
- if (!packageVersion?.dist?.tarball) {
358
- const available = Object.keys(metadata.versions ?? {}).slice(-8).join(", ") || "none"
359
- throw new Error(`missing npm package ${name}@${version}; available versions: ${available}`)
360
- }
361
- return {
362
- version: selectedVersion,
363
- tarball: packageVersion.dist.tarball,
364
- }
365
- }
366
-
367
- async function downloadNpmTarball({ tarballUrl, target, packageName, version }) {
368
- const response = await fetch(tarballUrl, {
369
- headers: {
370
- "User-Agent": "vantaloom-cli",
371
- },
372
- })
373
- if (!response.ok) {
374
- const detail = await response.text().catch(() => "")
375
- throw new Error(`failed to download ${packageName}@${version}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
376
- }
377
-
378
- const buffer = Buffer.from(await response.arrayBuffer())
379
- await writeFile(target, buffer)
380
- }
381
-
382
- function shouldUseSourceInstall(options) {
383
- return Boolean(options.local || options.source || options.buildWeb)
384
- }
385
-
386
- function shouldUseReleaseSync(options) {
387
- return Boolean(options.repo || options.remote || options.releaseTag || options.githubToken)
388
- }
389
-
390
- function assertRuntimePackage(packageRoot) {
391
- for (const name of ["bin", "web", "cli", "manifest.json"]) {
392
- if (!existsSync(path.join(packageRoot, name))) {
393
- throw new Error(`invalid Vantaloom package, missing ${name}: ${packageRoot}`)
394
- }
395
- }
396
- }
397
-
398
- function readVersion(packageRoot) {
399
- const versionPath = path.join(packageRoot, "VERSION")
400
- if (existsSync(versionPath)) {
401
- return readFileSync(versionPath, "utf8").trim() || "dev"
402
- }
403
- const manifest = readJSONIfExists(path.join(packageRoot, "manifest.json"))
404
- return manifest.version ?? "dev"
405
- }
406
-
407
- function readInstalledConfig(prefix) {
408
- return readJSONIfExists(path.join(prefix, "cli", "config.json"))
409
- }
410
-
411
- function readJSONIfExists(filePath) {
412
- if (!existsSync(filePath)) {
413
- return {}
414
- }
415
- return JSON.parse(readFileSync(filePath, "utf8"))
416
- }
417
-
418
- function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
419
- const merged = { ...packageConfig }
420
- for (const key of ["sourceRoot", "remote", "repo", "releaseTag", "runtimePackage", "runtimeVersion", "npmRegistry"]) {
421
- if (!merged[key] && existingConfig[key]) {
422
- merged[key] = existingConfig[key]
423
- }
424
- }
425
- if (overrides.sourceRoot) {
426
- merged.sourceRoot = overrides.sourceRoot
427
- }
428
- if (overrides.runtimePackage) {
429
- merged.runtimePackage = overrides.runtimePackage
430
- }
431
- if (overrides.runtimeVersion) {
432
- merged.runtimeVersion = overrides.runtimeVersion
433
- }
434
- if (overrides.npmRegistry) {
435
- merged.npmRegistry = overrides.npmRegistry
436
- }
437
- if (!merged.repo) {
438
- merged.repo = defaultRepo
439
- }
440
- if (!merged.releaseTag) {
441
- merged.releaseTag = defaultReleaseTag
442
- }
443
- if (!merged.runtimePackage) {
444
- merged.runtimePackage = runtimePackageName(platformId())
445
- }
446
- if (!merged.runtimeVersion) {
447
- merged.runtimeVersion = "latest"
448
- }
449
- if (!merged.npmRegistry) {
450
- merged.npmRegistry = defaultNpmRegistry
451
- }
452
- return merged
453
- }
454
-
455
- function runtimeConfigFromSource(sourceRoot) {
456
- const remote = gitRemoteUrl(sourceRoot)
457
- const repo = inferGitHubRepo(remote) || defaultRepo
458
- return {
459
- ...(process.env.GITHUB_ACTIONS ? {} : { sourceRoot }),
460
- ...(remote ? { remote } : {}),
461
- repo,
462
- releaseTag: defaultReleaseTag,
463
- runtimePackage: runtimePackageName(platformId()),
464
- runtimeVersion: "latest",
465
- npmRegistry: defaultNpmRegistry,
466
- }
467
- }
468
-
469
- function findExtractedPackage(extractRoot, platform) {
470
- const expected = path.join(extractRoot, `vantaloom-${platform}`)
471
- if (existsSync(expected)) {
472
- return expected
473
- }
474
-
475
- const directories = readdirSync(extractRoot, { withFileTypes: true })
476
- .filter((entry) => entry.isDirectory())
477
- .map((entry) => path.join(extractRoot, entry.name))
478
- if (directories.length === 1) {
479
- return directories[0]
480
- }
481
-
482
- throw new Error(`could not find extracted Vantaloom package in ${extractRoot}`)
483
- }
484
-
485
- function findExtractedNpmPackage(extractRoot) {
486
- const expected = path.join(extractRoot, "package")
487
- if (existsSync(expected)) {
488
- return expected
489
- }
490
-
491
- const directories = readdirSync(extractRoot, { withFileTypes: true })
492
- .filter((entry) => entry.isDirectory())
493
- .map((entry) => path.join(extractRoot, entry.name))
494
- if (directories.length === 1) {
495
- return directories[0]
496
- }
497
-
498
- throw new Error(`could not find extracted npm package in ${extractRoot}`)
499
- }
500
-
501
- function tryFindSourceRoot() {
502
- try {
503
- return findSourceRoot()
504
- } catch {
505
- return ""
506
- }
507
- }
508
-
509
- function tryAssertSourceRoot(sourceRoot) {
510
- try {
511
- return assertSourceRoot(sourceRoot)
512
- } catch {
513
- return ""
514
- }
515
- }
516
-
517
- function gitRemoteUrl(sourceRoot) {
518
- const result = spawnSync("git", ["remote", "get-url", "origin"], {
519
- cwd: sourceRoot,
520
- encoding: "utf8",
521
- })
522
- if (result.status === 0) {
523
- return result.stdout.trim()
524
- }
525
- return ""
526
- }
527
-
528
- function inferGitHubRepo(remote) {
529
- if (!remote) {
530
- return ""
531
- }
532
- const normalized = remote.replace(/\.git$/, "")
533
- const httpsMatch = normalized.match(/github\.com[:/]([^/]+\/[^/]+)$/)
534
- if (httpsMatch) {
535
- return httpsMatch[1]
536
- }
537
- const sshMatch = normalized.match(/^[^:]+:([^/]+\/[^/]+)$/)
538
- return sshMatch?.[1] ?? ""
539
- }
540
-
541
- async function copyStaticWeb(sourceRoot, buildWeb) {
542
- const exportRoot = path.join(sourceRoot, "apps", "vantaloom", "out")
543
- if (!existsSync(exportRoot)) {
544
- throw new Error(
545
- "missing Next static export output; let GitHub CI run production build, or pass --build-web for a local one-off build"
546
- )
547
- }
548
-
549
- removeKnownPath(buildWeb, path.dirname(buildWeb))
550
- await copyDir(exportRoot, buildWeb)
551
- }
552
-
553
- async function copyCliDirectory(target, sourceRoot, config) {
554
- const sourceCliRoot = path.join(sourceRoot, "packages", "cli")
555
- if (!existsSync(path.join(sourceCliRoot, "bin", "vantaloom.mjs"))) {
556
- throw new Error(`missing source CLI package: ${sourceCliRoot}`)
557
- }
558
- removeKnownPath(target, path.dirname(target))
559
- mkdirSync(target, { recursive: true })
560
- await copyDir(sourceCliRoot, target)
561
- await writeText(
562
- path.join(target, "config.json"),
563
- `${JSON.stringify(config, null, 2)}\n`
564
- )
565
- }
566
-
567
- async function writeLauncher(prefix) {
568
- if (process.platform === "win32") {
569
- await writeText(
570
- path.join(prefix, "vantaloom.cmd"),
571
- `@echo off\r\nnode "%~dp0cli\\bin\\vantaloom.mjs" %*\r\n`
572
- )
573
- } else {
574
- const launcher = `#!/usr/bin/env sh\nexec node "$(dirname "$0")/cli/bin/vantaloom.mjs" "$@"\n`
575
- const launcherPath = path.join(prefix, "vantaloom")
576
- await writeText(launcherPath, launcher)
577
- chmodSync(launcherPath, 0o755)
578
- }
579
- }
580
-
581
- function runCtl(command, options) {
582
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
583
- const ctl = path.join(prefix, "bin", binaryName("vantaloomctl"))
584
- if (!existsSync(ctl)) {
585
- throw new Error(`missing installed runtime at ${prefix}; run "vantaloom install" first`)
586
- }
587
-
588
- const args = [command, "--prefix", prefix]
589
- if (options.component) {
590
- args.push("--component", options.component)
591
- }
592
- run(ctl, args)
593
- }
594
-
595
- function printPaths(options) {
596
- const sourceRoot = findSourceRoot(options.source)
597
- const prefix = safeDirectory(options.prefix ?? defaultPrefix())
598
- console.log(JSON.stringify({ sourceRoot, prefix }, null, 2))
599
- }
600
-
601
- function buildGo(sourceRoot, buildBin, name) {
602
- run("go", [
603
- "build",
604
- "-o",
605
- path.join(buildBin, binaryName(name)),
606
- `./apps/api/cmd/${name}`,
607
- ], { cwd: sourceRoot })
608
- }
609
-
610
- async function copyDir(source, destination, options = {}) {
611
- if (!existsSync(source)) {
612
- throw new Error(`missing source directory: ${source}`)
613
- }
614
- removeKnownPath(destination, path.dirname(destination))
615
- mkdirSync(destination, { recursive: true })
616
- await cp(source, destination, {
617
- recursive: true,
618
- force: true,
619
- dereference: options.dereference ?? true,
620
- })
621
- }
622
-
623
- function writeBuildManifest(buildRoot, version, platform) {
624
- writeFileSync(path.join(buildRoot, "VERSION"), `${version}\n`)
625
- writeFileSync(
626
- path.join(buildRoot, "manifest.json"),
627
- `${JSON.stringify(
628
- {
629
- name: "Vantaloom Local Runtime",
630
- version,
631
- platform,
632
- updatedAt: new Date().toISOString(),
633
- components: ["api", "agent", "web", "ctl"],
634
- },
635
- null,
636
- 2
637
- )}\n`
638
- )
639
- }
640
-
641
- function writeRuntimePackageMetadata(packageRoot, sourceRoot, platform) {
642
- const version = npmPackageVersion(sourceRoot)
643
- const { os: runtimeOS, cpu } = parsePlatformId(platform)
644
- const name = runtimePackageName(platform)
645
- writeFileSync(
646
- path.join(packageRoot, "package.json"),
647
- `${JSON.stringify(
648
- {
649
- name,
650
- version,
651
- private: false,
652
- description: `Vantaloom local runtime for ${platform}.`,
653
- type: "module",
654
- os: [runtimeOS],
655
- cpu: [cpu],
656
- files: ["bin", "web", "cli", "manifest.json", "VERSION", "README.md"],
657
- publishConfig: {
658
- access: "public",
659
- },
660
- engines: {
661
- node: ">=20",
662
- },
663
- },
664
- null,
665
- 2
666
- )}\n`
667
- )
668
- writeFileSync(
669
- path.join(packageRoot, "README.md"),
670
- `# ${name}\n\nPlatform runtime package for Vantaloom ${platform}. Install @vantaloom/cli instead of this package directly.\n`
671
- )
672
- }
673
-
674
- function findSourceRoot(sourceOption) {
675
- if (sourceOption) {
676
- return assertSourceRoot(path.resolve(sourceOption))
677
- }
678
- if (process.env.VANTALOOM_SOURCE) {
679
- return assertSourceRoot(path.resolve(process.env.VANTALOOM_SOURCE))
680
- }
681
- if (existsSync(installedConfigPath)) {
682
- const config = JSON.parse(readFileSync(installedConfigPath, "utf8"))
683
- if (config.sourceRoot) {
684
- return assertSourceRoot(path.resolve(config.sourceRoot))
685
- }
686
- }
687
- return assertSourceRoot(repoCandidate)
688
- }
689
-
690
- function npmPackageVersion(sourceRoot) {
691
- const packageJSON = readJSONIfExists(path.join(sourceRoot, "packages", "cli", "package.json"))
692
- return packageJSON.version ?? "0.0.0"
693
- }
694
-
695
- function assertSourceRoot(sourceRoot) {
696
- if (!existsSync(path.join(sourceRoot, "apps", "api", "go.mod"))) {
697
- throw new Error(`not a Vantaloom source root: ${sourceRoot}`)
698
- }
699
- return sourceRoot
700
- }
701
-
702
- function gitVersion(sourceRoot) {
703
- const result = spawnSync("git", ["rev-parse", "--short", "HEAD"], {
704
- cwd: sourceRoot,
705
- encoding: "utf8",
706
- })
707
- if (result.status === 0) {
708
- return result.stdout.trim() || "dev"
709
- }
710
- return "dev"
711
- }
712
-
713
- function parseOptions(args) {
714
- const options = {}
715
- for (let index = 0; index < args.length; index += 1) {
716
- const arg = args[index]
717
- if (!arg.startsWith("--")) {
718
- throw new Error(`unexpected argument "${arg}"`)
719
- }
720
- const [key, inlineValue] = arg.slice(2).split("=", 2)
721
- switch (key) {
722
- case "prefix":
723
- case "source":
724
- case "component":
725
- case "package":
726
- case "output":
727
- case "repo":
728
- case "remote":
729
- case "release-tag":
730
- case "github-token":
731
- case "runtime-package":
732
- case "runtime-version":
733
- case "npm-registry":
734
- options[toCamel(key)] = inlineValue ?? args[++index]
735
- if (!options[toCamel(key)]) {
736
- throw new Error(`missing value for --${key}`)
737
- }
738
- break
739
- case "build-web":
740
- options.buildWeb = true
741
- break
742
- case "no-start":
743
- options.noStart = true
744
- break
745
- case "archive":
746
- options.archive = true
747
- break
748
- case "npm-package":
749
- options.npmPackage = true
750
- break
751
- case "local":
752
- options.local = true
753
- break
754
- default:
755
- throw new Error(`unknown option --${key}`)
756
- }
757
- }
758
- return options
759
- }
760
-
761
- function safeDirectory(value) {
762
- const full = path.resolve(value)
763
- const parsed = path.parse(full)
764
- if (full === parsed.root) {
765
- throw new Error(`refusing to operate on unsafe directory: ${value}`)
766
- }
767
- return full
768
- }
769
-
770
- function removeKnownPath(target, expectedParent) {
771
- if (!existsSync(target)) {
772
- return
773
- }
774
- const full = path.resolve(target)
775
- const parent = path.resolve(expectedParent)
776
- const prefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`
777
- if (!full.startsWith(prefix)) {
778
- throw new Error(`refusing to remove path outside expected parent: ${full}`)
779
- }
780
- rmSync(full, { recursive: true, force: true })
781
- }
782
-
783
- function run(command, args, options = {}) {
784
- try {
785
- execFileSync(command, args, { stdio: "inherit", ...options })
786
- } catch (error) {
787
- if (error && typeof error.status === "number") {
788
- throw new Error(`${command} exited with ${error.status}`)
789
- }
790
- throw error
791
- }
792
- }
793
-
794
- function runPnpm(args, options = {}) {
795
- if (process.platform === "win32") {
796
- run("cmd.exe", ["/d", "/s", "/c", "pnpm", ...args], options)
797
- return
798
- }
799
- run("pnpm", args, options)
800
- }
801
-
802
- function binaryName(name) {
803
- return process.platform === "win32" ? `${name}.exe` : name
804
- }
805
-
806
- function platformId() {
807
- return `${process.platform}-${process.arch}`
808
- }
809
-
810
- function runtimePackageName(platform) {
811
- return `@vantaloom/runtime-${platform}`
812
- }
813
-
814
- function parsePlatformId(platform) {
815
- const parts = platform.split("-")
816
- const cpu = parts.pop()
817
- const runtimeOS = parts.join("-")
818
- if (!runtimeOS || !cpu) {
819
- throw new Error(`invalid platform id: ${platform}`)
820
- }
821
- return { os: runtimeOS, cpu }
822
- }
823
-
824
- function packageBasename(name) {
825
- return name.split("/").pop() ?? name
826
- }
827
-
828
- function normalizeRegistry(value) {
829
- return value.replace(/\/+$/, "")
830
- }
831
-
832
- function defaultPrefix() {
833
- if (process.env.VANTALOOM_HOME) {
834
- return process.env.VANTALOOM_HOME
835
- }
836
- if (process.platform === "win32") {
837
- return "D:\\Vantaloom"
838
- }
839
- if (process.platform === "darwin") {
840
- return path.join(os.homedir(), "Applications", "Vantaloom")
841
- }
842
- return path.join(os.homedir(), ".local", "vantaloom")
843
- }
844
-
845
- function displayCommand(prefix) {
846
- if (process.platform === "win32") {
847
- return path.join(prefix, "vantaloom.cmd")
848
- }
849
- return path.join(prefix, "vantaloom")
850
- }
851
-
852
- function toCamel(value) {
853
- return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
854
- }
855
-
856
- async function writeText(filePath, content) {
857
- mkdirSync(path.dirname(filePath), { recursive: true })
858
- await writeFile(filePath, content)
859
- }
860
-
861
- function printHelp() {
862
- console.log(`Vantaloom CLI
863
-
864
- Usage:
865
- vantaloom install [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--package <dir>] [--no-start]
866
- vantaloom install --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
867
- vantaloom update [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--no-start]
868
- vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
869
- vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive] [--npm-package]
870
- vantaloom start [--prefix <dir>] [--component all|api|agent|web]
871
- vantaloom stop [--prefix <dir>] [--component all|api|agent|web]
872
- vantaloom restart [--prefix <dir>] [--component all|api|agent|web]
873
- vantaloom status [--prefix <dir>]
874
- vantaloom ports [--prefix <dir>]
875
- vantaloom path [--prefix <dir>] [--source <repo>]
876
- vantaloom platform
877
- `)
878
- }
1
+ import { execFileSync, spawnSync } from "node:child_process"
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs"
12
+ import { cp, writeFile } from "node:fs/promises"
13
+ import os from "node:os"
14
+ import path from "node:path"
15
+ import { fileURLToPath } from "node:url"
16
+
17
+ const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..")
18
+ const repoCandidate = path.resolve(cliRoot, "..", "..")
19
+ const installedConfigPath = path.join(cliRoot, "config.json")
20
+ const defaultReleaseTag = "runtime-latest"
21
+ const defaultRepo = "Timefiles404/Vantaloom-next"
22
+ const defaultNpmRegistry = "https://registry.npmjs.org"
23
+
24
+ export async function main(argv) {
25
+ const command = argv[0] ?? "help"
26
+ const options = parseOptions(argv.slice(1))
27
+
28
+ switch (command) {
29
+ case "install":
30
+ if (options.package) {
31
+ await installFromPackage({ ...options, update: false })
32
+ } else if (shouldUseSourceInstall(options)) {
33
+ await installFromSource(options)
34
+ } else if (shouldUseReleaseSync(options)) {
35
+ await syncFromRelease(options, "install")
36
+ } else {
37
+ await syncFromNpmRegistry(options, "install")
38
+ }
39
+ return
40
+ case "update":
41
+ if (options.package) {
42
+ await installFromPackage({ ...options, update: true })
43
+ } else if (shouldUseSourceInstall(options)) {
44
+ await installFromSource({ ...options, update: true })
45
+ } else if (shouldUseReleaseSync(options)) {
46
+ await syncFromRelease(options, "update")
47
+ } else {
48
+ await syncFromNpmRegistry(options, "update")
49
+ }
50
+ return
51
+ case "package":
52
+ await packageRuntime(options)
53
+ return
54
+ case "platform":
55
+ console.log(platformId())
56
+ return
57
+ case "start":
58
+ case "stop":
59
+ case "restart":
60
+ case "status":
61
+ case "ports":
62
+ runCtl(command, options)
63
+ return
64
+ case "path":
65
+ printPaths(options)
66
+ return
67
+ case "help":
68
+ case "-h":
69
+ case "--help":
70
+ printHelp()
71
+ return
72
+ default:
73
+ throw new Error(`unknown command "${command}"`)
74
+ }
75
+ }
76
+
77
+ async function installFromSource(options) {
78
+ const sourceRoot = findSourceRoot(options.source)
79
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
80
+ const buildRoot = path.join(sourceRoot, "artifacts", "local-install", platformId())
81
+
82
+ const { version } = await buildRuntimePackage(sourceRoot, buildRoot, {
83
+ buildWeb: options.buildWeb,
84
+ })
85
+
86
+ await applyPackage(buildRoot, prefix, {
87
+ noStart: options.noStart,
88
+ sourceRoot,
89
+ update: options.update,
90
+ })
91
+
92
+ console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
93
+ console.log(`version: ${version}`)
94
+ console.log(`run: ${displayCommand(prefix)} status`)
95
+ }
96
+
97
+ async function installFromPackage(options) {
98
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
99
+ const packageRoot = safeDirectory(options.package)
100
+ const version = await applyPackage(packageRoot, prefix, {
101
+ noStart: options.noStart,
102
+ update: options.update,
103
+ })
104
+
105
+ console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
106
+ console.log(`version: ${version}`)
107
+ console.log(`run: ${displayCommand(prefix)} status`)
108
+ }
109
+
110
+ async function packageRuntime(options) {
111
+ const sourceRoot = findSourceRoot(options.source)
112
+ const targetPlatform = options.target ?? platformId()
113
+ const packageRoot = safeDirectory(
114
+ options.output ?? path.join(sourceRoot, "artifacts", "packages", `vantaloom-${targetPlatform}`)
115
+ )
116
+ const { platform: builtPlatform, version } = await buildRuntimePackage(sourceRoot, packageRoot, {
117
+ buildWeb: options.buildWeb,
118
+ target: targetPlatform,
119
+ })
120
+
121
+ if (options.npmPackage) {
122
+ writeRuntimePackageMetadata(packageRoot, sourceRoot, builtPlatform)
123
+ }
124
+
125
+ if (options.archive) {
126
+ const archivePath = `${packageRoot}.tar.gz`
127
+ removeKnownPath(archivePath, path.dirname(archivePath))
128
+ run("tar", [
129
+ "-czf",
130
+ archivePath,
131
+ "-C",
132
+ path.dirname(packageRoot),
133
+ path.basename(packageRoot),
134
+ ])
135
+ console.log(`archive: ${archivePath}`)
136
+ }
137
+
138
+ console.log(`packaged Vantaloom: ${packageRoot}`)
139
+ console.log(`platform: ${builtPlatform}`)
140
+ console.log(`version: ${version}`)
141
+ }
142
+
143
+ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
144
+ const version = gitVersion(sourceRoot)
145
+ const platform = options.target ?? platformId()
146
+ const buildBin = path.join(packageRoot, "bin")
147
+ const buildWeb = path.join(packageRoot, "web")
148
+
149
+ removeKnownPath(packageRoot, path.dirname(packageRoot))
150
+ mkdirSync(buildBin, { recursive: true })
151
+
152
+ buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
153
+ buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
154
+ buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
155
+ // Tray app requires CGO (systray), only buildable natively on Windows.
156
+ if (platform.startsWith("win32") && process.platform === "win32") {
157
+ try {
158
+ buildGo(sourceRoot, buildBin, "vantaloom-tray", platform)
159
+ } catch { /* optional: skip if systray build fails */ }
160
+ }
161
+
162
+ if (options.buildWeb) {
163
+ runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
164
+ }
165
+
166
+ await copyStaticWeb(sourceRoot, buildWeb)
167
+ await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
168
+ writeBuildManifest(packageRoot, version, platform)
169
+
170
+ return { platform, version }
171
+ }
172
+
173
+ async function applyPackage(packageRoot, prefix, options) {
174
+ assertRuntimePackage(packageRoot)
175
+
176
+ const existingConfig = readInstalledConfig(prefix)
177
+ const packageConfig = readJSONIfExists(path.join(packageRoot, "cli", "config.json"))
178
+ const mergedConfig = mergeRuntimeConfig(packageConfig, existingConfig, {
179
+ sourceRoot: options.sourceRoot,
180
+ })
181
+ const version = readVersion(packageRoot)
182
+
183
+ mkdirSync(prefix, { recursive: true })
184
+ const existingCtl = path.join(prefix, "bin", binaryName("vantaloomctl"))
185
+ if (existsSync(existingCtl)) {
186
+ spawnSync(existingCtl, ["stop", "--prefix", prefix], { stdio: "inherit" })
187
+ }
188
+
189
+ for (const name of ["bin", "web", "cli"]) {
190
+ removeKnownPath(path.join(prefix, name), prefix)
191
+ await cp(path.join(packageRoot, name), path.join(prefix, name), {
192
+ recursive: true,
193
+ force: true,
194
+ dereference: false,
195
+ })
196
+ }
197
+
198
+ await writeLauncher(prefix)
199
+ await writeText(path.join(prefix, "cli", "config.json"), `${JSON.stringify(mergedConfig, null, 2)}\n`)
200
+ await writeText(path.join(prefix, "VERSION"), `${version}\n`)
201
+ await cp(path.join(packageRoot, "manifest.json"), path.join(prefix, "manifest.json"), {
202
+ force: true,
203
+ })
204
+
205
+ run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
206
+ "install",
207
+ "--prefix",
208
+ prefix,
209
+ "--version",
210
+ version,
211
+ ])
212
+
213
+ if (!options.noStart) {
214
+ run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
215
+ "start",
216
+ "--prefix",
217
+ prefix,
218
+ ])
219
+ }
220
+
221
+ return version
222
+ }
223
+
224
+ async function syncFromNpmRegistry(options, action) {
225
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
226
+ const installedConfig = readInstalledConfig(prefix)
227
+ const runtimePackage = options.runtimePackage || installedConfig.runtimePackage || runtimePackageName(platformId())
228
+ const runtimeVersion = options.runtimeVersion || installedConfig.runtimeVersion || "latest"
229
+ const registry = normalizeRegistry(options.npmRegistry || installedConfig.npmRegistry || defaultNpmRegistry)
230
+ const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-npm-"))
231
+
232
+ try {
233
+ const extractRoot = path.join(tempRoot, "extract")
234
+ const archive = path.join(tempRoot, `${packageBasename(runtimePackage)}-${runtimeVersion}.tgz`)
235
+ mkdirSync(extractRoot, { recursive: true })
236
+
237
+ const resolved = await resolveNpmPackage({ registry, name: runtimePackage, version: runtimeVersion })
238
+ await downloadNpmTarball({
239
+ tarballUrl: resolved.tarball,
240
+ target: archive,
241
+ packageName: runtimePackage,
242
+ version: resolved.version,
243
+ })
244
+ run("tar", ["-xzf", archive, "-C", extractRoot])
245
+
246
+ const packageRoot = findExtractedNpmPackage(extractRoot)
247
+ const version = await applyPackage(packageRoot, prefix, {
248
+ noStart: options.noStart,
249
+ runtimePackage,
250
+ runtimeVersion: options.runtimeVersion ? resolved.version : "latest",
251
+ npmRegistry: registry,
252
+ update: action === "update",
253
+ })
254
+
255
+ console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
256
+ console.log(`version: ${version}`)
257
+ console.log(`source: ${runtimePackage}@${resolved.version}`)
258
+ console.log(`run: ${displayCommand(prefix)} status`)
259
+ } finally {
260
+ removeKnownPath(tempRoot, os.tmpdir())
261
+ }
262
+ }
263
+
264
+ async function syncFromRelease(options, action) {
265
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
266
+ const installedConfig = readInstalledConfig(prefix)
267
+ const sourceRoot = installedConfig.sourceRoot
268
+ ? tryAssertSourceRoot(installedConfig.sourceRoot)
269
+ : tryFindSourceRoot()
270
+ const sourceRemote = sourceRoot ? gitRemoteUrl(sourceRoot) : ""
271
+ const repo = options.repo || installedConfig.repo || inferGitHubRepo(options.remote ?? installedConfig.remote ?? sourceRemote) || defaultRepo
272
+ const releaseTag = options.releaseTag || installedConfig.releaseTag || defaultReleaseTag
273
+ const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-update-"))
274
+
275
+ try {
276
+ const extractRoot = path.join(tempRoot, "extract")
277
+ const archive = path.join(tempRoot, `vantaloom-${platformId()}.tar.gz`)
278
+ mkdirSync(extractRoot, { recursive: true })
279
+
280
+ await downloadReleaseAsset({
281
+ repo,
282
+ releaseTag,
283
+ assetName: `vantaloom-${platformId()}.tar.gz`,
284
+ target: archive,
285
+ token: options.githubToken,
286
+ })
287
+ run("tar", ["-xzf", archive, "-C", extractRoot])
288
+
289
+ const packageRoot = findExtractedPackage(extractRoot, platformId())
290
+ const version = await applyPackage(packageRoot, prefix, {
291
+ noStart: options.noStart,
292
+ sourceRoot: installedConfig.sourceRoot,
293
+ update: action === "update",
294
+ })
295
+
296
+ console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
297
+ console.log(`version: ${version}`)
298
+ console.log(`source: ${repo}@${releaseTag}`)
299
+ console.log(`run: ${displayCommand(prefix)} status`)
300
+ } finally {
301
+ removeKnownPath(tempRoot, os.tmpdir())
302
+ }
303
+ }
304
+
305
+ async function downloadReleaseAsset({ repo, releaseTag, assetName, target, token }) {
306
+ const releaseUrl = `https://api.github.com/repos/${repo}/releases/tags/${releaseTag}`
307
+ const headers = {
308
+ "User-Agent": "vantaloom-cli",
309
+ }
310
+ const githubToken = token || process.env.VANTALOOM_GITHUB_TOKEN || process.env.GITHUB_TOKEN
311
+ if (githubToken) {
312
+ headers.Authorization = `Bearer ${githubToken}`
313
+ }
314
+
315
+ const releaseResponse = await fetch(releaseUrl, { headers })
316
+ if (!releaseResponse.ok) {
317
+ const detail = await releaseResponse.text().catch(() => "")
318
+ const authHint = releaseResponse.status === 404 || releaseResponse.status === 403
319
+ ? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
320
+ : ""
321
+ throw new Error(`failed to inspect ${repo}@${releaseTag}: HTTP ${releaseResponse.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
322
+ }
323
+
324
+ const release = await releaseResponse.json()
325
+ const asset = release.assets?.find((asset) => asset.name === assetName)
326
+ if (!asset) {
327
+ const names = release.assets?.map((asset) => asset.name).join(", ") || "none"
328
+ throw new Error(`missing release asset ${assetName} in ${repo}@${releaseTag}; available assets: ${names}`)
329
+ }
330
+
331
+ const response = await fetch(asset.url, {
332
+ headers: {
333
+ ...headers,
334
+ Accept: "application/octet-stream",
335
+ },
336
+ })
337
+ if (!response.ok) {
338
+ const detail = await response.text().catch(() => "")
339
+ const authHint = response.status === 404 || response.status === 403
340
+ ? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
341
+ : ""
342
+ throw new Error(`failed to download ${assetName} from ${repo}@${releaseTag}: HTTP ${response.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
343
+ }
344
+
345
+ const buffer = Buffer.from(await response.arrayBuffer())
346
+ await writeFile(target, buffer)
347
+ }
348
+
349
+ async function resolveNpmPackage({ registry, name, version }) {
350
+ const metadataUrl = `${registry}/${encodeURIComponent(name).replace("%2F", "%2f")}`
351
+ const response = await fetch(metadataUrl, {
352
+ headers: {
353
+ Accept: "application/vnd.npm.install-v1+json",
354
+ "User-Agent": "vantaloom-cli",
355
+ },
356
+ })
357
+ if (!response.ok) {
358
+ const detail = await response.text().catch(() => "")
359
+ throw new Error(`failed to inspect npm package ${name}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
360
+ }
361
+
362
+ const metadata = await response.json()
363
+ const selectedVersion = metadata["dist-tags"]?.[version] ?? version
364
+ const packageVersion = metadata.versions?.[selectedVersion]
365
+ if (!packageVersion?.dist?.tarball) {
366
+ const available = Object.keys(metadata.versions ?? {}).slice(-8).join(", ") || "none"
367
+ throw new Error(`missing npm package ${name}@${version}; available versions: ${available}`)
368
+ }
369
+ return {
370
+ version: selectedVersion,
371
+ tarball: packageVersion.dist.tarball,
372
+ }
373
+ }
374
+
375
+ async function downloadNpmTarball({ tarballUrl, target, packageName, version }) {
376
+ const response = await fetch(tarballUrl, {
377
+ headers: {
378
+ "User-Agent": "vantaloom-cli",
379
+ },
380
+ })
381
+ if (!response.ok) {
382
+ const detail = await response.text().catch(() => "")
383
+ throw new Error(`failed to download ${packageName}@${version}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
384
+ }
385
+
386
+ const buffer = Buffer.from(await response.arrayBuffer())
387
+ await writeFile(target, buffer)
388
+ }
389
+
390
+ function shouldUseSourceInstall(options) {
391
+ return Boolean(options.local || options.source || options.buildWeb)
392
+ }
393
+
394
+ function shouldUseReleaseSync(options) {
395
+ return Boolean(options.repo || options.remote || options.releaseTag || options.githubToken)
396
+ }
397
+
398
+ function assertRuntimePackage(packageRoot) {
399
+ for (const name of ["bin", "web", "cli", "manifest.json"]) {
400
+ if (!existsSync(path.join(packageRoot, name))) {
401
+ throw new Error(`invalid Vantaloom package, missing ${name}: ${packageRoot}`)
402
+ }
403
+ }
404
+ }
405
+
406
+ function readVersion(packageRoot) {
407
+ const versionPath = path.join(packageRoot, "VERSION")
408
+ if (existsSync(versionPath)) {
409
+ return readFileSync(versionPath, "utf8").trim() || "dev"
410
+ }
411
+ const manifest = readJSONIfExists(path.join(packageRoot, "manifest.json"))
412
+ return manifest.version ?? "dev"
413
+ }
414
+
415
+ function readInstalledConfig(prefix) {
416
+ return readJSONIfExists(path.join(prefix, "cli", "config.json"))
417
+ }
418
+
419
+ function readJSONIfExists(filePath) {
420
+ if (!existsSync(filePath)) {
421
+ return {}
422
+ }
423
+ return JSON.parse(readFileSync(filePath, "utf8"))
424
+ }
425
+
426
+ function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
427
+ const merged = { ...packageConfig }
428
+ for (const key of ["sourceRoot", "remote", "repo", "releaseTag", "runtimePackage", "runtimeVersion", "npmRegistry"]) {
429
+ if (!merged[key] && existingConfig[key]) {
430
+ merged[key] = existingConfig[key]
431
+ }
432
+ }
433
+ if (overrides.sourceRoot) {
434
+ merged.sourceRoot = overrides.sourceRoot
435
+ }
436
+ if (overrides.runtimePackage) {
437
+ merged.runtimePackage = overrides.runtimePackage
438
+ }
439
+ if (overrides.runtimeVersion) {
440
+ merged.runtimeVersion = overrides.runtimeVersion
441
+ }
442
+ if (overrides.npmRegistry) {
443
+ merged.npmRegistry = overrides.npmRegistry
444
+ }
445
+ if (!merged.repo) {
446
+ merged.repo = defaultRepo
447
+ }
448
+ if (!merged.releaseTag) {
449
+ merged.releaseTag = defaultReleaseTag
450
+ }
451
+ if (!merged.runtimePackage) {
452
+ merged.runtimePackage = runtimePackageName(platformId())
453
+ }
454
+ if (!merged.runtimeVersion) {
455
+ merged.runtimeVersion = "latest"
456
+ }
457
+ if (!merged.npmRegistry) {
458
+ merged.npmRegistry = defaultNpmRegistry
459
+ }
460
+ return merged
461
+ }
462
+
463
+ function runtimeConfigFromSource(sourceRoot) {
464
+ const remote = gitRemoteUrl(sourceRoot)
465
+ const repo = inferGitHubRepo(remote) || defaultRepo
466
+ return {
467
+ ...(process.env.GITHUB_ACTIONS ? {} : { sourceRoot }),
468
+ ...(remote ? { remote } : {}),
469
+ repo,
470
+ releaseTag: defaultReleaseTag,
471
+ runtimePackage: runtimePackageName(platformId()),
472
+ runtimeVersion: "latest",
473
+ npmRegistry: defaultNpmRegistry,
474
+ }
475
+ }
476
+
477
+ function findExtractedPackage(extractRoot, platform) {
478
+ const expected = path.join(extractRoot, `vantaloom-${platform}`)
479
+ if (existsSync(expected)) {
480
+ return expected
481
+ }
482
+
483
+ const directories = readdirSync(extractRoot, { withFileTypes: true })
484
+ .filter((entry) => entry.isDirectory())
485
+ .map((entry) => path.join(extractRoot, entry.name))
486
+ if (directories.length === 1) {
487
+ return directories[0]
488
+ }
489
+
490
+ throw new Error(`could not find extracted Vantaloom package in ${extractRoot}`)
491
+ }
492
+
493
+ function findExtractedNpmPackage(extractRoot) {
494
+ const expected = path.join(extractRoot, "package")
495
+ if (existsSync(expected)) {
496
+ return expected
497
+ }
498
+
499
+ const directories = readdirSync(extractRoot, { withFileTypes: true })
500
+ .filter((entry) => entry.isDirectory())
501
+ .map((entry) => path.join(extractRoot, entry.name))
502
+ if (directories.length === 1) {
503
+ return directories[0]
504
+ }
505
+
506
+ throw new Error(`could not find extracted npm package in ${extractRoot}`)
507
+ }
508
+
509
+ function tryFindSourceRoot() {
510
+ try {
511
+ return findSourceRoot()
512
+ } catch {
513
+ return ""
514
+ }
515
+ }
516
+
517
+ function tryAssertSourceRoot(sourceRoot) {
518
+ try {
519
+ return assertSourceRoot(sourceRoot)
520
+ } catch {
521
+ return ""
522
+ }
523
+ }
524
+
525
+ function gitRemoteUrl(sourceRoot) {
526
+ const result = spawnSync("git", ["remote", "get-url", "origin"], {
527
+ cwd: sourceRoot,
528
+ encoding: "utf8",
529
+ })
530
+ if (result.status === 0) {
531
+ return result.stdout.trim()
532
+ }
533
+ return ""
534
+ }
535
+
536
+ function inferGitHubRepo(remote) {
537
+ if (!remote) {
538
+ return ""
539
+ }
540
+ const normalized = remote.replace(/\.git$/, "")
541
+ const httpsMatch = normalized.match(/github\.com[:/]([^/]+\/[^/]+)$/)
542
+ if (httpsMatch) {
543
+ return httpsMatch[1]
544
+ }
545
+ const sshMatch = normalized.match(/^[^:]+:([^/]+\/[^/]+)$/)
546
+ return sshMatch?.[1] ?? ""
547
+ }
548
+
549
+ async function copyStaticWeb(sourceRoot, buildWeb) {
550
+ const exportRoot = path.join(sourceRoot, "apps", "vantaloom", "out")
551
+ if (!existsSync(exportRoot)) {
552
+ throw new Error(
553
+ "missing Next static export output; let GitHub CI run production build, or pass --build-web for a local one-off build"
554
+ )
555
+ }
556
+
557
+ removeKnownPath(buildWeb, path.dirname(buildWeb))
558
+ await copyDir(exportRoot, buildWeb)
559
+ }
560
+
561
+ async function copyCliDirectory(target, sourceRoot, config) {
562
+ const sourceCliRoot = path.join(sourceRoot, "packages", "cli")
563
+ if (!existsSync(path.join(sourceCliRoot, "bin", "vantaloom.mjs"))) {
564
+ throw new Error(`missing source CLI package: ${sourceCliRoot}`)
565
+ }
566
+ removeKnownPath(target, path.dirname(target))
567
+ mkdirSync(target, { recursive: true })
568
+ await copyDir(sourceCliRoot, target)
569
+ await writeText(
570
+ path.join(target, "config.json"),
571
+ `${JSON.stringify(config, null, 2)}\n`
572
+ )
573
+ }
574
+
575
+ async function writeLauncher(prefix) {
576
+ if (process.platform === "win32") {
577
+ await writeText(
578
+ path.join(prefix, "vantaloom.cmd"),
579
+ `@echo off\r\nnode "%~dp0cli\\bin\\vantaloom.mjs" %*\r\n`
580
+ )
581
+ } else {
582
+ const launcher = `#!/usr/bin/env sh\nexec node "$(dirname "$0")/cli/bin/vantaloom.mjs" "$@"\n`
583
+ const launcherPath = path.join(prefix, "vantaloom")
584
+ await writeText(launcherPath, launcher)
585
+ chmodSync(launcherPath, 0o755)
586
+ }
587
+ }
588
+
589
+ function runCtl(command, options) {
590
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
591
+ const ctl = path.join(prefix, "bin", binaryName("vantaloomctl"))
592
+ if (!existsSync(ctl)) {
593
+ throw new Error(`missing installed runtime at ${prefix}; run "vantaloom install" first`)
594
+ }
595
+
596
+ const args = [command, "--prefix", prefix]
597
+ if (options.component) {
598
+ args.push("--component", options.component)
599
+ }
600
+ run(ctl, args)
601
+ }
602
+
603
+ function printPaths(options) {
604
+ const sourceRoot = findSourceRoot(options.source)
605
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
606
+ console.log(JSON.stringify({ sourceRoot, prefix }, null, 2))
607
+ }
608
+
609
+ function buildGo(sourceRoot, buildBin, name, targetPlatform) {
610
+ const goEnv = targetPlatform ? platformToGoEnv(targetPlatform) : {}
611
+ const isWindowsTarget = targetPlatform
612
+ ? targetPlatform.startsWith("win32")
613
+ : process.platform === "win32"
614
+ const ext = isWindowsTarget ? ".exe" : ""
615
+ const ldflags = name === "vantaloom-tray" ? "-s -w -H windowsgui" : "-s -w"
616
+ run("go", [
617
+ "build",
618
+ "-ldflags", ldflags,
619
+ "-o",
620
+ path.join(buildBin, `${name}${ext}`),
621
+ `./apps/api/cmd/${name}`,
622
+ ], { cwd: sourceRoot, env: { ...process.env, ...goEnv } })
623
+ }
624
+
625
+ async function copyDir(source, destination, options = {}) {
626
+ if (!existsSync(source)) {
627
+ throw new Error(`missing source directory: ${source}`)
628
+ }
629
+ removeKnownPath(destination, path.dirname(destination))
630
+ mkdirSync(destination, { recursive: true })
631
+ await cp(source, destination, {
632
+ recursive: true,
633
+ force: true,
634
+ dereference: options.dereference ?? true,
635
+ })
636
+ }
637
+
638
+ function writeBuildManifest(buildRoot, version, platform) {
639
+ const components = ["api", "agent", "web", "ctl"]
640
+ if (platform.startsWith("win32")) {
641
+ components.push("tray")
642
+ }
643
+ writeFileSync(path.join(buildRoot, "VERSION"), `${version}\n`)
644
+ writeFileSync(
645
+ path.join(buildRoot, "manifest.json"),
646
+ `${JSON.stringify(
647
+ {
648
+ name: "Vantaloom Local Runtime",
649
+ version,
650
+ platform,
651
+ updatedAt: new Date().toISOString(),
652
+ components,
653
+ },
654
+ null,
655
+ 2
656
+ )}\n`
657
+ )
658
+ }
659
+
660
+ function writeRuntimePackageMetadata(packageRoot, sourceRoot, platform) {
661
+ const version = npmPackageVersion(sourceRoot)
662
+ const { os: runtimeOS, cpu } = parsePlatformId(platform)
663
+ const name = runtimePackageName(platform)
664
+ writeFileSync(
665
+ path.join(packageRoot, "package.json"),
666
+ `${JSON.stringify(
667
+ {
668
+ name,
669
+ version,
670
+ private: false,
671
+ description: `Vantaloom local runtime for ${platform}.`,
672
+ type: "module",
673
+ os: [runtimeOS],
674
+ cpu: [cpu],
675
+ files: ["bin", "web", "cli", "manifest.json", "VERSION", "README.md"],
676
+ publishConfig: {
677
+ access: "public",
678
+ },
679
+ engines: {
680
+ node: ">=20",
681
+ },
682
+ },
683
+ null,
684
+ 2
685
+ )}\n`
686
+ )
687
+ writeFileSync(
688
+ path.join(packageRoot, "README.md"),
689
+ `# ${name}\n\nPlatform runtime package for Vantaloom ${platform}. Install @vantaloom/cli instead of this package directly.\n`
690
+ )
691
+ }
692
+
693
+ function findSourceRoot(sourceOption) {
694
+ if (sourceOption) {
695
+ return assertSourceRoot(path.resolve(sourceOption))
696
+ }
697
+ if (process.env.VANTALOOM_SOURCE) {
698
+ return assertSourceRoot(path.resolve(process.env.VANTALOOM_SOURCE))
699
+ }
700
+ if (existsSync(installedConfigPath)) {
701
+ const config = JSON.parse(readFileSync(installedConfigPath, "utf8"))
702
+ if (config.sourceRoot) {
703
+ return assertSourceRoot(path.resolve(config.sourceRoot))
704
+ }
705
+ }
706
+ return assertSourceRoot(repoCandidate)
707
+ }
708
+
709
+ function npmPackageVersion(sourceRoot) {
710
+ const packageJSON = readJSONIfExists(path.join(sourceRoot, "packages", "cli", "package.json"))
711
+ return packageJSON.version ?? "0.0.0"
712
+ }
713
+
714
+ function assertSourceRoot(sourceRoot) {
715
+ if (!existsSync(path.join(sourceRoot, "apps", "api", "go.mod"))) {
716
+ throw new Error(`not a Vantaloom source root: ${sourceRoot}`)
717
+ }
718
+ return sourceRoot
719
+ }
720
+
721
+ function gitVersion(sourceRoot) {
722
+ const result = spawnSync("git", ["rev-parse", "--short", "HEAD"], {
723
+ cwd: sourceRoot,
724
+ encoding: "utf8",
725
+ })
726
+ if (result.status === 0) {
727
+ return result.stdout.trim() || "dev"
728
+ }
729
+ return "dev"
730
+ }
731
+
732
+ function parseOptions(args) {
733
+ const options = {}
734
+ for (let index = 0; index < args.length; index += 1) {
735
+ const arg = args[index]
736
+ if (!arg.startsWith("--")) {
737
+ throw new Error(`unexpected argument "${arg}"`)
738
+ }
739
+ const [key, inlineValue] = arg.slice(2).split("=", 2)
740
+ switch (key) {
741
+ case "prefix":
742
+ case "source":
743
+ case "component":
744
+ case "package":
745
+ case "output":
746
+ case "repo":
747
+ case "remote":
748
+ case "release-tag":
749
+ case "github-token":
750
+ case "runtime-package":
751
+ case "runtime-version":
752
+ case "npm-registry":
753
+ options[toCamel(key)] = inlineValue ?? args[++index]
754
+ if (!options[toCamel(key)]) {
755
+ throw new Error(`missing value for --${key}`)
756
+ }
757
+ break
758
+ case "build-web":
759
+ options.buildWeb = true
760
+ break
761
+ case "no-start":
762
+ options.noStart = true
763
+ break
764
+ case "archive":
765
+ options.archive = true
766
+ break
767
+ case "npm-package":
768
+ options.npmPackage = true
769
+ break
770
+ case "target":
771
+ options.target = inlineValue ?? args[++index]
772
+ if (!options.target) {
773
+ throw new Error("missing value for --target")
774
+ }
775
+ break
776
+ case "local":
777
+ options.local = true
778
+ break
779
+ default:
780
+ throw new Error(`unknown option --${key}`)
781
+ }
782
+ }
783
+ return options
784
+ }
785
+
786
+ function safeDirectory(value) {
787
+ const full = path.resolve(value)
788
+ const parsed = path.parse(full)
789
+ if (full === parsed.root) {
790
+ throw new Error(`refusing to operate on unsafe directory: ${value}`)
791
+ }
792
+ return full
793
+ }
794
+
795
+ function removeKnownPath(target, expectedParent) {
796
+ if (!existsSync(target)) {
797
+ return
798
+ }
799
+ const full = path.resolve(target)
800
+ const parent = path.resolve(expectedParent)
801
+ const prefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`
802
+ if (!full.startsWith(prefix)) {
803
+ throw new Error(`refusing to remove path outside expected parent: ${full}`)
804
+ }
805
+ rmSync(full, { recursive: true, force: true })
806
+ }
807
+
808
+ function run(command, args, options = {}) {
809
+ try {
810
+ execFileSync(command, args, { stdio: "inherit", ...options })
811
+ } catch (error) {
812
+ if (error && typeof error.status === "number") {
813
+ throw new Error(`${command} exited with ${error.status}`)
814
+ }
815
+ throw error
816
+ }
817
+ }
818
+
819
+ function runPnpm(args, options = {}) {
820
+ if (process.platform === "win32") {
821
+ run("cmd.exe", ["/d", "/s", "/c", "pnpm", ...args], options)
822
+ return
823
+ }
824
+ run("pnpm", args, options)
825
+ }
826
+
827
+ function binaryName(name, targetPlatform) {
828
+ const isWindows = targetPlatform
829
+ ? targetPlatform.startsWith("win32")
830
+ : process.platform === "win32"
831
+ return isWindows ? `${name}.exe` : name
832
+ }
833
+
834
+ function platformToGoEnv(platform) {
835
+ const { os: runtimeOS, cpu } = parsePlatformId(platform)
836
+ const goosMap = { win32: "windows", linux: "linux", darwin: "darwin" }
837
+ const goarchMap = { x64: "amd64", arm64: "arm64" }
838
+ return {
839
+ GOOS: goosMap[runtimeOS] ?? runtimeOS,
840
+ GOARCH: goarchMap[cpu] ?? cpu,
841
+ CGO_ENABLED: "0",
842
+ }
843
+ }
844
+
845
+ function platformId() {
846
+ return `${process.platform}-${process.arch}`
847
+ }
848
+
849
+ function runtimePackageName(platform) {
850
+ return `@vantaloom/runtime-${platform}`
851
+ }
852
+
853
+ function parsePlatformId(platform) {
854
+ const parts = platform.split("-")
855
+ const cpu = parts.pop()
856
+ const runtimeOS = parts.join("-")
857
+ if (!runtimeOS || !cpu) {
858
+ throw new Error(`invalid platform id: ${platform}`)
859
+ }
860
+ return { os: runtimeOS, cpu }
861
+ }
862
+
863
+ function packageBasename(name) {
864
+ return name.split("/").pop() ?? name
865
+ }
866
+
867
+ function normalizeRegistry(value) {
868
+ return value.replace(/\/+$/, "")
869
+ }
870
+
871
+ function defaultPrefix() {
872
+ if (process.env.VANTALOOM_HOME) {
873
+ return process.env.VANTALOOM_HOME
874
+ }
875
+ if (process.platform === "win32") {
876
+ return "D:\\Vantaloom"
877
+ }
878
+ if (process.platform === "darwin") {
879
+ return path.join(os.homedir(), "Applications", "Vantaloom")
880
+ }
881
+ return path.join(os.homedir(), ".local", "vantaloom")
882
+ }
883
+
884
+ function displayCommand(prefix) {
885
+ if (process.platform === "win32") {
886
+ return path.join(prefix, "vantaloom.cmd")
887
+ }
888
+ return path.join(prefix, "vantaloom")
889
+ }
890
+
891
+ function toCamel(value) {
892
+ return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
893
+ }
894
+
895
+ async function writeText(filePath, content) {
896
+ mkdirSync(path.dirname(filePath), { recursive: true })
897
+ await writeFile(filePath, content)
898
+ }
899
+
900
+ function printHelp() {
901
+ console.log(`Vantaloom CLI
902
+
903
+ Usage:
904
+ vantaloom install [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--package <dir>] [--no-start]
905
+ vantaloom install --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
906
+ vantaloom update [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--no-start]
907
+ vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
908
+ vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive] [--npm-package] [--target <platform>]
909
+ vantaloom start [--prefix <dir>] [--component all|api|agent|web]
910
+ vantaloom stop [--prefix <dir>] [--component all|api|agent|web]
911
+ vantaloom restart [--prefix <dir>] [--component all|api|agent|web]
912
+ vantaloom status [--prefix <dir>]
913
+ vantaloom ports [--prefix <dir>]
914
+ vantaloom path [--prefix <dir>] [--source <repo>]
915
+ vantaloom platform
916
+ `)
917
+ }