mcp-for-i 0.1.4 → 0.1.5

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.
@@ -19,6 +19,55 @@ export class RuntimeService {
19
19
  getStatus() {
20
20
  return this.state;
21
21
  }
22
+ async getVersions(options) {
23
+ const mode = await this.detectExecutionModeQuiet();
24
+ const checkedAt = new Date().toISOString();
25
+ const mcpInstalled = await this.readLocalPackageVersion(this.rootDir);
26
+ const mcpLatest = await this.readLatestPublishedVersion();
27
+ const mcpStatus = mcpInstalled && mcpLatest
28
+ ? (mcpInstalled === mcpLatest ? "latest" : "update-available")
29
+ : "unknown";
30
+ const configuredRepo = sanitizeRepoUrl(options?.repoUrl) || process.env.MCP_FOR_I_SKILLS_REPO || DEFAULT_SKILLS_REPO;
31
+ const configuredBranch = sanitizeBranch(options?.branch) || process.env.MCP_FOR_I_SKILLS_BRANCH || "main";
32
+ const skillsDir = path.join(this.rootDir, "skills");
33
+ const skillsGit = path.join(skillsDir, ".git");
34
+ const skills = {
35
+ configuredRepo,
36
+ configuredBranch,
37
+ status: "not-installed"
38
+ };
39
+ if (await pathExists(skillsDir)) {
40
+ if (await pathExists(skillsGit)) {
41
+ const localRepo = await this.getGitRemoteUrlQuiet(skillsDir);
42
+ const localBranch = await this.getGitBranchQuiet(skillsDir);
43
+ const localCommit = await this.getGitCommitQuiet(skillsDir, true);
44
+ const latestCommit = await this.getRemoteBranchCommitQuiet(configuredRepo, configuredBranch, true);
45
+ skills.localRepo = localRepo;
46
+ skills.localBranch = localBranch;
47
+ skills.localCommit = localCommit;
48
+ skills.latestCommit = latestCommit;
49
+ if (localCommit && latestCommit) {
50
+ skills.status = localCommit === latestCommit ? "latest" : "update-available";
51
+ }
52
+ else {
53
+ skills.status = "unknown";
54
+ }
55
+ }
56
+ else {
57
+ skills.status = "unknown";
58
+ }
59
+ }
60
+ return {
61
+ checkedAt,
62
+ mcp: {
63
+ source: mode,
64
+ installedVersion: mcpInstalled,
65
+ latestVersion: mcpLatest,
66
+ status: mcpStatus
67
+ },
68
+ skills
69
+ };
70
+ }
22
71
  async installOrRepair() {
23
72
  return this.runJob("install", async (push) => {
24
73
  const mode = await this.detectExecutionMode(push);
@@ -239,6 +288,10 @@ export class RuntimeService {
239
288
  push("No git checkout found. Falling back to npm-based package management.");
240
289
  return "package-install";
241
290
  }
291
+ async detectExecutionModeQuiet() {
292
+ const gitDir = path.join(this.rootDir, ".git");
293
+ return await pathExists(gitDir) ? "git-checkout" : "package-install";
294
+ }
242
295
  async buildIfPresent(push) {
243
296
  const tsconfigPath = path.join(this.rootDir, "tsconfig.json");
244
297
  if (!await pathExists(tsconfigPath)) {
@@ -304,6 +357,103 @@ export class RuntimeService {
304
357
  push("Control plane will stop briefly, then restart automatically.");
305
358
  push(`Background update log: ${logPath}`);
306
359
  }
360
+ async readLocalPackageVersion(cwd) {
361
+ try {
362
+ const packagePath = path.join(cwd, "package.json");
363
+ const raw = await fs.readFile(packagePath, "utf8");
364
+ const parsed = JSON.parse(raw);
365
+ return typeof parsed.version === "string" ? parsed.version : undefined;
366
+ }
367
+ catch {
368
+ return undefined;
369
+ }
370
+ }
371
+ async readLatestPublishedVersion() {
372
+ try {
373
+ const output = await this.runNpmCapture(["view", PACKAGE_NAME, "version", "--json"], this.rootDir);
374
+ const parsed = JSON.parse(output || "null");
375
+ if (typeof parsed === "string")
376
+ return parsed.trim();
377
+ if (parsed && typeof parsed.version === "string")
378
+ return parsed.version.trim();
379
+ return undefined;
380
+ }
381
+ catch {
382
+ return undefined;
383
+ }
384
+ }
385
+ async getGitBranchQuiet(cwd) {
386
+ try {
387
+ const branch = await this.runCommandCaptureSilent("git", ["rev-parse", "--abbrev-ref", "HEAD"], cwd);
388
+ return branch || undefined;
389
+ }
390
+ catch {
391
+ return undefined;
392
+ }
393
+ }
394
+ async getGitCommitQuiet(cwd, short = false) {
395
+ try {
396
+ const args = short ? ["rev-parse", "--short", "HEAD"] : ["rev-parse", "HEAD"];
397
+ const commit = await this.runCommandCaptureSilent("git", args, cwd);
398
+ return commit || undefined;
399
+ }
400
+ catch {
401
+ return undefined;
402
+ }
403
+ }
404
+ async getGitRemoteUrlQuiet(cwd) {
405
+ try {
406
+ const value = await this.runCommandCaptureSilent("git", ["remote", "get-url", "origin"], cwd);
407
+ return value || undefined;
408
+ }
409
+ catch {
410
+ return undefined;
411
+ }
412
+ }
413
+ async getRemoteBranchCommitQuiet(repoUrl, branch, short = false) {
414
+ try {
415
+ const out = await this.runCommandCaptureSilent("git", ["ls-remote", repoUrl, branch], this.rootDir);
416
+ const firstLine = out.split(/\r?\n/).find(Boolean);
417
+ if (!firstLine)
418
+ return undefined;
419
+ const [hash] = firstLine.trim().split(/\s+/);
420
+ if (!hash)
421
+ return undefined;
422
+ return short ? hash.slice(0, 7) : hash;
423
+ }
424
+ catch {
425
+ return undefined;
426
+ }
427
+ }
428
+ async runNpmCapture(args, cwd) {
429
+ const npm = await resolveNpmLaunch();
430
+ if (npm.mode === "node-cli") {
431
+ return this.runCommandCaptureSilent(process.execPath, [npm.path, ...args], cwd);
432
+ }
433
+ return this.runCommandCaptureSilent(npm.path, args, cwd);
434
+ }
435
+ runCommandCaptureSilent(command, args, cwd) {
436
+ return new Promise((resolve, reject) => {
437
+ const child = spawn(command, args, {
438
+ cwd,
439
+ shell: process.platform === "win32"
440
+ });
441
+ const stdout = [];
442
+ const stderr = [];
443
+ child.stdout.on("data", chunk => stdout.push(String(chunk)));
444
+ child.stderr.on("data", chunk => stderr.push(String(chunk)));
445
+ child.on("error", reject);
446
+ child.on("exit", code => {
447
+ if (code === 0) {
448
+ resolve(stdout.join("").trim());
449
+ }
450
+ else {
451
+ const errText = stderr.join("").trim();
452
+ reject(new Error(errText || `Command failed (${code}): ${command} ${args.join(" ")}`));
453
+ }
454
+ });
455
+ });
456
+ }
307
457
  }
308
458
  function makeJob(id) {
309
459
  return {
@@ -52,6 +52,14 @@ async function routeRequest(req, res, services) {
52
52
  sendJson(res, 200, { jobs: services.runtime.getStatus() });
53
53
  return;
54
54
  }
55
+ if (method === "GET" && pathname === "/api/runtime/versions") {
56
+ const versions = await services.runtime.getVersions({
57
+ repoUrl: url.searchParams.get("repoUrl") || undefined,
58
+ branch: url.searchParams.get("branch") || undefined
59
+ });
60
+ sendJson(res, 200, versions);
61
+ return;
62
+ }
55
63
  if (method === "POST" && pathname === "/api/runtime/install") {
56
64
  const job = await services.runtime.installOrRepair();
57
65
  sendJson(res, 202, { job });