heyspex 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/hs-vault-binding.mjs +157 -0
- package/bin/hs.mjs +81 -77
- package/bin/spexcode.mjs +91 -0
- package/dev-factory/engine/worktree.mjs +36 -7
- package/dev-factory/lib/epic-detect.mjs +2 -2
- package/dev-factory/lib/fleet-readiness.mjs +3 -5
- package/dev-factory/lib/fleet-state.mjs +2 -2
- package/dev-factory/lib/sufficiency-score.mjs +3 -5
- package/dist/cli/hs-check.mjs +76 -46
- package/dist/cli/hs-claim.mjs +63 -33
- package/dist/cli/hs-connect.mjs +520 -214
- package/dist/cli/hs-defer.mjs +161 -19
- package/dist/cli/hs-dev.mjs +11 -5
- package/dist/cli/hs-env.mjs +187 -161
- package/dist/cli/hs-index-rebuild.mjs +123 -78
- package/dist/cli/hs-index-reconcile.mjs +203 -107
- package/dist/cli/hs-init.mjs +426 -179
- package/dist/cli/hs-install.mjs +759 -437
- package/dist/cli/hs-join.mjs +590 -383
- package/dist/cli/hs-vault-pull.mjs +359 -254
- package/dist/cli/hs-vault-push.mjs +151 -113
- package/dist/cli/hs-vault.mjs +271 -104
- package/dist/cli/hs-workspace.mjs +90 -57
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
- package/docs/copilot-plugin-install.md +189 -177
- package/docs/driving-heyspex-from-copilot.md +174 -165
- package/docs/mcp-client-config.md +93 -25
- package/package.json +255 -250
- package/plugins/heyspex/.mcp.json +3 -4
- package/plugins/heyspex/README.md +323 -312
- package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
- package/plugins/heyspex/mcp.json +12 -13
- package/scripts/check-mcp-freshness.mjs +6 -4
- package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
- package/scripts/claude-hooks/research-method-guard.mjs +2 -2
- package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
- package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
- package/scripts/claude-statusline/statusline-command.mjs +1058 -0
- package/scripts/dev-server-status.mjs +3 -2
- package/scripts/dev-server-stop.mjs +3 -2
- package/scripts/hs-capture-cli.mjs +2 -2
- package/scripts/hs-settings.mjs +2 -2
- package/scripts/hs-view.mjs +3 -2
- package/scripts/install-copilot-hooks.mjs +64 -34
- package/scripts/lib/copilot-cli.mjs +108 -0
- package/scripts/lib/customer-kit.mjs +223 -30
- package/scripts/lib/dev-mode.mjs +28 -2
- package/scripts/lib/shared-path-helpers.mjs +32 -0
- package/scripts/lib/standalone-doctor.mjs +131 -32
- package/scripts/wip-ledger-health.mjs +3 -2
- package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
- package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
- package/spexharness/src/intent-directive.mjs +839 -626
- package/spexharness/src/rule-table.mjs +1 -1
- package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
- package/vendor/heyspex-mcp/cli.mjs +20 -4
- package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
- package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
- package/vendor/heyspex-mcp/events-tools.mjs +1 -1
- package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
- package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
- package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +59 -23
- package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
- package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
package/dist/cli/hs-install.mjs
CHANGED
|
@@ -154,9 +154,9 @@ function resolveBearer({ envToken, credentialsText, accountId, legacyAccountId }
|
|
|
154
154
|
if (entry?.mcp_token) return { token: entry.mcp_token, source: "credentials-file" };
|
|
155
155
|
return { token: null, source: null };
|
|
156
156
|
}
|
|
157
|
-
function readTextOrNull(read,
|
|
157
|
+
function readTextOrNull(read, path7) {
|
|
158
158
|
try {
|
|
159
|
-
const raw = read(
|
|
159
|
+
const raw = read(path7);
|
|
160
160
|
return typeof raw === "string" ? raw : null;
|
|
161
161
|
} catch {
|
|
162
162
|
return null;
|
|
@@ -260,6 +260,11 @@ function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultRea
|
|
|
260
260
|
return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
+
function overrideCloudCheck(source, cloudKnown, staleAgainstCloud) {
|
|
264
|
+
if (source !== "local-override") return "not-applicable";
|
|
265
|
+
if (!cloudKnown) return "unverified";
|
|
266
|
+
return staleAgainstCloud ? "stale" : "in-sync";
|
|
267
|
+
}
|
|
263
268
|
function resolveDevMode({
|
|
264
269
|
env = process.env,
|
|
265
270
|
homeDir = heyspexHome(),
|
|
@@ -299,6 +304,7 @@ function resolveDevMode({
|
|
|
299
304
|
const semantics = deriveSemantics(mode, reliance);
|
|
300
305
|
const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
|
|
301
306
|
const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
|
|
307
|
+
const cloudCheck = overrideCloudCheck(source, cloudKnown, staleAgainstCloud);
|
|
302
308
|
return {
|
|
303
309
|
mode,
|
|
304
310
|
reliance: semantics.reliance,
|
|
@@ -316,7 +322,8 @@ function resolveDevMode({
|
|
|
316
322
|
present: local.record !== null,
|
|
317
323
|
record: local.record,
|
|
318
324
|
parseError: local.parseError,
|
|
319
|
-
staleAgainstCloud
|
|
325
|
+
staleAgainstCloud,
|
|
326
|
+
cloudCheck
|
|
320
327
|
},
|
|
321
328
|
cloudSetting: { known: cloudKnown, value: cloudValue }
|
|
322
329
|
};
|
|
@@ -996,17 +1003,17 @@ var require_gray_matter = __commonJS({
|
|
|
996
1003
|
});
|
|
997
1004
|
|
|
998
1005
|
// spex-console/bin/hs-install.ts
|
|
999
|
-
import { spawnSync as
|
|
1006
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1000
1007
|
import {
|
|
1001
|
-
copyFileSync,
|
|
1002
|
-
existsSync as
|
|
1003
|
-
mkdirSync as
|
|
1008
|
+
copyFileSync as copyFileSync2,
|
|
1009
|
+
existsSync as existsSync9,
|
|
1010
|
+
mkdirSync as mkdirSync7,
|
|
1004
1011
|
readdirSync as readdirSync2,
|
|
1005
|
-
readFileSync as
|
|
1006
|
-
statSync,
|
|
1007
|
-
writeFileSync as
|
|
1012
|
+
readFileSync as readFileSync14,
|
|
1013
|
+
statSync as statSync5,
|
|
1014
|
+
writeFileSync as writeFileSync7
|
|
1008
1015
|
} from "node:fs";
|
|
1009
|
-
import { join as join12, resolve as
|
|
1016
|
+
import { join as join12, resolve as resolve6 } from "node:path";
|
|
1010
1017
|
import process4 from "node:process";
|
|
1011
1018
|
|
|
1012
1019
|
// scripts/lib/console-url-resolver.mjs
|
|
@@ -1115,7 +1122,9 @@ function resolveConsoleUrl({
|
|
|
1115
1122
|
}
|
|
1116
1123
|
|
|
1117
1124
|
// scripts/lib/shared-path-helpers.mjs
|
|
1125
|
+
import { realpathSync } from "node:fs";
|
|
1118
1126
|
import { isAbsolute, resolve as resolve2, sep } from "node:path";
|
|
1127
|
+
import { fileURLToPath } from "node:url";
|
|
1119
1128
|
function resolveCodeRoot({
|
|
1120
1129
|
explicit,
|
|
1121
1130
|
packageRoot,
|
|
@@ -1133,16 +1142,16 @@ function resolveCodeRoot({
|
|
|
1133
1142
|
}
|
|
1134
1143
|
|
|
1135
1144
|
// spex-console/src/lib/cli/control-shared.ts
|
|
1136
|
-
import
|
|
1145
|
+
import path2 from "node:path";
|
|
1137
1146
|
|
|
1138
1147
|
// scripts/lib/hs-command.mjs
|
|
1139
1148
|
import { existsSync } from "node:fs";
|
|
1140
1149
|
import { delimiter, dirname as dirname2, join as join4 } from "node:path";
|
|
1141
|
-
import { fileURLToPath } from "node:url";
|
|
1150
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1142
1151
|
var HS_BIN_NAME = "hs";
|
|
1143
1152
|
var HS_DISPATCHER_PATH = (() => {
|
|
1144
1153
|
try {
|
|
1145
|
-
const here = dirname2(
|
|
1154
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
1146
1155
|
return join4(dirname2(dirname2(here)), "bin", "hs.mjs");
|
|
1147
1156
|
} catch {
|
|
1148
1157
|
return null;
|
|
@@ -1156,8 +1165,8 @@ var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER
|
|
|
1156
1165
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
1157
1166
|
init_heyspex_home();
|
|
1158
1167
|
init_credentials();
|
|
1159
|
-
import { readFileSync as
|
|
1160
|
-
import { homedir as
|
|
1168
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
1169
|
+
import { homedir as homedir8 } from "node:os";
|
|
1161
1170
|
import { join as join8 } from "node:path";
|
|
1162
1171
|
|
|
1163
1172
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
@@ -1182,14 +1191,14 @@ var VAULT_LESS_ENDPOINTS = Object.freeze([
|
|
|
1182
1191
|
"GET /api/workspaces",
|
|
1183
1192
|
"* /api/admin/dev-mode"
|
|
1184
1193
|
]);
|
|
1185
|
-
function pathnameOf(
|
|
1186
|
-
const raw = String(
|
|
1194
|
+
function pathnameOf(path7) {
|
|
1195
|
+
const raw = String(path7 ?? "").split("?")[0].split("#")[0];
|
|
1187
1196
|
const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
|
|
1188
1197
|
return noTrailing || "/";
|
|
1189
1198
|
}
|
|
1190
|
-
function isVaultLessEndpoint(method,
|
|
1199
|
+
function isVaultLessEndpoint(method, path7) {
|
|
1191
1200
|
const m = String(method || "GET").toUpperCase();
|
|
1192
|
-
const pathname = pathnameOf(
|
|
1201
|
+
const pathname = pathnameOf(path7);
|
|
1193
1202
|
return VAULT_LESS_ENDPOINTS.some((row) => {
|
|
1194
1203
|
const [rowMethod, rowPath] = row.split(" ");
|
|
1195
1204
|
return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
|
|
@@ -1210,9 +1219,30 @@ import { basename, join as join6, isAbsolute as isAbsolute2 } from "node:path";
|
|
|
1210
1219
|
|
|
1211
1220
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
1212
1221
|
init_heyspex_home();
|
|
1213
|
-
import { existsSync as
|
|
1214
|
-
import { homedir as
|
|
1215
|
-
|
|
1222
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
|
|
1223
|
+
import { homedir as homedir7, tmpdir } from "node:os";
|
|
1224
|
+
|
|
1225
|
+
// vendor/heyspex-mcp/usable-dir-env.mjs
|
|
1226
|
+
import { statSync } from "node:fs";
|
|
1227
|
+
import { isAbsolute as isAbsolute3 } from "node:path";
|
|
1228
|
+
|
|
1229
|
+
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
1230
|
+
import { basename as basename2, dirname as dirname3, join as join7, resolve as resolve3 } from "node:path";
|
|
1231
|
+
|
|
1232
|
+
// vendor/heyspex-mcp/vault-binding.mjs
|
|
1233
|
+
import {
|
|
1234
|
+
copyFileSync,
|
|
1235
|
+
existsSync as existsSync3,
|
|
1236
|
+
mkdirSync as mkdirSync4,
|
|
1237
|
+
readFileSync as readFileSync6,
|
|
1238
|
+
realpathSync as realpathSync2,
|
|
1239
|
+
renameSync as renameSync3,
|
|
1240
|
+
statSync as statSync2,
|
|
1241
|
+
unlinkSync,
|
|
1242
|
+
writeFileSync as writeFileSync4
|
|
1243
|
+
} from "node:fs";
|
|
1244
|
+
import { homedir as homedir6 } from "node:os";
|
|
1245
|
+
import path from "node:path";
|
|
1216
1246
|
|
|
1217
1247
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
1218
1248
|
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
@@ -1221,17 +1251,17 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
|
|
|
1221
1251
|
init_credentials();
|
|
1222
1252
|
|
|
1223
1253
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
1224
|
-
import { existsSync as
|
|
1254
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1225
1255
|
import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
|
|
1226
1256
|
|
|
1227
1257
|
// spex-console/src/lib/heyspex-home.ts
|
|
1228
|
-
import { homedir as
|
|
1258
|
+
import { homedir as homedir9 } from "node:os";
|
|
1229
1259
|
function heyspexHome2(env = process.env) {
|
|
1230
1260
|
const override = env?.HEYSPEX_HOME;
|
|
1231
1261
|
if (typeof override === "string" && override.trim().length > 0) {
|
|
1232
1262
|
return override.trim();
|
|
1233
1263
|
}
|
|
1234
|
-
return
|
|
1264
|
+
return homedir9();
|
|
1235
1265
|
}
|
|
1236
1266
|
|
|
1237
1267
|
// spex-console/src/lib/vault-registry/types.ts
|
|
@@ -1353,9 +1383,9 @@ function parseVaultRegistryFile(json) {
|
|
|
1353
1383
|
}
|
|
1354
1384
|
function readRegistryJson(opts) {
|
|
1355
1385
|
const filePath = vaultRegistryFilePath(opts);
|
|
1356
|
-
if (!
|
|
1386
|
+
if (!existsSync5(filePath)) return { kind: "missing" };
|
|
1357
1387
|
try {
|
|
1358
|
-
const raw =
|
|
1388
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
1359
1389
|
const json = JSON.parse(raw);
|
|
1360
1390
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
1361
1391
|
return { kind: "ok", raw, json };
|
|
@@ -1425,10 +1455,10 @@ function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
|
1425
1455
|
function writeVaultRegistry(registry, opts = {}) {
|
|
1426
1456
|
const filePath = vaultRegistryFilePath(opts);
|
|
1427
1457
|
assertNotRealHomeRegistryUnderTest(filePath);
|
|
1428
|
-
|
|
1458
|
+
mkdirSync5(dirname4(filePath), { recursive: true });
|
|
1429
1459
|
const tmpPath = `${filePath}.tmp`;
|
|
1430
|
-
|
|
1431
|
-
|
|
1460
|
+
writeFileSync5(tmpPath, serializeVaultRegistry(registry), "utf8");
|
|
1461
|
+
renameSync4(tmpPath, filePath);
|
|
1432
1462
|
}
|
|
1433
1463
|
function isValidId(id) {
|
|
1434
1464
|
if (!isNonEmptyString(id)) return false;
|
|
@@ -1603,11 +1633,11 @@ function readPairedBearer(opts = {}) {
|
|
|
1603
1633
|
function readPairedToken(opts = {}) {
|
|
1604
1634
|
return readPairedBearer(opts).token ?? void 0;
|
|
1605
1635
|
}
|
|
1606
|
-
function scopeHeadersFor(scope, method,
|
|
1636
|
+
function scopeHeadersFor(scope, method, path7) {
|
|
1607
1637
|
if (scope === "none") {
|
|
1608
|
-
if (!isVaultLessEndpoint(method,
|
|
1638
|
+
if (!isVaultLessEndpoint(method, path7)) {
|
|
1609
1639
|
throw new Error(
|
|
1610
|
-
`controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${
|
|
1640
|
+
`controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path7} is vault-scoped and must name { accountId, vaultId }.`
|
|
1611
1641
|
);
|
|
1612
1642
|
}
|
|
1613
1643
|
return {};
|
|
@@ -1618,7 +1648,7 @@ function cloudRequestScope(scope) {
|
|
|
1618
1648
|
const named = { accountId: scope.accountId, vaultId: scope.vaultId };
|
|
1619
1649
|
return {
|
|
1620
1650
|
...named,
|
|
1621
|
-
headersFor: (method,
|
|
1651
|
+
headersFor: (method, path7) => isVaultLessEndpoint(method, path7) ? {} : scopeHeadersFor(named, method, path7)
|
|
1622
1652
|
};
|
|
1623
1653
|
}
|
|
1624
1654
|
function withCloudScope(deps, accountId, vaultId) {
|
|
@@ -1626,13 +1656,297 @@ function withCloudScope(deps, accountId, vaultId) {
|
|
|
1626
1656
|
return { ...deps, http: { ...deps.http, scope: cloudRequestScope({ accountId, vaultId }) } };
|
|
1627
1657
|
}
|
|
1628
1658
|
|
|
1659
|
+
// spex-console/src/lib/cli/generated-file-eol.ts
|
|
1660
|
+
import { spawnSync } from "node:child_process";
|
|
1661
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10 } from "node:fs";
|
|
1662
|
+
import { dirname as dirname5, relative, resolve as resolve5 } from "node:path";
|
|
1663
|
+
function dominantEol(text) {
|
|
1664
|
+
const crlf = (text.match(/\r\n/g) ?? []).length;
|
|
1665
|
+
const lf = (text.match(/\n/g) ?? []).length - crlf;
|
|
1666
|
+
if (crlf === 0 && lf === 0) return null;
|
|
1667
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
1668
|
+
}
|
|
1669
|
+
function applyEol(text, eol) {
|
|
1670
|
+
const lf = text.replace(/\r\n/g, "\n");
|
|
1671
|
+
return eol === "\n" ? lf : lf.replace(/\n/g, "\r\n");
|
|
1672
|
+
}
|
|
1673
|
+
function defaultRunGit(args, cwd) {
|
|
1674
|
+
try {
|
|
1675
|
+
const res = spawnSync("git", args, {
|
|
1676
|
+
cwd,
|
|
1677
|
+
encoding: "utf8",
|
|
1678
|
+
windowsHide: true,
|
|
1679
|
+
timeout: 1e4
|
|
1680
|
+
});
|
|
1681
|
+
if (res.status !== 0) return null;
|
|
1682
|
+
return String(res.stdout ?? "").trim();
|
|
1683
|
+
} catch {
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
function defaultReadFile3(path7) {
|
|
1688
|
+
try {
|
|
1689
|
+
return readFileSync10(path7, "utf8");
|
|
1690
|
+
} catch {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
function existingAncestor(path7) {
|
|
1695
|
+
let dir = dirname5(resolve5(path7));
|
|
1696
|
+
for (; ; ) {
|
|
1697
|
+
if (existsSync6(dir)) return dir;
|
|
1698
|
+
const parent = dirname5(dir);
|
|
1699
|
+
if (parent === dir) return dir;
|
|
1700
|
+
dir = parent;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function createGeneratedFileEol(opts) {
|
|
1704
|
+
const readFile = opts.readFile ?? defaultReadFile3;
|
|
1705
|
+
const runGit = opts.runGit ?? defaultRunGit;
|
|
1706
|
+
const platform = opts.platform ?? process.platform;
|
|
1707
|
+
const attrCache = /* @__PURE__ */ new Map();
|
|
1708
|
+
let configEol;
|
|
1709
|
+
const configDefault = () => {
|
|
1710
|
+
if (configEol !== void 0) return configEol;
|
|
1711
|
+
if (runGit(["rev-parse", "--is-inside-work-tree"], opts.repoRoot) !== "true") {
|
|
1712
|
+
configEol = null;
|
|
1713
|
+
return configEol;
|
|
1714
|
+
}
|
|
1715
|
+
const get = (key) => runGit(["config", "--get", key], opts.repoRoot);
|
|
1716
|
+
const coreEol = (get("core.eol") ?? "").toLowerCase();
|
|
1717
|
+
if (coreEol === "crlf") configEol = "\r\n";
|
|
1718
|
+
else if (coreEol === "lf") configEol = "\n";
|
|
1719
|
+
else if (platform === "win32" && (get("core.autocrlf") ?? "").toLowerCase() === "true")
|
|
1720
|
+
configEol = "\r\n";
|
|
1721
|
+
else configEol = null;
|
|
1722
|
+
return configEol;
|
|
1723
|
+
};
|
|
1724
|
+
const attrEol = (path7) => {
|
|
1725
|
+
const abs = resolve5(path7);
|
|
1726
|
+
const cwd = existingAncestor(abs);
|
|
1727
|
+
const rel = relative(cwd, abs).replace(/\\/g, "/") || ".";
|
|
1728
|
+
const key = `${cwd}\0${rel}`;
|
|
1729
|
+
let out = attrCache.get(key);
|
|
1730
|
+
if (out === void 0) {
|
|
1731
|
+
out = runGit(["check-attr", "eol", "--", rel], cwd) ?? "";
|
|
1732
|
+
attrCache.set(key, out);
|
|
1733
|
+
}
|
|
1734
|
+
const m = out.match(/:\s*eol:\s*(\S+)\s*$/);
|
|
1735
|
+
const value = (m?.[1] ?? "").toLowerCase();
|
|
1736
|
+
if (value === "crlf") return "\r\n";
|
|
1737
|
+
if (value === "lf") return "\n";
|
|
1738
|
+
return null;
|
|
1739
|
+
};
|
|
1740
|
+
const eolFor = (path7, existing) => {
|
|
1741
|
+
const current = existing === void 0 ? readFile(path7) : existing;
|
|
1742
|
+
if (current !== null) {
|
|
1743
|
+
const have = dominantEol(current);
|
|
1744
|
+
if (have) return have;
|
|
1745
|
+
}
|
|
1746
|
+
return attrEol(path7) ?? configDefault() ?? "\n";
|
|
1747
|
+
};
|
|
1748
|
+
return {
|
|
1749
|
+
eolFor,
|
|
1750
|
+
convert: (path7, content, existing) => {
|
|
1751
|
+
if (!content.includes("\n")) return content;
|
|
1752
|
+
return applyEol(content, eolFor(path7, existing));
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1629
1757
|
// spex-console/src/lib/cli/install.ts
|
|
1630
|
-
import { isAbsolute as
|
|
1758
|
+
import { isAbsolute as isAbsolute4, resolve as resolveNodePath2 } from "node:path";
|
|
1759
|
+
|
|
1760
|
+
// spex-console/src/lib/vault-resolve/index.ts
|
|
1761
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
1762
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
1763
|
+
import path3 from "node:path";
|
|
1764
|
+
|
|
1765
|
+
// spex-console/src/lib/cli/satellite.ts
|
|
1766
|
+
import { resolve as resolveNodePath } from "node:path";
|
|
1767
|
+
import process2 from "node:process";
|
|
1768
|
+
var SATELLITE_VAULT_ENV = "HEYSPEX_VAULT_ROOT";
|
|
1769
|
+
var SATELLITE_MANIFEST_RELS = [
|
|
1770
|
+
[".mcp.json"],
|
|
1771
|
+
[".cursor", "mcp.json"]
|
|
1772
|
+
];
|
|
1773
|
+
function normalizePathForCompare(p) {
|
|
1774
|
+
return p.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1775
|
+
}
|
|
1776
|
+
function samePath(a, b) {
|
|
1777
|
+
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
1778
|
+
}
|
|
1779
|
+
function isAtOrUnder(child, parent) {
|
|
1780
|
+
const c = normalizePathForCompare(child);
|
|
1781
|
+
const p = normalizePathForCompare(parent);
|
|
1782
|
+
return c === p || c.startsWith(`${p}/`);
|
|
1783
|
+
}
|
|
1784
|
+
function isAbsolutePathForCompare(p) {
|
|
1785
|
+
const s = p.trim();
|
|
1786
|
+
return /^[\\/]/.test(s) || /^[a-zA-Z]:[\\/]/.test(s);
|
|
1787
|
+
}
|
|
1788
|
+
function defaultResolveCliPath(p) {
|
|
1789
|
+
const base = process2.env.HS_INVOKED_CWD?.trim() || process2.cwd();
|
|
1790
|
+
return resolveNodePath(base, p);
|
|
1791
|
+
}
|
|
1792
|
+
function expandPinPlaceholders(value, repoRoot) {
|
|
1793
|
+
return value.replace(/\$\{(?:CLAUDE_PROJECT_DIR|workspaceFolder)(?::-[^}]*)?\}/g, repoRoot);
|
|
1794
|
+
}
|
|
1795
|
+
function lexicalJoin(base, rel) {
|
|
1796
|
+
const parts = `${base}/${rel}`.replace(/\\/g, "/").split("/");
|
|
1797
|
+
const out = [];
|
|
1798
|
+
for (const seg of parts) {
|
|
1799
|
+
if (seg === "" && out.length > 0) continue;
|
|
1800
|
+
if (seg === ".") continue;
|
|
1801
|
+
if (seg === ".." && out.length > 1) {
|
|
1802
|
+
out.pop();
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
out.push(seg);
|
|
1806
|
+
}
|
|
1807
|
+
return out.join("/");
|
|
1808
|
+
}
|
|
1809
|
+
function parseMarkerVaultPath(content) {
|
|
1810
|
+
const m = content.match(/^vault_path:[ \t]*(.+?)[ \t]*$/m);
|
|
1811
|
+
if (!m) return null;
|
|
1812
|
+
const v = m[1].trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
1813
|
+
if (!v || v.includes("${") || /^[\\/]/.test(v) || /^[a-zA-Z]:[\\/]/.test(v)) return null;
|
|
1814
|
+
return v;
|
|
1815
|
+
}
|
|
1816
|
+
function markerVaultIdOf(content) {
|
|
1817
|
+
const m = content.match(/^vault_id:\s*(.+?)\s*$/m);
|
|
1818
|
+
if (!m) return null;
|
|
1819
|
+
const v = m[1].trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
1820
|
+
return v.length > 0 ? v : null;
|
|
1821
|
+
}
|
|
1822
|
+
function detectMarkerAttachment(repoRoot, deps) {
|
|
1823
|
+
const markerPath = deps.joinPath(repoRoot, "heyspex.yaml");
|
|
1824
|
+
if (!deps.fileExists(markerPath)) return null;
|
|
1825
|
+
const raw = deps.readFile(markerPath);
|
|
1826
|
+
const vaultPath = raw ? parseMarkerVaultPath(raw) : null;
|
|
1827
|
+
if (!raw || !vaultPath) return null;
|
|
1828
|
+
const vaultRootAbs = lexicalJoin(repoRoot, vaultPath);
|
|
1829
|
+
const markerId = markerVaultIdOf(raw);
|
|
1830
|
+
let cloned = deps.fileExists(deps.joinPath(vaultRootAbs, "specs"));
|
|
1831
|
+
if (cloned && markerId) {
|
|
1832
|
+
const theirs = deps.readFile(deps.joinPath(vaultRootAbs, "heyspex.yaml"));
|
|
1833
|
+
const theirId = theirs ? markerVaultIdOf(theirs) : null;
|
|
1834
|
+
if (theirId && theirId !== markerId) cloned = false;
|
|
1835
|
+
}
|
|
1836
|
+
const registry = loadVaultRegistry(deps.registryOpts);
|
|
1837
|
+
const record = registry.vaults.find((v) => samePath(v.repo_root_abs, vaultRootAbs));
|
|
1838
|
+
return {
|
|
1839
|
+
vaultRootAbs,
|
|
1840
|
+
vaultId: markerId ?? record?.id ?? null,
|
|
1841
|
+
pins: [{ source: "heyspex.yaml", value: vaultRootAbs }],
|
|
1842
|
+
conflicting: false,
|
|
1843
|
+
viaMarker: true,
|
|
1844
|
+
cloned,
|
|
1845
|
+
vaultPath
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function readPinnedVaultRoot(repoRoot, deps) {
|
|
1849
|
+
const pins = [];
|
|
1850
|
+
for (const rel of SATELLITE_MANIFEST_RELS) {
|
|
1851
|
+
const path7 = deps.joinPath(repoRoot, ...rel);
|
|
1852
|
+
if (!deps.fileExists(path7)) continue;
|
|
1853
|
+
const raw = deps.readFile(path7);
|
|
1854
|
+
if (!raw) continue;
|
|
1855
|
+
let value;
|
|
1856
|
+
try {
|
|
1857
|
+
const parsed = JSON.parse(raw);
|
|
1858
|
+
value = parsed?.mcpServers?.heyspex?.env?.[SATELLITE_VAULT_ENV];
|
|
1859
|
+
} catch {
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (typeof value !== "string") continue;
|
|
1863
|
+
const trimmed = value.trim();
|
|
1864
|
+
if (!trimmed) continue;
|
|
1865
|
+
const expanded = expandPinPlaceholders(trimmed, repoRoot);
|
|
1866
|
+
if (expanded.includes("${")) continue;
|
|
1867
|
+
if (/^[a-zA-Z]:(?![\\/])/.test(expanded)) continue;
|
|
1868
|
+
const abs = isAbsolutePathForCompare(expanded) ? expanded.includes("..") ? lexicalJoin(expanded, ".") : expanded : lexicalJoin(repoRoot, expanded);
|
|
1869
|
+
if (expanded !== trimmed && isAtOrUnder(abs, repoRoot)) continue;
|
|
1870
|
+
pins.push({ source: rel.join("/"), value: abs });
|
|
1871
|
+
}
|
|
1872
|
+
return pins;
|
|
1873
|
+
}
|
|
1874
|
+
function isVaultRootOnDisk(dir, deps) {
|
|
1875
|
+
return deps.fileExists(deps.joinPath(dir, "heyspex.yaml")) || deps.fileExists(deps.joinPath(dir, "specs"));
|
|
1876
|
+
}
|
|
1877
|
+
function resolveAttachTarget(idOrPath, deps) {
|
|
1878
|
+
const raw = idOrPath.trim();
|
|
1879
|
+
if (!raw) return { ok: false, message: "--attach-vault requires a vault id or path" };
|
|
1880
|
+
const registry = loadVaultRegistry(deps.registryOpts);
|
|
1881
|
+
const looksLikePath = /[\\/]/.test(raw) || /^[a-zA-Z]:$/.test(raw.slice(0, 2)) || raw === "." || raw === "..";
|
|
1882
|
+
const asPath = looksLikePath && !isAbsolutePathForCompare(raw) ? (deps.resolvePath ?? defaultResolveCliPath)(raw) : raw;
|
|
1883
|
+
const shownPath = asPath === raw ? raw : `${raw} (${asPath})`;
|
|
1884
|
+
const record = registry.vaults.find((v) => v.id === raw) ?? (looksLikePath ? registry.vaults.find((v) => samePath(v.repo_root_abs, asPath)) : void 0);
|
|
1885
|
+
if (!record) {
|
|
1886
|
+
const known = registry.vaults.map((v) => v.id);
|
|
1887
|
+
const knownList = known.length ? `known vault ids: ${known.join(", ")}` : "no vaults are registered on this machine";
|
|
1888
|
+
return {
|
|
1889
|
+
ok: false,
|
|
1890
|
+
message: looksLikePath ? `--attach-vault: no registered vault at ${shownPath} \u2014 register it first with \`hs vault register --path <dir>\`, then attach by id (${knownList}).` : `--attach-vault: unknown vault id "${raw}" \u2014 run \`hs vault list\` to see registered vaults (${knownList}).`
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
if (!deps.fileExists(record.repo_root_abs)) {
|
|
1894
|
+
return {
|
|
1895
|
+
ok: false,
|
|
1896
|
+
message: `--attach-vault: vault "${record.id}" is registered at ${record.repo_root_abs}, which does not exist on this machine \u2014 clone it there, or fix the row with \`hs vault move\`.`
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
if (!isVaultRootOnDisk(record.repo_root_abs, deps)) {
|
|
1900
|
+
return {
|
|
1901
|
+
ok: false,
|
|
1902
|
+
message: `--attach-vault: ${record.repo_root_abs} (vault "${record.id}") has neither heyspex.yaml nor specs/ \u2014 it is not a vault root.`
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
return {
|
|
1906
|
+
ok: true,
|
|
1907
|
+
target: {
|
|
1908
|
+
vaultId: record.id,
|
|
1909
|
+
vaultRootAbs: record.repo_root_abs,
|
|
1910
|
+
workspaceId: record.workspace_id,
|
|
1911
|
+
displayName: record.display_name
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// spex-console/src/lib/vault-resolve/index.ts
|
|
1917
|
+
function parseRepoBinding(content) {
|
|
1918
|
+
const src = String(content ?? "");
|
|
1919
|
+
if (/^(vault_id|vault_root):/m.test(src)) return null;
|
|
1920
|
+
const kind = src.match(/^kind:\s*(.+?)\s*$/m)?.[1]?.replace(/^["']|["']$/g, "").trim();
|
|
1921
|
+
if (kind && kind !== "repo-binding") return null;
|
|
1922
|
+
const lines = src.split(/\r?\n/);
|
|
1923
|
+
const start = lines.findIndex((l) => /^vault:(\s|$)/.test(l));
|
|
1924
|
+
if (start === -1) return null;
|
|
1925
|
+
const unq = (v) => v.trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
1926
|
+
const scalar = lines[start].slice("vault:".length).trim();
|
|
1927
|
+
if (scalar && !scalar.startsWith("#")) {
|
|
1928
|
+
const id = unq(scalar);
|
|
1929
|
+
return id ? { id, name: null, remote: null } : null;
|
|
1930
|
+
}
|
|
1931
|
+
const out = {
|
|
1932
|
+
id: null,
|
|
1933
|
+
name: null,
|
|
1934
|
+
remote: null
|
|
1935
|
+
};
|
|
1936
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1937
|
+
const l = lines[i];
|
|
1938
|
+
if (!l.trim() || /^\s*#/.test(l)) continue;
|
|
1939
|
+
if (!/^\s+/.test(l)) break;
|
|
1940
|
+
const m = l.match(/^\s+([a-z_]+):\s*(.*)$/);
|
|
1941
|
+
if (m && (m[1] === "id" || m[1] === "name" || m[1] === "remote")) out[m[1]] = unq(m[2]) || null;
|
|
1942
|
+
}
|
|
1943
|
+
return out.id ? { id: out.id, name: out.name, remote: out.remote } : null;
|
|
1944
|
+
}
|
|
1631
1945
|
|
|
1632
1946
|
// scripts/install-cursor-hooks.mjs
|
|
1633
1947
|
import fs2 from "node:fs";
|
|
1634
|
-
import
|
|
1635
|
-
import { fileURLToPath as
|
|
1948
|
+
import path5 from "node:path";
|
|
1949
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1636
1950
|
|
|
1637
1951
|
// scripts/cursor-hooks/manifest.mjs
|
|
1638
1952
|
var SHIM = "scripts/cursor-hooks/_run.mjs";
|
|
@@ -1778,9 +2092,9 @@ function timeoutFor(entry) {
|
|
|
1778
2092
|
// scripts/lib/customer-kit.mjs
|
|
1779
2093
|
import { createHash } from "node:crypto";
|
|
1780
2094
|
import fs from "node:fs";
|
|
1781
|
-
import
|
|
2095
|
+
import path4 from "node:path";
|
|
1782
2096
|
import os2 from "node:os";
|
|
1783
|
-
import { fileURLToPath as
|
|
2097
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
1784
2098
|
|
|
1785
2099
|
// scripts/lib/hook-dispositions.mjs
|
|
1786
2100
|
var HOSTS = Object.freeze(["claude-code", "cursor", "copilot"]);
|
|
@@ -2050,7 +2364,7 @@ function toPosixPath(p) {
|
|
|
2050
2364
|
function samePlatformPath(a, b) {
|
|
2051
2365
|
if (!a || !b) return false;
|
|
2052
2366
|
const norm = (p) => {
|
|
2053
|
-
const s = toPosixPath(
|
|
2367
|
+
const s = toPosixPath(path4.resolve(p));
|
|
2054
2368
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
2055
2369
|
};
|
|
2056
2370
|
return norm(a) === norm(b);
|
|
@@ -2128,8 +2442,16 @@ var LAUNCHER_TEMPLATE = [
|
|
|
2128
2442
|
"// 4. node_modules/heyspex up from $__ENV__, $CLAUDE_PROJECT_DIR, $CURSOR_PROJECT_DIR, cwd",
|
|
2129
2443
|
"// 5. <npm root -g>/heyspex a global install, last resort",
|
|
2130
2444
|
"// Nothing found -> one stderr line and exit 0 (fail-open, like the shim itself).",
|
|
2445
|
+
"//",
|
|
2446
|
+
"// The same resolution starts the package's other entrypoints, so no committed",
|
|
2447
|
+
"// file names node_modules/heyspex directly and a repo with no node_modules of",
|
|
2448
|
+
"// its own still works off a global install:",
|
|
2449
|
+
"// --mcp the stdio MCP server (.mcp.json, .cursor/mcp.json)",
|
|
2450
|
+
"// --statusline the Claude Code status line (statusLine.command)",
|
|
2451
|
+
"// --resolve print the package root, exit 0; exit 1 when none (hs doctor)",
|
|
2131
2452
|
"import { spawnSync } from 'node:child_process';",
|
|
2132
|
-
"import { existsSync } from 'node:fs';",
|
|
2453
|
+
"import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs';",
|
|
2454
|
+
"import { homedir } from 'node:os';",
|
|
2133
2455
|
"import { dirname, resolve } from 'node:path';",
|
|
2134
2456
|
"import { fileURLToPath, pathToFileURL } from 'node:url';",
|
|
2135
2457
|
"",
|
|
@@ -2139,6 +2461,7 @@ var LAUNCHER_TEMPLATE = [
|
|
|
2139
2461
|
"const text = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);",
|
|
2140
2462
|
"const hasShim = (root) => Boolean(root) && existsSync(resolve(root, ...SHIM));",
|
|
2141
2463
|
"",
|
|
2464
|
+
"__YIELD__",
|
|
2142
2465
|
"function packageUpFrom(start) {",
|
|
2143
2466
|
" let dir = resolve(start);",
|
|
2144
2467
|
" for (;;) {",
|
|
@@ -2173,31 +2496,109 @@ var LAUNCHER_TEMPLATE = [
|
|
|
2173
2496
|
" if (hasShim(global)) root = global;",
|
|
2174
2497
|
"}",
|
|
2175
2498
|
"",
|
|
2499
|
+
"const ENTRYPOINTS = {",
|
|
2500
|
+
" '--mcp': 'vendor/heyspex-mcp/cli.mjs',",
|
|
2501
|
+
" '--statusline': 'scripts/claude-statusline/statusline-command.mjs',",
|
|
2502
|
+
"};",
|
|
2503
|
+
"const mode = process.argv[2];",
|
|
2504
|
+
"const entry = Object.hasOwn(ENTRYPOINTS, mode) ? ENTRYPOINTS[mode] : null;",
|
|
2505
|
+
"",
|
|
2176
2506
|
"if (!root) {",
|
|
2507
|
+
" const what = entry || mode === '--resolve' ? mode : 'hook ' + (mode ?? '?');",
|
|
2177
2508
|
" process.stderr.write(",
|
|
2178
|
-
" '[heyspex]
|
|
2179
|
-
` '(run "npm install heyspex" in this repo, or set HEYSPEX_PLATFORM_ROOT).
|
|
2180
|
-
" 'HEYSPEX_PLATFORM_ROOT, HEYSPEX_MCP_ENTRY, node_modules/heyspex up from ' + starts.join(', ') + ', and npm root -g.\\n',",
|
|
2509
|
+
" '[heyspex] ' + what + ' skipped: no heyspex package found ' +",
|
|
2510
|
+
` '(run "npm install heyspex" or "npm link heyspex" in this repo, install it globally, or set HEYSPEX_PLATFORM_ROOT). ' +`,
|
|
2511
|
+
" 'Looked for __SHIM__ under HEYSPEX_PLATFORM_ROOT, HEYSPEX_MCP_ENTRY, node_modules/heyspex up from ' + starts.join(', ') + ', and npm root -g.\\n',",
|
|
2181
2512
|
" );",
|
|
2513
|
+
" // Hooks and the status line fail open; the MCP server and --resolve fail",
|
|
2514
|
+
" // loudly, so the client and hs doctor both see it.",
|
|
2515
|
+
" process.exit(mode === '--mcp' || mode === '--resolve' ? 1 : 0);",
|
|
2516
|
+
"}",
|
|
2517
|
+
"if (mode === '--resolve') {",
|
|
2518
|
+
" process.stdout.write(root + '\\n');",
|
|
2182
2519
|
" process.exit(0);",
|
|
2183
2520
|
"}",
|
|
2184
2521
|
"",
|
|
2185
2522
|
"// argv is already [node, <this file>, <hook>, ...args] -- the shape the shim",
|
|
2186
|
-
'// reads; the
|
|
2187
|
-
"//
|
|
2188
|
-
"
|
|
2189
|
-
"
|
|
2523
|
+
'// reads; the target replaces this file so a script guarded by "am I main?"',
|
|
2524
|
+
"// runs exactly as if it had been invoked directly. Real path, so that guard",
|
|
2525
|
+
"// still matches when node_modules/heyspex is an npm link or a junction (node",
|
|
2526
|
+
"// realpaths import.meta.url but leaves argv[1] alone).",
|
|
2527
|
+
"let target = resolve(root, ...(entry ? entry.split('/') : SHIM));",
|
|
2190
2528
|
"try {",
|
|
2191
|
-
"
|
|
2529
|
+
" target = realpathSync(target);",
|
|
2530
|
+
"} catch {",
|
|
2531
|
+
" // keep the resolved path",
|
|
2532
|
+
"}",
|
|
2533
|
+
"if (entry) process.argv.splice(2, 1);",
|
|
2534
|
+
"process.argv[1] = target;",
|
|
2535
|
+
"try {",
|
|
2536
|
+
" await import(pathToFileURL(target).href);",
|
|
2192
2537
|
"} catch (err) {",
|
|
2193
|
-
" process.stderr.write('[heyspex] hook ' + (process.argv[2] ?? '?') + ' skipped: ' + (err && err.message ? err.message : err) + '\\n');",
|
|
2538
|
+
" process.stderr.write('[heyspex] ' + (entry ? mode : 'hook ' + (process.argv[2] ?? '?')) + ' skipped: ' + (err && err.message ? err.message : err) + '\\n');",
|
|
2539
|
+
" // Hooks and the status line fail open; the MCP server fails loudly.",
|
|
2540
|
+
" if (mode === '--mcp') process.exit(1);",
|
|
2194
2541
|
" process.exit(0);",
|
|
2195
2542
|
"}",
|
|
2196
2543
|
""
|
|
2197
2544
|
].join("\n");
|
|
2545
|
+
var LAUNCHER_YIELD_MARKER = "// yields-to-plugin: v1";
|
|
2546
|
+
var LAUNCHER_YIELD_BLOCK = [
|
|
2547
|
+
LAUNCHER_YIELD_MARKER + " -- one source of hooks: when the heyspex plugin is enabled on this machine",
|
|
2548
|
+
"// its own launcher (plugins/heyspex/bin/run-hook.mjs, which never calls this file) already runs every",
|
|
2549
|
+
"// hook, so this committed row exits 0 silently before resolving anything and each hook fires ONCE.",
|
|
2550
|
+
"// HEYSPEX_REPO_HOOKS=1 never yield (the repo rows are the chosen source);",
|
|
2551
|
+
`// enabledPlugins["heyspex@..."] === false in ~/.claude/settings.json or this repo's .claude/`,
|
|
2552
|
+
"// settings[.local].json: plugin off here, the row runs;",
|
|
2553
|
+
"// plugin evidence (what `hs doctor` hook-double-fire reads): a heyspex@... key in",
|
|
2554
|
+
"// ~/.claude/plugins/installed_plugins.json, enabledPlugins naming heyspex with a non-false",
|
|
2555
|
+
"// value, or a heyspex directory under ~/.copilot/installed-plugins | ~/.copilot/plugins.",
|
|
2556
|
+
"function readJson(file) {",
|
|
2557
|
+
" try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return undefined; }",
|
|
2558
|
+
"}",
|
|
2559
|
+
"function listDir(dir) {",
|
|
2560
|
+
" try { return readdirSync(dir); } catch { return []; }",
|
|
2561
|
+
"}",
|
|
2562
|
+
"function pluginTakesThisHook() {",
|
|
2563
|
+
" const flag = text(env.HEYSPEX_REPO_HOOKS);",
|
|
2564
|
+
" if (flag && flag !== '0' && flag.toLowerCase() !== 'false') return false;",
|
|
2565
|
+
" const home = homedir();",
|
|
2566
|
+
" const repo = resolve(here, '..', '..');",
|
|
2567
|
+
" const isHeyspex = (k) => /^heyspex(@|$)/i.test(k);",
|
|
2568
|
+
" let enabled = false;",
|
|
2569
|
+
" for (const f of [resolve(home, '.claude', 'settings.json'), resolve(repo, '.claude', 'settings.json'), resolve(repo, '.claude', 'settings.local.json')]) {",
|
|
2570
|
+
" const settings = readJson(f);",
|
|
2571
|
+
" const ep = settings && typeof settings === 'object' ? settings.enabledPlugins : null;",
|
|
2572
|
+
" if (!ep || typeof ep !== 'object') continue;",
|
|
2573
|
+
" for (const [k, v] of Object.entries(ep)) {",
|
|
2574
|
+
" if (!isHeyspex(k)) continue;",
|
|
2575
|
+
" if (v === false) return false;",
|
|
2576
|
+
" enabled = true;",
|
|
2577
|
+
" }",
|
|
2578
|
+
" }",
|
|
2579
|
+
" if (enabled) return true;",
|
|
2580
|
+
" const installed = readJson(resolve(home, '.claude', 'plugins', 'installed_plugins.json'));",
|
|
2581
|
+
" if (installed && typeof installed === 'object' && Object.keys(installed.plugins && typeof installed.plugins === 'object' ? installed.plugins : installed).some(isHeyspex)) return true;",
|
|
2582
|
+
" for (const sub of ['installed-plugins', 'plugins']) {",
|
|
2583
|
+
" const dir = resolve(home, '.copilot', sub);",
|
|
2584
|
+
" const names = listDir(dir);",
|
|
2585
|
+
" if (names.some((n) => /heyspex/i.test(n))) return true;",
|
|
2586
|
+
" if (names.some((n) => listDir(resolve(dir, n)).some((m) => /heyspex/i.test(m)))) return true;",
|
|
2587
|
+
" }",
|
|
2588
|
+
" return false;",
|
|
2589
|
+
"}",
|
|
2590
|
+
"// Only a HOOK EVENT yields: --mcp / --statusline / --resolve and an agent-run",
|
|
2591
|
+
"// `<script> --directive <key>` fetch are not hook events the plugin could run in",
|
|
2592
|
+
"// our place, so they always go on to the package below.",
|
|
2593
|
+
"const yieldMode = process.argv[2];",
|
|
2594
|
+
"const notAHookEvent = ['--mcp', '--statusline', '--resolve'].includes(yieldMode) || process.argv.includes('--directive');",
|
|
2595
|
+
"if (!notAHookEvent && pluginTakesThisHook()) process.exit(0);",
|
|
2596
|
+
"",
|
|
2597
|
+
""
|
|
2598
|
+
].join("\n");
|
|
2198
2599
|
function renderCustomerLauncher(surface = "claude") {
|
|
2199
2600
|
const l = CUSTOMER_LAUNCHERS[surface];
|
|
2200
|
-
return LAUNCHER_TEMPLATE.replace(
|
|
2601
|
+
return LAUNCHER_TEMPLATE.replace("__YIELD__\n", surface === "claude" ? LAUNCHER_YIELD_BLOCK : "").replace(
|
|
2201
2602
|
/__(REGISTRY|ENV|LAUNCHER|SHIM)__/g,
|
|
2202
2603
|
(_m, token) => token === "REGISTRY" ? l.registry : token === "ENV" ? l.projectDirEnv : token === "LAUNCHER" ? l.rel.join("/") : l.shim
|
|
2203
2604
|
);
|
|
@@ -2423,25 +2824,33 @@ var MARKER_RE = /\n<!-- heyspex-kit: managed by hs install \(sha256:([0-9a-f]{16
|
|
|
2423
2824
|
function shortHash(text) {
|
|
2424
2825
|
return createHash("sha256").update(text, "utf8").digest("hex").slice(0, 16);
|
|
2425
2826
|
}
|
|
2827
|
+
function normalizeEol(text) {
|
|
2828
|
+
return text === null || text === void 0 ? text : String(text).replace(/\r\n/g, "\n");
|
|
2829
|
+
}
|
|
2426
2830
|
function renderKitCommand(content, platformRoot) {
|
|
2427
|
-
const body = `${rewritePlatformPaths(content, platformRoot).replace(/\s+$/, "")}
|
|
2831
|
+
const body = `${rewritePlatformPaths(normalizeEol(content), platformRoot).replace(/\s+$/, "")}
|
|
2428
2832
|
`;
|
|
2429
2833
|
return `${body}
|
|
2430
2834
|
<!-- heyspex-kit: managed by hs install (sha256:${shortHash(body)}). Edit freely: once edited, hs install leaves this file alone. -->
|
|
2431
2835
|
`;
|
|
2432
2836
|
}
|
|
2433
2837
|
function kitFileState(content) {
|
|
2434
|
-
const
|
|
2838
|
+
const text = normalizeEol(String(content));
|
|
2839
|
+
const m = text.match(MARKER_RE);
|
|
2435
2840
|
if (!m) return "unmanaged";
|
|
2436
|
-
const body =
|
|
2437
|
-
|
|
2438
|
-
|
|
2841
|
+
const body = `${text.slice(0, m.index).replace(/\n$/, "")}
|
|
2842
|
+
`;
|
|
2843
|
+
if (shortHash(body) === m[1]) return "managed";
|
|
2844
|
+
if (shortHash(`${body.slice(0, -1).replace(/\n/g, "\r\n")}
|
|
2845
|
+
`) === m[1]) return "managed";
|
|
2846
|
+
return "customized";
|
|
2439
2847
|
}
|
|
2440
2848
|
function planKitFileWrite({ existing, rendered, raw, force }) {
|
|
2441
2849
|
if (existing === null || existing === void 0) return { write: true, reason: "missing" };
|
|
2442
|
-
|
|
2443
|
-
if (
|
|
2444
|
-
if (
|
|
2850
|
+
const have = normalizeEol(existing);
|
|
2851
|
+
if (have === normalizeEol(rendered)) return { write: false, reason: "unchanged" };
|
|
2852
|
+
if (kitFileState(have) === "managed") return { write: true, reason: "update" };
|
|
2853
|
+
if (have === normalizeEol(raw)) return { write: true, reason: "legacy-copy" };
|
|
2445
2854
|
return force ? { write: true, reason: "forced" } : { write: false, reason: "customized" };
|
|
2446
2855
|
}
|
|
2447
2856
|
function listKitCommandFiles(platformRoot, deps, opts = {}) {
|
|
@@ -2466,7 +2875,7 @@ function listKitCommandFiles(platformRoot, deps, opts = {}) {
|
|
|
2466
2875
|
return out;
|
|
2467
2876
|
}
|
|
2468
2877
|
var fsDeps = {
|
|
2469
|
-
joinPath: (...s) =>
|
|
2878
|
+
joinPath: (...s) => path4.join(...s),
|
|
2470
2879
|
readFile: (p) => {
|
|
2471
2880
|
try {
|
|
2472
2881
|
return fs.readFileSync(p, "utf8");
|
|
@@ -2485,22 +2894,22 @@ var fsDeps = {
|
|
|
2485
2894
|
function invokedDirectly(importMetaUrl, selfBasename, argv1 = process.argv[1]) {
|
|
2486
2895
|
try {
|
|
2487
2896
|
if (!argv1) return false;
|
|
2488
|
-
const self =
|
|
2489
|
-
if (
|
|
2490
|
-
return pathToFileURL(
|
|
2897
|
+
const self = fileURLToPath3(importMetaUrl);
|
|
2898
|
+
if (path4.basename(self) !== selfBasename) return false;
|
|
2899
|
+
return pathToFileURL(path4.resolve(argv1)).href === pathToFileURL(self).href;
|
|
2491
2900
|
} catch {
|
|
2492
2901
|
return false;
|
|
2493
2902
|
}
|
|
2494
2903
|
}
|
|
2495
|
-
var SELF =
|
|
2904
|
+
var SELF = fileURLToPath3(import.meta.url);
|
|
2496
2905
|
if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
|
|
2497
|
-
const platformRoot =
|
|
2498
|
-
const target =
|
|
2906
|
+
const platformRoot = path4.resolve(path4.dirname(SELF), "..", "..");
|
|
2907
|
+
const target = path4.join(platformRoot, "plugins", "heyspex", "hooks", "hooks.json");
|
|
2499
2908
|
const want = renderPluginHooksText();
|
|
2500
2909
|
const same = (fsDeps.readFile(target) ?? "").replace(/\r\n/g, "\n") === want;
|
|
2501
2910
|
if (process.argv.includes("--write-plugin-hooks")) {
|
|
2502
2911
|
if (!same) fs.writeFileSync(target, want, "utf8");
|
|
2503
|
-
console.log(`customer-kit: ${same ? "unchanged" : "wrote"} ${
|
|
2912
|
+
console.log(`customer-kit: ${same ? "unchanged" : "wrote"} ${path4.relative(platformRoot, target)}`);
|
|
2504
2913
|
} else if (process.argv.includes("--check-plugin-hooks")) {
|
|
2505
2914
|
if (!same) {
|
|
2506
2915
|
console.error("customer-kit: plugins/heyspex/hooks/hooks.json is stale \u2014 run `node scripts/lib/customer-kit.mjs --write-plugin-hooks`");
|
|
@@ -2508,15 +2917,15 @@ if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
|
|
|
2508
2917
|
}
|
|
2509
2918
|
console.log("customer-kit: plugins/heyspex/hooks/hooks.json up to date");
|
|
2510
2919
|
} else if (process.argv.includes("--write-github-hooks") || process.argv.includes("--check-github-hooks")) {
|
|
2511
|
-
const ghTarget =
|
|
2920
|
+
const ghTarget = path4.join(platformRoot, ".github", "hooks", "heyspex.json");
|
|
2512
2921
|
const ghWant = renderGithubHooksText();
|
|
2513
2922
|
const ghSame = (fsDeps.readFile(ghTarget) ?? "").replace(/\r\n/g, "\n") === ghWant;
|
|
2514
2923
|
if (process.argv.includes("--write-github-hooks")) {
|
|
2515
2924
|
if (!ghSame) {
|
|
2516
|
-
fs.mkdirSync(
|
|
2925
|
+
fs.mkdirSync(path4.dirname(ghTarget), { recursive: true });
|
|
2517
2926
|
fs.writeFileSync(ghTarget, ghWant, "utf8");
|
|
2518
2927
|
}
|
|
2519
|
-
console.log(`customer-kit: ${ghSame ? "unchanged" : "wrote"} ${
|
|
2928
|
+
console.log(`customer-kit: ${ghSame ? "unchanged" : "wrote"} ${path4.relative(platformRoot, ghTarget).replace(/\\/g, "/")}`);
|
|
2520
2929
|
} else if (!ghSame) {
|
|
2521
2930
|
console.error("customer-kit: .github/hooks/heyspex.json is stale - run `node scripts/lib/customer-kit.mjs --write-github-hooks`");
|
|
2522
2931
|
process.exit(1);
|
|
@@ -2529,9 +2938,9 @@ if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
|
|
|
2529
2938
|
}
|
|
2530
2939
|
|
|
2531
2940
|
// scripts/install-cursor-hooks.mjs
|
|
2532
|
-
var PLATFORM_ROOT =
|
|
2533
|
-
var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ?
|
|
2534
|
-
var SETTINGS_PATH =
|
|
2941
|
+
var PLATFORM_ROOT = path5.resolve(path5.dirname(fileURLToPath4(import.meta.url)), "..");
|
|
2942
|
+
var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path5.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
|
|
2943
|
+
var SETTINGS_PATH = path5.join(REPO_ROOT, ".cursor", "hooks.json");
|
|
2535
2944
|
function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
|
|
2536
2945
|
const customer = !samePlatformPath(targetRoot, platformRoot);
|
|
2537
2946
|
return {
|
|
@@ -2543,7 +2952,7 @@ function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
|
|
|
2543
2952
|
}
|
|
2544
2953
|
var KIT = cursorKitFor(REPO_ROOT);
|
|
2545
2954
|
var ACTIVE_HOOKS = KIT.hooks;
|
|
2546
|
-
var LAUNCHER_PATH =
|
|
2955
|
+
var LAUNCHER_PATH = path5.join(REPO_ROOT, ...CUSTOMER_LAUNCHERS.cursor.rel);
|
|
2547
2956
|
var LEGACY_MARKER = "scripts/claude-hooks/";
|
|
2548
2957
|
function readSettings() {
|
|
2549
2958
|
try {
|
|
@@ -2562,7 +2971,7 @@ function readSettings() {
|
|
|
2562
2971
|
return { version: 1, hooks: {} };
|
|
2563
2972
|
}
|
|
2564
2973
|
function writeSettings(settings) {
|
|
2565
|
-
fs2.mkdirSync(
|
|
2974
|
+
fs2.mkdirSync(path5.dirname(SETTINGS_PATH), { recursive: true });
|
|
2566
2975
|
fs2.writeFileSync(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}
|
|
2567
2976
|
`, "utf8");
|
|
2568
2977
|
}
|
|
@@ -2597,7 +3006,7 @@ function sameEntry(raw, entry, render = KIT.commandFor) {
|
|
|
2597
3006
|
const want = desiredEntry(entry, render);
|
|
2598
3007
|
return commandOf(raw) === want.command && raw.timeout === want.timeout && (raw.matcher ?? void 0) === (want.matcher ?? void 0);
|
|
2599
3008
|
}
|
|
2600
|
-
var PLUGIN_HOOKS_PATH =
|
|
3009
|
+
var PLUGIN_HOOKS_PATH = path5.join(REPO_ROOT, "plugins", "heyspex", "hooks", "cursor-hooks.json");
|
|
2601
3010
|
function pluginHooksDocument() {
|
|
2602
3011
|
const hooks = {};
|
|
2603
3012
|
for (const entry of HOOKS) {
|
|
@@ -2627,7 +3036,7 @@ function syncPluginHooks() {
|
|
|
2627
3036
|
have = null;
|
|
2628
3037
|
}
|
|
2629
3038
|
if (have === want) return false;
|
|
2630
|
-
fs2.mkdirSync(
|
|
3039
|
+
fs2.mkdirSync(path5.dirname(PLUGIN_HOOKS_PATH), { recursive: true });
|
|
2631
3040
|
fs2.writeFileSync(PLUGIN_HOOKS_PATH, want, "utf8");
|
|
2632
3041
|
return true;
|
|
2633
3042
|
}
|
|
@@ -2700,7 +3109,7 @@ function install({ json = false } = {}) {
|
|
|
2700
3109
|
if (KIT.customer) {
|
|
2701
3110
|
const plan = launcherPlan();
|
|
2702
3111
|
if (plan.write) {
|
|
2703
|
-
fs2.mkdirSync(
|
|
3112
|
+
fs2.mkdirSync(path5.dirname(LAUNCHER_PATH), { recursive: true });
|
|
2704
3113
|
fs2.writeFileSync(LAUNCHER_PATH, plan.text, "utf8");
|
|
2705
3114
|
launcherWritten = true;
|
|
2706
3115
|
}
|
|
@@ -2727,10 +3136,10 @@ function install({ json = false } = {}) {
|
|
|
2727
3136
|
return;
|
|
2728
3137
|
}
|
|
2729
3138
|
if (pluginChanged) {
|
|
2730
|
-
console.log(`install-cursor-hooks: regenerated ${
|
|
3139
|
+
console.log(`install-cursor-hooks: regenerated ${path5.relative(REPO_ROOT, PLUGIN_HOOKS_PATH)}`);
|
|
2731
3140
|
}
|
|
2732
3141
|
if (launcherWritten) {
|
|
2733
|
-
console.log(`install-cursor-hooks: wrote ${
|
|
3142
|
+
console.log(`install-cursor-hooks: wrote ${path5.relative(REPO_ROOT, LAUNCHER_PATH)} (resolves node_modules/heyspex)`);
|
|
2734
3143
|
}
|
|
2735
3144
|
if (!settingsChanged) {
|
|
2736
3145
|
console.log(`install-cursor-hooks: already installed (${ACTIVE_HOOKS.length} hooks, no changes).`);
|
|
@@ -2741,7 +3150,7 @@ function install({ json = false } = {}) {
|
|
|
2741
3150
|
if (repaired.length) parts.push(`repaired ${repaired.length}`);
|
|
2742
3151
|
if (removed.length) parts.push(`removed ${removed.length}`);
|
|
2743
3152
|
console.log(
|
|
2744
|
-
`install-cursor-hooks: ${parts.join("; ")} \u2192 ${
|
|
3153
|
+
`install-cursor-hooks: ${parts.join("; ")} \u2192 ${path5.relative(REPO_ROOT, SETTINGS_PATH)} (${ACTIVE_HOOKS.length} hooks registered)`
|
|
2745
3154
|
);
|
|
2746
3155
|
for (const line of removed) console.log(` - ${line}`);
|
|
2747
3156
|
console.log(
|
|
@@ -2843,11 +3252,11 @@ if (invokedDirectly(import.meta.url, "install-cursor-hooks.mjs")) {
|
|
|
2843
3252
|
init_credentials();
|
|
2844
3253
|
|
|
2845
3254
|
// spex-console/src/lib/cli/connect-checks.ts
|
|
2846
|
-
import { dirname as
|
|
3255
|
+
import { dirname as dirname6 } from "node:path";
|
|
2847
3256
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
2848
3257
|
|
|
2849
3258
|
// spex-console/src/lib/dev-mode.ts
|
|
2850
|
-
import { mkdirSync as
|
|
3259
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2851
3260
|
import { join as join10 } from "node:path";
|
|
2852
3261
|
init_credentials();
|
|
2853
3262
|
|
|
@@ -3094,12 +3503,15 @@ var HELP = [
|
|
|
3094
3503
|
"(spex-console/src/lib/cli/template-manifest.ts) \u2014 universal docs",
|
|
3095
3504
|
"copied verbatim, template-shape docs written as author-prompt skeletons \u2014",
|
|
3096
3505
|
"plus a generated CLAUDE.md, the kept-principles index, and the template_version",
|
|
3097
|
-
"pin; with --git, one initial `git init` + commit.
|
|
3098
|
-
"holds heyspex.yaml
|
|
3099
|
-
"
|
|
3100
|
-
"
|
|
3101
|
-
"
|
|
3102
|
-
"
|
|
3506
|
+
"pin; with --git, one initial `git init` + commit. On an EXISTING vault (<dir>",
|
|
3507
|
+
"holds heyspex.yaml with a vault_id) it is a REFRESH \u2014 the repair `hs doctor`'s",
|
|
3508
|
+
"vault-template row names: only missing shipped files are added, template_version",
|
|
3509
|
+
"is written/updated in heyspex.yaml (every other byte and the vault_id kept),",
|
|
3510
|
+
"nothing existing is ever overwritten (an existing CLAUDE.md gets a",
|
|
3511
|
+
"CLAUDE.md.template beside it), the result lists each manifest row as created or",
|
|
3512
|
+
"kept, and a second run writes nothing. <dir> may be any path (a satellite repo",
|
|
3513
|
+
"names its vault, e.g. `hs init --customer-vault ../my-specs`). Refuses only a",
|
|
3514
|
+
"non-empty <dir> that has no heyspex.yaml. See init.customer-vault.ts.",
|
|
3103
3515
|
"",
|
|
3104
3516
|
"Flags:",
|
|
3105
3517
|
" --console-url <url> console_url value (default: the mode-aware console --",
|
|
@@ -3121,157 +3533,6 @@ var HELP = [
|
|
|
3121
3533
|
"Exit codes: 0 ok \xB7 1 usage \xB7 11 bad-arg \xB7 13 write-failed"
|
|
3122
3534
|
].join("\n");
|
|
3123
3535
|
|
|
3124
|
-
// spex-console/src/lib/cli/satellite.ts
|
|
3125
|
-
import { resolve as resolveNodePath } from "node:path";
|
|
3126
|
-
import process2 from "node:process";
|
|
3127
|
-
var SATELLITE_VAULT_ENV = "HEYSPEX_VAULT_ROOT";
|
|
3128
|
-
var SATELLITE_MANIFEST_RELS = [
|
|
3129
|
-
[".mcp.json"],
|
|
3130
|
-
[".cursor", "mcp.json"]
|
|
3131
|
-
];
|
|
3132
|
-
function normalizePathForCompare(p) {
|
|
3133
|
-
return p.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
3134
|
-
}
|
|
3135
|
-
function samePath(a, b) {
|
|
3136
|
-
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
3137
|
-
}
|
|
3138
|
-
function isAtOrUnder(child, parent) {
|
|
3139
|
-
const c = normalizePathForCompare(child);
|
|
3140
|
-
const p = normalizePathForCompare(parent);
|
|
3141
|
-
return c === p || c.startsWith(`${p}/`);
|
|
3142
|
-
}
|
|
3143
|
-
function isAbsolutePathForCompare(p) {
|
|
3144
|
-
const s = p.trim();
|
|
3145
|
-
return /^[\\/]/.test(s) || /^[a-zA-Z]:[\\/]/.test(s);
|
|
3146
|
-
}
|
|
3147
|
-
function defaultResolveCliPath(p) {
|
|
3148
|
-
const base = process2.env.HS_INVOKED_CWD?.trim() || process2.cwd();
|
|
3149
|
-
return resolveNodePath(base, p);
|
|
3150
|
-
}
|
|
3151
|
-
function expandPinPlaceholders(value, repoRoot) {
|
|
3152
|
-
return value.replace(/\$\{(?:CLAUDE_PROJECT_DIR|workspaceFolder)(?::-[^}]*)?\}/g, repoRoot);
|
|
3153
|
-
}
|
|
3154
|
-
function lexicalJoin(base, rel) {
|
|
3155
|
-
const parts = `${base}/${rel}`.replace(/\\/g, "/").split("/");
|
|
3156
|
-
const out = [];
|
|
3157
|
-
for (const seg of parts) {
|
|
3158
|
-
if (seg === "" && out.length > 0) continue;
|
|
3159
|
-
if (seg === ".") continue;
|
|
3160
|
-
if (seg === ".." && out.length > 1) {
|
|
3161
|
-
out.pop();
|
|
3162
|
-
continue;
|
|
3163
|
-
}
|
|
3164
|
-
out.push(seg);
|
|
3165
|
-
}
|
|
3166
|
-
return out.join("/");
|
|
3167
|
-
}
|
|
3168
|
-
function parseMarkerVaultPath(content) {
|
|
3169
|
-
const m = content.match(/^vault_path:[ \t]*(.+?)[ \t]*$/m);
|
|
3170
|
-
if (!m) return null;
|
|
3171
|
-
const v = m[1].trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
3172
|
-
if (!v || v.includes("${") || /^[\\/]/.test(v) || /^[a-zA-Z]:[\\/]/.test(v)) return null;
|
|
3173
|
-
return v;
|
|
3174
|
-
}
|
|
3175
|
-
function markerVaultIdOf(content) {
|
|
3176
|
-
const m = content.match(/^vault_id:\s*(.+?)\s*$/m);
|
|
3177
|
-
if (!m) return null;
|
|
3178
|
-
const v = m[1].trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
3179
|
-
return v.length > 0 ? v : null;
|
|
3180
|
-
}
|
|
3181
|
-
function detectMarkerAttachment(repoRoot, deps) {
|
|
3182
|
-
const markerPath = deps.joinPath(repoRoot, "heyspex.yaml");
|
|
3183
|
-
if (!deps.fileExists(markerPath)) return null;
|
|
3184
|
-
const raw = deps.readFile(markerPath);
|
|
3185
|
-
const vaultPath = raw ? parseMarkerVaultPath(raw) : null;
|
|
3186
|
-
if (!raw || !vaultPath) return null;
|
|
3187
|
-
const vaultRootAbs = lexicalJoin(repoRoot, vaultPath);
|
|
3188
|
-
const markerId = markerVaultIdOf(raw);
|
|
3189
|
-
let cloned = deps.fileExists(deps.joinPath(vaultRootAbs, "specs"));
|
|
3190
|
-
if (cloned && markerId) {
|
|
3191
|
-
const theirs = deps.readFile(deps.joinPath(vaultRootAbs, "heyspex.yaml"));
|
|
3192
|
-
const theirId = theirs ? markerVaultIdOf(theirs) : null;
|
|
3193
|
-
if (theirId && theirId !== markerId) cloned = false;
|
|
3194
|
-
}
|
|
3195
|
-
const registry = loadVaultRegistry(deps.registryOpts);
|
|
3196
|
-
const record = registry.vaults.find((v) => samePath(v.repo_root_abs, vaultRootAbs));
|
|
3197
|
-
return {
|
|
3198
|
-
vaultRootAbs,
|
|
3199
|
-
vaultId: markerId ?? record?.id ?? null,
|
|
3200
|
-
pins: [{ source: "heyspex.yaml", value: vaultRootAbs }],
|
|
3201
|
-
conflicting: false,
|
|
3202
|
-
viaMarker: true,
|
|
3203
|
-
cloned,
|
|
3204
|
-
vaultPath
|
|
3205
|
-
};
|
|
3206
|
-
}
|
|
3207
|
-
function readPinnedVaultRoot(repoRoot, deps) {
|
|
3208
|
-
const pins = [];
|
|
3209
|
-
for (const rel of SATELLITE_MANIFEST_RELS) {
|
|
3210
|
-
const path5 = deps.joinPath(repoRoot, ...rel);
|
|
3211
|
-
if (!deps.fileExists(path5)) continue;
|
|
3212
|
-
const raw = deps.readFile(path5);
|
|
3213
|
-
if (!raw) continue;
|
|
3214
|
-
let value;
|
|
3215
|
-
try {
|
|
3216
|
-
const parsed = JSON.parse(raw);
|
|
3217
|
-
value = parsed?.mcpServers?.heyspex?.env?.[SATELLITE_VAULT_ENV];
|
|
3218
|
-
} catch {
|
|
3219
|
-
continue;
|
|
3220
|
-
}
|
|
3221
|
-
if (typeof value !== "string") continue;
|
|
3222
|
-
const trimmed = value.trim();
|
|
3223
|
-
if (!trimmed) continue;
|
|
3224
|
-
const expanded = expandPinPlaceholders(trimmed, repoRoot);
|
|
3225
|
-
if (expanded.includes("${")) continue;
|
|
3226
|
-
if (/^[a-zA-Z]:(?![\\/])/.test(expanded)) continue;
|
|
3227
|
-
const abs = isAbsolutePathForCompare(expanded) ? expanded.includes("..") ? lexicalJoin(expanded, ".") : expanded : lexicalJoin(repoRoot, expanded);
|
|
3228
|
-
if (expanded !== trimmed && isAtOrUnder(abs, repoRoot)) continue;
|
|
3229
|
-
pins.push({ source: rel.join("/"), value: abs });
|
|
3230
|
-
}
|
|
3231
|
-
return pins;
|
|
3232
|
-
}
|
|
3233
|
-
function isVaultRootOnDisk(dir, deps) {
|
|
3234
|
-
return deps.fileExists(deps.joinPath(dir, "heyspex.yaml")) || deps.fileExists(deps.joinPath(dir, "specs"));
|
|
3235
|
-
}
|
|
3236
|
-
function resolveAttachTarget(idOrPath, deps) {
|
|
3237
|
-
const raw = idOrPath.trim();
|
|
3238
|
-
if (!raw) return { ok: false, message: "--attach-vault requires a vault id or path" };
|
|
3239
|
-
const registry = loadVaultRegistry(deps.registryOpts);
|
|
3240
|
-
const looksLikePath = /[\\/]/.test(raw) || /^[a-zA-Z]:$/.test(raw.slice(0, 2)) || raw === "." || raw === "..";
|
|
3241
|
-
const asPath = looksLikePath && !isAbsolutePathForCompare(raw) ? (deps.resolvePath ?? defaultResolveCliPath)(raw) : raw;
|
|
3242
|
-
const shownPath = asPath === raw ? raw : `${raw} (${asPath})`;
|
|
3243
|
-
const record = registry.vaults.find((v) => v.id === raw) ?? (looksLikePath ? registry.vaults.find((v) => samePath(v.repo_root_abs, asPath)) : void 0);
|
|
3244
|
-
if (!record) {
|
|
3245
|
-
const known = registry.vaults.map((v) => v.id);
|
|
3246
|
-
const knownList = known.length ? `known vault ids: ${known.join(", ")}` : "no vaults are registered on this machine";
|
|
3247
|
-
return {
|
|
3248
|
-
ok: false,
|
|
3249
|
-
message: looksLikePath ? `--attach-vault: no registered vault at ${shownPath} \u2014 register it first with \`hs vault register --path <dir>\`, then attach by id (${knownList}).` : `--attach-vault: unknown vault id "${raw}" \u2014 run \`hs vault list\` to see registered vaults (${knownList}).`
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
if (!deps.fileExists(record.repo_root_abs)) {
|
|
3253
|
-
return {
|
|
3254
|
-
ok: false,
|
|
3255
|
-
message: `--attach-vault: vault "${record.id}" is registered at ${record.repo_root_abs}, which does not exist on this machine \u2014 clone it there, or fix the row with \`hs vault move\`.`
|
|
3256
|
-
};
|
|
3257
|
-
}
|
|
3258
|
-
if (!isVaultRootOnDisk(record.repo_root_abs, deps)) {
|
|
3259
|
-
return {
|
|
3260
|
-
ok: false,
|
|
3261
|
-
message: `--attach-vault: ${record.repo_root_abs} (vault "${record.id}") has neither heyspex.yaml nor specs/ \u2014 it is not a vault root.`
|
|
3262
|
-
};
|
|
3263
|
-
}
|
|
3264
|
-
return {
|
|
3265
|
-
ok: true,
|
|
3266
|
-
target: {
|
|
3267
|
-
vaultId: record.id,
|
|
3268
|
-
vaultRootAbs: record.repo_root_abs,
|
|
3269
|
-
workspaceId: record.workspace_id,
|
|
3270
|
-
displayName: record.display_name
|
|
3271
|
-
}
|
|
3272
|
-
};
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
3536
|
// spex-console/src/lib/cli/connect-checks.ts
|
|
3276
3537
|
function listCanonicalHarnessCommandFiles(platformRoot, deps, opts = {}) {
|
|
3277
3538
|
return listKitCommandFiles(
|
|
@@ -3405,7 +3666,7 @@ function writeProjectContract(ctx, options2 = {}) {
|
|
|
3405
3666
|
}
|
|
3406
3667
|
|
|
3407
3668
|
// spex-console/src/lib/cli/init.customer-vault.ts
|
|
3408
|
-
import { basename as
|
|
3669
|
+
import { basename as basename3 } from "node:path";
|
|
3409
3670
|
|
|
3410
3671
|
// spex-console/src/lib/cli/template-manifest.sources.mjs
|
|
3411
3672
|
var UNIVERSAL_TEMPLATE_ROWS = [
|
|
@@ -3890,41 +4151,42 @@ var SCAFFOLD_FOLDERS = [
|
|
|
3890
4151
|
"checks",
|
|
3891
4152
|
"events"
|
|
3892
4153
|
];
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
if (deps.isDirectory) return deps.isDirectory(path5);
|
|
3896
|
-
try {
|
|
3897
|
-
const kids = deps.readdir(path5);
|
|
3898
|
-
return kids.length > 0 || !name.includes(".");
|
|
3899
|
-
} catch {
|
|
3900
|
-
return false;
|
|
3901
|
-
}
|
|
4154
|
+
function defaultVaultName(targetDir) {
|
|
4155
|
+
return basename3(targetDir) || targetDir;
|
|
3902
4156
|
}
|
|
3903
|
-
function
|
|
3904
|
-
const
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
if (isDir(deps, childAbs, name)) walk(childAbs, childRel);
|
|
3910
|
-
else if (!PLACEHOLDER_FILES.has(name) && name.endsWith(".md") && !manifestDests.has(childRel))
|
|
3911
|
-
out.push(childRel);
|
|
3912
|
-
}
|
|
3913
|
-
};
|
|
3914
|
-
const specsRoot = deps.joinPath(targetDir, "specs");
|
|
3915
|
-
if (deps.exists(specsRoot)) walk(specsRoot, "specs");
|
|
3916
|
-
return out;
|
|
4157
|
+
function readTemplateVersionPin(yamlText) {
|
|
4158
|
+
const m = yamlText.match(/^template_version:[ \t]*(.*?)[ \t]*$/m);
|
|
4159
|
+
if (!m) return null;
|
|
4160
|
+
const raw = m[1].trim();
|
|
4161
|
+
const unquoted = raw.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
4162
|
+
return unquoted.length > 0 ? unquoted : null;
|
|
3917
4163
|
}
|
|
3918
|
-
function
|
|
3919
|
-
|
|
4164
|
+
function stampTemplateVersion(content, version) {
|
|
4165
|
+
if (readTemplateVersionPin(content) === version) return content;
|
|
4166
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
4167
|
+
const line = `template_version: ${version}`;
|
|
4168
|
+
if (/^template_version:[^\r\n]*$/m.test(content)) {
|
|
4169
|
+
return content.replace(/^template_version:[^\r\n]*$/m, line);
|
|
4170
|
+
}
|
|
4171
|
+
const body = content.replace(/(?:\r?\n)+$/, "");
|
|
4172
|
+
return body.length === 0 ? `${line}${eol}` : `${body}${eol}${line}${eol}`;
|
|
4173
|
+
}
|
|
4174
|
+
function parentDirOf(deps, targetDir, dest) {
|
|
4175
|
+
const segments = dest.split("/");
|
|
4176
|
+
if (segments.length < 2) return null;
|
|
4177
|
+
return deps.joinPath(targetDir, ...segments.slice(0, -1));
|
|
3920
4178
|
}
|
|
3921
4179
|
function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
3922
4180
|
const { targetDir } = args;
|
|
3923
4181
|
const written = [];
|
|
3924
4182
|
const filter = options2.manifestFilter ?? (() => true);
|
|
3925
4183
|
const rows = TEMPLATE_MANIFEST.filter(filter);
|
|
4184
|
+
const heyspexYamlPath = deps.joinPath(targetDir, "heyspex.yaml");
|
|
4185
|
+
const markerPresent = deps.exists(targetDir) && deps.exists(heyspexYamlPath);
|
|
4186
|
+
const existingYaml = markerPresent ? deps.readFile?.(heyspexYamlPath) ?? null : null;
|
|
4187
|
+
const existingVaultId = existingYaml ? parseVaultId(existingYaml) : null;
|
|
4188
|
+
const mode = markerPresent && existingVaultId ? "refresh" : "scaffold";
|
|
3926
4189
|
if (deps.exists(targetDir)) {
|
|
3927
|
-
const markerPresent = deps.exists(deps.joinPath(targetDir, "heyspex.yaml"));
|
|
3928
4190
|
if (!markerPresent && deps.readdir(targetDir).length > 0) {
|
|
3929
4191
|
return {
|
|
3930
4192
|
code: EXIT.INVALID_ARG,
|
|
@@ -3934,43 +4196,43 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
|
3934
4196
|
written: []
|
|
3935
4197
|
};
|
|
3936
4198
|
}
|
|
3937
|
-
if (markerPresent) {
|
|
3938
|
-
const manifestDests = new Set(TEMPLATE_MANIFEST.map((r) => r.dest));
|
|
3939
|
-
const managed = findManagedSpecs(deps, targetDir, manifestDests);
|
|
3940
|
-
if (managed.length > 0) {
|
|
3941
|
-
return {
|
|
3942
|
-
code: EXIT.INVALID_ARG,
|
|
3943
|
-
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.`,
|
|
3944
|
-
payload: { ok: false, error: "specs_not_empty", path: targetDir, specs: managed },
|
|
3945
|
-
isError: true,
|
|
3946
|
-
written: []
|
|
3947
|
-
};
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
4199
|
} else {
|
|
3951
4200
|
deps.mkdirRecursive(targetDir);
|
|
3952
4201
|
}
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
4202
|
+
if (mode === "scaffold") {
|
|
4203
|
+
const specsRoot = deps.joinPath(targetDir, "specs");
|
|
4204
|
+
deps.mkdirRecursive(specsRoot);
|
|
4205
|
+
for (const folder of SCAFFOLD_FOLDERS) {
|
|
4206
|
+
deps.mkdirRecursive(deps.joinPath(specsRoot, folder));
|
|
4207
|
+
}
|
|
3957
4208
|
}
|
|
3958
|
-
const heyspexYamlPath = deps.joinPath(targetDir, "heyspex.yaml");
|
|
3959
4209
|
let vaultId;
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
vaultId =
|
|
4210
|
+
let pin;
|
|
4211
|
+
if (markerPresent) {
|
|
4212
|
+
vaultId = existingVaultId ?? "";
|
|
4213
|
+
const stamped = existingYaml === null ? null : stampTemplateVersion(existingYaml, TEMPLATE_VERSION);
|
|
4214
|
+
if (stamped !== null && stamped !== existingYaml) {
|
|
4215
|
+
deps.writeFile(heyspexYamlPath, stamped);
|
|
4216
|
+
written.push(heyspexYamlPath);
|
|
4217
|
+
pin = "written";
|
|
4218
|
+
} else {
|
|
4219
|
+
pin = "kept";
|
|
4220
|
+
}
|
|
3963
4221
|
} else {
|
|
3964
4222
|
vaultId = mintVaultId();
|
|
3965
4223
|
deps.writeFile(
|
|
3966
4224
|
heyspexYamlPath,
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
4225
|
+
stampTemplateVersion(
|
|
4226
|
+
renderHeyspexYaml({
|
|
4227
|
+
consoleUrl: options2.consoleUrl ?? DEFAULT_CONSOLE_URL2,
|
|
4228
|
+
vaultRoot: "specs/",
|
|
4229
|
+
vaultId
|
|
4230
|
+
}),
|
|
4231
|
+
TEMPLATE_VERSION
|
|
4232
|
+
)
|
|
3972
4233
|
);
|
|
3973
4234
|
written.push(heyspexYamlPath);
|
|
4235
|
+
pin = "written";
|
|
3974
4236
|
}
|
|
3975
4237
|
const readConfigSeed = deps.readConfigSeed;
|
|
3976
4238
|
const configSeed = seedConfigYaml(
|
|
@@ -3986,6 +4248,10 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
|
3986
4248
|
rowResults.push({ dest: row.dest, action: "kept" });
|
|
3987
4249
|
continue;
|
|
3988
4250
|
}
|
|
4251
|
+
if (mode === "refresh") {
|
|
4252
|
+
const parent = parentDirOf(deps, targetDir, row.dest);
|
|
4253
|
+
if (parent && !deps.exists(parent)) deps.mkdirRecursive(parent);
|
|
4254
|
+
}
|
|
3989
4255
|
if (row.classification === "universal-template") {
|
|
3990
4256
|
const srcAbs = deps.joinPath(deps.platformRoot, ...row.source.split("/"));
|
|
3991
4257
|
deps.copyFile(srcAbs, destAbs);
|
|
@@ -4005,24 +4271,41 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
|
4005
4271
|
targetDir,
|
|
4006
4272
|
vaultName: options2.vaultName ?? defaultVaultName(targetDir),
|
|
4007
4273
|
templateVersion: TEMPLATE_VERSION,
|
|
4274
|
+
mode,
|
|
4008
4275
|
manifestRows: rows,
|
|
4009
4276
|
rows: rowResults,
|
|
4010
4277
|
deps,
|
|
4011
|
-
recordWrite: (
|
|
4012
|
-
written.push(
|
|
4278
|
+
recordWrite: (path7) => {
|
|
4279
|
+
written.push(path7);
|
|
4013
4280
|
}
|
|
4014
4281
|
};
|
|
4015
4282
|
for (const hook of options2.afterScaffold) hook(ctx);
|
|
4016
4283
|
}
|
|
4017
4284
|
const createdCount = rowResults.filter((r) => r.action === "created").length;
|
|
4018
4285
|
const keptCount = rowResults.length - createdCount;
|
|
4286
|
+
const added = rowResults.filter((r) => r.action === "created").map((r) => r.dest);
|
|
4287
|
+
let message2;
|
|
4288
|
+
if (mode === "refresh") {
|
|
4289
|
+
const parts = [];
|
|
4290
|
+
if (added.length > 0)
|
|
4291
|
+
parts.push(`added ${added.length} missing template file(s): ${added.join(", ")}`);
|
|
4292
|
+
if (pin === "written")
|
|
4293
|
+
parts.push(`pinned template_version: ${TEMPLATE_VERSION} in heyspex.yaml`);
|
|
4294
|
+
const hookWrites = written.length - added.length - (pin === "written" ? 1 : 0);
|
|
4295
|
+
if (hookWrites > 0) parts.push(`${hookWrites} other file(s) written`);
|
|
4296
|
+
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.`;
|
|
4297
|
+
} else {
|
|
4298
|
+
message2 = `Initialized customer vault at ${targetDir} (${written.length} files written, ${createdCount} manifest rows created, ${keptCount} kept, template v${TEMPLATE_VERSION}).`;
|
|
4299
|
+
}
|
|
4019
4300
|
return {
|
|
4020
4301
|
code: EXIT.OK,
|
|
4021
|
-
message:
|
|
4302
|
+
message: message2,
|
|
4022
4303
|
payload: {
|
|
4023
4304
|
ok: true,
|
|
4305
|
+
mode,
|
|
4024
4306
|
targetDir,
|
|
4025
4307
|
templateVersion: TEMPLATE_VERSION,
|
|
4308
|
+
templatePin: pin,
|
|
4026
4309
|
vaultId,
|
|
4027
4310
|
written,
|
|
4028
4311
|
rows: rowResults,
|
|
@@ -4035,7 +4318,7 @@ function runCustomerVaultInit(args, deps, options2 = {}) {
|
|
|
4035
4318
|
}
|
|
4036
4319
|
|
|
4037
4320
|
// spex-console/src/lib/cli/init-git-finalize.ts
|
|
4038
|
-
import { spawnSync } from "node:child_process";
|
|
4321
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4039
4322
|
function readTemplateVersion(yamlText) {
|
|
4040
4323
|
const m = yamlText.match(/^template_version:\s*(.+?)\s*$/m);
|
|
4041
4324
|
if (!m) return null;
|
|
@@ -4064,14 +4347,14 @@ function templatePinHook() {
|
|
|
4064
4347
|
}
|
|
4065
4348
|
|
|
4066
4349
|
// spex-console/src/lib/cli/principle-selection.ts
|
|
4067
|
-
import { basename as
|
|
4350
|
+
import { basename as basename4 } from "node:path";
|
|
4068
4351
|
var PRINCIPLES_INDEX_DEST = "specs/principles-index.md";
|
|
4069
4352
|
function principleKey(row) {
|
|
4070
|
-
const base =
|
|
4353
|
+
const base = basename4(row.dest, ".md");
|
|
4071
4354
|
return base.startsWith("principle-") ? base.slice("principle-".length) : base;
|
|
4072
4355
|
}
|
|
4073
4356
|
function principleWikilink(row) {
|
|
4074
|
-
return
|
|
4357
|
+
return basename4(row.dest, ".md");
|
|
4075
4358
|
}
|
|
4076
4359
|
function parsePrinciplesFlag(value, rows) {
|
|
4077
4360
|
const principleRows = rows.filter((row) => row.group === "principle");
|
|
@@ -4137,13 +4420,13 @@ function applyCustomerTemplate(args) {
|
|
|
4137
4420
|
templateVersion: ctx.templateVersion
|
|
4138
4421
|
});
|
|
4139
4422
|
for (const file of contractFiles) {
|
|
4140
|
-
const
|
|
4141
|
-
if (force && ctx.deps.exists(
|
|
4142
|
-
if (ctx.deps.readFile?.(
|
|
4423
|
+
const path7 = ctx.deps.joinPath(ctx.targetDir, file);
|
|
4424
|
+
if (force && ctx.deps.exists(path7)) {
|
|
4425
|
+
if (ctx.deps.readFile?.(path7) === content) {
|
|
4143
4426
|
contracts.push({ file, action: "unchanged" });
|
|
4144
4427
|
} else {
|
|
4145
|
-
ctx.deps.writeFile(
|
|
4146
|
-
ctx.recordWrite(
|
|
4428
|
+
ctx.deps.writeFile(path7, content);
|
|
4429
|
+
ctx.recordWrite(path7);
|
|
4147
4430
|
contracts.push({ file, action: "overwritten" });
|
|
4148
4431
|
}
|
|
4149
4432
|
continue;
|
|
@@ -4466,7 +4749,7 @@ function formatReceipt(receipt) {
|
|
|
4466
4749
|
}
|
|
4467
4750
|
|
|
4468
4751
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
4469
|
-
import { readFileSync as
|
|
4752
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
4470
4753
|
var VAULT_OWNED_BY_OTHER_ACCOUNT = "vault_owned_by_other_account";
|
|
4471
4754
|
var WORKSPACE_OWNED_BY_OTHER_ACCOUNT = "workspace_owned_by_other_account";
|
|
4472
4755
|
function vaultOwnedByOtherAccountMessage(id) {
|
|
@@ -4480,10 +4763,10 @@ function classifyHttpFailureReason(res) {
|
|
|
4480
4763
|
if (res.status === 401) return "unauthorized";
|
|
4481
4764
|
return "server-error";
|
|
4482
4765
|
}
|
|
4483
|
-
async function cloudHttpRequest(http, method,
|
|
4484
|
-
const scopeHeaders = http.scope ? http.scope.headersFor(method,
|
|
4766
|
+
async function cloudHttpRequest(http, method, path7, body) {
|
|
4767
|
+
const scopeHeaders = http.scope ? http.scope.headersFor(method, path7) : {};
|
|
4485
4768
|
try {
|
|
4486
|
-
const res = await http.fetchImpl(`${http.consoleUrl}${
|
|
4769
|
+
const res = await http.fetchImpl(`${http.consoleUrl}${path7}`, {
|
|
4487
4770
|
method,
|
|
4488
4771
|
headers: {
|
|
4489
4772
|
"content-type": "application/json",
|
|
@@ -4542,8 +4825,8 @@ async function classifyWorkspaceConflictHttp(id, conflictJson, http) {
|
|
|
4542
4825
|
// spex-console/src/lib/cli/receipt-readback.ts
|
|
4543
4826
|
var RECEIPT_CLOUD_READS = 3;
|
|
4544
4827
|
var RECEIPT_REREAD_DELAY_MS = 1e3;
|
|
4545
|
-
var realSleep = (ms) => new Promise((
|
|
4546
|
-
setTimeout(
|
|
4828
|
+
var realSleep = (ms) => new Promise((resolve7) => {
|
|
4829
|
+
setTimeout(resolve7, ms);
|
|
4547
4830
|
});
|
|
4548
4831
|
function message(err) {
|
|
4549
4832
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -4581,11 +4864,11 @@ function readLocalReceiptSide(subject, registryOpts = {}) {
|
|
|
4581
4864
|
return unreadSide(`registry.json could not be read: ${message(err)}`);
|
|
4582
4865
|
}
|
|
4583
4866
|
}
|
|
4584
|
-
async function readList(http,
|
|
4585
|
-
const res = await cloudHttpRequest(http, "GET",
|
|
4867
|
+
async function readList(http, path7, key) {
|
|
4868
|
+
const res = await cloudHttpRequest(http, "GET", path7);
|
|
4586
4869
|
const rows = res.json?.[key];
|
|
4587
4870
|
if (!res.ok || !Array.isArray(rows)) {
|
|
4588
|
-
return { reason: `GET ${
|
|
4871
|
+
return { reason: `GET ${path7.split("?")[0]} answered HTTP ${res.status}` };
|
|
4589
4872
|
}
|
|
4590
4873
|
return { rows: rows.filter((r) => Boolean(r) && typeof r === "object") };
|
|
4591
4874
|
}
|
|
@@ -4690,7 +4973,7 @@ async function readReceiptSides(subject, deps) {
|
|
|
4690
4973
|
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
4691
4974
|
import { createHash as createHash2 } from "node:crypto";
|
|
4692
4975
|
import { promises as fs3 } from "node:fs";
|
|
4693
|
-
import
|
|
4976
|
+
import path6 from "node:path";
|
|
4694
4977
|
|
|
4695
4978
|
// spex-console/src/lib/vault-id.ts
|
|
4696
4979
|
var FILE_KEY_RE = /^(?=[0-9]*[A-Z])[A-Z0-9]{4,}$/;
|
|
@@ -5272,8 +5555,8 @@ async function indexTemplateToCloud(args) {
|
|
|
5272
5555
|
const res = await cloudHttpRequest(args.http, "GET", REINDEX_PATH);
|
|
5273
5556
|
return { status: res.status, json: res.json };
|
|
5274
5557
|
},
|
|
5275
|
-
post: async (
|
|
5276
|
-
const res = await cloudHttpRequest(args.http, "POST",
|
|
5558
|
+
post: async (path7, body) => {
|
|
5559
|
+
const res = await cloudHttpRequest(args.http, "POST", path7, body);
|
|
5277
5560
|
return { status: res.status, json: res.json };
|
|
5278
5561
|
}
|
|
5279
5562
|
});
|
|
@@ -5319,9 +5602,17 @@ async function indexTemplateToCloud(args) {
|
|
|
5319
5602
|
// spex-console/src/lib/cli/install.ts
|
|
5320
5603
|
var MCP_REL = ["vendor", "heyspex-mcp", "cli.mjs"];
|
|
5321
5604
|
var PORTABLE_MCP_CLI = Object.freeze({
|
|
5605
|
+
claude: `\${CLAUDE_PROJECT_DIR:-.}/${CUSTOMER_LAUNCHERS.claude.rel.join("/")}`,
|
|
5606
|
+
cursor: `\${workspaceFolder}/${CUSTOMER_LAUNCHERS.cursor.rel.join("/")}`
|
|
5607
|
+
});
|
|
5608
|
+
var LEGACY_PORTABLE_MCP_CLI = Object.freeze({
|
|
5322
5609
|
claude: `\${CLAUDE_PROJECT_DIR:-.}/${PORTABLE_PACKAGE_DIR}/${MCP_REL.join("/")}`,
|
|
5323
5610
|
cursor: `\${workspaceFolder}/${PORTABLE_PACKAGE_DIR}/${MCP_REL.join("/")}`
|
|
5324
5611
|
});
|
|
5612
|
+
var LAUNCHER_MCP_FLAG = "--mcp";
|
|
5613
|
+
function mcpServerArgs(cliPath) {
|
|
5614
|
+
return /[\\/]heyspex[\\/]run-hook\.mjs$/.test(cliPath) ? [cliPath, LAUNCHER_MCP_FLAG] : [cliPath];
|
|
5615
|
+
}
|
|
5325
5616
|
function mcpCliArgFor(surface, platformRoot, targetDir, joinPath) {
|
|
5326
5617
|
return samePlatformPath(targetDir, platformRoot) ? mcpCliPath(platformRoot, joinPath) : PORTABLE_MCP_CLI[surface];
|
|
5327
5618
|
}
|
|
@@ -5482,7 +5773,7 @@ function mcpCliPath(platformRoot, joinPath) {
|
|
|
5482
5773
|
function resolveCallerPath(p, env, fallbackCwd) {
|
|
5483
5774
|
const trimmed = p?.trim() ?? "";
|
|
5484
5775
|
if (!trimmed) return "";
|
|
5485
|
-
if (
|
|
5776
|
+
if (isAbsolute4(trimmed)) return trimmed;
|
|
5486
5777
|
const base = env.HS_INVOKED_CWD?.trim() || fallbackCwd;
|
|
5487
5778
|
return resolveNodePath2(base, trimmed);
|
|
5488
5779
|
}
|
|
@@ -5493,30 +5784,37 @@ function resolveInstallTargetDir(argsTargetDir, env, fallbackCwd, opts) {
|
|
|
5493
5784
|
}
|
|
5494
5785
|
return explicit || env.HS_VAULT_ROOT?.trim() || env.HS_INVOKED_CWD?.trim() || fallbackCwd;
|
|
5495
5786
|
}
|
|
5496
|
-
function renderCursorMcpJson(cliAbsPath, attachedVaultRoot) {
|
|
5787
|
+
function renderCursorMcpJson(cliAbsPath, attachedVaultRoot, opts = {}) {
|
|
5788
|
+
const pin = vaultRootPin(attachedVaultRoot, "${workspaceFolder}", opts);
|
|
5497
5789
|
return {
|
|
5498
5790
|
mcpServers: {
|
|
5499
5791
|
heyspex: {
|
|
5500
5792
|
type: "stdio",
|
|
5501
5793
|
command: "node",
|
|
5502
|
-
args:
|
|
5503
|
-
env: {
|
|
5504
|
-
HEYSPEX_VAULT_ROOT: attachedVaultRoot ?? "${workspaceFolder}"
|
|
5505
|
-
}
|
|
5794
|
+
args: mcpServerArgs(cliAbsPath),
|
|
5795
|
+
...pin ? { env: { HEYSPEX_VAULT_ROOT: pin } } : {}
|
|
5506
5796
|
}
|
|
5507
5797
|
}
|
|
5508
5798
|
};
|
|
5509
5799
|
}
|
|
5510
|
-
function
|
|
5800
|
+
function vaultRootPin(attachedVaultRoot, placeholder, opts = {}) {
|
|
5801
|
+
if (attachedVaultRoot) return attachedVaultRoot;
|
|
5802
|
+
return opts.targetIsVault === false ? null : placeholder;
|
|
5803
|
+
}
|
|
5804
|
+
function isVaultTarget(targetDir, deps) {
|
|
5805
|
+
if (deps.fileExists(deps.joinPath(targetDir, "specs"))) return true;
|
|
5806
|
+
const marker = deps.readFile(deps.joinPath(targetDir, "heyspex.yaml"));
|
|
5807
|
+
return !(marker && parseRepoBinding(marker));
|
|
5808
|
+
}
|
|
5809
|
+
function renderClaudeMcpJson(cliAbsPath, attachedVaultRoot, opts = {}) {
|
|
5810
|
+
const pin = vaultRootPin(attachedVaultRoot, "${CLAUDE_PROJECT_DIR:-.}", opts);
|
|
5511
5811
|
return {
|
|
5512
5812
|
mcpServers: {
|
|
5513
5813
|
heyspex: {
|
|
5514
5814
|
type: "stdio",
|
|
5515
5815
|
command: "node",
|
|
5516
|
-
args:
|
|
5517
|
-
env: {
|
|
5518
|
-
HEYSPEX_VAULT_ROOT: attachedVaultRoot ?? "${CLAUDE_PROJECT_DIR:-.}"
|
|
5519
|
-
}
|
|
5816
|
+
args: mcpServerArgs(cliAbsPath),
|
|
5817
|
+
...pin ? { env: { HEYSPEX_VAULT_ROOT: pin } } : {}
|
|
5520
5818
|
}
|
|
5521
5819
|
}
|
|
5522
5820
|
};
|
|
@@ -5587,20 +5885,34 @@ var SATELLITE_PIN_TARGETS = [
|
|
|
5587
5885
|
function satellitePinnableManifests(args) {
|
|
5588
5886
|
return SATELLITE_PIN_TARGETS.filter((t) => t.enabled(args)).map((t) => t.rel);
|
|
5589
5887
|
}
|
|
5590
|
-
function
|
|
5888
|
+
function manifestHasHeyspexEntry(raw) {
|
|
5889
|
+
if (!raw) return false;
|
|
5890
|
+
try {
|
|
5891
|
+
const parsed = JSON.parse(raw);
|
|
5892
|
+
return Boolean(parsed?.mcpServers && Object.hasOwn(parsed.mcpServers, "heyspex"));
|
|
5893
|
+
} catch {
|
|
5894
|
+
return false;
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
function planSatelliteManifests(targetDir, attachedRoot, args, deps, satelliteDeps, markerBound = false) {
|
|
5591
5898
|
const onDisk = new Map(readPinnedVaultRoot(targetDir, satelliteDeps).map((p) => [p.source, p]));
|
|
5592
5899
|
const pinnable = [];
|
|
5593
5900
|
const stale = [];
|
|
5594
5901
|
for (const target of SATELLITE_PIN_TARGETS) {
|
|
5595
|
-
const
|
|
5902
|
+
const path7 = deps.joinPath(targetDir, ...target.segments);
|
|
5596
5903
|
const currentPin = onDisk.get(target.rel)?.value ?? null;
|
|
5597
|
-
const exists = deps.fileExists(
|
|
5598
|
-
const
|
|
5904
|
+
const exists = deps.fileExists(path7);
|
|
5905
|
+
const hasHeyspexEntry = exists && manifestHasHeyspexEntry(deps.readFile(path7));
|
|
5906
|
+
const matches = currentPin !== null ? samePath(currentPin, attachedRoot) : (
|
|
5907
|
+
// Pin-free + a heyspex entry is the portable shape a marker-bound repo commits.
|
|
5908
|
+
markerBound && hasHeyspexEntry
|
|
5909
|
+
);
|
|
5599
5910
|
const plan = {
|
|
5600
5911
|
rel: target.rel,
|
|
5601
|
-
path:
|
|
5912
|
+
path: path7,
|
|
5602
5913
|
exists,
|
|
5603
5914
|
currentPin,
|
|
5915
|
+
hasHeyspexEntry,
|
|
5604
5916
|
matches,
|
|
5605
5917
|
willWrite: !exists || Boolean(args.force)
|
|
5606
5918
|
};
|
|
@@ -5623,13 +5935,15 @@ function buildClaudeMcpAddRemoteCommandPowerShell(targetDir, url = DEFAULT_REMOT
|
|
|
5623
5935
|
`claude mcp add --transport http --scope project heyspex '${escapedUrl}' --header 'Authorization: Bearer \${${REMOTE_MCP_TOKEN_ENV}}'`
|
|
5624
5936
|
].join("\n");
|
|
5625
5937
|
}
|
|
5626
|
-
function buildClaudeMcpAddCommandPowerShell(targetDir,
|
|
5627
|
-
const escapedCli =
|
|
5938
|
+
function buildClaudeMcpAddCommandPowerShell(targetDir, cliPath, attachedVaultRoot, opts = {}) {
|
|
5939
|
+
const escapedCli = cliPath.replace(/'/g, "''");
|
|
5628
5940
|
const escapedTarget = targetDir.replace(/'/g, "''");
|
|
5629
|
-
const
|
|
5941
|
+
const serverArgs = mcpServerArgs(cliPath).map((a, i) => i === 0 ? `'${escapedCli}'` : a).join(" ");
|
|
5942
|
+
const pin = attachedVaultRoot === null ? null : vaultRootPin(attachedVaultRoot, "$PWD", opts);
|
|
5943
|
+
const envArg = pin ? ` --env HEYSPEX_VAULT_ROOT="${pin}"` : "";
|
|
5630
5944
|
return [
|
|
5631
5945
|
`Set-Location -LiteralPath '${escapedTarget}'`,
|
|
5632
|
-
`claude mcp add --transport stdio --scope project
|
|
5946
|
+
`claude mcp add --transport stdio --scope project${envArg} heyspex -- node ${serverArgs}`
|
|
5633
5947
|
].join("\n");
|
|
5634
5948
|
}
|
|
5635
5949
|
var PLUGIN_MARKETPLACE_NAME = "heyspex-marketplace";
|
|
@@ -5675,21 +5989,17 @@ function buildCopilotPluginInstallCommand(targetDir, platformRoot, spec = PLUGIN
|
|
|
5675
5989
|
`copilot plugin install ${spec}`
|
|
5676
5990
|
].join("\n");
|
|
5677
5991
|
}
|
|
5678
|
-
|
|
5679
|
-
["scripts", "install-statusline.mjs"],
|
|
5680
|
-
["scripts", "claude-statusline", "statusline-command.mjs"],
|
|
5681
|
-
["scripts", "claude-statusline", "statusline-command.ps1"],
|
|
5682
|
-
["scripts", "claude-statusline", "fonts", "HeySpexIcons.ttf"],
|
|
5683
|
-
["scripts", "claude-statusline", "fonts", "README.md"]
|
|
5684
|
-
];
|
|
5685
|
-
function renderStatusLineEntry() {
|
|
5992
|
+
function renderStatusLineEntry(customer = false) {
|
|
5686
5993
|
return {
|
|
5687
5994
|
type: "command",
|
|
5688
|
-
command: 'node "$CLAUDE_PROJECT_DIR/scripts/claude-statusline/statusline-command.mjs"',
|
|
5995
|
+
command: customer ? `node "$CLAUDE_PROJECT_DIR/${CUSTOMER_LAUNCHERS.claude.rel.join("/")}" --statusline` : 'node "$CLAUDE_PROJECT_DIR/scripts/claude-statusline/statusline-command.mjs"',
|
|
5689
5996
|
padding: 2,
|
|
5690
5997
|
refreshInterval: 5
|
|
5691
5998
|
};
|
|
5692
5999
|
}
|
|
6000
|
+
function isHeyspexStatusLineCommand(command) {
|
|
6001
|
+
return typeof command === "string" && (command.includes("statusline-command.") || /heyspex\/run-hook\.mjs"?\s+--statusline/.test(command));
|
|
6002
|
+
}
|
|
5693
6003
|
var MCP_ENTRY_ENV = "HEYSPEX_MCP_ENTRY";
|
|
5694
6004
|
function isAbsoluteFsPath(value) {
|
|
5695
6005
|
return typeof value === "string" && /^(?:[A-Za-z]:)?[\\/]/.test(value.trim());
|
|
@@ -5931,11 +6241,12 @@ function writeCustomerAgentKit(opts, deps) {
|
|
|
5931
6241
|
}
|
|
5932
6242
|
return { steps, settingsText };
|
|
5933
6243
|
}
|
|
5934
|
-
function buildCommandCodeMcpAddCommand(cliAbsPath) {
|
|
6244
|
+
function buildCommandCodeMcpAddCommand(cliAbsPath, opts = {}) {
|
|
5935
6245
|
const escapedCli = cliAbsPath.replace(/'/g, "''");
|
|
6246
|
+
const pin = vaultRootPin(void 0, "$PWD", opts);
|
|
5936
6247
|
return [
|
|
5937
6248
|
`cmdc mcp add --transport stdio heyspex -- node '${escapedCli}'`,
|
|
5938
|
-
`cmdc mcp env heyspex HEYSPEX_VAULT_ROOT "$
|
|
6249
|
+
...pin ? [`cmdc mcp env heyspex HEYSPEX_VAULT_ROOT "${pin}"`] : []
|
|
5939
6250
|
].join("\n");
|
|
5940
6251
|
}
|
|
5941
6252
|
function shouldWrite(exists, force) {
|
|
@@ -5948,7 +6259,6 @@ var WRITE_TARGET_KIND = {
|
|
|
5948
6259
|
claudeMcp: "claude_mcp",
|
|
5949
6260
|
claudeMd: "claude_md",
|
|
5950
6261
|
agentsMd: "agents_md",
|
|
5951
|
-
statuslineVendor: "statusline_vendor",
|
|
5952
6262
|
gitignore: "gitignore",
|
|
5953
6263
|
claudeMdTemplate: "claude_md_template",
|
|
5954
6264
|
agentsMdTemplate: "agents_md_template"
|
|
@@ -6009,12 +6319,6 @@ var INSTALL_WRITE_TARGETS = [
|
|
|
6009
6319
|
path: (targetDir, joinPath) => joinPath(targetDir, "AGENTS.md.template"),
|
|
6010
6320
|
appliesTo: (args, opts) => Boolean(args.registerVault) && !opts.attached && opts.installContract
|
|
6011
6321
|
},
|
|
6012
|
-
{
|
|
6013
|
-
kind: WRITE_TARGET_KIND.statuslineVendor,
|
|
6014
|
-
rel: "scripts/claude-statusline",
|
|
6015
|
-
path: (targetDir, joinPath) => joinPath(targetDir, "scripts", "claude-statusline"),
|
|
6016
|
-
appliesTo: (args) => (args.statusline ?? true) && args.claude
|
|
6017
|
-
},
|
|
6018
6322
|
{
|
|
6019
6323
|
kind: WRITE_TARGET_KIND.gitignore,
|
|
6020
6324
|
rel: ".gitignore",
|
|
@@ -6039,7 +6343,7 @@ function slugFromPath(p) {
|
|
|
6039
6343
|
return pathTail(p).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6040
6344
|
}
|
|
6041
6345
|
function registerInstalledVault(args, ctx) {
|
|
6042
|
-
const { targetDir, readFile, joinPath, registryOpts, now, basename:
|
|
6346
|
+
const { targetDir, readFile, joinPath, registryOpts, now, basename: basename5 } = ctx;
|
|
6043
6347
|
let vaultId;
|
|
6044
6348
|
let schemaVersion = HEYSPEX_YAML_SCHEMA_VERSION;
|
|
6045
6349
|
const yamlContent = readFile(joinPath(targetDir, "heyspex.yaml"));
|
|
@@ -6051,7 +6355,7 @@ function registerInstalledVault(args, ctx) {
|
|
|
6051
6355
|
if (versionMatch) schemaVersion = Number(versionMatch[1]);
|
|
6052
6356
|
}
|
|
6053
6357
|
if (!vaultId) vaultId = slugFromPath(targetDir);
|
|
6054
|
-
const displayName =
|
|
6358
|
+
const displayName = basename5(targetDir);
|
|
6055
6359
|
const before = loadVaultRegistry(registryOpts);
|
|
6056
6360
|
const existingVault = before.vaults.find(
|
|
6057
6361
|
(v) => v.id === vaultId || v.repo_root_abs === targetDir
|
|
@@ -6129,13 +6433,13 @@ function settingsInvalidResult(settingsPath, message2) {
|
|
|
6129
6433
|
function guardWritesAgainstLiteralBearer(writeFile, bearerToken) {
|
|
6130
6434
|
const token = typeof bearerToken === "string" ? bearerToken.trim() : "";
|
|
6131
6435
|
if (!token) return writeFile;
|
|
6132
|
-
return (
|
|
6436
|
+
return (path7, content) => {
|
|
6133
6437
|
if (content.includes(token)) {
|
|
6134
6438
|
throw new Error(
|
|
6135
|
-
`hs install: refusing to write ${
|
|
6439
|
+
`hs install: refusing to write ${path7} -- its content contains the literal bearer token (only the \${${REMOTE_MCP_TOKEN_ENV}} env reference may be written).`
|
|
6136
6440
|
);
|
|
6137
6441
|
}
|
|
6138
|
-
writeFile(
|
|
6442
|
+
writeFile(path7, content);
|
|
6139
6443
|
};
|
|
6140
6444
|
}
|
|
6141
6445
|
function runInstall(args, inputDeps) {
|
|
@@ -6221,16 +6525,20 @@ function runInstall(args, inputDeps) {
|
|
|
6221
6525
|
isError: true
|
|
6222
6526
|
};
|
|
6223
6527
|
}
|
|
6528
|
+
const markerBound2 = portableVaultPath !== null;
|
|
6224
6529
|
const planned = planSatelliteManifests(
|
|
6225
6530
|
targetDir,
|
|
6226
6531
|
attached.vaultRootAbs,
|
|
6227
6532
|
args,
|
|
6228
6533
|
deps,
|
|
6229
|
-
satelliteDeps
|
|
6534
|
+
satelliteDeps,
|
|
6535
|
+
markerBound2
|
|
6230
6536
|
);
|
|
6231
6537
|
satellitePins = planned.pinnable;
|
|
6232
6538
|
satelliteStalePins = planned.stale;
|
|
6233
|
-
const blocked = satellitePins.filter(
|
|
6539
|
+
const blocked = satellitePins.filter(
|
|
6540
|
+
(p) => p.exists && !p.matches && !args.force && (!markerBound2 || p.currentPin !== null)
|
|
6541
|
+
);
|
|
6234
6542
|
if (blocked.length > 0) {
|
|
6235
6543
|
const detail = blocked.map(
|
|
6236
6544
|
(p) => ` ${p.rel} \u2014 ${p.currentPin ? `currently pins ${p.currentPin}` : "carries no vault pin"}`
|
|
@@ -6368,15 +6676,17 @@ ${detail}
|
|
|
6368
6676
|
return !args.dryRun;
|
|
6369
6677
|
};
|
|
6370
6678
|
const pinsToWrite = satellitePins.filter((p) => p.willWrite);
|
|
6371
|
-
const pinsUnchanged = satellitePins.filter((p) => !p.willWrite);
|
|
6679
|
+
const pinsUnchanged = satellitePins.filter((p) => !p.willWrite && p.matches);
|
|
6372
6680
|
const satellitePinWritten = pinsToWrite.length > 0;
|
|
6373
6681
|
const relList = (plans) => plans.map((p) => p.rel).join(", ");
|
|
6682
|
+
const markerBound = portableVaultPath !== null;
|
|
6374
6683
|
if (attached) {
|
|
6684
|
+
const binding = markerBound ? `bound to vault "${attached.vaultId}" by the committed heyspex.yaml (vault_path ${portableVaultPath})` : `attached to vault "${attached.vaultId}" (workspace "${attached.workspaceId}")`;
|
|
6375
6685
|
steps.push({
|
|
6376
6686
|
kind: "satellite_attach",
|
|
6377
6687
|
path: attached.vaultRootAbs,
|
|
6378
6688
|
action: satellitePinWritten ? args.dryRun ? "would_create" : "created" : "skipped",
|
|
6379
|
-
detail: satellitePinWritten ?
|
|
6689
|
+
detail: satellitePinWritten ? `${binding} -- ${markerBound ? "portable manifest" : `${SATELLITE_VAULT_ENV} pin`} ${args.dryRun ? "would be written to" : "written to"} ` + relList(pinsToWrite) + (pinsUnchanged.length ? `; ${relList(pinsUnchanged)} already ${markerBound ? "current" : "pinned here"}` : "") : markerBound ? `already ${binding} -- ${relList(pinsUnchanged)} portable and current, nothing to write` : `already ${binding} -- ${relList(pinsUnchanged)} already pin this root, nothing to write`
|
|
6380
6690
|
});
|
|
6381
6691
|
for (const plan of satelliteStalePins) {
|
|
6382
6692
|
steps.push({
|
|
@@ -6483,7 +6793,9 @@ ${detail}
|
|
|
6483
6793
|
const claudePin = portableVaultPath ? "${CLAUDE_PROJECT_DIR:-.}/" + portableVaultPath : attachedRoot;
|
|
6484
6794
|
const cursorPin = portableVaultPath ? "${workspaceFolder}/" + portableVaultPath : attachedRoot;
|
|
6485
6795
|
const satelliteUnchanged = (rel) => pinsUnchanged.find((p) => p.rel === rel) ?? null;
|
|
6486
|
-
const
|
|
6796
|
+
const unchangedDetail = (plan) => plan.currentPin === null ? `portable manifest, bound by heyspex.yaml (vault "${attached?.vaultId}") -- unchanged` : `already pins vault "${attached?.vaultId}" (${attachedRoot}) -- unchanged`;
|
|
6797
|
+
const pinOpts = { targetIsVault: isVaultTarget(targetDir, deps) };
|
|
6798
|
+
const cursorJson = `${JSON.stringify(renderCursorMcpJson(cursorCliArg, cursorPin, pinOpts), null, 2)}
|
|
6487
6799
|
`;
|
|
6488
6800
|
if (args.cursor) {
|
|
6489
6801
|
const cursorUnchanged = satelliteUnchanged(".cursor/mcp.json");
|
|
@@ -6492,7 +6804,7 @@ ${detail}
|
|
|
6492
6804
|
kind: WRITE_TARGET_KIND.cursorMcp,
|
|
6493
6805
|
path: cursorPath,
|
|
6494
6806
|
action: "skipped",
|
|
6495
|
-
detail:
|
|
6807
|
+
detail: unchangedDetail(cursorUnchanged)
|
|
6496
6808
|
});
|
|
6497
6809
|
} else if (planWrite(WRITE_TARGET_KIND.cursorMcp, cursorPath, deps.fileExists(cursorPath))) {
|
|
6498
6810
|
deps.mkdir(deps.joinPath(targetDir, ".cursor"));
|
|
@@ -6504,9 +6816,9 @@ ${detail}
|
|
|
6504
6816
|
}
|
|
6505
6817
|
const remoteUrl = args.remote ? resolveRemoteMcpUrl(args.remoteUrl) : null;
|
|
6506
6818
|
const claudeJson = remoteUrl ? `${JSON.stringify(renderRemoteClaudeMcpJson(remoteUrl), null, 2)}
|
|
6507
|
-
` : `${JSON.stringify(renderClaudeMcpJson(claudeCliArg, claudePin), null, 2)}
|
|
6819
|
+
` : `${JSON.stringify(renderClaudeMcpJson(claudeCliArg, claudePin, pinOpts), null, 2)}
|
|
6508
6820
|
`;
|
|
6509
|
-
|
|
6821
|
+
let claudeManifestRegisters = false;
|
|
6510
6822
|
if (args.claude) {
|
|
6511
6823
|
const claudeUnchanged = satelliteUnchanged(".mcp.json");
|
|
6512
6824
|
if (attached && remoteUrl) {
|
|
@@ -6521,7 +6833,7 @@ ${detail}
|
|
|
6521
6833
|
existingJson = null;
|
|
6522
6834
|
}
|
|
6523
6835
|
}
|
|
6524
|
-
const pinEntry = renderClaudeMcpJson(claudeCliArg, claudePin).mcpServers.heyspex;
|
|
6836
|
+
const pinEntry = renderClaudeMcpJson(claudeCliArg, claudePin, pinOpts).mcpServers.heyspex;
|
|
6525
6837
|
const cloudEntry = renderRemoteClaudeMcpJson(remoteUrl).mcpServers.heyspex;
|
|
6526
6838
|
const merged = mergeAttachedRemoteMcpJson(
|
|
6527
6839
|
existingJson,
|
|
@@ -6559,12 +6871,42 @@ ${detail}
|
|
|
6559
6871
|
kind: WRITE_TARGET_KIND.claudeMcp,
|
|
6560
6872
|
path: claudePath,
|
|
6561
6873
|
action: "skipped",
|
|
6562
|
-
detail:
|
|
6874
|
+
detail: unchangedDetail(claudeUnchanged)
|
|
6563
6875
|
});
|
|
6876
|
+
claudeManifestRegisters = true;
|
|
6564
6877
|
} else if (planWrite(WRITE_TARGET_KIND.claudeMcp, claudePath, deps.fileExists(claudePath))) {
|
|
6565
6878
|
deps.writeFile(claudePath, claudeJson);
|
|
6879
|
+
claudeManifestRegisters = true;
|
|
6566
6880
|
}
|
|
6567
|
-
if (
|
|
6881
|
+
if (!claudeManifestRegisters) {
|
|
6882
|
+
claudeManifestRegisters = manifestHasHeyspexEntry(deps.readFile(claudePath));
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
const claudePs = remoteUrl ? buildClaudeMcpAddRemoteCommandPowerShell(targetDir, remoteUrl) : !customerTarget ? buildClaudeMcpAddCommandPowerShell(targetDir, cliPath, attachedRoot, pinOpts) : claudeManifestRegisters ? void 0 : (
|
|
6886
|
+
// Marker-bound: the committed heyspex.yaml binds the vault, so no
|
|
6887
|
+
// HEYSPEX_VAULT_ROOT pin is registered (`null`), matching the manifest.
|
|
6888
|
+
buildClaudeMcpAddCommandPowerShell(
|
|
6889
|
+
targetDir,
|
|
6890
|
+
claudeCliArg,
|
|
6891
|
+
markerBound ? null : attachedRoot,
|
|
6892
|
+
pinOpts
|
|
6893
|
+
)
|
|
6894
|
+
);
|
|
6895
|
+
const claudeMcpRegisteredByManifest = args.claude && !remoteUrl && claudePs === void 0;
|
|
6896
|
+
if (args.claude) {
|
|
6897
|
+
if (claudeMcpRegisteredByManifest) {
|
|
6898
|
+
if (args.claudeApply) {
|
|
6899
|
+
steps.push({
|
|
6900
|
+
kind: "claude_mcp_apply",
|
|
6901
|
+
path: "claude mcp add",
|
|
6902
|
+
action: "skipped",
|
|
6903
|
+
detail: ".mcp.json already registers the heyspex MCP server for Claude Code"
|
|
6904
|
+
});
|
|
6905
|
+
}
|
|
6906
|
+
nextSteps.push(
|
|
6907
|
+
"Claude Code: .mcp.json already registers the heyspex MCP server -- no `claude mcp add` needed; restart Claude Code (or `/mcp` reconnect) in this repo to load it."
|
|
6908
|
+
);
|
|
6909
|
+
} else if (args.claudeApply) {
|
|
6568
6910
|
steps.push({
|
|
6569
6911
|
kind: "claude_mcp_apply",
|
|
6570
6912
|
path: "claude mcp add",
|
|
@@ -6699,7 +7041,7 @@ ${detail}
|
|
|
6699
7041
|
if ((args.statusline ?? true) && args.claude) {
|
|
6700
7042
|
const statuslineShipsInPlatform = deps.fileExists(
|
|
6701
7043
|
deps.joinPath(platformRoot, "scripts", "claude-statusline", "statusline-command.mjs")
|
|
6702
|
-
);
|
|
7044
|
+
) && (!customerTarget || deps.fileExists(deps.joinPath(platformRoot, "scripts", "claude-hooks", "_run.mjs")));
|
|
6703
7045
|
if (!statuslineShipsInPlatform) {
|
|
6704
7046
|
steps.push({
|
|
6705
7047
|
kind: "claude_statusline",
|
|
@@ -6708,41 +7050,8 @@ ${detail}
|
|
|
6708
7050
|
detail: "not shipped in this package"
|
|
6709
7051
|
});
|
|
6710
7052
|
} else {
|
|
6711
|
-
if (targetDir !== platformRoot) {
|
|
6712
|
-
if (!deps.copyFile) {
|
|
6713
|
-
steps.push({
|
|
6714
|
-
kind: WRITE_TARGET_KIND.statuslineVendor,
|
|
6715
|
-
path: deps.joinPath(targetDir, "scripts", "claude-statusline"),
|
|
6716
|
-
action: "skipped",
|
|
6717
|
-
detail: "no copyFile capability in this caller"
|
|
6718
|
-
});
|
|
6719
|
-
} else {
|
|
6720
|
-
let copied = 0;
|
|
6721
|
-
let missing = 0;
|
|
6722
|
-
for (const rel of STATUSLINE_VENDOR_FILES) {
|
|
6723
|
-
const src = deps.joinPath(platformRoot, ...rel);
|
|
6724
|
-
const dest = deps.joinPath(targetDir, ...rel);
|
|
6725
|
-
if (!deps.fileExists(src)) {
|
|
6726
|
-
missing += 1;
|
|
6727
|
-
continue;
|
|
6728
|
-
}
|
|
6729
|
-
if (deps.fileExists(dest) && !args.force) continue;
|
|
6730
|
-
copied += 1;
|
|
6731
|
-
if (!args.dryRun) {
|
|
6732
|
-
deps.mkdir(deps.joinPath(targetDir, ...rel.slice(0, -1)));
|
|
6733
|
-
deps.copyFile(src, dest);
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
6736
|
-
steps.push({
|
|
6737
|
-
kind: WRITE_TARGET_KIND.statuslineVendor,
|
|
6738
|
-
path: deps.joinPath(targetDir, "scripts", "claude-statusline"),
|
|
6739
|
-
action: copied === 0 ? "skipped" : args.dryRun ? "would_create" : "created",
|
|
6740
|
-
detail: copied === 0 ? "already vendored" : `${copied} file${copied === 1 ? "" : "s"} (renderer + font + installer)` + (missing > 0 ? `; ${missing} missing from platform` : "")
|
|
6741
|
-
});
|
|
6742
|
-
}
|
|
6743
|
-
}
|
|
6744
7053
|
const settingsPath = deps.joinPath(targetDir, ".claude", "settings.json");
|
|
6745
|
-
const entry = renderStatusLineEntry();
|
|
7054
|
+
const entry = renderStatusLineEntry(customerTarget);
|
|
6746
7055
|
const raw = deps.readFile(settingsPath);
|
|
6747
7056
|
let settings = raw === null ? {} : null;
|
|
6748
7057
|
if (raw !== null) {
|
|
@@ -6753,7 +7062,7 @@ ${detail}
|
|
|
6753
7062
|
}
|
|
6754
7063
|
}
|
|
6755
7064
|
const existing = settings?.statusLine;
|
|
6756
|
-
const ours =
|
|
7065
|
+
const ours = isHeyspexStatusLineCommand(existing?.command);
|
|
6757
7066
|
if (settings === null) {
|
|
6758
7067
|
steps.push({
|
|
6759
7068
|
kind: "claude_statusline",
|
|
@@ -6780,7 +7089,7 @@ ${detail}
|
|
|
6780
7089
|
kind: "claude_statusline",
|
|
6781
7090
|
path: settingsPath,
|
|
6782
7091
|
action: args.dryRun ? "would_create" : "created",
|
|
6783
|
-
detail: existing ? "repaired to canonical entry" : "branded statusline (
|
|
7092
|
+
detail: existing ? "repaired to canonical entry" : customerTarget ? "branded statusline (via .claude/heyspex/run-hook.mjs)" : "branded statusline"
|
|
6784
7093
|
});
|
|
6785
7094
|
if (!args.dryRun) {
|
|
6786
7095
|
settings.statusLine = entry;
|
|
@@ -6839,7 +7148,10 @@ ${detail}
|
|
|
6839
7148
|
"The plugin's hooks and the kit hooks hs install wrote into .claude/settings.json are the same set -- enable one or the other, or every hook runs twice."
|
|
6840
7149
|
);
|
|
6841
7150
|
}
|
|
6842
|
-
const commandCodePs = buildCommandCodeMcpAddCommand(
|
|
7151
|
+
const commandCodePs = buildCommandCodeMcpAddCommand(
|
|
7152
|
+
customerTarget ? deps.joinPath(targetDir, PORTABLE_PACKAGE_DIR, ...MCP_REL) : cliPath,
|
|
7153
|
+
pinOpts
|
|
7154
|
+
);
|
|
6843
7155
|
if (args.commandCode) {
|
|
6844
7156
|
steps.push({
|
|
6845
7157
|
kind: "commandcode_mcp",
|
|
@@ -6927,6 +7239,7 @@ ${detail}
|
|
|
6927
7239
|
steps,
|
|
6928
7240
|
conflicts,
|
|
6929
7241
|
claude_powershell: args.claude ? claudePs : void 0,
|
|
7242
|
+
...claudeMcpRegisteredByManifest ? { claude_mcp_registered_by_manifest: true } : {},
|
|
6930
7243
|
remote_mcp_url: remoteUrl ?? void 0,
|
|
6931
7244
|
claude_plugin_powershell: args.plugin && pluginAvailable ? pluginPs : void 0,
|
|
6932
7245
|
claude_plugin_spec: args.plugin && pluginAvailable ? pluginSpec.spec : void 0,
|
|
@@ -6952,6 +7265,7 @@ ${detail}
|
|
|
6952
7265
|
repo_root_abs: attached.vaultRootAbs,
|
|
6953
7266
|
workspace_id: attached.workspaceId,
|
|
6954
7267
|
display_name: attached.displayName,
|
|
7268
|
+
...markerBound ? { via_marker: true } : {},
|
|
6955
7269
|
pinned_manifests: satellitePins.map((p) => p.rel),
|
|
6956
7270
|
pin_written: satellitePinWritten
|
|
6957
7271
|
}
|
|
@@ -6962,7 +7276,9 @@ ${detail}
|
|
|
6962
7276
|
"",
|
|
6963
7277
|
`Target: ${targetDir}`,
|
|
6964
7278
|
`Platform: ${platformRoot}`,
|
|
6965
|
-
|
|
7279
|
+
// A customer repo's manifests name the portable node_modules form, so
|
|
7280
|
+
// the receipt names that too -- never a path into the platform checkout.
|
|
7281
|
+
`MCP: ${customerTarget ? claudeCliArg : cliPath}`,
|
|
6966
7282
|
...attached ? [`Vault: ${attached.vaultRootAbs} (attached, id: ${attached.vaultId})`] : [],
|
|
6967
7283
|
""
|
|
6968
7284
|
];
|
|
@@ -7322,8 +7638,8 @@ var INSTALL_HELP = [
|
|
|
7322
7638
|
].join("\n");
|
|
7323
7639
|
|
|
7324
7640
|
// spex-console/src/lib/cli/install-identity.ts
|
|
7325
|
-
import { spawnSync as
|
|
7326
|
-
import { existsSync as
|
|
7641
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
7642
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
7327
7643
|
import { join as join11 } from "node:path";
|
|
7328
7644
|
import { createInterface } from "node:readline/promises";
|
|
7329
7645
|
init_credentials();
|
|
@@ -7331,7 +7647,7 @@ init_credentials();
|
|
|
7331
7647
|
// vendor/heyspex-mcp/whoami.mjs
|
|
7332
7648
|
init_heyspex_home();
|
|
7333
7649
|
init_credentials();
|
|
7334
|
-
import { homedir as
|
|
7650
|
+
import { homedir as homedir10 } from "node:os";
|
|
7335
7651
|
var SESSION_PATH = "/api/mcp/session";
|
|
7336
7652
|
async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
|
|
7337
7653
|
if (!token) return { kind: "no_credential" };
|
|
@@ -7685,7 +8001,7 @@ function isInteractiveTerminal() {
|
|
|
7685
8001
|
}
|
|
7686
8002
|
function defaultIdentityGateIo(opts) {
|
|
7687
8003
|
const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
|
|
7688
|
-
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join11(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) =>
|
|
8004
|
+
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join11(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync8(p));
|
|
7689
8005
|
return {
|
|
7690
8006
|
consoleUrl,
|
|
7691
8007
|
interactive: opts.interactive ?? isInteractiveTerminal(),
|
|
@@ -7714,7 +8030,7 @@ function defaultIdentityGateIo(opts) {
|
|
|
7714
8030
|
}
|
|
7715
8031
|
return 0;
|
|
7716
8032
|
}
|
|
7717
|
-
const res =
|
|
8033
|
+
const res = spawnSync3(
|
|
7718
8034
|
process.execPath,
|
|
7719
8035
|
[loginCli, "login", flag, "--console-url", consoleUrl],
|
|
7720
8036
|
{
|
|
@@ -7787,35 +8103,36 @@ async function main() {
|
|
|
7787
8103
|
return;
|
|
7788
8104
|
}
|
|
7789
8105
|
const args = parsed.args;
|
|
7790
|
-
const targetDir =
|
|
8106
|
+
const targetDir = resolve6(
|
|
7791
8107
|
resolveInstallTargetDir(args.targetDir, process4.env, process4.cwd(), {
|
|
7792
8108
|
satellite: args.attachVault !== void 0
|
|
7793
8109
|
})
|
|
7794
8110
|
);
|
|
7795
|
-
const platformRoot =
|
|
8111
|
+
const platformRoot = resolve6(
|
|
7796
8112
|
resolveCallerPath(args.platformRoot, process4.env, process4.cwd()) || platformDefault || process4.cwd()
|
|
7797
8113
|
);
|
|
8114
|
+
const eol = createGeneratedFileEol({ repoRoot: targetDir });
|
|
7798
8115
|
const deps = {
|
|
7799
8116
|
repoRoot: targetDir,
|
|
7800
8117
|
resolvedTarget: targetDir,
|
|
7801
8118
|
resolvedPlatform: platformRoot,
|
|
7802
|
-
fileExists: (p) =>
|
|
8119
|
+
fileExists: (p) => existsSync9(p),
|
|
7803
8120
|
readFile: (p) => {
|
|
7804
8121
|
try {
|
|
7805
|
-
return
|
|
8122
|
+
return readFileSync14(p, "utf8");
|
|
7806
8123
|
} catch {
|
|
7807
8124
|
return null;
|
|
7808
8125
|
}
|
|
7809
8126
|
},
|
|
7810
|
-
writeFile: (p, c) =>
|
|
7811
|
-
mkdir: (p) =>
|
|
7812
|
-
copyFile: (src, dest) =>
|
|
8127
|
+
writeFile: (p, c) => writeFileSync7(p, eol.convert(p, c), "utf8"),
|
|
8128
|
+
mkdir: (p) => mkdirSync7(p, { recursive: true }),
|
|
8129
|
+
copyFile: (src, dest) => copyFileSync2(src, dest),
|
|
7813
8130
|
joinPath: (...s) => join12(...s),
|
|
7814
8131
|
// Customer-template step (--register-vault) walks specs/ before applying.
|
|
7815
8132
|
readdir: (p) => readdirSync2(p),
|
|
7816
8133
|
isDirectory: (p) => {
|
|
7817
8134
|
try {
|
|
7818
|
-
return
|
|
8135
|
+
return statSync5(p).isDirectory();
|
|
7819
8136
|
} catch {
|
|
7820
8137
|
return false;
|
|
7821
8138
|
}
|
|
@@ -7874,11 +8191,17 @@ async function main() {
|
|
|
7874
8191
|
const payload = remoteReceipt && !result.isError ? { ...result.payload, ...remoteReceipt } : result.payload;
|
|
7875
8192
|
sink.write(`${args.json ? JSON.stringify(payload, null, 2) : result.message}
|
|
7876
8193
|
`);
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
8194
|
+
const applyPayload = result.payload;
|
|
8195
|
+
if (!result.isError && args.claudeApply && !args.dryRun && args.claude && applyPayload.claude_mcp_registered_by_manifest) {
|
|
8196
|
+
process4.stdout.write(
|
|
8197
|
+
"Claude Code: .mcp.json already registers the heyspex MCP server (nothing to apply).\n"
|
|
8198
|
+
);
|
|
8199
|
+
} else if (!result.isError && args.claudeApply && !args.dryRun && args.claude) {
|
|
8200
|
+
const cliPath = mcpCliArgFor("claude", platformRoot, targetDir, join12);
|
|
8201
|
+
const attachedRoot = applyPayload.attached_vault?.repo_root_abs;
|
|
8202
|
+
const viaMarker = applyPayload.attached_vault?.via_marker === true;
|
|
7880
8203
|
const mcpVaultRoot = attachedRoot ?? targetDir;
|
|
7881
|
-
const claude =
|
|
8204
|
+
const claude = spawnSync4(
|
|
7882
8205
|
"claude",
|
|
7883
8206
|
[
|
|
7884
8207
|
"mcp",
|
|
@@ -7887,8 +8210,7 @@ async function main() {
|
|
|
7887
8210
|
"stdio",
|
|
7888
8211
|
"--scope",
|
|
7889
8212
|
"project",
|
|
7890
|
-
"--env",
|
|
7891
|
-
`HEYSPEX_VAULT_ROOT=${mcpVaultRoot}`,
|
|
8213
|
+
...viaMarker ? [] : ["--env", `HEYSPEX_VAULT_ROOT=${mcpVaultRoot}`],
|
|
7892
8214
|
"heyspex",
|
|
7893
8215
|
"--",
|
|
7894
8216
|
process4.execPath,
|
|
@@ -7901,19 +8223,19 @@ async function main() {
|
|
|
7901
8223
|
`hs install: claude mcp add failed (exit ${claude.status ?? "?"}).
|
|
7902
8224
|
${claude.stderr || claude.stdout || ""}
|
|
7903
8225
|
Run manually:
|
|
7904
|
-
${buildClaudeMcpAddCommandPowerShell(targetDir, cliPath, attachedRoot)}
|
|
8226
|
+
${buildClaudeMcpAddCommandPowerShell(targetDir, cliPath, viaMarker ? null : attachedRoot)}
|
|
7905
8227
|
`
|
|
7906
8228
|
);
|
|
7907
8229
|
} else {
|
|
7908
8230
|
process4.stdout.write("Claude Code: registered heyspex MCP (--scope project).\n");
|
|
7909
8231
|
}
|
|
7910
8232
|
}
|
|
7911
|
-
if (!result.isError && args.pluginApply && !args.dryRun &&
|
|
8233
|
+
if (!result.isError && args.pluginApply && !args.dryRun && existsSync9(pluginMarketplaceManifestPath(platformRoot, join12))) {
|
|
7912
8234
|
const shell = process4.platform === "win32";
|
|
7913
8235
|
const spec = resolvePluginSpec(platformRoot, {
|
|
7914
8236
|
readFile: (p) => {
|
|
7915
8237
|
try {
|
|
7916
|
-
return
|
|
8238
|
+
return readFileSync14(p, "utf8");
|
|
7917
8239
|
} catch {
|
|
7918
8240
|
return null;
|
|
7919
8241
|
}
|
|
@@ -7927,7 +8249,7 @@ ${buildClaudeMcpAddCommandPowerShell(targetDir, cliPath, attachedRoot)}
|
|
|
7927
8249
|
spec.spec,
|
|
7928
8250
|
scope
|
|
7929
8251
|
);
|
|
7930
|
-
const claudeRun = (cliArgs) =>
|
|
8252
|
+
const claudeRun = (cliArgs) => spawnSync4("claude", cliArgs, { cwd: targetDir, encoding: "utf8", shell });
|
|
7931
8253
|
let marketplace = claudeRun(["plugin", "marketplace", "add", platformRoot]);
|
|
7932
8254
|
if (marketplace.status !== 0 && /already/i.test(marketplace.stderr + marketplace.stdout)) {
|
|
7933
8255
|
marketplace = claudeRun(["plugin", "marketplace", "update", spec.marketplaceName]);
|
|
@@ -7962,7 +8284,7 @@ ${manualBlock}
|
|
|
7962
8284
|
if (!result.isError && args.copilotPluginApply && !args.dryRun && copilotCommands) {
|
|
7963
8285
|
const shell = process4.platform === "win32";
|
|
7964
8286
|
const spec = result.payload.copilot_plugin_spec ?? "";
|
|
7965
|
-
const copilotRun = (cliArgs) =>
|
|
8287
|
+
const copilotRun = (cliArgs) => spawnSync4("copilot", cliArgs, { cwd: targetDir, encoding: "utf8", shell });
|
|
7966
8288
|
const failed = (what, r) => process4.stderr.write(
|
|
7967
8289
|
`hs install: copilot plugin ${what} failed (${r.error ? r.error.message : `exit ${r.status ?? "?"}`}).
|
|
7968
8290
|
${r.stderr || r.stdout || ""}
|
|
@@ -7989,8 +8311,8 @@ ${copilotCommands}
|
|
|
7989
8311
|
const glyphScript = [
|
|
7990
8312
|
join12(targetDir, "scripts", "install-statusline.mjs"),
|
|
7991
8313
|
join12(platformRoot, "scripts", "install-statusline.mjs")
|
|
7992
|
-
].find((p) =>
|
|
7993
|
-
const glyph = glyphScript ?
|
|
8314
|
+
].find((p) => existsSync9(p));
|
|
8315
|
+
const glyph = glyphScript ? spawnSync4(process4.execPath, [glyphScript, "--glyph-only"], {
|
|
7994
8316
|
cwd: targetDir,
|
|
7995
8317
|
encoding: "utf8"
|
|
7996
8318
|
}) : { status: 1, stdout: "", stderr: "install-statusline.mjs not found" };
|