@learnrudi/cli 1.4.0 → 1.6.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 (3) hide show
  1. package/README.md +60 -63
  2. package/dist/index.cjs +2289 -891
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -186,6 +186,7 @@ __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,
189
190
  checkCache: () => checkCache,
190
191
  clearCache: () => clearCache,
191
192
  computeHash: () => computeHash,
@@ -377,24 +378,72 @@ function getPackageKinds() {
377
378
  async function downloadPackage(pkg, destPath, options = {}) {
378
379
  const { onProgress } = options;
379
380
  const registryPath = pkg.path;
380
- for (const basePath of getLocalRegistryPaths()) {
381
- const registryDir = import_path2.default.dirname(basePath);
382
- const pkgSourcePath = import_path2.default.join(registryDir, registryPath);
383
- if (import_fs2.default.existsSync(pkgSourcePath)) {
384
- const stat = import_fs2.default.statSync(pkgSourcePath);
385
- if (stat.isFile()) {
386
- const destDir = import_path2.default.dirname(destPath);
387
- if (!import_fs2.default.existsSync(destDir)) {
388
- import_fs2.default.mkdirSync(destDir, { recursive: true });
389
- }
390
- import_fs2.default.copyFileSync(pkgSourcePath, destPath);
391
- } else {
392
- await copyDirectory(pkgSourcePath, destPath);
381
+ if (!import_fs2.default.existsSync(destPath)) {
382
+ import_fs2.default.mkdirSync(destPath, { recursive: true });
383
+ }
384
+ onProgress?.({ phase: "downloading", package: pkg.name || pkg.id });
385
+ if (pkg.kind === "stack" || registryPath.includes("/stacks/")) {
386
+ await downloadStackTarball(pkg, destPath, onProgress);
387
+ return { success: true, path: destPath };
388
+ }
389
+ if (registryPath.endsWith(".md")) {
390
+ const url = `${GITHUB_RAW_BASE}/${registryPath}`;
391
+ const response = await fetch(url, {
392
+ headers: { "User-Agent": "rudi-cli/2.0" }
393
+ });
394
+ if (!response.ok) {
395
+ throw new Error(`Failed to download ${registryPath}: HTTP ${response.status}`);
396
+ }
397
+ const content = await response.text();
398
+ const destDir = import_path2.default.dirname(destPath);
399
+ if (!import_fs2.default.existsSync(destDir)) {
400
+ import_fs2.default.mkdirSync(destDir, { recursive: true });
401
+ }
402
+ import_fs2.default.writeFileSync(destPath, content);
403
+ return { success: true, path: destPath };
404
+ }
405
+ throw new Error(`Unsupported package type: ${registryPath}`);
406
+ }
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 });
416
+ }
417
+ const tempFile = import_path2.default.join(tempDir, tarballName);
418
+ try {
419
+ const response = await fetch(url, {
420
+ headers: {
421
+ "User-Agent": "rudi-cli/2.0",
422
+ "Accept": "application/octet-stream"
393
423
  }
394
- return { success: true, path: destPath };
424
+ });
425
+ if (!response.ok) {
426
+ throw new Error(`Failed to download ${stackName}: HTTP ${response.status}`);
427
+ }
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 });
433
+ }
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);
395
444
  }
445
+ throw new Error(`Failed to install ${stackName}: ${error.message}`);
396
446
  }
397
- throw new Error(`Package source not found: ${registryPath}`);
398
447
  }
399
448
  async function downloadRuntime(runtime, version, destPath, options = {}) {
400
449
  const { onProgress } = options;
@@ -425,8 +474,8 @@ async function downloadRuntime(runtime, version, destPath, options = {}) {
425
474
  import_fs2.default.rmSync(destPath, { recursive: true });
426
475
  }
427
476
  import_fs2.default.mkdirSync(destPath, { recursive: true });
428
- const { execSync: execSync5 } = await import("child_process");
429
- execSync5(`tar -xzf "${tempFile}" -C "${destPath}" --strip-components=1`, {
477
+ const { execSync: execSync7 } = await import("child_process");
478
+ execSync7(`tar -xzf "${tempFile}" -C "${destPath}" --strip-components=1`, {
430
479
  stdio: "pipe"
431
480
  });
432
481
  import_fs2.default.unlinkSync(tempFile);
@@ -464,7 +513,7 @@ async function downloadTool(toolName, destPath, options = {}) {
464
513
  import_fs2.default.rmSync(destPath, { recursive: true });
465
514
  }
466
515
  import_fs2.default.mkdirSync(destPath, { recursive: true });
467
- const { execSync: execSync5 } = await import("child_process");
516
+ const { execSync: execSync7 } = await import("child_process");
468
517
  const downloads = toolManifest.downloads?.[platformArch];
469
518
  if (downloads && Array.isArray(downloads)) {
470
519
  const downloadedUrls = /* @__PURE__ */ new Set();
@@ -493,11 +542,11 @@ async function downloadTool(toolName, destPath, options = {}) {
493
542
  onProgress?.({ phase: "extracting", tool: toolName, binary: import_path2.default.basename(binary) });
494
543
  const archiveType = type || guessArchiveType(urlFilename);
495
544
  if (archiveType === "zip") {
496
- execSync5(`unzip -o "${tempFile}" -d "${destPath}"`, { stdio: "pipe" });
545
+ execSync7(`unzip -o "${tempFile}" -d "${destPath}"`, { stdio: "pipe" });
497
546
  } else if (archiveType === "tar.xz") {
498
- execSync5(`tar -xJf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
547
+ execSync7(`tar -xJf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
499
548
  } else if (archiveType === "tar.gz" || archiveType === "tgz") {
500
- execSync5(`tar -xzf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
549
+ execSync7(`tar -xzf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
501
550
  } else {
502
551
  throw new Error(`Unsupported archive type: ${archiveType}`);
503
552
  }
@@ -544,11 +593,11 @@ async function downloadTool(toolName, destPath, options = {}) {
544
593
  onProgress?.({ phase: "extracting", tool: toolName });
545
594
  const archiveType = extractConfig.type || guessArchiveType(urlFilename);
546
595
  if (archiveType === "zip") {
547
- execSync5(`unzip -o "${tempFile}" -d "${destPath}"`, { stdio: "pipe" });
596
+ execSync7(`unzip -o "${tempFile}" -d "${destPath}"`, { stdio: "pipe" });
548
597
  } else if (archiveType === "tar.xz") {
549
- execSync5(`tar -xJf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
598
+ execSync7(`tar -xJf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
550
599
  } else if (archiveType === "tar.gz" || archiveType === "tgz") {
551
- execSync5(`tar -xzf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
600
+ execSync7(`tar -xzf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
552
601
  } else {
553
602
  throw new Error(`Unsupported archive type: ${archiveType}`);
554
603
  }
@@ -671,22 +720,7 @@ async function computeHash(filePath) {
671
720
  stream.on("error", reject);
672
721
  });
673
722
  }
674
- async function copyDirectory(src, dest) {
675
- import_fs2.default.mkdirSync(dest, { recursive: true });
676
- const entries = import_fs2.default.readdirSync(src, { withFileTypes: true });
677
- for (const entry of entries) {
678
- const srcPath = import_path2.default.join(src, entry.name);
679
- const destPath = import_path2.default.join(dest, entry.name);
680
- if (entry.isDirectory()) {
681
- if (entry.name !== "node_modules" && entry.name !== ".git") {
682
- await copyDirectory(srcPath, destPath);
683
- }
684
- } else {
685
- import_fs2.default.copyFileSync(srcPath, destPath);
686
- }
687
- }
688
- }
689
- var import_fs2, import_path2, import_crypto, DEFAULT_REGISTRY_URL, RUNTIMES_DOWNLOAD_BASE, CACHE_TTL, PACKAGE_KINDS2, KIND_PLURALS, RUNTIMES_RELEASE_VERSION;
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;
690
724
  var init_src2 = __esm({
691
725
  "../packages/registry-client/src/index.js"() {
692
726
  import_fs2 = __toESM(require("fs"), 1);
@@ -700,6 +734,9 @@ var init_src2 = __esm({
700
734
  KIND_PLURALS = {
701
735
  binary: "binaries"
702
736
  };
737
+ 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";
703
740
  RUNTIMES_RELEASE_VERSION = "v1.0.0";
704
741
  }
705
742
  });
@@ -781,17 +818,17 @@ var require_visit = __commonJS({
781
818
  visit.BREAK = BREAK;
782
819
  visit.SKIP = SKIP;
783
820
  visit.REMOVE = REMOVE;
784
- function visit_(key, node, visitor, path17) {
785
- const ctrl = callVisitor(key, node, visitor, path17);
821
+ function visit_(key, node, visitor, path24) {
822
+ const ctrl = callVisitor(key, node, visitor, path24);
786
823
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
787
- replaceNode(key, path17, ctrl);
788
- return visit_(key, ctrl, visitor, path17);
824
+ replaceNode(key, path24, ctrl);
825
+ return visit_(key, ctrl, visitor, path24);
789
826
  }
790
827
  if (typeof ctrl !== "symbol") {
791
828
  if (identity.isCollection(node)) {
792
- path17 = Object.freeze(path17.concat(node));
829
+ path24 = Object.freeze(path24.concat(node));
793
830
  for (let i = 0; i < node.items.length; ++i) {
794
- const ci = visit_(i, node.items[i], visitor, path17);
831
+ const ci = visit_(i, node.items[i], visitor, path24);
795
832
  if (typeof ci === "number")
796
833
  i = ci - 1;
797
834
  else if (ci === BREAK)
@@ -802,13 +839,13 @@ var require_visit = __commonJS({
802
839
  }
803
840
  }
804
841
  } else if (identity.isPair(node)) {
805
- path17 = Object.freeze(path17.concat(node));
806
- const ck = visit_("key", node.key, visitor, path17);
842
+ path24 = Object.freeze(path24.concat(node));
843
+ const ck = visit_("key", node.key, visitor, path24);
807
844
  if (ck === BREAK)
808
845
  return BREAK;
809
846
  else if (ck === REMOVE)
810
847
  node.key = null;
811
- const cv = visit_("value", node.value, visitor, path17);
848
+ const cv = visit_("value", node.value, visitor, path24);
812
849
  if (cv === BREAK)
813
850
  return BREAK;
814
851
  else if (cv === REMOVE)
@@ -829,17 +866,17 @@ var require_visit = __commonJS({
829
866
  visitAsync.BREAK = BREAK;
830
867
  visitAsync.SKIP = SKIP;
831
868
  visitAsync.REMOVE = REMOVE;
832
- async function visitAsync_(key, node, visitor, path17) {
833
- const ctrl = await callVisitor(key, node, visitor, path17);
869
+ async function visitAsync_(key, node, visitor, path24) {
870
+ const ctrl = await callVisitor(key, node, visitor, path24);
834
871
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
835
- replaceNode(key, path17, ctrl);
836
- return visitAsync_(key, ctrl, visitor, path17);
872
+ replaceNode(key, path24, ctrl);
873
+ return visitAsync_(key, ctrl, visitor, path24);
837
874
  }
838
875
  if (typeof ctrl !== "symbol") {
839
876
  if (identity.isCollection(node)) {
840
- path17 = Object.freeze(path17.concat(node));
877
+ path24 = Object.freeze(path24.concat(node));
841
878
  for (let i = 0; i < node.items.length; ++i) {
842
- const ci = await visitAsync_(i, node.items[i], visitor, path17);
879
+ const ci = await visitAsync_(i, node.items[i], visitor, path24);
843
880
  if (typeof ci === "number")
844
881
  i = ci - 1;
845
882
  else if (ci === BREAK)
@@ -850,13 +887,13 @@ var require_visit = __commonJS({
850
887
  }
851
888
  }
852
889
  } else if (identity.isPair(node)) {
853
- path17 = Object.freeze(path17.concat(node));
854
- const ck = await visitAsync_("key", node.key, visitor, path17);
890
+ path24 = Object.freeze(path24.concat(node));
891
+ const ck = await visitAsync_("key", node.key, visitor, path24);
855
892
  if (ck === BREAK)
856
893
  return BREAK;
857
894
  else if (ck === REMOVE)
858
895
  node.key = null;
859
- const cv = await visitAsync_("value", node.value, visitor, path17);
896
+ const cv = await visitAsync_("value", node.value, visitor, path24);
860
897
  if (cv === BREAK)
861
898
  return BREAK;
862
899
  else if (cv === REMOVE)
@@ -883,23 +920,23 @@ var require_visit = __commonJS({
883
920
  }
884
921
  return visitor;
885
922
  }
886
- function callVisitor(key, node, visitor, path17) {
923
+ function callVisitor(key, node, visitor, path24) {
887
924
  if (typeof visitor === "function")
888
- return visitor(key, node, path17);
925
+ return visitor(key, node, path24);
889
926
  if (identity.isMap(node))
890
- return visitor.Map?.(key, node, path17);
927
+ return visitor.Map?.(key, node, path24);
891
928
  if (identity.isSeq(node))
892
- return visitor.Seq?.(key, node, path17);
929
+ return visitor.Seq?.(key, node, path24);
893
930
  if (identity.isPair(node))
894
- return visitor.Pair?.(key, node, path17);
931
+ return visitor.Pair?.(key, node, path24);
895
932
  if (identity.isScalar(node))
896
- return visitor.Scalar?.(key, node, path17);
933
+ return visitor.Scalar?.(key, node, path24);
897
934
  if (identity.isAlias(node))
898
- return visitor.Alias?.(key, node, path17);
935
+ return visitor.Alias?.(key, node, path24);
899
936
  return void 0;
900
937
  }
901
- function replaceNode(key, path17, node) {
902
- const parent = path17[path17.length - 1];
938
+ function replaceNode(key, path24, node) {
939
+ const parent = path24[path24.length - 1];
903
940
  if (identity.isCollection(parent)) {
904
941
  parent.items[key] = node;
905
942
  } else if (identity.isPair(parent)) {
@@ -1507,10 +1544,10 @@ var require_Collection = __commonJS({
1507
1544
  var createNode = require_createNode();
1508
1545
  var identity = require_identity();
1509
1546
  var Node = require_Node();
1510
- function collectionFromPath(schema, path17, value) {
1547
+ function collectionFromPath(schema, path24, value) {
1511
1548
  let v = value;
1512
- for (let i = path17.length - 1; i >= 0; --i) {
1513
- const k = path17[i];
1549
+ for (let i = path24.length - 1; i >= 0; --i) {
1550
+ const k = path24[i];
1514
1551
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
1515
1552
  const a = [];
1516
1553
  a[k] = v;
@@ -1529,7 +1566,7 @@ var require_Collection = __commonJS({
1529
1566
  sourceObjects: /* @__PURE__ */ new Map()
1530
1567
  });
1531
1568
  }
1532
- var isEmptyPath = (path17) => path17 == null || typeof path17 === "object" && !!path17[Symbol.iterator]().next().done;
1569
+ var isEmptyPath = (path24) => path24 == null || typeof path24 === "object" && !!path24[Symbol.iterator]().next().done;
1533
1570
  var Collection = class extends Node.NodeBase {
1534
1571
  constructor(type, schema) {
1535
1572
  super(type);
@@ -1559,11 +1596,11 @@ var require_Collection = __commonJS({
1559
1596
  * be a Pair instance or a `{ key, value }` object, which may not have a key
1560
1597
  * that already exists in the map.
1561
1598
  */
1562
- addIn(path17, value) {
1563
- if (isEmptyPath(path17))
1599
+ addIn(path24, value) {
1600
+ if (isEmptyPath(path24))
1564
1601
  this.add(value);
1565
1602
  else {
1566
- const [key, ...rest] = path17;
1603
+ const [key, ...rest] = path24;
1567
1604
  const node = this.get(key, true);
1568
1605
  if (identity.isCollection(node))
1569
1606
  node.addIn(rest, value);
@@ -1577,8 +1614,8 @@ var require_Collection = __commonJS({
1577
1614
  * Removes a value from the collection.
1578
1615
  * @returns `true` if the item was found and removed.
1579
1616
  */
1580
- deleteIn(path17) {
1581
- const [key, ...rest] = path17;
1617
+ deleteIn(path24) {
1618
+ const [key, ...rest] = path24;
1582
1619
  if (rest.length === 0)
1583
1620
  return this.delete(key);
1584
1621
  const node = this.get(key, true);
@@ -1592,8 +1629,8 @@ var require_Collection = __commonJS({
1592
1629
  * scalar values from their surrounding node; to disable set `keepScalar` to
1593
1630
  * `true` (collections are always returned intact).
1594
1631
  */
1595
- getIn(path17, keepScalar) {
1596
- const [key, ...rest] = path17;
1632
+ getIn(path24, keepScalar) {
1633
+ const [key, ...rest] = path24;
1597
1634
  const node = this.get(key, true);
1598
1635
  if (rest.length === 0)
1599
1636
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -1611,8 +1648,8 @@ var require_Collection = __commonJS({
1611
1648
  /**
1612
1649
  * Checks if the collection includes a value with the key `key`.
1613
1650
  */
1614
- hasIn(path17) {
1615
- const [key, ...rest] = path17;
1651
+ hasIn(path24) {
1652
+ const [key, ...rest] = path24;
1616
1653
  if (rest.length === 0)
1617
1654
  return this.has(key);
1618
1655
  const node = this.get(key, true);
@@ -1622,8 +1659,8 @@ var require_Collection = __commonJS({
1622
1659
  * Sets a value in this collection. For `!!set`, `value` needs to be a
1623
1660
  * boolean to add/remove the item from the set.
1624
1661
  */
1625
- setIn(path17, value) {
1626
- const [key, ...rest] = path17;
1662
+ setIn(path24, value) {
1663
+ const [key, ...rest] = path24;
1627
1664
  if (rest.length === 0) {
1628
1665
  this.set(key, value);
1629
1666
  } else {
@@ -4127,9 +4164,9 @@ var require_Document = __commonJS({
4127
4164
  this.contents.add(value);
4128
4165
  }
4129
4166
  /** Adds a value to the document. */
4130
- addIn(path17, value) {
4167
+ addIn(path24, value) {
4131
4168
  if (assertCollection(this.contents))
4132
- this.contents.addIn(path17, value);
4169
+ this.contents.addIn(path24, value);
4133
4170
  }
4134
4171
  /**
4135
4172
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -4204,14 +4241,14 @@ var require_Document = __commonJS({
4204
4241
  * Removes a value from the document.
4205
4242
  * @returns `true` if the item was found and removed.
4206
4243
  */
4207
- deleteIn(path17) {
4208
- if (Collection.isEmptyPath(path17)) {
4244
+ deleteIn(path24) {
4245
+ if (Collection.isEmptyPath(path24)) {
4209
4246
  if (this.contents == null)
4210
4247
  return false;
4211
4248
  this.contents = null;
4212
4249
  return true;
4213
4250
  }
4214
- return assertCollection(this.contents) ? this.contents.deleteIn(path17) : false;
4251
+ return assertCollection(this.contents) ? this.contents.deleteIn(path24) : false;
4215
4252
  }
4216
4253
  /**
4217
4254
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -4226,10 +4263,10 @@ var require_Document = __commonJS({
4226
4263
  * scalar values from their surrounding node; to disable set `keepScalar` to
4227
4264
  * `true` (collections are always returned intact).
4228
4265
  */
4229
- getIn(path17, keepScalar) {
4230
- if (Collection.isEmptyPath(path17))
4266
+ getIn(path24, keepScalar) {
4267
+ if (Collection.isEmptyPath(path24))
4231
4268
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
4232
- return identity.isCollection(this.contents) ? this.contents.getIn(path17, keepScalar) : void 0;
4269
+ return identity.isCollection(this.contents) ? this.contents.getIn(path24, keepScalar) : void 0;
4233
4270
  }
4234
4271
  /**
4235
4272
  * Checks if the document includes a value with the key `key`.
@@ -4240,10 +4277,10 @@ var require_Document = __commonJS({
4240
4277
  /**
4241
4278
  * Checks if the document includes a value at `path`.
4242
4279
  */
4243
- hasIn(path17) {
4244
- if (Collection.isEmptyPath(path17))
4280
+ hasIn(path24) {
4281
+ if (Collection.isEmptyPath(path24))
4245
4282
  return this.contents !== void 0;
4246
- return identity.isCollection(this.contents) ? this.contents.hasIn(path17) : false;
4283
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path24) : false;
4247
4284
  }
4248
4285
  /**
4249
4286
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -4260,13 +4297,13 @@ var require_Document = __commonJS({
4260
4297
  * Sets a value in this document. For `!!set`, `value` needs to be a
4261
4298
  * boolean to add/remove the item from the set.
4262
4299
  */
4263
- setIn(path17, value) {
4264
- if (Collection.isEmptyPath(path17)) {
4300
+ setIn(path24, value) {
4301
+ if (Collection.isEmptyPath(path24)) {
4265
4302
  this.contents = value;
4266
4303
  } else if (this.contents == null) {
4267
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path17), value);
4304
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path24), value);
4268
4305
  } else if (assertCollection(this.contents)) {
4269
- this.contents.setIn(path17, value);
4306
+ this.contents.setIn(path24, value);
4270
4307
  }
4271
4308
  }
4272
4309
  /**
@@ -6218,9 +6255,9 @@ var require_cst_visit = __commonJS({
6218
6255
  visit.BREAK = BREAK;
6219
6256
  visit.SKIP = SKIP;
6220
6257
  visit.REMOVE = REMOVE;
6221
- visit.itemAtPath = (cst, path17) => {
6258
+ visit.itemAtPath = (cst, path24) => {
6222
6259
  let item = cst;
6223
- for (const [field, index] of path17) {
6260
+ for (const [field, index] of path24) {
6224
6261
  const tok = item?.[field];
6225
6262
  if (tok && "items" in tok) {
6226
6263
  item = tok.items[index];
@@ -6229,23 +6266,23 @@ var require_cst_visit = __commonJS({
6229
6266
  }
6230
6267
  return item;
6231
6268
  };
6232
- visit.parentCollection = (cst, path17) => {
6233
- const parent = visit.itemAtPath(cst, path17.slice(0, -1));
6234
- const field = path17[path17.length - 1][0];
6269
+ visit.parentCollection = (cst, path24) => {
6270
+ const parent = visit.itemAtPath(cst, path24.slice(0, -1));
6271
+ const field = path24[path24.length - 1][0];
6235
6272
  const coll = parent?.[field];
6236
6273
  if (coll && "items" in coll)
6237
6274
  return coll;
6238
6275
  throw new Error("Parent collection not found");
6239
6276
  };
6240
- function _visit(path17, item, visitor) {
6241
- let ctrl = visitor(item, path17);
6277
+ function _visit(path24, item, visitor) {
6278
+ let ctrl = visitor(item, path24);
6242
6279
  if (typeof ctrl === "symbol")
6243
6280
  return ctrl;
6244
6281
  for (const field of ["key", "value"]) {
6245
6282
  const token = item[field];
6246
6283
  if (token && "items" in token) {
6247
6284
  for (let i = 0; i < token.items.length; ++i) {
6248
- const ci = _visit(Object.freeze(path17.concat([[field, i]])), token.items[i], visitor);
6285
+ const ci = _visit(Object.freeze(path24.concat([[field, i]])), token.items[i], visitor);
6249
6286
  if (typeof ci === "number")
6250
6287
  i = ci - 1;
6251
6288
  else if (ci === BREAK)
@@ -6256,10 +6293,10 @@ var require_cst_visit = __commonJS({
6256
6293
  }
6257
6294
  }
6258
6295
  if (typeof ctrl === "function" && field === "key")
6259
- ctrl = ctrl(item, path17);
6296
+ ctrl = ctrl(item, path24);
6260
6297
  }
6261
6298
  }
6262
- return typeof ctrl === "function" ? ctrl(item, path17) : ctrl;
6299
+ return typeof ctrl === "function" ? ctrl(item, path24) : ctrl;
6263
6300
  }
6264
6301
  exports2.visit = visit;
6265
6302
  }
@@ -7544,14 +7581,14 @@ var require_parser = __commonJS({
7544
7581
  case "scalar":
7545
7582
  case "single-quoted-scalar":
7546
7583
  case "double-quoted-scalar": {
7547
- const fs19 = this.flowScalar(this.type);
7584
+ const fs26 = this.flowScalar(this.type);
7548
7585
  if (atNextItem || it.value) {
7549
- map.items.push({ start, key: fs19, sep: [] });
7586
+ map.items.push({ start, key: fs26, sep: [] });
7550
7587
  this.onKeyLine = true;
7551
7588
  } else if (it.sep) {
7552
- this.stack.push(fs19);
7589
+ this.stack.push(fs26);
7553
7590
  } else {
7554
- Object.assign(it, { key: fs19, sep: [] });
7591
+ Object.assign(it, { key: fs26, sep: [] });
7555
7592
  this.onKeyLine = true;
7556
7593
  }
7557
7594
  return;
@@ -7679,13 +7716,13 @@ var require_parser = __commonJS({
7679
7716
  case "scalar":
7680
7717
  case "single-quoted-scalar":
7681
7718
  case "double-quoted-scalar": {
7682
- const fs19 = this.flowScalar(this.type);
7719
+ const fs26 = this.flowScalar(this.type);
7683
7720
  if (!it || it.value)
7684
- fc.items.push({ start: [], key: fs19, sep: [] });
7721
+ fc.items.push({ start: [], key: fs26, sep: [] });
7685
7722
  else if (it.sep)
7686
- this.stack.push(fs19);
7723
+ this.stack.push(fs26);
7687
7724
  else
7688
- Object.assign(it, { key: fs19, sep: [] });
7725
+ Object.assign(it, { key: fs26, sep: [] });
7689
7726
  return;
7690
7727
  }
7691
7728
  case "flow-map-end":
@@ -11186,8 +11223,8 @@ var require_utils = __commonJS({
11186
11223
  }
11187
11224
  return ind;
11188
11225
  }
11189
- function removeDotSegments(path17) {
11190
- let input = path17;
11226
+ function removeDotSegments(path24) {
11227
+ let input = path24;
11191
11228
  const output = [];
11192
11229
  let nextSlash = -1;
11193
11230
  let len = 0;
@@ -11386,8 +11423,8 @@ var require_schemes = __commonJS({
11386
11423
  wsComponent.secure = void 0;
11387
11424
  }
11388
11425
  if (wsComponent.resourceName) {
11389
- const [path17, query] = wsComponent.resourceName.split("?");
11390
- wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
11426
+ const [path24, query] = wsComponent.resourceName.split("?");
11427
+ wsComponent.path = path24 && path24 !== "/" ? path24 : void 0;
11391
11428
  wsComponent.query = query;
11392
11429
  wsComponent.resourceName = void 0;
11393
11430
  }
@@ -14740,12 +14777,12 @@ var require_dist2 = __commonJS({
14740
14777
  throw new Error(`Unknown format "${name}"`);
14741
14778
  return f;
14742
14779
  };
14743
- function addFormats2(ajv2, list, fs19, exportName) {
14780
+ function addFormats2(ajv2, list, fs26, exportName) {
14744
14781
  var _a;
14745
14782
  var _b;
14746
14783
  (_a = (_b = ajv2.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
14747
14784
  for (const f of list)
14748
- ajv2.addFormat(f, fs19[f]);
14785
+ ajv2.addFormat(f, fs26[f]);
14749
14786
  }
14750
14787
  module2.exports = exports2 = formatsPlugin;
14751
14788
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -14819,51 +14856,63 @@ rudi - RUDI CLI
14819
14856
  USAGE
14820
14857
  rudi <command> [options]
14821
14858
 
14822
- COMMANDS
14823
- search <query> Search for stacks, prompts, runtimes, binaries, agents
14824
- search --all List all available packages in registry
14825
- install <pkg> Install a package (stack, prompt, runtime, binary, agent)
14826
- remove <pkg> Remove an installed package
14827
- update [pkg] Update packages (or all if no pkg specified)
14828
- run <stack> [args] Run a stack with optional arguments
14829
- list [kind] List installed packages (stacks, prompts, runtimes, binaries, agents)
14830
-
14831
- secrets set <name> Set a secret
14832
- secrets list List configured secrets (masked)
14833
- secrets remove <name> Remove a secret
14834
-
14859
+ SETUP
14860
+ init Bootstrap RUDI (download runtimes, create shims)
14861
+
14862
+ INTROSPECTION
14863
+ home Show ~/.rudi structure and installed packages
14864
+ stacks List installed stacks
14865
+ runtimes List installed runtimes
14866
+ binaries List installed binaries
14867
+ agents List installed agents
14868
+ prompts List installed prompts
14869
+ doctor Check system health and dependencies
14870
+ doctor --all Show all available runtimes/binaries from registry
14871
+
14872
+ PACKAGE MANAGEMENT
14873
+ search <query> Search registry for packages
14874
+ search --all List all available packages
14875
+ install <pkg> Install a package
14876
+ remove <pkg> Remove a package
14877
+ update [pkg] Update packages
14878
+ run <stack> Run a stack
14879
+
14880
+ DATABASE
14835
14881
  db stats Show database statistics
14836
14882
  db search <query> Search conversation history
14883
+ db reset --force Delete all data
14884
+ db vacuum Compact and reclaim space
14885
+ db tables Show table row counts
14837
14886
 
14838
- import sessions Import sessions from AI providers (claude, codex, gemini)
14839
- import status Show import status for all providers
14840
-
14841
- logs [options] Query agent visibility logs
14887
+ SESSION IMPORT
14888
+ import sessions Import from AI providers (claude, codex, gemini)
14889
+ import status Show import status
14842
14890
 
14843
- doctor Check system health and configuration
14891
+ SECRETS
14892
+ secrets set <name> Set a secret
14893
+ secrets list List configured secrets
14894
+ secrets remove <name> Remove a secret
14844
14895
 
14845
14896
  OPTIONS
14846
14897
  -h, --help Show help
14847
14898
  -v, --version Show version
14848
14899
  --verbose Verbose output
14849
14900
  --json Output as JSON
14850
- --force Force operation (skip confirmations)
14901
+ --force Force operation
14851
14902
 
14852
14903
  EXAMPLES
14853
- rudi search pdf Search for PDF-related stacks
14854
- rudi install pdf-creator Install the pdf-creator stack
14855
- rudi run pdf-creator Run the pdf-creator stack
14856
- rudi list stacks List installed stacks
14857
- rudi secrets set VERCEL_KEY Set a secret
14858
-
14859
- PACKAGE NAMESPACES
14860
- stack:name A stack (executable workflow)
14861
- prompt:name A prompt template
14862
- runtime:name A runtime interpreter (node, python, deno, bun)
14863
- binary:name A utility binary (ffmpeg, imagemagick, ripgrep)
14864
- agent:name An AI CLI tool (claude, codex, gemini, copilot)
14865
-
14866
- If no namespace is given, 'stack:' is assumed.
14904
+ rudi home Show ~/.rudi structure
14905
+ rudi runtimes List installed runtimes
14906
+ rudi install runtime:python Install Python in ~/.rudi
14907
+ rudi install binary:ffmpeg Install ffmpeg
14908
+ rudi doctor --all Show all available deps
14909
+
14910
+ PACKAGE TYPES
14911
+ stack:name MCP server stack
14912
+ runtime:name Node, Python, Deno, Bun
14913
+ binary:name ffmpeg, ripgrep, etc.
14914
+ agent:name Claude, Codex, Gemini CLIs
14915
+ prompt:name Prompt template
14867
14916
  `);
14868
14917
  }
14869
14918
  function printCommandHelp(command) {
@@ -14932,12 +14981,15 @@ ARGUMENTS
14932
14981
 
14933
14982
  OPTIONS
14934
14983
  --json Output as JSON
14984
+ --detected Show MCP servers from agent configs (stacks only)
14985
+ --category=X Filter prompts by category
14935
14986
 
14936
14987
  EXAMPLES
14937
14988
  rudi list
14938
14989
  rudi list stacks
14990
+ rudi list stacks --detected Show MCP servers in Claude/Gemini/Codex
14939
14991
  rudi list binaries
14940
- rudi list agents
14992
+ rudi list prompts --category=coding
14941
14993
  `,
14942
14994
  secrets: `
14943
14995
  rudi secrets - Manage secrets
@@ -15007,6 +15059,54 @@ EXAMPLES
15007
15059
  rudi import sessions claude Import only Claude sessions
15008
15060
  rudi import sessions --dry-run Preview without importing
15009
15061
  rudi import status Check what's available to import
15062
+ `,
15063
+ init: `
15064
+ rudi init - Bootstrap RUDI environment
15065
+
15066
+ USAGE
15067
+ rudi init [options]
15068
+
15069
+ OPTIONS
15070
+ --force Reinitialize even if already set up
15071
+ --skip-downloads Skip downloading runtimes/binaries
15072
+ --quiet Minimal output (for programmatic use)
15073
+
15074
+ WHAT IT DOES
15075
+ 1. Creates ~/.rudi directory structure (if missing)
15076
+ 2. Downloads bundled runtimes (Node.js, Python) if not installed
15077
+ 3. Downloads essential binaries (sqlite3, ripgrep) if not installed
15078
+ 4. Creates/updates shims in ~/.rudi/shims/
15079
+ 5. Initializes the database (if missing)
15080
+ 6. Creates settings.json (if missing)
15081
+
15082
+ NOTE: Safe to run multiple times - only creates what's missing.
15083
+
15084
+ EXAMPLES
15085
+ rudi init
15086
+ rudi init --force
15087
+ rudi init --skip-downloads
15088
+ rudi init --quiet
15089
+ `,
15090
+ home: `
15091
+ rudi home - Show ~/.rudi structure and status
15092
+
15093
+ USAGE
15094
+ rudi home [options]
15095
+
15096
+ OPTIONS
15097
+ --verbose Show package details
15098
+ --json Output as JSON
15099
+
15100
+ SHOWS
15101
+ - Directory structure with sizes
15102
+ - Installed package counts
15103
+ - Database status
15104
+ - Quick commands reference
15105
+
15106
+ EXAMPLES
15107
+ rudi home
15108
+ rudi home --verbose
15109
+ rudi home --json
15010
15110
  `,
15011
15111
  doctor: `
15012
15112
  rudi doctor - System health check
@@ -15016,13 +15116,20 @@ USAGE
15016
15116
 
15017
15117
  OPTIONS
15018
15118
  --fix Attempt to fix issues
15119
+ --all Show all available runtimes/binaries from registry
15019
15120
 
15020
15121
  CHECKS
15021
15122
  - Directory structure
15022
15123
  - Database integrity
15023
15124
  - Installed packages
15024
- - Required runtimes
15125
+ - Available runtimes (node, python, deno, bun)
15126
+ - Available binaries (ffmpeg, ripgrep, etc.)
15025
15127
  - Secrets configuration
15128
+
15129
+ EXAMPLES
15130
+ rudi doctor
15131
+ rudi doctor --fix
15132
+ rudi doctor --all
15026
15133
  `,
15027
15134
  logs: `
15028
15135
  rudi logs - Query agent visibility logs
@@ -15280,7 +15387,7 @@ async function installSinglePackage(pkg, options = {}) {
15280
15387
  onProgress?.({ phase: "downloading", package: pkg.id });
15281
15388
  if (pkg.npmPackage) {
15282
15389
  try {
15283
- const { execSync: execSync5 } = await import("child_process");
15390
+ const { execSync: execSync7 } = await import("child_process");
15284
15391
  if (!import_fs4.default.existsSync(installPath)) {
15285
15392
  import_fs4.default.mkdirSync(installPath, { recursive: true });
15286
15393
  }
@@ -15288,16 +15395,16 @@ async function installSinglePackage(pkg, options = {}) {
15288
15395
  const resourcesPath = process.env.RESOURCES_PATH;
15289
15396
  const npmCmd = resourcesPath ? import_path4.default.join(resourcesPath, "bundled-runtimes", "node", "bin", "npm") : "npm";
15290
15397
  if (!import_fs4.default.existsSync(import_path4.default.join(installPath, "package.json"))) {
15291
- execSync5(`"${npmCmd}" init -y`, { cwd: installPath, stdio: "pipe" });
15398
+ execSync7(`"${npmCmd}" init -y`, { cwd: installPath, stdio: "pipe" });
15292
15399
  }
15293
- execSync5(`"${npmCmd}" install ${pkg.npmPackage}`, { cwd: installPath, stdio: "pipe" });
15400
+ execSync7(`"${npmCmd}" install ${pkg.npmPackage}`, { cwd: installPath, stdio: "pipe" });
15294
15401
  if (pkg.postInstall) {
15295
15402
  onProgress?.({ phase: "postInstall", package: pkg.id, message: pkg.postInstall });
15296
15403
  const postInstallCmd = pkg.postInstall.replace(
15297
15404
  /^npx\s+(\S+)/,
15298
15405
  `"${import_path4.default.join(installPath, "node_modules", ".bin", "$1")}"`
15299
15406
  );
15300
- execSync5(postInstallCmd, { cwd: installPath, stdio: "pipe" });
15407
+ execSync7(postInstallCmd, { cwd: installPath, stdio: "pipe" });
15301
15408
  }
15302
15409
  import_fs4.default.writeFileSync(
15303
15410
  import_path4.default.join(installPath, "manifest.json"),
@@ -15319,15 +15426,15 @@ async function installSinglePackage(pkg, options = {}) {
15319
15426
  }
15320
15427
  if (pkg.pipPackage) {
15321
15428
  try {
15322
- const { execSync: execSync5 } = await import("child_process");
15429
+ const { execSync: execSync7 } = await import("child_process");
15323
15430
  if (!import_fs4.default.existsSync(installPath)) {
15324
15431
  import_fs4.default.mkdirSync(installPath, { recursive: true });
15325
15432
  }
15326
15433
  onProgress?.({ phase: "installing", package: pkg.id, message: `pip install ${pkg.pipPackage}` });
15327
15434
  const pythonPath = import_path4.default.join(PATHS.runtimes, "python", "bin", "python3");
15328
15435
  const pythonCmd = import_fs4.default.existsSync(pythonPath) ? pythonPath : "python3";
15329
- execSync5(`"${pythonCmd}" -m venv "${installPath}/venv"`, { stdio: "pipe" });
15330
- execSync5(`"${installPath}/venv/bin/pip" install ${pkg.pipPackage}`, { stdio: "pipe" });
15436
+ execSync7(`"${pythonCmd}" -m venv "${installPath}/venv"`, { stdio: "pipe" });
15437
+ execSync7(`"${installPath}/venv/bin/pip" install ${pkg.pipPackage}`, { stdio: "pipe" });
15331
15438
  import_fs4.default.writeFileSync(
15332
15439
  import_path4.default.join(installPath, "manifest.json"),
15333
15440
  JSON.stringify({
@@ -15549,7 +15656,7 @@ async function listInstalled(kind) {
15549
15656
  return packages;
15550
15657
  }
15551
15658
  async function installStackDependencies(stackPath, onProgress) {
15552
- const { execSync: execSync5 } = await import("child_process");
15659
+ const { execSync: execSync7 } = await import("child_process");
15553
15660
  const nodePath = import_path4.default.join(stackPath, "node");
15554
15661
  if (import_fs4.default.existsSync(nodePath)) {
15555
15662
  const packageJsonPath = import_path4.default.join(nodePath, "package.json");
@@ -15557,7 +15664,7 @@ async function installStackDependencies(stackPath, onProgress) {
15557
15664
  onProgress?.({ phase: "installing-deps", message: "Installing Node.js dependencies..." });
15558
15665
  try {
15559
15666
  const npmCmd = await findNpmExecutable();
15560
- execSync5(`"${npmCmd}" install`, { cwd: nodePath, stdio: "pipe" });
15667
+ execSync7(`"${npmCmd}" install`, { cwd: nodePath, stdio: "pipe" });
15561
15668
  } catch (error) {
15562
15669
  console.warn(`Warning: Failed to install Node.js dependencies: ${error.message}`);
15563
15670
  }
@@ -15570,8 +15677,8 @@ async function installStackDependencies(stackPath, onProgress) {
15570
15677
  onProgress?.({ phase: "installing-deps", message: "Installing Python dependencies..." });
15571
15678
  try {
15572
15679
  const pythonCmd = await findPythonExecutable();
15573
- execSync5(`"${pythonCmd}" -m venv venv`, { cwd: pythonPath, stdio: "pipe" });
15574
- execSync5("./venv/bin/pip install -r requirements.txt", { cwd: pythonPath, stdio: "pipe" });
15680
+ execSync7(`"${pythonCmd}" -m venv venv`, { cwd: pythonPath, stdio: "pipe" });
15681
+ execSync7("./venv/bin/pip install -r requirements.txt", { cwd: pythonPath, stdio: "pipe" });
15575
15682
  } catch (error) {
15576
15683
  console.warn(`Warning: Failed to install Python dependencies: ${error.message}`);
15577
15684
  }
@@ -15923,407 +16030,319 @@ Total: ${totalCount} package(s) available`);
15923
16030
  }
15924
16031
 
15925
16032
  // src/commands/install.js
15926
- var fs7 = __toESM(require("fs/promises"), 1);
15927
- var path7 = __toESM(require("path"), 1);
16033
+ var fs8 = __toESM(require("fs/promises"), 1);
16034
+ var path8 = __toESM(require("path"), 1);
16035
+ var import_child_process2 = require("child_process");
15928
16036
 
15929
- // src/utils/mcp-registry.js
15930
- var fs6 = __toESM(require("fs/promises"), 1);
16037
+ // src/commands/secrets-store.js
16038
+ var fs6 = __toESM(require("fs"), 1);
15931
16039
  var path6 = __toESM(require("path"), 1);
15932
- var os2 = __toESM(require("os"), 1);
15933
- var HOME = os2.homedir();
15934
- var AGENT_CONFIGS = {
15935
- claude: path6.join(HOME, ".claude", "settings.json"),
15936
- codex: path6.join(HOME, ".codex", "config.toml"),
15937
- gemini: path6.join(HOME, ".gemini", "settings.json")
15938
- };
15939
- async function readJson(filePath) {
16040
+ init_src();
16041
+ var SECRETS_FILE = path6.join(PATHS.home, "secrets.json");
16042
+ function ensureSecretsFile() {
16043
+ const dir = path6.dirname(SECRETS_FILE);
16044
+ if (!fs6.existsSync(dir)) {
16045
+ fs6.mkdirSync(dir, { recursive: true });
16046
+ }
16047
+ if (!fs6.existsSync(SECRETS_FILE)) {
16048
+ fs6.writeFileSync(SECRETS_FILE, "{}", { mode: 384 });
16049
+ } else {
16050
+ try {
16051
+ fs6.chmodSync(SECRETS_FILE, 384);
16052
+ } catch {
16053
+ }
16054
+ }
16055
+ }
16056
+ function loadSecrets() {
16057
+ ensureSecretsFile();
15940
16058
  try {
15941
- const content = await fs6.readFile(filePath, "utf-8");
16059
+ const content = fs6.readFileSync(SECRETS_FILE, "utf-8");
15942
16060
  return JSON.parse(content);
15943
16061
  } catch {
15944
16062
  return {};
15945
16063
  }
15946
16064
  }
15947
- async function writeJson(filePath, data) {
15948
- const dir = path6.dirname(filePath);
15949
- await fs6.mkdir(dir, { recursive: true });
15950
- await fs6.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
16065
+ function saveSecrets(secrets) {
16066
+ ensureSecretsFile();
16067
+ fs6.writeFileSync(SECRETS_FILE, JSON.stringify(secrets, null, 2), {
16068
+ encoding: "utf-8",
16069
+ mode: 384
16070
+ });
15951
16071
  }
15952
- function parseTomlValue(value) {
15953
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
15954
- return value.slice(1, -1);
15955
- }
15956
- if (value.startsWith("[") && value.endsWith("]")) {
15957
- const inner = value.slice(1, -1).trim();
15958
- if (!inner) return [];
15959
- const items = [];
15960
- let current = "";
15961
- let inQuote = false;
15962
- let quoteChar = "";
15963
- for (const char of inner) {
15964
- if ((char === '"' || char === "'") && !inQuote) {
15965
- inQuote = true;
15966
- quoteChar = char;
15967
- } else if (char === quoteChar && inQuote) {
15968
- inQuote = false;
15969
- items.push(current);
15970
- current = "";
15971
- } else if (char === "," && !inQuote) {
15972
- } else if (inQuote) {
15973
- current += char;
15974
- }
15975
- }
15976
- return items;
15977
- }
15978
- if (value === "true") return true;
15979
- if (value === "false") return false;
15980
- const num = Number(value);
15981
- if (!isNaN(num)) return num;
15982
- return value;
16072
+ async function getSecret(name) {
16073
+ const secrets = loadSecrets();
16074
+ return secrets[name] || null;
15983
16075
  }
15984
- function parseToml(content) {
15985
- const result = {};
15986
- const lines = content.split("\n");
15987
- let currentTable = [];
15988
- for (const line of lines) {
15989
- const trimmed = line.trim();
15990
- if (!trimmed || trimmed.startsWith("#")) continue;
15991
- const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
15992
- if (tableMatch) {
15993
- currentTable = tableMatch[1].split(".");
15994
- let obj = result;
15995
- for (const key of currentTable) {
15996
- obj[key] = obj[key] || {};
15997
- obj = obj[key];
15998
- }
15999
- continue;
16000
- }
16001
- const kvMatch = trimmed.match(/^([^=]+)=(.*)$/);
16002
- if (kvMatch) {
16003
- const key = kvMatch[1].trim();
16004
- let value = kvMatch[2].trim();
16005
- const parsed = parseTomlValue(value);
16006
- let obj = result;
16007
- for (const tableKey of currentTable) {
16008
- obj = obj[tableKey];
16009
- }
16010
- obj[key] = parsed;
16076
+ async function setSecret(name, value) {
16077
+ const secrets = loadSecrets();
16078
+ secrets[name] = value;
16079
+ saveSecrets(secrets);
16080
+ return true;
16081
+ }
16082
+ async function removeSecret(name) {
16083
+ const secrets = loadSecrets();
16084
+ delete secrets[name];
16085
+ saveSecrets(secrets);
16086
+ return true;
16087
+ }
16088
+ async function listSecrets() {
16089
+ const secrets = loadSecrets();
16090
+ return Object.keys(secrets).sort();
16091
+ }
16092
+ async function hasSecret(name) {
16093
+ const secrets = loadSecrets();
16094
+ return secrets[name] !== void 0 && secrets[name] !== null && secrets[name] !== "";
16095
+ }
16096
+ async function getMaskedSecrets() {
16097
+ const secrets = loadSecrets();
16098
+ const masked = {};
16099
+ for (const [name, value] of Object.entries(secrets)) {
16100
+ if (value && typeof value === "string" && value.length > 8) {
16101
+ masked[name] = value.slice(0, 4) + "..." + value.slice(-4);
16102
+ } else if (value && typeof value === "string" && value.length > 0) {
16103
+ masked[name] = "****";
16104
+ } else {
16105
+ masked[name] = "(pending)";
16011
16106
  }
16012
16107
  }
16013
- return result;
16108
+ return masked;
16014
16109
  }
16015
- function tomlValue(value) {
16016
- if (typeof value === "string") {
16017
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
16018
- }
16019
- if (typeof value === "boolean") {
16020
- return value ? "true" : "false";
16021
- }
16022
- if (typeof value === "number") {
16023
- return String(value);
16024
- }
16025
- if (Array.isArray(value)) {
16026
- const items = value.map((v) => tomlValue(v));
16027
- return `[${items.join(", ")}]`;
16028
- }
16029
- return String(value);
16110
+ function getStorageInfo() {
16111
+ return {
16112
+ backend: "file",
16113
+ file: SECRETS_FILE,
16114
+ permissions: "0600 (owner read/write only)"
16115
+ };
16030
16116
  }
16031
- function stringifyToml(config, prefix = "") {
16032
- const lines = [];
16033
- for (const [key, value] of Object.entries(config)) {
16034
- if (typeof value !== "object" || Array.isArray(value)) {
16035
- lines.push(`${key} = ${tomlValue(value)}`);
16117
+
16118
+ // src/utils/agents.js
16119
+ var import_fs6 = __toESM(require("fs"), 1);
16120
+ var import_path6 = __toESM(require("path"), 1);
16121
+ var import_os2 = __toESM(require("os"), 1);
16122
+ var AGENT_CONFIGS2 = [
16123
+ // Claude Desktop (Anthropic)
16124
+ {
16125
+ id: "claude-desktop",
16126
+ name: "Claude Desktop",
16127
+ key: "mcpServers",
16128
+ paths: {
16129
+ darwin: ["Library/Application Support/Claude/claude_desktop_config.json"],
16130
+ win32: ["AppData/Roaming/Claude/claude_desktop_config.json"],
16131
+ linux: [".config/claude/claude_desktop_config.json"]
16036
16132
  }
16037
- }
16038
- for (const [key, value] of Object.entries(config)) {
16039
- if (typeof value === "object" && !Array.isArray(value)) {
16040
- const tablePath = prefix ? `${prefix}.${key}` : key;
16041
- const hasSimpleValues = Object.values(value).some(
16042
- (v) => typeof v !== "object" || Array.isArray(v)
16043
- );
16044
- if (hasSimpleValues) {
16045
- lines.push("");
16046
- lines.push(`[${tablePath}]`);
16047
- }
16048
- const nested = stringifyToml(value, tablePath);
16049
- if (nested.trim()) {
16050
- lines.push(nested);
16051
- }
16133
+ },
16134
+ // Claude Code CLI (Anthropic)
16135
+ {
16136
+ id: "claude-code",
16137
+ name: "Claude Code",
16138
+ key: "mcpServers",
16139
+ paths: {
16140
+ darwin: [".claude.json"],
16141
+ // User scope (all projects)
16142
+ win32: [".claude.json"],
16143
+ linux: [".claude.json"]
16144
+ }
16145
+ },
16146
+ // Cursor (Anysphere)
16147
+ {
16148
+ id: "cursor",
16149
+ name: "Cursor",
16150
+ key: "mcpServers",
16151
+ paths: {
16152
+ darwin: [".cursor/mcp.json"],
16153
+ win32: [".cursor/mcp.json"],
16154
+ linux: [".cursor/mcp.json"]
16155
+ }
16156
+ },
16157
+ // Windsurf (Codeium)
16158
+ {
16159
+ id: "windsurf",
16160
+ name: "Windsurf",
16161
+ key: "mcpServers",
16162
+ paths: {
16163
+ darwin: [".codeium/windsurf/mcp_config.json"],
16164
+ win32: [".codeium/windsurf/mcp_config.json"],
16165
+ linux: [".codeium/windsurf/mcp_config.json"]
16166
+ }
16167
+ },
16168
+ // Cline (VS Code extension)
16169
+ {
16170
+ id: "cline",
16171
+ name: "Cline",
16172
+ key: "mcpServers",
16173
+ paths: {
16174
+ darwin: ["Documents/Cline/cline_mcp_settings.json"],
16175
+ win32: ["Documents/Cline/cline_mcp_settings.json"],
16176
+ linux: ["Documents/Cline/cline_mcp_settings.json"]
16177
+ }
16178
+ },
16179
+ // Zed Editor
16180
+ {
16181
+ id: "zed",
16182
+ name: "Zed",
16183
+ key: "context_servers",
16184
+ // Zed uses different key
16185
+ paths: {
16186
+ darwin: [".zed/settings.json"],
16187
+ win32: [".config/zed/settings.json"],
16188
+ linux: [".config/zed/settings.json"]
16189
+ }
16190
+ },
16191
+ // VS Code / GitHub Copilot
16192
+ {
16193
+ id: "vscode",
16194
+ name: "VS Code",
16195
+ key: "servers",
16196
+ paths: {
16197
+ darwin: ["Library/Application Support/Code/User/mcp.json"],
16198
+ win32: ["AppData/Roaming/Code/User/mcp.json"],
16199
+ linux: [".config/Code/User/mcp.json"]
16200
+ }
16201
+ },
16202
+ // Gemini CLI (Google)
16203
+ {
16204
+ id: "gemini",
16205
+ name: "Gemini",
16206
+ key: "mcpServers",
16207
+ paths: {
16208
+ darwin: [".gemini/settings.json"],
16209
+ win32: [".gemini/settings.json"],
16210
+ linux: [".gemini/settings.json"]
16211
+ }
16212
+ },
16213
+ // Codex CLI (OpenAI)
16214
+ {
16215
+ id: "codex",
16216
+ name: "Codex",
16217
+ key: "mcpServers",
16218
+ paths: {
16219
+ darwin: [".codex/config.json", ".codex/settings.json"],
16220
+ win32: [".codex/config.json", ".codex/settings.json"],
16221
+ linux: [".codex/config.json", ".codex/settings.json"]
16222
+ }
16223
+ }
16224
+ ];
16225
+ function getAgentConfigPaths(agentConfig) {
16226
+ const home = import_os2.default.homedir();
16227
+ const platform = process.platform;
16228
+ const relativePaths = agentConfig.paths[platform] || agentConfig.paths.linux || [];
16229
+ return relativePaths.map((p) => import_path6.default.join(home, p));
16230
+ }
16231
+ function findAgentConfig(agentConfig) {
16232
+ const paths = getAgentConfigPaths(agentConfig);
16233
+ for (const configPath of paths) {
16234
+ if (import_fs6.default.existsSync(configPath)) {
16235
+ return configPath;
16052
16236
  }
16053
16237
  }
16054
- return lines.join("\n");
16238
+ return null;
16055
16239
  }
16056
- async function readToml(filePath) {
16240
+ function readAgentMcpServers(agentConfig) {
16241
+ const configPath = findAgentConfig(agentConfig);
16242
+ if (!configPath) return [];
16057
16243
  try {
16058
- const content = await fs6.readFile(filePath, "utf-8");
16059
- return parseToml(content);
16060
- } catch {
16061
- return {};
16244
+ const content = JSON.parse(import_fs6.default.readFileSync(configPath, "utf-8"));
16245
+ const mcpServers = content[agentConfig.key] || {};
16246
+ return Object.entries(mcpServers).map(([name, config]) => {
16247
+ const command = config.command || config.path || config.command?.path;
16248
+ return {
16249
+ name,
16250
+ agent: agentConfig.id,
16251
+ agentName: agentConfig.name,
16252
+ command: command || "unknown",
16253
+ args: config.args,
16254
+ cwd: config.cwd,
16255
+ env: config.env ? Object.keys(config.env) : [],
16256
+ // Just list env var names, not values
16257
+ configFile: configPath
16258
+ };
16259
+ });
16260
+ } catch (e) {
16261
+ return [];
16062
16262
  }
16063
16263
  }
16064
- async function writeToml(filePath, data) {
16065
- const dir = path6.dirname(filePath);
16066
- await fs6.mkdir(dir, { recursive: true });
16067
- await fs6.writeFile(filePath, stringifyToml(data), "utf-8");
16264
+ function detectAllMcpServers() {
16265
+ const servers = [];
16266
+ for (const agentConfig of AGENT_CONFIGS2) {
16267
+ const agentServers = readAgentMcpServers(agentConfig);
16268
+ servers.push(...agentServers);
16269
+ }
16270
+ return servers;
16068
16271
  }
16069
- function parseEnvFile(content) {
16070
- const env = {};
16071
- const lines = content.split("\n");
16072
- for (const line of lines) {
16073
- const trimmed = line.trim();
16074
- if (!trimmed || trimmed.startsWith("#")) continue;
16075
- const eqIndex = trimmed.indexOf("=");
16076
- if (eqIndex === -1) continue;
16077
- const key = trimmed.slice(0, eqIndex).trim();
16078
- let value = trimmed.slice(eqIndex + 1).trim();
16079
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
16080
- value = value.slice(1, -1);
16081
- }
16082
- if (value) {
16083
- env[key] = value;
16272
+ function getInstalledAgents() {
16273
+ return AGENT_CONFIGS2.filter((agent) => findAgentConfig(agent) !== null).map((agent) => ({
16274
+ id: agent.id,
16275
+ name: agent.name,
16276
+ configFile: findAgentConfig(agent)
16277
+ }));
16278
+ }
16279
+ function getMcpServerSummary() {
16280
+ const summary = {};
16281
+ for (const agentConfig of AGENT_CONFIGS2) {
16282
+ const configPath = findAgentConfig(agentConfig);
16283
+ if (configPath) {
16284
+ const servers = readAgentMcpServers(agentConfig);
16285
+ summary[agentConfig.id] = {
16286
+ name: agentConfig.name,
16287
+ configFile: configPath,
16288
+ serverCount: servers.length,
16289
+ servers: servers.map((s) => s.name)
16290
+ };
16084
16291
  }
16085
16292
  }
16086
- return env;
16293
+ return summary;
16087
16294
  }
16088
- async function readStackEnv(installPath) {
16089
- const envPath = path6.join(installPath, ".env");
16295
+
16296
+ // src/commands/install.js
16297
+ async function loadManifest(installPath) {
16298
+ const manifestPath = path8.join(installPath, "manifest.json");
16090
16299
  try {
16091
- const content = await fs6.readFile(envPath, "utf-8");
16092
- return parseEnvFile(content);
16300
+ const content = await fs8.readFile(manifestPath, "utf-8");
16301
+ return JSON.parse(content);
16093
16302
  } catch {
16094
- return {};
16303
+ return null;
16095
16304
  }
16096
16305
  }
16097
- async function buildMcpConfig(stackId, installPath, manifest) {
16098
- let command;
16099
- let args = [];
16100
- const cwd = installPath;
16101
- const resolveRelativePath2 = (value) => {
16102
- if (!value || path6.isAbsolute(value)) return value;
16103
- const isPathLike = value.startsWith(".") || value.includes("/") || value.includes("\\");
16104
- return isPathLike ? path6.join(installPath, value) : value;
16105
- };
16106
- if (manifest.command) {
16107
- const cmdArray = Array.isArray(manifest.command) ? manifest.command : [manifest.command];
16108
- command = resolveRelativePath2(cmdArray[0]);
16109
- args = cmdArray.slice(1).map((arg) => resolveRelativePath2(arg));
16110
- } else if (manifest.mcp) {
16111
- command = resolveRelativePath2(manifest.mcp.command);
16112
- args = (manifest.mcp.args || []).map((arg) => resolveRelativePath2(arg));
16113
- if (manifest.mcp.entry) {
16114
- args = args.map(
16115
- (arg) => arg === manifest.mcp.entry ? path6.join(installPath, manifest.mcp.entry) : arg
16116
- );
16117
- }
16118
- } else {
16119
- return null;
16120
- }
16121
- const optimized = await optimizeEntryPoint(installPath, command, args);
16122
- if (optimized) {
16123
- command = optimized.command;
16124
- args = optimized.args;
16125
- }
16126
- const env = await readStackEnv(installPath);
16127
- const config = {
16128
- command,
16129
- cwd
16130
- };
16131
- if (args.length > 0) {
16132
- config.args = args;
16133
- }
16134
- if (Object.keys(env).length > 0) {
16135
- config.env = env;
16136
- }
16137
- return config;
16138
- }
16139
- async function optimizeEntryPoint(installPath, command, args) {
16140
- if (command !== "npx" || !args.includes("tsx")) {
16141
- return null;
16142
- }
16143
- const tsFileIndex = args.findIndex((arg) => arg.endsWith(".ts"));
16144
- if (tsFileIndex === -1) {
16145
- return null;
16146
- }
16147
- const tsFile = args[tsFileIndex];
16148
- const jsFile = tsFile.replace("/src/", "/dist/").replace(".ts", ".js");
16149
- const jsPath = path6.isAbsolute(jsFile) ? jsFile : path6.join(installPath, jsFile);
16150
- try {
16151
- await fs6.access(jsPath);
16152
- return {
16153
- command: "node",
16154
- args: [jsPath]
16155
- };
16156
- } catch {
16157
- return null;
16158
- }
16159
- }
16160
- async function registerMcpClaude(stackId, installPath, manifest) {
16161
- const configPath = AGENT_CONFIGS.claude;
16162
- try {
16163
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16164
- if (!mcpConfig) {
16165
- return { success: true, skipped: true };
16166
- }
16167
- mcpConfig.type = "stdio";
16168
- const settings = await readJson(configPath);
16169
- if (!settings.mcpServers) {
16170
- settings.mcpServers = {};
16171
- }
16172
- settings.mcpServers[stackId] = mcpConfig;
16173
- await writeJson(configPath, settings);
16174
- console.log(` Registered MCP in Claude: ${stackId}`);
16175
- return { success: true };
16176
- } catch (error) {
16177
- console.error(` Failed to register MCP in Claude: ${error.message}`);
16178
- return { success: false, error: error.message };
16179
- }
16180
- }
16181
- async function unregisterMcpClaude(stackId) {
16182
- const configPath = AGENT_CONFIGS.claude;
16183
- try {
16184
- const settings = await readJson(configPath);
16185
- if (!settings.mcpServers || !settings.mcpServers[stackId]) {
16186
- return { success: true, skipped: true };
16187
- }
16188
- delete settings.mcpServers[stackId];
16189
- await writeJson(configPath, settings);
16190
- console.log(` Unregistered MCP from Claude: ${stackId}`);
16191
- return { success: true };
16192
- } catch (error) {
16193
- console.error(` Failed to unregister MCP from Claude: ${error.message}`);
16194
- return { success: false, error: error.message };
16195
- }
16196
- }
16197
- async function registerMcpCodex(stackId, installPath, manifest) {
16198
- const configPath = AGENT_CONFIGS.codex;
16199
- try {
16200
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16201
- if (!mcpConfig) {
16202
- return { success: true, skipped: true };
16203
- }
16204
- const config = await readToml(configPath);
16205
- if (!config.mcp_servers) {
16206
- config.mcp_servers = {};
16207
- }
16208
- config.mcp_servers[stackId] = mcpConfig;
16209
- await writeToml(configPath, config);
16210
- console.log(` Registered MCP in Codex: ${stackId}`);
16211
- return { success: true };
16212
- } catch (error) {
16213
- console.error(` Failed to register MCP in Codex: ${error.message}`);
16214
- return { success: false, error: error.message };
16215
- }
16216
- }
16217
- async function unregisterMcpCodex(stackId) {
16218
- const configPath = AGENT_CONFIGS.codex;
16219
- try {
16220
- const config = await readToml(configPath);
16221
- if (!config.mcp_servers || !config.mcp_servers[stackId]) {
16222
- return { success: true, skipped: true };
16223
- }
16224
- delete config.mcp_servers[stackId];
16225
- await writeToml(configPath, config);
16226
- console.log(` Unregistered MCP from Codex: ${stackId}`);
16227
- return { success: true };
16228
- } catch (error) {
16229
- console.error(` Failed to unregister MCP from Codex: ${error.message}`);
16230
- return { success: false, error: error.message };
16231
- }
16232
- }
16233
- async function registerMcpGemini(stackId, installPath, manifest) {
16234
- const configPath = AGENT_CONFIGS.gemini;
16235
- try {
16236
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16237
- if (!mcpConfig) {
16238
- return { success: true, skipped: true };
16239
- }
16240
- const settings = await readJson(configPath);
16241
- if (!settings.mcpServers) {
16242
- settings.mcpServers = {};
16243
- }
16244
- settings.mcpServers[stackId] = mcpConfig;
16245
- await writeJson(configPath, settings);
16246
- console.log(` Registered MCP in Gemini: ${stackId}`);
16247
- return { success: true };
16248
- } catch (error) {
16249
- console.error(` Failed to register MCP in Gemini: ${error.message}`);
16250
- return { success: false, error: error.message };
16251
- }
16252
- }
16253
- async function unregisterMcpGemini(stackId) {
16254
- const configPath = AGENT_CONFIGS.gemini;
16306
+ async function installDependencies(stackPath, manifest) {
16307
+ const runtime = manifest?.runtime || "node";
16255
16308
  try {
16256
- const settings = await readJson(configPath);
16257
- if (!settings.mcpServers || !settings.mcpServers[stackId]) {
16258
- return { success: true, skipped: true };
16309
+ if (runtime === "node") {
16310
+ const packageJsonPath = path8.join(stackPath, "package.json");
16311
+ try {
16312
+ await fs8.access(packageJsonPath);
16313
+ } catch {
16314
+ return { installed: false, reason: "No package.json" };
16315
+ }
16316
+ const nodeModulesPath = path8.join(stackPath, "node_modules");
16317
+ try {
16318
+ await fs8.access(nodeModulesPath);
16319
+ return { installed: false, reason: "Dependencies already installed" };
16320
+ } catch {
16321
+ }
16322
+ console.log(` Installing npm dependencies...`);
16323
+ (0, import_child_process2.execSync)("npm install --production", {
16324
+ cwd: stackPath,
16325
+ stdio: "pipe"
16326
+ // Suppress output
16327
+ });
16328
+ return { installed: true };
16329
+ } else if (runtime === "python") {
16330
+ const requirementsPath = path8.join(stackPath, "requirements.txt");
16331
+ try {
16332
+ await fs8.access(requirementsPath);
16333
+ } catch {
16334
+ return { installed: false, reason: "No requirements.txt" };
16335
+ }
16336
+ console.log(` Installing pip dependencies...`);
16337
+ (0, import_child_process2.execSync)("pip install -r requirements.txt", {
16338
+ cwd: stackPath,
16339
+ stdio: "pipe"
16340
+ });
16341
+ return { installed: true };
16259
16342
  }
16260
- delete settings.mcpServers[stackId];
16261
- await writeJson(configPath, settings);
16262
- console.log(` Unregistered MCP from Gemini: ${stackId}`);
16263
- return { success: true };
16343
+ return { installed: false, reason: `Unknown runtime: ${runtime}` };
16264
16344
  } catch (error) {
16265
- console.error(` Failed to unregister MCP from Gemini: ${error.message}`);
16266
- return { success: false, error: error.message };
16267
- }
16268
- }
16269
- async function getInstalledAgents() {
16270
- const agentsDir = path6.join(HOME, ".rudi", "agents");
16271
- const installed = [];
16272
- for (const agent of ["claude", "codex", "gemini"]) {
16273
- const agentPath = path6.join(agentsDir, agent);
16274
- try {
16275
- await fs6.access(agentPath);
16276
- installed.push(agent);
16277
- } catch {
16278
- }
16279
- }
16280
- return installed;
16281
- }
16282
- async function registerMcpAll(stackId, installPath, manifest, targetAgents = null) {
16283
- const agents = targetAgents || await getInstalledAgents();
16284
- const results = {};
16285
- for (const agent of agents) {
16286
- switch (agent) {
16287
- case "claude":
16288
- results.claude = await registerMcpClaude(stackId, installPath, manifest);
16289
- break;
16290
- case "codex":
16291
- results.codex = await registerMcpCodex(stackId, installPath, manifest);
16292
- break;
16293
- case "gemini":
16294
- results.gemini = await registerMcpGemini(stackId, installPath, manifest);
16295
- break;
16296
- }
16297
- }
16298
- return results;
16299
- }
16300
- async function unregisterMcpAll(stackId, targetAgents = null) {
16301
- const agents = targetAgents || await getInstalledAgents();
16302
- const results = {};
16303
- for (const agent of agents) {
16304
- switch (agent) {
16305
- case "claude":
16306
- results.claude = await unregisterMcpClaude(stackId);
16307
- break;
16308
- case "codex":
16309
- results.codex = await unregisterMcpCodex(stackId);
16310
- break;
16311
- case "gemini":
16312
- results.gemini = await unregisterMcpGemini(stackId);
16313
- break;
16314
- }
16315
- }
16316
- return results;
16317
- }
16318
-
16319
- // src/commands/install.js
16320
- async function loadManifest(installPath) {
16321
- const manifestPath = path7.join(installPath, "manifest.json");
16322
- try {
16323
- const content = await fs7.readFile(manifestPath, "utf-8");
16324
- return JSON.parse(content);
16325
- } catch {
16326
- return null;
16345
+ return { installed: false, error: error.message };
16327
16346
  }
16328
16347
  }
16329
16348
  function getManifestSecrets(manifest) {
@@ -16333,67 +16352,56 @@ function getSecretName(secret) {
16333
16352
  if (typeof secret === "string") return secret;
16334
16353
  return secret.name || secret.key;
16335
16354
  }
16336
- function getSecretDescription(secret) {
16337
- if (typeof secret !== "object" || !secret) return null;
16338
- return secret.description || secret.label || null;
16339
- }
16340
16355
  function getSecretLink(secret) {
16341
16356
  if (typeof secret !== "object" || !secret) return null;
16342
16357
  return secret.link || secret.helpUrl || null;
16343
16358
  }
16344
- function getSecretLabel(secret) {
16345
- if (typeof secret !== "object" || !secret) return null;
16346
- return secret.label || secret.name || secret.key || null;
16347
- }
16348
- async function createEnvFile(installPath, manifest) {
16359
+ async function checkSecrets(manifest) {
16349
16360
  const secrets = getManifestSecrets(manifest);
16350
- if (!secrets.length) return null;
16351
- const envPath = path7.join(installPath, ".env");
16352
- try {
16353
- await fs7.access(envPath);
16354
- console.log(` .env file already exists, preserving existing secrets`);
16355
- return envPath;
16356
- } catch {
16357
- }
16358
- const lines = [];
16359
- lines.push("# Environment variables for this stack");
16360
- lines.push("# Fill in your API keys below");
16361
- lines.push("");
16361
+ const found = [];
16362
+ const missing = [];
16362
16363
  for (const secret of secrets) {
16363
16364
  const key = getSecretName(secret);
16364
- const description = getSecretDescription(secret);
16365
- const helpUrl = getSecretLink(secret);
16366
- if (description) {
16367
- lines.push(`# ${description}`);
16365
+ const isRequired = typeof secret === "object" ? secret.required !== false : true;
16366
+ const exists = await hasSecret(key);
16367
+ if (exists) {
16368
+ found.push(key);
16369
+ } else if (isRequired) {
16370
+ missing.push(key);
16368
16371
  }
16369
- if (helpUrl) {
16370
- lines.push(`# Get yours: ${helpUrl}`);
16372
+ }
16373
+ return { found, missing };
16374
+ }
16375
+ async function parseEnvExample(installPath) {
16376
+ const examplePath = path8.join(installPath, ".env.example");
16377
+ try {
16378
+ const content = await fs8.readFile(examplePath, "utf-8");
16379
+ const keys = [];
16380
+ for (const line of content.split("\n")) {
16381
+ const trimmed = line.trim();
16382
+ if (!trimmed || trimmed.startsWith("#")) continue;
16383
+ const match = trimmed.match(/^([A-Z][A-Z0-9_]*)=/);
16384
+ if (match) {
16385
+ keys.push(match[1]);
16386
+ }
16371
16387
  }
16372
- lines.push(`${key}=`);
16373
- lines.push("");
16388
+ return keys;
16389
+ } catch {
16390
+ return [];
16374
16391
  }
16375
- await fs7.writeFile(envPath, lines.join("\n"), "utf-8");
16376
- return envPath;
16377
16392
  }
16378
16393
  async function cmdInstall(args, flags) {
16379
16394
  const pkgId = args[0];
16380
16395
  if (!pkgId) {
16381
16396
  console.error("Usage: rudi install <package>");
16382
- console.error(" rudi install <package> --agent=claude (register to Claude only)");
16383
- console.error(" rudi install <package> --agent=claude,codex (register to specific agents)");
16384
- console.error("Example: rudi install pdf-creator");
16397
+ console.error("Example: rudi install slack");
16398
+ console.error("");
16399
+ console.error("After installing, run:");
16400
+ console.error(" rudi secrets set <KEY> # Configure required secrets");
16401
+ console.error(" rudi integrate all # Wire up your agents");
16385
16402
  process.exit(1);
16386
16403
  }
16387
16404
  const force = flags.force || false;
16388
- let targetAgents = null;
16389
- if (flags.agent) {
16390
- const validAgents = ["claude", "codex", "gemini"];
16391
- targetAgents = flags.agent.split(",").map((a) => a.trim()).filter((a) => validAgents.includes(a));
16392
- if (targetAgents.length === 0) {
16393
- console.error(`Invalid --agent value. Valid agents: ${validAgents.join(", ")}`);
16394
- process.exit(1);
16395
- }
16396
- }
16397
16405
  console.log(`Resolving ${pkgId}...`);
16398
16406
  try {
16399
16407
  const resolved = await resolvePackage(pkgId);
@@ -16416,26 +16424,40 @@ Dependencies:`);
16416
16424
  console.log(` - ${dep.id} ${status}`);
16417
16425
  }
16418
16426
  }
16419
- if (resolved.requires?.secrets?.length > 0) {
16420
- console.log(`
16421
- Required secrets:`);
16422
- for (const secret of resolved.requires.secrets) {
16423
- const name = typeof secret === "string" ? secret : secret.name;
16424
- console.log(` - ${name}`);
16425
- }
16426
- }
16427
+ console.log(`
16428
+ Dependency check:`);
16427
16429
  const depCheck = checkAllDependencies(resolved);
16428
16430
  if (depCheck.results.length > 0) {
16429
- console.log(`
16430
- System dependencies:`);
16431
16431
  for (const line of formatDependencyResults(depCheck.results)) {
16432
16432
  console.log(line);
16433
16433
  }
16434
- if (!depCheck.satisfied && !force) {
16435
- console.error(`
16436
- \u2717 Missing required dependencies. Install them first or use --force to skip.`);
16437
- process.exit(1);
16434
+ }
16435
+ const secretsCheck = { found: [], missing: [] };
16436
+ if (resolved.requires?.secrets?.length > 0) {
16437
+ for (const secret of resolved.requires.secrets) {
16438
+ const name = typeof secret === "string" ? secret : secret.name;
16439
+ const isRequired = typeof secret === "object" ? secret.required !== false : true;
16440
+ const exists = await hasSecret(name);
16441
+ if (exists) {
16442
+ secretsCheck.found.push(name);
16443
+ console.log(` \u2713 ${name} (from secrets store)`);
16444
+ } else if (isRequired) {
16445
+ secretsCheck.missing.push(name);
16446
+ console.log(` \u25CB ${name} - not configured`);
16447
+ } else {
16448
+ console.log(` \u25CB ${name} (optional)`);
16449
+ }
16450
+ }
16451
+ }
16452
+ if (!depCheck.satisfied && !force) {
16453
+ console.error(`
16454
+ \u2717 Missing required dependencies. Install them first:`);
16455
+ for (const r of depCheck.results.filter((r2) => !r2.available)) {
16456
+ console.error(` rudi install ${r.type}:${r.name}`);
16438
16457
  }
16458
+ console.error(`
16459
+ Or use --force to install anyway.`);
16460
+ process.exit(1);
16439
16461
  }
16440
16462
  console.log(`
16441
16463
  Installing...`);
@@ -16461,29 +16483,70 @@ Installing...`);
16461
16483
  if (resolved.kind === "stack") {
16462
16484
  const manifest = await loadManifest(result.path);
16463
16485
  if (manifest) {
16464
- const stackId = resolved.id.replace(/^stack:/, "");
16465
- const envPath = await createEnvFile(result.path, manifest);
16466
- await registerMcpAll(stackId, result.path, manifest, targetAgents);
16467
- const secrets = getManifestSecrets(manifest);
16468
- if (secrets.length > 0) {
16469
- console.log(`
16470
- \u26A0\uFE0F This stack requires configuration:`);
16471
- console.log(` Edit: ${envPath}`);
16486
+ const depResult = await installDependencies(result.path, manifest);
16487
+ if (depResult.installed) {
16488
+ console.log(` \u2713 Dependencies installed`);
16489
+ } else if (depResult.error) {
16490
+ console.log(` \u26A0 Failed to install dependencies: ${depResult.error}`);
16491
+ }
16492
+ const { found, missing } = await checkSecrets(manifest);
16493
+ const envExampleKeys = await parseEnvExample(result.path);
16494
+ for (const key of envExampleKeys) {
16495
+ if (!found.includes(key) && !missing.includes(key)) {
16496
+ const exists = await hasSecret(key);
16497
+ if (!exists) {
16498
+ missing.push(key);
16499
+ } else {
16500
+ found.push(key);
16501
+ }
16502
+ }
16503
+ }
16504
+ if (missing.length > 0) {
16505
+ for (const key of missing) {
16506
+ const existing = await getSecret(key);
16507
+ if (existing === null) {
16508
+ await setSecret(key, "");
16509
+ }
16510
+ }
16511
+ }
16512
+ console.log(`
16513
+ Next steps:`);
16514
+ if (missing.length > 0) {
16472
16515
  console.log(`
16473
- Required secrets:`);
16474
- for (const secret of secrets) {
16475
- const key = getSecretName(secret);
16476
- const label = getSecretLabel(secret) || key;
16477
- console.log(` - ${label} (${key})`);
16516
+ 1. Configure secrets (${missing.length} pending):`);
16517
+ for (const key of missing) {
16518
+ const secret = getManifestSecrets(manifest).find(
16519
+ (s) => (typeof s === "string" ? s : s.name) === key
16520
+ );
16521
+ const helpUrl = getSecretLink(secret);
16522
+ console.log(` rudi secrets set ${key} "<your-value>"`);
16523
+ if (helpUrl) {
16524
+ console.log(` # Get yours: ${helpUrl}`);
16525
+ }
16478
16526
  }
16479
16527
  console.log(`
16480
- After adding secrets, run: rudi run ${pkgId}`);
16481
- return;
16528
+ Check status: rudi secrets list`);
16529
+ } else if (found.length > 0) {
16530
+ console.log(`
16531
+ 1. Secrets: \u2713 ${found.length} configured`);
16532
+ } else {
16533
+ console.log(`
16534
+ 1. Secrets: \u2713 None required`);
16482
16535
  }
16536
+ const agents = getInstalledAgents();
16537
+ if (agents.length > 0) {
16538
+ console.log(`
16539
+ 2. Wire up your agents:`);
16540
+ console.log(` rudi integrate all`);
16541
+ console.log(` # Detected: ${agents.map((a) => a.name).join(", ")}`);
16542
+ }
16543
+ console.log(`
16544
+ 3. Restart your agent to use the stack`);
16545
+ return;
16483
16546
  }
16484
16547
  }
16485
16548
  console.log(`
16486
- Run with: rudi run ${pkgId}`);
16549
+ \u2713 Installed successfully.`);
16487
16550
  } else {
16488
16551
  console.error(`
16489
16552
  \u2717 Installation failed: ${result.error}`);
@@ -16499,38 +16562,28 @@ Run with: rudi run ${pkgId}`);
16499
16562
  }
16500
16563
 
16501
16564
  // ../packages/runner/src/spawn.js
16502
- var import_child_process2 = require("child_process");
16503
- var import_path7 = __toESM(require("path"), 1);
16504
- var import_fs7 = __toESM(require("fs"), 1);
16565
+ var import_child_process3 = require("child_process");
16566
+ var import_path8 = __toESM(require("path"), 1);
16567
+ var import_fs8 = __toESM(require("fs"), 1);
16505
16568
 
16506
16569
  // ../packages/runner/src/secrets.js
16507
- var import_fs6 = __toESM(require("fs"), 1);
16508
- var import_path6 = __toESM(require("path"), 1);
16509
- var import_os2 = __toESM(require("os"), 1);
16510
- var SECRETS_PATH = import_path6.default.join(import_os2.default.homedir(), ".rudi", "secrets.json");
16511
- function loadSecrets() {
16512
- if (!import_fs6.default.existsSync(SECRETS_PATH)) {
16570
+ var import_fs7 = __toESM(require("fs"), 1);
16571
+ var import_path7 = __toESM(require("path"), 1);
16572
+ var import_os3 = __toESM(require("os"), 1);
16573
+ var SECRETS_PATH = import_path7.default.join(import_os3.default.homedir(), ".rudi", "secrets.json");
16574
+ function loadSecrets2() {
16575
+ if (!import_fs7.default.existsSync(SECRETS_PATH)) {
16513
16576
  return {};
16514
16577
  }
16515
16578
  try {
16516
- const content = import_fs6.default.readFileSync(SECRETS_PATH, "utf-8");
16579
+ const content = import_fs7.default.readFileSync(SECRETS_PATH, "utf-8");
16517
16580
  return JSON.parse(content);
16518
16581
  } catch {
16519
16582
  return {};
16520
16583
  }
16521
16584
  }
16522
- function saveSecrets(secrets) {
16523
- const dir = import_path6.default.dirname(SECRETS_PATH);
16524
- if (!import_fs6.default.existsSync(dir)) {
16525
- import_fs6.default.mkdirSync(dir, { recursive: true });
16526
- }
16527
- import_fs6.default.writeFileSync(SECRETS_PATH, JSON.stringify(secrets, null, 2), {
16528
- mode: 384
16529
- // Read/write only for owner
16530
- });
16531
- }
16532
16585
  async function getSecrets(required) {
16533
- const allSecrets = loadSecrets();
16586
+ const allSecrets = loadSecrets2();
16534
16587
  const result = {};
16535
16588
  for (const req of required) {
16536
16589
  const name = typeof req === "string" ? req : req.name;
@@ -16543,8 +16596,8 @@ async function getSecrets(required) {
16543
16596
  }
16544
16597
  return result;
16545
16598
  }
16546
- function checkSecrets(required) {
16547
- const allSecrets = loadSecrets();
16599
+ function checkSecrets2(required) {
16600
+ const allSecrets = loadSecrets2();
16548
16601
  const missing = [];
16549
16602
  for (const req of required) {
16550
16603
  const name = typeof req === "string" ? req : req.name;
@@ -16558,34 +16611,12 @@ function checkSecrets(required) {
16558
16611
  missing
16559
16612
  };
16560
16613
  }
16561
- function setSecret(name, value) {
16562
- const secrets = loadSecrets();
16563
- secrets[name] = value;
16564
- saveSecrets(secrets);
16565
- }
16566
- function removeSecret(name) {
16567
- const secrets = loadSecrets();
16568
- delete secrets[name];
16569
- saveSecrets(secrets);
16570
- }
16571
16614
  function listSecretNames() {
16572
- const secrets = loadSecrets();
16615
+ const secrets = loadSecrets2();
16573
16616
  return Object.keys(secrets);
16574
16617
  }
16575
- function getMaskedSecrets() {
16576
- const secrets = loadSecrets();
16577
- const masked = {};
16578
- for (const [name, value] of Object.entries(secrets)) {
16579
- if (typeof value === "string" && value.length > 8) {
16580
- masked[name] = value.slice(0, 4) + "..." + value.slice(-4);
16581
- } else {
16582
- masked[name] = "***";
16583
- }
16584
- }
16585
- return masked;
16586
- }
16587
16618
  function redactSecrets(text, secrets) {
16588
- const allSecrets = secrets || loadSecrets();
16619
+ const allSecrets = secrets || loadSecrets2();
16589
16620
  let result = text;
16590
16621
  for (const value of Object.values(allSecrets)) {
16591
16622
  if (typeof value === "string" && value.length > 3) {
@@ -16595,27 +16626,18 @@ function redactSecrets(text, secrets) {
16595
16626
  }
16596
16627
  return result;
16597
16628
  }
16598
- function exportToEnv() {
16599
- const secrets = loadSecrets();
16600
- const lines = [];
16601
- for (const [name, value] of Object.entries(secrets)) {
16602
- const escaped = String(value).replace(/'/g, "'\\''");
16603
- lines.push(`export ${name}='${escaped}'`);
16604
- }
16605
- return lines.join("\n");
16606
- }
16607
16629
 
16608
16630
  // ../packages/runner/src/spawn.js
16609
16631
  async function runStack(id, options = {}) {
16610
16632
  const { inputs = {}, cwd, env = {}, onStdout, onStderr, onExit, signal } = options;
16611
16633
  const startTime = Date.now();
16612
16634
  const packagePath = getPackagePath(id);
16613
- const manifestPath = import_path7.default.join(packagePath, "manifest.json");
16614
- const { default: fs19 } = await import("fs");
16615
- if (!fs19.existsSync(manifestPath)) {
16635
+ const manifestPath = import_path8.default.join(packagePath, "manifest.json");
16636
+ const { default: fs26 } = await import("fs");
16637
+ if (!fs26.existsSync(manifestPath)) {
16616
16638
  throw new Error(`Stack manifest not found: ${id}`);
16617
16639
  }
16618
- const manifest = JSON.parse(fs19.readFileSync(manifestPath, "utf-8"));
16640
+ const manifest = JSON.parse(fs26.readFileSync(manifestPath, "utf-8"));
16619
16641
  const { command, args } = resolveCommandFromManifest(manifest, packagePath);
16620
16642
  const secrets = await getSecrets(manifest.requires?.secrets || []);
16621
16643
  const runEnv = {
@@ -16626,7 +16648,7 @@ async function runStack(id, options = {}) {
16626
16648
  RUDI_PACKAGE_ID: id,
16627
16649
  RUDI_PACKAGE_PATH: packagePath
16628
16650
  };
16629
- const proc = (0, import_child_process2.spawn)(command, args, {
16651
+ const proc = (0, import_child_process3.spawn)(command, args, {
16630
16652
  cwd: cwd || packagePath,
16631
16653
  env: runEnv,
16632
16654
  stdio: ["pipe", "pipe", "pipe"],
@@ -16671,15 +16693,15 @@ async function runStack(id, options = {}) {
16671
16693
  }
16672
16694
  function getCommand(runtime) {
16673
16695
  const runtimeName = runtime.replace("runtime:", "");
16674
- const runtimePath = import_path7.default.join(PATHS.runtimes, runtimeName);
16696
+ const runtimePath = import_path8.default.join(PATHS.runtimes, runtimeName);
16675
16697
  const binaryPaths = [
16676
- import_path7.default.join(runtimePath, "bin", runtimeName === "python" ? "python3" : runtimeName),
16677
- import_path7.default.join(runtimePath, "bin", runtimeName),
16678
- import_path7.default.join(runtimePath, runtimeName === "python" ? "python3" : runtimeName),
16679
- import_path7.default.join(runtimePath, runtimeName)
16698
+ import_path8.default.join(runtimePath, "bin", runtimeName === "python" ? "python3" : runtimeName),
16699
+ import_path8.default.join(runtimePath, "bin", runtimeName),
16700
+ import_path8.default.join(runtimePath, runtimeName === "python" ? "python3" : runtimeName),
16701
+ import_path8.default.join(runtimePath, runtimeName)
16680
16702
  ];
16681
16703
  for (const binPath of binaryPaths) {
16682
- if (import_fs7.default.existsSync(binPath)) {
16704
+ if (import_fs8.default.existsSync(binPath)) {
16683
16705
  return binPath;
16684
16706
  }
16685
16707
  }
@@ -16703,7 +16725,7 @@ function resolveCommandFromManifest(manifest, packagePath) {
16703
16725
  return { command: command2, args };
16704
16726
  }
16705
16727
  const entry = manifest.entry || "index.js";
16706
- const entryPath = import_path7.default.join(packagePath, entry);
16728
+ const entryPath = import_path8.default.join(packagePath, entry);
16707
16729
  const runtime = manifest.runtime || "runtime:node";
16708
16730
  const command = getCommand(runtime);
16709
16731
  return { command, args: [entryPath] };
@@ -16712,21 +16734,21 @@ function resolveRelativePath(value, basePath) {
16712
16734
  if (typeof value !== "string" || value.startsWith("-")) {
16713
16735
  return value;
16714
16736
  }
16715
- if (import_path7.default.isAbsolute(value)) {
16737
+ if (import_path8.default.isAbsolute(value)) {
16716
16738
  return value;
16717
16739
  }
16718
16740
  if (value.includes("/") || value.startsWith(".")) {
16719
- return import_path7.default.join(basePath, value);
16741
+ return import_path8.default.join(basePath, value);
16720
16742
  }
16721
16743
  return value;
16722
16744
  }
16723
16745
 
16724
16746
  // ../packages/manifest/src/stack.js
16725
16747
  var import_yaml2 = __toESM(require_dist(), 1);
16726
- var import_fs8 = __toESM(require("fs"), 1);
16727
- var import_path8 = __toESM(require("path"), 1);
16748
+ var import_fs9 = __toESM(require("fs"), 1);
16749
+ var import_path9 = __toESM(require("path"), 1);
16728
16750
  function parseStackManifest(filePath) {
16729
- const content = import_fs8.default.readFileSync(filePath, "utf-8");
16751
+ const content = import_fs9.default.readFileSync(filePath, "utf-8");
16730
16752
  return parseStackYaml(content, filePath);
16731
16753
  }
16732
16754
  function parseStackYaml(content, source = "stack.yaml") {
@@ -16844,8 +16866,8 @@ function validateStackManifest(manifest, source) {
16844
16866
  function findStackManifest(dir) {
16845
16867
  const candidates = ["stack.yaml", "stack.yml", "manifest.yaml", "manifest.yml"];
16846
16868
  for (const filename of candidates) {
16847
- const filePath = import_path8.default.join(dir, filename);
16848
- if (import_fs8.default.existsSync(filePath)) {
16869
+ const filePath = import_path9.default.join(dir, filename);
16870
+ if (import_fs9.default.existsSync(filePath)) {
16849
16871
  return filePath;
16850
16872
  }
16851
16873
  }
@@ -17000,8 +17022,8 @@ var validatePromptInternal = ajv.compile(promptSchema);
17000
17022
  var validateRuntimeInternal = ajv.compile(runtimeSchema);
17001
17023
 
17002
17024
  // src/commands/run.js
17003
- var import_fs9 = __toESM(require("fs"), 1);
17004
- var import_path9 = __toESM(require("path"), 1);
17025
+ var import_fs10 = __toESM(require("fs"), 1);
17026
+ var import_path10 = __toESM(require("path"), 1);
17005
17027
  async function cmdRun(args, flags) {
17006
17028
  const stackId = args[0];
17007
17029
  if (!stackId) {
@@ -17022,9 +17044,9 @@ async function cmdRun(args, flags) {
17022
17044
  if (manifestPath) {
17023
17045
  manifest = parseStackManifest(manifestPath);
17024
17046
  } else {
17025
- const jsonPath = import_path9.default.join(packagePath, "manifest.json");
17026
- if (import_fs9.default.existsSync(jsonPath)) {
17027
- manifest = JSON.parse(import_fs9.default.readFileSync(jsonPath, "utf-8"));
17047
+ const jsonPath = import_path10.default.join(packagePath, "manifest.json");
17048
+ if (import_fs10.default.existsSync(jsonPath)) {
17049
+ manifest = JSON.parse(import_fs10.default.readFileSync(jsonPath, "utf-8"));
17028
17050
  }
17029
17051
  }
17030
17052
  } catch (error) {
@@ -17038,7 +17060,7 @@ async function cmdRun(args, flags) {
17038
17060
  console.log(`Running: ${manifest.name || stackId}`);
17039
17061
  const requiredSecrets = manifest.requires?.secrets || [];
17040
17062
  if (requiredSecrets.length > 0) {
17041
- const { satisfied, missing } = checkSecrets(requiredSecrets);
17063
+ const { satisfied, missing } = checkSecrets2(requiredSecrets);
17042
17064
  if (!satisfied) {
17043
17065
  console.error(`
17044
17066
  Missing required secrets:`);
@@ -17115,6 +17137,66 @@ async function cmdList(args, flags) {
17115
17137
  process.exit(1);
17116
17138
  }
17117
17139
  }
17140
+ if (flags.detected && kind === "agent") {
17141
+ const installedAgents = getInstalledAgents();
17142
+ const summary = getMcpServerSummary();
17143
+ if (flags.json) {
17144
+ console.log(JSON.stringify({ installedAgents, summary }, null, 2));
17145
+ return;
17146
+ }
17147
+ console.log(`
17148
+ DETECTED AI AGENTS (${installedAgents.length}/${AGENT_CONFIGS2.length}):`);
17149
+ console.log("\u2500".repeat(50));
17150
+ for (const agent of AGENT_CONFIGS2) {
17151
+ const installed = installedAgents.find((a) => a.id === agent.id);
17152
+ const serverCount = summary[agent.id]?.serverCount || 0;
17153
+ if (installed) {
17154
+ console.log(` \u2713 ${agent.name}`);
17155
+ console.log(` ${serverCount} MCP server(s)`);
17156
+ console.log(` ${installed.configFile}`);
17157
+ } else {
17158
+ console.log(` \u25CB ${agent.name} (not installed)`);
17159
+ }
17160
+ }
17161
+ console.log(`
17162
+ Installed: ${installedAgents.length} of ${AGENT_CONFIGS2.length} agents`);
17163
+ return;
17164
+ }
17165
+ if (flags.detected && kind === "stack") {
17166
+ const servers = detectAllMcpServers();
17167
+ if (flags.json) {
17168
+ console.log(JSON.stringify(servers, null, 2));
17169
+ return;
17170
+ }
17171
+ if (servers.length === 0) {
17172
+ console.log("No MCP servers detected in agent configs.");
17173
+ console.log("\nChecked these agents:");
17174
+ for (const agent of AGENT_CONFIGS2) {
17175
+ console.log(` - ${agent.name}`);
17176
+ }
17177
+ return;
17178
+ }
17179
+ const byAgent = {};
17180
+ for (const server of servers) {
17181
+ if (!byAgent[server.agent]) byAgent[server.agent] = [];
17182
+ byAgent[server.agent].push(server);
17183
+ }
17184
+ console.log(`
17185
+ DETECTED MCP SERVERS (${servers.length}):`);
17186
+ console.log("\u2500".repeat(50));
17187
+ for (const [agentId, agentServers] of Object.entries(byAgent)) {
17188
+ const agentName = agentServers[0]?.agentName || agentId;
17189
+ console.log(`
17190
+ ${agentName.toUpperCase()} (${agentServers.length}):`);
17191
+ for (const server of agentServers) {
17192
+ console.log(` \u{1F4E6} ${server.name}`);
17193
+ console.log(` ${server.command} ${server.cwd ? `(${server.cwd})` : ""}`);
17194
+ }
17195
+ }
17196
+ console.log(`
17197
+ Total: ${servers.length} MCP server(s) configured`);
17198
+ return;
17199
+ }
17118
17200
  try {
17119
17201
  let packages = await listInstalled(kind);
17120
17202
  const categoryFilter = flags.category;
@@ -17167,45 +17249,244 @@ Total: ${packages.length} prompt(s)`);
17167
17249
  Filter by category: rudi list prompts --category=coding`);
17168
17250
  return;
17169
17251
  }
17170
- const grouped = {
17171
- stack: packages.filter((p) => p.kind === "stack"),
17172
- prompt: packages.filter((p) => p.kind === "prompt"),
17173
- runtime: packages.filter((p) => p.kind === "runtime"),
17174
- binary: packages.filter((p) => p.kind === "binary"),
17175
- agent: packages.filter((p) => p.kind === "agent")
17176
- };
17177
- let total = 0;
17178
- for (const [pkgKind, pkgs] of Object.entries(grouped)) {
17179
- if (pkgs.length === 0) continue;
17180
- if (kind && kind !== pkgKind) continue;
17181
- console.log(`
17182
- ${headingForKind2(pkgKind)} (${pkgs.length}):`);
17183
- console.log("\u2500".repeat(50));
17184
- for (const pkg of pkgs) {
17185
- const icon = pkg.icon ? `${pkg.icon} ` : "";
17186
- console.log(` ${icon}${pkg.id || `${pkgKind}:${pkg.name}`}`);
17187
- console.log(` Version: ${pkg.version || "unknown"}`);
17188
- if (pkg.description) {
17189
- console.log(` ${pkg.description}`);
17190
- }
17191
- if (pkg.category) {
17192
- console.log(` Category: ${pkg.category}`);
17193
- }
17194
- if (pkg.tags && pkg.tags.length > 0) {
17195
- console.log(` Tags: ${pkg.tags.join(", ")}`);
17196
- }
17197
- if (pkg.installedAt) {
17198
- console.log(` Installed: ${new Date(pkg.installedAt).toLocaleDateString()}`);
17199
- }
17200
- total++;
17252
+ const grouped = {
17253
+ stack: packages.filter((p) => p.kind === "stack"),
17254
+ prompt: packages.filter((p) => p.kind === "prompt"),
17255
+ runtime: packages.filter((p) => p.kind === "runtime"),
17256
+ binary: packages.filter((p) => p.kind === "binary"),
17257
+ agent: packages.filter((p) => p.kind === "agent")
17258
+ };
17259
+ let total = 0;
17260
+ for (const [pkgKind, pkgs] of Object.entries(grouped)) {
17261
+ if (pkgs.length === 0) continue;
17262
+ if (kind && kind !== pkgKind) continue;
17263
+ console.log(`
17264
+ ${headingForKind2(pkgKind)} (${pkgs.length}):`);
17265
+ console.log("\u2500".repeat(50));
17266
+ for (const pkg of pkgs) {
17267
+ const icon = pkg.icon ? `${pkg.icon} ` : "";
17268
+ console.log(` ${icon}${pkg.id || `${pkgKind}:${pkg.name}`}`);
17269
+ console.log(` Version: ${pkg.version || "unknown"}`);
17270
+ if (pkg.description) {
17271
+ console.log(` ${pkg.description}`);
17272
+ }
17273
+ if (pkg.category) {
17274
+ console.log(` Category: ${pkg.category}`);
17275
+ }
17276
+ if (pkg.tags && pkg.tags.length > 0) {
17277
+ console.log(` Tags: ${pkg.tags.join(", ")}`);
17278
+ }
17279
+ if (pkg.installedAt) {
17280
+ console.log(` Installed: ${new Date(pkg.installedAt).toLocaleDateString()}`);
17281
+ }
17282
+ total++;
17283
+ }
17284
+ }
17285
+ console.log(`
17286
+ Total: ${total} package(s)`);
17287
+ } catch (error) {
17288
+ console.error(`Failed to list packages: ${error.message}`);
17289
+ process.exit(1);
17290
+ }
17291
+ }
17292
+
17293
+ // src/utils/mcp-registry.js
17294
+ var fs13 = __toESM(require("fs/promises"), 1);
17295
+ var path13 = __toESM(require("path"), 1);
17296
+ var os4 = __toESM(require("os"), 1);
17297
+ var HOME = os4.homedir();
17298
+ async function readJson(filePath) {
17299
+ try {
17300
+ const content = await fs13.readFile(filePath, "utf-8");
17301
+ return JSON.parse(content);
17302
+ } catch {
17303
+ return {};
17304
+ }
17305
+ }
17306
+ async function writeJson(filePath, data) {
17307
+ const dir = path13.dirname(filePath);
17308
+ await fs13.mkdir(dir, { recursive: true });
17309
+ await fs13.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
17310
+ }
17311
+ function parseTomlValue(value) {
17312
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
17313
+ return value.slice(1, -1);
17314
+ }
17315
+ if (value.startsWith("[") && value.endsWith("]")) {
17316
+ const inner = value.slice(1, -1).trim();
17317
+ if (!inner) return [];
17318
+ const items = [];
17319
+ let current = "";
17320
+ let inQuote = false;
17321
+ let quoteChar = "";
17322
+ for (const char of inner) {
17323
+ if ((char === '"' || char === "'") && !inQuote) {
17324
+ inQuote = true;
17325
+ quoteChar = char;
17326
+ } else if (char === quoteChar && inQuote) {
17327
+ inQuote = false;
17328
+ items.push(current);
17329
+ current = "";
17330
+ } else if (char === "," && !inQuote) {
17331
+ } else if (inQuote) {
17332
+ current += char;
17333
+ }
17334
+ }
17335
+ return items;
17336
+ }
17337
+ if (value === "true") return true;
17338
+ if (value === "false") return false;
17339
+ const num = Number(value);
17340
+ if (!isNaN(num)) return num;
17341
+ return value;
17342
+ }
17343
+ function parseToml(content) {
17344
+ const result = {};
17345
+ const lines = content.split("\n");
17346
+ let currentTable = [];
17347
+ for (const line of lines) {
17348
+ const trimmed = line.trim();
17349
+ if (!trimmed || trimmed.startsWith("#")) continue;
17350
+ const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
17351
+ if (tableMatch) {
17352
+ currentTable = tableMatch[1].split(".");
17353
+ let obj = result;
17354
+ for (const key of currentTable) {
17355
+ obj[key] = obj[key] || {};
17356
+ obj = obj[key];
17357
+ }
17358
+ continue;
17359
+ }
17360
+ const kvMatch = trimmed.match(/^([^=]+)=(.*)$/);
17361
+ if (kvMatch) {
17362
+ const key = kvMatch[1].trim();
17363
+ let value = kvMatch[2].trim();
17364
+ const parsed = parseTomlValue(value);
17365
+ let obj = result;
17366
+ for (const tableKey of currentTable) {
17367
+ obj = obj[tableKey];
17368
+ }
17369
+ obj[key] = parsed;
17370
+ }
17371
+ }
17372
+ return result;
17373
+ }
17374
+ function tomlValue(value) {
17375
+ if (typeof value === "string") {
17376
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
17377
+ }
17378
+ if (typeof value === "boolean") {
17379
+ return value ? "true" : "false";
17380
+ }
17381
+ if (typeof value === "number") {
17382
+ return String(value);
17383
+ }
17384
+ if (Array.isArray(value)) {
17385
+ const items = value.map((v) => tomlValue(v));
17386
+ return `[${items.join(", ")}]`;
17387
+ }
17388
+ return String(value);
17389
+ }
17390
+ function stringifyToml(config, prefix = "") {
17391
+ const lines = [];
17392
+ for (const [key, value] of Object.entries(config)) {
17393
+ if (typeof value !== "object" || Array.isArray(value)) {
17394
+ lines.push(`${key} = ${tomlValue(value)}`);
17395
+ }
17396
+ }
17397
+ for (const [key, value] of Object.entries(config)) {
17398
+ if (typeof value === "object" && !Array.isArray(value)) {
17399
+ const tablePath = prefix ? `${prefix}.${key}` : key;
17400
+ const hasSimpleValues = Object.values(value).some(
17401
+ (v) => typeof v !== "object" || Array.isArray(v)
17402
+ );
17403
+ if (hasSimpleValues) {
17404
+ lines.push("");
17405
+ lines.push(`[${tablePath}]`);
17406
+ }
17407
+ const nested = stringifyToml(value, tablePath);
17408
+ if (nested.trim()) {
17409
+ lines.push(nested);
17201
17410
  }
17202
17411
  }
17203
- console.log(`
17204
- Total: ${total} package(s)`);
17412
+ }
17413
+ return lines.join("\n");
17414
+ }
17415
+ async function readToml(filePath) {
17416
+ try {
17417
+ const content = await fs13.readFile(filePath, "utf-8");
17418
+ return parseToml(content);
17419
+ } catch {
17420
+ return {};
17421
+ }
17422
+ }
17423
+ async function writeToml(filePath, data) {
17424
+ const dir = path13.dirname(filePath);
17425
+ await fs13.mkdir(dir, { recursive: true });
17426
+ await fs13.writeFile(filePath, stringifyToml(data), "utf-8");
17427
+ }
17428
+ async function unregisterMcpCodex(stackId) {
17429
+ const configPath = AGENT_CONFIGS.codex;
17430
+ try {
17431
+ const config = await readToml(configPath);
17432
+ if (!config.mcp_servers || !config.mcp_servers[stackId]) {
17433
+ return { success: true, skipped: true };
17434
+ }
17435
+ delete config.mcp_servers[stackId];
17436
+ await writeToml(configPath, config);
17437
+ console.log(` Unregistered MCP from Codex: ${stackId}`);
17438
+ return { success: true };
17205
17439
  } catch (error) {
17206
- console.error(`Failed to list packages: ${error.message}`);
17207
- process.exit(1);
17440
+ console.error(` Failed to unregister MCP from Codex: ${error.message}`);
17441
+ return { success: false, error: error.message };
17442
+ }
17443
+ }
17444
+ function getInstalledAgentIds() {
17445
+ return getInstalledAgents().map((a) => a.id);
17446
+ }
17447
+ async function unregisterMcpGeneric(agentId, stackId) {
17448
+ const agentConfig = AGENT_CONFIGS2.find((a) => a.id === agentId);
17449
+ if (!agentConfig) {
17450
+ return { success: false, error: `Unknown agent: ${agentId}` };
17451
+ }
17452
+ const configPath = findAgentConfig(agentConfig);
17453
+ if (!configPath) {
17454
+ return { success: true, skipped: true, reason: "Agent not installed" };
17455
+ }
17456
+ if (agentId === "codex") {
17457
+ return unregisterMcpCodex(stackId);
17458
+ }
17459
+ try {
17460
+ const settings = await readJson(configPath);
17461
+ const key = agentConfig.key;
17462
+ if (!settings[key] || !settings[key][stackId]) {
17463
+ return { success: true, skipped: true, reason: "Server not found" };
17464
+ }
17465
+ delete settings[key][stackId];
17466
+ await writeJson(configPath, settings);
17467
+ console.log(` Unregistered MCP from ${agentConfig.name}: ${stackId}`);
17468
+ return { success: true, configPath };
17469
+ } catch (error) {
17470
+ console.error(` Failed to unregister MCP from ${agentConfig.name}: ${error.message}`);
17471
+ return { success: false, error: error.message };
17472
+ }
17473
+ }
17474
+ async function unregisterMcpAll(stackId, targetAgents = null) {
17475
+ let agentIds = getInstalledAgentIds();
17476
+ if (targetAgents && targetAgents.length > 0) {
17477
+ const idMap = {
17478
+ "claude": "claude-code",
17479
+ "codex": "codex",
17480
+ "gemini": "gemini"
17481
+ };
17482
+ const targetIds = targetAgents.map((a) => idMap[a] || a);
17483
+ agentIds = agentIds.filter((id) => targetIds.includes(id));
17484
+ }
17485
+ const results = {};
17486
+ for (const agentId of agentIds) {
17487
+ results[agentId] = await unregisterMcpGeneric(agentId, stackId);
17208
17488
  }
17489
+ return results;
17209
17490
  }
17210
17491
 
17211
17492
  // src/commands/remove.js
@@ -17344,89 +17625,136 @@ async function cmdSecrets(args, flags) {
17344
17625
  case "set":
17345
17626
  await secretsSet(args.slice(1), flags);
17346
17627
  break;
17628
+ case "get":
17629
+ await secretsGet(args.slice(1), flags);
17630
+ break;
17347
17631
  case "list":
17348
17632
  case "ls":
17349
- secretsList(flags);
17633
+ await secretsList(flags);
17350
17634
  break;
17351
17635
  case "remove":
17352
17636
  case "rm":
17353
17637
  case "delete":
17354
- secretsRemove(args.slice(1), flags);
17638
+ await secretsRemove(args.slice(1), flags);
17355
17639
  break;
17356
- case "export":
17357
- secretsExport(flags);
17640
+ case "info":
17641
+ secretsInfo();
17358
17642
  break;
17359
17643
  default:
17360
17644
  console.log(`
17361
- rudi secrets - Manage secrets
17645
+ rudi secrets - Manage secrets (stored in ${getStorageInfo().backend})
17362
17646
 
17363
17647
  COMMANDS
17364
- set <name> Set a secret (prompts for value)
17648
+ set <name> Set a secret (prompts for value securely)
17649
+ get <name> Get a secret value (for scripts)
17365
17650
  list List configured secrets (values masked)
17366
17651
  remove <name> Remove a secret
17367
- export Export as environment variables
17652
+ info Show storage backend info
17368
17653
 
17369
17654
  EXAMPLES
17370
- rudi secrets set VERCEL_TOKEN
17655
+ rudi secrets set SLACK_BOT_TOKEN
17371
17656
  rudi secrets list
17372
17657
  rudi secrets remove GITHUB_TOKEN
17658
+
17659
+ SECURITY
17660
+ Secrets are stored in macOS Keychain when available.
17661
+ Fallback uses encrypted JSON at ~/.rudi/secrets.json
17373
17662
  `);
17374
17663
  }
17375
17664
  }
17376
17665
  async function secretsSet(args, flags) {
17377
17666
  const name = args[0];
17667
+ const valueArg = args[1];
17378
17668
  if (!name) {
17379
- console.error("Usage: rudi secrets set <name>");
17380
- console.error("Example: rudi secrets set VERCEL_TOKEN");
17669
+ console.error("Usage: rudi secrets set <name> [value]");
17670
+ console.error("");
17671
+ console.error("Examples:");
17672
+ console.error(" rudi secrets set SLACK_BOT_TOKEN # Interactive prompt");
17673
+ console.error(' rudi secrets set SLACK_BOT_TOKEN "xoxb-..." # Direct value');
17381
17674
  process.exit(1);
17382
17675
  }
17383
17676
  if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
17384
17677
  console.error("Secret name should be UPPER_SNAKE_CASE");
17385
- console.error("Example: VERCEL_TOKEN, GITHUB_API_KEY");
17678
+ console.error("Example: SLACK_BOT_TOKEN, GITHUB_API_KEY");
17386
17679
  process.exit(1);
17387
17680
  }
17388
- const existing = loadSecrets();
17389
- if (existing[name] && !flags.force) {
17681
+ const exists = await hasSecret(name);
17682
+ if (exists && !flags.force) {
17390
17683
  console.log(`Secret ${name} already exists.`);
17391
17684
  console.log("Use --force to overwrite.");
17392
17685
  process.exit(0);
17393
17686
  }
17394
- const value = await promptSecret(`Enter value for ${name}: `);
17687
+ let value = valueArg;
17688
+ if (!value) {
17689
+ if (process.stdin.isTTY) {
17690
+ value = await promptSecret(`Enter value for ${name}: `);
17691
+ } else {
17692
+ console.error("No value provided.");
17693
+ console.error("Usage: rudi secrets set <name> <value>");
17694
+ process.exit(1);
17695
+ }
17696
+ }
17395
17697
  if (!value) {
17396
17698
  console.error("No value provided");
17397
17699
  process.exit(1);
17398
17700
  }
17399
- setSecret(name, value);
17400
- console.log(`\u2713 Secret ${name} saved`);
17701
+ await setSecret(name, value);
17702
+ const info = getStorageInfo();
17703
+ console.log(`\u2713 Secret ${name} saved (${info.backend})`);
17704
+ }
17705
+ async function secretsGet(args, flags) {
17706
+ const name = args[0];
17707
+ if (!name) {
17708
+ console.error("Usage: rudi secrets get <name>");
17709
+ process.exit(1);
17710
+ }
17711
+ const value = await getSecret(name);
17712
+ if (value) {
17713
+ process.stdout.write(value);
17714
+ } else {
17715
+ process.exit(1);
17716
+ }
17401
17717
  }
17402
- function secretsList(flags) {
17403
- const names = listSecretNames();
17718
+ async function secretsList(flags) {
17719
+ const names = await listSecrets();
17404
17720
  if (names.length === 0) {
17405
17721
  console.log("No secrets configured.");
17406
17722
  console.log("\nSet with: rudi secrets set <name>");
17407
17723
  return;
17408
17724
  }
17409
17725
  if (flags.json) {
17410
- console.log(JSON.stringify(getMaskedSecrets(), null, 2));
17726
+ const masked2 = await getMaskedSecrets();
17727
+ console.log(JSON.stringify(masked2, null, 2));
17411
17728
  return;
17412
17729
  }
17413
- const masked = getMaskedSecrets();
17414
- console.log("\nConfigured secrets:");
17730
+ const masked = await getMaskedSecrets();
17731
+ const info = getStorageInfo();
17732
+ const pending = Object.values(masked).filter((v) => v === "(pending)").length;
17733
+ const configured = names.length - pending;
17734
+ console.log(`
17735
+ Secrets (${info.backend}):`);
17415
17736
  console.log("\u2500".repeat(50));
17416
- for (const name of names.sort()) {
17417
- console.log(` ${name.padEnd(25)} ${masked[name]}`);
17737
+ for (const name of names) {
17738
+ const status = masked[name] === "(pending)" ? "\u25CB" : "\u2713";
17739
+ console.log(` ${status} ${name.padEnd(28)} ${masked[name]}`);
17740
+ }
17741
+ console.log("\u2500".repeat(50));
17742
+ if (pending > 0) {
17743
+ console.log(` ${configured} configured, ${pending} pending`);
17744
+ console.log(`
17745
+ Set pending: rudi secrets set <name> "<value>"`);
17746
+ } else {
17747
+ console.log(` ${configured} configured`);
17418
17748
  }
17419
- console.log(`
17420
- Total: ${names.length} secret(s)`);
17421
17749
  }
17422
- function secretsRemove(args, flags) {
17750
+ async function secretsRemove(args, flags) {
17423
17751
  const name = args[0];
17424
17752
  if (!name) {
17425
17753
  console.error("Usage: rudi secrets remove <name>");
17426
17754
  process.exit(1);
17427
17755
  }
17428
- const existing = loadSecrets();
17429
- if (!existing[name]) {
17756
+ const allNames = await listSecrets();
17757
+ if (!allNames.includes(name)) {
17430
17758
  console.error(`Secret not found: ${name}`);
17431
17759
  process.exit(1);
17432
17760
  }
@@ -17435,17 +17763,19 @@ function secretsRemove(args, flags) {
17435
17763
  console.log("Run with --force to confirm.");
17436
17764
  process.exit(0);
17437
17765
  }
17438
- removeSecret(name);
17766
+ await removeSecret(name);
17439
17767
  console.log(`\u2713 Secret ${name} removed`);
17440
17768
  }
17441
- function secretsExport(flags) {
17442
- const exports2 = exportToEnv();
17443
- if (!exports2) {
17444
- console.log("No secrets to export.");
17445
- return;
17446
- }
17447
- console.log("# Add to your shell profile:");
17448
- console.log(exports2);
17769
+ function secretsInfo() {
17770
+ const info = getStorageInfo();
17771
+ console.log("\nSecrets Storage:");
17772
+ console.log("\u2500".repeat(50));
17773
+ console.log(` Backend: ${info.backend}`);
17774
+ console.log(` File: ${info.file}`);
17775
+ console.log(` Permissions: ${info.permissions}`);
17776
+ console.log("");
17777
+ console.log(" Security: File permissions (0600) protect secrets.");
17778
+ console.log(" Same approach as AWS CLI, SSH, GitHub CLI.");
17449
17779
  }
17450
17780
  function promptSecret(prompt) {
17451
17781
  return new Promise((resolve) => {
@@ -17480,13 +17810,13 @@ function promptSecret(prompt) {
17480
17810
  }
17481
17811
 
17482
17812
  // src/commands/db.js
17483
- var import_fs11 = require("fs");
17484
- var import_path11 = require("path");
17813
+ var import_fs12 = require("fs");
17814
+ var import_path12 = require("path");
17485
17815
 
17486
17816
  // ../packages/db/src/index.js
17487
17817
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
17488
- var import_path10 = __toESM(require("path"), 1);
17489
- var import_fs10 = __toESM(require("fs"), 1);
17818
+ var import_path11 = __toESM(require("path"), 1);
17819
+ var import_fs11 = __toESM(require("fs"), 1);
17490
17820
  init_src();
17491
17821
 
17492
17822
  // ../packages/db/src/schema.js
@@ -18223,9 +18553,9 @@ var DB_PATH = PATHS.dbFile;
18223
18553
  var db = null;
18224
18554
  function getDb(options = {}) {
18225
18555
  if (!db) {
18226
- const dbDir = import_path10.default.dirname(DB_PATH);
18227
- if (!import_fs10.default.existsSync(dbDir)) {
18228
- import_fs10.default.mkdirSync(dbDir, { recursive: true });
18556
+ const dbDir = import_path11.default.dirname(DB_PATH);
18557
+ if (!import_fs11.default.existsSync(dbDir)) {
18558
+ import_fs11.default.mkdirSync(dbDir, { recursive: true });
18229
18559
  }
18230
18560
  db = new import_better_sqlite3.default(DB_PATH, {
18231
18561
  readonly: options.readonly || false
@@ -18238,7 +18568,7 @@ function getDb(options = {}) {
18238
18568
  return db;
18239
18569
  }
18240
18570
  function isDatabaseInitialized() {
18241
- if (!import_fs10.default.existsSync(DB_PATH)) {
18571
+ if (!import_fs11.default.existsSync(DB_PATH)) {
18242
18572
  return false;
18243
18573
  }
18244
18574
  try {
@@ -18258,7 +18588,7 @@ function getDbPath() {
18258
18588
  }
18259
18589
  function getDbSize() {
18260
18590
  try {
18261
- const stats = import_fs10.default.statSync(DB_PATH);
18591
+ const stats = import_fs11.default.statSync(DB_PATH);
18262
18592
  return stats.size;
18263
18593
  } catch {
18264
18594
  return null;
@@ -18519,12 +18849,12 @@ function dbBackup(args, flags) {
18519
18849
  let backupPath = args[0];
18520
18850
  if (!backupPath) {
18521
18851
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18522
- backupPath = (0, import_path11.join)((0, import_path11.dirname)(dbPath), `rudi-backup-${timestamp}.db`);
18852
+ backupPath = (0, import_path12.join)((0, import_path12.dirname)(dbPath), `rudi-backup-${timestamp}.db`);
18523
18853
  }
18524
18854
  if (backupPath.startsWith("~")) {
18525
- backupPath = (0, import_path11.join)(process.env.HOME || "", backupPath.slice(1));
18855
+ backupPath = (0, import_path12.join)(process.env.HOME || "", backupPath.slice(1));
18526
18856
  }
18527
- if ((0, import_fs11.existsSync)(backupPath) && !flags.force) {
18857
+ if ((0, import_fs12.existsSync)(backupPath) && !flags.force) {
18528
18858
  console.error(`Backup file already exists: ${backupPath}`);
18529
18859
  console.error("Use --force to overwrite.");
18530
18860
  process.exit(1);
@@ -18536,7 +18866,7 @@ function dbBackup(args, flags) {
18536
18866
  const db3 = getDb();
18537
18867
  db3.exec("VACUUM INTO ?", [backupPath]);
18538
18868
  } catch (e) {
18539
- (0, import_fs11.copyFileSync)(dbPath, backupPath);
18869
+ (0, import_fs12.copyFileSync)(dbPath, backupPath);
18540
18870
  }
18541
18871
  const size = getDbSize();
18542
18872
  console.log(` Size: ${formatBytes(size)}`);
@@ -18641,24 +18971,24 @@ function dbTables(flags) {
18641
18971
  }
18642
18972
 
18643
18973
  // src/commands/import.js
18644
- var import_fs12 = require("fs");
18645
- var import_path12 = require("path");
18646
- var import_os3 = require("os");
18974
+ var import_fs13 = require("fs");
18975
+ var import_path13 = require("path");
18976
+ var import_os4 = require("os");
18647
18977
  var import_crypto2 = require("crypto");
18648
18978
  var PROVIDERS = {
18649
18979
  claude: {
18650
18980
  name: "Claude Code",
18651
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".claude", "projects"),
18981
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".claude", "projects"),
18652
18982
  pattern: /\.jsonl$/
18653
18983
  },
18654
18984
  codex: {
18655
18985
  name: "Codex",
18656
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".codex", "sessions"),
18986
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".codex", "sessions"),
18657
18987
  pattern: /\.jsonl$/
18658
18988
  },
18659
18989
  gemini: {
18660
18990
  name: "Gemini",
18661
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".gemini", "sessions"),
18991
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".gemini", "sessions"),
18662
18992
  pattern: /\.jsonl$/
18663
18993
  }
18664
18994
  };
@@ -18725,7 +19055,7 @@ async function importSessions(args, flags) {
18725
19055
  console.log(`
18726
19056
  \u25B6 ${provider.name}`);
18727
19057
  console.log(` Source: ${provider.baseDir}`);
18728
- if (!(0, import_fs12.existsSync)(provider.baseDir)) {
19058
+ if (!(0, import_fs13.existsSync)(provider.baseDir)) {
18729
19059
  console.log(` \u26A0 Directory not found, skipping`);
18730
19060
  continue;
18731
19061
  }
@@ -18768,14 +19098,14 @@ async function importSessions(args, flags) {
18768
19098
  const now = Date.now();
18769
19099
  const maxAgeMs = maxAgeDays ? maxAgeDays * 24 * 60 * 60 * 1e3 : null;
18770
19100
  for (const filepath of files) {
18771
- const sessionId = (0, import_path12.basename)(filepath, ".jsonl");
19101
+ const sessionId = (0, import_path13.basename)(filepath, ".jsonl");
18772
19102
  if (existingIds.has(sessionId)) {
18773
19103
  skipped.existing++;
18774
19104
  continue;
18775
19105
  }
18776
19106
  let stat;
18777
19107
  try {
18778
- stat = (0, import_fs12.statSync)(filepath);
19108
+ stat = (0, import_fs13.statSync)(filepath);
18779
19109
  } catch (e) {
18780
19110
  skipped.error++;
18781
19111
  continue;
@@ -18873,7 +19203,7 @@ function showImportStatus(flags) {
18873
19203
  }
18874
19204
  console.log("\nProvider directories:");
18875
19205
  for (const [key, provider] of Object.entries(PROVIDERS)) {
18876
- const exists = (0, import_fs12.existsSync)(provider.baseDir);
19206
+ const exists = (0, import_fs13.existsSync)(provider.baseDir);
18877
19207
  let count = 0;
18878
19208
  if (exists) {
18879
19209
  const files = findSessionFiles(provider.baseDir, provider.pattern);
@@ -18887,10 +19217,10 @@ function showImportStatus(flags) {
18887
19217
  console.log("To import: rudi import sessions [provider]");
18888
19218
  }
18889
19219
  function findSessionFiles(dir, pattern, files = []) {
18890
- if (!(0, import_fs12.existsSync)(dir)) return files;
19220
+ if (!(0, import_fs13.existsSync)(dir)) return files;
18891
19221
  try {
18892
- for (const entry of (0, import_fs12.readdirSync)(dir, { withFileTypes: true })) {
18893
- const fullPath = (0, import_path12.join)(dir, entry.name);
19222
+ for (const entry of (0, import_fs13.readdirSync)(dir, { withFileTypes: true })) {
19223
+ const fullPath = (0, import_path13.join)(dir, entry.name);
18894
19224
  if (entry.isDirectory()) {
18895
19225
  findSessionFiles(fullPath, pattern, files);
18896
19226
  } else if (pattern.test(entry.name)) {
@@ -18903,11 +19233,11 @@ function findSessionFiles(dir, pattern, files = []) {
18903
19233
  }
18904
19234
  function parseSessionFile(filepath, provider) {
18905
19235
  try {
18906
- const stat = (0, import_fs12.statSync)(filepath);
18907
- const content = (0, import_fs12.readFileSync)(filepath, "utf-8");
19236
+ const stat = (0, import_fs13.statSync)(filepath);
19237
+ const content = (0, import_fs13.readFileSync)(filepath, "utf-8");
18908
19238
  const lines = content.split("\n").filter((l) => l.trim());
18909
19239
  if (lines.length === 0) return null;
18910
- const sessionId = (0, import_path12.basename)(filepath, ".jsonl");
19240
+ const sessionId = (0, import_path13.basename)(filepath, ".jsonl");
18911
19241
  const isAgent = sessionId.startsWith("agent-");
18912
19242
  let title = null;
18913
19243
  let cwd = null;
@@ -18939,11 +19269,11 @@ function parseSessionFile(filepath, provider) {
18939
19269
  title = isAgent ? "Agent Session" : "Imported Session";
18940
19270
  }
18941
19271
  if (!cwd) {
18942
- const parentDir = (0, import_path12.basename)((0, import_path12.dirname)(filepath));
19272
+ const parentDir = (0, import_path13.basename)((0, import_path13.dirname)(filepath));
18943
19273
  if (parentDir.startsWith("-")) {
18944
19274
  cwd = parentDir.replace(/-/g, "/").replace(/^\//, "/");
18945
19275
  } else {
18946
- cwd = (0, import_os3.homedir)();
19276
+ cwd = (0, import_os4.homedir)();
18947
19277
  }
18948
19278
  }
18949
19279
  return {
@@ -18963,7 +19293,7 @@ function parseSessionFile(filepath, provider) {
18963
19293
  }
18964
19294
 
18965
19295
  // src/commands/doctor.js
18966
- var import_fs13 = __toESM(require("fs"), 1);
19296
+ var import_fs14 = __toESM(require("fs"), 1);
18967
19297
  async function cmdDoctor(args, flags) {
18968
19298
  console.log("RUDI Health Check");
18969
19299
  console.log("\u2550".repeat(50));
@@ -18981,12 +19311,12 @@ async function cmdDoctor(args, flags) {
18981
19311
  { path: PATHS.cache, name: "Cache" }
18982
19312
  ];
18983
19313
  for (const dir of dirs) {
18984
- const exists = import_fs13.default.existsSync(dir.path);
19314
+ const exists = import_fs14.default.existsSync(dir.path);
18985
19315
  const status = exists ? "\u2713" : "\u2717";
18986
19316
  console.log(` ${status} ${dir.name}: ${dir.path}`);
18987
19317
  if (!exists) {
18988
19318
  issues.push(`Missing directory: ${dir.name}`);
18989
- fixes.push(() => import_fs13.default.mkdirSync(dir.path, { recursive: true }));
19319
+ fixes.push(() => import_fs14.default.mkdirSync(dir.path, { recursive: true }));
18990
19320
  }
18991
19321
  }
18992
19322
  console.log("\n\u{1F4BE} Database");
@@ -19060,35 +19390,416 @@ async function cmdDoctor(args, flags) {
19060
19390
  if (!nodeOk) {
19061
19391
  issues.push("Node.js version too old (requires >=18)");
19062
19392
  }
19063
- console.log("\n" + "\u2500".repeat(50));
19064
- if (issues.length === 0) {
19065
- console.log("\u2713 All checks passed!");
19393
+ console.log("\n" + "\u2500".repeat(50));
19394
+ if (issues.length === 0) {
19395
+ console.log("\u2713 All checks passed!");
19396
+ } else {
19397
+ console.log(`Found ${issues.length} issue(s):
19398
+ `);
19399
+ for (const issue of issues) {
19400
+ console.log(` \u2022 ${issue}`);
19401
+ }
19402
+ if (flags.fix && fixes.length > 0) {
19403
+ console.log("\nAttempting fixes...");
19404
+ for (const fix of fixes) {
19405
+ try {
19406
+ fix();
19407
+ } catch (error) {
19408
+ console.error(` Fix failed: ${error.message}`);
19409
+ }
19410
+ }
19411
+ console.log("Done. Run doctor again to verify.");
19412
+ } else if (fixes.length > 0) {
19413
+ console.log("\nRun with --fix to attempt automatic fixes.");
19414
+ }
19415
+ }
19416
+ }
19417
+
19418
+ // src/commands/home.js
19419
+ var import_fs15 = __toESM(require("fs"), 1);
19420
+ var import_path14 = __toESM(require("path"), 1);
19421
+ function formatBytes2(bytes) {
19422
+ if (bytes === 0) return "0 B";
19423
+ const k = 1024;
19424
+ const sizes = ["B", "KB", "MB", "GB"];
19425
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
19426
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
19427
+ }
19428
+ function getDirSize(dir) {
19429
+ if (!import_fs15.default.existsSync(dir)) return 0;
19430
+ let size = 0;
19431
+ try {
19432
+ const entries = import_fs15.default.readdirSync(dir, { withFileTypes: true });
19433
+ for (const entry of entries) {
19434
+ const fullPath = import_path14.default.join(dir, entry.name);
19435
+ if (entry.isDirectory()) {
19436
+ size += getDirSize(fullPath);
19437
+ } else {
19438
+ size += import_fs15.default.statSync(fullPath).size;
19439
+ }
19440
+ }
19441
+ } catch {
19442
+ }
19443
+ return size;
19444
+ }
19445
+ function countItems(dir) {
19446
+ if (!import_fs15.default.existsSync(dir)) return 0;
19447
+ try {
19448
+ return import_fs15.default.readdirSync(dir).filter((f) => !f.startsWith(".")).length;
19449
+ } catch {
19450
+ return 0;
19451
+ }
19452
+ }
19453
+ async function cmdHome(args, flags) {
19454
+ console.log("\u2550".repeat(60));
19455
+ console.log("RUDI Home: " + PATHS.home);
19456
+ console.log("\u2550".repeat(60));
19457
+ if (flags.json) {
19458
+ const data = {
19459
+ home: PATHS.home,
19460
+ directories: {},
19461
+ packages: {},
19462
+ database: {}
19463
+ };
19464
+ const dirs2 = [
19465
+ { key: "stacks", path: PATHS.stacks },
19466
+ { key: "prompts", path: PATHS.prompts },
19467
+ { key: "runtimes", path: PATHS.runtimes },
19468
+ { key: "binaries", path: PATHS.binaries },
19469
+ { key: "agents", path: PATHS.agents },
19470
+ { key: "cache", path: PATHS.cache }
19471
+ ];
19472
+ for (const dir of dirs2) {
19473
+ data.directories[dir.key] = {
19474
+ path: dir.path,
19475
+ exists: import_fs15.default.existsSync(dir.path),
19476
+ items: countItems(dir.path),
19477
+ size: getDirSize(dir.path)
19478
+ };
19479
+ }
19480
+ for (const kind of ["stack", "prompt", "runtime", "binary", "agent"]) {
19481
+ data.packages[kind] = getInstalledPackages(kind).length;
19482
+ }
19483
+ data.database = {
19484
+ initialized: isDatabaseInitialized(),
19485
+ size: getDbSize() || 0
19486
+ };
19487
+ console.log(JSON.stringify(data, null, 2));
19488
+ return;
19489
+ }
19490
+ console.log("\n\u{1F4C1} Directory Structure\n");
19491
+ const dirs = [
19492
+ { name: "stacks", path: PATHS.stacks, icon: "\u{1F4E6}", desc: "MCP server stacks" },
19493
+ { name: "prompts", path: PATHS.prompts, icon: "\u{1F4DD}", desc: "Prompt templates" },
19494
+ { name: "runtimes", path: PATHS.runtimes, icon: "\u2699\uFE0F", desc: "Node, Python, Deno, Bun" },
19495
+ { name: "binaries", path: PATHS.binaries, icon: "\u{1F527}", desc: "ffmpeg, ripgrep, etc." },
19496
+ { name: "agents", path: PATHS.agents, icon: "\u{1F916}", desc: "Claude, Codex, Gemini CLIs" },
19497
+ { name: "cache", path: PATHS.cache, icon: "\u{1F4BE}", desc: "Registry cache" }
19498
+ ];
19499
+ for (const dir of dirs) {
19500
+ const exists = import_fs15.default.existsSync(dir.path);
19501
+ const count = countItems(dir.path);
19502
+ const size = getDirSize(dir.path);
19503
+ console.log(`${dir.icon} ${dir.name}/`);
19504
+ console.log(` ${dir.desc}`);
19505
+ if (exists) {
19506
+ console.log(` ${count} items, ${formatBytes2(size)}`);
19507
+ } else {
19508
+ console.log(` (not created)`);
19509
+ }
19510
+ console.log();
19511
+ }
19512
+ console.log("\u{1F4BE} Database");
19513
+ const dbPath = import_path14.default.join(PATHS.home, "rudi.db");
19514
+ if (import_fs15.default.existsSync(dbPath)) {
19515
+ const dbSize = getDbSize() || import_fs15.default.statSync(dbPath).size;
19516
+ console.log(` ${formatBytes2(dbSize)}`);
19517
+ console.log(` ${dbPath}`);
19518
+ } else {
19519
+ console.log(` Not initialized`);
19520
+ }
19521
+ console.log();
19522
+ console.log("\u2550".repeat(60));
19523
+ console.log("Installed Packages");
19524
+ console.log("\u2550".repeat(60));
19525
+ const kinds = ["stack", "prompt", "runtime", "binary", "agent"];
19526
+ let total = 0;
19527
+ for (const kind of kinds) {
19528
+ const packages = getInstalledPackages(kind);
19529
+ const label = kind === "binary" ? "Binaries" : `${kind.charAt(0).toUpperCase() + kind.slice(1)}s`;
19530
+ console.log(` ${label.padEnd(12)} ${packages.length}`);
19531
+ if (packages.length > 0 && flags.verbose) {
19532
+ for (const pkg of packages.slice(0, 3)) {
19533
+ console.log(` - ${pkg.name || pkg.id}`);
19534
+ }
19535
+ if (packages.length > 3) {
19536
+ console.log(` ... and ${packages.length - 3} more`);
19537
+ }
19538
+ }
19539
+ total += packages.length;
19540
+ }
19541
+ console.log("\u2500".repeat(30));
19542
+ console.log(` ${"Total".padEnd(12)} ${total}`);
19543
+ console.log("\n\u{1F4CB} Quick Commands");
19544
+ console.log("\u2500".repeat(30));
19545
+ console.log(" rudi list stacks Show installed stacks");
19546
+ console.log(" rudi list runtimes Show installed runtimes");
19547
+ console.log(" rudi list binaries Show installed binaries");
19548
+ console.log(" rudi doctor --all Check system dependencies");
19549
+ console.log(" rudi db stats Database statistics");
19550
+ }
19551
+
19552
+ // src/commands/init.js
19553
+ var import_fs16 = __toESM(require("fs"), 1);
19554
+ var import_path15 = __toESM(require("path"), 1);
19555
+ var import_promises = require("stream/promises");
19556
+ var import_fs17 = require("fs");
19557
+ var import_child_process4 = require("child_process");
19558
+ init_src();
19559
+ init_src2();
19560
+ var RELEASES_BASE = "https://github.com/learn-rudi/registry/releases/download/v1.0.0";
19561
+ var BUNDLED_RUNTIMES = ["node", "python"];
19562
+ var ESSENTIAL_BINARIES = ["sqlite", "ripgrep"];
19563
+ async function cmdInit(args, flags) {
19564
+ const force = flags.force || false;
19565
+ const skipDownloads = flags["skip-downloads"] || false;
19566
+ const quiet = flags.quiet || false;
19567
+ if (!quiet) {
19568
+ console.log("\u2550".repeat(60));
19569
+ console.log("RUDI Initialization");
19570
+ console.log("\u2550".repeat(60));
19571
+ console.log(`Home: ${PATHS.home}`);
19572
+ console.log();
19573
+ }
19574
+ const actions = { created: [], skipped: [], failed: [] };
19575
+ if (!quiet) console.log("1. Checking directory structure...");
19576
+ ensureDirectories();
19577
+ const dirs = [
19578
+ PATHS.stacks,
19579
+ PATHS.prompts,
19580
+ PATHS.runtimes,
19581
+ PATHS.binaries,
19582
+ PATHS.agents,
19583
+ PATHS.cache,
19584
+ import_path15.default.join(PATHS.home, "shims")
19585
+ ];
19586
+ for (const dir of dirs) {
19587
+ const dirName = import_path15.default.basename(dir);
19588
+ if (!import_fs16.default.existsSync(dir)) {
19589
+ import_fs16.default.mkdirSync(dir, { recursive: true });
19590
+ actions.created.push(`dir:${dirName}`);
19591
+ if (!quiet) console.log(` + ${dirName}/ (created)`);
19592
+ } else {
19593
+ actions.skipped.push(`dir:${dirName}`);
19594
+ if (!quiet) console.log(` \u2713 ${dirName}/ (exists)`);
19595
+ }
19596
+ }
19597
+ if (!skipDownloads) {
19598
+ if (!quiet) console.log("\n2. Checking runtimes...");
19599
+ const index = await fetchIndex();
19600
+ const platform = getPlatformArch();
19601
+ for (const runtimeName of BUNDLED_RUNTIMES) {
19602
+ const runtime = index.packages?.runtimes?.official?.find(
19603
+ (r) => r.id === `runtime:${runtimeName}` || r.id === runtimeName
19604
+ );
19605
+ if (!runtime) {
19606
+ actions.failed.push(`runtime:${runtimeName}`);
19607
+ if (!quiet) console.log(` \u26A0 ${runtimeName}: not found in registry`);
19608
+ continue;
19609
+ }
19610
+ const destPath = import_path15.default.join(PATHS.runtimes, runtimeName);
19611
+ if (import_fs16.default.existsSync(destPath) && !force) {
19612
+ actions.skipped.push(`runtime:${runtimeName}`);
19613
+ if (!quiet) console.log(` \u2713 ${runtimeName}: already installed`);
19614
+ continue;
19615
+ }
19616
+ try {
19617
+ await downloadRuntime2(runtime, runtimeName, destPath, platform);
19618
+ actions.created.push(`runtime:${runtimeName}`);
19619
+ if (!quiet) console.log(` + ${runtimeName}: installed`);
19620
+ } catch (error) {
19621
+ actions.failed.push(`runtime:${runtimeName}`);
19622
+ if (!quiet) console.log(` \u2717 ${runtimeName}: ${error.message}`);
19623
+ }
19624
+ }
19625
+ if (!quiet) console.log("\n3. Checking essential binaries...");
19626
+ for (const binaryName of ESSENTIAL_BINARIES) {
19627
+ const binary = index.packages?.binaries?.official?.find(
19628
+ (b) => b.id === `binary:${binaryName}` || b.id === binaryName || b.name?.toLowerCase() === binaryName
19629
+ );
19630
+ if (!binary) {
19631
+ actions.failed.push(`binary:${binaryName}`);
19632
+ if (!quiet) console.log(` \u26A0 ${binaryName}: not found in registry`);
19633
+ continue;
19634
+ }
19635
+ const destPath = import_path15.default.join(PATHS.binaries, binaryName);
19636
+ if (import_fs16.default.existsSync(destPath) && !force) {
19637
+ actions.skipped.push(`binary:${binaryName}`);
19638
+ if (!quiet) console.log(` \u2713 ${binaryName}: already installed`);
19639
+ continue;
19640
+ }
19641
+ try {
19642
+ await downloadBinary(binary, binaryName, destPath, platform);
19643
+ actions.created.push(`binary:${binaryName}`);
19644
+ if (!quiet) console.log(` + ${binaryName}: installed`);
19645
+ } catch (error) {
19646
+ actions.failed.push(`binary:${binaryName}`);
19647
+ if (!quiet) console.log(` \u2717 ${binaryName}: ${error.message}`);
19648
+ }
19649
+ }
19650
+ } else {
19651
+ if (!quiet) console.log("\n2-3. Skipping downloads (--skip-downloads)");
19652
+ }
19653
+ if (!quiet) console.log("\n4. Updating shims...");
19654
+ const shimsDir = import_path15.default.join(PATHS.home, "shims");
19655
+ const shimCount = await createShims(shimsDir, quiet);
19656
+ if (shimCount > 0) {
19657
+ actions.created.push(`shims:${shimCount}`);
19658
+ }
19659
+ if (!quiet) console.log("\n5. Checking database...");
19660
+ const dbPath = import_path15.default.join(PATHS.home, "rudi.db");
19661
+ const dbExists = import_fs16.default.existsSync(dbPath);
19662
+ try {
19663
+ const result = initSchema();
19664
+ if (dbExists) {
19665
+ actions.skipped.push("database");
19666
+ if (!quiet) console.log(` \u2713 Database exists (v${result.version})`);
19667
+ } else {
19668
+ actions.created.push("database");
19669
+ if (!quiet) console.log(` + Database created (v${result.version})`);
19670
+ }
19671
+ } catch (error) {
19672
+ actions.failed.push("database");
19673
+ if (!quiet) console.log(` \u2717 Database error: ${error.message}`);
19674
+ }
19675
+ if (!quiet) console.log("\n6. Checking settings...");
19676
+ const settingsPath = import_path15.default.join(PATHS.home, "settings.json");
19677
+ if (!import_fs16.default.existsSync(settingsPath)) {
19678
+ const settings = {
19679
+ version: "1.0.0",
19680
+ initialized: (/* @__PURE__ */ new Date()).toISOString(),
19681
+ theme: "system"
19682
+ };
19683
+ import_fs16.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
19684
+ actions.created.push("settings");
19685
+ if (!quiet) console.log(" + settings.json created");
19686
+ } else {
19687
+ actions.skipped.push("settings");
19688
+ if (!quiet) console.log(" \u2713 settings.json exists");
19689
+ }
19690
+ if (!quiet) {
19691
+ console.log("\n" + "\u2550".repeat(60));
19692
+ if (actions.created.length > 0) {
19693
+ console.log(`\u2713 RUDI initialized! (${actions.created.length} items created, ${actions.skipped.length} already existed)`);
19694
+ } else {
19695
+ console.log("\u2713 RUDI is up to date! (all items already existed)");
19696
+ }
19697
+ console.log("\u2550".repeat(60));
19698
+ if (actions.created.includes("settings")) {
19699
+ const shimsPath = import_path15.default.join(PATHS.home, "shims");
19700
+ console.log("\nAdd to your shell profile (~/.zshrc or ~/.bashrc):");
19701
+ console.log(` export PATH="${shimsPath}:$PATH"`);
19702
+ console.log("\nThen run:");
19703
+ console.log(" rudi home # View your setup");
19704
+ console.log(" rudi doctor # Check health");
19705
+ }
19706
+ }
19707
+ return actions;
19708
+ }
19709
+ async function downloadRuntime2(runtime, name, destPath, platform) {
19710
+ let url;
19711
+ if (runtime.upstream?.[platform]) {
19712
+ url = runtime.upstream[platform];
19713
+ } else if (runtime.download?.[platform]) {
19714
+ url = `${RELEASES_BASE}/${runtime.download[platform]}`;
19066
19715
  } else {
19067
- console.log(`Found ${issues.length} issue(s):
19068
- `);
19069
- for (const issue of issues) {
19070
- console.log(` \u2022 ${issue}`);
19716
+ throw new Error(`No download for ${platform}`);
19717
+ }
19718
+ await downloadAndExtract(url, destPath, name);
19719
+ }
19720
+ async function downloadBinary(binary, name, destPath, platform) {
19721
+ let url;
19722
+ if (binary.upstream?.[platform]) {
19723
+ url = binary.upstream[platform];
19724
+ } else if (binary.download?.[platform]) {
19725
+ url = `${RELEASES_BASE}/${binary.download[platform]}`;
19726
+ } else {
19727
+ throw new Error(`No download for ${platform}`);
19728
+ }
19729
+ await downloadAndExtract(url, destPath, name, binary.extract);
19730
+ }
19731
+ async function downloadAndExtract(url, destPath, name, extractConfig) {
19732
+ const tempFile = import_path15.default.join(PATHS.cache, `${name}-download.tar.gz`);
19733
+ const response = await fetch(url);
19734
+ if (!response.ok) {
19735
+ throw new Error(`HTTP ${response.status}`);
19736
+ }
19737
+ if (!import_fs16.default.existsSync(destPath)) {
19738
+ import_fs16.default.mkdirSync(destPath, { recursive: true });
19739
+ }
19740
+ const fileStream = (0, import_fs17.createWriteStream)(tempFile);
19741
+ await (0, import_promises.pipeline)(response.body, fileStream);
19742
+ try {
19743
+ (0, import_child_process4.execSync)(`tar -xzf "${tempFile}" -C "${destPath}" --strip-components=1`, {
19744
+ stdio: "pipe"
19745
+ });
19746
+ } catch {
19747
+ (0, import_child_process4.execSync)(`tar -xzf "${tempFile}" -C "${destPath}"`, { stdio: "pipe" });
19748
+ }
19749
+ import_fs16.default.unlinkSync(tempFile);
19750
+ }
19751
+ async function createShims(shimsDir, quiet = false) {
19752
+ const shims = [];
19753
+ const runtimeShims = {
19754
+ node: "runtimes/node/bin/node",
19755
+ npm: "runtimes/node/bin/npm",
19756
+ npx: "runtimes/node/bin/npx",
19757
+ python: "runtimes/python/bin/python3",
19758
+ python3: "runtimes/python/bin/python3",
19759
+ pip: "runtimes/python/bin/pip3",
19760
+ pip3: "runtimes/python/bin/pip3"
19761
+ };
19762
+ const binaryShims = {
19763
+ sqlite3: "binaries/sqlite/sqlite3",
19764
+ rg: "binaries/ripgrep/rg",
19765
+ ripgrep: "binaries/ripgrep/rg"
19766
+ };
19767
+ for (const [shimName, targetPath] of Object.entries(runtimeShims)) {
19768
+ const fullTarget = import_path15.default.join(PATHS.home, targetPath);
19769
+ const shimPath = import_path15.default.join(shimsDir, shimName);
19770
+ if (import_fs16.default.existsSync(fullTarget)) {
19771
+ createShim(shimPath, fullTarget);
19772
+ shims.push(shimName);
19071
19773
  }
19072
- if (flags.fix && fixes.length > 0) {
19073
- console.log("\nAttempting fixes...");
19074
- for (const fix of fixes) {
19075
- try {
19076
- fix();
19077
- } catch (error) {
19078
- console.error(` Fix failed: ${error.message}`);
19079
- }
19080
- }
19081
- console.log("Done. Run doctor again to verify.");
19082
- } else if (fixes.length > 0) {
19083
- console.log("\nRun with --fix to attempt automatic fixes.");
19774
+ }
19775
+ for (const [shimName, targetPath] of Object.entries(binaryShims)) {
19776
+ const fullTarget = import_path15.default.join(PATHS.home, targetPath);
19777
+ const shimPath = import_path15.default.join(shimsDir, shimName);
19778
+ if (import_fs16.default.existsSync(fullTarget)) {
19779
+ createShim(shimPath, fullTarget);
19780
+ shims.push(shimName);
19781
+ }
19782
+ }
19783
+ if (!quiet) {
19784
+ if (shims.length > 0) {
19785
+ console.log(` \u2713 ${shims.length} shims: ${shims.join(", ")}`);
19786
+ } else {
19787
+ console.log(" \u26A0 No shims (runtimes/binaries not installed)");
19084
19788
  }
19085
19789
  }
19790
+ return shims.length;
19791
+ }
19792
+ function createShim(shimPath, targetPath) {
19793
+ if (import_fs16.default.existsSync(shimPath)) {
19794
+ import_fs16.default.unlinkSync(shimPath);
19795
+ }
19796
+ import_fs16.default.symlinkSync(targetPath, shimPath);
19086
19797
  }
19087
19798
 
19088
19799
  // src/commands/update.js
19089
- var import_fs14 = __toESM(require("fs"), 1);
19090
- var import_path13 = __toESM(require("path"), 1);
19091
- var import_child_process3 = require("child_process");
19800
+ var import_fs18 = __toESM(require("fs"), 1);
19801
+ var import_path16 = __toESM(require("path"), 1);
19802
+ var import_child_process5 = require("child_process");
19092
19803
  init_src();
19093
19804
  init_src2();
19094
19805
  async function cmdUpdate(args, flags) {
@@ -19113,7 +19824,7 @@ async function cmdUpdate(args, flags) {
19113
19824
  async function updatePackage(pkgId, flags) {
19114
19825
  const [kind, name] = parsePackageId(pkgId);
19115
19826
  const installPath = getPackagePath(pkgId);
19116
- if (!import_fs14.default.existsSync(installPath)) {
19827
+ if (!import_fs18.default.existsSync(installPath)) {
19117
19828
  return { success: false, error: "Package not installed" };
19118
19829
  }
19119
19830
  const pkg = await getPackage(pkgId);
@@ -19123,7 +19834,7 @@ async function updatePackage(pkgId, flags) {
19123
19834
  console.log(`Updating ${pkgId}...`);
19124
19835
  if (pkg.npmPackage) {
19125
19836
  try {
19126
- (0, import_child_process3.execSync)(`npm install ${pkg.npmPackage}@latest`, {
19837
+ (0, import_child_process5.execSync)(`npm install ${pkg.npmPackage}@latest`, {
19127
19838
  cwd: installPath,
19128
19839
  stdio: flags.verbose ? "inherit" : "pipe"
19129
19840
  });
@@ -19136,11 +19847,11 @@ async function updatePackage(pkgId, flags) {
19136
19847
  }
19137
19848
  if (pkg.pipPackage) {
19138
19849
  try {
19139
- const venvPip = import_path13.default.join(installPath, "venv", "bin", "pip");
19140
- (0, import_child_process3.execSync)(`"${venvPip}" install --upgrade ${pkg.pipPackage}`, {
19850
+ const venvPip = import_path16.default.join(installPath, "venv", "bin", "pip");
19851
+ (0, import_child_process5.execSync)(`"${venvPip}" install --upgrade ${pkg.pipPackage}`, {
19141
19852
  stdio: flags.verbose ? "inherit" : "pipe"
19142
19853
  });
19143
- const versionOutput = (0, import_child_process3.execSync)(`"${venvPip}" show ${pkg.pipPackage} | grep Version`, {
19854
+ const versionOutput = (0, import_child_process5.execSync)(`"${venvPip}" show ${pkg.pipPackage} | grep Version`, {
19144
19855
  encoding: "utf-8"
19145
19856
  });
19146
19857
  const version = versionOutput.split(":")[1]?.trim();
@@ -19152,9 +19863,9 @@ async function updatePackage(pkgId, flags) {
19152
19863
  }
19153
19864
  if (kind === "runtime" && !pkg.npmPackage && !pkg.pipPackage) {
19154
19865
  try {
19155
- const { downloadRuntime: downloadRuntime2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
19156
- import_fs14.default.rmSync(installPath, { recursive: true, force: true });
19157
- await downloadRuntime2(name, pkg.version || "latest", installPath, {
19866
+ const { downloadRuntime: downloadRuntime3 } = await Promise.resolve().then(() => (init_src2(), src_exports));
19867
+ import_fs18.default.rmSync(installPath, { recursive: true, force: true });
19868
+ await downloadRuntime3(name, pkg.version || "latest", installPath, {
19158
19869
  onProgress: (p) => {
19159
19870
  if (flags.verbose) console.log(` ${p.phase}...`);
19160
19871
  }
@@ -19173,8 +19884,8 @@ async function updateAll(flags) {
19173
19884
  let failed = 0;
19174
19885
  for (const kind of kinds) {
19175
19886
  const dir = kind === "runtime" ? PATHS.runtimes : kind === "stack" ? PATHS.stacks : PATHS.prompts;
19176
- if (!import_fs14.default.existsSync(dir)) continue;
19177
- const entries = import_fs14.default.readdirSync(dir, { withFileTypes: true });
19887
+ if (!import_fs18.default.existsSync(dir)) continue;
19888
+ const entries = import_fs18.default.readdirSync(dir, { withFileTypes: true });
19178
19889
  for (const entry of entries) {
19179
19890
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19180
19891
  const pkgId = `${kind}:${entry.name}`;
@@ -19193,14 +19904,14 @@ Updated ${updated} package(s)${failed > 0 ? `, ${failed} failed` : ""}`);
19193
19904
  }
19194
19905
  function getInstalledVersion(installPath, npmPackage) {
19195
19906
  try {
19196
- const pkgJsonPath = import_path13.default.join(installPath, "node_modules", npmPackage.replace("@", "").split("/")[0], "package.json");
19197
- if (import_fs14.default.existsSync(pkgJsonPath)) {
19198
- const pkgJson = JSON.parse(import_fs14.default.readFileSync(pkgJsonPath, "utf-8"));
19907
+ const pkgJsonPath = import_path16.default.join(installPath, "node_modules", npmPackage.replace("@", "").split("/")[0], "package.json");
19908
+ if (import_fs18.default.existsSync(pkgJsonPath)) {
19909
+ const pkgJson = JSON.parse(import_fs18.default.readFileSync(pkgJsonPath, "utf-8"));
19199
19910
  return pkgJson.version;
19200
19911
  }
19201
- const rootPkgPath = import_path13.default.join(installPath, "package.json");
19202
- if (import_fs14.default.existsSync(rootPkgPath)) {
19203
- const rootPkg = JSON.parse(import_fs14.default.readFileSync(rootPkgPath, "utf-8"));
19912
+ const rootPkgPath = import_path16.default.join(installPath, "package.json");
19913
+ if (import_fs18.default.existsSync(rootPkgPath)) {
19914
+ const rootPkg = JSON.parse(import_fs18.default.readFileSync(rootPkgPath, "utf-8"));
19204
19915
  const dep = rootPkg.dependencies?.[npmPackage];
19205
19916
  if (dep) return dep.replace(/[\^~]/, "");
19206
19917
  }
@@ -19209,30 +19920,30 @@ function getInstalledVersion(installPath, npmPackage) {
19209
19920
  return null;
19210
19921
  }
19211
19922
  function updateRuntimeMetadata(installPath, updates) {
19212
- const metaPath = import_path13.default.join(installPath, "runtime.json");
19923
+ const metaPath = import_path16.default.join(installPath, "runtime.json");
19213
19924
  try {
19214
19925
  let meta = {};
19215
- if (import_fs14.default.existsSync(metaPath)) {
19216
- meta = JSON.parse(import_fs14.default.readFileSync(metaPath, "utf-8"));
19926
+ if (import_fs18.default.existsSync(metaPath)) {
19927
+ meta = JSON.parse(import_fs18.default.readFileSync(metaPath, "utf-8"));
19217
19928
  }
19218
19929
  meta = { ...meta, ...updates };
19219
- import_fs14.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
19930
+ import_fs18.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
19220
19931
  } catch {
19221
19932
  }
19222
19933
  }
19223
19934
 
19224
19935
  // db/index.js
19225
19936
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
19226
- var import_path14 = __toESM(require("path"), 1);
19227
- var import_os4 = __toESM(require("os"), 1);
19228
- var import_fs15 = __toESM(require("fs"), 1);
19229
- var RUDI_HOME2 = import_path14.default.join(import_os4.default.homedir(), ".rudi");
19230
- var DB_PATH2 = import_path14.default.join(RUDI_HOME2, "rudi.db");
19937
+ var import_path17 = __toESM(require("path"), 1);
19938
+ var import_os5 = __toESM(require("os"), 1);
19939
+ var import_fs19 = __toESM(require("fs"), 1);
19940
+ var RUDI_HOME2 = import_path17.default.join(import_os5.default.homedir(), ".rudi");
19941
+ var DB_PATH2 = import_path17.default.join(RUDI_HOME2, "rudi.db");
19231
19942
  var db2 = null;
19232
19943
  function getDb2(options = {}) {
19233
19944
  if (!db2) {
19234
- if (!import_fs15.default.existsSync(RUDI_HOME2)) {
19235
- import_fs15.default.mkdirSync(RUDI_HOME2, { recursive: true });
19945
+ if (!import_fs19.default.existsSync(RUDI_HOME2)) {
19946
+ import_fs19.default.mkdirSync(RUDI_HOME2, { recursive: true });
19236
19947
  }
19237
19948
  db2 = new import_better_sqlite32.default(DB_PATH2, {
19238
19949
  readonly: options.readonly || false
@@ -19390,7 +20101,7 @@ function getBeforeCrashLogs() {
19390
20101
  }
19391
20102
 
19392
20103
  // src/commands/logs.js
19393
- var import_fs16 = __toESM(require("fs"), 1);
20104
+ var import_fs20 = __toESM(require("fs"), 1);
19394
20105
  function parseTimeAgo(str) {
19395
20106
  const match = str.match(/^(\d+)([smhd])$/);
19396
20107
  if (!match) return null;
@@ -19490,7 +20201,7 @@ function exportLogs(logs, filepath, format) {
19490
20201
  });
19491
20202
  content = JSON.stringify(formatted, null, 2);
19492
20203
  }
19493
- import_fs16.default.writeFileSync(filepath, content, "utf-8");
20204
+ import_fs20.default.writeFileSync(filepath, content, "utf-8");
19494
20205
  return filepath;
19495
20206
  }
19496
20207
  function printStats(stats) {
@@ -19616,9 +20327,9 @@ async function handleLogsCommand(args, flags) {
19616
20327
  }
19617
20328
 
19618
20329
  // src/commands/which.js
19619
- var fs17 = __toESM(require("fs/promises"), 1);
19620
- var path15 = __toESM(require("path"), 1);
19621
- var import_child_process4 = require("child_process");
20330
+ var fs21 = __toESM(require("fs/promises"), 1);
20331
+ var path19 = __toESM(require("path"), 1);
20332
+ var import_child_process6 = require("child_process");
19622
20333
  init_src();
19623
20334
  async function cmdWhich(args, flags) {
19624
20335
  const stackId = args[0];
@@ -19686,7 +20397,7 @@ Installed stacks:`);
19686
20397
  if (runtimeInfo.entry) {
19687
20398
  console.log("");
19688
20399
  console.log("Run MCP server directly:");
19689
- const entryPath = path15.join(stackPath, runtimeInfo.entry);
20400
+ const entryPath = path19.join(stackPath, runtimeInfo.entry);
19690
20401
  if (runtimeInfo.runtime === "node") {
19691
20402
  console.log(` echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node ${entryPath}`);
19692
20403
  } else if (runtimeInfo.runtime === "python") {
@@ -19705,27 +20416,27 @@ Installed stacks:`);
19705
20416
  async function detectRuntime(stackPath) {
19706
20417
  const runtimes = ["node", "python"];
19707
20418
  for (const runtime of runtimes) {
19708
- const runtimePath = path15.join(stackPath, runtime);
20419
+ const runtimePath = path19.join(stackPath, runtime);
19709
20420
  try {
19710
- await fs17.access(runtimePath);
20421
+ await fs21.access(runtimePath);
19711
20422
  if (runtime === "node") {
19712
- const distEntry = path15.join(runtimePath, "dist", "index.js");
19713
- const srcEntry = path15.join(runtimePath, "src", "index.ts");
20423
+ const distEntry = path19.join(runtimePath, "dist", "index.js");
20424
+ const srcEntry = path19.join(runtimePath, "src", "index.ts");
19714
20425
  try {
19715
- await fs17.access(distEntry);
20426
+ await fs21.access(distEntry);
19716
20427
  return { runtime: "node", entry: `${runtime}/dist/index.js` };
19717
20428
  } catch {
19718
20429
  try {
19719
- await fs17.access(srcEntry);
20430
+ await fs21.access(srcEntry);
19720
20431
  return { runtime: "node", entry: `${runtime}/src/index.ts` };
19721
20432
  } catch {
19722
20433
  return { runtime: "node", entry: null };
19723
20434
  }
19724
20435
  }
19725
20436
  } else if (runtime === "python") {
19726
- const entry = path15.join(runtimePath, "src", "index.py");
20437
+ const entry = path19.join(runtimePath, "src", "index.py");
19727
20438
  try {
19728
- await fs17.access(entry);
20439
+ await fs21.access(entry);
19729
20440
  return { runtime: "python", entry: `${runtime}/src/index.py` };
19730
20441
  } catch {
19731
20442
  return { runtime: "python", entry: null };
@@ -19741,21 +20452,21 @@ async function checkAuth(stackPath, runtime) {
19741
20452
  const authFiles = [];
19742
20453
  let configured = false;
19743
20454
  if (runtime === "node" || runtime === "python") {
19744
- const runtimePath = path15.join(stackPath, runtime);
19745
- const tokenPath = path15.join(runtimePath, "token.json");
20455
+ const runtimePath = path19.join(stackPath, runtime);
20456
+ const tokenPath = path19.join(runtimePath, "token.json");
19746
20457
  try {
19747
- await fs17.access(tokenPath);
20458
+ await fs21.access(tokenPath);
19748
20459
  authFiles.push(`${runtime}/token.json`);
19749
20460
  configured = true;
19750
20461
  } catch {
19751
- const accountsPath = path15.join(runtimePath, "accounts");
20462
+ const accountsPath = path19.join(runtimePath, "accounts");
19752
20463
  try {
19753
- const accounts = await fs17.readdir(accountsPath);
20464
+ const accounts = await fs21.readdir(accountsPath);
19754
20465
  for (const account of accounts) {
19755
20466
  if (account.startsWith(".")) continue;
19756
- const accountTokenPath = path15.join(accountsPath, account, "token.json");
20467
+ const accountTokenPath = path19.join(accountsPath, account, "token.json");
19757
20468
  try {
19758
- await fs17.access(accountTokenPath);
20469
+ await fs21.access(accountTokenPath);
19759
20470
  authFiles.push(`${runtime}/accounts/${account}/token.json`);
19760
20471
  configured = true;
19761
20472
  } catch {
@@ -19765,9 +20476,9 @@ async function checkAuth(stackPath, runtime) {
19765
20476
  }
19766
20477
  }
19767
20478
  }
19768
- const envPath = path15.join(stackPath, ".env");
20479
+ const envPath = path19.join(stackPath, ".env");
19769
20480
  try {
19770
- const envContent = await fs17.readFile(envPath, "utf-8");
20481
+ const envContent = await fs21.readFile(envPath, "utf-8");
19771
20482
  const hasValues = envContent.split("\n").some((line) => {
19772
20483
  const trimmed = line.trim();
19773
20484
  if (!trimmed || trimmed.startsWith("#")) return false;
@@ -19796,7 +20507,7 @@ async function checkAuth(stackPath, runtime) {
19796
20507
  }
19797
20508
  function checkIfRunning(stackName) {
19798
20509
  try {
19799
- const result = (0, import_child_process4.execSync)(`ps aux | grep "${stackName}" | grep -v grep || true`, {
20510
+ const result = (0, import_child_process6.execSync)(`ps aux | grep "${stackName}" | grep -v grep || true`, {
19800
20511
  encoding: "utf-8",
19801
20512
  stdio: ["pipe", "pipe", "ignore"]
19802
20513
  // Suppress stderr
@@ -19811,9 +20522,9 @@ function checkIfRunning(stackName) {
19811
20522
  }
19812
20523
 
19813
20524
  // src/commands/auth.js
19814
- var fs18 = __toESM(require("fs/promises"), 1);
19815
- var path16 = __toESM(require("path"), 1);
19816
- var import_child_process5 = require("child_process");
20525
+ var fs22 = __toESM(require("fs/promises"), 1);
20526
+ var path20 = __toESM(require("path"), 1);
20527
+ var import_child_process7 = require("child_process");
19817
20528
  init_src();
19818
20529
  var net = __toESM(require("net"), 1);
19819
20530
  async function findAvailablePort(basePort = 3456) {
@@ -19844,26 +20555,26 @@ function isPortAvailable(port) {
19844
20555
  async function detectRuntime2(stackPath) {
19845
20556
  const runtimes = ["node", "python"];
19846
20557
  for (const runtime of runtimes) {
19847
- const runtimePath = path16.join(stackPath, runtime);
20558
+ const runtimePath = path20.join(stackPath, runtime);
19848
20559
  try {
19849
- await fs18.access(runtimePath);
20560
+ await fs22.access(runtimePath);
19850
20561
  if (runtime === "node") {
19851
- const authTs = path16.join(runtimePath, "src", "auth.ts");
19852
- const authJs = path16.join(runtimePath, "dist", "auth.js");
20562
+ const authTs = path20.join(runtimePath, "src", "auth.ts");
20563
+ const authJs = path20.join(runtimePath, "dist", "auth.js");
19853
20564
  try {
19854
- await fs18.access(authTs);
20565
+ await fs22.access(authTs);
19855
20566
  return { runtime: "node", authScript: authTs, useTsx: true };
19856
20567
  } catch {
19857
20568
  try {
19858
- await fs18.access(authJs);
20569
+ await fs22.access(authJs);
19859
20570
  return { runtime: "node", authScript: authJs, useTsx: false };
19860
20571
  } catch {
19861
20572
  }
19862
20573
  }
19863
20574
  } else if (runtime === "python") {
19864
- const authPy = path16.join(runtimePath, "src", "auth.py");
20575
+ const authPy = path20.join(runtimePath, "src", "auth.py");
19865
20576
  try {
19866
- await fs18.access(authPy);
20577
+ await fs22.access(authPy);
19867
20578
  return { runtime: "python", authScript: authPy, useTsx: false };
19868
20579
  } catch {
19869
20580
  }
@@ -19913,14 +20624,14 @@ Installed stacks:`);
19913
20624
  console.log(`Using port: ${port}`);
19914
20625
  console.log("");
19915
20626
  let cmd;
19916
- const cwd = path16.dirname(authInfo.authScript);
20627
+ const cwd = path20.dirname(authInfo.authScript);
19917
20628
  if (authInfo.runtime === "node") {
19918
- const distAuth = path16.join(cwd, "..", "dist", "auth.js");
20629
+ const distAuth = path20.join(cwd, "..", "dist", "auth.js");
19919
20630
  let useBuiltInPort = false;
19920
20631
  let tempAuthScript = null;
19921
20632
  try {
19922
- await fs18.access(distAuth);
19923
- const distContent = await fs18.readFile(distAuth, "utf-8");
20633
+ await fs22.access(distAuth);
20634
+ const distContent = await fs22.readFile(distAuth, "utf-8");
19924
20635
  if (distContent.includes("findAvailablePort")) {
19925
20636
  console.log("Using compiled authentication script...");
19926
20637
  cmd = `node ${distAuth}${accountEmail ? ` ${accountEmail}` : ""}`;
@@ -19929,11 +20640,11 @@ Installed stacks:`);
19929
20640
  } catch {
19930
20641
  }
19931
20642
  if (!useBuiltInPort) {
19932
- const authContent = await fs18.readFile(authInfo.authScript, "utf-8");
20643
+ const authContent = await fs22.readFile(authInfo.authScript, "utf-8");
19933
20644
  const tempExt = authInfo.useTsx ? ".ts" : ".mjs";
19934
- tempAuthScript = path16.join(cwd, "..", `auth-temp${tempExt}`);
20645
+ tempAuthScript = path20.join(cwd, "..", `auth-temp${tempExt}`);
19935
20646
  const modifiedContent = authContent.replace(/localhost:3456/g, `localhost:${port}`).replace(/server\.listen\(3456/g, `server.listen(${port}`);
19936
- await fs18.writeFile(tempAuthScript, modifiedContent);
20647
+ await fs22.writeFile(tempAuthScript, modifiedContent);
19937
20648
  if (authInfo.useTsx) {
19938
20649
  cmd = `npx tsx ${tempAuthScript}${accountEmail ? ` ${accountEmail}` : ""}`;
19939
20650
  } else {
@@ -19943,17 +20654,17 @@ Installed stacks:`);
19943
20654
  console.log("Starting OAuth flow...");
19944
20655
  console.log("");
19945
20656
  try {
19946
- (0, import_child_process5.execSync)(cmd, {
20657
+ (0, import_child_process7.execSync)(cmd, {
19947
20658
  cwd,
19948
20659
  stdio: "inherit"
19949
20660
  });
19950
20661
  if (tempAuthScript) {
19951
- await fs18.unlink(tempAuthScript);
20662
+ await fs22.unlink(tempAuthScript);
19952
20663
  }
19953
20664
  } catch (error) {
19954
20665
  if (tempAuthScript) {
19955
20666
  try {
19956
- await fs18.unlink(tempAuthScript);
20667
+ await fs22.unlink(tempAuthScript);
19957
20668
  } catch {
19958
20669
  }
19959
20670
  }
@@ -19963,7 +20674,7 @@ Installed stacks:`);
19963
20674
  cmd = `python3 ${authInfo.authScript}${accountEmail ? ` ${accountEmail}` : ""}`;
19964
20675
  console.log("Starting OAuth flow...");
19965
20676
  console.log("");
19966
- (0, import_child_process5.execSync)(cmd, {
20677
+ (0, import_child_process7.execSync)(cmd, {
19967
20678
  cwd,
19968
20679
  stdio: "inherit",
19969
20680
  env: {
@@ -19984,6 +20695,657 @@ Installed stacks:`);
19984
20695
  }
19985
20696
  }
19986
20697
 
20698
+ // src/commands/mcp.js
20699
+ var fs23 = __toESM(require("fs"), 1);
20700
+ var path21 = __toESM(require("path"), 1);
20701
+ var import_child_process8 = require("child_process");
20702
+ init_src();
20703
+ function getBundledRuntime(runtime) {
20704
+ const platform = process.platform;
20705
+ if (runtime === "node") {
20706
+ const nodePath = platform === "win32" ? path21.join(PATHS.runtimes, "node", "node.exe") : path21.join(PATHS.runtimes, "node", "bin", "node");
20707
+ if (fs23.existsSync(nodePath)) {
20708
+ return nodePath;
20709
+ }
20710
+ }
20711
+ if (runtime === "python") {
20712
+ const pythonPath = platform === "win32" ? path21.join(PATHS.runtimes, "python", "python.exe") : path21.join(PATHS.runtimes, "python", "bin", "python3");
20713
+ if (fs23.existsSync(pythonPath)) {
20714
+ return pythonPath;
20715
+ }
20716
+ }
20717
+ return null;
20718
+ }
20719
+ function getBundledNpx() {
20720
+ const platform = process.platform;
20721
+ const npxPath = platform === "win32" ? path21.join(PATHS.runtimes, "node", "npx.cmd") : path21.join(PATHS.runtimes, "node", "bin", "npx");
20722
+ if (fs23.existsSync(npxPath)) {
20723
+ return npxPath;
20724
+ }
20725
+ return null;
20726
+ }
20727
+ function loadManifest2(stackPath) {
20728
+ const manifestPath = path21.join(stackPath, "manifest.json");
20729
+ if (!fs23.existsSync(manifestPath)) {
20730
+ return null;
20731
+ }
20732
+ return JSON.parse(fs23.readFileSync(manifestPath, "utf-8"));
20733
+ }
20734
+ function getRequiredSecrets(manifest) {
20735
+ const secrets = manifest?.requires?.secrets || manifest?.secrets || [];
20736
+ return secrets.map((s) => ({
20737
+ name: typeof s === "string" ? s : s.name || s.key,
20738
+ required: typeof s === "object" ? s.required !== false : true
20739
+ }));
20740
+ }
20741
+ async function buildEnv(manifest) {
20742
+ const env = { ...process.env };
20743
+ const requiredSecrets = getRequiredSecrets(manifest);
20744
+ const missing = [];
20745
+ for (const secret of requiredSecrets) {
20746
+ const value = await getSecret(secret.name);
20747
+ if (value) {
20748
+ env[secret.name] = value;
20749
+ } else if (secret.required) {
20750
+ missing.push(secret.name);
20751
+ }
20752
+ }
20753
+ return { env, missing };
20754
+ }
20755
+ async function cmdMcp(args, flags) {
20756
+ const stackName = args[0];
20757
+ if (!stackName) {
20758
+ console.error("Usage: rudi mcp <stack>");
20759
+ console.error("");
20760
+ console.error("This command is typically called by agent shims, not directly.");
20761
+ console.error("");
20762
+ console.error("Example: rudi mcp slack");
20763
+ process.exit(1);
20764
+ }
20765
+ const stackPath = path21.join(PATHS.stacks, stackName);
20766
+ if (!fs23.existsSync(stackPath)) {
20767
+ console.error(`Stack not found: ${stackName}`);
20768
+ console.error(`Expected at: ${stackPath}`);
20769
+ console.error("");
20770
+ console.error(`Install with: rudi install ${stackName}`);
20771
+ process.exit(1);
20772
+ }
20773
+ const manifest = loadManifest2(stackPath);
20774
+ if (!manifest) {
20775
+ console.error(`No manifest.json found in stack: ${stackName}`);
20776
+ process.exit(1);
20777
+ }
20778
+ const { env, missing } = await buildEnv(manifest);
20779
+ if (missing.length > 0 && !flags.force) {
20780
+ console.error(`Missing required secrets for ${stackName}:`);
20781
+ for (const name of missing) {
20782
+ console.error(` - ${name}`);
20783
+ }
20784
+ console.error("");
20785
+ console.error(`Set with: rudi secrets set ${missing[0]}`);
20786
+ process.exit(1);
20787
+ }
20788
+ let command = manifest.command;
20789
+ if (!command || command.length === 0) {
20790
+ if (manifest.mcp?.command) {
20791
+ const mcpCmd = manifest.mcp.command;
20792
+ const mcpArgs = manifest.mcp.args || [];
20793
+ command = [mcpCmd, ...mcpArgs];
20794
+ }
20795
+ }
20796
+ if (!command || command.length === 0) {
20797
+ console.error(`No command defined in manifest for: ${stackName}`);
20798
+ process.exit(1);
20799
+ }
20800
+ const runtime = manifest.runtime || manifest.mcp?.runtime || "node";
20801
+ const resolvedCommand = command.map((part, i) => {
20802
+ if (i === 0) {
20803
+ if (part === "node") {
20804
+ const bundledNode = getBundledRuntime("node");
20805
+ if (bundledNode) return bundledNode;
20806
+ } else if (part === "npx") {
20807
+ const bundledNpx = getBundledNpx();
20808
+ if (bundledNpx) return bundledNpx;
20809
+ } else if (part === "python" || part === "python3") {
20810
+ const bundledPython = getBundledRuntime("python");
20811
+ if (bundledPython) return bundledPython;
20812
+ }
20813
+ return part;
20814
+ }
20815
+ if (part.startsWith("./") || part.startsWith("../") || !path21.isAbsolute(part)) {
20816
+ const resolved = path21.join(stackPath, part);
20817
+ if (fs23.existsSync(resolved)) {
20818
+ return resolved;
20819
+ }
20820
+ }
20821
+ return part;
20822
+ });
20823
+ const [cmd, ...cmdArgs] = resolvedCommand;
20824
+ const bundledNodeBin = path21.join(PATHS.runtimes, "node", "bin");
20825
+ const bundledPythonBin = path21.join(PATHS.runtimes, "python", "bin");
20826
+ if (fs23.existsSync(bundledNodeBin) || fs23.existsSync(bundledPythonBin)) {
20827
+ const runtimePaths = [];
20828
+ if (fs23.existsSync(bundledNodeBin)) runtimePaths.push(bundledNodeBin);
20829
+ if (fs23.existsSync(bundledPythonBin)) runtimePaths.push(bundledPythonBin);
20830
+ env.PATH = runtimePaths.join(path21.delimiter) + path21.delimiter + (env.PATH || "");
20831
+ }
20832
+ if (flags.debug) {
20833
+ console.error(`[rudi mcp] Stack: ${stackName}`);
20834
+ console.error(`[rudi mcp] Path: ${stackPath}`);
20835
+ console.error(`[rudi mcp] Runtime: ${runtime}`);
20836
+ console.error(`[rudi mcp] Command: ${cmd} ${cmdArgs.join(" ")}`);
20837
+ console.error(`[rudi mcp] Secrets loaded: ${getRequiredSecrets(manifest).length - missing.length}`);
20838
+ if (getBundledRuntime(runtime)) {
20839
+ console.error(`[rudi mcp] Using bundled ${runtime} runtime`);
20840
+ } else {
20841
+ console.error(`[rudi mcp] Using system ${runtime} (no bundled runtime found)`);
20842
+ }
20843
+ }
20844
+ const child = (0, import_child_process8.spawn)(cmd, cmdArgs, {
20845
+ cwd: stackPath,
20846
+ env,
20847
+ stdio: "inherit"
20848
+ // MCP uses stdio for communication
20849
+ });
20850
+ child.on("error", (err) => {
20851
+ console.error(`Failed to start MCP server: ${err.message}`);
20852
+ process.exit(1);
20853
+ });
20854
+ child.on("exit", (code) => {
20855
+ process.exit(code || 0);
20856
+ });
20857
+ }
20858
+
20859
+ // src/commands/integrate.js
20860
+ var fs24 = __toESM(require("fs"), 1);
20861
+ var path22 = __toESM(require("path"), 1);
20862
+ var import_os6 = __toESM(require("os"), 1);
20863
+ init_src();
20864
+ var HOME2 = import_os6.default.homedir();
20865
+ var SHIM_PATH = path22.join(PATHS.home, "shims", "rudi-mcp");
20866
+ function getInstalledStacks() {
20867
+ const stacksDir = PATHS.stacks;
20868
+ if (!fs24.existsSync(stacksDir)) return [];
20869
+ return fs24.readdirSync(stacksDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).filter((d) => fs24.existsSync(path22.join(stacksDir, d.name, "manifest.json"))).map((d) => d.name);
20870
+ }
20871
+ function ensureShim() {
20872
+ const shimsDir = path22.dirname(SHIM_PATH);
20873
+ if (!fs24.existsSync(shimsDir)) {
20874
+ fs24.mkdirSync(shimsDir, { recursive: true });
20875
+ }
20876
+ const shimContent = `#!/usr/bin/env bash
20877
+ set -euo pipefail
20878
+ # Try rudi in PATH first, fall back to npx
20879
+ if command -v rudi &> /dev/null; then
20880
+ exec rudi mcp "$1"
20881
+ else
20882
+ exec npx --yes @learnrudi/cli mcp "$1"
20883
+ fi
20884
+ `;
20885
+ if (fs24.existsSync(SHIM_PATH)) {
20886
+ const existing = fs24.readFileSync(SHIM_PATH, "utf-8");
20887
+ if (existing === shimContent) {
20888
+ return { created: false, path: SHIM_PATH };
20889
+ }
20890
+ }
20891
+ fs24.writeFileSync(SHIM_PATH, shimContent, { mode: 493 });
20892
+ return { created: true, path: SHIM_PATH };
20893
+ }
20894
+ function backupConfig(configPath) {
20895
+ if (!fs24.existsSync(configPath)) return null;
20896
+ const backupPath = configPath + ".backup." + Date.now();
20897
+ fs24.copyFileSync(configPath, backupPath);
20898
+ return backupPath;
20899
+ }
20900
+ function readJsonConfig(configPath) {
20901
+ if (!fs24.existsSync(configPath)) {
20902
+ return {};
20903
+ }
20904
+ try {
20905
+ return JSON.parse(fs24.readFileSync(configPath, "utf-8"));
20906
+ } catch {
20907
+ return {};
20908
+ }
20909
+ }
20910
+ function writeJsonConfig(configPath, config) {
20911
+ const dir = path22.dirname(configPath);
20912
+ if (!fs24.existsSync(dir)) {
20913
+ fs24.mkdirSync(dir, { recursive: true });
20914
+ }
20915
+ fs24.writeFileSync(configPath, JSON.stringify(config, null, 2));
20916
+ }
20917
+ function buildMcpEntry(stackName, agentId) {
20918
+ const base = {
20919
+ command: SHIM_PATH,
20920
+ args: [stackName]
20921
+ };
20922
+ if (agentId === "claude-desktop" || agentId === "claude-code") {
20923
+ return { type: "stdio", ...base };
20924
+ }
20925
+ return base;
20926
+ }
20927
+ async function integrateAgent(agentId, stacks, flags) {
20928
+ const agentConfig = AGENT_CONFIGS2.find((a) => a.id === agentId);
20929
+ if (!agentConfig) {
20930
+ console.error(`Unknown agent: ${agentId}`);
20931
+ return { success: false, error: "Unknown agent" };
20932
+ }
20933
+ const configPath = findAgentConfig(agentConfig);
20934
+ const configDir = configPath ? path22.dirname(configPath) : null;
20935
+ const targetPath = configPath || path22.join(HOME2, agentConfig.paths[process.platform]?.[0] || agentConfig.paths.darwin[0]);
20936
+ console.log(`
20937
+ ${agentConfig.name}:`);
20938
+ console.log(` Config: ${targetPath}`);
20939
+ if (fs24.existsSync(targetPath)) {
20940
+ const backup = backupConfig(targetPath);
20941
+ if (backup && flags.verbose) {
20942
+ console.log(` Backup: ${backup}`);
20943
+ }
20944
+ }
20945
+ const config = readJsonConfig(targetPath);
20946
+ const key = agentConfig.key;
20947
+ if (!config[key]) {
20948
+ config[key] = {};
20949
+ }
20950
+ let added = 0;
20951
+ let updated = 0;
20952
+ for (const stackName of stacks) {
20953
+ const entry = buildMcpEntry(stackName, agentId);
20954
+ const existing = config[key][stackName];
20955
+ if (!existing) {
20956
+ config[key][stackName] = entry;
20957
+ added++;
20958
+ } else if (existing.command !== entry.command || JSON.stringify(existing.args) !== JSON.stringify(entry.args)) {
20959
+ config[key][stackName] = entry;
20960
+ updated++;
20961
+ }
20962
+ }
20963
+ if (added > 0 || updated > 0) {
20964
+ writeJsonConfig(targetPath, config);
20965
+ console.log(` Added: ${added}, Updated: ${updated}`);
20966
+ } else {
20967
+ console.log(` Already up to date`);
20968
+ }
20969
+ return { success: true, added, updated };
20970
+ }
20971
+ async function cmdIntegrate(args, flags) {
20972
+ const target = args[0];
20973
+ if (!target) {
20974
+ console.log(`
20975
+ rudi integrate - Wire RUDI stacks into agent configs
20976
+
20977
+ USAGE
20978
+ rudi integrate <agent> Integrate with specific agent
20979
+ rudi integrate all Integrate with all detected agents
20980
+ rudi integrate --list Show detected agents
20981
+
20982
+ AGENTS
20983
+ claude Claude Desktop + Claude Code
20984
+ cursor Cursor IDE
20985
+ windsurf Windsurf IDE
20986
+ vscode VS Code / GitHub Copilot
20987
+ gemini Gemini CLI
20988
+ codex OpenAI Codex CLI
20989
+ zed Zed Editor
20990
+
20991
+ OPTIONS
20992
+ --verbose Show detailed output
20993
+ --dry-run Show what would be done without making changes
20994
+
20995
+ EXAMPLES
20996
+ rudi integrate claude
20997
+ rudi integrate all
20998
+ `);
20999
+ return;
21000
+ }
21001
+ if (flags.list || target === "list") {
21002
+ const installed = getInstalledAgents();
21003
+ console.log("\nDetected agents:");
21004
+ for (const agent of installed) {
21005
+ console.log(` \u2713 ${agent.name}`);
21006
+ console.log(` ${agent.configFile}`);
21007
+ }
21008
+ if (installed.length === 0) {
21009
+ console.log(" (none detected)");
21010
+ }
21011
+ return;
21012
+ }
21013
+ const stacks = getInstalledStacks();
21014
+ if (stacks.length === 0) {
21015
+ console.log("No stacks installed. Install with: rudi install <stack>");
21016
+ return;
21017
+ }
21018
+ console.log(`
21019
+ Integrating ${stacks.length} stack(s)...`);
21020
+ const shimResult = ensureShim();
21021
+ if (shimResult.created) {
21022
+ console.log(`Created shim: ${shimResult.path}`);
21023
+ }
21024
+ let targetAgents = [];
21025
+ if (target === "all") {
21026
+ targetAgents = getInstalledAgents().map((a) => a.id);
21027
+ if (targetAgents.length === 0) {
21028
+ console.log("No agents detected.");
21029
+ return;
21030
+ }
21031
+ } else if (target === "claude") {
21032
+ targetAgents = ["claude-desktop", "claude-code"].filter((id) => {
21033
+ const agent = AGENT_CONFIGS2.find((a) => a.id === id);
21034
+ return agent && findAgentConfig(agent);
21035
+ });
21036
+ if (targetAgents.length === 0) {
21037
+ targetAgents = ["claude-code"];
21038
+ }
21039
+ } else {
21040
+ const idMap = {
21041
+ "cursor": "cursor",
21042
+ "windsurf": "windsurf",
21043
+ "vscode": "vscode",
21044
+ "gemini": "gemini",
21045
+ "codex": "codex",
21046
+ "zed": "zed",
21047
+ "cline": "cline"
21048
+ };
21049
+ const agentId = idMap[target] || target;
21050
+ targetAgents = [agentId];
21051
+ }
21052
+ if (flags["dry-run"]) {
21053
+ console.log("\nDry run - would integrate:");
21054
+ for (const agentId of targetAgents) {
21055
+ const agent = AGENT_CONFIGS2.find((a) => a.id === agentId);
21056
+ console.log(` ${agent?.name || agentId}:`);
21057
+ for (const stack of stacks) {
21058
+ console.log(` - ${stack}`);
21059
+ }
21060
+ }
21061
+ return;
21062
+ }
21063
+ const results = [];
21064
+ for (const agentId of targetAgents) {
21065
+ const result = await integrateAgent(agentId, stacks, flags);
21066
+ results.push({ agent: agentId, ...result });
21067
+ }
21068
+ const successful = results.filter((r) => r.success);
21069
+ console.log(`
21070
+ \u2713 Integrated with ${successful.length} agent(s)`);
21071
+ console.log("\nRestart your agent(s) to use the new stacks.");
21072
+ }
21073
+
21074
+ // src/commands/migrate.js
21075
+ var fs25 = __toESM(require("fs"), 1);
21076
+ var path23 = __toESM(require("path"), 1);
21077
+ var import_os7 = __toESM(require("os"), 1);
21078
+ init_src();
21079
+ var HOME3 = import_os7.default.homedir();
21080
+ var OLD_PROMPT_STACK = path23.join(HOME3, ".prompt-stack");
21081
+ var SHIM_PATH2 = path23.join(PATHS.home, "shims", "rudi-mcp");
21082
+ function getOldStacks() {
21083
+ const stacksDir = path23.join(OLD_PROMPT_STACK, "stacks");
21084
+ if (!fs25.existsSync(stacksDir)) return [];
21085
+ return fs25.readdirSync(stacksDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).filter((d) => {
21086
+ const hasManifest = fs25.existsSync(path23.join(stacksDir, d.name, "manifest.json"));
21087
+ const hasPackage = fs25.existsSync(path23.join(stacksDir, d.name, "package.json"));
21088
+ return hasManifest || hasPackage;
21089
+ }).map((d) => d.name);
21090
+ }
21091
+ function copyStack(stackName) {
21092
+ const oldPath = path23.join(OLD_PROMPT_STACK, "stacks", stackName);
21093
+ const newPath = path23.join(PATHS.stacks, stackName);
21094
+ if (!fs25.existsSync(oldPath)) {
21095
+ return { success: false, error: "Source not found" };
21096
+ }
21097
+ if (fs25.existsSync(newPath)) {
21098
+ return { success: true, skipped: true, reason: "Already exists" };
21099
+ }
21100
+ if (!fs25.existsSync(PATHS.stacks)) {
21101
+ fs25.mkdirSync(PATHS.stacks, { recursive: true });
21102
+ }
21103
+ copyRecursive(oldPath, newPath);
21104
+ return { success: true, copied: true };
21105
+ }
21106
+ function copyRecursive(src, dest) {
21107
+ const stat = fs25.statSync(src);
21108
+ if (stat.isDirectory()) {
21109
+ fs25.mkdirSync(dest, { recursive: true });
21110
+ for (const child of fs25.readdirSync(src)) {
21111
+ copyRecursive(path23.join(src, child), path23.join(dest, child));
21112
+ }
21113
+ } else {
21114
+ fs25.copyFileSync(src, dest);
21115
+ }
21116
+ }
21117
+ function ensureShim2() {
21118
+ const shimsDir = path23.dirname(SHIM_PATH2);
21119
+ if (!fs25.existsSync(shimsDir)) {
21120
+ fs25.mkdirSync(shimsDir, { recursive: true });
21121
+ }
21122
+ const shimContent = `#!/usr/bin/env bash
21123
+ set -euo pipefail
21124
+ if command -v rudi &> /dev/null; then
21125
+ exec rudi mcp "$1"
21126
+ else
21127
+ exec npx --yes @learnrudi/cli mcp "$1"
21128
+ fi
21129
+ `;
21130
+ fs25.writeFileSync(SHIM_PATH2, shimContent, { mode: 493 });
21131
+ }
21132
+ function buildNewEntry(stackName, agentId) {
21133
+ const base = {
21134
+ command: SHIM_PATH2,
21135
+ args: [stackName]
21136
+ };
21137
+ if (agentId === "claude-desktop" || agentId === "claude-code") {
21138
+ return { type: "stdio", ...base };
21139
+ }
21140
+ return base;
21141
+ }
21142
+ function isOldEntry(entry) {
21143
+ if (!entry) return false;
21144
+ const command = entry.command || "";
21145
+ const args = entry.args || [];
21146
+ const cwd = entry.cwd || "";
21147
+ return command.includes(".prompt-stack") || args.some((a) => typeof a === "string" && a.includes(".prompt-stack")) || cwd.includes(".prompt-stack");
21148
+ }
21149
+ function migrateAgentConfig(agentConfig, installedStacks, flags) {
21150
+ const configPath = findAgentConfig(agentConfig);
21151
+ if (!configPath) return { skipped: true, reason: "Config not found" };
21152
+ let config;
21153
+ try {
21154
+ config = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
21155
+ } catch {
21156
+ return { skipped: true, reason: "Could not parse config" };
21157
+ }
21158
+ const key = agentConfig.key;
21159
+ const mcpServers = config[key] || {};
21160
+ let updated = 0;
21161
+ let removed = 0;
21162
+ const changes = [];
21163
+ for (const [name, entry] of Object.entries(mcpServers)) {
21164
+ if (isOldEntry(entry)) {
21165
+ if (installedStacks.includes(name)) {
21166
+ const newEntry = buildNewEntry(name, agentConfig.id);
21167
+ mcpServers[name] = newEntry;
21168
+ updated++;
21169
+ changes.push({ name, action: "updated" });
21170
+ } else if (flags.removeOrphans) {
21171
+ delete mcpServers[name];
21172
+ removed++;
21173
+ changes.push({ name, action: "removed (not installed)" });
21174
+ } else {
21175
+ changes.push({ name, action: "skipped (not installed in .rudi)" });
21176
+ }
21177
+ }
21178
+ }
21179
+ if (updated > 0 || removed > 0) {
21180
+ const backupPath = configPath + ".backup." + Date.now();
21181
+ fs25.copyFileSync(configPath, backupPath);
21182
+ config[key] = mcpServers;
21183
+ fs25.writeFileSync(configPath, JSON.stringify(config, null, 2));
21184
+ }
21185
+ return { updated, removed, changes };
21186
+ }
21187
+ async function cmdMigrate(args, flags) {
21188
+ const subcommand = args[0];
21189
+ if (!subcommand || subcommand === "help") {
21190
+ console.log(`
21191
+ rudi migrate - Migrate from .prompt-stack to .rudi
21192
+
21193
+ USAGE
21194
+ rudi migrate status Show what needs to be migrated
21195
+ rudi migrate stacks Copy stacks from .prompt-stack to .rudi
21196
+ rudi migrate configs Update agent configs to use new shim
21197
+ rudi migrate all Do everything
21198
+
21199
+ OPTIONS
21200
+ --remove-orphans Remove entries for stacks not installed in .rudi
21201
+ --dry-run Show what would be done without making changes
21202
+ `);
21203
+ return;
21204
+ }
21205
+ if (subcommand === "status") {
21206
+ await migrateStatus();
21207
+ return;
21208
+ }
21209
+ if (subcommand === "stacks") {
21210
+ await migrateStacks(flags);
21211
+ return;
21212
+ }
21213
+ if (subcommand === "configs") {
21214
+ await migrateConfigs(flags);
21215
+ return;
21216
+ }
21217
+ if (subcommand === "all") {
21218
+ await migrateStacks(flags);
21219
+ console.log("");
21220
+ await migrateConfigs(flags);
21221
+ return;
21222
+ }
21223
+ console.error(`Unknown subcommand: ${subcommand}`);
21224
+ console.error("Run: rudi migrate help");
21225
+ }
21226
+ async function migrateStatus() {
21227
+ console.log("\n=== Migration Status ===\n");
21228
+ const oldStacks = getOldStacks();
21229
+ console.log(`Old .prompt-stack stacks: ${oldStacks.length}`);
21230
+ if (oldStacks.length > 0) {
21231
+ for (const name of oldStacks) {
21232
+ const existsInRudi = fs25.existsSync(path23.join(PATHS.stacks, name));
21233
+ const status = existsInRudi ? "\u2713 (already in .rudi)" : "\u25CB (needs migration)";
21234
+ console.log(` ${status} ${name}`);
21235
+ }
21236
+ }
21237
+ const newStacksDir = PATHS.stacks;
21238
+ let newStacks = [];
21239
+ if (fs25.existsSync(newStacksDir)) {
21240
+ newStacks = fs25.readdirSync(newStacksDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
21241
+ }
21242
+ console.log(`
21243
+ New .rudi stacks: ${newStacks.length}`);
21244
+ if (newStacks.length > 0) {
21245
+ for (const name of newStacks) {
21246
+ console.log(` \u2713 ${name}`);
21247
+ }
21248
+ }
21249
+ console.log("\n=== Agent Configs ===\n");
21250
+ for (const agentConfig of AGENT_CONFIGS2) {
21251
+ const configPath = findAgentConfig(agentConfig);
21252
+ if (!configPath) continue;
21253
+ let config;
21254
+ try {
21255
+ config = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
21256
+ } catch {
21257
+ continue;
21258
+ }
21259
+ const mcpServers = config[agentConfig.key] || {};
21260
+ const entries = Object.entries(mcpServers);
21261
+ const oldEntries = entries.filter(([_, e]) => isOldEntry(e));
21262
+ const newEntries = entries.filter(([_, e]) => !isOldEntry(e));
21263
+ if (entries.length === 0) continue;
21264
+ console.log(`${agentConfig.name}:`);
21265
+ console.log(` Config: ${configPath}`);
21266
+ console.log(` Old entries: ${oldEntries.length}`);
21267
+ console.log(` New entries: ${newEntries.length}`);
21268
+ if (oldEntries.length > 0) {
21269
+ console.log(" Needs update:");
21270
+ for (const [name] of oldEntries) {
21271
+ const installed = newStacks.includes(name);
21272
+ const status = installed ? "(ready)" : "(not in .rudi)";
21273
+ console.log(` - ${name} ${status}`);
21274
+ }
21275
+ }
21276
+ console.log("");
21277
+ }
21278
+ console.log("Run: rudi migrate all");
21279
+ }
21280
+ async function migrateStacks(flags) {
21281
+ console.log("=== Migrating Stacks ===\n");
21282
+ const oldStacks = getOldStacks();
21283
+ if (oldStacks.length === 0) {
21284
+ console.log("No stacks found in .prompt-stack");
21285
+ return;
21286
+ }
21287
+ console.log(`Found ${oldStacks.length} stack(s) in .prompt-stack
21288
+ `);
21289
+ for (const name of oldStacks) {
21290
+ if (flags.dryRun) {
21291
+ const exists = fs25.existsSync(path23.join(PATHS.stacks, name));
21292
+ console.log(` [dry-run] ${name}: ${exists ? "would skip (exists)" : "would copy"}`);
21293
+ } else {
21294
+ const result = copyStack(name);
21295
+ if (result.skipped) {
21296
+ console.log(` \u25CB ${name}: skipped (${result.reason})`);
21297
+ } else if (result.copied) {
21298
+ console.log(` \u2713 ${name}: copied to .rudi/stacks/`);
21299
+ } else {
21300
+ console.log(` \u2717 ${name}: ${result.error}`);
21301
+ }
21302
+ }
21303
+ }
21304
+ if (!flags.dryRun) {
21305
+ console.log(`
21306
+ Stacks migrated to: ${PATHS.stacks}`);
21307
+ }
21308
+ }
21309
+ async function migrateConfigs(flags) {
21310
+ console.log("=== Updating Agent Configs ===\n");
21311
+ if (!flags.dryRun) {
21312
+ ensureShim2();
21313
+ console.log(`Shim ready: ${SHIM_PATH2}
21314
+ `);
21315
+ }
21316
+ let installedStacks = [];
21317
+ if (fs25.existsSync(PATHS.stacks)) {
21318
+ installedStacks = fs25.readdirSync(PATHS.stacks, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
21319
+ }
21320
+ for (const agentConfig of AGENT_CONFIGS2) {
21321
+ const configPath = findAgentConfig(agentConfig);
21322
+ if (!configPath) continue;
21323
+ if (flags.dryRun) {
21324
+ console.log(`${agentConfig.name}:`);
21325
+ console.log(` [dry-run] Would update entries using .prompt-stack paths`);
21326
+ continue;
21327
+ }
21328
+ const result = migrateAgentConfig(agentConfig, installedStacks, flags);
21329
+ if (result.skipped) {
21330
+ continue;
21331
+ }
21332
+ console.log(`${agentConfig.name}:`);
21333
+ console.log(` Config: ${configPath}`);
21334
+ if (result.changes && result.changes.length > 0) {
21335
+ for (const change of result.changes) {
21336
+ console.log(` ${change.action}: ${change.name}`);
21337
+ }
21338
+ }
21339
+ if (result.updated > 0 || result.removed > 0) {
21340
+ console.log(` Updated: ${result.updated}, Removed: ${result.removed}`);
21341
+ } else {
21342
+ console.log(` No changes needed`);
21343
+ }
21344
+ console.log("");
21345
+ }
21346
+ console.log("Restart your agents to use the updated configs.");
21347
+ }
21348
+
19987
21349
  // src/index.js
19988
21350
  var VERSION = "2.0.0";
19989
21351
  async function main() {
@@ -20034,6 +21396,11 @@ async function main() {
20034
21396
  case "check":
20035
21397
  await cmdDoctor(args, flags);
20036
21398
  break;
21399
+ case "init":
21400
+ case "bootstrap":
21401
+ case "setup":
21402
+ await cmdInit(args, flags);
21403
+ break;
20037
21404
  case "update":
20038
21405
  case "upgrade":
20039
21406
  await cmdUpdate(args, flags);
@@ -20051,6 +21418,37 @@ async function main() {
20051
21418
  case "login":
20052
21419
  await cmdAuth(args, flags);
20053
21420
  break;
21421
+ case "mcp":
21422
+ await cmdMcp(args, flags);
21423
+ break;
21424
+ case "integrate":
21425
+ await cmdIntegrate(args, flags);
21426
+ break;
21427
+ case "migrate":
21428
+ await cmdMigrate(args, flags);
21429
+ break;
21430
+ case "home":
21431
+ case "status":
21432
+ await cmdHome(args, flags);
21433
+ break;
21434
+ // Shortcuts for listing specific package types
21435
+ case "stacks":
21436
+ await cmdList(["stacks"], flags);
21437
+ break;
21438
+ case "prompts":
21439
+ await cmdList(["prompts"], flags);
21440
+ break;
21441
+ case "runtimes":
21442
+ await cmdList(["runtimes"], flags);
21443
+ break;
21444
+ case "binaries":
21445
+ case "bins":
21446
+ case "tools":
21447
+ await cmdList(["binaries"], flags);
21448
+ break;
21449
+ case "agents":
21450
+ await cmdList(["agents"], flags);
21451
+ break;
20054
21452
  case "help":
20055
21453
  printHelp(args[0]);
20056
21454
  break;