@learnrudi/cli 1.5.0 → 1.7.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.
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, path18) {
785
- const ctrl = callVisitor(key, node, visitor, path18);
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, path18, ctrl);
788
- return visit_(key, ctrl, visitor, path18);
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
- path18 = Object.freeze(path18.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, path18);
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
- path18 = Object.freeze(path18.concat(node));
806
- const ck = visit_("key", node.key, visitor, path18);
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, path18);
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, path18) {
833
- const ctrl = await callVisitor(key, node, visitor, path18);
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, path18, ctrl);
836
- return visitAsync_(key, ctrl, visitor, path18);
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
- path18 = Object.freeze(path18.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, path18);
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
- path18 = Object.freeze(path18.concat(node));
854
- const ck = await visitAsync_("key", node.key, visitor, path18);
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, path18);
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, path18) {
923
+ function callVisitor(key, node, visitor, path24) {
887
924
  if (typeof visitor === "function")
888
- return visitor(key, node, path18);
925
+ return visitor(key, node, path24);
889
926
  if (identity.isMap(node))
890
- return visitor.Map?.(key, node, path18);
927
+ return visitor.Map?.(key, node, path24);
891
928
  if (identity.isSeq(node))
892
- return visitor.Seq?.(key, node, path18);
929
+ return visitor.Seq?.(key, node, path24);
893
930
  if (identity.isPair(node))
894
- return visitor.Pair?.(key, node, path18);
931
+ return visitor.Pair?.(key, node, path24);
895
932
  if (identity.isScalar(node))
896
- return visitor.Scalar?.(key, node, path18);
933
+ return visitor.Scalar?.(key, node, path24);
897
934
  if (identity.isAlias(node))
898
- return visitor.Alias?.(key, node, path18);
935
+ return visitor.Alias?.(key, node, path24);
899
936
  return void 0;
900
937
  }
901
- function replaceNode(key, path18, node) {
902
- const parent = path18[path18.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, path18, value) {
1547
+ function collectionFromPath(schema, path24, value) {
1511
1548
  let v = value;
1512
- for (let i = path18.length - 1; i >= 0; --i) {
1513
- const k = path18[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 = (path18) => path18 == null || typeof path18 === "object" && !!path18[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(path18, value) {
1563
- if (isEmptyPath(path18))
1599
+ addIn(path24, value) {
1600
+ if (isEmptyPath(path24))
1564
1601
  this.add(value);
1565
1602
  else {
1566
- const [key, ...rest] = path18;
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(path18) {
1581
- const [key, ...rest] = path18;
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(path18, keepScalar) {
1596
- const [key, ...rest] = path18;
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(path18) {
1615
- const [key, ...rest] = path18;
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(path18, value) {
1626
- const [key, ...rest] = path18;
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(path18, value) {
4167
+ addIn(path24, value) {
4131
4168
  if (assertCollection(this.contents))
4132
- this.contents.addIn(path18, 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(path18) {
4208
- if (Collection.isEmptyPath(path18)) {
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(path18) : 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(path18, keepScalar) {
4230
- if (Collection.isEmptyPath(path18))
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(path18, 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(path18) {
4244
- if (Collection.isEmptyPath(path18))
4280
+ hasIn(path24) {
4281
+ if (Collection.isEmptyPath(path24))
4245
4282
  return this.contents !== void 0;
4246
- return identity.isCollection(this.contents) ? this.contents.hasIn(path18) : 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(path18, value) {
4264
- if (Collection.isEmptyPath(path18)) {
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(path18), value);
4304
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path24), value);
4268
4305
  } else if (assertCollection(this.contents)) {
4269
- this.contents.setIn(path18, 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, path18) => {
6258
+ visit.itemAtPath = (cst, path24) => {
6222
6259
  let item = cst;
6223
- for (const [field, index] of path18) {
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, path18) => {
6233
- const parent = visit.itemAtPath(cst, path18.slice(0, -1));
6234
- const field = path18[path18.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(path18, item, visitor) {
6241
- let ctrl = visitor(item, path18);
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(path18.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, path18);
6296
+ ctrl = ctrl(item, path24);
6260
6297
  }
6261
6298
  }
6262
- return typeof ctrl === "function" ? ctrl(item, path18) : 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 fs20 = this.flowScalar(this.type);
7584
+ const fs26 = this.flowScalar(this.type);
7548
7585
  if (atNextItem || it.value) {
7549
- map.items.push({ start, key: fs20, sep: [] });
7586
+ map.items.push({ start, key: fs26, sep: [] });
7550
7587
  this.onKeyLine = true;
7551
7588
  } else if (it.sep) {
7552
- this.stack.push(fs20);
7589
+ this.stack.push(fs26);
7553
7590
  } else {
7554
- Object.assign(it, { key: fs20, 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 fs20 = this.flowScalar(this.type);
7719
+ const fs26 = this.flowScalar(this.type);
7683
7720
  if (!it || it.value)
7684
- fc.items.push({ start: [], key: fs20, sep: [] });
7721
+ fc.items.push({ start: [], key: fs26, sep: [] });
7685
7722
  else if (it.sep)
7686
- this.stack.push(fs20);
7723
+ this.stack.push(fs26);
7687
7724
  else
7688
- Object.assign(it, { key: fs20, 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(path18) {
11190
- let input = path18;
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 [path18, query] = wsComponent.resourceName.split("?");
11390
- wsComponent.path = path18 && path18 !== "/" ? path18 : 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, fs20, 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, fs20[f]);
14785
+ ajv2.addFormat(f, fs26[f]);
14749
14786
  }
14750
14787
  module2.exports = exports2 = formatsPlugin;
14751
14788
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -14819,6 +14856,9 @@ rudi - RUDI CLI
14819
14856
  USAGE
14820
14857
  rudi <command> [options]
14821
14858
 
14859
+ SETUP
14860
+ init Bootstrap RUDI (download runtimes, create shims)
14861
+
14822
14862
  INTROSPECTION
14823
14863
  home Show ~/.rudi structure and installed packages
14824
14864
  stacks List installed stacks
@@ -14941,12 +14981,15 @@ ARGUMENTS
14941
14981
 
14942
14982
  OPTIONS
14943
14983
  --json Output as JSON
14984
+ --detected Show MCP servers from agent configs (stacks only)
14985
+ --category=X Filter prompts by category
14944
14986
 
14945
14987
  EXAMPLES
14946
14988
  rudi list
14947
14989
  rudi list stacks
14990
+ rudi list stacks --detected Show MCP servers in Claude/Gemini/Codex
14948
14991
  rudi list binaries
14949
- rudi list agents
14992
+ rudi list prompts --category=coding
14950
14993
  `,
14951
14994
  secrets: `
14952
14995
  rudi secrets - Manage secrets
@@ -15016,6 +15059,33 @@ EXAMPLES
15016
15059
  rudi import sessions claude Import only Claude sessions
15017
15060
  rudi import sessions --dry-run Preview without importing
15018
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
15019
15089
  `,
15020
15090
  home: `
15021
15091
  rudi home - Show ~/.rudi structure and status
@@ -15317,7 +15387,7 @@ async function installSinglePackage(pkg, options = {}) {
15317
15387
  onProgress?.({ phase: "downloading", package: pkg.id });
15318
15388
  if (pkg.npmPackage) {
15319
15389
  try {
15320
- const { execSync: execSync5 } = await import("child_process");
15390
+ const { execSync: execSync7 } = await import("child_process");
15321
15391
  if (!import_fs4.default.existsSync(installPath)) {
15322
15392
  import_fs4.default.mkdirSync(installPath, { recursive: true });
15323
15393
  }
@@ -15325,16 +15395,16 @@ async function installSinglePackage(pkg, options = {}) {
15325
15395
  const resourcesPath = process.env.RESOURCES_PATH;
15326
15396
  const npmCmd = resourcesPath ? import_path4.default.join(resourcesPath, "bundled-runtimes", "node", "bin", "npm") : "npm";
15327
15397
  if (!import_fs4.default.existsSync(import_path4.default.join(installPath, "package.json"))) {
15328
- execSync5(`"${npmCmd}" init -y`, { cwd: installPath, stdio: "pipe" });
15398
+ execSync7(`"${npmCmd}" init -y`, { cwd: installPath, stdio: "pipe" });
15329
15399
  }
15330
- execSync5(`"${npmCmd}" install ${pkg.npmPackage}`, { cwd: installPath, stdio: "pipe" });
15400
+ execSync7(`"${npmCmd}" install ${pkg.npmPackage}`, { cwd: installPath, stdio: "pipe" });
15331
15401
  if (pkg.postInstall) {
15332
15402
  onProgress?.({ phase: "postInstall", package: pkg.id, message: pkg.postInstall });
15333
15403
  const postInstallCmd = pkg.postInstall.replace(
15334
15404
  /^npx\s+(\S+)/,
15335
15405
  `"${import_path4.default.join(installPath, "node_modules", ".bin", "$1")}"`
15336
15406
  );
15337
- execSync5(postInstallCmd, { cwd: installPath, stdio: "pipe" });
15407
+ execSync7(postInstallCmd, { cwd: installPath, stdio: "pipe" });
15338
15408
  }
15339
15409
  import_fs4.default.writeFileSync(
15340
15410
  import_path4.default.join(installPath, "manifest.json"),
@@ -15356,15 +15426,15 @@ async function installSinglePackage(pkg, options = {}) {
15356
15426
  }
15357
15427
  if (pkg.pipPackage) {
15358
15428
  try {
15359
- const { execSync: execSync5 } = await import("child_process");
15429
+ const { execSync: execSync7 } = await import("child_process");
15360
15430
  if (!import_fs4.default.existsSync(installPath)) {
15361
15431
  import_fs4.default.mkdirSync(installPath, { recursive: true });
15362
15432
  }
15363
15433
  onProgress?.({ phase: "installing", package: pkg.id, message: `pip install ${pkg.pipPackage}` });
15364
15434
  const pythonPath = import_path4.default.join(PATHS.runtimes, "python", "bin", "python3");
15365
15435
  const pythonCmd = import_fs4.default.existsSync(pythonPath) ? pythonPath : "python3";
15366
- execSync5(`"${pythonCmd}" -m venv "${installPath}/venv"`, { stdio: "pipe" });
15367
- 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" });
15368
15438
  import_fs4.default.writeFileSync(
15369
15439
  import_path4.default.join(installPath, "manifest.json"),
15370
15440
  JSON.stringify({
@@ -15586,7 +15656,7 @@ async function listInstalled(kind) {
15586
15656
  return packages;
15587
15657
  }
15588
15658
  async function installStackDependencies(stackPath, onProgress) {
15589
- const { execSync: execSync5 } = await import("child_process");
15659
+ const { execSync: execSync7 } = await import("child_process");
15590
15660
  const nodePath = import_path4.default.join(stackPath, "node");
15591
15661
  if (import_fs4.default.existsSync(nodePath)) {
15592
15662
  const packageJsonPath = import_path4.default.join(nodePath, "package.json");
@@ -15594,7 +15664,7 @@ async function installStackDependencies(stackPath, onProgress) {
15594
15664
  onProgress?.({ phase: "installing-deps", message: "Installing Node.js dependencies..." });
15595
15665
  try {
15596
15666
  const npmCmd = await findNpmExecutable();
15597
- execSync5(`"${npmCmd}" install`, { cwd: nodePath, stdio: "pipe" });
15667
+ execSync7(`"${npmCmd}" install`, { cwd: nodePath, stdio: "pipe" });
15598
15668
  } catch (error) {
15599
15669
  console.warn(`Warning: Failed to install Node.js dependencies: ${error.message}`);
15600
15670
  }
@@ -15607,8 +15677,8 @@ async function installStackDependencies(stackPath, onProgress) {
15607
15677
  onProgress?.({ phase: "installing-deps", message: "Installing Python dependencies..." });
15608
15678
  try {
15609
15679
  const pythonCmd = await findPythonExecutable();
15610
- execSync5(`"${pythonCmd}" -m venv venv`, { cwd: pythonPath, stdio: "pipe" });
15611
- 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" });
15612
15682
  } catch (error) {
15613
15683
  console.warn(`Warning: Failed to install Python dependencies: ${error.message}`);
15614
15684
  }
@@ -15960,407 +16030,319 @@ Total: ${totalCount} package(s) available`);
15960
16030
  }
15961
16031
 
15962
16032
  // src/commands/install.js
15963
- var fs7 = __toESM(require("fs/promises"), 1);
15964
- 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");
15965
16036
 
15966
- // src/utils/mcp-registry.js
15967
- var fs6 = __toESM(require("fs/promises"), 1);
16037
+ // src/commands/secrets-store.js
16038
+ var fs6 = __toESM(require("fs"), 1);
15968
16039
  var path6 = __toESM(require("path"), 1);
15969
- var os2 = __toESM(require("os"), 1);
15970
- var HOME = os2.homedir();
15971
- var AGENT_CONFIGS = {
15972
- claude: path6.join(HOME, ".claude", "settings.json"),
15973
- codex: path6.join(HOME, ".codex", "config.toml"),
15974
- gemini: path6.join(HOME, ".gemini", "settings.json")
15975
- };
15976
- 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();
15977
16058
  try {
15978
- const content = await fs6.readFile(filePath, "utf-8");
16059
+ const content = fs6.readFileSync(SECRETS_FILE, "utf-8");
15979
16060
  return JSON.parse(content);
15980
16061
  } catch {
15981
16062
  return {};
15982
16063
  }
15983
16064
  }
15984
- async function writeJson(filePath, data) {
15985
- const dir = path6.dirname(filePath);
15986
- await fs6.mkdir(dir, { recursive: true });
15987
- 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
+ });
15988
16071
  }
15989
- function parseTomlValue(value) {
15990
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
15991
- return value.slice(1, -1);
15992
- }
15993
- if (value.startsWith("[") && value.endsWith("]")) {
15994
- const inner = value.slice(1, -1).trim();
15995
- if (!inner) return [];
15996
- const items = [];
15997
- let current = "";
15998
- let inQuote = false;
15999
- let quoteChar = "";
16000
- for (const char of inner) {
16001
- if ((char === '"' || char === "'") && !inQuote) {
16002
- inQuote = true;
16003
- quoteChar = char;
16004
- } else if (char === quoteChar && inQuote) {
16005
- inQuote = false;
16006
- items.push(current);
16007
- current = "";
16008
- } else if (char === "," && !inQuote) {
16009
- } else if (inQuote) {
16010
- current += char;
16011
- }
16012
- }
16013
- return items;
16014
- }
16015
- if (value === "true") return true;
16016
- if (value === "false") return false;
16017
- const num = Number(value);
16018
- if (!isNaN(num)) return num;
16019
- return value;
16072
+ async function getSecret(name) {
16073
+ const secrets = loadSecrets();
16074
+ return secrets[name] || null;
16020
16075
  }
16021
- function parseToml(content) {
16022
- const result = {};
16023
- const lines = content.split("\n");
16024
- let currentTable = [];
16025
- for (const line of lines) {
16026
- const trimmed = line.trim();
16027
- if (!trimmed || trimmed.startsWith("#")) continue;
16028
- const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
16029
- if (tableMatch) {
16030
- currentTable = tableMatch[1].split(".");
16031
- let obj = result;
16032
- for (const key of currentTable) {
16033
- obj[key] = obj[key] || {};
16034
- obj = obj[key];
16035
- }
16036
- continue;
16037
- }
16038
- const kvMatch = trimmed.match(/^([^=]+)=(.*)$/);
16039
- if (kvMatch) {
16040
- const key = kvMatch[1].trim();
16041
- let value = kvMatch[2].trim();
16042
- const parsed = parseTomlValue(value);
16043
- let obj = result;
16044
- for (const tableKey of currentTable) {
16045
- obj = obj[tableKey];
16046
- }
16047
- 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)";
16048
16106
  }
16049
16107
  }
16050
- return result;
16108
+ return masked;
16051
16109
  }
16052
- function tomlValue(value) {
16053
- if (typeof value === "string") {
16054
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
16055
- }
16056
- if (typeof value === "boolean") {
16057
- return value ? "true" : "false";
16058
- }
16059
- if (typeof value === "number") {
16060
- return String(value);
16061
- }
16062
- if (Array.isArray(value)) {
16063
- const items = value.map((v) => tomlValue(v));
16064
- return `[${items.join(", ")}]`;
16065
- }
16066
- return String(value);
16110
+ function getStorageInfo() {
16111
+ return {
16112
+ backend: "file",
16113
+ file: SECRETS_FILE,
16114
+ permissions: "0600 (owner read/write only)"
16115
+ };
16067
16116
  }
16068
- function stringifyToml(config, prefix = "") {
16069
- const lines = [];
16070
- for (const [key, value] of Object.entries(config)) {
16071
- if (typeof value !== "object" || Array.isArray(value)) {
16072
- 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"]
16073
16132
  }
16074
- }
16075
- for (const [key, value] of Object.entries(config)) {
16076
- if (typeof value === "object" && !Array.isArray(value)) {
16077
- const tablePath = prefix ? `${prefix}.${key}` : key;
16078
- const hasSimpleValues = Object.values(value).some(
16079
- (v) => typeof v !== "object" || Array.isArray(v)
16080
- );
16081
- if (hasSimpleValues) {
16082
- lines.push("");
16083
- lines.push(`[${tablePath}]`);
16084
- }
16085
- const nested = stringifyToml(value, tablePath);
16086
- if (nested.trim()) {
16087
- lines.push(nested);
16088
- }
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;
16089
16236
  }
16090
16237
  }
16091
- return lines.join("\n");
16238
+ return null;
16092
16239
  }
16093
- async function readToml(filePath) {
16240
+ function readAgentMcpServers(agentConfig) {
16241
+ const configPath = findAgentConfig(agentConfig);
16242
+ if (!configPath) return [];
16094
16243
  try {
16095
- const content = await fs6.readFile(filePath, "utf-8");
16096
- return parseToml(content);
16097
- } catch {
16098
- 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 [];
16099
16262
  }
16100
16263
  }
16101
- async function writeToml(filePath, data) {
16102
- const dir = path6.dirname(filePath);
16103
- await fs6.mkdir(dir, { recursive: true });
16104
- await fs6.writeFile(filePath, stringifyToml(data), "utf-8");
16105
- }
16106
- function parseEnvFile(content) {
16107
- const env = {};
16108
- const lines = content.split("\n");
16109
- for (const line of lines) {
16110
- const trimmed = line.trim();
16111
- if (!trimmed || trimmed.startsWith("#")) continue;
16112
- const eqIndex = trimmed.indexOf("=");
16113
- if (eqIndex === -1) continue;
16114
- const key = trimmed.slice(0, eqIndex).trim();
16115
- let value = trimmed.slice(eqIndex + 1).trim();
16116
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
16117
- value = value.slice(1, -1);
16118
- }
16119
- if (value) {
16120
- env[key] = value;
16121
- }
16264
+ function detectAllMcpServers() {
16265
+ const servers = [];
16266
+ for (const agentConfig of AGENT_CONFIGS2) {
16267
+ const agentServers = readAgentMcpServers(agentConfig);
16268
+ servers.push(...agentServers);
16122
16269
  }
16123
- return env;
16270
+ return servers;
16124
16271
  }
16125
- async function readStackEnv(installPath) {
16126
- const envPath = path6.join(installPath, ".env");
16127
- try {
16128
- const content = await fs6.readFile(envPath, "utf-8");
16129
- return parseEnvFile(content);
16130
- } catch {
16131
- return {};
16132
- }
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
+ }));
16133
16278
  }
16134
- async function buildMcpConfig(stackId, installPath, manifest) {
16135
- let command;
16136
- let args = [];
16137
- const cwd = installPath;
16138
- const resolveRelativePath2 = (value) => {
16139
- if (!value || path6.isAbsolute(value)) return value;
16140
- const isPathLike = value.startsWith(".") || value.includes("/") || value.includes("\\");
16141
- return isPathLike ? path6.join(installPath, value) : value;
16142
- };
16143
- if (manifest.command) {
16144
- const cmdArray = Array.isArray(manifest.command) ? manifest.command : [manifest.command];
16145
- command = resolveRelativePath2(cmdArray[0]);
16146
- args = cmdArray.slice(1).map((arg) => resolveRelativePath2(arg));
16147
- } else if (manifest.mcp) {
16148
- command = resolveRelativePath2(manifest.mcp.command);
16149
- args = (manifest.mcp.args || []).map((arg) => resolveRelativePath2(arg));
16150
- if (manifest.mcp.entry) {
16151
- args = args.map(
16152
- (arg) => arg === manifest.mcp.entry ? path6.join(installPath, manifest.mcp.entry) : arg
16153
- );
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
+ };
16154
16291
  }
16155
- } else {
16156
- return null;
16157
16292
  }
16158
- const optimized = await optimizeEntryPoint(installPath, command, args);
16159
- if (optimized) {
16160
- command = optimized.command;
16161
- args = optimized.args;
16162
- }
16163
- const env = await readStackEnv(installPath);
16164
- const config = {
16165
- command,
16166
- cwd
16167
- };
16168
- if (args.length > 0) {
16169
- config.args = args;
16170
- }
16171
- if (Object.keys(env).length > 0) {
16172
- config.env = env;
16173
- }
16174
- return config;
16293
+ return summary;
16175
16294
  }
16176
- async function optimizeEntryPoint(installPath, command, args) {
16177
- if (command !== "npx" || !args.includes("tsx")) {
16178
- return null;
16179
- }
16180
- const tsFileIndex = args.findIndex((arg) => arg.endsWith(".ts"));
16181
- if (tsFileIndex === -1) {
16182
- return null;
16183
- }
16184
- const tsFile = args[tsFileIndex];
16185
- const jsFile = tsFile.replace("/src/", "/dist/").replace(".ts", ".js");
16186
- const jsPath = path6.isAbsolute(jsFile) ? jsFile : path6.join(installPath, jsFile);
16295
+
16296
+ // src/commands/install.js
16297
+ async function loadManifest(installPath) {
16298
+ const manifestPath = path8.join(installPath, "manifest.json");
16187
16299
  try {
16188
- await fs6.access(jsPath);
16189
- return {
16190
- command: "node",
16191
- args: [jsPath]
16192
- };
16300
+ const content = await fs8.readFile(manifestPath, "utf-8");
16301
+ return JSON.parse(content);
16193
16302
  } catch {
16194
16303
  return null;
16195
16304
  }
16196
16305
  }
16197
- async function registerMcpClaude(stackId, installPath, manifest) {
16198
- const configPath = AGENT_CONFIGS.claude;
16306
+ async function installDependencies(stackPath, manifest) {
16307
+ const runtime = manifest?.runtime || "node";
16199
16308
  try {
16200
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16201
- if (!mcpConfig) {
16202
- return { success: true, skipped: true };
16203
- }
16204
- mcpConfig.type = "stdio";
16205
- const settings = await readJson(configPath);
16206
- if (!settings.mcpServers) {
16207
- settings.mcpServers = {};
16208
- }
16209
- settings.mcpServers[stackId] = mcpConfig;
16210
- await writeJson(configPath, settings);
16211
- console.log(` Registered MCP in Claude: ${stackId}`);
16212
- return { success: true };
16213
- } catch (error) {
16214
- console.error(` Failed to register MCP in Claude: ${error.message}`);
16215
- return { success: false, error: error.message };
16216
- }
16217
- }
16218
- async function unregisterMcpClaude(stackId) {
16219
- const configPath = AGENT_CONFIGS.claude;
16220
- try {
16221
- const settings = await readJson(configPath);
16222
- if (!settings.mcpServers || !settings.mcpServers[stackId]) {
16223
- return { success: true, skipped: true };
16224
- }
16225
- delete settings.mcpServers[stackId];
16226
- await writeJson(configPath, settings);
16227
- console.log(` Unregistered MCP from Claude: ${stackId}`);
16228
- return { success: true };
16229
- } catch (error) {
16230
- console.error(` Failed to unregister MCP from Claude: ${error.message}`);
16231
- return { success: false, error: error.message };
16232
- }
16233
- }
16234
- async function registerMcpCodex(stackId, installPath, manifest) {
16235
- const configPath = AGENT_CONFIGS.codex;
16236
- try {
16237
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16238
- if (!mcpConfig) {
16239
- return { success: true, skipped: true };
16240
- }
16241
- const config = await readToml(configPath);
16242
- if (!config.mcp_servers) {
16243
- config.mcp_servers = {};
16244
- }
16245
- config.mcp_servers[stackId] = mcpConfig;
16246
- await writeToml(configPath, config);
16247
- console.log(` Registered MCP in Codex: ${stackId}`);
16248
- return { success: true };
16249
- } catch (error) {
16250
- console.error(` Failed to register MCP in Codex: ${error.message}`);
16251
- return { success: false, error: error.message };
16252
- }
16253
- }
16254
- async function unregisterMcpCodex(stackId) {
16255
- const configPath = AGENT_CONFIGS.codex;
16256
- try {
16257
- const config = await readToml(configPath);
16258
- if (!config.mcp_servers || !config.mcp_servers[stackId]) {
16259
- return { success: true, skipped: true };
16260
- }
16261
- delete config.mcp_servers[stackId];
16262
- await writeToml(configPath, config);
16263
- console.log(` Unregistered MCP from Codex: ${stackId}`);
16264
- return { success: true };
16265
- } catch (error) {
16266
- console.error(` Failed to unregister MCP from Codex: ${error.message}`);
16267
- return { success: false, error: error.message };
16268
- }
16269
- }
16270
- async function registerMcpGemini(stackId, installPath, manifest) {
16271
- const configPath = AGENT_CONFIGS.gemini;
16272
- try {
16273
- const mcpConfig = await buildMcpConfig(stackId, installPath, manifest);
16274
- if (!mcpConfig) {
16275
- return { success: true, skipped: true };
16276
- }
16277
- const settings = await readJson(configPath);
16278
- if (!settings.mcpServers) {
16279
- settings.mcpServers = {};
16280
- }
16281
- settings.mcpServers[stackId] = mcpConfig;
16282
- await writeJson(configPath, settings);
16283
- console.log(` Registered MCP in Gemini: ${stackId}`);
16284
- return { success: true };
16285
- } catch (error) {
16286
- console.error(` Failed to register MCP in Gemini: ${error.message}`);
16287
- return { success: false, error: error.message };
16288
- }
16289
- }
16290
- async function unregisterMcpGemini(stackId) {
16291
- const configPath = AGENT_CONFIGS.gemini;
16292
- try {
16293
- const settings = await readJson(configPath);
16294
- if (!settings.mcpServers || !settings.mcpServers[stackId]) {
16295
- 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 };
16296
16342
  }
16297
- delete settings.mcpServers[stackId];
16298
- await writeJson(configPath, settings);
16299
- console.log(` Unregistered MCP from Gemini: ${stackId}`);
16300
- return { success: true };
16343
+ return { installed: false, reason: `Unknown runtime: ${runtime}` };
16301
16344
  } catch (error) {
16302
- console.error(` Failed to unregister MCP from Gemini: ${error.message}`);
16303
- return { success: false, error: error.message };
16304
- }
16305
- }
16306
- async function getInstalledAgents() {
16307
- const agentsDir = path6.join(HOME, ".rudi", "agents");
16308
- const installed = [];
16309
- for (const agent of ["claude", "codex", "gemini"]) {
16310
- const agentPath = path6.join(agentsDir, agent);
16311
- try {
16312
- await fs6.access(agentPath);
16313
- installed.push(agent);
16314
- } catch {
16315
- }
16316
- }
16317
- return installed;
16318
- }
16319
- async function registerMcpAll(stackId, installPath, manifest, targetAgents = null) {
16320
- const agents = targetAgents || await getInstalledAgents();
16321
- const results = {};
16322
- for (const agent of agents) {
16323
- switch (agent) {
16324
- case "claude":
16325
- results.claude = await registerMcpClaude(stackId, installPath, manifest);
16326
- break;
16327
- case "codex":
16328
- results.codex = await registerMcpCodex(stackId, installPath, manifest);
16329
- break;
16330
- case "gemini":
16331
- results.gemini = await registerMcpGemini(stackId, installPath, manifest);
16332
- break;
16333
- }
16334
- }
16335
- return results;
16336
- }
16337
- async function unregisterMcpAll(stackId, targetAgents = null) {
16338
- const agents = targetAgents || await getInstalledAgents();
16339
- const results = {};
16340
- for (const agent of agents) {
16341
- switch (agent) {
16342
- case "claude":
16343
- results.claude = await unregisterMcpClaude(stackId);
16344
- break;
16345
- case "codex":
16346
- results.codex = await unregisterMcpCodex(stackId);
16347
- break;
16348
- case "gemini":
16349
- results.gemini = await unregisterMcpGemini(stackId);
16350
- break;
16351
- }
16352
- }
16353
- return results;
16354
- }
16355
-
16356
- // src/commands/install.js
16357
- async function loadManifest(installPath) {
16358
- const manifestPath = path7.join(installPath, "manifest.json");
16359
- try {
16360
- const content = await fs7.readFile(manifestPath, "utf-8");
16361
- return JSON.parse(content);
16362
- } catch {
16363
- return null;
16345
+ return { installed: false, error: error.message };
16364
16346
  }
16365
16347
  }
16366
16348
  function getManifestSecrets(manifest) {
@@ -16370,67 +16352,56 @@ function getSecretName(secret) {
16370
16352
  if (typeof secret === "string") return secret;
16371
16353
  return secret.name || secret.key;
16372
16354
  }
16373
- function getSecretDescription(secret) {
16374
- if (typeof secret !== "object" || !secret) return null;
16375
- return secret.description || secret.label || null;
16376
- }
16377
16355
  function getSecretLink(secret) {
16378
16356
  if (typeof secret !== "object" || !secret) return null;
16379
16357
  return secret.link || secret.helpUrl || null;
16380
16358
  }
16381
- function getSecretLabel(secret) {
16382
- if (typeof secret !== "object" || !secret) return null;
16383
- return secret.label || secret.name || secret.key || null;
16384
- }
16385
- async function createEnvFile(installPath, manifest) {
16359
+ async function checkSecrets(manifest) {
16386
16360
  const secrets = getManifestSecrets(manifest);
16387
- if (!secrets.length) return null;
16388
- const envPath = path7.join(installPath, ".env");
16389
- try {
16390
- await fs7.access(envPath);
16391
- console.log(` .env file already exists, preserving existing secrets`);
16392
- return envPath;
16393
- } catch {
16394
- }
16395
- const lines = [];
16396
- lines.push("# Environment variables for this stack");
16397
- lines.push("# Fill in your API keys below");
16398
- lines.push("");
16361
+ const found = [];
16362
+ const missing = [];
16399
16363
  for (const secret of secrets) {
16400
16364
  const key = getSecretName(secret);
16401
- const description = getSecretDescription(secret);
16402
- const helpUrl = getSecretLink(secret);
16403
- if (description) {
16404
- 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);
16405
16371
  }
16406
- if (helpUrl) {
16407
- 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
+ }
16408
16387
  }
16409
- lines.push(`${key}=`);
16410
- lines.push("");
16388
+ return keys;
16389
+ } catch {
16390
+ return [];
16411
16391
  }
16412
- await fs7.writeFile(envPath, lines.join("\n"), "utf-8");
16413
- return envPath;
16414
16392
  }
16415
16393
  async function cmdInstall(args, flags) {
16416
16394
  const pkgId = args[0];
16417
16395
  if (!pkgId) {
16418
16396
  console.error("Usage: rudi install <package>");
16419
- console.error(" rudi install <package> --agent=claude (register to Claude only)");
16420
- console.error(" rudi install <package> --agent=claude,codex (register to specific agents)");
16421
- 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");
16422
16402
  process.exit(1);
16423
16403
  }
16424
16404
  const force = flags.force || false;
16425
- let targetAgents = null;
16426
- if (flags.agent) {
16427
- const validAgents = ["claude", "codex", "gemini"];
16428
- targetAgents = flags.agent.split(",").map((a) => a.trim()).filter((a) => validAgents.includes(a));
16429
- if (targetAgents.length === 0) {
16430
- console.error(`Invalid --agent value. Valid agents: ${validAgents.join(", ")}`);
16431
- process.exit(1);
16432
- }
16433
- }
16434
16405
  console.log(`Resolving ${pkgId}...`);
16435
16406
  try {
16436
16407
  const resolved = await resolvePackage(pkgId);
@@ -16453,27 +16424,41 @@ Dependencies:`);
16453
16424
  console.log(` - ${dep.id} ${status}`);
16454
16425
  }
16455
16426
  }
16456
- if (resolved.requires?.secrets?.length > 0) {
16457
- console.log(`
16458
- Required secrets:`);
16459
- for (const secret of resolved.requires.secrets) {
16460
- const name = typeof secret === "string" ? secret : secret.name;
16461
- console.log(` - ${name}`);
16462
- }
16463
- }
16427
+ console.log(`
16428
+ Dependency check:`);
16464
16429
  const depCheck = checkAllDependencies(resolved);
16465
16430
  if (depCheck.results.length > 0) {
16466
- console.log(`
16467
- System dependencies:`);
16468
16431
  for (const line of formatDependencyResults(depCheck.results)) {
16469
16432
  console.log(line);
16470
16433
  }
16471
- if (!depCheck.satisfied && !force) {
16472
- console.error(`
16473
- \u2717 Missing required dependencies. Install them first or use --force to skip.`);
16474
- 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
+ }
16475
16450
  }
16476
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}`);
16457
+ }
16458
+ console.error(`
16459
+ Or use --force to install anyway.`);
16460
+ process.exit(1);
16461
+ }
16477
16462
  console.log(`
16478
16463
  Installing...`);
16479
16464
  const result = await installPackage(pkgId, {
@@ -16498,29 +16483,70 @@ Installing...`);
16498
16483
  if (resolved.kind === "stack") {
16499
16484
  const manifest = await loadManifest(result.path);
16500
16485
  if (manifest) {
16501
- const stackId = resolved.id.replace(/^stack:/, "");
16502
- const envPath = await createEnvFile(result.path, manifest);
16503
- await registerMcpAll(stackId, result.path, manifest, targetAgents);
16504
- const secrets = getManifestSecrets(manifest);
16505
- if (secrets.length > 0) {
16506
- console.log(`
16507
- \u26A0\uFE0F This stack requires configuration:`);
16508
- 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) {
16509
16515
  console.log(`
16510
- Required secrets:`);
16511
- for (const secret of secrets) {
16512
- const key = getSecretName(secret);
16513
- const label = getSecretLabel(secret) || key;
16514
- 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
+ }
16515
16526
  }
16516
16527
  console.log(`
16517
- After adding secrets, run: rudi run ${pkgId}`);
16518
- 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`);
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(", ")}`);
16519
16542
  }
16543
+ console.log(`
16544
+ 3. Restart your agent to use the stack`);
16545
+ return;
16520
16546
  }
16521
16547
  }
16522
16548
  console.log(`
16523
- Run with: rudi run ${pkgId}`);
16549
+ \u2713 Installed successfully.`);
16524
16550
  } else {
16525
16551
  console.error(`
16526
16552
  \u2717 Installation failed: ${result.error}`);
@@ -16536,38 +16562,28 @@ Run with: rudi run ${pkgId}`);
16536
16562
  }
16537
16563
 
16538
16564
  // ../packages/runner/src/spawn.js
16539
- var import_child_process2 = require("child_process");
16540
- var import_path7 = __toESM(require("path"), 1);
16541
- 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);
16542
16568
 
16543
16569
  // ../packages/runner/src/secrets.js
16544
- var import_fs6 = __toESM(require("fs"), 1);
16545
- var import_path6 = __toESM(require("path"), 1);
16546
- var import_os2 = __toESM(require("os"), 1);
16547
- var SECRETS_PATH = import_path6.default.join(import_os2.default.homedir(), ".rudi", "secrets.json");
16548
- function loadSecrets() {
16549
- 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)) {
16550
16576
  return {};
16551
16577
  }
16552
16578
  try {
16553
- const content = import_fs6.default.readFileSync(SECRETS_PATH, "utf-8");
16579
+ const content = import_fs7.default.readFileSync(SECRETS_PATH, "utf-8");
16554
16580
  return JSON.parse(content);
16555
16581
  } catch {
16556
16582
  return {};
16557
16583
  }
16558
16584
  }
16559
- function saveSecrets(secrets) {
16560
- const dir = import_path6.default.dirname(SECRETS_PATH);
16561
- if (!import_fs6.default.existsSync(dir)) {
16562
- import_fs6.default.mkdirSync(dir, { recursive: true });
16563
- }
16564
- import_fs6.default.writeFileSync(SECRETS_PATH, JSON.stringify(secrets, null, 2), {
16565
- mode: 384
16566
- // Read/write only for owner
16567
- });
16568
- }
16569
16585
  async function getSecrets(required) {
16570
- const allSecrets = loadSecrets();
16586
+ const allSecrets = loadSecrets2();
16571
16587
  const result = {};
16572
16588
  for (const req of required) {
16573
16589
  const name = typeof req === "string" ? req : req.name;
@@ -16580,8 +16596,8 @@ async function getSecrets(required) {
16580
16596
  }
16581
16597
  return result;
16582
16598
  }
16583
- function checkSecrets(required) {
16584
- const allSecrets = loadSecrets();
16599
+ function checkSecrets2(required) {
16600
+ const allSecrets = loadSecrets2();
16585
16601
  const missing = [];
16586
16602
  for (const req of required) {
16587
16603
  const name = typeof req === "string" ? req : req.name;
@@ -16595,34 +16611,12 @@ function checkSecrets(required) {
16595
16611
  missing
16596
16612
  };
16597
16613
  }
16598
- function setSecret(name, value) {
16599
- const secrets = loadSecrets();
16600
- secrets[name] = value;
16601
- saveSecrets(secrets);
16602
- }
16603
- function removeSecret(name) {
16604
- const secrets = loadSecrets();
16605
- delete secrets[name];
16606
- saveSecrets(secrets);
16607
- }
16608
16614
  function listSecretNames() {
16609
- const secrets = loadSecrets();
16615
+ const secrets = loadSecrets2();
16610
16616
  return Object.keys(secrets);
16611
16617
  }
16612
- function getMaskedSecrets() {
16613
- const secrets = loadSecrets();
16614
- const masked = {};
16615
- for (const [name, value] of Object.entries(secrets)) {
16616
- if (typeof value === "string" && value.length > 8) {
16617
- masked[name] = value.slice(0, 4) + "..." + value.slice(-4);
16618
- } else {
16619
- masked[name] = "***";
16620
- }
16621
- }
16622
- return masked;
16623
- }
16624
16618
  function redactSecrets(text, secrets) {
16625
- const allSecrets = secrets || loadSecrets();
16619
+ const allSecrets = secrets || loadSecrets2();
16626
16620
  let result = text;
16627
16621
  for (const value of Object.values(allSecrets)) {
16628
16622
  if (typeof value === "string" && value.length > 3) {
@@ -16632,27 +16626,18 @@ function redactSecrets(text, secrets) {
16632
16626
  }
16633
16627
  return result;
16634
16628
  }
16635
- function exportToEnv() {
16636
- const secrets = loadSecrets();
16637
- const lines = [];
16638
- for (const [name, value] of Object.entries(secrets)) {
16639
- const escaped = String(value).replace(/'/g, "'\\''");
16640
- lines.push(`export ${name}='${escaped}'`);
16641
- }
16642
- return lines.join("\n");
16643
- }
16644
16629
 
16645
16630
  // ../packages/runner/src/spawn.js
16646
16631
  async function runStack(id, options = {}) {
16647
16632
  const { inputs = {}, cwd, env = {}, onStdout, onStderr, onExit, signal } = options;
16648
16633
  const startTime = Date.now();
16649
16634
  const packagePath = getPackagePath(id);
16650
- const manifestPath = import_path7.default.join(packagePath, "manifest.json");
16651
- const { default: fs20 } = await import("fs");
16652
- if (!fs20.existsSync(manifestPath)) {
16635
+ const manifestPath = import_path8.default.join(packagePath, "manifest.json");
16636
+ const { default: fs26 } = await import("fs");
16637
+ if (!fs26.existsSync(manifestPath)) {
16653
16638
  throw new Error(`Stack manifest not found: ${id}`);
16654
16639
  }
16655
- const manifest = JSON.parse(fs20.readFileSync(manifestPath, "utf-8"));
16640
+ const manifest = JSON.parse(fs26.readFileSync(manifestPath, "utf-8"));
16656
16641
  const { command, args } = resolveCommandFromManifest(manifest, packagePath);
16657
16642
  const secrets = await getSecrets(manifest.requires?.secrets || []);
16658
16643
  const runEnv = {
@@ -16663,7 +16648,7 @@ async function runStack(id, options = {}) {
16663
16648
  RUDI_PACKAGE_ID: id,
16664
16649
  RUDI_PACKAGE_PATH: packagePath
16665
16650
  };
16666
- const proc = (0, import_child_process2.spawn)(command, args, {
16651
+ const proc = (0, import_child_process3.spawn)(command, args, {
16667
16652
  cwd: cwd || packagePath,
16668
16653
  env: runEnv,
16669
16654
  stdio: ["pipe", "pipe", "pipe"],
@@ -16708,15 +16693,15 @@ async function runStack(id, options = {}) {
16708
16693
  }
16709
16694
  function getCommand(runtime) {
16710
16695
  const runtimeName = runtime.replace("runtime:", "");
16711
- const runtimePath = import_path7.default.join(PATHS.runtimes, runtimeName);
16696
+ const runtimePath = import_path8.default.join(PATHS.runtimes, runtimeName);
16712
16697
  const binaryPaths = [
16713
- import_path7.default.join(runtimePath, "bin", runtimeName === "python" ? "python3" : runtimeName),
16714
- import_path7.default.join(runtimePath, "bin", runtimeName),
16715
- import_path7.default.join(runtimePath, runtimeName === "python" ? "python3" : runtimeName),
16716
- 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)
16717
16702
  ];
16718
16703
  for (const binPath of binaryPaths) {
16719
- if (import_fs7.default.existsSync(binPath)) {
16704
+ if (import_fs8.default.existsSync(binPath)) {
16720
16705
  return binPath;
16721
16706
  }
16722
16707
  }
@@ -16740,7 +16725,7 @@ function resolveCommandFromManifest(manifest, packagePath) {
16740
16725
  return { command: command2, args };
16741
16726
  }
16742
16727
  const entry = manifest.entry || "index.js";
16743
- const entryPath = import_path7.default.join(packagePath, entry);
16728
+ const entryPath = import_path8.default.join(packagePath, entry);
16744
16729
  const runtime = manifest.runtime || "runtime:node";
16745
16730
  const command = getCommand(runtime);
16746
16731
  return { command, args: [entryPath] };
@@ -16749,21 +16734,21 @@ function resolveRelativePath(value, basePath) {
16749
16734
  if (typeof value !== "string" || value.startsWith("-")) {
16750
16735
  return value;
16751
16736
  }
16752
- if (import_path7.default.isAbsolute(value)) {
16737
+ if (import_path8.default.isAbsolute(value)) {
16753
16738
  return value;
16754
16739
  }
16755
16740
  if (value.includes("/") || value.startsWith(".")) {
16756
- return import_path7.default.join(basePath, value);
16741
+ return import_path8.default.join(basePath, value);
16757
16742
  }
16758
16743
  return value;
16759
16744
  }
16760
16745
 
16761
16746
  // ../packages/manifest/src/stack.js
16762
16747
  var import_yaml2 = __toESM(require_dist(), 1);
16763
- var import_fs8 = __toESM(require("fs"), 1);
16764
- var import_path8 = __toESM(require("path"), 1);
16748
+ var import_fs9 = __toESM(require("fs"), 1);
16749
+ var import_path9 = __toESM(require("path"), 1);
16765
16750
  function parseStackManifest(filePath) {
16766
- const content = import_fs8.default.readFileSync(filePath, "utf-8");
16751
+ const content = import_fs9.default.readFileSync(filePath, "utf-8");
16767
16752
  return parseStackYaml(content, filePath);
16768
16753
  }
16769
16754
  function parseStackYaml(content, source = "stack.yaml") {
@@ -16881,8 +16866,8 @@ function validateStackManifest(manifest, source) {
16881
16866
  function findStackManifest(dir) {
16882
16867
  const candidates = ["stack.yaml", "stack.yml", "manifest.yaml", "manifest.yml"];
16883
16868
  for (const filename of candidates) {
16884
- const filePath = import_path8.default.join(dir, filename);
16885
- if (import_fs8.default.existsSync(filePath)) {
16869
+ const filePath = import_path9.default.join(dir, filename);
16870
+ if (import_fs9.default.existsSync(filePath)) {
16886
16871
  return filePath;
16887
16872
  }
16888
16873
  }
@@ -17037,8 +17022,8 @@ var validatePromptInternal = ajv.compile(promptSchema);
17037
17022
  var validateRuntimeInternal = ajv.compile(runtimeSchema);
17038
17023
 
17039
17024
  // src/commands/run.js
17040
- var import_fs9 = __toESM(require("fs"), 1);
17041
- var import_path9 = __toESM(require("path"), 1);
17025
+ var import_fs10 = __toESM(require("fs"), 1);
17026
+ var import_path10 = __toESM(require("path"), 1);
17042
17027
  async function cmdRun(args, flags) {
17043
17028
  const stackId = args[0];
17044
17029
  if (!stackId) {
@@ -17059,9 +17044,9 @@ async function cmdRun(args, flags) {
17059
17044
  if (manifestPath) {
17060
17045
  manifest = parseStackManifest(manifestPath);
17061
17046
  } else {
17062
- const jsonPath = import_path9.default.join(packagePath, "manifest.json");
17063
- if (import_fs9.default.existsSync(jsonPath)) {
17064
- 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"));
17065
17050
  }
17066
17051
  }
17067
17052
  } catch (error) {
@@ -17075,7 +17060,7 @@ async function cmdRun(args, flags) {
17075
17060
  console.log(`Running: ${manifest.name || stackId}`);
17076
17061
  const requiredSecrets = manifest.requires?.secrets || [];
17077
17062
  if (requiredSecrets.length > 0) {
17078
- const { satisfied, missing } = checkSecrets(requiredSecrets);
17063
+ const { satisfied, missing } = checkSecrets2(requiredSecrets);
17079
17064
  if (!satisfied) {
17080
17065
  console.error(`
17081
17066
  Missing required secrets:`);
@@ -17152,6 +17137,66 @@ async function cmdList(args, flags) {
17152
17137
  process.exit(1);
17153
17138
  }
17154
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
+ }
17155
17200
  try {
17156
17201
  let packages = await listInstalled(kind);
17157
17202
  const categoryFilter = flags.category;
@@ -17181,68 +17226,267 @@ Install with: rudi install <package>`);
17181
17226
  if (!byCategory[cat]) byCategory[cat] = [];
17182
17227
  byCategory[cat].push(pkg);
17183
17228
  }
17184
- console.log(`
17185
- PROMPTS (${packages.length}):`);
17186
- console.log("\u2500".repeat(50));
17187
- for (const [category, prompts] of Object.entries(byCategory).sort()) {
17188
- console.log(`
17189
- ${category.toUpperCase()} (${prompts.length}):`);
17190
- for (const pkg of prompts) {
17191
- const icon = pkg.icon ? `${pkg.icon} ` : "";
17192
- console.log(` ${icon}${pkg.id || `prompt:${pkg.name}`}`);
17193
- if (pkg.description) {
17194
- console.log(` ${pkg.description}`);
17195
- }
17196
- if (pkg.tags && pkg.tags.length > 0) {
17197
- console.log(` Tags: ${pkg.tags.join(", ")}`);
17198
- }
17199
- }
17229
+ console.log(`
17230
+ PROMPTS (${packages.length}):`);
17231
+ console.log("\u2500".repeat(50));
17232
+ for (const [category, prompts] of Object.entries(byCategory).sort()) {
17233
+ console.log(`
17234
+ ${category.toUpperCase()} (${prompts.length}):`);
17235
+ for (const pkg of prompts) {
17236
+ const icon = pkg.icon ? `${pkg.icon} ` : "";
17237
+ console.log(` ${icon}${pkg.id || `prompt:${pkg.name}`}`);
17238
+ if (pkg.description) {
17239
+ console.log(` ${pkg.description}`);
17240
+ }
17241
+ if (pkg.tags && pkg.tags.length > 0) {
17242
+ console.log(` Tags: ${pkg.tags.join(", ")}`);
17243
+ }
17244
+ }
17245
+ }
17246
+ console.log(`
17247
+ Total: ${packages.length} prompt(s)`);
17248
+ console.log(`
17249
+ Filter by category: rudi list prompts --category=coding`);
17250
+ return;
17251
+ }
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);
17200
17410
  }
17201
- console.log(`
17202
- Total: ${packages.length} prompt(s)`);
17203
- console.log(`
17204
- Filter by category: rudi list prompts --category=coding`);
17205
- return;
17206
17411
  }
17207
- const grouped = {
17208
- stack: packages.filter((p) => p.kind === "stack"),
17209
- prompt: packages.filter((p) => p.kind === "prompt"),
17210
- runtime: packages.filter((p) => p.kind === "runtime"),
17211
- binary: packages.filter((p) => p.kind === "binary"),
17212
- agent: packages.filter((p) => p.kind === "agent")
17213
- };
17214
- let total = 0;
17215
- for (const [pkgKind, pkgs] of Object.entries(grouped)) {
17216
- if (pkgs.length === 0) continue;
17217
- if (kind && kind !== pkgKind) continue;
17218
- console.log(`
17219
- ${headingForKind2(pkgKind)} (${pkgs.length}):`);
17220
- console.log("\u2500".repeat(50));
17221
- for (const pkg of pkgs) {
17222
- const icon = pkg.icon ? `${pkg.icon} ` : "";
17223
- console.log(` ${icon}${pkg.id || `${pkgKind}:${pkg.name}`}`);
17224
- console.log(` Version: ${pkg.version || "unknown"}`);
17225
- if (pkg.description) {
17226
- console.log(` ${pkg.description}`);
17227
- }
17228
- if (pkg.category) {
17229
- console.log(` Category: ${pkg.category}`);
17230
- }
17231
- if (pkg.tags && pkg.tags.length > 0) {
17232
- console.log(` Tags: ${pkg.tags.join(", ")}`);
17233
- }
17234
- if (pkg.installedAt) {
17235
- console.log(` Installed: ${new Date(pkg.installedAt).toLocaleDateString()}`);
17236
- }
17237
- total++;
17238
- }
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 };
17239
17434
  }
17240
- console.log(`
17241
- Total: ${total} package(s)`);
17435
+ delete config.mcp_servers[stackId];
17436
+ await writeToml(configPath, config);
17437
+ console.log(` Unregistered MCP from Codex: ${stackId}`);
17438
+ return { success: true };
17242
17439
  } catch (error) {
17243
- console.error(`Failed to list packages: ${error.message}`);
17244
- 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);
17245
17488
  }
17489
+ return results;
17246
17490
  }
17247
17491
 
17248
17492
  // src/commands/remove.js
@@ -17381,89 +17625,136 @@ async function cmdSecrets(args, flags) {
17381
17625
  case "set":
17382
17626
  await secretsSet(args.slice(1), flags);
17383
17627
  break;
17628
+ case "get":
17629
+ await secretsGet(args.slice(1), flags);
17630
+ break;
17384
17631
  case "list":
17385
17632
  case "ls":
17386
- secretsList(flags);
17633
+ await secretsList(flags);
17387
17634
  break;
17388
17635
  case "remove":
17389
17636
  case "rm":
17390
17637
  case "delete":
17391
- secretsRemove(args.slice(1), flags);
17638
+ await secretsRemove(args.slice(1), flags);
17392
17639
  break;
17393
- case "export":
17394
- secretsExport(flags);
17640
+ case "info":
17641
+ secretsInfo();
17395
17642
  break;
17396
17643
  default:
17397
17644
  console.log(`
17398
- rudi secrets - Manage secrets
17645
+ rudi secrets - Manage secrets (stored in ${getStorageInfo().backend})
17399
17646
 
17400
17647
  COMMANDS
17401
- 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)
17402
17650
  list List configured secrets (values masked)
17403
17651
  remove <name> Remove a secret
17404
- export Export as environment variables
17652
+ info Show storage backend info
17405
17653
 
17406
17654
  EXAMPLES
17407
- rudi secrets set VERCEL_TOKEN
17655
+ rudi secrets set SLACK_BOT_TOKEN
17408
17656
  rudi secrets list
17409
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
17410
17662
  `);
17411
17663
  }
17412
17664
  }
17413
17665
  async function secretsSet(args, flags) {
17414
17666
  const name = args[0];
17667
+ const valueArg = args[1];
17415
17668
  if (!name) {
17416
- console.error("Usage: rudi secrets set <name>");
17417
- 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');
17418
17674
  process.exit(1);
17419
17675
  }
17420
17676
  if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
17421
17677
  console.error("Secret name should be UPPER_SNAKE_CASE");
17422
- console.error("Example: VERCEL_TOKEN, GITHUB_API_KEY");
17678
+ console.error("Example: SLACK_BOT_TOKEN, GITHUB_API_KEY");
17423
17679
  process.exit(1);
17424
17680
  }
17425
- const existing = loadSecrets();
17426
- if (existing[name] && !flags.force) {
17681
+ const exists = await hasSecret(name);
17682
+ if (exists && !flags.force) {
17427
17683
  console.log(`Secret ${name} already exists.`);
17428
17684
  console.log("Use --force to overwrite.");
17429
17685
  process.exit(0);
17430
17686
  }
17431
- 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
+ }
17432
17697
  if (!value) {
17433
17698
  console.error("No value provided");
17434
17699
  process.exit(1);
17435
17700
  }
17436
- setSecret(name, value);
17437
- 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
+ }
17438
17717
  }
17439
- function secretsList(flags) {
17440
- const names = listSecretNames();
17718
+ async function secretsList(flags) {
17719
+ const names = await listSecrets();
17441
17720
  if (names.length === 0) {
17442
17721
  console.log("No secrets configured.");
17443
17722
  console.log("\nSet with: rudi secrets set <name>");
17444
17723
  return;
17445
17724
  }
17446
17725
  if (flags.json) {
17447
- console.log(JSON.stringify(getMaskedSecrets(), null, 2));
17726
+ const masked2 = await getMaskedSecrets();
17727
+ console.log(JSON.stringify(masked2, null, 2));
17448
17728
  return;
17449
17729
  }
17450
- const masked = getMaskedSecrets();
17451
- 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}):`);
17452
17736
  console.log("\u2500".repeat(50));
17453
- for (const name of names.sort()) {
17454
- 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`);
17455
17748
  }
17456
- console.log(`
17457
- Total: ${names.length} secret(s)`);
17458
17749
  }
17459
- function secretsRemove(args, flags) {
17750
+ async function secretsRemove(args, flags) {
17460
17751
  const name = args[0];
17461
17752
  if (!name) {
17462
17753
  console.error("Usage: rudi secrets remove <name>");
17463
17754
  process.exit(1);
17464
17755
  }
17465
- const existing = loadSecrets();
17466
- if (!existing[name]) {
17756
+ const allNames = await listSecrets();
17757
+ if (!allNames.includes(name)) {
17467
17758
  console.error(`Secret not found: ${name}`);
17468
17759
  process.exit(1);
17469
17760
  }
@@ -17472,17 +17763,19 @@ function secretsRemove(args, flags) {
17472
17763
  console.log("Run with --force to confirm.");
17473
17764
  process.exit(0);
17474
17765
  }
17475
- removeSecret(name);
17766
+ await removeSecret(name);
17476
17767
  console.log(`\u2713 Secret ${name} removed`);
17477
17768
  }
17478
- function secretsExport(flags) {
17479
- const exports2 = exportToEnv();
17480
- if (!exports2) {
17481
- console.log("No secrets to export.");
17482
- return;
17483
- }
17484
- console.log("# Add to your shell profile:");
17485
- 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.");
17486
17779
  }
17487
17780
  function promptSecret(prompt) {
17488
17781
  return new Promise((resolve) => {
@@ -17517,13 +17810,13 @@ function promptSecret(prompt) {
17517
17810
  }
17518
17811
 
17519
17812
  // src/commands/db.js
17520
- var import_fs11 = require("fs");
17521
- var import_path11 = require("path");
17813
+ var import_fs12 = require("fs");
17814
+ var import_path12 = require("path");
17522
17815
 
17523
17816
  // ../packages/db/src/index.js
17524
17817
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
17525
- var import_path10 = __toESM(require("path"), 1);
17526
- var import_fs10 = __toESM(require("fs"), 1);
17818
+ var import_path11 = __toESM(require("path"), 1);
17819
+ var import_fs11 = __toESM(require("fs"), 1);
17527
17820
  init_src();
17528
17821
 
17529
17822
  // ../packages/db/src/schema.js
@@ -18260,9 +18553,9 @@ var DB_PATH = PATHS.dbFile;
18260
18553
  var db = null;
18261
18554
  function getDb(options = {}) {
18262
18555
  if (!db) {
18263
- const dbDir = import_path10.default.dirname(DB_PATH);
18264
- if (!import_fs10.default.existsSync(dbDir)) {
18265
- 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 });
18266
18559
  }
18267
18560
  db = new import_better_sqlite3.default(DB_PATH, {
18268
18561
  readonly: options.readonly || false
@@ -18275,7 +18568,7 @@ function getDb(options = {}) {
18275
18568
  return db;
18276
18569
  }
18277
18570
  function isDatabaseInitialized() {
18278
- if (!import_fs10.default.existsSync(DB_PATH)) {
18571
+ if (!import_fs11.default.existsSync(DB_PATH)) {
18279
18572
  return false;
18280
18573
  }
18281
18574
  try {
@@ -18295,7 +18588,7 @@ function getDbPath() {
18295
18588
  }
18296
18589
  function getDbSize() {
18297
18590
  try {
18298
- const stats = import_fs10.default.statSync(DB_PATH);
18591
+ const stats = import_fs11.default.statSync(DB_PATH);
18299
18592
  return stats.size;
18300
18593
  } catch {
18301
18594
  return null;
@@ -18556,12 +18849,12 @@ function dbBackup(args, flags) {
18556
18849
  let backupPath = args[0];
18557
18850
  if (!backupPath) {
18558
18851
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18559
- 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`);
18560
18853
  }
18561
18854
  if (backupPath.startsWith("~")) {
18562
- backupPath = (0, import_path11.join)(process.env.HOME || "", backupPath.slice(1));
18855
+ backupPath = (0, import_path12.join)(process.env.HOME || "", backupPath.slice(1));
18563
18856
  }
18564
- if ((0, import_fs11.existsSync)(backupPath) && !flags.force) {
18857
+ if ((0, import_fs12.existsSync)(backupPath) && !flags.force) {
18565
18858
  console.error(`Backup file already exists: ${backupPath}`);
18566
18859
  console.error("Use --force to overwrite.");
18567
18860
  process.exit(1);
@@ -18573,7 +18866,7 @@ function dbBackup(args, flags) {
18573
18866
  const db3 = getDb();
18574
18867
  db3.exec("VACUUM INTO ?", [backupPath]);
18575
18868
  } catch (e) {
18576
- (0, import_fs11.copyFileSync)(dbPath, backupPath);
18869
+ (0, import_fs12.copyFileSync)(dbPath, backupPath);
18577
18870
  }
18578
18871
  const size = getDbSize();
18579
18872
  console.log(` Size: ${formatBytes(size)}`);
@@ -18678,24 +18971,24 @@ function dbTables(flags) {
18678
18971
  }
18679
18972
 
18680
18973
  // src/commands/import.js
18681
- var import_fs12 = require("fs");
18682
- var import_path12 = require("path");
18683
- var import_os3 = require("os");
18974
+ var import_fs13 = require("fs");
18975
+ var import_path13 = require("path");
18976
+ var import_os4 = require("os");
18684
18977
  var import_crypto2 = require("crypto");
18685
18978
  var PROVIDERS = {
18686
18979
  claude: {
18687
18980
  name: "Claude Code",
18688
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".claude", "projects"),
18981
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".claude", "projects"),
18689
18982
  pattern: /\.jsonl$/
18690
18983
  },
18691
18984
  codex: {
18692
18985
  name: "Codex",
18693
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".codex", "sessions"),
18986
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".codex", "sessions"),
18694
18987
  pattern: /\.jsonl$/
18695
18988
  },
18696
18989
  gemini: {
18697
18990
  name: "Gemini",
18698
- baseDir: (0, import_path12.join)((0, import_os3.homedir)(), ".gemini", "sessions"),
18991
+ baseDir: (0, import_path13.join)((0, import_os4.homedir)(), ".gemini", "sessions"),
18699
18992
  pattern: /\.jsonl$/
18700
18993
  }
18701
18994
  };
@@ -18762,7 +19055,7 @@ async function importSessions(args, flags) {
18762
19055
  console.log(`
18763
19056
  \u25B6 ${provider.name}`);
18764
19057
  console.log(` Source: ${provider.baseDir}`);
18765
- if (!(0, import_fs12.existsSync)(provider.baseDir)) {
19058
+ if (!(0, import_fs13.existsSync)(provider.baseDir)) {
18766
19059
  console.log(` \u26A0 Directory not found, skipping`);
18767
19060
  continue;
18768
19061
  }
@@ -18805,14 +19098,14 @@ async function importSessions(args, flags) {
18805
19098
  const now = Date.now();
18806
19099
  const maxAgeMs = maxAgeDays ? maxAgeDays * 24 * 60 * 60 * 1e3 : null;
18807
19100
  for (const filepath of files) {
18808
- const sessionId = (0, import_path12.basename)(filepath, ".jsonl");
19101
+ const sessionId = (0, import_path13.basename)(filepath, ".jsonl");
18809
19102
  if (existingIds.has(sessionId)) {
18810
19103
  skipped.existing++;
18811
19104
  continue;
18812
19105
  }
18813
19106
  let stat;
18814
19107
  try {
18815
- stat = (0, import_fs12.statSync)(filepath);
19108
+ stat = (0, import_fs13.statSync)(filepath);
18816
19109
  } catch (e) {
18817
19110
  skipped.error++;
18818
19111
  continue;
@@ -18910,7 +19203,7 @@ function showImportStatus(flags) {
18910
19203
  }
18911
19204
  console.log("\nProvider directories:");
18912
19205
  for (const [key, provider] of Object.entries(PROVIDERS)) {
18913
- const exists = (0, import_fs12.existsSync)(provider.baseDir);
19206
+ const exists = (0, import_fs13.existsSync)(provider.baseDir);
18914
19207
  let count = 0;
18915
19208
  if (exists) {
18916
19209
  const files = findSessionFiles(provider.baseDir, provider.pattern);
@@ -18924,10 +19217,10 @@ function showImportStatus(flags) {
18924
19217
  console.log("To import: rudi import sessions [provider]");
18925
19218
  }
18926
19219
  function findSessionFiles(dir, pattern, files = []) {
18927
- if (!(0, import_fs12.existsSync)(dir)) return files;
19220
+ if (!(0, import_fs13.existsSync)(dir)) return files;
18928
19221
  try {
18929
- for (const entry of (0, import_fs12.readdirSync)(dir, { withFileTypes: true })) {
18930
- 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);
18931
19224
  if (entry.isDirectory()) {
18932
19225
  findSessionFiles(fullPath, pattern, files);
18933
19226
  } else if (pattern.test(entry.name)) {
@@ -18940,11 +19233,11 @@ function findSessionFiles(dir, pattern, files = []) {
18940
19233
  }
18941
19234
  function parseSessionFile(filepath, provider) {
18942
19235
  try {
18943
- const stat = (0, import_fs12.statSync)(filepath);
18944
- 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");
18945
19238
  const lines = content.split("\n").filter((l) => l.trim());
18946
19239
  if (lines.length === 0) return null;
18947
- const sessionId = (0, import_path12.basename)(filepath, ".jsonl");
19240
+ const sessionId = (0, import_path13.basename)(filepath, ".jsonl");
18948
19241
  const isAgent = sessionId.startsWith("agent-");
18949
19242
  let title = null;
18950
19243
  let cwd = null;
@@ -18976,11 +19269,11 @@ function parseSessionFile(filepath, provider) {
18976
19269
  title = isAgent ? "Agent Session" : "Imported Session";
18977
19270
  }
18978
19271
  if (!cwd) {
18979
- const parentDir = (0, import_path12.basename)((0, import_path12.dirname)(filepath));
19272
+ const parentDir = (0, import_path13.basename)((0, import_path13.dirname)(filepath));
18980
19273
  if (parentDir.startsWith("-")) {
18981
19274
  cwd = parentDir.replace(/-/g, "/").replace(/^\//, "/");
18982
19275
  } else {
18983
- cwd = (0, import_os3.homedir)();
19276
+ cwd = (0, import_os4.homedir)();
18984
19277
  }
18985
19278
  }
18986
19279
  return {
@@ -19000,7 +19293,7 @@ function parseSessionFile(filepath, provider) {
19000
19293
  }
19001
19294
 
19002
19295
  // src/commands/doctor.js
19003
- var import_fs13 = __toESM(require("fs"), 1);
19296
+ var import_fs14 = __toESM(require("fs"), 1);
19004
19297
  async function cmdDoctor(args, flags) {
19005
19298
  console.log("RUDI Health Check");
19006
19299
  console.log("\u2550".repeat(50));
@@ -19018,12 +19311,12 @@ async function cmdDoctor(args, flags) {
19018
19311
  { path: PATHS.cache, name: "Cache" }
19019
19312
  ];
19020
19313
  for (const dir of dirs) {
19021
- const exists = import_fs13.default.existsSync(dir.path);
19314
+ const exists = import_fs14.default.existsSync(dir.path);
19022
19315
  const status = exists ? "\u2713" : "\u2717";
19023
19316
  console.log(` ${status} ${dir.name}: ${dir.path}`);
19024
19317
  if (!exists) {
19025
19318
  issues.push(`Missing directory: ${dir.name}`);
19026
- fixes.push(() => import_fs13.default.mkdirSync(dir.path, { recursive: true }));
19319
+ fixes.push(() => import_fs14.default.mkdirSync(dir.path, { recursive: true }));
19027
19320
  }
19028
19321
  }
19029
19322
  console.log("\n\u{1F4BE} Database");
@@ -19123,8 +19416,8 @@ async function cmdDoctor(args, flags) {
19123
19416
  }
19124
19417
 
19125
19418
  // src/commands/home.js
19126
- var import_fs14 = __toESM(require("fs"), 1);
19127
- var import_path13 = __toESM(require("path"), 1);
19419
+ var import_fs15 = __toESM(require("fs"), 1);
19420
+ var import_path14 = __toESM(require("path"), 1);
19128
19421
  function formatBytes2(bytes) {
19129
19422
  if (bytes === 0) return "0 B";
19130
19423
  const k = 1024;
@@ -19133,16 +19426,16 @@ function formatBytes2(bytes) {
19133
19426
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
19134
19427
  }
19135
19428
  function getDirSize(dir) {
19136
- if (!import_fs14.default.existsSync(dir)) return 0;
19429
+ if (!import_fs15.default.existsSync(dir)) return 0;
19137
19430
  let size = 0;
19138
19431
  try {
19139
- const entries = import_fs14.default.readdirSync(dir, { withFileTypes: true });
19432
+ const entries = import_fs15.default.readdirSync(dir, { withFileTypes: true });
19140
19433
  for (const entry of entries) {
19141
- const fullPath = import_path13.default.join(dir, entry.name);
19434
+ const fullPath = import_path14.default.join(dir, entry.name);
19142
19435
  if (entry.isDirectory()) {
19143
19436
  size += getDirSize(fullPath);
19144
19437
  } else {
19145
- size += import_fs14.default.statSync(fullPath).size;
19438
+ size += import_fs15.default.statSync(fullPath).size;
19146
19439
  }
19147
19440
  }
19148
19441
  } catch {
@@ -19150,9 +19443,9 @@ function getDirSize(dir) {
19150
19443
  return size;
19151
19444
  }
19152
19445
  function countItems(dir) {
19153
- if (!import_fs14.default.existsSync(dir)) return 0;
19446
+ if (!import_fs15.default.existsSync(dir)) return 0;
19154
19447
  try {
19155
- return import_fs14.default.readdirSync(dir).filter((f) => !f.startsWith(".")).length;
19448
+ return import_fs15.default.readdirSync(dir).filter((f) => !f.startsWith(".")).length;
19156
19449
  } catch {
19157
19450
  return 0;
19158
19451
  }
@@ -19179,7 +19472,7 @@ async function cmdHome(args, flags) {
19179
19472
  for (const dir of dirs2) {
19180
19473
  data.directories[dir.key] = {
19181
19474
  path: dir.path,
19182
- exists: import_fs14.default.existsSync(dir.path),
19475
+ exists: import_fs15.default.existsSync(dir.path),
19183
19476
  items: countItems(dir.path),
19184
19477
  size: getDirSize(dir.path)
19185
19478
  };
@@ -19204,7 +19497,7 @@ async function cmdHome(args, flags) {
19204
19497
  { name: "cache", path: PATHS.cache, icon: "\u{1F4BE}", desc: "Registry cache" }
19205
19498
  ];
19206
19499
  for (const dir of dirs) {
19207
- const exists = import_fs14.default.existsSync(dir.path);
19500
+ const exists = import_fs15.default.existsSync(dir.path);
19208
19501
  const count = countItems(dir.path);
19209
19502
  const size = getDirSize(dir.path);
19210
19503
  console.log(`${dir.icon} ${dir.name}/`);
@@ -19214,52 +19507,299 @@ async function cmdHome(args, flags) {
19214
19507
  } else {
19215
19508
  console.log(` (not created)`);
19216
19509
  }
19217
- console.log();
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]}`;
19715
+ } else {
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);
19773
+ }
19218
19774
  }
19219
- console.log("\u{1F4BE} Database");
19220
- const dbPath = import_path13.default.join(PATHS.home, "rudi.db");
19221
- if (import_fs14.default.existsSync(dbPath)) {
19222
- const dbSize = getDbSize() || import_fs14.default.statSync(dbPath).size;
19223
- console.log(` ${formatBytes2(dbSize)}`);
19224
- console.log(` ${dbPath}`);
19225
- } else {
19226
- console.log(` Not initialized`);
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
+ }
19227
19782
  }
19228
- console.log();
19229
- console.log("\u2550".repeat(60));
19230
- console.log("Installed Packages");
19231
- console.log("\u2550".repeat(60));
19232
- const kinds = ["stack", "prompt", "runtime", "binary", "agent"];
19233
- let total = 0;
19234
- for (const kind of kinds) {
19235
- const packages = getInstalledPackages(kind);
19236
- const label = kind === "binary" ? "Binaries" : `${kind.charAt(0).toUpperCase() + kind.slice(1)}s`;
19237
- console.log(` ${label.padEnd(12)} ${packages.length}`);
19238
- if (packages.length > 0 && flags.verbose) {
19239
- for (const pkg of packages.slice(0, 3)) {
19240
- console.log(` - ${pkg.name || pkg.id}`);
19241
- }
19242
- if (packages.length > 3) {
19243
- console.log(` ... and ${packages.length - 3} more`);
19244
- }
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)");
19245
19788
  }
19246
- total += packages.length;
19247
19789
  }
19248
- console.log("\u2500".repeat(30));
19249
- console.log(` ${"Total".padEnd(12)} ${total}`);
19250
- console.log("\n\u{1F4CB} Quick Commands");
19251
- console.log("\u2500".repeat(30));
19252
- console.log(" rudi list stacks Show installed stacks");
19253
- console.log(" rudi list runtimes Show installed runtimes");
19254
- console.log(" rudi list binaries Show installed binaries");
19255
- console.log(" rudi doctor --all Check system dependencies");
19256
- console.log(" rudi db stats Database statistics");
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);
19257
19797
  }
19258
19798
 
19259
19799
  // src/commands/update.js
19260
- var import_fs15 = __toESM(require("fs"), 1);
19261
- var import_path14 = __toESM(require("path"), 1);
19262
- 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");
19263
19803
  init_src();
19264
19804
  init_src2();
19265
19805
  async function cmdUpdate(args, flags) {
@@ -19284,7 +19824,7 @@ async function cmdUpdate(args, flags) {
19284
19824
  async function updatePackage(pkgId, flags) {
19285
19825
  const [kind, name] = parsePackageId(pkgId);
19286
19826
  const installPath = getPackagePath(pkgId);
19287
- if (!import_fs15.default.existsSync(installPath)) {
19827
+ if (!import_fs18.default.existsSync(installPath)) {
19288
19828
  return { success: false, error: "Package not installed" };
19289
19829
  }
19290
19830
  const pkg = await getPackage(pkgId);
@@ -19294,7 +19834,7 @@ async function updatePackage(pkgId, flags) {
19294
19834
  console.log(`Updating ${pkgId}...`);
19295
19835
  if (pkg.npmPackage) {
19296
19836
  try {
19297
- (0, import_child_process3.execSync)(`npm install ${pkg.npmPackage}@latest`, {
19837
+ (0, import_child_process5.execSync)(`npm install ${pkg.npmPackage}@latest`, {
19298
19838
  cwd: installPath,
19299
19839
  stdio: flags.verbose ? "inherit" : "pipe"
19300
19840
  });
@@ -19307,11 +19847,11 @@ async function updatePackage(pkgId, flags) {
19307
19847
  }
19308
19848
  if (pkg.pipPackage) {
19309
19849
  try {
19310
- const venvPip = import_path14.default.join(installPath, "venv", "bin", "pip");
19311
- (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}`, {
19312
19852
  stdio: flags.verbose ? "inherit" : "pipe"
19313
19853
  });
19314
- 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`, {
19315
19855
  encoding: "utf-8"
19316
19856
  });
19317
19857
  const version = versionOutput.split(":")[1]?.trim();
@@ -19323,9 +19863,9 @@ async function updatePackage(pkgId, flags) {
19323
19863
  }
19324
19864
  if (kind === "runtime" && !pkg.npmPackage && !pkg.pipPackage) {
19325
19865
  try {
19326
- const { downloadRuntime: downloadRuntime2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
19327
- import_fs15.default.rmSync(installPath, { recursive: true, force: true });
19328
- 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, {
19329
19869
  onProgress: (p) => {
19330
19870
  if (flags.verbose) console.log(` ${p.phase}...`);
19331
19871
  }
@@ -19344,8 +19884,8 @@ async function updateAll(flags) {
19344
19884
  let failed = 0;
19345
19885
  for (const kind of kinds) {
19346
19886
  const dir = kind === "runtime" ? PATHS.runtimes : kind === "stack" ? PATHS.stacks : PATHS.prompts;
19347
- if (!import_fs15.default.existsSync(dir)) continue;
19348
- const entries = import_fs15.default.readdirSync(dir, { withFileTypes: true });
19887
+ if (!import_fs18.default.existsSync(dir)) continue;
19888
+ const entries = import_fs18.default.readdirSync(dir, { withFileTypes: true });
19349
19889
  for (const entry of entries) {
19350
19890
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19351
19891
  const pkgId = `${kind}:${entry.name}`;
@@ -19364,14 +19904,14 @@ Updated ${updated} package(s)${failed > 0 ? `, ${failed} failed` : ""}`);
19364
19904
  }
19365
19905
  function getInstalledVersion(installPath, npmPackage) {
19366
19906
  try {
19367
- const pkgJsonPath = import_path14.default.join(installPath, "node_modules", npmPackage.replace("@", "").split("/")[0], "package.json");
19368
- if (import_fs15.default.existsSync(pkgJsonPath)) {
19369
- const pkgJson = JSON.parse(import_fs15.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"));
19370
19910
  return pkgJson.version;
19371
19911
  }
19372
- const rootPkgPath = import_path14.default.join(installPath, "package.json");
19373
- if (import_fs15.default.existsSync(rootPkgPath)) {
19374
- const rootPkg = JSON.parse(import_fs15.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"));
19375
19915
  const dep = rootPkg.dependencies?.[npmPackage];
19376
19916
  if (dep) return dep.replace(/[\^~]/, "");
19377
19917
  }
@@ -19380,30 +19920,30 @@ function getInstalledVersion(installPath, npmPackage) {
19380
19920
  return null;
19381
19921
  }
19382
19922
  function updateRuntimeMetadata(installPath, updates) {
19383
- const metaPath = import_path14.default.join(installPath, "runtime.json");
19923
+ const metaPath = import_path16.default.join(installPath, "runtime.json");
19384
19924
  try {
19385
19925
  let meta = {};
19386
- if (import_fs15.default.existsSync(metaPath)) {
19387
- meta = JSON.parse(import_fs15.default.readFileSync(metaPath, "utf-8"));
19926
+ if (import_fs18.default.existsSync(metaPath)) {
19927
+ meta = JSON.parse(import_fs18.default.readFileSync(metaPath, "utf-8"));
19388
19928
  }
19389
19929
  meta = { ...meta, ...updates };
19390
- import_fs15.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
19930
+ import_fs18.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
19391
19931
  } catch {
19392
19932
  }
19393
19933
  }
19394
19934
 
19395
19935
  // db/index.js
19396
19936
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
19397
- var import_path15 = __toESM(require("path"), 1);
19398
- var import_os4 = __toESM(require("os"), 1);
19399
- var import_fs16 = __toESM(require("fs"), 1);
19400
- var RUDI_HOME2 = import_path15.default.join(import_os4.default.homedir(), ".rudi");
19401
- var DB_PATH2 = import_path15.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");
19402
19942
  var db2 = null;
19403
19943
  function getDb2(options = {}) {
19404
19944
  if (!db2) {
19405
- if (!import_fs16.default.existsSync(RUDI_HOME2)) {
19406
- import_fs16.default.mkdirSync(RUDI_HOME2, { recursive: true });
19945
+ if (!import_fs19.default.existsSync(RUDI_HOME2)) {
19946
+ import_fs19.default.mkdirSync(RUDI_HOME2, { recursive: true });
19407
19947
  }
19408
19948
  db2 = new import_better_sqlite32.default(DB_PATH2, {
19409
19949
  readonly: options.readonly || false
@@ -19561,7 +20101,7 @@ function getBeforeCrashLogs() {
19561
20101
  }
19562
20102
 
19563
20103
  // src/commands/logs.js
19564
- var import_fs17 = __toESM(require("fs"), 1);
20104
+ var import_fs20 = __toESM(require("fs"), 1);
19565
20105
  function parseTimeAgo(str) {
19566
20106
  const match = str.match(/^(\d+)([smhd])$/);
19567
20107
  if (!match) return null;
@@ -19661,7 +20201,7 @@ function exportLogs(logs, filepath, format) {
19661
20201
  });
19662
20202
  content = JSON.stringify(formatted, null, 2);
19663
20203
  }
19664
- import_fs17.default.writeFileSync(filepath, content, "utf-8");
20204
+ import_fs20.default.writeFileSync(filepath, content, "utf-8");
19665
20205
  return filepath;
19666
20206
  }
19667
20207
  function printStats(stats) {
@@ -19787,9 +20327,9 @@ async function handleLogsCommand(args, flags) {
19787
20327
  }
19788
20328
 
19789
20329
  // src/commands/which.js
19790
- var fs18 = __toESM(require("fs/promises"), 1);
19791
- var path16 = __toESM(require("path"), 1);
19792
- 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");
19793
20333
  init_src();
19794
20334
  async function cmdWhich(args, flags) {
19795
20335
  const stackId = args[0];
@@ -19857,7 +20397,7 @@ Installed stacks:`);
19857
20397
  if (runtimeInfo.entry) {
19858
20398
  console.log("");
19859
20399
  console.log("Run MCP server directly:");
19860
- const entryPath = path16.join(stackPath, runtimeInfo.entry);
20400
+ const entryPath = path19.join(stackPath, runtimeInfo.entry);
19861
20401
  if (runtimeInfo.runtime === "node") {
19862
20402
  console.log(` echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node ${entryPath}`);
19863
20403
  } else if (runtimeInfo.runtime === "python") {
@@ -19876,27 +20416,27 @@ Installed stacks:`);
19876
20416
  async function detectRuntime(stackPath) {
19877
20417
  const runtimes = ["node", "python"];
19878
20418
  for (const runtime of runtimes) {
19879
- const runtimePath = path16.join(stackPath, runtime);
20419
+ const runtimePath = path19.join(stackPath, runtime);
19880
20420
  try {
19881
- await fs18.access(runtimePath);
20421
+ await fs21.access(runtimePath);
19882
20422
  if (runtime === "node") {
19883
- const distEntry = path16.join(runtimePath, "dist", "index.js");
19884
- const srcEntry = path16.join(runtimePath, "src", "index.ts");
20423
+ const distEntry = path19.join(runtimePath, "dist", "index.js");
20424
+ const srcEntry = path19.join(runtimePath, "src", "index.ts");
19885
20425
  try {
19886
- await fs18.access(distEntry);
20426
+ await fs21.access(distEntry);
19887
20427
  return { runtime: "node", entry: `${runtime}/dist/index.js` };
19888
20428
  } catch {
19889
20429
  try {
19890
- await fs18.access(srcEntry);
20430
+ await fs21.access(srcEntry);
19891
20431
  return { runtime: "node", entry: `${runtime}/src/index.ts` };
19892
20432
  } catch {
19893
20433
  return { runtime: "node", entry: null };
19894
20434
  }
19895
20435
  }
19896
20436
  } else if (runtime === "python") {
19897
- const entry = path16.join(runtimePath, "src", "index.py");
20437
+ const entry = path19.join(runtimePath, "src", "index.py");
19898
20438
  try {
19899
- await fs18.access(entry);
20439
+ await fs21.access(entry);
19900
20440
  return { runtime: "python", entry: `${runtime}/src/index.py` };
19901
20441
  } catch {
19902
20442
  return { runtime: "python", entry: null };
@@ -19912,21 +20452,21 @@ async function checkAuth(stackPath, runtime) {
19912
20452
  const authFiles = [];
19913
20453
  let configured = false;
19914
20454
  if (runtime === "node" || runtime === "python") {
19915
- const runtimePath = path16.join(stackPath, runtime);
19916
- const tokenPath = path16.join(runtimePath, "token.json");
20455
+ const runtimePath = path19.join(stackPath, runtime);
20456
+ const tokenPath = path19.join(runtimePath, "token.json");
19917
20457
  try {
19918
- await fs18.access(tokenPath);
20458
+ await fs21.access(tokenPath);
19919
20459
  authFiles.push(`${runtime}/token.json`);
19920
20460
  configured = true;
19921
20461
  } catch {
19922
- const accountsPath = path16.join(runtimePath, "accounts");
20462
+ const accountsPath = path19.join(runtimePath, "accounts");
19923
20463
  try {
19924
- const accounts = await fs18.readdir(accountsPath);
20464
+ const accounts = await fs21.readdir(accountsPath);
19925
20465
  for (const account of accounts) {
19926
20466
  if (account.startsWith(".")) continue;
19927
- const accountTokenPath = path16.join(accountsPath, account, "token.json");
20467
+ const accountTokenPath = path19.join(accountsPath, account, "token.json");
19928
20468
  try {
19929
- await fs18.access(accountTokenPath);
20469
+ await fs21.access(accountTokenPath);
19930
20470
  authFiles.push(`${runtime}/accounts/${account}/token.json`);
19931
20471
  configured = true;
19932
20472
  } catch {
@@ -19936,9 +20476,9 @@ async function checkAuth(stackPath, runtime) {
19936
20476
  }
19937
20477
  }
19938
20478
  }
19939
- const envPath = path16.join(stackPath, ".env");
20479
+ const envPath = path19.join(stackPath, ".env");
19940
20480
  try {
19941
- const envContent = await fs18.readFile(envPath, "utf-8");
20481
+ const envContent = await fs21.readFile(envPath, "utf-8");
19942
20482
  const hasValues = envContent.split("\n").some((line) => {
19943
20483
  const trimmed = line.trim();
19944
20484
  if (!trimmed || trimmed.startsWith("#")) return false;
@@ -19967,7 +20507,7 @@ async function checkAuth(stackPath, runtime) {
19967
20507
  }
19968
20508
  function checkIfRunning(stackName) {
19969
20509
  try {
19970
- 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`, {
19971
20511
  encoding: "utf-8",
19972
20512
  stdio: ["pipe", "pipe", "ignore"]
19973
20513
  // Suppress stderr
@@ -19982,9 +20522,9 @@ function checkIfRunning(stackName) {
19982
20522
  }
19983
20523
 
19984
20524
  // src/commands/auth.js
19985
- var fs19 = __toESM(require("fs/promises"), 1);
19986
- var path17 = __toESM(require("path"), 1);
19987
- 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");
19988
20528
  init_src();
19989
20529
  var net = __toESM(require("net"), 1);
19990
20530
  async function findAvailablePort(basePort = 3456) {
@@ -20015,26 +20555,26 @@ function isPortAvailable(port) {
20015
20555
  async function detectRuntime2(stackPath) {
20016
20556
  const runtimes = ["node", "python"];
20017
20557
  for (const runtime of runtimes) {
20018
- const runtimePath = path17.join(stackPath, runtime);
20558
+ const runtimePath = path20.join(stackPath, runtime);
20019
20559
  try {
20020
- await fs19.access(runtimePath);
20560
+ await fs22.access(runtimePath);
20021
20561
  if (runtime === "node") {
20022
- const authTs = path17.join(runtimePath, "src", "auth.ts");
20023
- const authJs = path17.join(runtimePath, "dist", "auth.js");
20562
+ const authTs = path20.join(runtimePath, "src", "auth.ts");
20563
+ const authJs = path20.join(runtimePath, "dist", "auth.js");
20024
20564
  try {
20025
- await fs19.access(authTs);
20565
+ await fs22.access(authTs);
20026
20566
  return { runtime: "node", authScript: authTs, useTsx: true };
20027
20567
  } catch {
20028
20568
  try {
20029
- await fs19.access(authJs);
20569
+ await fs22.access(authJs);
20030
20570
  return { runtime: "node", authScript: authJs, useTsx: false };
20031
20571
  } catch {
20032
20572
  }
20033
20573
  }
20034
20574
  } else if (runtime === "python") {
20035
- const authPy = path17.join(runtimePath, "src", "auth.py");
20575
+ const authPy = path20.join(runtimePath, "src", "auth.py");
20036
20576
  try {
20037
- await fs19.access(authPy);
20577
+ await fs22.access(authPy);
20038
20578
  return { runtime: "python", authScript: authPy, useTsx: false };
20039
20579
  } catch {
20040
20580
  }
@@ -20084,14 +20624,14 @@ Installed stacks:`);
20084
20624
  console.log(`Using port: ${port}`);
20085
20625
  console.log("");
20086
20626
  let cmd;
20087
- const cwd = path17.dirname(authInfo.authScript);
20627
+ const cwd = path20.dirname(authInfo.authScript);
20088
20628
  if (authInfo.runtime === "node") {
20089
- const distAuth = path17.join(cwd, "..", "dist", "auth.js");
20629
+ const distAuth = path20.join(cwd, "..", "dist", "auth.js");
20090
20630
  let useBuiltInPort = false;
20091
20631
  let tempAuthScript = null;
20092
20632
  try {
20093
- await fs19.access(distAuth);
20094
- const distContent = await fs19.readFile(distAuth, "utf-8");
20633
+ await fs22.access(distAuth);
20634
+ const distContent = await fs22.readFile(distAuth, "utf-8");
20095
20635
  if (distContent.includes("findAvailablePort")) {
20096
20636
  console.log("Using compiled authentication script...");
20097
20637
  cmd = `node ${distAuth}${accountEmail ? ` ${accountEmail}` : ""}`;
@@ -20100,11 +20640,11 @@ Installed stacks:`);
20100
20640
  } catch {
20101
20641
  }
20102
20642
  if (!useBuiltInPort) {
20103
- const authContent = await fs19.readFile(authInfo.authScript, "utf-8");
20643
+ const authContent = await fs22.readFile(authInfo.authScript, "utf-8");
20104
20644
  const tempExt = authInfo.useTsx ? ".ts" : ".mjs";
20105
- tempAuthScript = path17.join(cwd, "..", `auth-temp${tempExt}`);
20645
+ tempAuthScript = path20.join(cwd, "..", `auth-temp${tempExt}`);
20106
20646
  const modifiedContent = authContent.replace(/localhost:3456/g, `localhost:${port}`).replace(/server\.listen\(3456/g, `server.listen(${port}`);
20107
- await fs19.writeFile(tempAuthScript, modifiedContent);
20647
+ await fs22.writeFile(tempAuthScript, modifiedContent);
20108
20648
  if (authInfo.useTsx) {
20109
20649
  cmd = `npx tsx ${tempAuthScript}${accountEmail ? ` ${accountEmail}` : ""}`;
20110
20650
  } else {
@@ -20114,17 +20654,17 @@ Installed stacks:`);
20114
20654
  console.log("Starting OAuth flow...");
20115
20655
  console.log("");
20116
20656
  try {
20117
- (0, import_child_process5.execSync)(cmd, {
20657
+ (0, import_child_process7.execSync)(cmd, {
20118
20658
  cwd,
20119
20659
  stdio: "inherit"
20120
20660
  });
20121
20661
  if (tempAuthScript) {
20122
- await fs19.unlink(tempAuthScript);
20662
+ await fs22.unlink(tempAuthScript);
20123
20663
  }
20124
20664
  } catch (error) {
20125
20665
  if (tempAuthScript) {
20126
20666
  try {
20127
- await fs19.unlink(tempAuthScript);
20667
+ await fs22.unlink(tempAuthScript);
20128
20668
  } catch {
20129
20669
  }
20130
20670
  }
@@ -20134,7 +20674,7 @@ Installed stacks:`);
20134
20674
  cmd = `python3 ${authInfo.authScript}${accountEmail ? ` ${accountEmail}` : ""}`;
20135
20675
  console.log("Starting OAuth flow...");
20136
20676
  console.log("");
20137
- (0, import_child_process5.execSync)(cmd, {
20677
+ (0, import_child_process7.execSync)(cmd, {
20138
20678
  cwd,
20139
20679
  stdio: "inherit",
20140
20680
  env: {
@@ -20155,6 +20695,657 @@ Installed stacks:`);
20155
20695
  }
20156
20696
  }
20157
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
+
20158
21349
  // src/index.js
20159
21350
  var VERSION = "2.0.0";
20160
21351
  async function main() {
@@ -20205,6 +21396,11 @@ async function main() {
20205
21396
  case "check":
20206
21397
  await cmdDoctor(args, flags);
20207
21398
  break;
21399
+ case "init":
21400
+ case "bootstrap":
21401
+ case "setup":
21402
+ await cmdInit(args, flags);
21403
+ break;
20208
21404
  case "update":
20209
21405
  case "upgrade":
20210
21406
  await cmdUpdate(args, flags);
@@ -20222,6 +21418,15 @@ async function main() {
20222
21418
  case "login":
20223
21419
  await cmdAuth(args, flags);
20224
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;
20225
21430
  case "home":
20226
21431
  case "status":
20227
21432
  await cmdHome(args, flags);