@vibecodetown/mcp-server 2.2.4 → 2.2.6
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/build/bootstrap/installer.js +233 -0
- package/build/generated/contracts_bundle_info.js +2 -2
- package/build/generated/doctor_output.js +1 -1
- package/build/generated/update_input.js +1 -1
- package/build/generated/update_output.js +1 -1
- package/build/local-mode/version-lock.js +10 -0
- package/build/tools/vibe_pm/update.js +372 -59
- package/package.json +1 -1
|
@@ -437,6 +437,239 @@ export async function checkUpdates() {
|
|
|
437
437
|
}
|
|
438
438
|
return result;
|
|
439
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Parse engine version from asset filename
|
|
442
|
+
* Format: {prefix}_{version}_{platform}.tar.gz
|
|
443
|
+
* Example: spec-high_1.0.0_linux_x64.tar.gz
|
|
444
|
+
*/
|
|
445
|
+
function parseEngineVersionFromAsset(assetName) {
|
|
446
|
+
const match = assetName.match(/^(.+?)_(\d+\.\d+\.\d+)_(.+)\.tar\.gz$/);
|
|
447
|
+
if (!match)
|
|
448
|
+
return null;
|
|
449
|
+
return {
|
|
450
|
+
prefix: match[1],
|
|
451
|
+
version: match[2],
|
|
452
|
+
platform: match[3],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Find the latest engines release from GitHub
|
|
457
|
+
*/
|
|
458
|
+
async function discoverLatestEnginesRelease(repo) {
|
|
459
|
+
if (isOfflineMode())
|
|
460
|
+
return null;
|
|
461
|
+
const apiBase = `https://api.github.com/repos/${repo}/releases`;
|
|
462
|
+
try {
|
|
463
|
+
const controller = new AbortController();
|
|
464
|
+
const timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
465
|
+
// Get all releases and find latest engines-v* tag
|
|
466
|
+
const res = await fetch(`${apiBase}?per_page=20`, {
|
|
467
|
+
signal: controller.signal,
|
|
468
|
+
headers: {
|
|
469
|
+
...getAuthHeaders(),
|
|
470
|
+
"Accept": "application/vnd.github+json",
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
clearTimeout(timeoutId);
|
|
474
|
+
if (!res.ok)
|
|
475
|
+
return null;
|
|
476
|
+
const releases = await res.json();
|
|
477
|
+
// Find latest engines release
|
|
478
|
+
const enginesRelease = releases.find(r => r.tag_name.startsWith("engines-v"));
|
|
479
|
+
if (enginesRelease) {
|
|
480
|
+
if (isDebugMode()) {
|
|
481
|
+
console.log(`[debug] Found latest engines release: ${enginesRelease.tag_name}`);
|
|
482
|
+
}
|
|
483
|
+
return enginesRelease;
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
catch (e) {
|
|
488
|
+
if (isDebugMode()) {
|
|
489
|
+
console.log(`[debug] Failed to discover latest release: ${e}`);
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Extract engine versions from release assets
|
|
496
|
+
*/
|
|
497
|
+
function extractEngineVersionsFromRelease(release, platform) {
|
|
498
|
+
const versions = new Map();
|
|
499
|
+
for (const asset of release.assets) {
|
|
500
|
+
const parsed = parseEngineVersionFromAsset(asset.name);
|
|
501
|
+
if (!parsed || parsed.platform !== platform)
|
|
502
|
+
continue;
|
|
503
|
+
// Map asset prefix to engine name
|
|
504
|
+
const engineName = Object.keys(ENGINE_SPECS).find(name => ENGINE_SPECS[name].assetPrefix === parsed.prefix);
|
|
505
|
+
if (engineName) {
|
|
506
|
+
versions.set(engineName, {
|
|
507
|
+
name: engineName,
|
|
508
|
+
version: parsed.version,
|
|
509
|
+
assetName: asset.name,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return versions;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Check for remote updates from GitHub Releases
|
|
517
|
+
* This allows updates without npm package update
|
|
518
|
+
*/
|
|
519
|
+
export async function checkRemoteUpdates() {
|
|
520
|
+
const platform = detectPlatform();
|
|
521
|
+
const result = {
|
|
522
|
+
available: false,
|
|
523
|
+
engines: [],
|
|
524
|
+
releaseTag: null,
|
|
525
|
+
};
|
|
526
|
+
// Use first engine's repo (all engines use same repo)
|
|
527
|
+
const repo = ENGINE_SPECS["spec-high"].repo;
|
|
528
|
+
const release = await discoverLatestEnginesRelease(repo);
|
|
529
|
+
if (!release) {
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
result.releaseTag = release.tag_name;
|
|
533
|
+
const remoteVersions = extractEngineVersionsFromRelease(release, platform);
|
|
534
|
+
for (const name of Object.keys(ENGINE_SPECS)) {
|
|
535
|
+
const currentVersion = await getCurrentVersion(name);
|
|
536
|
+
const remote = remoteVersions.get(name);
|
|
537
|
+
if (remote) {
|
|
538
|
+
const needsUpdate = currentVersion !== remote.version;
|
|
539
|
+
result.engines.push({
|
|
540
|
+
name,
|
|
541
|
+
currentVersion,
|
|
542
|
+
remoteVersion: remote.version,
|
|
543
|
+
needsUpdate,
|
|
544
|
+
});
|
|
545
|
+
if (needsUpdate) {
|
|
546
|
+
result.available = true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Update engines from remote release (ignoring registry.ts versions)
|
|
554
|
+
*/
|
|
555
|
+
export async function updateFromRemote(force = false) {
|
|
556
|
+
const platform = detectPlatform();
|
|
557
|
+
const repo = ENGINE_SPECS["spec-high"].repo;
|
|
558
|
+
const release = await discoverLatestEnginesRelease(repo);
|
|
559
|
+
const updated = [];
|
|
560
|
+
const failed = [];
|
|
561
|
+
if (!release) {
|
|
562
|
+
if (isDebugMode()) {
|
|
563
|
+
console.log("[debug] No remote release found");
|
|
564
|
+
}
|
|
565
|
+
return { updated, failed };
|
|
566
|
+
}
|
|
567
|
+
const remoteVersions = extractEngineVersionsFromRelease(release, platform);
|
|
568
|
+
for (const name of Object.keys(ENGINE_SPECS)) {
|
|
569
|
+
const remote = remoteVersions.get(name);
|
|
570
|
+
if (!remote)
|
|
571
|
+
continue;
|
|
572
|
+
const currentVersion = await getCurrentVersion(name);
|
|
573
|
+
const needsUpdate = force || currentVersion !== remote.version;
|
|
574
|
+
if (!needsUpdate)
|
|
575
|
+
continue;
|
|
576
|
+
try {
|
|
577
|
+
// Download and install from remote
|
|
578
|
+
await installEngineFromRelease(name, release, remote.version, platform);
|
|
579
|
+
updated.push(name);
|
|
580
|
+
if (isDebugMode()) {
|
|
581
|
+
console.log(`[debug] Updated ${name}: ${currentVersion} → ${remote.version}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
failed.push(name);
|
|
586
|
+
if (isDebugMode()) {
|
|
587
|
+
console.log(`[debug] Failed to update ${name}: ${e}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return { updated, failed };
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Install a specific engine from a GitHub release
|
|
595
|
+
*/
|
|
596
|
+
async function installEngineFromRelease(name, release, version, platform) {
|
|
597
|
+
const spec = ENGINE_SPECS[name];
|
|
598
|
+
const asset = `${spec.assetPrefix}_${version}_${platform}.tar.gz`;
|
|
599
|
+
const dir = engineDir(name, version, platform);
|
|
600
|
+
const bin = path.join(dir, exeName(spec.assetPrefix));
|
|
601
|
+
const marker = path.join(dir, ".installed");
|
|
602
|
+
const archivePath = path.join(dir, asset);
|
|
603
|
+
const assetInfo = findAsset(release, asset);
|
|
604
|
+
const shaInfo = findAsset(release, spec.shaSumsAsset);
|
|
605
|
+
if (!assetInfo) {
|
|
606
|
+
throw new Error(`Asset not found: ${asset}`);
|
|
607
|
+
}
|
|
608
|
+
// Read SHA256
|
|
609
|
+
let shaMap;
|
|
610
|
+
if (shaInfo) {
|
|
611
|
+
const token = getGitHubToken();
|
|
612
|
+
if (token) {
|
|
613
|
+
const res = await fetchWithRetry(shaInfo.url, MAX_RETRIES, {
|
|
614
|
+
"Accept": "application/octet-stream",
|
|
615
|
+
});
|
|
616
|
+
const text = await res.text();
|
|
617
|
+
shaMap = new Map();
|
|
618
|
+
for (const line of text.split("\n")) {
|
|
619
|
+
const t = line.trim();
|
|
620
|
+
if (!t)
|
|
621
|
+
continue;
|
|
622
|
+
const m = t.match(/^([a-fA-F0-9]{64})\s+(.+)$/);
|
|
623
|
+
if (m)
|
|
624
|
+
shaMap.set(m[2].trim(), m[1].toLowerCase());
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
shaMap = await readShaSumsFromUrl(shaInfo.browser_download_url);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
throw new Error(`SHA256SUMS not found in release`);
|
|
633
|
+
}
|
|
634
|
+
const expected = shaMap.get(asset);
|
|
635
|
+
if (!expected)
|
|
636
|
+
throw new Error(`sha_missing_for_asset:${asset}`);
|
|
637
|
+
// Clean and prepare directory
|
|
638
|
+
await fs.promises.rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
639
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
640
|
+
// Download
|
|
641
|
+
if (getGitHubToken()) {
|
|
642
|
+
await downloadAsset(assetInfo, archivePath);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
await download(assetInfo.browser_download_url, archivePath);
|
|
646
|
+
}
|
|
647
|
+
// Verify SHA256
|
|
648
|
+
const got = (await sha256File(archivePath)).toLowerCase();
|
|
649
|
+
if (got !== expected)
|
|
650
|
+
throw new Error(`sha_mismatch:${asset}`);
|
|
651
|
+
// Extract
|
|
652
|
+
await extractTarGz(archivePath, dir);
|
|
653
|
+
// Find binary
|
|
654
|
+
const candidates = [
|
|
655
|
+
path.join(dir, exeName(spec.assetPrefix)),
|
|
656
|
+
path.join(dir, spec.assetPrefix, exeName(spec.assetPrefix))
|
|
657
|
+
];
|
|
658
|
+
const found = await firstExisting(candidates);
|
|
659
|
+
if (!found)
|
|
660
|
+
throw new Error(`bin_not_found_after_extract:${spec.assetPrefix}`);
|
|
661
|
+
// Normalize location
|
|
662
|
+
if (found !== bin) {
|
|
663
|
+
await fs.promises.copyFile(found, bin);
|
|
664
|
+
}
|
|
665
|
+
await makeExecutable(bin);
|
|
666
|
+
await fs.promises.writeFile(marker, `ok ${new Date().toISOString()}\n`, "utf-8");
|
|
667
|
+
// Update version tracker
|
|
668
|
+
await setCurrentVersion(name, version);
|
|
669
|
+
// Clean up
|
|
670
|
+
await fs.promises.rm(archivePath, { force: true }).catch(() => { });
|
|
671
|
+
return bin;
|
|
672
|
+
}
|
|
440
673
|
// ============================================================
|
|
441
674
|
// Cache Validation
|
|
442
675
|
// ============================================================
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated from schemas/contracts.version.json + schemas/contracts.lock.json
|
|
2
2
|
// DO NOT EDIT MANUALLY - run scripts/generate-contracts.sh
|
|
3
|
-
export const CONTRACTS_VERSION = "1.8.
|
|
4
|
-
export const CONTRACTS_BUNDLE_SHA256 = "
|
|
3
|
+
export const CONTRACTS_VERSION = "1.8.7";
|
|
4
|
+
export const CONTRACTS_BUNDLE_SHA256 = "a1e258a6eda62a87093b91e7bc28e2cde414450a05c26f3ba52b0e6eaa0660e1";
|
|
5
5
|
export const CONTRACTS_SCHEMA_COUNT = 103;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const DoctorOutputSchema = z.object({ "status": z.enum(["OK", "FIXED", "NEEDS_ATTENTION", "ERROR"]), "summary": z.string().min(1), "contracts": z.object({ "version": z.string().min(1), "bundle_sha256": z.string().min(1), "schema_count": z.number().int().gte(0) }).strict().optional(), "engines": z.array(z.object({ "name": z.string().min(1), "status": z.enum(["정상", "업데이트 필요", "설치 필요", "손상됨"]), "version": z.string().min(1), "current_version": z.union([z.string(), z.null()]) }).strict()), "actions_taken": z.array(z.string()).optional(), "local_memory": z.any().superRefine((x, ctx) => {
|
|
2
|
+
export const DoctorOutputSchema = z.object({ "status": z.enum(["OK", "FIXED", "NEEDS_ATTENTION", "ERROR"]), "summary": z.string().min(1), "contracts": z.object({ "version": z.string().min(1), "bundle_sha256": z.string().min(1), "schema_count": z.number().int().gte(0) }).strict().optional(), "engines": z.array(z.object({ "name": z.string().min(1), "status": z.enum(["정상", "업데이트 필요", "설치 필요", "손상됨"]), "version": z.string().min(1), "current_version": z.union([z.string(), z.null()]) }).strict()), "actions_taken": z.array(z.string()).optional(), "python_cli": z.object({ "status": z.enum(["정상", "오류"]), "message": z.string().min(1), "pythonpath_detected": z.union([z.string(), z.null()]) }).strict().optional(), "skills": z.object({ "status": z.enum(["OK", "WARN", "ERROR"]), "version": z.union([z.string(), z.null()]), "installed": z.number().int().gte(0), "total": z.number().int().gte(0) }).strict().optional(), "remote_updates": z.object({ "available": z.boolean(), "release_tag": z.union([z.string(), z.null()]), "updates": z.array(z.object({ "name": z.string().min(1), "current": z.union([z.string(), z.null()]), "remote": z.string().min(1) }).strict()) }).strict().optional(), "local_memory": z.any().superRefine((x, ctx) => {
|
|
3
3
|
const schemas = [z.object({ "project_id": z.string().min(1), "status": z.enum(["READY", "NEEDS_SYNC", "OFFLINE_NO_INDEX", "ERROR"]), "summary": z.string().min(1), "docs_root": z.string().min(1), "persist_dir": z.string().min(1), "collection": z.string().min(1), "stats": z.object({ "document_count": z.number().int().gte(0), "chunk_count": z.number().int().gte(0), "last_sync_at": z.union([z.string(), z.null()]).optional() }).strict(), "embedding": z.object({ "backend": z.string().min(1), "model": z.union([z.string(), z.null()]).optional(), "ready": z.boolean() }).strict().optional(), "offline_mode": z.boolean(), "issues": z.array(z.string()), "next_action": z.any().superRefine((x, ctx) => {
|
|
4
4
|
const schemas = [z.object({ "tool": z.enum(["vibe_pm.memory_sync", "vibe_pm.doctor"]), "reason": z.string().min(1) }).strict(), z.null()];
|
|
5
5
|
const errors = schemas.reduce((errors, schema) => ((result) => result.error ? [...errors, result.error] : errors)(schema.safeParse(x)), []);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const UpdateInputSchema = z.object({ "force": z.boolean().describe("캐시를 무시하고 강제로 재설치할지 여부 (기본: false)").default(false), "engines": z.array(z.string().min(1)).describe("업데이트할 엔진 목록 (미지정 시
|
|
2
|
+
export const UpdateInputSchema = z.object({ "target": z.enum(["engines", "npm", "local", "all"]).describe("업데이트 대상: engines(엔진 바이너리), npm(npm 패키지), local(로컬 git pull + build), all(전체)").default("engines"), "force": z.boolean().describe("캐시를 무시하고 강제로 재설치할지 여부 (기본: false)").default(false), "engines": z.array(z.string().min(1)).describe("업데이트할 엔진 목록 (미지정 시 전체, target=engines일 때만 적용)").optional(), "local_path": z.string().min(1).describe("로컬 개발 폴더 경로 (target=local/all일 때 사용, 미지정 시 VIBE_LOCAL_DEV_PATH 환경변수 사용)").optional() }).strict().describe("SSOT schema for vibe_pm.update MCP tool input");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const UpdateOutputSchema = z.object({ "status": z.enum(["OK", "PARTIAL", "ERROR"]), "summary": z.string().min(1), "results": z.array(z.object({ "name": z.string().min(1), "action": z.enum(["updated", "skipped", "failed"]), "from_version": z.union([z.string(), z.null()]), "to_version": z.string().min(1), "message": z.string().min(1) }).strict()), "next_action": z.object({ "type": z.enum(["NONE", "RETRY", "CONTACT_SUPPORT"]), "message": z.string().min(1) }).strict() }).strict().describe("SSOT schema for vibe_pm.update MCP tool output");
|
|
2
|
+
export const UpdateOutputSchema = z.object({ "status": z.enum(["OK", "PARTIAL", "ERROR"]), "summary": z.string().min(1), "results": z.array(z.object({ "name": z.string().min(1), "action": z.enum(["updated", "skipped", "failed"]), "from_version": z.union([z.string(), z.null()]), "to_version": z.string().min(1), "message": z.string().min(1) }).strict()).describe("엔진 업데이트 결과 목록 (target=engines/all일 때)").optional(), "npm_result": z.object({ "action": z.enum(["updated", "skipped", "failed"]), "from_version": z.union([z.string(), z.null()]).optional(), "to_version": z.union([z.string(), z.null()]).optional(), "message": z.string().min(1) }).strict().describe("npm 패키지 업데이트 결과 (target=npm/all일 때)").optional(), "local_result": z.object({ "action": z.enum(["updated", "skipped", "failed"]), "path": z.string().min(1).optional(), "git_status": z.string().optional(), "build_status": z.string().optional(), "message": z.string().min(1) }).strict().describe("로컬 빌드 업데이트 결과 (target=local/all일 때)").optional(), "next_action": z.object({ "type": z.enum(["NONE", "RETRY", "RESTART_CLAUDE", "CONTACT_SUPPORT"]), "message": z.string().min(1) }).strict() }).strict().describe("SSOT schema for vibe_pm.update MCP tool output");
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/local-mode/version-lock.ts
|
|
2
2
|
// Version lock management for unified installation
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { getVibeRepoPaths } from "./paths.js";
|
|
6
7
|
export const VersionLockSchema = z.object({
|
|
7
8
|
schema_version: z.literal(1),
|
|
9
|
+
repo_root: z.string().min(1).optional(),
|
|
8
10
|
created_at: z.string(),
|
|
9
11
|
updated_at: z.string(),
|
|
10
12
|
cli: z.object({
|
|
@@ -18,6 +20,7 @@ export const VersionLockSchema = z.object({
|
|
|
18
20
|
});
|
|
19
21
|
/**
|
|
20
22
|
* Read version lock file from repo
|
|
23
|
+
* Auto-injects repo_root if missing (migration for existing files)
|
|
21
24
|
*/
|
|
22
25
|
export function readVersionLock(repoRoot) {
|
|
23
26
|
const paths = getVibeRepoPaths(repoRoot);
|
|
@@ -25,6 +28,10 @@ export function readVersionLock(repoRoot) {
|
|
|
25
28
|
return null;
|
|
26
29
|
try {
|
|
27
30
|
const data = JSON.parse(fs.readFileSync(paths.versionLockFile, "utf-8"));
|
|
31
|
+
// Auto-inject repo_root if missing (migration for existing lock files)
|
|
32
|
+
if (!data.repo_root) {
|
|
33
|
+
data.repo_root = path.resolve(repoRoot);
|
|
34
|
+
}
|
|
28
35
|
return VersionLockSchema.parse(data);
|
|
29
36
|
}
|
|
30
37
|
catch {
|
|
@@ -38,8 +45,11 @@ export function writeVersionLock(repoRoot, cliVersion, engines) {
|
|
|
38
45
|
const paths = getVibeRepoPaths(repoRoot);
|
|
39
46
|
const now = new Date().toISOString();
|
|
40
47
|
const existing = readVersionLock(repoRoot);
|
|
48
|
+
// Store absolute path for later reference (e.g., vibe_pm.update with target=local)
|
|
49
|
+
const absoluteRepoRoot = path.resolve(repoRoot);
|
|
41
50
|
const lock = {
|
|
42
51
|
schema_version: 1,
|
|
52
|
+
repo_root: absoluteRepoRoot,
|
|
43
53
|
created_at: existing?.created_at ?? now,
|
|
44
54
|
updated_at: now,
|
|
45
55
|
cli: { name: "@vibecode/mcp-server", version: cliVersion },
|
|
@@ -1,30 +1,193 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/tools/vibe_pm/update.ts
|
|
2
|
-
// vibe_pm.update -
|
|
2
|
+
// vibe_pm.update - Unified update tool (engines + npm + local build)
|
|
3
3
|
import { UpdateInputSchema } from "../../generated/update_input.js";
|
|
4
|
-
import { ensureEngines, checkUpdates, clearCache, getEngineHealth } from "../../bootstrap/installer.js";
|
|
4
|
+
import { ensureEngines, checkUpdates, clearCache, getEngineHealth, checkRemoteUpdates, updateFromRemote } from "../../bootstrap/installer.js";
|
|
5
5
|
import { ENGINE_SPECS } from "../../bootstrap/registry.js";
|
|
6
|
+
import { invokeCli } from "../../runtime/cli_invoker.js";
|
|
7
|
+
import { readVersionLock } from "../../local-mode/version-lock.js";
|
|
6
8
|
// ============================================================
|
|
7
9
|
// Input/Output Types
|
|
8
10
|
// ============================================================
|
|
9
11
|
export { UpdateInputSchema };
|
|
10
12
|
// ============================================================
|
|
11
|
-
//
|
|
13
|
+
// Helper Functions
|
|
12
14
|
// ============================================================
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* PM-friendly description:
|
|
17
|
-
* 엔진 바이너리를 최신 버전으로 업데이트합니다.
|
|
16
|
+
* Update npm package (@vibecodetown/mcp-server)
|
|
18
17
|
*/
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
18
|
+
async function updateNpmPackage() {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
try {
|
|
21
|
+
// Get current version
|
|
22
|
+
let fromVersion = null;
|
|
23
|
+
try {
|
|
24
|
+
const listResult = await invokeCli({
|
|
25
|
+
bin: "npm",
|
|
26
|
+
args: ["list", "@vibecodetown/mcp-server", "--json"],
|
|
27
|
+
cwd,
|
|
28
|
+
timeoutMs: 30000
|
|
29
|
+
});
|
|
30
|
+
if (listResult.exitCode === 0) {
|
|
31
|
+
const parsed = JSON.parse(listResult.stdout);
|
|
32
|
+
fromVersion = parsed.dependencies?.["@vibecodetown/mcp-server"]?.version ?? null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Not installed globally, might be using npx
|
|
37
|
+
}
|
|
38
|
+
// Clear npx cache and update
|
|
39
|
+
await invokeCli({
|
|
40
|
+
bin: "npm",
|
|
41
|
+
args: ["cache", "clean", "--force"],
|
|
42
|
+
cwd,
|
|
43
|
+
timeoutMs: 30000
|
|
44
|
+
});
|
|
45
|
+
// Try to update global package
|
|
46
|
+
try {
|
|
47
|
+
await invokeCli({
|
|
48
|
+
bin: "npm",
|
|
49
|
+
args: ["update", "-g", "@vibecodetown/mcp-server"],
|
|
50
|
+
cwd,
|
|
51
|
+
timeoutMs: 60000
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Global update failed, try clearing npx cache
|
|
56
|
+
}
|
|
57
|
+
// Get new version
|
|
58
|
+
let toVersion = null;
|
|
59
|
+
try {
|
|
60
|
+
const showResult = await invokeCli({
|
|
61
|
+
bin: "npm",
|
|
62
|
+
args: ["show", "@vibecodetown/mcp-server", "version"],
|
|
63
|
+
cwd,
|
|
64
|
+
timeoutMs: 30000
|
|
65
|
+
});
|
|
66
|
+
if (showResult.exitCode === 0) {
|
|
67
|
+
toVersion = showResult.stdout.trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Could not fetch remote version
|
|
72
|
+
}
|
|
73
|
+
if (toVersion && fromVersion !== toVersion) {
|
|
74
|
+
return {
|
|
75
|
+
action: "updated",
|
|
76
|
+
from_version: fromVersion,
|
|
77
|
+
to_version: toVersion,
|
|
78
|
+
message: "npm 패키지 업데이트 완료 (npx 캐시 클리어됨)"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
else if (toVersion) {
|
|
82
|
+
return {
|
|
83
|
+
action: "skipped",
|
|
84
|
+
from_version: fromVersion,
|
|
85
|
+
to_version: toVersion,
|
|
86
|
+
message: "이미 최신 버전입니다"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
return {
|
|
91
|
+
action: "failed",
|
|
92
|
+
from_version: fromVersion,
|
|
93
|
+
to_version: null,
|
|
94
|
+
message: "버전 확인 실패"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return {
|
|
100
|
+
action: "failed",
|
|
101
|
+
from_version: null,
|
|
102
|
+
to_version: null,
|
|
103
|
+
message: e instanceof Error ? e.message : "npm 업데이트 실패"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Update local development build (git pull + npm run build)
|
|
109
|
+
*/
|
|
110
|
+
async function updateLocalBuild(localPath) {
|
|
111
|
+
const result = {
|
|
112
|
+
action: "updated",
|
|
113
|
+
path: localPath,
|
|
114
|
+
git_status: "",
|
|
115
|
+
build_status: "",
|
|
116
|
+
message: ""
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
// Step 1: git pull
|
|
120
|
+
const gitResult = await invokeCli({
|
|
121
|
+
bin: "git",
|
|
122
|
+
args: ["pull", "origin", "main"],
|
|
123
|
+
cwd: localPath,
|
|
124
|
+
timeoutMs: 60000
|
|
125
|
+
});
|
|
126
|
+
if (gitResult.exitCode !== 0) {
|
|
127
|
+
result.git_status = "failed";
|
|
128
|
+
result.action = "failed";
|
|
129
|
+
result.message = `git pull 실패: ${gitResult.stderr || gitResult.stdout}`;
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
result.git_status = gitResult.stdout.includes("Already up to date")
|
|
133
|
+
? "already_up_to_date"
|
|
134
|
+
: "pulled";
|
|
135
|
+
// Step 2: npm run build (in adapters/mcp-ts)
|
|
136
|
+
const mcpTsPath = `${localPath}/adapters/mcp-ts`;
|
|
137
|
+
const buildResult = await invokeCli({
|
|
138
|
+
bin: "npm",
|
|
139
|
+
args: ["run", "build"],
|
|
140
|
+
cwd: mcpTsPath,
|
|
141
|
+
timeoutMs: 60000
|
|
142
|
+
});
|
|
143
|
+
if (buildResult.exitCode !== 0) {
|
|
144
|
+
result.build_status = "failed";
|
|
145
|
+
result.action = "failed";
|
|
146
|
+
result.message = `빌드 실패: ${buildResult.stderr || buildResult.stdout}`;
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
result.build_status = "success";
|
|
150
|
+
// Success
|
|
151
|
+
if (result.git_status === "already_up_to_date") {
|
|
152
|
+
result.action = "skipped";
|
|
153
|
+
result.message = "이미 최신 상태입니다 (빌드 갱신됨)";
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
result.action = "updated";
|
|
157
|
+
result.message = "git pull + 빌드 완료";
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
return {
|
|
163
|
+
action: "failed",
|
|
164
|
+
path: localPath,
|
|
165
|
+
git_status: "error",
|
|
166
|
+
build_status: "error",
|
|
167
|
+
message: e instanceof Error ? e.message : "로컬 업데이트 실패"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Update engines (existing logic extracted)
|
|
173
|
+
*/
|
|
174
|
+
async function updateEngines(targetEngines, force) {
|
|
22
175
|
const results = [];
|
|
23
176
|
try {
|
|
24
|
-
//
|
|
177
|
+
// Check for remote updates first (GitHub Releases)
|
|
178
|
+
const remoteUpdates = await checkRemoteUpdates();
|
|
179
|
+
const hasRemoteUpdates = remoteUpdates.available;
|
|
180
|
+
// Check local registry updates
|
|
25
181
|
const updateStatus = await checkUpdates();
|
|
26
|
-
//
|
|
182
|
+
// Determine what to update (prefer remote versions)
|
|
27
183
|
const enginesToUpdate = [];
|
|
184
|
+
const remoteVersionMap = new Map();
|
|
185
|
+
// Build remote version map
|
|
186
|
+
for (const engine of remoteUpdates.engines) {
|
|
187
|
+
if (engine.needsUpdate) {
|
|
188
|
+
remoteVersionMap.set(engine.name, engine.remoteVersion);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
28
191
|
for (const name of targetEngines) {
|
|
29
192
|
if (!(name in ENGINE_SPECS)) {
|
|
30
193
|
results.push({
|
|
@@ -38,7 +201,9 @@ export async function update(input) {
|
|
|
38
201
|
}
|
|
39
202
|
const engineName = name;
|
|
40
203
|
const status = updateStatus[engineName];
|
|
41
|
-
|
|
204
|
+
const remoteVersion = remoteVersionMap.get(engineName);
|
|
205
|
+
// Update if: force mode, local needs update, OR remote has newer version
|
|
206
|
+
if (force || status.needsUpdate || remoteVersion) {
|
|
42
207
|
enginesToUpdate.push(engineName);
|
|
43
208
|
}
|
|
44
209
|
else {
|
|
@@ -51,84 +216,230 @@ export async function update(input) {
|
|
|
51
216
|
});
|
|
52
217
|
}
|
|
53
218
|
}
|
|
54
|
-
//
|
|
219
|
+
// Clear cache if force mode
|
|
55
220
|
if (force) {
|
|
56
221
|
for (const name of enginesToUpdate) {
|
|
57
222
|
await clearCache(name);
|
|
58
223
|
}
|
|
59
224
|
}
|
|
60
|
-
//
|
|
225
|
+
// Update engines
|
|
61
226
|
if (enginesToUpdate.length > 0) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
227
|
+
if (hasRemoteUpdates) {
|
|
228
|
+
const remoteResult = await updateFromRemote(force);
|
|
229
|
+
const newHealth = await getEngineHealth();
|
|
230
|
+
for (const name of enginesToUpdate) {
|
|
231
|
+
const health = newHealth.find((h) => h.name === name);
|
|
232
|
+
const oldStatus = updateStatus[name];
|
|
233
|
+
const remoteVersion = remoteVersionMap.get(name);
|
|
234
|
+
if (remoteResult.updated.includes(name)) {
|
|
235
|
+
results.push({
|
|
236
|
+
name,
|
|
237
|
+
action: "updated",
|
|
238
|
+
from_version: oldStatus.current,
|
|
239
|
+
to_version: remoteVersion ?? health?.version ?? oldStatus.required,
|
|
240
|
+
message: force ? "강제 재설치 완료 (원격)" : "업데이트 완료 (원격)"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else if (remoteResult.failed.includes(name)) {
|
|
244
|
+
results.push({
|
|
245
|
+
name,
|
|
246
|
+
action: "failed",
|
|
247
|
+
from_version: oldStatus.current,
|
|
248
|
+
to_version: remoteVersion ?? oldStatus.required,
|
|
249
|
+
message: "원격 업데이트 실패"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else if (health?.status === "ok") {
|
|
253
|
+
results.push({
|
|
254
|
+
name,
|
|
255
|
+
action: "skipped",
|
|
256
|
+
from_version: oldStatus.current,
|
|
257
|
+
to_version: health.version,
|
|
258
|
+
message: "이미 최신 버전입니다"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Fallback to local registry update
|
|
265
|
+
await ensureEngines();
|
|
266
|
+
const newHealth = await getEngineHealth();
|
|
267
|
+
for (const name of enginesToUpdate) {
|
|
268
|
+
const health = newHealth.find((h) => h.name === name);
|
|
269
|
+
const oldStatus = updateStatus[name];
|
|
270
|
+
if (health?.status === "ok") {
|
|
271
|
+
results.push({
|
|
272
|
+
name,
|
|
273
|
+
action: "updated",
|
|
274
|
+
from_version: oldStatus.current,
|
|
275
|
+
to_version: health.version,
|
|
276
|
+
message: force ? "강제 재설치 완료" : "업데이트 완료"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
results.push({
|
|
281
|
+
name,
|
|
282
|
+
action: "failed",
|
|
283
|
+
from_version: oldStatus.current,
|
|
284
|
+
to_version: oldStatus.required,
|
|
285
|
+
message: `업데이트 실패: ${health?.status ?? "unknown"}`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
77
288
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return results;
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
results.push({
|
|
295
|
+
name: "engines",
|
|
296
|
+
action: "failed",
|
|
297
|
+
from_version: null,
|
|
298
|
+
to_version: "unknown",
|
|
299
|
+
message: e instanceof Error ? e.message : "엔진 업데이트 실패"
|
|
300
|
+
});
|
|
301
|
+
return results;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// ============================================================
|
|
305
|
+
// Main Update Function
|
|
306
|
+
// ============================================================
|
|
307
|
+
/**
|
|
308
|
+
* vibe_pm.update - Unified update tool
|
|
309
|
+
*
|
|
310
|
+
* PM-friendly description:
|
|
311
|
+
* 엔진, npm 패키지, 로컬 빌드를 업데이트합니다.
|
|
312
|
+
*
|
|
313
|
+
* target options:
|
|
314
|
+
* - "engines": 엔진 바이너리만 업데이트 (기본값)
|
|
315
|
+
* - "npm": npm 패키지만 업데이트
|
|
316
|
+
* - "local": 로컬 개발 환경만 업데이트 (git pull + build)
|
|
317
|
+
* - "all": 전체 업데이트
|
|
318
|
+
*/
|
|
319
|
+
export async function update(input) {
|
|
320
|
+
const target = input.target ?? "engines";
|
|
321
|
+
const force = input.force ?? false;
|
|
322
|
+
const targetEngines = input.engines ?? Object.keys(ENGINE_SPECS);
|
|
323
|
+
// Resolve local path: input > env > version_lock.json
|
|
324
|
+
let localPath = input.local_path ?? process.env.VIBE_LOCAL_DEV_PATH;
|
|
325
|
+
if (!localPath && (target === "local" || target === "all")) {
|
|
326
|
+
const versionLock = readVersionLock(process.cwd());
|
|
327
|
+
if (versionLock?.repo_root) {
|
|
328
|
+
localPath = versionLock.repo_root;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
let engineResults;
|
|
332
|
+
let npmResult;
|
|
333
|
+
let localResult;
|
|
334
|
+
let needsRestart = false;
|
|
335
|
+
try {
|
|
336
|
+
// Execute updates based on target
|
|
337
|
+
if (target === "engines" || target === "all") {
|
|
338
|
+
engineResults = await updateEngines(targetEngines, force);
|
|
339
|
+
}
|
|
340
|
+
if (target === "npm" || target === "all") {
|
|
341
|
+
npmResult = await updateNpmPackage();
|
|
342
|
+
if (npmResult.action === "updated") {
|
|
343
|
+
needsRestart = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (target === "local" || target === "all") {
|
|
347
|
+
if (!localPath) {
|
|
348
|
+
localResult = {
|
|
349
|
+
action: "failed",
|
|
350
|
+
path: "(not configured)",
|
|
351
|
+
git_status: "error",
|
|
352
|
+
build_status: "error",
|
|
353
|
+
message: "로컬 경로가 지정되지 않았습니다. 'vibe setup'을 먼저 실행하거나, local_path 파라미터를 지정하세요."
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
localResult = await updateLocalBuild(localPath);
|
|
358
|
+
if (localResult.action === "updated") {
|
|
359
|
+
needsRestart = true;
|
|
86
360
|
}
|
|
87
361
|
}
|
|
88
362
|
}
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
363
|
+
// Generate summary
|
|
364
|
+
const summaryParts = [];
|
|
365
|
+
let hasError = false;
|
|
366
|
+
let hasUpdate = false;
|
|
367
|
+
if (engineResults) {
|
|
368
|
+
const engineUpdated = engineResults.filter((r) => r.action === "updated").length;
|
|
369
|
+
const engineFailed = engineResults.filter((r) => r.action === "failed").length;
|
|
370
|
+
if (engineUpdated > 0) {
|
|
371
|
+
summaryParts.push(`엔진 ${engineUpdated}개 업데이트`);
|
|
372
|
+
hasUpdate = true;
|
|
373
|
+
}
|
|
374
|
+
if (engineFailed > 0) {
|
|
375
|
+
summaryParts.push(`엔진 ${engineFailed}개 실패`);
|
|
376
|
+
hasError = true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (npmResult) {
|
|
380
|
+
if (npmResult.action === "updated") {
|
|
381
|
+
summaryParts.push("npm 패키지 업데이트");
|
|
382
|
+
hasUpdate = true;
|
|
383
|
+
}
|
|
384
|
+
else if (npmResult.action === "failed") {
|
|
385
|
+
summaryParts.push("npm 업데이트 실패");
|
|
386
|
+
hasError = true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (localResult) {
|
|
390
|
+
if (localResult.action === "updated") {
|
|
391
|
+
summaryParts.push("로컬 빌드 업데이트");
|
|
392
|
+
hasUpdate = true;
|
|
393
|
+
}
|
|
394
|
+
else if (localResult.action === "failed") {
|
|
395
|
+
summaryParts.push("로컬 빌드 실패");
|
|
396
|
+
hasError = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Determine status and next action
|
|
93
400
|
let status;
|
|
94
|
-
let summary;
|
|
95
401
|
let nextAction;
|
|
96
|
-
if (
|
|
402
|
+
if (hasError && !hasUpdate) {
|
|
97
403
|
status = "ERROR";
|
|
98
|
-
summary = `업데이트 실패: ${failed}개 엔진`;
|
|
99
404
|
nextAction = {
|
|
100
405
|
type: "RETRY",
|
|
101
|
-
message: "force: true 옵션으로 다시
|
|
406
|
+
message: "업데이트에 실패했습니다. force: true 옵션으로 다시 시도하세요."
|
|
102
407
|
};
|
|
103
408
|
}
|
|
104
|
-
else if (
|
|
409
|
+
else if (hasError) {
|
|
105
410
|
status = "PARTIAL";
|
|
106
|
-
summary = `부분 성공: ${updated}개 업데이트, ${failed}개 실패`;
|
|
107
411
|
nextAction = {
|
|
108
|
-
type: "RETRY",
|
|
109
|
-
message:
|
|
412
|
+
type: needsRestart ? "RESTART_CLAUDE" : "RETRY",
|
|
413
|
+
message: needsRestart
|
|
414
|
+
? "일부 업데이트 완료. Claude Code를 재시작하세요."
|
|
415
|
+
: "일부 업데이트 실패. 실패 항목을 확인하세요."
|
|
110
416
|
};
|
|
111
417
|
}
|
|
112
|
-
else if (
|
|
418
|
+
else if (hasUpdate) {
|
|
113
419
|
status = "OK";
|
|
114
|
-
summary = `${updated}개 엔진 업데이트 완료`;
|
|
115
420
|
nextAction = {
|
|
116
|
-
type: "NONE",
|
|
117
|
-
message:
|
|
421
|
+
type: needsRestart ? "RESTART_CLAUDE" : "NONE",
|
|
422
|
+
message: needsRestart
|
|
423
|
+
? "업데이트 완료. Claude Code를 재시작하면 적용됩니다."
|
|
424
|
+
: "업데이트 완료."
|
|
118
425
|
};
|
|
119
426
|
}
|
|
120
427
|
else {
|
|
121
428
|
status = "OK";
|
|
122
|
-
summary = "모든 엔진이 이미 최신 버전입니다";
|
|
123
429
|
nextAction = {
|
|
124
430
|
type: "NONE",
|
|
125
|
-
message: "
|
|
431
|
+
message: "모두 최신 상태입니다."
|
|
126
432
|
};
|
|
127
433
|
}
|
|
434
|
+
const summary = summaryParts.length > 0
|
|
435
|
+
? summaryParts.join(", ")
|
|
436
|
+
: "업데이트할 항목이 없습니다";
|
|
128
437
|
return {
|
|
129
438
|
status,
|
|
130
439
|
summary,
|
|
131
|
-
results,
|
|
440
|
+
results: engineResults,
|
|
441
|
+
npm_result: npmResult,
|
|
442
|
+
local_result: localResult,
|
|
132
443
|
next_action: nextAction
|
|
133
444
|
};
|
|
134
445
|
}
|
|
@@ -136,10 +447,12 @@ export async function update(input) {
|
|
|
136
447
|
return {
|
|
137
448
|
status: "ERROR",
|
|
138
449
|
summary: e instanceof Error ? e.message : "알 수 없는 오류",
|
|
139
|
-
results,
|
|
450
|
+
results: engineResults,
|
|
451
|
+
npm_result: npmResult,
|
|
452
|
+
local_result: localResult,
|
|
140
453
|
next_action: {
|
|
141
454
|
type: "CONTACT_SUPPORT",
|
|
142
|
-
message: "업데이트 중 오류가 발생했습니다.
|
|
455
|
+
message: "업데이트 중 오류가 발생했습니다."
|
|
143
456
|
}
|
|
144
457
|
};
|
|
145
458
|
}
|