@learnrudi/cli 1.7.0 → 1.8.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 (2) hide show
  1. package/dist/index.cjs +137 -38
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -186,7 +186,6 @@ __export(src_exports, {
186
186
  PACKAGE_KINDS: () => PACKAGE_KINDS2,
187
187
  RUNTIMES_DOWNLOAD_BASE: () => RUNTIMES_DOWNLOAD_BASE,
188
188
  RUNTIMES_RELEASE_VERSION: () => RUNTIMES_RELEASE_VERSION,
189
- STACKS_RELEASE_VERSION: () => STACKS_RELEASE_VERSION,
190
189
  checkCache: () => checkCache,
191
190
  clearCache: () => clearCache,
192
191
  computeHash: () => computeHash,
@@ -383,7 +382,7 @@ async function downloadPackage(pkg, destPath, options = {}) {
383
382
  }
384
383
  onProgress?.({ phase: "downloading", package: pkg.name || pkg.id });
385
384
  if (pkg.kind === "stack" || registryPath.includes("/stacks/")) {
386
- await downloadStackTarball(pkg, destPath, onProgress);
385
+ await downloadStackFromGitHub(registryPath, destPath, onProgress);
387
386
  return { success: true, path: destPath };
388
387
  }
389
388
  if (registryPath.endsWith(".md")) {
@@ -404,45 +403,129 @@ async function downloadPackage(pkg, destPath, options = {}) {
404
403
  }
405
404
  throw new Error(`Unsupported package type: ${registryPath}`);
406
405
  }
407
- async function downloadStackTarball(pkg, destPath, onProgress) {
408
- const stackName = pkg.id?.replace("stack:", "") || import_path2.default.basename(pkg.path);
409
- const version = pkg.version || "1.0.0";
410
- const tarballName = `${stackName}-${version}.tar.gz`;
411
- const url = `${STACKS_DOWNLOAD_BASE}/${STACKS_RELEASE_VERSION}/${tarballName}`;
412
- onProgress?.({ phase: "downloading", package: stackName, url });
413
- const tempDir = import_path2.default.join(PATHS.cache, "downloads");
414
- if (!import_fs2.default.existsSync(tempDir)) {
415
- import_fs2.default.mkdirSync(tempDir, { recursive: true });
406
+ async function downloadStackFromGitHub(registryPath, destPath, onProgress) {
407
+ const baseUrl = `${GITHUB_RAW_BASE}/${registryPath}`;
408
+ const apiUrl = `https://api.github.com/repos/learn-rudi/registry/contents/${registryPath}`;
409
+ const listResponse = await fetch(apiUrl, {
410
+ headers: {
411
+ "User-Agent": "rudi-cli/2.0",
412
+ "Accept": "application/vnd.github.v3+json"
413
+ }
414
+ });
415
+ if (!listResponse.ok) {
416
+ throw new Error(`Stack not found: ${registryPath}`);
417
+ }
418
+ const contents = await listResponse.json();
419
+ if (!Array.isArray(contents)) {
420
+ throw new Error(`Invalid stack directory: ${registryPath}`);
421
+ }
422
+ const existingItems = /* @__PURE__ */ new Map();
423
+ for (const item of contents) {
424
+ existingItems.set(item.name, item);
425
+ }
426
+ const manifestItem = existingItems.get("manifest.json");
427
+ if (!manifestItem) {
428
+ throw new Error(`Stack missing manifest.json: ${registryPath}`);
416
429
  }
417
- const tempFile = import_path2.default.join(tempDir, tarballName);
430
+ const manifestResponse = await fetch(manifestItem.download_url, {
431
+ headers: { "User-Agent": "rudi-cli/2.0" }
432
+ });
433
+ const manifest = await manifestResponse.json();
434
+ import_fs2.default.writeFileSync(import_path2.default.join(destPath, "manifest.json"), JSON.stringify(manifest, null, 2));
435
+ onProgress?.({ phase: "downloading", file: "manifest.json" });
436
+ const pkgJsonItem = existingItems.get("package.json");
437
+ if (pkgJsonItem) {
438
+ const pkgJsonResponse = await fetch(pkgJsonItem.download_url, {
439
+ headers: { "User-Agent": "rudi-cli/2.0" }
440
+ });
441
+ if (pkgJsonResponse.ok) {
442
+ const pkgJson = await pkgJsonResponse.text();
443
+ import_fs2.default.writeFileSync(import_path2.default.join(destPath, "package.json"), pkgJson);
444
+ onProgress?.({ phase: "downloading", file: "package.json" });
445
+ }
446
+ }
447
+ const envExampleItem = existingItems.get(".env.example");
448
+ if (envExampleItem) {
449
+ const envResponse = await fetch(envExampleItem.download_url, {
450
+ headers: { "User-Agent": "rudi-cli/2.0" }
451
+ });
452
+ if (envResponse.ok) {
453
+ const envContent = await envResponse.text();
454
+ import_fs2.default.writeFileSync(import_path2.default.join(destPath, ".env.example"), envContent);
455
+ }
456
+ }
457
+ const tsconfigItem = existingItems.get("tsconfig.json");
458
+ if (tsconfigItem) {
459
+ const tsconfigResponse = await fetch(tsconfigItem.download_url, {
460
+ headers: { "User-Agent": "rudi-cli/2.0" }
461
+ });
462
+ if (tsconfigResponse.ok) {
463
+ const tsconfig = await tsconfigResponse.text();
464
+ import_fs2.default.writeFileSync(import_path2.default.join(destPath, "tsconfig.json"), tsconfig);
465
+ }
466
+ }
467
+ const requirementsItem = existingItems.get("requirements.txt");
468
+ if (requirementsItem) {
469
+ const reqResponse = await fetch(requirementsItem.download_url, {
470
+ headers: { "User-Agent": "rudi-cli/2.0" }
471
+ });
472
+ if (reqResponse.ok) {
473
+ const requirements = await reqResponse.text();
474
+ import_fs2.default.writeFileSync(import_path2.default.join(destPath, "requirements.txt"), requirements);
475
+ }
476
+ }
477
+ const sourceDirs = ["src", "dist", "node", "python", "lib"];
478
+ for (const dirName of sourceDirs) {
479
+ const dirItem = existingItems.get(dirName);
480
+ if (dirItem && dirItem.type === "dir") {
481
+ onProgress?.({ phase: "downloading", directory: dirName });
482
+ await downloadDirectoryFromGitHub(
483
+ `${baseUrl}/${dirName}`,
484
+ import_path2.default.join(destPath, dirName),
485
+ onProgress
486
+ );
487
+ }
488
+ }
489
+ }
490
+ async function downloadDirectoryFromGitHub(dirUrl, destDir, onProgress) {
491
+ const apiUrl = dirUrl.replace("https://raw.githubusercontent.com/", "https://api.github.com/repos/").replace("/main/", "/contents/");
418
492
  try {
419
- const response = await fetch(url, {
493
+ const response = await fetch(apiUrl, {
420
494
  headers: {
421
495
  "User-Agent": "rudi-cli/2.0",
422
- "Accept": "application/octet-stream"
496
+ "Accept": "application/vnd.github.v3+json"
423
497
  }
424
498
  });
425
499
  if (!response.ok) {
426
- throw new Error(`Failed to download ${stackName}: HTTP ${response.status}`);
500
+ return;
427
501
  }
428
- const buffer = await response.arrayBuffer();
429
- import_fs2.default.writeFileSync(tempFile, Buffer.from(buffer));
430
- onProgress?.({ phase: "extracting", package: stackName });
431
- if (import_fs2.default.existsSync(destPath)) {
432
- import_fs2.default.rmSync(destPath, { recursive: true });
502
+ const contents = await response.json();
503
+ if (!Array.isArray(contents)) {
504
+ return;
433
505
  }
434
- import_fs2.default.mkdirSync(destPath, { recursive: true });
435
- const { execSync: execSync7 } = await import("child_process");
436
- execSync7(`tar -xzf "${tempFile}" -C "${destPath}"`, {
437
- stdio: "pipe"
438
- });
439
- import_fs2.default.unlinkSync(tempFile);
440
- onProgress?.({ phase: "complete", package: stackName, path: destPath });
441
- } catch (error) {
442
- if (import_fs2.default.existsSync(tempFile)) {
443
- import_fs2.default.unlinkSync(tempFile);
506
+ if (!import_fs2.default.existsSync(destDir)) {
507
+ import_fs2.default.mkdirSync(destDir, { recursive: true });
444
508
  }
445
- throw new Error(`Failed to install ${stackName}: ${error.message}`);
509
+ for (const item of contents) {
510
+ if (item.type === "file") {
511
+ const fileResponse = await fetch(item.download_url, {
512
+ headers: { "User-Agent": "rudi-cli/2.0" }
513
+ });
514
+ if (fileResponse.ok) {
515
+ const content = await fileResponse.text();
516
+ import_fs2.default.writeFileSync(import_path2.default.join(destDir, item.name), content);
517
+ onProgress?.({ phase: "downloading", file: item.name });
518
+ }
519
+ } else if (item.type === "dir") {
520
+ await downloadDirectoryFromGitHub(
521
+ item.url.replace("https://api.github.com/repos/", "https://raw.githubusercontent.com/").replace("/contents/", "/main/"),
522
+ import_path2.default.join(destDir, item.name),
523
+ onProgress
524
+ );
525
+ }
526
+ }
527
+ } catch (error) {
528
+ console.error(`Warning: Could not download ${dirUrl}: ${error.message}`);
446
529
  }
447
530
  }
448
531
  async function downloadRuntime(runtime, version, destPath, options = {}) {
@@ -720,7 +803,7 @@ async function computeHash(filePath) {
720
803
  stream.on("error", reject);
721
804
  });
722
805
  }
723
- var import_fs2, import_path2, import_crypto, DEFAULT_REGISTRY_URL, RUNTIMES_DOWNLOAD_BASE, CACHE_TTL, PACKAGE_KINDS2, KIND_PLURALS, GITHUB_RAW_BASE, STACKS_DOWNLOAD_BASE, STACKS_RELEASE_VERSION, RUNTIMES_RELEASE_VERSION;
806
+ var import_fs2, import_path2, import_crypto, DEFAULT_REGISTRY_URL, RUNTIMES_DOWNLOAD_BASE, CACHE_TTL, PACKAGE_KINDS2, KIND_PLURALS, GITHUB_RAW_BASE, RUNTIMES_RELEASE_VERSION;
724
807
  var init_src2 = __esm({
725
808
  "../packages/registry-client/src/index.js"() {
726
809
  import_fs2 = __toESM(require("fs"), 1);
@@ -735,8 +818,6 @@ var init_src2 = __esm({
735
818
  binary: "binaries"
736
819
  };
737
820
  GITHUB_RAW_BASE = "https://raw.githubusercontent.com/learn-rudi/registry/main";
738
- STACKS_DOWNLOAD_BASE = "https://github.com/learn-rudi/registry/releases/download";
739
- STACKS_RELEASE_VERSION = "stacks-v1.0.0";
740
821
  RUNTIMES_RELEASE_VERSION = "v1.0.0";
741
822
  }
742
823
  });
@@ -16303,8 +16384,25 @@ async function loadManifest(installPath) {
16303
16384
  return null;
16304
16385
  }
16305
16386
  }
16387
+ function getBundledBinary(runtime, binary) {
16388
+ const platform = process.platform;
16389
+ const rudiHome = process.env.RUDI_HOME || path8.join(process.env.HOME || process.env.USERPROFILE, ".rudi");
16390
+ if (runtime === "node") {
16391
+ const npmPath = platform === "win32" ? path8.join(rudiHome, "runtimes", "node", "npm.cmd") : path8.join(rudiHome, "runtimes", "node", "bin", "npm");
16392
+ if (require("fs").existsSync(npmPath)) {
16393
+ return npmPath;
16394
+ }
16395
+ }
16396
+ if (runtime === "python") {
16397
+ const pipPath = platform === "win32" ? path8.join(rudiHome, "runtimes", "python", "Scripts", "pip.exe") : path8.join(rudiHome, "runtimes", "python", "bin", "pip3");
16398
+ if (require("fs").existsSync(pipPath)) {
16399
+ return pipPath;
16400
+ }
16401
+ }
16402
+ return binary;
16403
+ }
16306
16404
  async function installDependencies(stackPath, manifest) {
16307
- const runtime = manifest?.runtime || "node";
16405
+ const runtime = manifest?.runtime || manifest?.mcp?.runtime || "node";
16308
16406
  try {
16309
16407
  if (runtime === "node") {
16310
16408
  const packageJsonPath = path8.join(stackPath, "package.json");
@@ -16319,11 +16417,11 @@ async function installDependencies(stackPath, manifest) {
16319
16417
  return { installed: false, reason: "Dependencies already installed" };
16320
16418
  } catch {
16321
16419
  }
16420
+ const npmCmd = getBundledBinary("node", "npm");
16322
16421
  console.log(` Installing npm dependencies...`);
16323
- (0, import_child_process2.execSync)("npm install --production", {
16422
+ (0, import_child_process2.execSync)(`"${npmCmd}" install --production`, {
16324
16423
  cwd: stackPath,
16325
16424
  stdio: "pipe"
16326
- // Suppress output
16327
16425
  });
16328
16426
  return { installed: true };
16329
16427
  } else if (runtime === "python") {
@@ -16333,8 +16431,9 @@ async function installDependencies(stackPath, manifest) {
16333
16431
  } catch {
16334
16432
  return { installed: false, reason: "No requirements.txt" };
16335
16433
  }
16434
+ const pipCmd = getBundledBinary("python", "pip");
16336
16435
  console.log(` Installing pip dependencies...`);
16337
- (0, import_child_process2.execSync)("pip install -r requirements.txt", {
16436
+ (0, import_child_process2.execSync)(`"${pipCmd}" install -r requirements.txt`, {
16338
16437
  cwd: stackPath,
16339
16438
  stdio: "pipe"
16340
16439
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@learnrudi/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "RUDI CLI - Install and manage MCP stacks, runtimes, and AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",