heyspex 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +2 -1
  2. package/README.md +11 -0
  3. package/bin/hs-vault-binding.mjs +157 -0
  4. package/bin/hs.mjs +83 -79
  5. package/bin/spexcode.mjs +91 -0
  6. package/dev-factory/engine/worktree.mjs +36 -7
  7. package/dev-factory/lib/epic-detect.mjs +2 -2
  8. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  9. package/dev-factory/lib/fleet-state.mjs +2 -2
  10. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  11. package/dist/cli/hs-check.mjs +76 -46
  12. package/dist/cli/hs-claim.mjs +63 -33
  13. package/dist/cli/hs-connect.mjs +520 -214
  14. package/dist/cli/hs-defer.mjs +161 -19
  15. package/dist/cli/hs-dev.mjs +11 -5
  16. package/dist/cli/hs-env.mjs +187 -161
  17. package/dist/cli/hs-index-rebuild.mjs +123 -78
  18. package/dist/cli/hs-index-reconcile.mjs +203 -107
  19. package/dist/cli/hs-init.mjs +426 -179
  20. package/dist/cli/hs-install.mjs +759 -437
  21. package/dist/cli/hs-join.mjs +590 -383
  22. package/dist/cli/hs-vault-pull.mjs +359 -254
  23. package/dist/cli/hs-vault-push.mjs +151 -113
  24. package/dist/cli/hs-vault.mjs +271 -104
  25. package/dist/cli/hs-workspace.mjs +90 -57
  26. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  27. package/docs/copilot-plugin-install.md +200 -177
  28. package/docs/driving-heyspex-from-copilot.md +174 -165
  29. package/docs/mcp-client-config.md +93 -25
  30. package/package.json +257 -250
  31. package/plugins/heyspex/.claude-plugin/plugin.json +13 -12
  32. package/plugins/heyspex/.cursor-plugin/plugin.json +16 -15
  33. package/plugins/heyspex/.mcp.json +3 -4
  34. package/plugins/heyspex/README.md +323 -312
  35. package/plugins/heyspex/assets/heyspex-glyph-128.png +0 -0
  36. package/plugins/heyspex/assets/heyspex-glyph-256.png +0 -0
  37. package/plugins/heyspex/assets/heyspex-glyph.svg +12 -0
  38. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  39. package/plugins/heyspex/mcp.json +12 -13
  40. package/scripts/check-mcp-freshness.mjs +6 -4
  41. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  42. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  43. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  44. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  45. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  46. package/scripts/claude-statusline/statusline-command.mjs +1073 -0
  47. package/scripts/copilot-statusline.mjs +130 -0
  48. package/scripts/dev-server-status.mjs +3 -2
  49. package/scripts/dev-server-stop.mjs +3 -2
  50. package/scripts/hs-capture-cli.mjs +2 -2
  51. package/scripts/hs-settings.mjs +2 -2
  52. package/scripts/hs-view.mjs +3 -2
  53. package/scripts/install-copilot-hooks.mjs +152 -36
  54. package/scripts/lib/copilot-cli.mjs +108 -0
  55. package/scripts/lib/customer-kit.mjs +223 -30
  56. package/scripts/lib/dev-mode.mjs +28 -2
  57. package/scripts/lib/shared-path-helpers.mjs +32 -0
  58. package/scripts/lib/standalone-doctor.mjs +131 -32
  59. package/scripts/wip-ledger-health.mjs +3 -2
  60. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  61. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  62. package/spexharness/src/intent-directive.mjs +839 -626
  63. package/spexharness/src/rule-table.mjs +1 -1
  64. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  65. package/vendor/heyspex-mcp/cli.mjs +20 -4
  66. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  67. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  68. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  69. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  70. package/vendor/heyspex-mcp/http-transport.mjs +6 -1
  71. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  72. package/vendor/heyspex-mcp/server-identity.mjs +13 -0
  73. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  74. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  75. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  76. package/vendor/heyspex-mcp/tool-dispatch.mjs +63 -23
  77. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  78. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  79. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -158,9 +158,9 @@ function resolveBearer({ envToken, credentialsText, accountId, legacyAccountId }
158
158
  if (entry?.mcp_token) return { token: entry.mcp_token, source: "credentials-file" };
159
159
  return { token: null, source: null };
160
160
  }
161
- function readTextOrNull(read2, path9) {
161
+ function readTextOrNull(read2, path11) {
162
162
  try {
163
- const raw = read2(path9);
163
+ const raw = read2(path11);
164
164
  return typeof raw === "string" ? raw : null;
165
165
  } catch {
166
166
  return null;
@@ -264,6 +264,11 @@ function readLocalDevModeRecord({ homeDir = heyspexHome2(), readFile: readFile3
264
264
  return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
265
265
  }
266
266
  }
267
+ function overrideCloudCheck(source, cloudKnown, staleAgainstCloud) {
268
+ if (source !== "local-override") return "not-applicable";
269
+ if (!cloudKnown) return "unverified";
270
+ return staleAgainstCloud ? "stale" : "in-sync";
271
+ }
267
272
  function resolveDevMode({
268
273
  env = process.env,
269
274
  homeDir = heyspexHome2(),
@@ -303,6 +308,7 @@ function resolveDevMode({
303
308
  const semantics = deriveSemantics(mode, reliance);
304
309
  const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
305
310
  const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
311
+ const cloudCheck = overrideCloudCheck(source, cloudKnown, staleAgainstCloud);
306
312
  return {
307
313
  mode,
308
314
  reliance: semantics.reliance,
@@ -320,7 +326,8 @@ function resolveDevMode({
320
326
  present: local.record !== null,
321
327
  record: local.record,
322
328
  parseError: local.parseError,
323
- staleAgainstCloud
329
+ staleAgainstCloud,
330
+ cloudCheck
324
331
  },
325
332
  cloudSetting: { known: cloudKnown, value: cloudValue }
326
333
  };
@@ -1072,7 +1079,7 @@ var require_main = __commonJS({
1072
1079
  "node_modules/dotenv/lib/main.js"(exports2, module2) {
1073
1080
  "use strict";
1074
1081
  var fs4 = __require("fs");
1075
- var path9 = __require("path");
1082
+ var path11 = __require("path");
1076
1083
  var os3 = __require("os");
1077
1084
  var crypto3 = __require("crypto");
1078
1085
  var packageJson = require_package();
@@ -1188,7 +1195,7 @@ var require_main = __commonJS({
1188
1195
  possibleVaultPath = options2.path.endsWith(".vault") ? options2.path : `${options2.path}.vault`;
1189
1196
  }
1190
1197
  } else {
1191
- possibleVaultPath = path9.resolve(process.cwd(), ".env.vault");
1198
+ possibleVaultPath = path11.resolve(process.cwd(), ".env.vault");
1192
1199
  }
1193
1200
  if (fs4.existsSync(possibleVaultPath)) {
1194
1201
  return possibleVaultPath;
@@ -1196,7 +1203,7 @@ var require_main = __commonJS({
1196
1203
  return null;
1197
1204
  }
1198
1205
  function _resolveHome(envPath) {
1199
- return envPath[0] === "~" ? path9.join(os3.homedir(), envPath.slice(1)) : envPath;
1206
+ return envPath[0] === "~" ? path11.join(os3.homedir(), envPath.slice(1)) : envPath;
1200
1207
  }
1201
1208
  function _configVault(options2) {
1202
1209
  const debug = Boolean(options2 && options2.debug);
@@ -1213,7 +1220,7 @@ var require_main = __commonJS({
1213
1220
  return { parsed };
1214
1221
  }
1215
1222
  function configDotenv(options2) {
1216
- const dotenvPath2 = path9.resolve(process.cwd(), ".env");
1223
+ const dotenvPath2 = path11.resolve(process.cwd(), ".env");
1217
1224
  let encoding = "utf8";
1218
1225
  const debug = Boolean(options2 && options2.debug);
1219
1226
  const quiet = options2 && "quiet" in options2 ? options2.quiet : true;
@@ -1237,13 +1244,13 @@ var require_main = __commonJS({
1237
1244
  }
1238
1245
  let lastError;
1239
1246
  const parsedAll = {};
1240
- for (const path10 of optionPaths) {
1247
+ for (const path12 of optionPaths) {
1241
1248
  try {
1242
- const parsed = DotenvModule.parse(fs4.readFileSync(path10, { encoding }));
1249
+ const parsed = DotenvModule.parse(fs4.readFileSync(path12, { encoding }));
1243
1250
  DotenvModule.populate(parsedAll, parsed, options2);
1244
1251
  } catch (e) {
1245
1252
  if (debug) {
1246
- _debug(`Failed to load ${path10} ${e.message}`);
1253
+ _debug(`Failed to load ${path12} ${e.message}`);
1247
1254
  }
1248
1255
  lastError = e;
1249
1256
  }
@@ -1258,7 +1265,7 @@ var require_main = __commonJS({
1258
1265
  const shortPaths = [];
1259
1266
  for (const filePath of optionPaths) {
1260
1267
  try {
1261
- const relative = path9.relative(process.cwd(), filePath);
1268
+ const relative = path11.relative(process.cwd(), filePath);
1262
1269
  shortPaths.push(relative);
1263
1270
  } catch (e) {
1264
1271
  if (debug) {
@@ -1435,17 +1442,17 @@ var require_visit = __commonJS({
1435
1442
  visit.BREAK = BREAK;
1436
1443
  visit.SKIP = SKIP;
1437
1444
  visit.REMOVE = REMOVE;
1438
- function visit_(key, node, visitor, path9) {
1439
- const ctrl = callVisitor(key, node, visitor, path9);
1445
+ function visit_(key, node, visitor, path11) {
1446
+ const ctrl = callVisitor(key, node, visitor, path11);
1440
1447
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
1441
- replaceNode(key, path9, ctrl);
1442
- return visit_(key, ctrl, visitor, path9);
1448
+ replaceNode(key, path11, ctrl);
1449
+ return visit_(key, ctrl, visitor, path11);
1443
1450
  }
1444
1451
  if (typeof ctrl !== "symbol") {
1445
1452
  if (identity.isCollection(node)) {
1446
- path9 = Object.freeze(path9.concat(node));
1453
+ path11 = Object.freeze(path11.concat(node));
1447
1454
  for (let i = 0; i < node.items.length; ++i) {
1448
- const ci = visit_(i, node.items[i], visitor, path9);
1455
+ const ci = visit_(i, node.items[i], visitor, path11);
1449
1456
  if (typeof ci === "number")
1450
1457
  i = ci - 1;
1451
1458
  else if (ci === BREAK)
@@ -1456,13 +1463,13 @@ var require_visit = __commonJS({
1456
1463
  }
1457
1464
  }
1458
1465
  } else if (identity.isPair(node)) {
1459
- path9 = Object.freeze(path9.concat(node));
1460
- const ck = visit_("key", node.key, visitor, path9);
1466
+ path11 = Object.freeze(path11.concat(node));
1467
+ const ck = visit_("key", node.key, visitor, path11);
1461
1468
  if (ck === BREAK)
1462
1469
  return BREAK;
1463
1470
  else if (ck === REMOVE)
1464
1471
  node.key = null;
1465
- const cv = visit_("value", node.value, visitor, path9);
1472
+ const cv = visit_("value", node.value, visitor, path11);
1466
1473
  if (cv === BREAK)
1467
1474
  return BREAK;
1468
1475
  else if (cv === REMOVE)
@@ -1483,17 +1490,17 @@ var require_visit = __commonJS({
1483
1490
  visitAsync.BREAK = BREAK;
1484
1491
  visitAsync.SKIP = SKIP;
1485
1492
  visitAsync.REMOVE = REMOVE;
1486
- async function visitAsync_(key, node, visitor, path9) {
1487
- const ctrl = await callVisitor(key, node, visitor, path9);
1493
+ async function visitAsync_(key, node, visitor, path11) {
1494
+ const ctrl = await callVisitor(key, node, visitor, path11);
1488
1495
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
1489
- replaceNode(key, path9, ctrl);
1490
- return visitAsync_(key, ctrl, visitor, path9);
1496
+ replaceNode(key, path11, ctrl);
1497
+ return visitAsync_(key, ctrl, visitor, path11);
1491
1498
  }
1492
1499
  if (typeof ctrl !== "symbol") {
1493
1500
  if (identity.isCollection(node)) {
1494
- path9 = Object.freeze(path9.concat(node));
1501
+ path11 = Object.freeze(path11.concat(node));
1495
1502
  for (let i = 0; i < node.items.length; ++i) {
1496
- const ci = await visitAsync_(i, node.items[i], visitor, path9);
1503
+ const ci = await visitAsync_(i, node.items[i], visitor, path11);
1497
1504
  if (typeof ci === "number")
1498
1505
  i = ci - 1;
1499
1506
  else if (ci === BREAK)
@@ -1504,13 +1511,13 @@ var require_visit = __commonJS({
1504
1511
  }
1505
1512
  }
1506
1513
  } else if (identity.isPair(node)) {
1507
- path9 = Object.freeze(path9.concat(node));
1508
- const ck = await visitAsync_("key", node.key, visitor, path9);
1514
+ path11 = Object.freeze(path11.concat(node));
1515
+ const ck = await visitAsync_("key", node.key, visitor, path11);
1509
1516
  if (ck === BREAK)
1510
1517
  return BREAK;
1511
1518
  else if (ck === REMOVE)
1512
1519
  node.key = null;
1513
- const cv = await visitAsync_("value", node.value, visitor, path9);
1520
+ const cv = await visitAsync_("value", node.value, visitor, path11);
1514
1521
  if (cv === BREAK)
1515
1522
  return BREAK;
1516
1523
  else if (cv === REMOVE)
@@ -1537,23 +1544,23 @@ var require_visit = __commonJS({
1537
1544
  }
1538
1545
  return visitor;
1539
1546
  }
1540
- function callVisitor(key, node, visitor, path9) {
1547
+ function callVisitor(key, node, visitor, path11) {
1541
1548
  if (typeof visitor === "function")
1542
- return visitor(key, node, path9);
1549
+ return visitor(key, node, path11);
1543
1550
  if (identity.isMap(node))
1544
- return visitor.Map?.(key, node, path9);
1551
+ return visitor.Map?.(key, node, path11);
1545
1552
  if (identity.isSeq(node))
1546
- return visitor.Seq?.(key, node, path9);
1553
+ return visitor.Seq?.(key, node, path11);
1547
1554
  if (identity.isPair(node))
1548
- return visitor.Pair?.(key, node, path9);
1555
+ return visitor.Pair?.(key, node, path11);
1549
1556
  if (identity.isScalar(node))
1550
- return visitor.Scalar?.(key, node, path9);
1557
+ return visitor.Scalar?.(key, node, path11);
1551
1558
  if (identity.isAlias(node))
1552
- return visitor.Alias?.(key, node, path9);
1559
+ return visitor.Alias?.(key, node, path11);
1553
1560
  return void 0;
1554
1561
  }
1555
- function replaceNode(key, path9, node) {
1556
- const parent = path9[path9.length - 1];
1562
+ function replaceNode(key, path11, node) {
1563
+ const parent = path11[path11.length - 1];
1557
1564
  if (identity.isCollection(parent)) {
1558
1565
  parent.items[key] = node;
1559
1566
  } else if (identity.isPair(parent)) {
@@ -2163,10 +2170,10 @@ var require_Collection = __commonJS({
2163
2170
  var createNode = require_createNode();
2164
2171
  var identity = require_identity();
2165
2172
  var Node = require_Node();
2166
- function collectionFromPath(schema, path9, value) {
2173
+ function collectionFromPath(schema, path11, value) {
2167
2174
  let v = value;
2168
- for (let i = path9.length - 1; i >= 0; --i) {
2169
- const k = path9[i];
2175
+ for (let i = path11.length - 1; i >= 0; --i) {
2176
+ const k = path11[i];
2170
2177
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
2171
2178
  const a = [];
2172
2179
  a[k] = v;
@@ -2185,7 +2192,7 @@ var require_Collection = __commonJS({
2185
2192
  sourceObjects: /* @__PURE__ */ new Map()
2186
2193
  });
2187
2194
  }
2188
- var isEmptyPath = (path9) => path9 == null || typeof path9 === "object" && !!path9[Symbol.iterator]().next().done;
2195
+ var isEmptyPath = (path11) => path11 == null || typeof path11 === "object" && !!path11[Symbol.iterator]().next().done;
2189
2196
  var Collection = class extends Node.NodeBase {
2190
2197
  constructor(type, schema) {
2191
2198
  super(type);
@@ -2215,11 +2222,11 @@ var require_Collection = __commonJS({
2215
2222
  * be a Pair instance or a `{ key, value }` object, which may not have a key
2216
2223
  * that already exists in the map.
2217
2224
  */
2218
- addIn(path9, value) {
2219
- if (isEmptyPath(path9))
2225
+ addIn(path11, value) {
2226
+ if (isEmptyPath(path11))
2220
2227
  this.add(value);
2221
2228
  else {
2222
- const [key, ...rest] = path9;
2229
+ const [key, ...rest] = path11;
2223
2230
  const node = this.get(key, true);
2224
2231
  if (identity.isCollection(node))
2225
2232
  node.addIn(rest, value);
@@ -2233,8 +2240,8 @@ var require_Collection = __commonJS({
2233
2240
  * Removes a value from the collection.
2234
2241
  * @returns `true` if the item was found and removed.
2235
2242
  */
2236
- deleteIn(path9) {
2237
- const [key, ...rest] = path9;
2243
+ deleteIn(path11) {
2244
+ const [key, ...rest] = path11;
2238
2245
  if (rest.length === 0)
2239
2246
  return this.delete(key);
2240
2247
  const node = this.get(key, true);
@@ -2248,8 +2255,8 @@ var require_Collection = __commonJS({
2248
2255
  * scalar values from their surrounding node; to disable set `keepScalar` to
2249
2256
  * `true` (collections are always returned intact).
2250
2257
  */
2251
- getIn(path9, keepScalar) {
2252
- const [key, ...rest] = path9;
2258
+ getIn(path11, keepScalar) {
2259
+ const [key, ...rest] = path11;
2253
2260
  const node = this.get(key, true);
2254
2261
  if (rest.length === 0)
2255
2262
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -2267,8 +2274,8 @@ var require_Collection = __commonJS({
2267
2274
  /**
2268
2275
  * Checks if the collection includes a value with the key `key`.
2269
2276
  */
2270
- hasIn(path9) {
2271
- const [key, ...rest] = path9;
2277
+ hasIn(path11) {
2278
+ const [key, ...rest] = path11;
2272
2279
  if (rest.length === 0)
2273
2280
  return this.has(key);
2274
2281
  const node = this.get(key, true);
@@ -2278,8 +2285,8 @@ var require_Collection = __commonJS({
2278
2285
  * Sets a value in this collection. For `!!set`, `value` needs to be a
2279
2286
  * boolean to add/remove the item from the set.
2280
2287
  */
2281
- setIn(path9, value) {
2282
- const [key, ...rest] = path9;
2288
+ setIn(path11, value) {
2289
+ const [key, ...rest] = path11;
2283
2290
  if (rest.length === 0) {
2284
2291
  this.set(key, value);
2285
2292
  } else {
@@ -4794,9 +4801,9 @@ var require_Document = __commonJS({
4794
4801
  this.contents.add(value);
4795
4802
  }
4796
4803
  /** Adds a value to the document. */
4797
- addIn(path9, value) {
4804
+ addIn(path11, value) {
4798
4805
  if (assertCollection(this.contents))
4799
- this.contents.addIn(path9, value);
4806
+ this.contents.addIn(path11, value);
4800
4807
  }
4801
4808
  /**
4802
4809
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -4871,14 +4878,14 @@ var require_Document = __commonJS({
4871
4878
  * Removes a value from the document.
4872
4879
  * @returns `true` if the item was found and removed.
4873
4880
  */
4874
- deleteIn(path9) {
4875
- if (Collection.isEmptyPath(path9)) {
4881
+ deleteIn(path11) {
4882
+ if (Collection.isEmptyPath(path11)) {
4876
4883
  if (this.contents == null)
4877
4884
  return false;
4878
4885
  this.contents = null;
4879
4886
  return true;
4880
4887
  }
4881
- return assertCollection(this.contents) ? this.contents.deleteIn(path9) : false;
4888
+ return assertCollection(this.contents) ? this.contents.deleteIn(path11) : false;
4882
4889
  }
4883
4890
  /**
4884
4891
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -4893,10 +4900,10 @@ var require_Document = __commonJS({
4893
4900
  * scalar values from their surrounding node; to disable set `keepScalar` to
4894
4901
  * `true` (collections are always returned intact).
4895
4902
  */
4896
- getIn(path9, keepScalar) {
4897
- if (Collection.isEmptyPath(path9))
4903
+ getIn(path11, keepScalar) {
4904
+ if (Collection.isEmptyPath(path11))
4898
4905
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
4899
- return identity.isCollection(this.contents) ? this.contents.getIn(path9, keepScalar) : void 0;
4906
+ return identity.isCollection(this.contents) ? this.contents.getIn(path11, keepScalar) : void 0;
4900
4907
  }
4901
4908
  /**
4902
4909
  * Checks if the document includes a value with the key `key`.
@@ -4907,10 +4914,10 @@ var require_Document = __commonJS({
4907
4914
  /**
4908
4915
  * Checks if the document includes a value at `path`.
4909
4916
  */
4910
- hasIn(path9) {
4911
- if (Collection.isEmptyPath(path9))
4917
+ hasIn(path11) {
4918
+ if (Collection.isEmptyPath(path11))
4912
4919
  return this.contents !== void 0;
4913
- return identity.isCollection(this.contents) ? this.contents.hasIn(path9) : false;
4920
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path11) : false;
4914
4921
  }
4915
4922
  /**
4916
4923
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -4927,13 +4934,13 @@ var require_Document = __commonJS({
4927
4934
  * Sets a value in this document. For `!!set`, `value` needs to be a
4928
4935
  * boolean to add/remove the item from the set.
4929
4936
  */
4930
- setIn(path9, value) {
4931
- if (Collection.isEmptyPath(path9)) {
4937
+ setIn(path11, value) {
4938
+ if (Collection.isEmptyPath(path11)) {
4932
4939
  this.contents = value;
4933
4940
  } else if (this.contents == null) {
4934
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path9), value);
4941
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path11), value);
4935
4942
  } else if (assertCollection(this.contents)) {
4936
- this.contents.setIn(path9, value);
4943
+ this.contents.setIn(path11, value);
4937
4944
  }
4938
4945
  }
4939
4946
  /**
@@ -6893,9 +6900,9 @@ var require_cst_visit = __commonJS({
6893
6900
  visit.BREAK = BREAK;
6894
6901
  visit.SKIP = SKIP;
6895
6902
  visit.REMOVE = REMOVE;
6896
- visit.itemAtPath = (cst, path9) => {
6903
+ visit.itemAtPath = (cst, path11) => {
6897
6904
  let item = cst;
6898
- for (const [field, index2] of path9) {
6905
+ for (const [field, index2] of path11) {
6899
6906
  const tok = item?.[field];
6900
6907
  if (tok && "items" in tok) {
6901
6908
  item = tok.items[index2];
@@ -6904,23 +6911,23 @@ var require_cst_visit = __commonJS({
6904
6911
  }
6905
6912
  return item;
6906
6913
  };
6907
- visit.parentCollection = (cst, path9) => {
6908
- const parent = visit.itemAtPath(cst, path9.slice(0, -1));
6909
- const field = path9[path9.length - 1][0];
6914
+ visit.parentCollection = (cst, path11) => {
6915
+ const parent = visit.itemAtPath(cst, path11.slice(0, -1));
6916
+ const field = path11[path11.length - 1][0];
6910
6917
  const coll = parent?.[field];
6911
6918
  if (coll && "items" in coll)
6912
6919
  return coll;
6913
6920
  throw new Error("Parent collection not found");
6914
6921
  };
6915
- function _visit(path9, item, visitor) {
6916
- let ctrl = visitor(item, path9);
6922
+ function _visit(path11, item, visitor) {
6923
+ let ctrl = visitor(item, path11);
6917
6924
  if (typeof ctrl === "symbol")
6918
6925
  return ctrl;
6919
6926
  for (const field of ["key", "value"]) {
6920
6927
  const token = item[field];
6921
6928
  if (token && "items" in token) {
6922
6929
  for (let i = 0; i < token.items.length; ++i) {
6923
- const ci = _visit(Object.freeze(path9.concat([[field, i]])), token.items[i], visitor);
6930
+ const ci = _visit(Object.freeze(path11.concat([[field, i]])), token.items[i], visitor);
6924
6931
  if (typeof ci === "number")
6925
6932
  i = ci - 1;
6926
6933
  else if (ci === BREAK)
@@ -6931,10 +6938,10 @@ var require_cst_visit = __commonJS({
6931
6938
  }
6932
6939
  }
6933
6940
  if (typeof ctrl === "function" && field === "key")
6934
- ctrl = ctrl(item, path9);
6941
+ ctrl = ctrl(item, path11);
6935
6942
  }
6936
6943
  }
6937
- return typeof ctrl === "function" ? ctrl(item, path9) : ctrl;
6944
+ return typeof ctrl === "function" ? ctrl(item, path11) : ctrl;
6938
6945
  }
6939
6946
  exports2.visit = visit;
6940
6947
  }
@@ -10789,11 +10796,11 @@ var require_baseGet = __commonJS({
10789
10796
  "use strict";
10790
10797
  var castPath = require_castPath();
10791
10798
  var toKey = require_toKey();
10792
- function baseGet(object, path9) {
10793
- path9 = castPath(path9, object);
10794
- var index2 = 0, length = path9.length;
10799
+ function baseGet(object, path11) {
10800
+ path11 = castPath(path11, object);
10801
+ var index2 = 0, length = path11.length;
10795
10802
  while (object != null && index2 < length) {
10796
- object = object[toKey(path9[index2++])];
10803
+ object = object[toKey(path11[index2++])];
10797
10804
  }
10798
10805
  return index2 && index2 == length ? object : void 0;
10799
10806
  }
@@ -10806,8 +10813,8 @@ var require_get = __commonJS({
10806
10813
  "node_modules/lodash/get.js"(exports2, module2) {
10807
10814
  "use strict";
10808
10815
  var baseGet = require_baseGet();
10809
- function get2(object, path9, defaultValue) {
10810
- var result = object == null ? void 0 : baseGet(object, path9);
10816
+ function get2(object, path11, defaultValue) {
10817
+ var result = object == null ? void 0 : baseGet(object, path11);
10811
10818
  return result === void 0 ? defaultValue : result;
10812
10819
  }
10813
10820
  module2.exports = get2;
@@ -22739,9 +22746,9 @@ var require_lib2 = __commonJS({
22739
22746
 
22740
22747
  // spex-console/bin/hs-join.ts
22741
22748
  import { spawn, spawnSync as spawnSync5 } from "node:child_process";
22742
- import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
22749
+ import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "node:fs";
22743
22750
  import { userInfo } from "node:os";
22744
- import { basename as basename4, dirname as dirname6, join as join16, resolve as resolve5 } from "node:path";
22751
+ import { basename as basename5, dirname as dirname6, join as join16, resolve as resolve5 } from "node:path";
22745
22752
  import process4 from "node:process";
22746
22753
  import { createInterface as createInterface2 } from "node:readline/promises";
22747
22754
 
@@ -22852,10 +22859,10 @@ function classifyHttpFailureReason(res) {
22852
22859
  if (res.status === 401) return "unauthorized";
22853
22860
  return "server-error";
22854
22861
  }
22855
- async function cloudHttpRequest(http, method, path9, body) {
22856
- const scopeHeaders = http.scope ? http.scope.headersFor(method, path9) : {};
22862
+ async function cloudHttpRequest(http, method, path11, body) {
22863
+ const scopeHeaders = http.scope ? http.scope.headersFor(method, path11) : {};
22857
22864
  try {
22858
- const res = await http.fetchImpl(`${http.consoleUrl}${path9}`, {
22865
+ const res = await http.fetchImpl(`${http.consoleUrl}${path11}`, {
22859
22866
  method,
22860
22867
  headers: {
22861
22868
  "content-type": "application/json",
@@ -26127,8 +26134,16 @@ var LAUNCHER_TEMPLATE = [
26127
26134
  "// 4. node_modules/heyspex up from $__ENV__, $CLAUDE_PROJECT_DIR, $CURSOR_PROJECT_DIR, cwd",
26128
26135
  "// 5. <npm root -g>/heyspex a global install, last resort",
26129
26136
  "// Nothing found -> one stderr line and exit 0 (fail-open, like the shim itself).",
26137
+ "//",
26138
+ "// The same resolution starts the package's other entrypoints, so no committed",
26139
+ "// file names node_modules/heyspex directly and a repo with no node_modules of",
26140
+ "// its own still works off a global install:",
26141
+ "// --mcp the stdio MCP server (.mcp.json, .cursor/mcp.json)",
26142
+ "// --statusline the Claude Code status line (statusLine.command)",
26143
+ "// --resolve print the package root, exit 0; exit 1 when none (hs doctor)",
26130
26144
  "import { spawnSync } from 'node:child_process';",
26131
- "import { existsSync } from 'node:fs';",
26145
+ "import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs';",
26146
+ "import { homedir } from 'node:os';",
26132
26147
  "import { dirname, resolve } from 'node:path';",
26133
26148
  "import { fileURLToPath, pathToFileURL } from 'node:url';",
26134
26149
  "",
@@ -26138,6 +26153,7 @@ var LAUNCHER_TEMPLATE = [
26138
26153
  "const text = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);",
26139
26154
  "const hasShim = (root) => Boolean(root) && existsSync(resolve(root, ...SHIM));",
26140
26155
  "",
26156
+ "__YIELD__",
26141
26157
  "function packageUpFrom(start) {",
26142
26158
  " let dir = resolve(start);",
26143
26159
  " for (;;) {",
@@ -26172,31 +26188,109 @@ var LAUNCHER_TEMPLATE = [
26172
26188
  " if (hasShim(global)) root = global;",
26173
26189
  "}",
26174
26190
  "",
26191
+ "const ENTRYPOINTS = {",
26192
+ " '--mcp': 'vendor/heyspex-mcp/cli.mjs',",
26193
+ " '--statusline': 'scripts/claude-statusline/statusline-command.mjs',",
26194
+ "};",
26195
+ "const mode = process.argv[2];",
26196
+ "const entry = Object.hasOwn(ENTRYPOINTS, mode) ? ENTRYPOINTS[mode] : null;",
26197
+ "",
26175
26198
  "if (!root) {",
26199
+ " const what = entry || mode === '--resolve' ? mode : 'hook ' + (mode ?? '?');",
26176
26200
  " process.stderr.write(",
26177
- " '[heyspex] hook ' + (process.argv[2] ?? '?') + ' skipped: no heyspex package found ' +",
26178
- ` '(run "npm install heyspex" in this repo, or set HEYSPEX_PLATFORM_ROOT). Looked for __SHIM__ under ' +`,
26179
- " 'HEYSPEX_PLATFORM_ROOT, HEYSPEX_MCP_ENTRY, node_modules/heyspex up from ' + starts.join(', ') + ', and npm root -g.\\n',",
26201
+ " '[heyspex] ' + what + ' skipped: no heyspex package found ' +",
26202
+ ` '(run "npm install heyspex" or "npm link heyspex" in this repo, install it globally, or set HEYSPEX_PLATFORM_ROOT). ' +`,
26203
+ " 'Looked for __SHIM__ under HEYSPEX_PLATFORM_ROOT, HEYSPEX_MCP_ENTRY, node_modules/heyspex up from ' + starts.join(', ') + ', and npm root -g.\\n',",
26180
26204
  " );",
26205
+ " // Hooks and the status line fail open; the MCP server and --resolve fail",
26206
+ " // loudly, so the client and hs doctor both see it.",
26207
+ " process.exit(mode === '--mcp' || mode === '--resolve' ? 1 : 0);",
26208
+ "}",
26209
+ "if (mode === '--resolve') {",
26210
+ " process.stdout.write(root + '\\n');",
26181
26211
  " process.exit(0);",
26182
26212
  "}",
26183
26213
  "",
26184
26214
  "// argv is already [node, <this file>, <hook>, ...args] -- the shape the shim",
26185
- '// reads; the shim path replaces this file so a shim guarded by "am I main?"',
26186
- "// (the Cursor one) runs exactly as if Cursor had invoked it directly.",
26187
- "const shim = resolve(root, ...SHIM);",
26188
- "process.argv[1] = shim;",
26215
+ '// reads; the target replaces this file so a script guarded by "am I main?"',
26216
+ "// runs exactly as if it had been invoked directly. Real path, so that guard",
26217
+ "// still matches when node_modules/heyspex is an npm link or a junction (node",
26218
+ "// realpaths import.meta.url but leaves argv[1] alone).",
26219
+ "let target = resolve(root, ...(entry ? entry.split('/') : SHIM));",
26189
26220
  "try {",
26190
- " await import(pathToFileURL(shim).href);",
26221
+ " target = realpathSync(target);",
26222
+ "} catch {",
26223
+ " // keep the resolved path",
26224
+ "}",
26225
+ "if (entry) process.argv.splice(2, 1);",
26226
+ "process.argv[1] = target;",
26227
+ "try {",
26228
+ " await import(pathToFileURL(target).href);",
26191
26229
  "} catch (err) {",
26192
- " process.stderr.write('[heyspex] hook ' + (process.argv[2] ?? '?') + ' skipped: ' + (err && err.message ? err.message : err) + '\\n');",
26230
+ " process.stderr.write('[heyspex] ' + (entry ? mode : 'hook ' + (process.argv[2] ?? '?')) + ' skipped: ' + (err && err.message ? err.message : err) + '\\n');",
26231
+ " // Hooks and the status line fail open; the MCP server fails loudly.",
26232
+ " if (mode === '--mcp') process.exit(1);",
26193
26233
  " process.exit(0);",
26194
26234
  "}",
26195
26235
  ""
26196
26236
  ].join("\n");
26237
+ var LAUNCHER_YIELD_MARKER = "// yields-to-plugin: v1";
26238
+ var LAUNCHER_YIELD_BLOCK = [
26239
+ LAUNCHER_YIELD_MARKER + " -- one source of hooks: when the heyspex plugin is enabled on this machine",
26240
+ "// its own launcher (plugins/heyspex/bin/run-hook.mjs, which never calls this file) already runs every",
26241
+ "// hook, so this committed row exits 0 silently before resolving anything and each hook fires ONCE.",
26242
+ "// HEYSPEX_REPO_HOOKS=1 never yield (the repo rows are the chosen source);",
26243
+ `// enabledPlugins["heyspex@..."] === false in ~/.claude/settings.json or this repo's .claude/`,
26244
+ "// settings[.local].json: plugin off here, the row runs;",
26245
+ "// plugin evidence (what `hs doctor` hook-double-fire reads): a heyspex@... key in",
26246
+ "// ~/.claude/plugins/installed_plugins.json, enabledPlugins naming heyspex with a non-false",
26247
+ "// value, or a heyspex directory under ~/.copilot/installed-plugins | ~/.copilot/plugins.",
26248
+ "function readJson(file) {",
26249
+ " try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return undefined; }",
26250
+ "}",
26251
+ "function listDir(dir) {",
26252
+ " try { return readdirSync(dir); } catch { return []; }",
26253
+ "}",
26254
+ "function pluginTakesThisHook() {",
26255
+ " const flag = text(env.HEYSPEX_REPO_HOOKS);",
26256
+ " if (flag && flag !== '0' && flag.toLowerCase() !== 'false') return false;",
26257
+ " const home = homedir();",
26258
+ " const repo = resolve(here, '..', '..');",
26259
+ " const isHeyspex = (k) => /^heyspex(@|$)/i.test(k);",
26260
+ " let enabled = false;",
26261
+ " for (const f of [resolve(home, '.claude', 'settings.json'), resolve(repo, '.claude', 'settings.json'), resolve(repo, '.claude', 'settings.local.json')]) {",
26262
+ " const settings = readJson(f);",
26263
+ " const ep = settings && typeof settings === 'object' ? settings.enabledPlugins : null;",
26264
+ " if (!ep || typeof ep !== 'object') continue;",
26265
+ " for (const [k, v] of Object.entries(ep)) {",
26266
+ " if (!isHeyspex(k)) continue;",
26267
+ " if (v === false) return false;",
26268
+ " enabled = true;",
26269
+ " }",
26270
+ " }",
26271
+ " if (enabled) return true;",
26272
+ " const installed = readJson(resolve(home, '.claude', 'plugins', 'installed_plugins.json'));",
26273
+ " if (installed && typeof installed === 'object' && Object.keys(installed.plugins && typeof installed.plugins === 'object' ? installed.plugins : installed).some(isHeyspex)) return true;",
26274
+ " for (const sub of ['installed-plugins', 'plugins']) {",
26275
+ " const dir = resolve(home, '.copilot', sub);",
26276
+ " const names = listDir(dir);",
26277
+ " if (names.some((n) => /heyspex/i.test(n))) return true;",
26278
+ " if (names.some((n) => listDir(resolve(dir, n)).some((m) => /heyspex/i.test(m)))) return true;",
26279
+ " }",
26280
+ " return false;",
26281
+ "}",
26282
+ "// Only a HOOK EVENT yields: --mcp / --statusline / --resolve and an agent-run",
26283
+ "// `<script> --directive <key>` fetch are not hook events the plugin could run in",
26284
+ "// our place, so they always go on to the package below.",
26285
+ "const yieldMode = process.argv[2];",
26286
+ "const notAHookEvent = ['--mcp', '--statusline', '--resolve'].includes(yieldMode) || process.argv.includes('--directive');",
26287
+ "if (!notAHookEvent && pluginTakesThisHook()) process.exit(0);",
26288
+ "",
26289
+ ""
26290
+ ].join("\n");
26197
26291
  function renderCustomerLauncher(surface = "claude") {
26198
26292
  const l = CUSTOMER_LAUNCHERS[surface];
26199
- return LAUNCHER_TEMPLATE.replace(
26293
+ return LAUNCHER_TEMPLATE.replace("__YIELD__\n", surface === "claude" ? LAUNCHER_YIELD_BLOCK : "").replace(
26200
26294
  /__(REGISTRY|ENV|LAUNCHER|SHIM)__/g,
26201
26295
  (_m, token) => token === "REGISTRY" ? l.registry : token === "ENV" ? l.projectDirEnv : token === "LAUNCHER" ? l.rel.join("/") : l.shim
26202
26296
  );
@@ -26422,25 +26516,33 @@ var MARKER_RE = /\n<!-- heyspex-kit: managed by hs install \(sha256:([0-9a-f]{16
26422
26516
  function shortHash(text2) {
26423
26517
  return createHash("sha256").update(text2, "utf8").digest("hex").slice(0, 16);
26424
26518
  }
26519
+ function normalizeEol(text2) {
26520
+ return text2 === null || text2 === void 0 ? text2 : String(text2).replace(/\r\n/g, "\n");
26521
+ }
26425
26522
  function renderKitCommand(content, platformRoot) {
26426
- const body = `${rewritePlatformPaths(content, platformRoot).replace(/\s+$/, "")}
26523
+ const body = `${rewritePlatformPaths(normalizeEol(content), platformRoot).replace(/\s+$/, "")}
26427
26524
  `;
26428
26525
  return `${body}
26429
26526
  <!-- heyspex-kit: managed by hs install (sha256:${shortHash(body)}). Edit freely: once edited, hs install leaves this file alone. -->
26430
26527
  `;
26431
26528
  }
26432
26529
  function kitFileState(content) {
26433
- const m = String(content).match(MARKER_RE);
26530
+ const text2 = normalizeEol(String(content));
26531
+ const m = text2.match(MARKER_RE);
26434
26532
  if (!m) return "unmanaged";
26435
- const body = String(content).slice(0, m.index).replace(/\n$/, "");
26436
- return shortHash(`${body}
26437
- `) === m[1] ? "managed" : "customized";
26533
+ const body = `${text2.slice(0, m.index).replace(/\n$/, "")}
26534
+ `;
26535
+ if (shortHash(body) === m[1]) return "managed";
26536
+ if (shortHash(`${body.slice(0, -1).replace(/\n/g, "\r\n")}
26537
+ `) === m[1]) return "managed";
26538
+ return "customized";
26438
26539
  }
26439
26540
  function planKitFileWrite({ existing, rendered, raw, force }) {
26440
26541
  if (existing === null || existing === void 0) return { write: true, reason: "missing" };
26441
- if (existing === rendered) return { write: false, reason: "unchanged" };
26442
- if (kitFileState(existing) === "managed") return { write: true, reason: "update" };
26443
- if (existing === raw) return { write: true, reason: "legacy-copy" };
26542
+ const have = normalizeEol(existing);
26543
+ if (have === normalizeEol(rendered)) return { write: false, reason: "unchanged" };
26544
+ if (kitFileState(have) === "managed") return { write: true, reason: "update" };
26545
+ if (have === normalizeEol(raw)) return { write: true, reason: "legacy-copy" };
26444
26546
  return force ? { write: true, reason: "forced" } : { write: false, reason: "customized" };
26445
26547
  }
26446
26548
  function listKitCommandFiles(platformRoot, deps, opts = {}) {
@@ -26537,6 +26639,11 @@ import { join as join6 } from "node:path";
26537
26639
  init_credentials();
26538
26640
  var CLOUD_CONSOLE_URL2 = "https://app.heyspex.com";
26539
26641
  var LOCAL_CONSOLE_URL2 = "http://localhost:3000";
26642
+ function overrideCloudCheck2(source, cloudKnown, staleAgainstCloud) {
26643
+ if (source !== "local-override") return "not-applicable";
26644
+ if (!cloudKnown) return "unverified";
26645
+ return staleAgainstCloud ? "stale" : "in-sync";
26646
+ }
26540
26647
  function normalizeMode2(raw) {
26541
26648
  const v = String(raw ?? "").trim().toLowerCase();
26542
26649
  if (v === "prod" || v === "production") return { mode: "prod", reliance: "cloud" };
@@ -26644,6 +26751,7 @@ function resolveDevMode2({
26644
26751
  const semantics = deriveSemantics2(mode, reliance);
26645
26752
  const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
26646
26753
  const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
26754
+ const cloudCheck = overrideCloudCheck2(source, cloudKnown, staleAgainstCloud);
26647
26755
  return {
26648
26756
  mode,
26649
26757
  reliance: semantics.reliance,
@@ -26661,7 +26769,8 @@ function resolveDevMode2({
26661
26769
  present: local.record !== null,
26662
26770
  record: local.record,
26663
26771
  parseError: local.parseError,
26664
- staleAgainstCloud
26772
+ staleAgainstCloud,
26773
+ cloudCheck
26665
26774
  },
26666
26775
  cloudSetting: { known: cloudKnown, value: cloudValue }
26667
26776
  };
@@ -26697,7 +26806,7 @@ init_credentials();
26697
26806
  import { randomUUID } from "node:crypto";
26698
26807
 
26699
26808
  // spex-console/src/lib/cli/control-shared.ts
26700
- import path3 from "node:path";
26809
+ import path4 from "node:path";
26701
26810
 
26702
26811
  // scripts/lib/hs-command.mjs
26703
26812
  import { existsSync as existsSync2 } from "node:fs";
@@ -26720,8 +26829,8 @@ var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER
26720
26829
  // vendor/heyspex-mcp/console-fetch.mjs
26721
26830
  init_heyspex_home();
26722
26831
  init_credentials();
26723
- import { readFileSync as readFileSync10 } from "node:fs";
26724
- import { homedir as homedir8 } from "node:os";
26832
+ import { readFileSync as readFileSync11 } from "node:fs";
26833
+ import { homedir as homedir9 } from "node:os";
26725
26834
  import { join as join10 } from "node:path";
26726
26835
 
26727
26836
  // vendor/heyspex-mcp/vault-less-endpoints.mjs
@@ -26734,14 +26843,14 @@ var VAULT_LESS_ENDPOINTS = Object.freeze([
26734
26843
  "GET /api/workspaces",
26735
26844
  "* /api/admin/dev-mode"
26736
26845
  ]);
26737
- function pathnameOf(path9) {
26738
- const raw = String(path9 ?? "").split("?")[0].split("#")[0];
26846
+ function pathnameOf(path11) {
26847
+ const raw = String(path11 ?? "").split("?")[0].split("#")[0];
26739
26848
  const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
26740
26849
  return noTrailing || "/";
26741
26850
  }
26742
- function isVaultLessEndpoint(method, path9) {
26851
+ function isVaultLessEndpoint(method, path11) {
26743
26852
  const m = String(method || "GET").toUpperCase();
26744
- const pathname = pathnameOf(path9);
26853
+ const pathname = pathnameOf(path11);
26745
26854
  return VAULT_LESS_ENDPOINTS.some((row2) => {
26746
26855
  const [rowMethod, rowPath] = row2.split(" ");
26747
26856
  return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
@@ -26761,7 +26870,9 @@ import {
26761
26870
  import { basename, join as join8, isAbsolute as isAbsolute2 } from "node:path";
26762
26871
 
26763
26872
  // scripts/lib/shared-path-helpers.mjs
26873
+ import { realpathSync } from "node:fs";
26764
26874
  import { isAbsolute, resolve as resolve3, sep } from "node:path";
26875
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
26765
26876
  function resolveCodeRoot({
26766
26877
  explicit,
26767
26878
  packageRoot,
@@ -26780,9 +26891,30 @@ function resolveCodeRoot({
26780
26891
 
26781
26892
  // vendor/heyspex-mcp/vault-resolve.mjs
26782
26893
  init_heyspex_home();
26783
- import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
26784
- import { homedir as homedir7, tmpdir } from "node:os";
26785
- import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
26894
+ import { existsSync as existsSync5, readFileSync as readFileSync10, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
26895
+ import { homedir as homedir8, tmpdir } from "node:os";
26896
+
26897
+ // vendor/heyspex-mcp/usable-dir-env.mjs
26898
+ import { statSync } from "node:fs";
26899
+ import { isAbsolute as isAbsolute3 } from "node:path";
26900
+
26901
+ // vendor/heyspex-mcp/vault-resolve.mjs
26902
+ import { basename as basename2, dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
26903
+
26904
+ // vendor/heyspex-mcp/vault-binding.mjs
26905
+ import {
26906
+ copyFileSync,
26907
+ existsSync as existsSync4,
26908
+ mkdirSync as mkdirSync6,
26909
+ readFileSync as readFileSync9,
26910
+ realpathSync as realpathSync2,
26911
+ renameSync as renameSync4,
26912
+ statSync as statSync2,
26913
+ unlinkSync,
26914
+ writeFileSync as writeFileSync6
26915
+ } from "node:fs";
26916
+ import { homedir as homedir7 } from "node:os";
26917
+ import path3 from "node:path";
26786
26918
 
26787
26919
  // vendor/heyspex-mcp/console-fetch.mjs
26788
26920
  var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
@@ -26884,11 +27016,11 @@ function readPairedBearer(opts = {}) {
26884
27016
  function readPairedToken(opts = {}) {
26885
27017
  return readPairedBearer(opts).token ?? void 0;
26886
27018
  }
26887
- function scopeHeadersFor(scope, method, path9) {
27019
+ function scopeHeadersFor(scope, method, path11) {
26888
27020
  if (scope === "none") {
26889
- if (!isVaultLessEndpoint(method, path9)) {
27021
+ if (!isVaultLessEndpoint(method, path11)) {
26890
27022
  throw new Error(
26891
- `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path9} is vault-scoped and must name { accountId, vaultId }.`
27023
+ `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path11} is vault-scoped and must name { accountId, vaultId }.`
26892
27024
  );
26893
27025
  }
26894
27026
  return {};
@@ -26899,7 +27031,7 @@ function cloudRequestScope(scope) {
26899
27031
  const named = { accountId: scope.accountId, vaultId: scope.vaultId };
26900
27032
  return {
26901
27033
  ...named,
26902
- headersFor: (method, path9) => isVaultLessEndpoint(method, path9) ? {} : scopeHeadersFor(named, method, path9)
27034
+ headersFor: (method, path11) => isVaultLessEndpoint(method, path11) ? {} : scopeHeadersFor(named, method, path11)
26903
27035
  };
26904
27036
  }
26905
27037
  function withCloudScope(deps, accountId, vaultId) {
@@ -27146,12 +27278,15 @@ var HELP = [
27146
27278
  "(spex-console/src/lib/cli/template-manifest.ts) \u2014 universal docs",
27147
27279
  "copied verbatim, template-shape docs written as author-prompt skeletons \u2014",
27148
27280
  "plus a generated CLAUDE.md, the kept-principles index, and the template_version",
27149
- "pin; with --git, one initial `git init` + commit. Rerunnable: a <dir> that already",
27150
- "holds heyspex.yaml and an empty/placeholder-only specs/ is accepted, existing",
27151
- "files are never overwritten (an existing CLAUDE.md gets a CLAUDE.md.template",
27152
- "beside it), and the result lists each manifest row as created or kept. Refuses a",
27153
- "non-empty <dir> without heyspex.yaml, or a specs/ holding managed specs.",
27154
- "See init.customer-vault.ts.",
27281
+ "pin; with --git, one initial `git init` + commit. On an EXISTING vault (<dir>",
27282
+ "holds heyspex.yaml with a vault_id) it is a REFRESH \u2014 the repair `hs doctor`'s",
27283
+ "vault-template row names: only missing shipped files are added, template_version",
27284
+ "is written/updated in heyspex.yaml (every other byte and the vault_id kept),",
27285
+ "nothing existing is ever overwritten (an existing CLAUDE.md gets a",
27286
+ "CLAUDE.md.template beside it), the result lists each manifest row as created or",
27287
+ "kept, and a second run writes nothing. <dir> may be any path (a satellite repo",
27288
+ "names its vault, e.g. `hs init --customer-vault ../my-specs`). Refuses only a",
27289
+ "non-empty <dir> that has no heyspex.yaml. See init.customer-vault.ts.",
27155
27290
  "",
27156
27291
  "Flags:",
27157
27292
  " --console-url <url> console_url value (default: the mode-aware console --",
@@ -27415,9 +27550,9 @@ function detectMarkerAttachment(repoRoot, deps) {
27415
27550
  function readPinnedVaultRoot(repoRoot, deps) {
27416
27551
  const pins = [];
27417
27552
  for (const rel of SATELLITE_MANIFEST_RELS) {
27418
- const path9 = deps.joinPath(repoRoot, ...rel);
27419
- if (!deps.fileExists(path9)) continue;
27420
- const raw = deps.readFile(path9);
27553
+ const path11 = deps.joinPath(repoRoot, ...rel);
27554
+ if (!deps.fileExists(path11)) continue;
27555
+ const raw = deps.readFile(path11);
27421
27556
  if (!raw) continue;
27422
27557
  let value;
27423
27558
  try {
@@ -27487,7 +27622,7 @@ function checkHarnessCommandDrift(repoRoot, deps) {
27487
27622
  const installedContent = deps.fileExists(installedPath) ? deps.readFile(installedPath) : null;
27488
27623
  if (installedContent === null) {
27489
27624
  out.missingFiles.push(`${label}/${f.file}`);
27490
- } else if (installedContent !== f.content) {
27625
+ } else if (normalizeEol(installedContent) !== normalizeEol(f.content)) {
27491
27626
  out.staleFiles.push(`${label}/${f.file}`);
27492
27627
  }
27493
27628
  }
@@ -27507,8 +27642,8 @@ function row(id, label, present, detail) {
27507
27642
  ...detail ? { detail } : {}
27508
27643
  };
27509
27644
  }
27510
- function readIfExists(deps, path9) {
27511
- return deps.fileExists(path9) ? deps.readFile(path9) : null;
27645
+ function readIfExists(deps, path11) {
27646
+ return deps.fileExists(path11) ? deps.readFile(path11) : null;
27512
27647
  }
27513
27648
  var PRE_COMMIT_HOOK_MARKER = "run-pre-commit-checks.mjs";
27514
27649
  function hasHeyspexMcpEntry(raw) {
@@ -27818,12 +27953,12 @@ function runConnectChecks(repoRoot, deps, opts = {}) {
27818
27953
  };
27819
27954
  }
27820
27955
  var CLOUD_READ_TIMEOUT_MS = 8e3;
27821
- async function cloudGet(http, token, path9) {
27956
+ async function cloudGet(http, token, path11) {
27822
27957
  const controller = new AbortController();
27823
27958
  const timer = setTimeout(() => controller.abort(), CLOUD_READ_TIMEOUT_MS);
27824
27959
  timer.unref?.();
27825
27960
  try {
27826
- const res = await http.fetch(`${http.consoleUrl.replace(/\/+$/, "")}${path9}`, {
27961
+ const res = await http.fetch(`${http.consoleUrl.replace(/\/+$/, "")}${path11}`, {
27827
27962
  method: "GET",
27828
27963
  headers: token ? { authorization: `Bearer ${token}` } : {},
27829
27964
  signal: controller.signal
@@ -27841,7 +27976,7 @@ async function cloudGet(http, token, path9) {
27841
27976
  clearTimeout(timer);
27842
27977
  }
27843
27978
  }
27844
- function readFailure(read2, path9, http) {
27979
+ function readFailure(read2, path11, http) {
27845
27980
  if (read2 === null) {
27846
27981
  return {
27847
27982
  cause: "no sign-in token on this machine, so the console was not asked",
@@ -27857,9 +27992,9 @@ function readFailure(read2, path9, http) {
27857
27992
  };
27858
27993
  }
27859
27994
  if (read2.status === 401) {
27860
- return { cause: `GET ${path9} answered HTTP 401 (the sign-in was refused)`, repair: "hs login" };
27995
+ return { cause: `GET ${path11} answered HTTP 401 (the sign-in was refused)`, repair: "hs login" };
27861
27996
  }
27862
- return { cause: `GET ${path9} answered HTTP ${read2.status}`, repair: retry };
27997
+ return { cause: `GET ${path11} answered HTTP ${read2.status}`, repair: retry };
27863
27998
  }
27864
27999
  var str2 = (v) => typeof v === "string" && v ? v : null;
27865
28000
  function cloudRow(id, fields) {
@@ -28047,7 +28182,13 @@ async function defaultCheckMcpBuild(deps, opts) {
28047
28182
  }
28048
28183
  }
28049
28184
  var shortFp = (fp) => fp ? fp.slice(0, 12) : null;
28050
- function hookScriptPath(command, repoRoot) {
28185
+ function hookScriptPath(command, repoRoot, joinPath) {
28186
+ const bootstrap = /^\s*node\s+-e\s+"[^"]*\/\/([^"\s]+)"/.exec(command);
28187
+ if (bootstrap) {
28188
+ const rel = bootstrap[1].replace(/^\/+/, "");
28189
+ if (rel === "bin/run-hook.mjs") return null;
28190
+ return joinPath(repoRoot, ...rel.split("/"));
28191
+ }
28051
28192
  const m = /^\s*node\s+(?:"([^"]+)"|(\S+))/.exec(command);
28052
28193
  const raw = m?.[1] ?? m?.[2];
28053
28194
  if (!raw) return null;
@@ -28081,7 +28222,7 @@ function hooksRow(repoRoot, deps) {
28081
28222
  const list = Array.isArray(group2?.hooks) ? group2.hooks : [];
28082
28223
  for (const h of list) {
28083
28224
  if (typeof h?.command !== "string" || ourHookTail(h.command) === null) continue;
28084
- const script = hookScriptPath(h.command, repoRoot);
28225
+ const script = hookScriptPath(h.command, repoRoot, deps.joinPath);
28085
28226
  if (script) scripts.add(script);
28086
28227
  }
28087
28228
  }
@@ -28121,7 +28262,7 @@ async function mcpBuildRow(vaultSubject, deps) {
28121
28262
  );
28122
28263
  }
28123
28264
  async function readCloudView(http, tokens) {
28124
- const get2 = (token, path9) => http && token ? cloudGet(http, token, path9) : Promise.resolve(null);
28265
+ const get2 = (token, path11) => http && token ? cloudGet(http, token, path11) : Promise.resolve(null);
28125
28266
  const [session, vaults2, workspaces2, devMode] = await Promise.all([
28126
28267
  get2(tokens.current, "/api/mcp/session"),
28127
28268
  get2(tokens.repo, "/api/vaults?include_archived=true"),
@@ -28132,9 +28273,9 @@ async function readCloudView(http, tokens) {
28132
28273
  cause: "no console URL configured for this machine",
28133
28274
  repair: "hs login"
28134
28275
  };
28135
- const fail = (read2, path9) => http ? readFailure(read2, path9, http) : noConsole;
28136
- const shapeFailure = (path9, what) => ({
28137
- cause: `GET ${path9} answered without ${what}`,
28276
+ const fail = (read2, path11) => http ? readFailure(read2, path11, http) : noConsole;
28277
+ const shapeFailure = (path11, what) => ({
28278
+ cause: `GET ${path11} answered without ${what}`,
28138
28279
  repair: `retry \`hs connect --check\` once ${http?.consoleUrl ?? "the console"} answers`
28139
28280
  });
28140
28281
  const sessionBody = session?.ok ? session.json : null;
@@ -28442,7 +28583,7 @@ function writeProjectContract(ctx, options2 = {}) {
28442
28583
  }
28443
28584
 
28444
28585
  // spex-console/src/lib/cli/init.customer-vault.ts
28445
- import { basename as basename2 } from "node:path";
28586
+ import { basename as basename3 } from "node:path";
28446
28587
 
28447
28588
  // spex-console/src/lib/cli/template-manifest.sources.mjs
28448
28589
  var UNIVERSAL_TEMPLATE_ROWS = [
@@ -28927,41 +29068,42 @@ var SCAFFOLD_FOLDERS = [
28927
29068
  "checks",
28928
29069
  "events"
28929
29070
  ];
28930
- var PLACEHOLDER_FILES = /* @__PURE__ */ new Set([".gitkeep"]);
28931
- function isDir(deps, path9, name) {
28932
- if (deps.isDirectory) return deps.isDirectory(path9);
28933
- try {
28934
- const kids = deps.readdir(path9);
28935
- return kids.length > 0 || !name.includes(".");
28936
- } catch {
28937
- return false;
28938
- }
29071
+ function defaultVaultName(targetDir) {
29072
+ return basename3(targetDir) || targetDir;
28939
29073
  }
28940
- function findManagedSpecs(deps, targetDir, manifestDests) {
28941
- const out = [];
28942
- const walk = (abs, rel) => {
28943
- for (const name of deps.readdir(abs)) {
28944
- const childAbs = deps.joinPath(abs, name);
28945
- const childRel = `${rel}/${name}`;
28946
- if (isDir(deps, childAbs, name)) walk(childAbs, childRel);
28947
- else if (!PLACEHOLDER_FILES.has(name) && name.endsWith(".md") && !manifestDests.has(childRel))
28948
- out.push(childRel);
28949
- }
28950
- };
28951
- const specsRoot = deps.joinPath(targetDir, "specs");
28952
- if (deps.exists(specsRoot)) walk(specsRoot, "specs");
28953
- return out;
29074
+ function readTemplateVersionPin(yamlText) {
29075
+ const m = yamlText.match(/^template_version:[ \t]*(.*?)[ \t]*$/m);
29076
+ if (!m) return null;
29077
+ const raw = m[1].trim();
29078
+ const unquoted = raw.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
29079
+ return unquoted.length > 0 ? unquoted : null;
28954
29080
  }
28955
- function defaultVaultName(targetDir) {
28956
- return basename2(targetDir) || targetDir;
29081
+ function stampTemplateVersion(content, version) {
29082
+ if (readTemplateVersionPin(content) === version) return content;
29083
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
29084
+ const line = `template_version: ${version}`;
29085
+ if (/^template_version:[^\r\n]*$/m.test(content)) {
29086
+ return content.replace(/^template_version:[^\r\n]*$/m, line);
29087
+ }
29088
+ const body = content.replace(/(?:\r?\n)+$/, "");
29089
+ return body.length === 0 ? `${line}${eol}` : `${body}${eol}${line}${eol}`;
29090
+ }
29091
+ function parentDirOf(deps, targetDir, dest) {
29092
+ const segments = dest.split("/");
29093
+ if (segments.length < 2) return null;
29094
+ return deps.joinPath(targetDir, ...segments.slice(0, -1));
28957
29095
  }
28958
29096
  function runCustomerVaultInit(args, deps, options2 = {}) {
28959
29097
  const { targetDir } = args;
28960
29098
  const written = [];
28961
29099
  const filter = options2.manifestFilter ?? (() => true);
28962
29100
  const rows = TEMPLATE_MANIFEST.filter(filter);
29101
+ const heyspexYamlPath = deps.joinPath(targetDir, "heyspex.yaml");
29102
+ const markerPresent = deps.exists(targetDir) && deps.exists(heyspexYamlPath);
29103
+ const existingYaml = markerPresent ? deps.readFile?.(heyspexYamlPath) ?? null : null;
29104
+ const existingVaultId = existingYaml ? parseVaultId(existingYaml) : null;
29105
+ const mode = markerPresent && existingVaultId ? "refresh" : "scaffold";
28963
29106
  if (deps.exists(targetDir)) {
28964
- const markerPresent = deps.exists(deps.joinPath(targetDir, "heyspex.yaml"));
28965
29107
  if (!markerPresent && deps.readdir(targetDir).length > 0) {
28966
29108
  return {
28967
29109
  code: EXIT.INVALID_ARG,
@@ -28971,43 +29113,43 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
28971
29113
  written: []
28972
29114
  };
28973
29115
  }
28974
- if (markerPresent) {
28975
- const manifestDests = new Set(TEMPLATE_MANIFEST.map((r) => r.dest));
28976
- const managed = findManagedSpecs(deps, targetDir, manifestDests);
28977
- if (managed.length > 0) {
28978
- return {
28979
- code: EXIT.INVALID_ARG,
28980
- message: `${targetDir}/specs already holds managed spec(s) (${managed.slice(0, 3).join(", ")}${managed.length > 3 ? ", ..." : ""}) \u2014 customer-vault init refuses to write over an existing vault.`,
28981
- payload: { ok: false, error: "specs_not_empty", path: targetDir, specs: managed },
28982
- isError: true,
28983
- written: []
28984
- };
28985
- }
28986
- }
28987
29116
  } else {
28988
29117
  deps.mkdirRecursive(targetDir);
28989
29118
  }
28990
- const specsRoot = deps.joinPath(targetDir, "specs");
28991
- deps.mkdirRecursive(specsRoot);
28992
- for (const folder of SCAFFOLD_FOLDERS) {
28993
- deps.mkdirRecursive(deps.joinPath(specsRoot, folder));
29119
+ if (mode === "scaffold") {
29120
+ const specsRoot = deps.joinPath(targetDir, "specs");
29121
+ deps.mkdirRecursive(specsRoot);
29122
+ for (const folder of SCAFFOLD_FOLDERS) {
29123
+ deps.mkdirRecursive(deps.joinPath(specsRoot, folder));
29124
+ }
28994
29125
  }
28995
- const heyspexYamlPath = deps.joinPath(targetDir, "heyspex.yaml");
28996
29126
  let vaultId;
28997
- if (deps.exists(heyspexYamlPath)) {
28998
- const existingYaml = deps.readFile?.(heyspexYamlPath) ?? null;
28999
- vaultId = (existingYaml ? parseVaultId(existingYaml) : null) ?? "";
29127
+ let pin;
29128
+ if (markerPresent) {
29129
+ vaultId = existingVaultId ?? "";
29130
+ const stamped = existingYaml === null ? null : stampTemplateVersion(existingYaml, TEMPLATE_VERSION);
29131
+ if (stamped !== null && stamped !== existingYaml) {
29132
+ deps.writeFile(heyspexYamlPath, stamped);
29133
+ written.push(heyspexYamlPath);
29134
+ pin = "written";
29135
+ } else {
29136
+ pin = "kept";
29137
+ }
29000
29138
  } else {
29001
29139
  vaultId = mintVaultId();
29002
29140
  deps.writeFile(
29003
29141
  heyspexYamlPath,
29004
- renderHeyspexYaml({
29005
- consoleUrl: options2.consoleUrl ?? DEFAULT_CONSOLE_URL2,
29006
- vaultRoot: "specs/",
29007
- vaultId
29008
- })
29142
+ stampTemplateVersion(
29143
+ renderHeyspexYaml({
29144
+ consoleUrl: options2.consoleUrl ?? DEFAULT_CONSOLE_URL2,
29145
+ vaultRoot: "specs/",
29146
+ vaultId
29147
+ }),
29148
+ TEMPLATE_VERSION
29149
+ )
29009
29150
  );
29010
29151
  written.push(heyspexYamlPath);
29152
+ pin = "written";
29011
29153
  }
29012
29154
  const readConfigSeed = deps.readConfigSeed;
29013
29155
  const configSeed = seedConfigYaml(
@@ -29023,6 +29165,10 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
29023
29165
  rowResults.push({ dest: row2.dest, action: "kept" });
29024
29166
  continue;
29025
29167
  }
29168
+ if (mode === "refresh") {
29169
+ const parent = parentDirOf(deps, targetDir, row2.dest);
29170
+ if (parent && !deps.exists(parent)) deps.mkdirRecursive(parent);
29171
+ }
29026
29172
  if (row2.classification === "universal-template") {
29027
29173
  const srcAbs = deps.joinPath(deps.platformRoot, ...row2.source.split("/"));
29028
29174
  deps.copyFile(srcAbs, destAbs);
@@ -29042,24 +29188,41 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
29042
29188
  targetDir,
29043
29189
  vaultName: options2.vaultName ?? defaultVaultName(targetDir),
29044
29190
  templateVersion: TEMPLATE_VERSION,
29191
+ mode,
29045
29192
  manifestRows: rows,
29046
29193
  rows: rowResults,
29047
29194
  deps,
29048
- recordWrite: (path9) => {
29049
- written.push(path9);
29195
+ recordWrite: (path11) => {
29196
+ written.push(path11);
29050
29197
  }
29051
29198
  };
29052
29199
  for (const hook of options2.afterScaffold) hook(ctx);
29053
29200
  }
29054
29201
  const createdCount = rowResults.filter((r) => r.action === "created").length;
29055
29202
  const keptCount = rowResults.length - createdCount;
29203
+ const added = rowResults.filter((r) => r.action === "created").map((r) => r.dest);
29204
+ let message2;
29205
+ if (mode === "refresh") {
29206
+ const parts = [];
29207
+ if (added.length > 0)
29208
+ parts.push(`added ${added.length} missing template file(s): ${added.join(", ")}`);
29209
+ if (pin === "written")
29210
+ parts.push(`pinned template_version: ${TEMPLATE_VERSION} in heyspex.yaml`);
29211
+ const hookWrites = written.length - added.length - (pin === "written" ? 1 : 0);
29212
+ if (hookWrites > 0) parts.push(`${hookWrites} other file(s) written`);
29213
+ message2 = parts.length > 0 ? `Refreshed customer vault at ${targetDir}: ${parts.join("; ")} (${keptCount} existing file(s) kept untouched).` : `Customer vault at ${targetDir} is already current (template v${TEMPLATE_VERSION}, ${keptCount} file(s) kept) \u2014 nothing written.`;
29214
+ } else {
29215
+ message2 = `Initialized customer vault at ${targetDir} (${written.length} files written, ${createdCount} manifest rows created, ${keptCount} kept, template v${TEMPLATE_VERSION}).`;
29216
+ }
29056
29217
  return {
29057
29218
  code: EXIT.OK,
29058
- message: `Initialized customer vault at ${targetDir} (${written.length} files written, ${createdCount} manifest rows created, ${keptCount} kept, template v${TEMPLATE_VERSION}).`,
29219
+ message: message2,
29059
29220
  payload: {
29060
29221
  ok: true,
29222
+ mode,
29061
29223
  targetDir,
29062
29224
  templateVersion: TEMPLATE_VERSION,
29225
+ templatePin: pin,
29063
29226
  vaultId,
29064
29227
  written,
29065
29228
  rows: rowResults,
@@ -29101,14 +29264,14 @@ function templatePinHook() {
29101
29264
  }
29102
29265
 
29103
29266
  // spex-console/src/lib/cli/principle-selection.ts
29104
- import { basename as basename3 } from "node:path";
29267
+ import { basename as basename4 } from "node:path";
29105
29268
  var PRINCIPLES_INDEX_DEST = "specs/principles-index.md";
29106
29269
  function principleKey(row2) {
29107
- const base = basename3(row2.dest, ".md");
29270
+ const base = basename4(row2.dest, ".md");
29108
29271
  return base.startsWith("principle-") ? base.slice("principle-".length) : base;
29109
29272
  }
29110
29273
  function principleWikilink(row2) {
29111
- return basename3(row2.dest, ".md");
29274
+ return basename4(row2.dest, ".md");
29112
29275
  }
29113
29276
  function parsePrinciplesFlag(value, rows) {
29114
29277
  const principleRows = rows.filter((row2) => row2.group === "principle");
@@ -29174,13 +29337,13 @@ function applyCustomerTemplate(args) {
29174
29337
  templateVersion: ctx.templateVersion
29175
29338
  });
29176
29339
  for (const file of contractFiles) {
29177
- const path9 = ctx.deps.joinPath(ctx.targetDir, file);
29178
- if (force && ctx.deps.exists(path9)) {
29179
- if (ctx.deps.readFile?.(path9) === content) {
29340
+ const path11 = ctx.deps.joinPath(ctx.targetDir, file);
29341
+ if (force && ctx.deps.exists(path11)) {
29342
+ if (ctx.deps.readFile?.(path11) === content) {
29180
29343
  contracts.push({ file, action: "unchanged" });
29181
29344
  } else {
29182
- ctx.deps.writeFile(path9, content);
29183
- ctx.recordWrite(path9);
29345
+ ctx.deps.writeFile(path11, content);
29346
+ ctx.recordWrite(path11);
29184
29347
  contracts.push({ file, action: "overwritten" });
29185
29348
  }
29186
29349
  continue;
@@ -29219,12 +29382,17 @@ function applyCustomerTemplate(args) {
29219
29382
  }
29220
29383
 
29221
29384
  // spex-console/src/lib/cli/install.ts
29222
- import { isAbsolute as isAbsolute3, resolve as resolveNodePath2 } from "node:path";
29385
+ import { isAbsolute as isAbsolute4, resolve as resolveNodePath2 } from "node:path";
29386
+
29387
+ // spex-console/src/lib/vault-resolve/index.ts
29388
+ import { existsSync as existsSync6, readFileSync as readFileSync12, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
29389
+ import { tmpdir as tmpdir2 } from "node:os";
29390
+ import path5 from "node:path";
29223
29391
 
29224
29392
  // scripts/install-cursor-hooks.mjs
29225
29393
  import fs2 from "node:fs";
29226
- import path4 from "node:path";
29227
- import { fileURLToPath as fileURLToPath3 } from "node:url";
29394
+ import path6 from "node:path";
29395
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
29228
29396
 
29229
29397
  // scripts/cursor-hooks/manifest.mjs
29230
29398
  var SHIM = "scripts/cursor-hooks/_run.mjs";
@@ -29368,9 +29536,9 @@ function timeoutFor(entry) {
29368
29536
  }
29369
29537
 
29370
29538
  // scripts/install-cursor-hooks.mjs
29371
- var PLATFORM_ROOT = path4.resolve(path4.dirname(fileURLToPath3(import.meta.url)), "..");
29372
- var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path4.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
29373
- var SETTINGS_PATH = path4.join(REPO_ROOT, ".cursor", "hooks.json");
29539
+ var PLATFORM_ROOT = path6.resolve(path6.dirname(fileURLToPath4(import.meta.url)), "..");
29540
+ var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path6.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
29541
+ var SETTINGS_PATH = path6.join(REPO_ROOT, ".cursor", "hooks.json");
29374
29542
  function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
29375
29543
  const customer = !samePlatformPath(targetRoot, platformRoot);
29376
29544
  return {
@@ -29382,7 +29550,7 @@ function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
29382
29550
  }
29383
29551
  var KIT = cursorKitFor(REPO_ROOT);
29384
29552
  var ACTIVE_HOOKS = KIT.hooks;
29385
- var LAUNCHER_PATH = path4.join(REPO_ROOT, ...CUSTOMER_LAUNCHERS.cursor.rel);
29553
+ var LAUNCHER_PATH = path6.join(REPO_ROOT, ...CUSTOMER_LAUNCHERS.cursor.rel);
29386
29554
  var LEGACY_MARKER = "scripts/claude-hooks/";
29387
29555
  function readSettings() {
29388
29556
  try {
@@ -29401,7 +29569,7 @@ function readSettings() {
29401
29569
  return { version: 1, hooks: {} };
29402
29570
  }
29403
29571
  function writeSettings(settings) {
29404
- fs2.mkdirSync(path4.dirname(SETTINGS_PATH), { recursive: true });
29572
+ fs2.mkdirSync(path6.dirname(SETTINGS_PATH), { recursive: true });
29405
29573
  fs2.writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}
29406
29574
  `, "utf8");
29407
29575
  }
@@ -29436,7 +29604,7 @@ function sameEntry(raw, entry, render = KIT.commandFor) {
29436
29604
  const want = desiredEntry(entry, render);
29437
29605
  return commandOf(raw) === want.command && raw.timeout === want.timeout && (raw.matcher ?? void 0) === (want.matcher ?? void 0);
29438
29606
  }
29439
- var PLUGIN_HOOKS_PATH = path4.join(REPO_ROOT, "plugins", "heyspex", "hooks", "cursor-hooks.json");
29607
+ var PLUGIN_HOOKS_PATH = path6.join(REPO_ROOT, "plugins", "heyspex", "hooks", "cursor-hooks.json");
29440
29608
  function pluginHooksDocument() {
29441
29609
  const hooks = {};
29442
29610
  for (const entry of HOOKS) {
@@ -29466,7 +29634,7 @@ function syncPluginHooks() {
29466
29634
  have = null;
29467
29635
  }
29468
29636
  if (have === want) return false;
29469
- fs2.mkdirSync(path4.dirname(PLUGIN_HOOKS_PATH), { recursive: true });
29637
+ fs2.mkdirSync(path6.dirname(PLUGIN_HOOKS_PATH), { recursive: true });
29470
29638
  fs2.writeFileSync(PLUGIN_HOOKS_PATH, want, "utf8");
29471
29639
  return true;
29472
29640
  }
@@ -29539,7 +29707,7 @@ function install({ json = false } = {}) {
29539
29707
  if (KIT.customer) {
29540
29708
  const plan = launcherPlan();
29541
29709
  if (plan.write) {
29542
- fs2.mkdirSync(path4.dirname(LAUNCHER_PATH), { recursive: true });
29710
+ fs2.mkdirSync(path6.dirname(LAUNCHER_PATH), { recursive: true });
29543
29711
  fs2.writeFileSync(LAUNCHER_PATH, plan.text, "utf8");
29544
29712
  launcherWritten = true;
29545
29713
  }
@@ -29566,10 +29734,10 @@ function install({ json = false } = {}) {
29566
29734
  return;
29567
29735
  }
29568
29736
  if (pluginChanged) {
29569
- console.log(`install-cursor-hooks: regenerated ${path4.relative(REPO_ROOT, PLUGIN_HOOKS_PATH)}`);
29737
+ console.log(`install-cursor-hooks: regenerated ${path6.relative(REPO_ROOT, PLUGIN_HOOKS_PATH)}`);
29570
29738
  }
29571
29739
  if (launcherWritten) {
29572
- console.log(`install-cursor-hooks: wrote ${path4.relative(REPO_ROOT, LAUNCHER_PATH)} (resolves node_modules/heyspex)`);
29740
+ console.log(`install-cursor-hooks: wrote ${path6.relative(REPO_ROOT, LAUNCHER_PATH)} (resolves node_modules/heyspex)`);
29573
29741
  }
29574
29742
  if (!settingsChanged) {
29575
29743
  console.log(`install-cursor-hooks: already installed (${ACTIVE_HOOKS.length} hooks, no changes).`);
@@ -29580,7 +29748,7 @@ function install({ json = false } = {}) {
29580
29748
  if (repaired.length) parts.push(`repaired ${repaired.length}`);
29581
29749
  if (removed.length) parts.push(`removed ${removed.length}`);
29582
29750
  console.log(
29583
- `install-cursor-hooks: ${parts.join("; ")} \u2192 ${path4.relative(REPO_ROOT, SETTINGS_PATH)} (${ACTIVE_HOOKS.length} hooks registered)`
29751
+ `install-cursor-hooks: ${parts.join("; ")} \u2192 ${path6.relative(REPO_ROOT, SETTINGS_PATH)} (${ACTIVE_HOOKS.length} hooks registered)`
29584
29752
  );
29585
29753
  for (const line of removed) console.log(` - ${line}`);
29586
29754
  console.log(
@@ -29868,11 +30036,11 @@ function readLocalReceiptSide(subject, registryOpts = {}) {
29868
30036
  return unreadSide(`registry.json could not be read: ${message(err)}`);
29869
30037
  }
29870
30038
  }
29871
- async function readList(http, path9, key) {
29872
- const res = await cloudHttpRequest(http, "GET", path9);
30039
+ async function readList(http, path11, key) {
30040
+ const res = await cloudHttpRequest(http, "GET", path11);
29873
30041
  const rows = res.json?.[key];
29874
30042
  if (!res.ok || !Array.isArray(rows)) {
29875
- return { reason: `GET ${path9.split("?")[0]} answered HTTP ${res.status}` };
30043
+ return { reason: `GET ${path11.split("?")[0]} answered HTTP ${res.status}` };
29876
30044
  }
29877
30045
  return { rows: rows.filter((r) => Boolean(r) && typeof r === "object") };
29878
30046
  }
@@ -29947,7 +30115,7 @@ async function readReceiptSides(subject, deps) {
29947
30115
  var import_gray_matter = __toESM(require_gray_matter(), 1);
29948
30116
  import { createHash as createHash2 } from "node:crypto";
29949
30117
  import { promises as fs3 } from "node:fs";
29950
- import path5 from "node:path";
30118
+ import path7 from "node:path";
29951
30119
 
29952
30120
  // spex-console/src/lib/vault-id.ts
29953
30121
  var FILE_KEY_RE = /^(?=[0-9]*[A-Z])[A-Z0-9]{4,}$/;
@@ -30529,8 +30697,8 @@ async function indexTemplateToCloud(args) {
30529
30697
  const res = await cloudHttpRequest(args.http, "GET", REINDEX_PATH);
30530
30698
  return { status: res.status, json: res.json };
30531
30699
  },
30532
- post: async (path9, body) => {
30533
- const res = await cloudHttpRequest(args.http, "POST", path9, body);
30700
+ post: async (path11, body) => {
30701
+ const res = await cloudHttpRequest(args.http, "POST", path11, body);
30534
30702
  return { status: res.status, json: res.json };
30535
30703
  }
30536
30704
  });
@@ -30576,39 +30744,58 @@ async function indexTemplateToCloud(args) {
30576
30744
  // spex-console/src/lib/cli/install.ts
30577
30745
  var MCP_REL = ["vendor", "heyspex-mcp", "cli.mjs"];
30578
30746
  var PORTABLE_MCP_CLI = Object.freeze({
30747
+ claude: `\${CLAUDE_PROJECT_DIR:-.}/${CUSTOMER_LAUNCHERS.claude.rel.join("/")}`,
30748
+ cursor: `\${workspaceFolder}/${CUSTOMER_LAUNCHERS.cursor.rel.join("/")}`
30749
+ });
30750
+ var LEGACY_PORTABLE_MCP_CLI = Object.freeze({
30579
30751
  claude: `\${CLAUDE_PROJECT_DIR:-.}/${PORTABLE_PACKAGE_DIR}/${MCP_REL.join("/")}`,
30580
30752
  cursor: `\${workspaceFolder}/${PORTABLE_PACKAGE_DIR}/${MCP_REL.join("/")}`
30581
30753
  });
30754
+ var LAUNCHER_MCP_FLAG = "--mcp";
30755
+ function mcpServerArgs(cliPath) {
30756
+ return /[\\/]heyspex[\\/]run-hook\.mjs$/.test(cliPath) ? [cliPath, LAUNCHER_MCP_FLAG] : [cliPath];
30757
+ }
30758
+ function repairLegacyMcpManifest(manifest, surface) {
30759
+ const servers = manifest?.mcpServers;
30760
+ const entry = servers && typeof servers === "object" ? servers.heyspex : void 0;
30761
+ if (!entry || !Array.isArray(entry.args) || entry.args[0] !== LEGACY_PORTABLE_MCP_CLI[surface]) {
30762
+ return false;
30763
+ }
30764
+ entry.args = mcpServerArgs(PORTABLE_MCP_CLI[surface]);
30765
+ return true;
30766
+ }
30582
30767
  function mcpCliArgFor(surface, platformRoot, targetDir, joinPath) {
30583
30768
  return samePlatformPath(targetDir, platformRoot) ? mcpCliPath(platformRoot, joinPath) : PORTABLE_MCP_CLI[surface];
30584
30769
  }
30585
30770
  function mcpCliPath(platformRoot, joinPath) {
30586
30771
  return joinPath(platformRoot, ...MCP_REL);
30587
30772
  }
30588
- function renderCursorMcpJson(cliAbsPath, attachedVaultRoot) {
30773
+ function renderCursorMcpJson(cliAbsPath, attachedVaultRoot, opts = {}) {
30774
+ const pin = vaultRootPin(attachedVaultRoot, "${workspaceFolder}", opts);
30589
30775
  return {
30590
30776
  mcpServers: {
30591
30777
  heyspex: {
30592
30778
  type: "stdio",
30593
30779
  command: "node",
30594
- args: [cliAbsPath],
30595
- env: {
30596
- HEYSPEX_VAULT_ROOT: attachedVaultRoot ?? "${workspaceFolder}"
30597
- }
30780
+ args: mcpServerArgs(cliAbsPath),
30781
+ ...pin ? { env: { HEYSPEX_VAULT_ROOT: pin } } : {}
30598
30782
  }
30599
30783
  }
30600
30784
  };
30601
30785
  }
30602
- function renderClaudeMcpJson(cliAbsPath, attachedVaultRoot) {
30786
+ function vaultRootPin(attachedVaultRoot, placeholder, opts = {}) {
30787
+ if (attachedVaultRoot) return attachedVaultRoot;
30788
+ return opts.targetIsVault === false ? null : placeholder;
30789
+ }
30790
+ function renderClaudeMcpJson(cliAbsPath, attachedVaultRoot, opts = {}) {
30791
+ const pin = vaultRootPin(attachedVaultRoot, "${CLAUDE_PROJECT_DIR:-.}", opts);
30603
30792
  return {
30604
30793
  mcpServers: {
30605
30794
  heyspex: {
30606
30795
  type: "stdio",
30607
30796
  command: "node",
30608
- args: [cliAbsPath],
30609
- env: {
30610
- HEYSPEX_VAULT_ROOT: attachedVaultRoot ?? "${CLAUDE_PROJECT_DIR:-.}"
30611
- }
30797
+ args: mcpServerArgs(cliAbsPath),
30798
+ ...pin ? { env: { HEYSPEX_VAULT_ROOT: pin } } : {}
30612
30799
  }
30613
30800
  }
30614
30801
  };
@@ -30886,7 +31073,6 @@ var WRITE_TARGET_KIND = {
30886
31073
  claudeMcp: "claude_mcp",
30887
31074
  claudeMd: "claude_md",
30888
31075
  agentsMd: "agents_md",
30889
- statuslineVendor: "statusline_vendor",
30890
31076
  gitignore: "gitignore",
30891
31077
  claudeMdTemplate: "claude_md_template",
30892
31078
  agentsMdTemplate: "agents_md_template"
@@ -30943,12 +31129,6 @@ var INSTALL_WRITE_TARGETS = [
30943
31129
  path: (targetDir, joinPath) => joinPath(targetDir, "AGENTS.md.template"),
30944
31130
  appliesTo: (args, opts) => Boolean(args.registerVault) && !opts.attached && opts.installContract
30945
31131
  },
30946
- {
30947
- kind: WRITE_TARGET_KIND.statuslineVendor,
30948
- rel: "scripts/claude-statusline",
30949
- path: (targetDir, joinPath) => joinPath(targetDir, "scripts", "claude-statusline"),
30950
- appliesTo: (args) => (args.statusline ?? true) && args.claude
30951
- },
30952
31132
  {
30953
31133
  kind: WRITE_TARGET_KIND.gitignore,
30954
31134
  rel: ".gitignore",
@@ -31081,7 +31261,7 @@ var INSTALL_HELP = [
31081
31261
 
31082
31262
  // spex-console/src/lib/cli/install-identity.ts
31083
31263
  import { spawnSync as spawnSync2 } from "node:child_process";
31084
- import { existsSync as existsSync5 } from "node:fs";
31264
+ import { existsSync as existsSync7 } from "node:fs";
31085
31265
  import { join as join11 } from "node:path";
31086
31266
  import { createInterface } from "node:readline/promises";
31087
31267
  init_credentials();
@@ -31089,7 +31269,7 @@ init_credentials();
31089
31269
  // vendor/heyspex-mcp/whoami.mjs
31090
31270
  init_heyspex_home();
31091
31271
  init_credentials();
31092
- import { homedir as homedir9 } from "node:os";
31272
+ import { homedir as homedir10 } from "node:os";
31093
31273
  var SESSION_PATH = "/api/mcp/session";
31094
31274
  async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
31095
31275
  if (!token) return { kind: "no_credential" };
@@ -31373,7 +31553,7 @@ function isInteractiveTerminal() {
31373
31553
  }
31374
31554
  function defaultIdentityGateIo(opts) {
31375
31555
  const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
31376
- const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join11(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync5(p));
31556
+ const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join11(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync7(p));
31377
31557
  return {
31378
31558
  consoleUrl,
31379
31559
  interactive: opts.interactive ?? isInteractiveTerminal(),
@@ -31666,9 +31846,9 @@ function detachedSatelliteWarning(repoRoot, gitRemote, registryBefore, deps) {
31666
31846
  reasons.push(`${wiredManifests.join(" + ")} already declares the heyspex MCP server`);
31667
31847
  }
31668
31848
  const contractFiles = ["CLAUDE.md", "AGENTS.md"].filter((name) => {
31669
- const path9 = deps.joinPath(repoRoot, name);
31670
- if (!deps.fileExists(path9)) return false;
31671
- const content = deps.readFile(path9);
31849
+ const path11 = deps.joinPath(repoRoot, name);
31850
+ if (!deps.fileExists(path11)) return false;
31851
+ const content = deps.readFile(path11);
31672
31852
  return content !== null && HEYSPEX_CONTRACT_MARKERS.some((m) => content.includes(m));
31673
31853
  });
31674
31854
  if (contractFiles.length > 0) {
@@ -32487,7 +32667,32 @@ async function runConnectFullRun(args, deps) {
32487
32667
  const mcpConfirmedBy = "read .mcp.json + .cursor/mcp.json";
32488
32668
  const claudeMissing = !deps.fileExists(claudeMcpPath);
32489
32669
  const cursorMissing = !deps.fileExists(cursorMcpPath);
32490
- if (!claudeMissing && !cursorMissing) {
32670
+ const repairedLegacy = [];
32671
+ for (const [file, surface, missing] of [
32672
+ [claudeMcpPath, "claude", claudeMissing],
32673
+ [cursorMcpPath, "cursor", cursorMissing]
32674
+ ]) {
32675
+ if (missing || samePlatformPath(repoRoot, platformRoot)) continue;
32676
+ let parsed = null;
32677
+ try {
32678
+ parsed = JSON.parse(deps.readFile(file) ?? "");
32679
+ } catch {
32680
+ continue;
32681
+ }
32682
+ if (!repairLegacyMcpManifest(parsed, surface)) continue;
32683
+ if (!args.dryRun) deps.writeFile(file, `${JSON.stringify(parsed, null, 2)}
32684
+ `);
32685
+ repairedLegacy.push(file);
32686
+ }
32687
+ if (repairedLegacy.length > 0 && !claudeMissing && !cursorMissing) {
32688
+ steps.push({
32689
+ id: "mcp_entry",
32690
+ label,
32691
+ status: "run",
32692
+ detail: `${args.dryRun ? "would repoint" : "repointed"} ${repairedLegacy.join(", ")} at the committed launcher (${PORTABLE_MCP_CLI.claude})`,
32693
+ confirmed_by: mcpConfirmedBy
32694
+ });
32695
+ } else if (!claudeMissing && !cursorMissing) {
32491
32696
  steps.push({
32492
32697
  id: "mcp_entry",
32493
32698
  label,
@@ -32510,12 +32715,14 @@ async function runConnectFullRun(args, deps) {
32510
32715
  try {
32511
32716
  const claudeCli = mcpCliArgFor("claude", platformRoot, repoRoot, deps.joinPath);
32512
32717
  const cursorCli = mcpCliArgFor("cursor", platformRoot, repoRoot, deps.joinPath);
32513
- const pinnedRoot = satellite?.vaultRootAbs;
32718
+ const markerRel = satellite?.viaMarker ? satellite.vaultPath : void 0;
32719
+ const claudePin = markerRel ? "${CLAUDE_PROJECT_DIR:-.}/" + markerRel : satellite?.vaultRootAbs;
32720
+ const cursorPin = markerRel ? "${workspaceFolder}/" + markerRel : satellite?.vaultRootAbs;
32514
32721
  const written = [];
32515
32722
  if (claudeMissing) {
32516
32723
  deps.writeFile(
32517
32724
  claudeMcpPath,
32518
- `${JSON.stringify(renderClaudeMcpJson(claudeCli, pinnedRoot), null, 2)}
32725
+ `${JSON.stringify(renderClaudeMcpJson(claudeCli, claudePin), null, 2)}
32519
32726
  `
32520
32727
  );
32521
32728
  written.push(claudeMcpPath);
@@ -32524,7 +32731,7 @@ async function runConnectFullRun(args, deps) {
32524
32731
  deps.mkdir(deps.joinPath(repoRoot, ".cursor"));
32525
32732
  deps.writeFile(
32526
32733
  cursorMcpPath,
32527
- `${JSON.stringify(renderCursorMcpJson(cursorCli, pinnedRoot), null, 2)}
32734
+ `${JSON.stringify(renderCursorMcpJson(cursorCli, cursorPin), null, 2)}
32528
32735
  `
32529
32736
  );
32530
32737
  written.push(cursorMcpPath);
@@ -32900,11 +33107,11 @@ function resolveDoctorCommand(repoRoot, platformRoot, deps) {
32900
33107
  const hs = deps.joinPath(platformRoot, "bin", "hs.mjs");
32901
33108
  return deps.fileExists(hs) ? { args: [hs, "doctor", "--json"], confirmedBy: "hs doctor --json" } : null;
32902
33109
  }
32903
- function assertMarker(content, path9, vaultId, accountId) {
32904
- if (content === null) throw new Error(`${path9} could not be read back after the write`);
33110
+ function assertMarker(content, path11, vaultId, accountId) {
33111
+ if (content === null) throw new Error(`${path11} could not be read back after the write`);
32905
33112
  const back = parseVaultId(content);
32906
33113
  if (vaultId && back !== vaultId) {
32907
- throw new Error(`${path9} reads back vault_id ${back ?? "(none)"}, not ${vaultId}`);
33114
+ throw new Error(`${path11} reads back vault_id ${back ?? "(none)"}, not ${vaultId}`);
32908
33115
  }
32909
33116
  }
32910
33117
  var MIRRORED_SKILL_DIRS = [
@@ -33058,7 +33265,7 @@ async function runFixHarnessCommands(args, deps) {
33058
33265
  continue;
33059
33266
  }
33060
33267
  const existingContent = deps.fileExists(installedPath) ? deps.readFile(installedPath) : null;
33061
- if (existingContent === f.content) {
33268
+ if (existingContent !== null && normalizeEol(existingContent) === normalizeEol(f.content)) {
33062
33269
  unchangedCount += 1;
33063
33270
  continue;
33064
33271
  }
@@ -33563,14 +33770,14 @@ import {
33563
33770
  import {
33564
33771
  appendFileSync,
33565
33772
  closeSync,
33566
- existsSync as existsSync6,
33567
- mkdirSync as mkdirSync6,
33773
+ existsSync as existsSync8,
33774
+ mkdirSync as mkdirSync7,
33568
33775
  openSync,
33569
- readFileSync as readFileSync11,
33570
- renameSync as renameSync4,
33571
- statSync,
33572
- unlinkSync,
33573
- writeFileSync as writeFileSync6,
33776
+ readFileSync as readFileSync13,
33777
+ renameSync as renameSync5,
33778
+ statSync as statSync5,
33779
+ unlinkSync as unlinkSync2,
33780
+ writeFileSync as writeFileSync7,
33574
33781
  writeSync
33575
33782
  } from "node:fs";
33576
33783
  import { join as join12 } from "node:path";
@@ -33610,14 +33817,14 @@ function unwrapPrivateKey(token, wrapSaltHex, wrapped) {
33610
33817
  }
33611
33818
  function heyspexDir(vaultRoot) {
33612
33819
  const dir = join12(vaultRoot, ".heyspex");
33613
- mkdirSync6(dir, { recursive: true });
33820
+ mkdirSync7(dir, { recursive: true });
33614
33821
  return dir;
33615
33822
  }
33616
33823
  function memberTokenStorePath(vaultRoot) {
33617
33824
  return join12(vaultRoot, ".heyspex", STORE_FILE);
33618
33825
  }
33619
33826
  function memberTokenStorePresent(vaultRoot) {
33620
- return existsSync6(memberTokenStorePath(vaultRoot));
33827
+ return existsSync8(memberTokenStorePath(vaultRoot));
33621
33828
  }
33622
33829
  function sleepSync(ms) {
33623
33830
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
@@ -33632,8 +33839,8 @@ function acquireLock(lockPath) {
33632
33839
  } catch (err) {
33633
33840
  if (err?.code !== "EEXIST") throw err;
33634
33841
  try {
33635
- if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
33636
- unlinkSync(lockPath);
33842
+ if (Date.now() - statSync5(lockPath).mtimeMs > LOCK_STALE_MS) {
33843
+ unlinkSync2(lockPath);
33637
33844
  continue;
33638
33845
  }
33639
33846
  } catch {
@@ -33658,16 +33865,16 @@ function withTokenLock(vaultRoot, fn) {
33658
33865
  } catch {
33659
33866
  }
33660
33867
  try {
33661
- unlinkSync(lockPath);
33868
+ unlinkSync2(lockPath);
33662
33869
  } catch {
33663
33870
  }
33664
33871
  }
33665
33872
  }
33666
33873
  function readStore(vaultRoot) {
33667
33874
  const p = memberTokenStorePath(vaultRoot);
33668
- if (!existsSync6(p)) return [];
33875
+ if (!existsSync8(p)) return [];
33669
33876
  try {
33670
- const parsed = JSON.parse(readFileSync11(p, "utf8"));
33877
+ const parsed = JSON.parse(readFileSync13(p, "utf8"));
33671
33878
  return Array.isArray(parsed) ? parsed : [];
33672
33879
  } catch {
33673
33880
  return [];
@@ -33676,8 +33883,8 @@ function readStore(vaultRoot) {
33676
33883
  function writeStore(vaultRoot, records) {
33677
33884
  const p = join12(heyspexDir(vaultRoot), STORE_FILE);
33678
33885
  const tmp = `${p}.tmp`;
33679
- writeFileSync6(tmp, JSON.stringify(records, null, 2) + "\n", { mode: FILE_MODE });
33680
- renameSync4(tmp, p);
33886
+ writeFileSync7(tmp, JSON.stringify(records, null, 2) + "\n", { mode: FILE_MODE });
33887
+ renameSync5(tmp, p);
33681
33888
  }
33682
33889
  function isLive(r, nowMs) {
33683
33890
  if (r.revokedAt) return false;
@@ -33732,14 +33939,14 @@ import { randomBytes as randomBytes3, scryptSync as scryptSync2, timingSafeEqual
33732
33939
  import {
33733
33940
  appendFileSync as appendFileSync2,
33734
33941
  closeSync as closeSync2,
33735
- existsSync as existsSync8,
33736
- mkdirSync as mkdirSync7,
33942
+ existsSync as existsSync10,
33943
+ mkdirSync as mkdirSync8,
33737
33944
  openSync as openSync2,
33738
- readFileSync as readFileSync12,
33739
- renameSync as renameSync5,
33740
- statSync as statSync2,
33741
- unlinkSync as unlinkSync2,
33742
- writeFileSync as writeFileSync7,
33945
+ readFileSync as readFileSync14,
33946
+ renameSync as renameSync6,
33947
+ statSync as statSync6,
33948
+ unlinkSync as unlinkSync3,
33949
+ writeFileSync as writeFileSync8,
33743
33950
  writeSync as writeSync2
33744
33951
  } from "node:fs";
33745
33952
  import { join as join13 } from "node:path";
@@ -33747,9 +33954,9 @@ import { join as join13 } from "node:path";
33747
33954
  // spex-console/src/lib/env-vault/sops.ts
33748
33955
  import { spawnSync as spawnSync3 } from "node:child_process";
33749
33956
  import { createHash as createHash5 } from "node:crypto";
33750
- import { existsSync as existsSync7 } from "node:fs";
33751
- import { homedir as homedir10 } from "node:os";
33752
- import path6 from "node:path";
33957
+ import { existsSync as existsSync9 } from "node:fs";
33958
+ import { homedir as homedir11 } from "node:os";
33959
+ import path8 from "node:path";
33753
33960
  function ageRecipientFingerprint(recipient, length = 12) {
33754
33961
  return createHash5("sha256").update(recipient, "utf8").digest("hex").slice(0, length);
33755
33962
  }
@@ -33766,14 +33973,14 @@ var toolCache = /* @__PURE__ */ new Map();
33766
33973
  function withExtendedPath(extraDirs) {
33767
33974
  const env = { ...process.env };
33768
33975
  const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
33769
- env[key] = [env[key], ...extraDirs].filter(Boolean).join(path6.delimiter);
33976
+ env[key] = [env[key], ...extraDirs].filter(Boolean).join(path8.delimiter);
33770
33977
  return env;
33771
33978
  }
33772
33979
  function candidateInvocations(tool) {
33773
33980
  const candidates = [{ command: tool, env: process.env }];
33774
33981
  const local = process.env.LOCALAPPDATA;
33775
33982
  if (local) {
33776
- const miseBin = path6.join(
33983
+ const miseBin = path8.join(
33777
33984
  local,
33778
33985
  "Microsoft",
33779
33986
  "WinGet",
@@ -33782,14 +33989,14 @@ function candidateInvocations(tool) {
33782
33989
  "mise",
33783
33990
  "bin"
33784
33991
  );
33785
- const extra = existsSync7(miseBin) ? [miseBin] : [];
33992
+ const extra = existsSync9(miseBin) ? [miseBin] : [];
33786
33993
  candidates.push({
33787
- command: path6.join(local, "mise", "shims", `${tool}.exe`),
33994
+ command: path8.join(local, "mise", "shims", `${tool}.exe`),
33788
33995
  env: withExtendedPath(extra)
33789
33996
  });
33790
33997
  }
33791
33998
  candidates.push({
33792
- command: path6.join(homedir10(), ".local", "share", "mise", "shims", tool),
33999
+ command: path8.join(homedir11(), ".local", "share", "mise", "shims", tool),
33793
34000
  env: process.env
33794
34001
  });
33795
34002
  return candidates;
@@ -34114,14 +34321,14 @@ function hashEquals2(a, b) {
34114
34321
  }
34115
34322
  function heyspexDir2(vaultRoot) {
34116
34323
  const dir = join13(vaultRoot, ".heyspex");
34117
- mkdirSync7(dir, { recursive: true });
34324
+ mkdirSync8(dir, { recursive: true });
34118
34325
  return dir;
34119
34326
  }
34120
34327
  function envPairingStorePath(vaultRoot) {
34121
34328
  return join13(vaultRoot, ".heyspex", STORE_FILE2);
34122
34329
  }
34123
34330
  function envPairingStorePresent(vaultRoot) {
34124
- return existsSync8(envPairingStorePath(vaultRoot));
34331
+ return existsSync10(envPairingStorePath(vaultRoot));
34125
34332
  }
34126
34333
  function auditPath(vaultRoot) {
34127
34334
  return join13(heyspexDir2(vaultRoot), AUDIT_FILE);
@@ -34139,8 +34346,8 @@ function acquireLock2(lockPath) {
34139
34346
  } catch (err) {
34140
34347
  if (err?.code !== "EEXIST") throw err;
34141
34348
  try {
34142
- if (Date.now() - statSync2(lockPath).mtimeMs > LOCK_STALE_MS2) {
34143
- unlinkSync2(lockPath);
34349
+ if (Date.now() - statSync6(lockPath).mtimeMs > LOCK_STALE_MS2) {
34350
+ unlinkSync3(lockPath);
34144
34351
  continue;
34145
34352
  }
34146
34353
  } catch {
@@ -34165,16 +34372,16 @@ function withPairingLock(vaultRoot, fn) {
34165
34372
  } catch {
34166
34373
  }
34167
34374
  try {
34168
- unlinkSync2(lockPath);
34375
+ unlinkSync3(lockPath);
34169
34376
  } catch {
34170
34377
  }
34171
34378
  }
34172
34379
  }
34173
34380
  function readStore2(vaultRoot) {
34174
34381
  const p = envPairingStorePath(vaultRoot);
34175
- if (!existsSync8(p)) return [];
34382
+ if (!existsSync10(p)) return [];
34176
34383
  try {
34177
- const parsed = JSON.parse(readFileSync12(p, "utf8"));
34384
+ const parsed = JSON.parse(readFileSync14(p, "utf8"));
34178
34385
  return Array.isArray(parsed) ? parsed : [];
34179
34386
  } catch {
34180
34387
  return [];
@@ -34190,8 +34397,8 @@ function prune(records) {
34190
34397
  function writeStore2(vaultRoot, records) {
34191
34398
  const p = join13(heyspexDir2(vaultRoot), STORE_FILE2);
34192
34399
  const tmp = `${p}.tmp`;
34193
- writeFileSync7(tmp, JSON.stringify(prune(records), null, 2) + "\n", { mode: FILE_MODE2 });
34194
- renameSync5(tmp, p);
34400
+ writeFileSync8(tmp, JSON.stringify(prune(records), null, 2) + "\n", { mode: FILE_MODE2 });
34401
+ renameSync6(tmp, p);
34195
34402
  }
34196
34403
  function appendEnvPairingAudit(vaultRoot, event) {
34197
34404
  const line = {
@@ -34209,14 +34416,14 @@ function strikesPath(vaultRoot) {
34209
34416
  }
34210
34417
  function readStrikes(vaultRoot) {
34211
34418
  try {
34212
- const parsed = JSON.parse(readFileSync12(strikesPath(vaultRoot), "utf8"));
34419
+ const parsed = JSON.parse(readFileSync14(strikesPath(vaultRoot), "utf8"));
34213
34420
  return typeof parsed.count === "number" && Number.isFinite(parsed.count) ? Math.max(0, Math.floor(parsed.count)) : 0;
34214
34421
  } catch {
34215
34422
  return 0;
34216
34423
  }
34217
34424
  }
34218
34425
  function writeStrikes(vaultRoot, count) {
34219
- writeFileSync7(
34426
+ writeFileSync8(
34220
34427
  strikesPath(vaultRoot),
34221
34428
  JSON.stringify({ count, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n",
34222
34429
  { mode: FILE_MODE2 }
@@ -34425,8 +34632,8 @@ function markEnvInviteApplied(vaultRoot, id) {
34425
34632
  }
34426
34633
 
34427
34634
  // spex-console/src/lib/env-vault/policy.ts
34428
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "node:fs";
34429
- import path7 from "node:path";
34635
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync15, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "node:fs";
34636
+ import path9 from "node:path";
34430
34637
  var POLICY_REL_PATH = ".heyspex/env/policy.json";
34431
34638
  var POLICY_VERSION = 1;
34432
34639
  var MODES = ["allow", "ask", "deny"];
@@ -34434,7 +34641,7 @@ function isPolicyMode(value) {
34434
34641
  return typeof value === "string" && MODES.includes(value);
34435
34642
  }
34436
34643
  function policyPath(vaultRoot) {
34437
- return path7.join(vaultRoot, ".heyspex", "env", "policy.json");
34644
+ return path9.join(vaultRoot, ".heyspex", "env", "policy.json");
34438
34645
  }
34439
34646
  function parsePolicyFile(text2) {
34440
34647
  let raw;
@@ -34458,7 +34665,7 @@ function readPolicy(vaultRoot, profile) {
34458
34665
  const file = policyPath(vaultRoot);
34459
34666
  let text2;
34460
34667
  try {
34461
- text2 = readFileSync13(file, "utf-8");
34668
+ text2 = readFileSync15(file, "utf-8");
34462
34669
  } catch {
34463
34670
  return { mode: "allow", source: "default" };
34464
34671
  }
@@ -34478,8 +34685,8 @@ function serializePolicyFile(file) {
34478
34685
  }
34479
34686
  function loadForWrite(vaultRoot) {
34480
34687
  const file = policyPath(vaultRoot);
34481
- if (!existsSync9(file)) return { version: POLICY_VERSION, profiles: {} };
34482
- const parsed = parsePolicyFile(readFileSync13(file, "utf-8"));
34688
+ if (!existsSync11(file)) return { version: POLICY_VERSION, profiles: {} };
34689
+ const parsed = parsePolicyFile(readFileSync15(file, "utf-8"));
34483
34690
  if (parsed === null) {
34484
34691
  throw new Error(
34485
34692
  `${POLICY_REL_PATH} exists but is not a valid v${POLICY_VERSION} policy file \u2014 fix or remove it before changing policy (refusing to overwrite what it meant to say).`
@@ -34489,10 +34696,10 @@ function loadForWrite(vaultRoot) {
34489
34696
  }
34490
34697
  function writePolicyFile(vaultRoot, file) {
34491
34698
  const target = policyPath(vaultRoot);
34492
- mkdirSync8(path7.dirname(target), { recursive: true });
34699
+ mkdirSync9(path9.dirname(target), { recursive: true });
34493
34700
  const tmp = `${target}.tmp`;
34494
- writeFileSync8(tmp, serializePolicyFile(file), "utf-8");
34495
- renameSync6(tmp, target);
34701
+ writeFileSync9(tmp, serializePolicyFile(file), "utf-8");
34702
+ renameSync7(tmp, target);
34496
34703
  }
34497
34704
  function writePolicy(vaultRoot, profile, mode) {
34498
34705
  if (!isPolicyMode(mode)) {
@@ -34504,7 +34711,7 @@ function writePolicy(vaultRoot, profile, mode) {
34504
34711
  }
34505
34712
  function clearPolicy(vaultRoot, profile) {
34506
34713
  const file = policyPath(vaultRoot);
34507
- if (!existsSync9(file)) return;
34714
+ if (!existsSync11(file)) return;
34508
34715
  const parsed = loadForWrite(vaultRoot);
34509
34716
  if (!(profile in parsed.profiles)) return;
34510
34717
  delete parsed.profiles[profile];
@@ -36111,17 +36318,17 @@ import { spawnSync as spawnSync4 } from "node:child_process";
36111
36318
  import { randomBytes as randomBytes5 } from "node:crypto";
36112
36319
  import {
36113
36320
  chmodSync,
36114
- existsSync as existsSync10,
36115
- mkdirSync as mkdirSync9,
36321
+ existsSync as existsSync12,
36322
+ mkdirSync as mkdirSync10,
36116
36323
  readdirSync as readdirSync3,
36117
- readFileSync as readFileSync14,
36118
- renameSync as renameSync7,
36119
- statSync as statSync3,
36120
- unlinkSync as unlinkSync3,
36121
- writeFileSync as writeFileSync9
36324
+ readFileSync as readFileSync16,
36325
+ renameSync as renameSync8,
36326
+ statSync as statSync7,
36327
+ unlinkSync as unlinkSync4,
36328
+ writeFileSync as writeFileSync10
36122
36329
  } from "node:fs";
36123
- import { homedir as homedir12 } from "node:os";
36124
- import path8 from "node:path";
36330
+ import { homedir as homedir13 } from "node:os";
36331
+ import path10 from "node:path";
36125
36332
 
36126
36333
  // node_modules/sops-age/dist/index.js
36127
36334
  var import_dotenv = __toESM(require_main(), 1);
@@ -36605,8 +36812,8 @@ function getErrorMap() {
36605
36812
 
36606
36813
  // node_modules/zod/v3/helpers/parseUtil.js
36607
36814
  var makeIssue = (params) => {
36608
- const { data, path: path9, errorMaps, issueData } = params;
36609
- const fullPath = [...path9, ...issueData.path || []];
36815
+ const { data, path: path11, errorMaps, issueData } = params;
36816
+ const fullPath = [...path11, ...issueData.path || []];
36610
36817
  const fullIssue = {
36611
36818
  ...issueData,
36612
36819
  path: fullPath
@@ -36722,11 +36929,11 @@ var errorUtil;
36722
36929
 
36723
36930
  // node_modules/zod/v3/types.js
36724
36931
  var ParseInputLazyPath = class {
36725
- constructor(parent, value, path9, key) {
36932
+ constructor(parent, value, path11, key) {
36726
36933
  this._cachedPath = [];
36727
36934
  this.parent = parent;
36728
36935
  this.data = value;
36729
- this._path = path9;
36936
+ this._path = path11;
36730
36937
  this._key = key;
36731
36938
  }
36732
36939
  get path() {
@@ -44791,22 +44998,22 @@ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength
44791
44998
  // node_modules/sops-age/dist/index.js
44792
44999
  import { readFile } from "node:fs/promises";
44793
45000
  import { join as join15 } from "node:path";
44794
- import { homedir as homedir11, platform } from "node:os";
45001
+ import { homedir as homedir12, platform } from "node:os";
44795
45002
  import { exec } from "node:child_process";
44796
45003
  import { promisify } from "node:util";
44797
45004
  var import_sshpk = __toESM(require_lib2(), 1);
44798
- async function loadFromFile(path9) {
45005
+ async function loadFromFile(path11) {
44799
45006
  if (globalThis.Deno) {
44800
- return Deno.readTextFile(path9);
45007
+ return Deno.readTextFile(path11);
44801
45008
  }
44802
45009
  if (globalThis.Bun) {
44803
- return Bun.file(path9).text();
45010
+ return Bun.file(path11).text();
44804
45011
  }
44805
45012
  if (typeof process !== "undefined" && process.versions?.node) {
44806
45013
  const fs4 = await import("fs/promises");
44807
- return fs4.readFile(path9, "utf-8");
45014
+ return fs4.readFile(path11, "utf-8");
44808
45015
  }
44809
- throw new Error(`Unable to determine method to load file "${path9}"`);
45016
+ throw new Error(`Unable to determine method to load file "${path11}"`);
44810
45017
  }
44811
45018
  function isSopsInput(value) {
44812
45019
  if (!value) {
@@ -44914,13 +45121,13 @@ async function parseSops(input, sopsFileType) {
44914
45121
  });
44915
45122
  }
44916
45123
  }
44917
- async function loadSopsFile(path9, sopsFileType) {
45124
+ async function loadSopsFile(path11, sopsFileType) {
44918
45125
  try {
44919
- const content = await loadFromFile(path9);
45126
+ const content = await loadFromFile(path11);
44920
45127
  return await parseSops(content, sopsFileType);
44921
45128
  } catch (err) {
44922
45129
  throw new Error(
44923
- `Failed to load SOPS file '${path9}': ${err.message}`,
45130
+ `Failed to load SOPS file '${path11}': ${err.message}`,
44924
45131
  {
44925
45132
  cause: err
44926
45133
  }
@@ -45132,11 +45339,11 @@ async function getUserConfigDir() {
45132
45339
  if (!appData) throw new Error("APPDATA env var not set");
45133
45340
  return appData;
45134
45341
  case "darwin":
45135
- return join15(homedir11(), "Library", "Application Support");
45342
+ return join15(homedir12(), "Library", "Application Support");
45136
45343
  default:
45137
45344
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
45138
45345
  if (xdgConfigHome && xdgConfigHome.trim() !== "") return xdgConfigHome;
45139
- return join15(homedir11(), ".config");
45346
+ return join15(homedir12(), ".config");
45140
45347
  }
45141
45348
  }
45142
45349
  async function findAllAgeKeys() {
@@ -45157,7 +45364,7 @@ async function findAllAgeKeys() {
45157
45364
  );
45158
45365
  }
45159
45366
  } else {
45160
- const userHomeDir = homedir11();
45367
+ const userHomeDir = homedir12();
45161
45368
  if (userHomeDir) {
45162
45369
  const defaultSshPaths = [
45163
45370
  join15(userHomeDir, ".ssh", "id_ed25519"),
@@ -45337,15 +45544,15 @@ async function getSopsEncryptionKey(sops, secretKeys) {
45337
45544
  ${errors.join("\n")}`
45338
45545
  );
45339
45546
  }
45340
- function path2gopath(path9) {
45341
- return `${path9.filter((x) => !/^\d+$/.test(x)).join(":")}:`;
45547
+ function path2gopath(path11) {
45548
+ return `${path11.filter((x) => !/^\d+$/.test(x)).join(":")}:`;
45342
45549
  }
45343
- function decryptSOPSValue(ciphertext, decryptionKey, path9) {
45550
+ function decryptSOPSValue(ciphertext, decryptionKey, path11) {
45344
45551
  if (!ciphertext) {
45345
45552
  return "";
45346
45553
  }
45347
45554
  const encryptedValue = parse2(ciphertext);
45348
- const aad = path2gopath(path9);
45555
+ const aad = path2gopath(path11);
45349
45556
  let decrypted;
45350
45557
  try {
45351
45558
  decrypted = decryptAesGcm(
@@ -45355,22 +45562,22 @@ function decryptSOPSValue(ciphertext, decryptionKey, path9) {
45355
45562
  );
45356
45563
  } catch (err) {
45357
45564
  throw new Error(
45358
- `AES-GCM decryption failed at path ${JSON.stringify(path9)} for value "${ciphertext}": ${err instanceof Error ? err.message : String(err)}`
45565
+ `AES-GCM decryption failed at path ${JSON.stringify(path11)} for value "${ciphertext}": ${err instanceof Error ? err.message : String(err)}`
45359
45566
  );
45360
45567
  }
45361
45568
  const decryptedValue = new TextDecoder().decode(decrypted);
45362
45569
  return convertDecryptedValue(decryptedValue, encryptedValue.datatype);
45363
45570
  }
45364
- function decryptObject(obj, decryptionKey, path9 = []) {
45571
+ function decryptObject(obj, decryptionKey, path11 = []) {
45365
45572
  if (typeof obj !== "object" || obj === null) {
45366
45573
  return obj;
45367
45574
  }
45368
45575
  for (const key of Object.keys(obj)) {
45369
45576
  const value = obj[key];
45370
45577
  if (typeof value === "string" && value.startsWith("ENC[AES256_GCM,data:")) {
45371
- obj[key] = decryptSOPSValue(value, decryptionKey, [...path9, key]);
45578
+ obj[key] = decryptSOPSValue(value, decryptionKey, [...path11, key]);
45372
45579
  } else if (typeof value === "object") {
45373
- obj[key] = decryptObject(value, decryptionKey, [...path9, key]);
45580
+ obj[key] = decryptObject(value, decryptionKey, [...path11, key]);
45374
45581
  }
45375
45582
  }
45376
45583
  return obj;
@@ -45722,23 +45929,23 @@ function isExpiredDate(expires, today = todayIso()) {
45722
45929
  return expires !== null && today > expires;
45723
45930
  }
45724
45931
  function envDir(vaultRoot) {
45725
- return path8.join(vaultRoot, ".heyspex", "env");
45932
+ return path10.join(vaultRoot, ".heyspex", "env");
45726
45933
  }
45727
45934
  function profilePath(vaultRoot, name) {
45728
- return path8.join(envDir(vaultRoot), `${name}.env`);
45935
+ return path10.join(envDir(vaultRoot), `${name}.env`);
45729
45936
  }
45730
45937
  function keysPath(vaultRoot) {
45731
- return path8.join(vaultRoot, ".heyspex", "env.keys");
45938
+ return path10.join(vaultRoot, ".heyspex", "env.keys");
45732
45939
  }
45733
45940
  function homeKeysPath() {
45734
- return path8.join(process.env.HEYSPEX_ENV_KEYS_HOME || homedir12(), ".heyspex", "env.keys");
45941
+ return path10.join(process.env.HEYSPEX_ENV_KEYS_HOME || homedir13(), ".heyspex", "env.keys");
45735
45942
  }
45736
45943
  function dotenvPath(vaultRoot) {
45737
- return path8.join(vaultRoot, ".env");
45944
+ return path10.join(vaultRoot, ".env");
45738
45945
  }
45739
45946
  function readOrNull(file) {
45740
45947
  try {
45741
- return readFileSync14(file, "utf-8");
45948
+ return readFileSync16(file, "utf-8");
45742
45949
  } catch {
45743
45950
  return null;
45744
45951
  }
@@ -45752,7 +45959,7 @@ function renameWithRetry(tmp, file) {
45752
45959
  const retryable = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
45753
45960
  for (let attempt = 0; ; attempt++) {
45754
45961
  try {
45755
- renameSync7(tmp, file);
45962
+ renameSync8(tmp, file);
45756
45963
  return;
45757
45964
  } catch (err) {
45758
45965
  const code = err.code ?? "";
@@ -45763,21 +45970,21 @@ function renameWithRetry(tmp, file) {
45763
45970
  }
45764
45971
  function unlinkQuiet(file) {
45765
45972
  try {
45766
- unlinkSync3(file);
45973
+ unlinkSync4(file);
45767
45974
  } catch {
45768
45975
  }
45769
45976
  }
45770
45977
  function atomicWrite(file, content, mode = 420) {
45771
45978
  const secret = mode === SECRET_MODE;
45772
- const dir = path8.dirname(file);
45773
- mkdirSync9(dir, { recursive: true, ...secret ? { mode: SECRET_DIR_MODE } : {} });
45774
- const tmp = path8.join(
45979
+ const dir = path10.dirname(file);
45980
+ mkdirSync10(dir, { recursive: true, ...secret ? { mode: SECRET_DIR_MODE } : {} });
45981
+ const tmp = path10.join(
45775
45982
  dir,
45776
- `.${path8.basename(file)}.${process.pid}.${randomBytes5(4).toString("hex")}.tmp`
45983
+ `.${path10.basename(file)}.${process.pid}.${randomBytes5(4).toString("hex")}.tmp`
45777
45984
  );
45778
45985
  let renamed = false;
45779
45986
  try {
45780
- writeFileSync9(tmp, content, { encoding: "utf-8", mode });
45987
+ writeFileSync10(tmp, content, { encoding: "utf-8", mode });
45781
45988
  if (secret) chmodOwnerOnly(tmp);
45782
45989
  renameWithRetry(tmp, file);
45783
45990
  renamed = true;
@@ -45791,18 +45998,18 @@ var LOCK_RETRY_MS3 = 50;
45791
45998
  var LOCK_STALE_MS3 = 5e3;
45792
45999
  function withFileLock(file, fn) {
45793
46000
  const lock = `${file}.lock`;
45794
- mkdirSync9(path8.dirname(file), { recursive: true, mode: SECRET_DIR_MODE });
46001
+ mkdirSync10(path10.dirname(file), { recursive: true, mode: SECRET_DIR_MODE });
45795
46002
  let held = false;
45796
46003
  for (let attempt = 0; attempt < LOCK_RETRIES && !held; attempt++) {
45797
46004
  try {
45798
- writeFileSync9(lock, `${process.pid}
46005
+ writeFileSync10(lock, `${process.pid}
45799
46006
  `, { flag: "wx" });
45800
46007
  held = true;
45801
46008
  } catch (err) {
45802
46009
  if (err.code !== "EEXIST") throw err;
45803
46010
  let ageMs = Infinity;
45804
46011
  try {
45805
- ageMs = Date.now() - statSync3(lock).mtimeMs;
46012
+ ageMs = Date.now() - statSync7(lock).mtimeMs;
45806
46013
  } catch {
45807
46014
  ageMs = Infinity;
45808
46015
  }
@@ -45844,7 +46051,7 @@ function writeHomeKeyLine(profileName, privateKey, vaultId) {
45844
46051
  }
45845
46052
  function readVaultId(vaultRoot) {
45846
46053
  try {
45847
- const raw = readOrNull(path8.join(vaultRoot, "heyspex.yaml"));
46054
+ const raw = readOrNull(path10.join(vaultRoot, "heyspex.yaml"));
45848
46055
  return raw === null ? null : parseVaultId(raw);
45849
46056
  } catch {
45850
46057
  return null;
@@ -45987,7 +46194,7 @@ function legacyDecryptAllSync(vaultRoot, name) {
45987
46194
  }
45988
46195
  function initProfile(vaultRoot, name = DEFAULT_PROFILE2, opts = {}) {
45989
46196
  const pFile = profilePath(vaultRoot, name);
45990
- if (existsSync10(pFile) && !opts.force) {
46197
+ if (existsSync12(pFile) && !opts.force) {
45991
46198
  throw new EnvVaultConfigError(
45992
46199
  `Env profile "${name}" already exists at ${pFile}. Pass --force to re-key it (this discards existing values and mints a new keypair).`
45993
46200
  );
@@ -46029,7 +46236,7 @@ function saveProfile(vaultRoot, name, profile) {
46029
46236
  function listProfiles(vaultRoot) {
46030
46237
  const dir = envDir(vaultRoot);
46031
46238
  try {
46032
- if (!statSync3(dir).isDirectory()) return [];
46239
+ if (!statSync7(dir).isDirectory()) return [];
46033
46240
  } catch {
46034
46241
  return [];
46035
46242
  }
@@ -46373,7 +46580,7 @@ function removeGrant(vaultRoot, profileName, name, opts = {}) {
46373
46580
  }
46374
46581
  async function exportProfile(vaultRoot, name = DEFAULT_PROFILE2, opts = {}) {
46375
46582
  const target = dotenvPath(vaultRoot);
46376
- if (existsSync10(target) && !opts.force) {
46583
+ if (existsSync12(target) && !opts.force) {
46377
46584
  throw new EnvVaultConfigError(
46378
46585
  `${target} already exists. Pass --force to overwrite it (it is disposable \u2014 the encrypted profile is the system of record).`
46379
46586
  );
@@ -46385,10 +46592,10 @@ async function exportProfile(vaultRoot, name = DEFAULT_PROFILE2, opts = {}) {
46385
46592
  return { path: target };
46386
46593
  }
46387
46594
  function resolveVaultPath(vaultRoot, filePath) {
46388
- const root = path8.resolve(vaultRoot);
46389
- const resolved = path8.resolve(root, filePath);
46390
- const rel = path8.relative(root, resolved);
46391
- if (path8.isAbsolute(filePath) || path8.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path8.sep}`)) {
46595
+ const root = path10.resolve(vaultRoot);
46596
+ const resolved = path10.resolve(root, filePath);
46597
+ const rel = path10.relative(root, resolved);
46598
+ if (path10.isAbsolute(filePath) || path10.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path10.sep}`)) {
46392
46599
  throw new EnvVaultConfigError(
46393
46600
  `Refusing to touch "${filePath}" \u2014 it resolves outside the vault root (${root}). Vault file paths must stay inside the repo (no "..", no absolute paths).`
46394
46601
  );
@@ -46402,12 +46609,12 @@ function writeTextFile(vaultRoot, filePath, content) {
46402
46609
  atomicWrite(resolveVaultPath(vaultRoot, filePath), content, SECRET_MODE);
46403
46610
  }
46404
46611
  function pathExists(vaultRoot, filePath) {
46405
- return existsSync10(resolveVaultPath(vaultRoot, filePath));
46612
+ return existsSync12(resolveVaultPath(vaultRoot, filePath));
46406
46613
  }
46407
46614
  function fileMtime(vaultRoot, filePath) {
46408
46615
  const resolved = resolveVaultPath(vaultRoot, filePath);
46409
46616
  try {
46410
- return statSync3(resolved).mtimeMs;
46617
+ return statSync7(resolved).mtimeMs;
46411
46618
  } catch {
46412
46619
  return null;
46413
46620
  }
@@ -46436,7 +46643,7 @@ function listRepoSourceFiles(vaultRoot) {
46436
46643
  for (const entry of entries) {
46437
46644
  const childRel = rel === "" ? entry.name : `${rel}/${entry.name}`;
46438
46645
  if (entry.isDirectory()) {
46439
- if (!WALK_SKIP_DIRS.has(entry.name)) walk(path8.join(dir, entry.name), childRel);
46646
+ if (!WALK_SKIP_DIRS.has(entry.name)) walk(path10.join(dir, entry.name), childRel);
46440
46647
  } else if (entry.isFile()) {
46441
46648
  out.push(childRel);
46442
46649
  }
@@ -46462,7 +46669,7 @@ function isKeysFileGitignored(vaultRoot) {
46462
46669
  if (res.status === 1) return false;
46463
46670
  } catch {
46464
46671
  }
46465
- const gitignore = readOrNull(path8.join(vaultRoot, ".gitignore"));
46672
+ const gitignore = readOrNull(path10.join(vaultRoot, ".gitignore"));
46466
46673
  if (gitignore === null) return false;
46467
46674
  const ignorePatterns = /* @__PURE__ */ new Set([
46468
46675
  ".heyspex/env.keys",
@@ -46584,7 +46791,7 @@ async function confirm(question) {
46584
46791
  function findRepoRoot(start) {
46585
46792
  let dir = resolve5(start);
46586
46793
  for (; ; ) {
46587
- if (existsSync11(join16(dir, ".git"))) return dir;
46794
+ if (existsSync13(join16(dir, ".git"))) return dir;
46588
46795
  const parent = dirname6(dir);
46589
46796
  if (parent === dir) return null;
46590
46797
  dir = parent;
@@ -46592,7 +46799,7 @@ function findRepoRoot(start) {
46592
46799
  }
46593
46800
  function readFile2(p) {
46594
46801
  try {
46595
- return readFileSync15(p, "utf-8");
46802
+ return readFileSync17(p, "utf-8");
46596
46803
  } catch {
46597
46804
  return null;
46598
46805
  }
@@ -46600,12 +46807,12 @@ function readFile2(p) {
46600
46807
  function connectCliDeps(repoRoot) {
46601
46808
  return {
46602
46809
  repoRoot,
46603
- fileExists: (p) => existsSync11(p),
46810
+ fileExists: (p) => existsSync13(p),
46604
46811
  readFile: readFile2,
46605
- writeFile: (p, c) => writeFileSync10(p, c, "utf-8"),
46606
- mkdir: (p) => mkdirSync10(p, { recursive: true }),
46812
+ writeFile: (p, c) => writeFileSync11(p, c, "utf-8"),
46813
+ mkdir: (p) => mkdirSync11(p, { recursive: true }),
46607
46814
  joinPath: (...s) => join16(...s),
46608
- basename: (p) => basename4(p),
46815
+ basename: (p) => basename5(p),
46609
46816
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
46610
46817
  spawn: (command, spawnArgs, opts) => {
46611
46818
  const res = spawnSync5(command, spawnArgs, {
@@ -46654,7 +46861,7 @@ async function main() {
46654
46861
  const launchCwd = process4.env.HS_JOIN_CWD ? resolve5(process4.env.HS_JOIN_CWD) : process4.cwd();
46655
46862
  const walked = findRepoRoot(launchCwd);
46656
46863
  const rawHint = walked ?? (process4.env.HS_VAULT_ROOT ? resolve5(process4.env.HS_VAULT_ROOT) : null);
46657
- const repoRootHint = rawHint && existsSync11(join16(rawHint, "package.json")) ? rawHint : null;
46864
+ const repoRootHint = rawHint && existsSync13(join16(rawHint, "package.json")) ? rawHint : null;
46658
46865
  let userName = null;
46659
46866
  try {
46660
46867
  userName = userInfo().username || null;
@@ -46667,7 +46874,7 @@ async function main() {
46667
46874
  platform: process4.platform,
46668
46875
  interactive: INTERACTIVE,
46669
46876
  userName,
46670
- pathExists: (p) => existsSync11(p),
46877
+ pathExists: (p) => existsSync13(p),
46671
46878
  readFile: readFile2,
46672
46879
  joinPath: (...s) => join16(...s),
46673
46880
  resolvePath: (...s) => resolve5(...s),
@@ -46716,7 +46923,7 @@ async function main() {
46716
46923
  connect: {
46717
46924
  isConnected: (repoRoot) => Promise.resolve(
46718
46925
  runConnectChecks(repoRoot, {
46719
- fileExists: (p) => existsSync11(p),
46926
+ fileExists: (p) => existsSync13(p),
46720
46927
  readFile: readFile2,
46721
46928
  joinPath: (...s) => join16(...s)
46722
46929
  }).connected