heyspex 0.2.0 → 0.2.1
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/.claude-plugin/marketplace.json +14 -0
- package/bin/hs.mjs +163 -11
- package/dist/cli/hs-check.mjs +88 -46
- package/dist/cli/hs-claim.mjs +84 -39
- package/dist/cli/hs-connect.mjs +583 -129
- package/dist/cli/hs-defer.mjs +251 -30
- package/dist/cli/hs-dev.mjs +20 -8
- package/dist/cli/hs-env.mjs +62 -34
- package/dist/cli/hs-index-rebuild.mjs +288 -83
- package/dist/cli/hs-index-reconcile.mjs +397 -111
- package/dist/cli/hs-init.mjs +2143 -91
- package/dist/cli/hs-install.mjs +628 -106
- package/dist/cli/hs-join.mjs +686 -236
- package/dist/cli/hs-vault-pull.mjs +3189 -179
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +396 -178
- package/dist/cli/hs-workspace.mjs +289 -151
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
- package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
- package/docs/copilot-plugin-install.md +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +20 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +309 -0
- package/plugins/heyspex/bin/run-hook.mjs +20 -2
- package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
- package/plugins/heyspex/hooks/hooks.json +94 -37
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
- package/scripts/claude-hooks/_run.mjs +316 -23
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
- package/scripts/claude-hooks/session-end.mjs +2 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
- package/scripts/cursor-hooks/manifest.mjs +46 -0
- package/scripts/heyspex-mode.mjs +20 -0
- package/scripts/hs-mode.mjs +1 -1
- package/scripts/hs-view.mjs +180 -17
- package/scripts/install-claude-code-hooks.mjs +3 -1
- package/scripts/lib/console-url-resolver.mjs +3 -2
- package/scripts/lib/customer-kit.mjs +327 -10
- package/scripts/lib/dev-mode.mjs +7 -6
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-log.mjs +2 -1
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -356
- package/scripts/lib/window-placement.mjs +109 -0
- package/spexharness/src/adapters/claude-code.mjs +11 -3
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/intent-classifier.mjs +4 -2
- package/spexharness/src/intent-directive.mjs +626 -541
- package/spexharness/src/rule-table.mjs +31 -1
- package/vendor/heyspex-mcp/account-switch.mjs +6 -5
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
- package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
- package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
- package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
- package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
- package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
- package/vendor/heyspex-mcp/cli.mjs +7 -1
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
- package/vendor/heyspex-mcp/console-transport.mjs +7 -1
- package/vendor/heyspex-mcp/credentials.mjs +9 -8
- package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
- package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
- package/vendor/heyspex-mcp/http-transport.mjs +2 -0
- package/vendor/heyspex-mcp/list-tools.mjs +1 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/plan-push.mjs +3 -2
- package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
- package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
- package/vendor/heyspex-mcp/promote-research.mjs +6 -0
- package/vendor/heyspex-mcp/read-path.mjs +162 -6
- package/vendor/heyspex-mcp/request-context.mjs +25 -1
- package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/session-owner.mjs +3 -2
- package/vendor/heyspex-mcp/session-tools.mjs +10 -0
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
- package/vendor/heyspex-mcp/split-research.mjs +3 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
- package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
- package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
- package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
- package/vendor/heyspex-mcp/vault-io.mjs +720 -724
- package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
- package/vendor/heyspex-mcp/whoami.mjs +2 -1
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
package/dist/cli/hs-vault.mjs
CHANGED
|
@@ -10,11 +10,26 @@ var __export = (target, all) => {
|
|
|
10
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// scripts/lib/heyspex-home.mjs
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
function heyspexHome(env = process.env) {
|
|
16
|
+
const override = env?.HEYSPEX_HOME;
|
|
17
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
18
|
+
return override.trim();
|
|
19
|
+
}
|
|
20
|
+
return homedir();
|
|
21
|
+
}
|
|
22
|
+
var init_heyspex_home = __esm({
|
|
23
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
24
|
+
"use strict";
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
13
28
|
// vendor/heyspex-mcp/credentials.mjs
|
|
14
29
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
15
|
-
import os, { homedir } from "node:os";
|
|
30
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
16
31
|
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
17
|
-
function credentialsFilePath(homeDir =
|
|
32
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
18
33
|
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
19
34
|
}
|
|
20
35
|
function parseMcpToken(raw) {
|
|
@@ -127,14 +142,14 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
127
142
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
128
143
|
return value || null;
|
|
129
144
|
}
|
|
130
|
-
function readBearer({ env = process.env, homeDir =
|
|
145
|
+
function readBearer({ env = process.env, homeDir = heyspexHome(), readFile: readFile2, accountId: accountId2 } = {}) {
|
|
131
146
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
132
147
|
const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
|
|
133
148
|
const named = typeof accountId2 === "string" && accountId2.trim() ? accountId2.trim() : null;
|
|
134
149
|
const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
|
|
135
150
|
return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
|
|
136
151
|
}
|
|
137
|
-
function readCurrentAccountId({ homeDir =
|
|
152
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
138
153
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
139
154
|
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
140
155
|
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
@@ -152,6 +167,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
|
|
|
152
167
|
var init_credentials = __esm({
|
|
153
168
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
154
169
|
"use strict";
|
|
170
|
+
init_heyspex_home();
|
|
155
171
|
MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
|
|
156
172
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
157
173
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
@@ -164,9 +180,9 @@ var init_credentials = __esm({
|
|
|
164
180
|
|
|
165
181
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
166
182
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
167
|
-
import { homedir as
|
|
183
|
+
import { homedir as homedir3 } from "node:os";
|
|
168
184
|
import { join as join3 } from "node:path";
|
|
169
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
185
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
170
186
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
171
187
|
if (fromEnv) return fromEnv;
|
|
172
188
|
return readCurrentAccountId({ homeDir, readFile: readFile2 });
|
|
@@ -174,10 +190,26 @@ function resolveSessionOwnerUncached({ env = process.env, homeDir = homedir2(),
|
|
|
174
190
|
var init_session_owner = __esm({
|
|
175
191
|
"vendor/heyspex-mcp/session-owner.mjs"() {
|
|
176
192
|
"use strict";
|
|
193
|
+
init_heyspex_home();
|
|
177
194
|
init_credentials();
|
|
178
195
|
}
|
|
179
196
|
});
|
|
180
197
|
|
|
198
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
199
|
+
import { homedir as homedir6 } from "node:os";
|
|
200
|
+
function heyspexHome2(env = process.env) {
|
|
201
|
+
const override = env?.HEYSPEX_HOME;
|
|
202
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
203
|
+
return override.trim();
|
|
204
|
+
}
|
|
205
|
+
return homedir6();
|
|
206
|
+
}
|
|
207
|
+
var init_heyspex_home2 = __esm({
|
|
208
|
+
"spex-console/src/lib/heyspex-home.ts"() {
|
|
209
|
+
"use strict";
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
181
213
|
// spex-console/src/lib/vault-registry/types.ts
|
|
182
214
|
function emptyVaultRegistry() {
|
|
183
215
|
return {
|
|
@@ -198,14 +230,13 @@ var init_types = __esm({
|
|
|
198
230
|
});
|
|
199
231
|
|
|
200
232
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
201
|
-
import { existsSync as
|
|
202
|
-
import {
|
|
203
|
-
import { dirname as dirname4, join as join6, resolve as resolve3 } from "node:path";
|
|
233
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
234
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
204
235
|
function vaultRegistryDir(opts = {}) {
|
|
205
|
-
return opts.registryDir ??
|
|
236
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
206
237
|
}
|
|
207
238
|
function vaultRegistryFilePath(opts = {}) {
|
|
208
|
-
return
|
|
239
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
209
240
|
}
|
|
210
241
|
function isNonEmptyString(v) {
|
|
211
242
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -306,9 +337,9 @@ function parseVaultRegistryFile(json) {
|
|
|
306
337
|
}
|
|
307
338
|
function readRegistryJson(opts) {
|
|
308
339
|
const filePath = vaultRegistryFilePath(opts);
|
|
309
|
-
if (!
|
|
340
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
310
341
|
try {
|
|
311
|
-
const raw =
|
|
342
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
312
343
|
const json = JSON.parse(raw);
|
|
313
344
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
314
345
|
return { kind: "ok", raw, json };
|
|
@@ -362,14 +393,14 @@ function underTestRunner() {
|
|
|
362
393
|
}
|
|
363
394
|
function samePathLoose(a, b) {
|
|
364
395
|
const norm = (p) => {
|
|
365
|
-
const r =
|
|
396
|
+
const r = resolve4(p).replace(/[\\/]+$/, "");
|
|
366
397
|
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
367
398
|
};
|
|
368
399
|
return norm(a) === norm(b);
|
|
369
400
|
}
|
|
370
401
|
function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
371
402
|
if (!underTestRunner()) return;
|
|
372
|
-
if (samePathLoose(dirname4(filePath),
|
|
403
|
+
if (samePathLoose(dirname4(filePath), join7(heyspexHome2(), ".heyspex"))) {
|
|
373
404
|
throw new Error(
|
|
374
405
|
`registry-file: refusing to write ${filePath} under a test runner \u2014 that is the real ~/.heyspex registry. Pass opts.registryDir (a temp dir).`
|
|
375
406
|
);
|
|
@@ -378,10 +409,10 @@ function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
|
378
409
|
function writeVaultRegistry(registry, opts = {}) {
|
|
379
410
|
const filePath = vaultRegistryFilePath(opts);
|
|
380
411
|
assertNotRealHomeRegistryUnderTest(filePath);
|
|
381
|
-
|
|
412
|
+
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
382
413
|
const tmpPath = `${filePath}.tmp`;
|
|
383
|
-
|
|
384
|
-
|
|
414
|
+
writeFileSync3(tmpPath, serializeVaultRegistry(registry), "utf8");
|
|
415
|
+
renameSync3(tmpPath, filePath);
|
|
385
416
|
}
|
|
386
417
|
function isValidId(id) {
|
|
387
418
|
if (!isNonEmptyString(id)) return false;
|
|
@@ -550,10 +581,10 @@ function rekeyVaultRowsToCloudIds(registry, proofs, plan) {
|
|
|
550
581
|
};
|
|
551
582
|
}
|
|
552
583
|
function planRegistryMigration(registry, opts = {}) {
|
|
553
|
-
const pathExists = opts.pathExists ?? ((p) =>
|
|
584
|
+
const pathExists = opts.pathExists ?? ((p) => existsSync4(p));
|
|
554
585
|
const readMarker = opts.readMarker ?? ((root) => {
|
|
555
586
|
try {
|
|
556
|
-
return
|
|
587
|
+
return readFileSync6(join7(root, "heyspex.yaml"), "utf8");
|
|
557
588
|
} catch {
|
|
558
589
|
return null;
|
|
559
590
|
}
|
|
@@ -687,6 +718,7 @@ var emptyPlan;
|
|
|
687
718
|
var init_registry_file = __esm({
|
|
688
719
|
"spex-console/src/lib/vault-registry/registry-file.ts"() {
|
|
689
720
|
"use strict";
|
|
721
|
+
init_heyspex_home2();
|
|
690
722
|
init_types();
|
|
691
723
|
init_types();
|
|
692
724
|
emptyPlan = (current) => ({
|
|
@@ -703,11 +735,11 @@ var init_registry_file = __esm({
|
|
|
703
735
|
});
|
|
704
736
|
|
|
705
737
|
// scripts/lib/dev-mode.mjs
|
|
706
|
-
import { mkdirSync as
|
|
707
|
-
import { homedir as
|
|
708
|
-
import { join as
|
|
709
|
-
function devModeFilePath(homeDir =
|
|
710
|
-
return
|
|
738
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
739
|
+
import { homedir as homedir7 } from "node:os";
|
|
740
|
+
import { join as join8 } from "node:path";
|
|
741
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
742
|
+
return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
711
743
|
}
|
|
712
744
|
function normalizeMode(raw) {
|
|
713
745
|
const v = String(raw ?? "").trim().toLowerCase();
|
|
@@ -731,7 +763,7 @@ function deriveSemantics(mode, reliance) {
|
|
|
731
763
|
vaultWriteMode: local ? "local" : "cloud"
|
|
732
764
|
};
|
|
733
765
|
}
|
|
734
|
-
function readLocalDevModeRecord({ homeDir =
|
|
766
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile } = {}) {
|
|
735
767
|
let raw;
|
|
736
768
|
try {
|
|
737
769
|
raw = readFile2(devModeFilePath(homeDir));
|
|
@@ -761,7 +793,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile: readFile2 = de
|
|
|
761
793
|
}
|
|
762
794
|
function resolveDevMode({
|
|
763
795
|
env = process.env,
|
|
764
|
-
homeDir =
|
|
796
|
+
homeDir = heyspexHome(),
|
|
765
797
|
readFile: readFile2 = defaultReadFile,
|
|
766
798
|
cloudSetting = void 0
|
|
767
799
|
} = {}) {
|
|
@@ -824,11 +856,12 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
|
824
856
|
var init_dev_mode = __esm({
|
|
825
857
|
"scripts/lib/dev-mode.mjs"() {
|
|
826
858
|
"use strict";
|
|
859
|
+
init_heyspex_home();
|
|
827
860
|
init_credentials();
|
|
828
861
|
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
829
862
|
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
830
863
|
DEV_MODE_FILENAME = "dev-mode.json";
|
|
831
|
-
defaultReadFile = (p) =>
|
|
864
|
+
defaultReadFile = (p) => readFileSync7(p, "utf8");
|
|
832
865
|
}
|
|
833
866
|
});
|
|
834
867
|
|
|
@@ -903,13 +936,12 @@ var init_default_workspace = __esm({
|
|
|
903
936
|
});
|
|
904
937
|
|
|
905
938
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
906
|
-
import { readFileSync as
|
|
907
|
-
import os2 from "node:os";
|
|
939
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
908
940
|
function resolveConfiguredAccountId(opts = {}) {
|
|
909
941
|
return resolveSessionOwnerUncached({
|
|
910
942
|
env: opts.env ?? process.env,
|
|
911
|
-
homeDir: opts.homeDir ??
|
|
912
|
-
readFile: opts.readFile ?? ((p) =>
|
|
943
|
+
homeDir: opts.homeDir ?? heyspexHome2(),
|
|
944
|
+
readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
|
|
913
945
|
});
|
|
914
946
|
}
|
|
915
947
|
function resolveCloudAccountIdOrRefuse(opts = {}) {
|
|
@@ -919,6 +951,9 @@ function resolveCloudAccountIdOrRefuse(opts = {}) {
|
|
|
919
951
|
function vaultOwnedByOtherAccountMessage(id) {
|
|
920
952
|
return `vault "${id}" is already registered to another HeySpex account, so nothing was changed. To move it to this account: sign in as the account that owns it (\`hs login --switch-account\`), remove it there (\`hs vault unregister ${id}\` or the console's vault settings; this deletes only the registry row, never repo files, and archiving alone keeps the id taken), then sign back in here and re-run this command. Or keep both by registering this repo under a new vault id (\`hs vault register --path . --id <new-id>\`). Transferring a vault between accounts is not supported.`;
|
|
921
953
|
}
|
|
954
|
+
function vaultNotOwnedByCallerMessage(id) {
|
|
955
|
+
return `vault "${id}" belongs to another HeySpex account, so nothing was changed. Only the account that owns a vault can rename, move, archive, restore or unregister it (\`hs login --switch-account\` to act as that account).`;
|
|
956
|
+
}
|
|
922
957
|
function workspaceOwnedByOtherAccountMessage(id) {
|
|
923
958
|
return `workspace "${id}" belongs to another HeySpex account, so nothing was changed. Pick a different workspace id (e.g. \`--workspace <new-id>\`), or sign in as the account that owns it (\`hs login --switch-account\`). Transferring a workspace between accounts is not supported.`;
|
|
924
959
|
}
|
|
@@ -1005,8 +1040,14 @@ async function mutateHttp(http, method, path5, body, landed) {
|
|
|
1005
1040
|
const res = await cloudHttpRequest(http, method, path5, body);
|
|
1006
1041
|
if (res.ok) return landed;
|
|
1007
1042
|
if (res.status === 404) return "not_found";
|
|
1043
|
+
if (res.status === 403 && isOwnedByOtherAccountCode(errorCodeOf(res.json))) {
|
|
1044
|
+
return "owned_by_other_account";
|
|
1045
|
+
}
|
|
1008
1046
|
return cloudWriteFailure(http, res);
|
|
1009
1047
|
}
|
|
1048
|
+
function isOwnedByOtherAccountCode(code) {
|
|
1049
|
+
return code === VAULT_OWNED_BY_OTHER_ACCOUNT || code === WORKSPACE_OWNED_BY_OTHER_ACCOUNT;
|
|
1050
|
+
}
|
|
1010
1051
|
function manageVerbPath(kind, id) {
|
|
1011
1052
|
return `/api/${kind}/${encodeURIComponent(id)}`;
|
|
1012
1053
|
}
|
|
@@ -1014,6 +1055,7 @@ var CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE, VAULT_OWNED_BY_OTHER_ACCOUNT, WORKSPACE_O
|
|
|
1014
1055
|
var init_vault_workspace_shared = __esm({
|
|
1015
1056
|
"spex-console/src/lib/cli/vault-workspace-shared.ts"() {
|
|
1016
1057
|
"use strict";
|
|
1058
|
+
init_heyspex_home2();
|
|
1017
1059
|
init_session_owner();
|
|
1018
1060
|
init_default_workspace();
|
|
1019
1061
|
CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE = "refusing -- no signed-in account (no HS_ACCOUNT_ID env var, and no account_id: in ~/.heyspex/config.yaml). Run `hs login` first; nothing was written.";
|
|
@@ -1058,7 +1100,7 @@ var init_interfaces = __esm({
|
|
|
1058
1100
|
});
|
|
1059
1101
|
|
|
1060
1102
|
// spex-console/src/lib/db/schema/postgres.ts
|
|
1061
|
-
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements;
|
|
1103
|
+
var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
|
|
1062
1104
|
var init_postgres = __esm({
|
|
1063
1105
|
"spex-console/src/lib/db/schema/postgres.ts"() {
|
|
1064
1106
|
"use strict";
|
|
@@ -1500,7 +1542,13 @@ var init_postgres = __esm({
|
|
|
1500
1542
|
"priority_change",
|
|
1501
1543
|
"parent_change",
|
|
1502
1544
|
"claim_acquired",
|
|
1503
|
-
"claim_released"
|
|
1545
|
+
"claim_released",
|
|
1546
|
+
// Collaborator write audit (HS-FWB6, migration 0077).
|
|
1547
|
+
"spec_created",
|
|
1548
|
+
"spec_edited",
|
|
1549
|
+
"spec_deleted",
|
|
1550
|
+
"capture_written",
|
|
1551
|
+
"wip_recorded"
|
|
1504
1552
|
]);
|
|
1505
1553
|
focusPin = pgTable(
|
|
1506
1554
|
"focus_pin",
|
|
@@ -2693,11 +2741,43 @@ var init_postgres = __esm({
|
|
|
2693
2741
|
// `owner/name`
|
|
2694
2742
|
branch: text("branch").notNull(),
|
|
2695
2743
|
commitSha: text("commit_sha").notNull(),
|
|
2696
|
-
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`)
|
|
2744
|
+
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2745
|
+
// The pull request that carries this settlement
|
|
2746
|
+
// ([[HS-9D25-CWPX-EPIC-branch-lifecycle-open-a-pull-request]]). NULL until
|
|
2747
|
+
// the branch has gone quiet and a PR was opened or found; a write made
|
|
2748
|
+
// after its PR merged stays NULL, which is what triggers the next PR.
|
|
2749
|
+
pullRequestNumber: integer("pull_request_number"),
|
|
2750
|
+
pullRequestUrl: text("pull_request_url")
|
|
2697
2751
|
},
|
|
2698
2752
|
(table) => [
|
|
2699
2753
|
// "latest settlement for this spec" — the read path's only query.
|
|
2700
|
-
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc())
|
|
2754
|
+
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc()),
|
|
2755
|
+
// The PR sweep's only query: branches with settlements not yet in a PR.
|
|
2756
|
+
index("spec_settlements_unproposed_idx").on(table.vaultId, table.repo, table.branch).where(sql`${table.pullRequestNumber} IS NULL`)
|
|
2757
|
+
]
|
|
2758
|
+
);
|
|
2759
|
+
sessionBranchDeclarations = pgTable(
|
|
2760
|
+
"session_branch_declarations",
|
|
2761
|
+
{
|
|
2762
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
2763
|
+
vaultId: text("vault_id").notNull().default(DEFAULT_VAULT_ID),
|
|
2764
|
+
actorKey: text("actor_key").notNull(),
|
|
2765
|
+
sessionId: text("session_id").notNull(),
|
|
2766
|
+
branch: text("branch").notNull(),
|
|
2767
|
+
declaredAt: timestamp("declared_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2768
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
2769
|
+
},
|
|
2770
|
+
(table) => [
|
|
2771
|
+
uniqueIndex("session_branch_declarations_uq").on(
|
|
2772
|
+
table.vaultId,
|
|
2773
|
+
table.actorKey,
|
|
2774
|
+
table.sessionId
|
|
2775
|
+
),
|
|
2776
|
+
index("session_branch_declarations_lookup_idx").on(
|
|
2777
|
+
table.vaultId,
|
|
2778
|
+
table.actorKey,
|
|
2779
|
+
table.declaredAt.desc()
|
|
2780
|
+
)
|
|
2701
2781
|
]
|
|
2702
2782
|
);
|
|
2703
2783
|
}
|
|
@@ -3352,7 +3432,7 @@ var init_spec_read_service = __esm({
|
|
|
3352
3432
|
});
|
|
3353
3433
|
|
|
3354
3434
|
// spex-console/bin/hs-vault.ts
|
|
3355
|
-
import { existsSync as
|
|
3435
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "node:fs";
|
|
3356
3436
|
import path4 from "node:path";
|
|
3357
3437
|
import process4 from "node:process";
|
|
3358
3438
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
@@ -3379,11 +3459,12 @@ function shellQuote(p) {
|
|
|
3379
3459
|
var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER_PATH)}` : HS_BIN_NAME;
|
|
3380
3460
|
|
|
3381
3461
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
3462
|
+
init_heyspex_home();
|
|
3382
3463
|
init_credentials();
|
|
3383
3464
|
init_session_owner();
|
|
3384
|
-
import { readFileSync as
|
|
3385
|
-
import { homedir as
|
|
3386
|
-
import { join as
|
|
3465
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
3466
|
+
import { homedir as homedir5 } from "node:os";
|
|
3467
|
+
import { join as join6 } from "node:path";
|
|
3387
3468
|
|
|
3388
3469
|
// vendor/heyspex-mcp/vault-less-endpoints.mjs
|
|
3389
3470
|
var VAULT_LESS_ENDPOINTS = Object.freeze([
|
|
@@ -3409,10 +3490,41 @@ function isVaultLessEndpoint(method, path5) {
|
|
|
3409
3490
|
});
|
|
3410
3491
|
}
|
|
3411
3492
|
|
|
3493
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
3494
|
+
import {
|
|
3495
|
+
readdirSync,
|
|
3496
|
+
readFileSync as readFileSync3,
|
|
3497
|
+
writeFileSync as writeFileSync2,
|
|
3498
|
+
mkdirSync as mkdirSync2,
|
|
3499
|
+
existsSync as existsSync2,
|
|
3500
|
+
renameSync as renameSync2,
|
|
3501
|
+
rmSync
|
|
3502
|
+
} from "node:fs";
|
|
3503
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
3504
|
+
|
|
3505
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
3506
|
+
import { isAbsolute, resolve as resolve2, sep } from "node:path";
|
|
3507
|
+
function resolveCodeRoot({
|
|
3508
|
+
explicit,
|
|
3509
|
+
packageRoot,
|
|
3510
|
+
vaultRoot,
|
|
3511
|
+
env = process.env
|
|
3512
|
+
} = {}) {
|
|
3513
|
+
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
3514
|
+
if (fromExplicit) return fromExplicit;
|
|
3515
|
+
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
3516
|
+
if (fromEnv) return fromEnv;
|
|
3517
|
+
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
3518
|
+
if (fromPackage) return fromPackage;
|
|
3519
|
+
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
3520
|
+
return fromVault;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3412
3523
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
3413
|
-
|
|
3414
|
-
import {
|
|
3415
|
-
import {
|
|
3524
|
+
init_heyspex_home();
|
|
3525
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
3526
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
3527
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
3416
3528
|
|
|
3417
3529
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
3418
3530
|
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
@@ -3540,10 +3652,11 @@ function withCloudScope(deps, accountId2, vaultId) {
|
|
|
3540
3652
|
}
|
|
3541
3653
|
|
|
3542
3654
|
// scripts/lib/console-url-resolver.mjs
|
|
3655
|
+
init_heyspex_home();
|
|
3543
3656
|
init_dev_mode();
|
|
3544
|
-
import { readFileSync as
|
|
3545
|
-
import { homedir as
|
|
3546
|
-
import { join as
|
|
3657
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
3658
|
+
import { homedir as homedir8 } from "node:os";
|
|
3659
|
+
import { join as join9 } from "node:path";
|
|
3547
3660
|
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
3548
3661
|
function trimTrailingSlash(url) {
|
|
3549
3662
|
return url.replace(/\/+$/, "");
|
|
@@ -3569,9 +3682,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
3569
3682
|
} catch {
|
|
3570
3683
|
}
|
|
3571
3684
|
}
|
|
3572
|
-
function readCredentialsConsoleUrl({ homeDir =
|
|
3685
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile2 } = {}) {
|
|
3573
3686
|
try {
|
|
3574
|
-
const raw = readFile2(
|
|
3687
|
+
const raw = readFile2(join9(homeDir, ".heyspex", "credentials.yaml"));
|
|
3575
3688
|
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
3576
3689
|
const value = match?.[1]?.trim();
|
|
3577
3690
|
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
@@ -3580,12 +3693,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile: readFile2 =
|
|
|
3580
3693
|
}
|
|
3581
3694
|
}
|
|
3582
3695
|
function defaultReadFile2(p) {
|
|
3583
|
-
return
|
|
3696
|
+
return readFileSync8(p, "utf8");
|
|
3584
3697
|
}
|
|
3585
3698
|
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
|
|
3586
3699
|
if (!repoRoot) return null;
|
|
3587
3700
|
try {
|
|
3588
|
-
const discovery = String(readDiscovery(
|
|
3701
|
+
const discovery = String(readDiscovery(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
3589
3702
|
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
3590
3703
|
} catch {
|
|
3591
3704
|
}
|
|
@@ -3613,7 +3726,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
3613
3726
|
function resolveConsoleUrl({
|
|
3614
3727
|
repoRoot,
|
|
3615
3728
|
env = process.env,
|
|
3616
|
-
homeDir =
|
|
3729
|
+
homeDir = heyspexHome(),
|
|
3617
3730
|
strict = false,
|
|
3618
3731
|
devMode,
|
|
3619
3732
|
log = (line) => process.stderr.write(line),
|
|
@@ -3677,37 +3790,19 @@ var HELP = [
|
|
|
3677
3790
|
].join("\n");
|
|
3678
3791
|
|
|
3679
3792
|
// spex-console/src/lib/cli/install-identity.ts
|
|
3793
|
+
init_heyspex_home2();
|
|
3794
|
+
init_registry_file();
|
|
3680
3795
|
import { spawnSync } from "node:child_process";
|
|
3681
|
-
import { existsSync as
|
|
3682
|
-
import {
|
|
3683
|
-
import { join as join9 } from "node:path";
|
|
3796
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
3797
|
+
import { join as join10 } from "node:path";
|
|
3684
3798
|
import { createInterface } from "node:readline/promises";
|
|
3685
|
-
|
|
3686
|
-
// scripts/lib/shared-path-helpers.mjs
|
|
3687
|
-
import { isAbsolute, resolve as resolve4, sep } from "node:path";
|
|
3688
|
-
function resolveCodeRoot({
|
|
3689
|
-
explicit,
|
|
3690
|
-
packageRoot,
|
|
3691
|
-
vaultRoot,
|
|
3692
|
-
env = process.env
|
|
3693
|
-
} = {}) {
|
|
3694
|
-
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
3695
|
-
if (fromExplicit) return fromExplicit;
|
|
3696
|
-
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
3697
|
-
if (fromEnv) return fromEnv;
|
|
3698
|
-
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
3699
|
-
if (fromPackage) return fromPackage;
|
|
3700
|
-
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
3701
|
-
return fromVault;
|
|
3702
|
-
}
|
|
3703
|
-
|
|
3704
|
-
// spex-console/src/lib/cli/install-identity.ts
|
|
3705
3799
|
init_credentials();
|
|
3706
3800
|
init_session_owner();
|
|
3707
3801
|
|
|
3708
3802
|
// vendor/heyspex-mcp/whoami.mjs
|
|
3803
|
+
init_heyspex_home();
|
|
3709
3804
|
init_credentials();
|
|
3710
|
-
import { homedir as
|
|
3805
|
+
import { homedir as homedir9 } from "node:os";
|
|
3711
3806
|
var SESSION_PATH = "/api/mcp/session";
|
|
3712
3807
|
async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
|
|
3713
3808
|
if (!token) return { kind: "no_credential" };
|
|
@@ -3809,6 +3904,81 @@ var HELP2 = [
|
|
|
3809
3904
|
"Exit codes: 0 ok \xB7 1 usage \xB7 11 bad-arg \xB7 13 write-failed"
|
|
3810
3905
|
].join("\n");
|
|
3811
3906
|
|
|
3907
|
+
// spex-console/src/lib/cli/satellite.ts
|
|
3908
|
+
init_registry_file();
|
|
3909
|
+
import { resolve as resolveNodePath } from "node:path";
|
|
3910
|
+
import process2 from "node:process";
|
|
3911
|
+
var SATELLITE_VAULT_ENV = "HEYSPEX_VAULT_ROOT";
|
|
3912
|
+
var SATELLITE_MANIFEST_RELS = [
|
|
3913
|
+
[".mcp.json"],
|
|
3914
|
+
[".cursor", "mcp.json"]
|
|
3915
|
+
];
|
|
3916
|
+
function normalizePathForCompare(p) {
|
|
3917
|
+
return p.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
3918
|
+
}
|
|
3919
|
+
function samePath(a, b) {
|
|
3920
|
+
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
3921
|
+
}
|
|
3922
|
+
function isAtOrUnder(child, parent) {
|
|
3923
|
+
const c = normalizePathForCompare(child);
|
|
3924
|
+
const p = normalizePathForCompare(parent);
|
|
3925
|
+
return c === p || c.startsWith(`${p}/`);
|
|
3926
|
+
}
|
|
3927
|
+
function isAbsolutePathForCompare(p) {
|
|
3928
|
+
const s = p.trim();
|
|
3929
|
+
return /^[\\/]/.test(s) || /^[a-zA-Z]:[\\/]/.test(s);
|
|
3930
|
+
}
|
|
3931
|
+
function expandPinPlaceholders(value, repoRoot) {
|
|
3932
|
+
return value.replace(/\$\{(?:CLAUDE_PROJECT_DIR|workspaceFolder)(?::-[^}]*)?\}/g, repoRoot);
|
|
3933
|
+
}
|
|
3934
|
+
function lexicalJoin(base, rel) {
|
|
3935
|
+
const parts = `${base}/${rel}`.replace(/\\/g, "/").split("/");
|
|
3936
|
+
const out = [];
|
|
3937
|
+
for (const seg of parts) {
|
|
3938
|
+
if (seg === "" && out.length > 0) continue;
|
|
3939
|
+
if (seg === ".") continue;
|
|
3940
|
+
if (seg === ".." && out.length > 1) {
|
|
3941
|
+
out.pop();
|
|
3942
|
+
continue;
|
|
3943
|
+
}
|
|
3944
|
+
out.push(seg);
|
|
3945
|
+
}
|
|
3946
|
+
return out.join("/");
|
|
3947
|
+
}
|
|
3948
|
+
function parseMarkerVaultPath(content) {
|
|
3949
|
+
const m = content.match(/^vault_path:[ \t]*(.+?)[ \t]*$/m);
|
|
3950
|
+
if (!m) return null;
|
|
3951
|
+
const v = m[1].trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
3952
|
+
if (!v || v.includes("${") || /^[\\/]/.test(v) || /^[a-zA-Z]:[\\/]/.test(v)) return null;
|
|
3953
|
+
return v;
|
|
3954
|
+
}
|
|
3955
|
+
function readPinnedVaultRoot(repoRoot, deps) {
|
|
3956
|
+
const pins = [];
|
|
3957
|
+
for (const rel of SATELLITE_MANIFEST_RELS) {
|
|
3958
|
+
const path5 = deps.joinPath(repoRoot, ...rel);
|
|
3959
|
+
if (!deps.fileExists(path5)) continue;
|
|
3960
|
+
const raw = deps.readFile(path5);
|
|
3961
|
+
if (!raw) continue;
|
|
3962
|
+
let value;
|
|
3963
|
+
try {
|
|
3964
|
+
const parsed = JSON.parse(raw);
|
|
3965
|
+
value = parsed?.mcpServers?.heyspex?.env?.[SATELLITE_VAULT_ENV];
|
|
3966
|
+
} catch {
|
|
3967
|
+
continue;
|
|
3968
|
+
}
|
|
3969
|
+
if (typeof value !== "string") continue;
|
|
3970
|
+
const trimmed = value.trim();
|
|
3971
|
+
if (!trimmed) continue;
|
|
3972
|
+
const expanded = expandPinPlaceholders(trimmed, repoRoot);
|
|
3973
|
+
if (expanded.includes("${")) continue;
|
|
3974
|
+
if (/^[a-zA-Z]:(?![\\/])/.test(expanded)) continue;
|
|
3975
|
+
const abs = isAbsolutePathForCompare(expanded) ? expanded.includes("..") ? lexicalJoin(expanded, ".") : expanded : lexicalJoin(repoRoot, expanded);
|
|
3976
|
+
if (expanded !== trimmed && isAtOrUnder(abs, repoRoot)) continue;
|
|
3977
|
+
pins.push({ source: rel.join("/"), value: abs });
|
|
3978
|
+
}
|
|
3979
|
+
return pins;
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3812
3982
|
// spex-console/src/lib/cli/install-identity.ts
|
|
3813
3983
|
var SWITCH_HINT = "Sign in as another account with `hs login --switch-account` (or create one with `hs login --create-account`).";
|
|
3814
3984
|
var ATTACH_WORDING = {
|
|
@@ -4044,7 +4214,7 @@ async function gateWriteVerb(opts) {
|
|
|
4044
4214
|
if (!opts.writeVerbs.has(opts.sub)) return null;
|
|
4045
4215
|
return runIdentityGate(opts.io, opts.flags, `${opts.namespace} ${opts.sub}`, WRITE_WORDING);
|
|
4046
4216
|
}
|
|
4047
|
-
function readConfiguredAccountId(env = process.env, homeDir =
|
|
4217
|
+
function readConfiguredAccountId(env = process.env, homeDir = heyspexHome2()) {
|
|
4048
4218
|
return resolveSessionOwnerUncached({ env, homeDir });
|
|
4049
4219
|
}
|
|
4050
4220
|
function isInteractiveTerminal() {
|
|
@@ -4052,7 +4222,7 @@ function isInteractiveTerminal() {
|
|
|
4052
4222
|
}
|
|
4053
4223
|
function defaultIdentityGateIo(opts) {
|
|
4054
4224
|
const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
|
|
4055
|
-
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) =>
|
|
4225
|
+
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join10(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync5(p));
|
|
4056
4226
|
return {
|
|
4057
4227
|
consoleUrl,
|
|
4058
4228
|
interactive: opts.interactive ?? isInteractiveTerminal(),
|
|
@@ -4276,12 +4446,17 @@ async function listWorkspacesNeon(accountId2, deps = {}) {
|
|
|
4276
4446
|
id: workspaces.id,
|
|
4277
4447
|
displayName: workspaces.displayName,
|
|
4278
4448
|
githubOrg: workspaces.githubOrg,
|
|
4279
|
-
createdAt: workspaces.createdAt
|
|
4449
|
+
createdAt: workspaces.createdAt,
|
|
4450
|
+
archived: workspaces.archived
|
|
4280
4451
|
}).from(workspaces).where(eq(workspaces.accountId, accountId2));
|
|
4281
4452
|
return rows.map((r) => ({
|
|
4282
4453
|
id: r.id,
|
|
4283
4454
|
display_name: r.displayName,
|
|
4284
4455
|
created_at: (r.createdAt instanceof Date ? r.createdAt : /* @__PURE__ */ new Date()).toISOString(),
|
|
4456
|
+
// The flag `setWorkspaceArchivedNeon` writes; `list.ts` filters on it.
|
|
4457
|
+
// On a cloud host this row IS the roster (no registry.json to carry the
|
|
4458
|
+
// flag), so leaving it out made an archived workspace list forever.
|
|
4459
|
+
archived: r.archived,
|
|
4285
4460
|
...r.githubOrg ? { github_org: r.githubOrg } : {}
|
|
4286
4461
|
}));
|
|
4287
4462
|
} catch {
|
|
@@ -4299,7 +4474,8 @@ async function listVaultsNeon(accountId2, deps = {}) {
|
|
|
4299
4474
|
repoRootAbs: vaults.repoRootAbs,
|
|
4300
4475
|
githubRemote: vaults.githubRemote,
|
|
4301
4476
|
schemaVersion: vaults.schemaVersion,
|
|
4302
|
-
createdAt: vaults.createdAt
|
|
4477
|
+
createdAt: vaults.createdAt,
|
|
4478
|
+
archived: vaults.archived
|
|
4303
4479
|
}).from(vaults).where(eq(vaults.accountId, accountId2));
|
|
4304
4480
|
const grantsDb = deps.db ?? getPostgresAdminDb();
|
|
4305
4481
|
const grantedRows = await grantsDb.select({
|
|
@@ -4309,7 +4485,10 @@ async function listVaultsNeon(accountId2, deps = {}) {
|
|
|
4309
4485
|
repoRootAbs: vaults.repoRootAbs,
|
|
4310
4486
|
githubRemote: vaults.githubRemote,
|
|
4311
4487
|
schemaVersion: vaults.schemaVersion,
|
|
4312
|
-
createdAt: vaults.createdAt
|
|
4488
|
+
createdAt: vaults.createdAt,
|
|
4489
|
+
archived: vaults.archived,
|
|
4490
|
+
// [[HS-RRCG-8QDO-DIAG-a-shared-vault-is-labelled-read-only-in]] -- the grant's access level must reach the client.
|
|
4491
|
+
access: vaultGrants.access
|
|
4313
4492
|
}).from(vaultGrants).innerJoin(vaults, eq(vaults.id, vaultGrants.vaultId)).where(and(eq(vaultGrants.accountId, accountId2), ne(vaults.accountId, accountId2)));
|
|
4314
4493
|
const toRecord = (r, granted) => ({
|
|
4315
4494
|
id: r.id,
|
|
@@ -4321,8 +4500,16 @@ async function listVaultsNeon(accountId2, deps = {}) {
|
|
|
4321
4500
|
repo_root_abs: r.repoRootAbs ?? "",
|
|
4322
4501
|
schema_version: r.schemaVersion,
|
|
4323
4502
|
registered_at: (r.createdAt instanceof Date ? r.createdAt : /* @__PURE__ */ new Date()).toISOString(),
|
|
4503
|
+
// The flag `setVaultArchivedNeon` writes and `list.ts` filters on. On a
|
|
4504
|
+
// cloud host (VERCEL=1) this row IS the roster -- there is no
|
|
4505
|
+
// registry.json to carry the flag -- so omitting it here is why
|
|
4506
|
+
// `PATCH /api/vaults/:id {archived:true}` answered 200 while
|
|
4507
|
+
// `GET /api/vaults` kept listing the vault with no `archived` field.
|
|
4508
|
+
archived: r.archived,
|
|
4324
4509
|
...r.githubRemote ? { github_remote: r.githubRemote } : {},
|
|
4325
|
-
...granted ? { granted: true } : {}
|
|
4510
|
+
...granted ? { granted: true } : {},
|
|
4511
|
+
// [[HS-RRCG-8QDO-DIAG-a-shared-vault-is-labelled-read-only-in]]: granted rows only; unknown = read.
|
|
4512
|
+
...granted ? { access: r.access === "write" ? "write" : "read" } : {}
|
|
4326
4513
|
});
|
|
4327
4514
|
return [
|
|
4328
4515
|
...ownedRows.map((r) => toRecord(r, false)),
|
|
@@ -4332,6 +4519,14 @@ async function listVaultsNeon(accountId2, deps = {}) {
|
|
|
4332
4519
|
return null;
|
|
4333
4520
|
}
|
|
4334
4521
|
}
|
|
4522
|
+
async function classifyZeroRowVaultWrite(db, id, accountId2) {
|
|
4523
|
+
try {
|
|
4524
|
+
const rows = await db.select({ accountId: vaults.accountId }).from(vaults).where(eq(vaults.id, id)).limit(1);
|
|
4525
|
+
return rows[0] && rows[0].accountId !== accountId2 ? "owned_by_other_account" : "not_found";
|
|
4526
|
+
} catch {
|
|
4527
|
+
return "not_found";
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4335
4530
|
async function renameVaultNeon(id, displayName, accountId2, deps = {}) {
|
|
4336
4531
|
if (shouldUseCloudHttp(deps)) {
|
|
4337
4532
|
return mutateHttp(
|
|
@@ -4346,7 +4541,7 @@ async function renameVaultNeon(id, displayName, accountId2, deps = {}) {
|
|
|
4346
4541
|
try {
|
|
4347
4542
|
const db = deps.db ?? getPostgresDb();
|
|
4348
4543
|
const rows = await db.update(vaults).set({ displayName, updatedAt: /* @__PURE__ */ new Date() }).where(and(eq(vaults.id, id), eq(vaults.accountId, accountId2))).returning({ id: vaults.id });
|
|
4349
|
-
return rows.length > 0 ? "updated" :
|
|
4544
|
+
return rows.length > 0 ? "updated" : classifyZeroRowVaultWrite(db, id, accountId2);
|
|
4350
4545
|
} catch {
|
|
4351
4546
|
return "unreachable";
|
|
4352
4547
|
}
|
|
@@ -4361,6 +4556,9 @@ async function moveVaultNeon(id, workspaceId, accountId2, deps = {}) {
|
|
|
4361
4556
|
if (res.status === 409 && code === WORKSPACE_OWNED_BY_OTHER_ACCOUNT) {
|
|
4362
4557
|
return "workspace_owned_by_other_account";
|
|
4363
4558
|
}
|
|
4559
|
+
if (res.status === 403 && code === VAULT_OWNED_BY_OTHER_ACCOUNT) {
|
|
4560
|
+
return "owned_by_other_account";
|
|
4561
|
+
}
|
|
4364
4562
|
if (res.status === 404)
|
|
4365
4563
|
return code === "workspace_not_found" ? "workspace_not_found" : "not_found";
|
|
4366
4564
|
return cloudWriteFailure(deps.http, res);
|
|
@@ -4379,7 +4577,7 @@ async function moveVaultNeon(id, workspaceId, accountId2, deps = {}) {
|
|
|
4379
4577
|
).returning({ id: vaults.id });
|
|
4380
4578
|
if (rows.length > 0) return "updated";
|
|
4381
4579
|
const mine = await db.select({ id: vaults.id }).from(vaults).where(and(eq(vaults.id, id), eq(vaults.accountId, accountId2))).limit(1);
|
|
4382
|
-
if (mine.length === 0) return
|
|
4580
|
+
if (mine.length === 0) return classifyZeroRowVaultWrite(db, id, accountId2);
|
|
4383
4581
|
const ws = await db.select({ accountId: workspaces.accountId }).from(workspaces).where(eq(workspaces.id, workspaceId)).limit(1);
|
|
4384
4582
|
return ws[0] ? "workspace_owned_by_other_account" : "workspace_not_found";
|
|
4385
4583
|
} catch {
|
|
@@ -4394,7 +4592,7 @@ async function setVaultArchivedNeon(id, archived, accountId2, deps = {}) {
|
|
|
4394
4592
|
try {
|
|
4395
4593
|
const db = deps.db ?? getPostgresDb();
|
|
4396
4594
|
const rows = await db.update(vaults).set({ archived, updatedAt: /* @__PURE__ */ new Date() }).where(and(eq(vaults.id, id), eq(vaults.accountId, accountId2))).returning({ id: vaults.id });
|
|
4397
|
-
return rows.length > 0 ? "updated" :
|
|
4595
|
+
return rows.length > 0 ? "updated" : classifyZeroRowVaultWrite(db, id, accountId2);
|
|
4398
4596
|
} catch {
|
|
4399
4597
|
return "unreachable";
|
|
4400
4598
|
}
|
|
@@ -4407,7 +4605,7 @@ async function deleteVaultNeon(id, accountId2, deps = {}) {
|
|
|
4407
4605
|
try {
|
|
4408
4606
|
const db = deps.db ?? getPostgresDb();
|
|
4409
4607
|
const rows = await db.delete(vaults).where(and(eq(vaults.id, id), eq(vaults.accountId, accountId2))).returning({ id: vaults.id });
|
|
4410
|
-
return rows.length > 0 ? "deleted" :
|
|
4608
|
+
return rows.length > 0 ? "deleted" : classifyZeroRowVaultWrite(db, id, accountId2);
|
|
4411
4609
|
} catch {
|
|
4412
4610
|
return "unreachable";
|
|
4413
4611
|
}
|
|
@@ -4468,7 +4666,7 @@ async function repointActiveVaultOnRemoval(accountId2, removedVaultId, removedVa
|
|
|
4468
4666
|
return { repointed: false, newActiveId: activeId };
|
|
4469
4667
|
}
|
|
4470
4668
|
const registry = loadVaultRegistry(deps.registryOpts);
|
|
4471
|
-
const candidate = registry.vaults.find(
|
|
4669
|
+
const candidate = removedVaultWorkspaceId === null ? void 0 : registry.vaults.find(
|
|
4472
4670
|
(v) => v.id !== removedVaultId && v.workspace_id === removedVaultWorkspaceId && !v.archived
|
|
4473
4671
|
);
|
|
4474
4672
|
if (!candidate) {
|
|
@@ -4481,16 +4679,40 @@ async function repointActiveVaultOnRemoval(accountId2, removedVaultId, removedVa
|
|
|
4481
4679
|
|
|
4482
4680
|
// spex-console/src/lib/vault-registry/archive.ts
|
|
4483
4681
|
init_registry_file();
|
|
4682
|
+
|
|
4683
|
+
// spex-console/src/lib/vault-registry/side-effects.ts
|
|
4684
|
+
function isLocalRegistryHost() {
|
|
4685
|
+
const override = process.env.HEYSPEX_VAULT_WRITE_MODE?.trim().toLowerCase();
|
|
4686
|
+
if (override === "cloud") return false;
|
|
4687
|
+
if (override === "local") return true;
|
|
4688
|
+
return process.env.VERCEL !== "1";
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
// spex-console/src/lib/vault-registry/archive.ts
|
|
4484
4692
|
function cloudWriteFailed(failure) {
|
|
4485
4693
|
return { ok: false, code: "cloud_write_failed", message: failure.message };
|
|
4486
4694
|
}
|
|
4695
|
+
function neonUnwritten(kind, id) {
|
|
4696
|
+
return {
|
|
4697
|
+
ok: false,
|
|
4698
|
+
code: "neon_unwritten",
|
|
4699
|
+
message: `${kind} "${id}" was not written to the cloud registry (database unreachable); nothing was changed. Retry once the console can reach its database.`
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4487
4702
|
function vaultNotFound(id) {
|
|
4488
4703
|
return { ok: false, code: "vault_not_found", message: `vault "${id}" not found` };
|
|
4489
4704
|
}
|
|
4705
|
+
function vaultNotOwned(id) {
|
|
4706
|
+
return {
|
|
4707
|
+
ok: false,
|
|
4708
|
+
code: VAULT_OWNED_BY_OTHER_ACCOUNT,
|
|
4709
|
+
message: vaultNotOwnedByCallerMessage(id)
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4490
4712
|
async function setVaultArchivedState(input, archived, deps = {}) {
|
|
4491
|
-
const
|
|
4492
|
-
const existing =
|
|
4493
|
-
if (!existing) return vaultNotFound(input.id);
|
|
4713
|
+
const local = isLocalRegistryHost();
|
|
4714
|
+
const existing = local ? loadVaultRegistry(deps.registryOpts).vaults.find((v) => v.id === input.id) : void 0;
|
|
4715
|
+
if (local && !existing) return vaultNotFound(input.id);
|
|
4494
4716
|
const neonResult = await setVaultArchivedNeon(
|
|
4495
4717
|
input.id,
|
|
4496
4718
|
archived,
|
|
@@ -4498,21 +4720,23 @@ async function setVaultArchivedState(input, archived, deps = {}) {
|
|
|
4498
4720
|
deps.neon ?? {}
|
|
4499
4721
|
);
|
|
4500
4722
|
if (neonResult === "not_found") return vaultNotFound(input.id);
|
|
4723
|
+
if (neonResult === "owned_by_other_account") return vaultNotOwned(input.id);
|
|
4501
4724
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed(neonResult);
|
|
4725
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten("vault", input.id);
|
|
4502
4726
|
if (archived) {
|
|
4503
|
-
await repointActiveVaultOnRemoval(input.accountId, input.id, existing
|
|
4727
|
+
await repointActiveVaultOnRemoval(input.accountId, input.id, existing?.workspace_id ?? null, {
|
|
4504
4728
|
registryOpts: deps.registryOpts,
|
|
4505
4729
|
accountPointer: { ...deps.neon, registryOpts: deps.registryOpts }
|
|
4506
4730
|
});
|
|
4507
4731
|
}
|
|
4508
|
-
registerVault({ ...existing, archived }, deps.registryOpts);
|
|
4732
|
+
if (existing) registerVault({ ...existing, archived }, deps.registryOpts);
|
|
4509
4733
|
return {
|
|
4510
4734
|
ok: true,
|
|
4511
4735
|
vault: {
|
|
4512
4736
|
id: input.id,
|
|
4513
4737
|
archived,
|
|
4514
4738
|
neon_written: neonResult === "updated",
|
|
4515
|
-
registry_written:
|
|
4739
|
+
registry_written: existing !== void 0
|
|
4516
4740
|
}
|
|
4517
4741
|
};
|
|
4518
4742
|
}
|
|
@@ -4551,19 +4775,9 @@ function buildCloseNotDestroyConfirm(kind, verb, displayName) {
|
|
|
4551
4775
|
}
|
|
4552
4776
|
|
|
4553
4777
|
// spex-console/src/lib/vault-registry/list.ts
|
|
4554
|
-
import { existsSync as
|
|
4778
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4555
4779
|
init_account_pointer();
|
|
4556
4780
|
init_registry_file();
|
|
4557
|
-
|
|
4558
|
-
// spex-console/src/lib/vault-registry/side-effects.ts
|
|
4559
|
-
function isLocalRegistryHost() {
|
|
4560
|
-
const override = process.env.HEYSPEX_VAULT_WRITE_MODE?.trim().toLowerCase();
|
|
4561
|
-
if (override === "cloud") return false;
|
|
4562
|
-
if (override === "local") return true;
|
|
4563
|
-
return process.env.VERCEL !== "1";
|
|
4564
|
-
}
|
|
4565
|
-
|
|
4566
|
-
// spex-console/src/lib/vault-registry/list.ts
|
|
4567
4781
|
async function defaultNeonReadTierEnabled() {
|
|
4568
4782
|
const { isNeonReadTierEnabled: isNeonReadTierEnabled2 } = await Promise.resolve().then(() => (init_spec_read_service(), spec_read_service_exports));
|
|
4569
4783
|
return isNeonReadTierEnabled2();
|
|
@@ -4594,7 +4808,7 @@ async function listVaultsForWorkspace(accountId2, workspaceId, deps = {}) {
|
|
|
4594
4808
|
const activeId = await getActiveVault(accountId2, sharedPointerDeps) ?? await adoptSole(accountId2, sharedPointerDeps);
|
|
4595
4809
|
const rows = deps.includeArchived ? allRows : allRows.filter((v) => !v.archived);
|
|
4596
4810
|
const scoped = workspaceId ? rows.filter((v) => v.workspace_id === workspaceId) : rows;
|
|
4597
|
-
const pathExists = deps.pathExists ??
|
|
4811
|
+
const pathExists = deps.pathExists ?? existsSync6;
|
|
4598
4812
|
const anyPathUnusable = scoped.some((v) => !v.repo_root_abs || !pathExists(v.repo_root_abs));
|
|
4599
4813
|
const neonBackedServeable = anyPathUnusable && neonRows !== null ? await (deps.neonReadTierEnabled ?? defaultNeonReadTierEnabled)() : false;
|
|
4600
4814
|
return {
|
|
@@ -4642,6 +4856,25 @@ function isValidId2(id) {
|
|
|
4642
4856
|
function notFound(id) {
|
|
4643
4857
|
return { ok: false, code: "vault_not_found", message: `vault "${id}" not found` };
|
|
4644
4858
|
}
|
|
4859
|
+
function notOwned(id) {
|
|
4860
|
+
return {
|
|
4861
|
+
ok: false,
|
|
4862
|
+
code: VAULT_OWNED_BY_OTHER_ACCOUNT,
|
|
4863
|
+
message: vaultNotOwnedByCallerMessage(id)
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
function neonUnwritten2(id) {
|
|
4867
|
+
return {
|
|
4868
|
+
ok: false,
|
|
4869
|
+
code: "neon_unwritten",
|
|
4870
|
+
message: `vault "${id}" was not written to the cloud registry (database unreachable); nothing was changed. Retry once the console can reach its database.`
|
|
4871
|
+
};
|
|
4872
|
+
}
|
|
4873
|
+
function localRow(id, registryOpts) {
|
|
4874
|
+
const local = isLocalRegistryHost();
|
|
4875
|
+
const existing = local ? loadVaultRegistry(registryOpts).vaults.find((v) => v.id === id) : void 0;
|
|
4876
|
+
return { local, existing };
|
|
4877
|
+
}
|
|
4645
4878
|
function cloudWriteFailed2(failure) {
|
|
4646
4879
|
return { ok: false, code: "cloud_write_failed", message: failure.message };
|
|
4647
4880
|
}
|
|
@@ -4654,20 +4887,21 @@ async function renameVault(input, deps = {}) {
|
|
|
4654
4887
|
message: `display name must be 1-${MAX_DISPLAY_NAME_LEN} characters (trimmed)`
|
|
4655
4888
|
};
|
|
4656
4889
|
}
|
|
4657
|
-
const
|
|
4658
|
-
|
|
4659
|
-
if (!existing) return notFound(input.id);
|
|
4890
|
+
const { local, existing } = localRow(input.id, deps.registryOpts);
|
|
4891
|
+
if (local && !existing) return notFound(input.id);
|
|
4660
4892
|
const neonResult = await renameVaultNeon(input.id, trimmedName, input.accountId, deps.neon ?? {});
|
|
4661
4893
|
if (neonResult === "not_found") return notFound(input.id);
|
|
4894
|
+
if (neonResult === "owned_by_other_account") return notOwned(input.id);
|
|
4662
4895
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed2(neonResult);
|
|
4663
|
-
|
|
4896
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten2(input.id);
|
|
4897
|
+
if (existing) registerVault({ ...existing, display_name: trimmedName }, deps.registryOpts);
|
|
4664
4898
|
return {
|
|
4665
4899
|
ok: true,
|
|
4666
4900
|
vault: {
|
|
4667
4901
|
id: input.id,
|
|
4668
4902
|
display_name: trimmedName,
|
|
4669
4903
|
neon_written: neonResult === "updated",
|
|
4670
|
-
registry_written:
|
|
4904
|
+
registry_written: existing !== void 0
|
|
4671
4905
|
}
|
|
4672
4906
|
};
|
|
4673
4907
|
}
|
|
@@ -4679,9 +4913,8 @@ async function moveVault(input, deps = {}) {
|
|
|
4679
4913
|
message: `invalid workspace id "${String(input.workspaceId)}" -- must be non-empty with no path separators`
|
|
4680
4914
|
};
|
|
4681
4915
|
}
|
|
4682
|
-
const
|
|
4683
|
-
|
|
4684
|
-
if (!existing) return notFound(input.id);
|
|
4916
|
+
const { local, existing } = localRow(input.id, deps.registryOpts);
|
|
4917
|
+
if (local && !existing) return notFound(input.id);
|
|
4685
4918
|
const neonResult = await moveVaultNeon(
|
|
4686
4919
|
input.id,
|
|
4687
4920
|
input.workspaceId,
|
|
@@ -4689,7 +4922,9 @@ async function moveVault(input, deps = {}) {
|
|
|
4689
4922
|
deps.neon ?? {}
|
|
4690
4923
|
);
|
|
4691
4924
|
if (neonResult === "not_found") return notFound(input.id);
|
|
4925
|
+
if (neonResult === "owned_by_other_account") return notOwned(input.id);
|
|
4692
4926
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed2(neonResult);
|
|
4927
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten2(input.id);
|
|
4693
4928
|
if (neonResult === "workspace_owned_by_other_account") {
|
|
4694
4929
|
return {
|
|
4695
4930
|
ok: false,
|
|
@@ -4704,92 +4939,46 @@ async function moveVault(input, deps = {}) {
|
|
|
4704
4939
|
message: `workspace "${input.workspaceId}" not found -- create it first (\`hs workspace create\`)`
|
|
4705
4940
|
};
|
|
4706
4941
|
}
|
|
4707
|
-
|
|
4942
|
+
if (existing) {
|
|
4943
|
+
registerVault({ ...existing, workspace_id: input.workspaceId }, deps.registryOpts);
|
|
4944
|
+
}
|
|
4708
4945
|
return {
|
|
4709
4946
|
ok: true,
|
|
4710
4947
|
vault: {
|
|
4711
4948
|
id: input.id,
|
|
4712
4949
|
workspace_id: input.workspaceId,
|
|
4713
4950
|
neon_written: neonResult === "updated",
|
|
4714
|
-
registry_written:
|
|
4951
|
+
registry_written: existing !== void 0
|
|
4715
4952
|
}
|
|
4716
4953
|
};
|
|
4717
4954
|
}
|
|
4718
4955
|
async function unregisterVault(input, deps = {}) {
|
|
4719
|
-
const
|
|
4720
|
-
|
|
4721
|
-
if (!existing) return notFound(input.id);
|
|
4956
|
+
const { local, existing } = localRow(input.id, deps.registryOpts);
|
|
4957
|
+
if (local && !existing) return notFound(input.id);
|
|
4722
4958
|
const neonResult = await deleteVaultNeon(input.id, input.accountId, deps.neon ?? {});
|
|
4723
4959
|
if (neonResult === "not_found") return notFound(input.id);
|
|
4960
|
+
if (neonResult === "owned_by_other_account") return notOwned(input.id);
|
|
4724
4961
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed2(neonResult);
|
|
4725
|
-
|
|
4962
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten2(input.id);
|
|
4963
|
+
await repointActiveVaultOnRemoval(input.accountId, input.id, existing?.workspace_id ?? null, {
|
|
4726
4964
|
registryOpts: deps.registryOpts,
|
|
4727
4965
|
accountPointer: { ...deps.neon, registryOpts: deps.registryOpts }
|
|
4728
4966
|
});
|
|
4729
|
-
removeVault(input.id, deps.registryOpts);
|
|
4967
|
+
if (existing) removeVault(input.id, deps.registryOpts);
|
|
4730
4968
|
return {
|
|
4731
4969
|
ok: true,
|
|
4732
4970
|
vault: {
|
|
4733
4971
|
id: input.id,
|
|
4734
4972
|
neon_written: neonResult === "deleted",
|
|
4735
|
-
registry_written:
|
|
4973
|
+
registry_written: existing !== void 0
|
|
4736
4974
|
}
|
|
4737
4975
|
};
|
|
4738
4976
|
}
|
|
4739
4977
|
|
|
4740
4978
|
// spex-console/src/lib/vault-resolve/index.ts
|
|
4741
|
-
import { existsSync as
|
|
4979
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "node:fs";
|
|
4742
4980
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4743
4981
|
import path3 from "node:path";
|
|
4744
|
-
|
|
4745
|
-
// spex-console/src/lib/cli/satellite.ts
|
|
4746
|
-
init_registry_file();
|
|
4747
|
-
import { resolve as resolveNodePath } from "node:path";
|
|
4748
|
-
import process2 from "node:process";
|
|
4749
|
-
var SATELLITE_VAULT_ENV = "HEYSPEX_VAULT_ROOT";
|
|
4750
|
-
var SATELLITE_MANIFEST_RELS = [
|
|
4751
|
-
[".mcp.json"],
|
|
4752
|
-
[".cursor", "mcp.json"]
|
|
4753
|
-
];
|
|
4754
|
-
function normalizePathForCompare(p) {
|
|
4755
|
-
return p.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
4756
|
-
}
|
|
4757
|
-
function samePath(a, b) {
|
|
4758
|
-
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
4759
|
-
}
|
|
4760
|
-
function isAtOrUnder(child, parent) {
|
|
4761
|
-
const c = normalizePathForCompare(child);
|
|
4762
|
-
const p = normalizePathForCompare(parent);
|
|
4763
|
-
return c === p || c.startsWith(`${p}/`);
|
|
4764
|
-
}
|
|
4765
|
-
function isAbsolutePathForCompare(p) {
|
|
4766
|
-
const s = p.trim();
|
|
4767
|
-
return /^[\\/]/.test(s) || /^[a-zA-Z]:[\\/]/.test(s);
|
|
4768
|
-
}
|
|
4769
|
-
function readPinnedVaultRoot(repoRoot, deps) {
|
|
4770
|
-
const pins = [];
|
|
4771
|
-
for (const rel of SATELLITE_MANIFEST_RELS) {
|
|
4772
|
-
const path5 = deps.joinPath(repoRoot, ...rel);
|
|
4773
|
-
if (!deps.fileExists(path5)) continue;
|
|
4774
|
-
const raw = deps.readFile(path5);
|
|
4775
|
-
if (!raw) continue;
|
|
4776
|
-
let value;
|
|
4777
|
-
try {
|
|
4778
|
-
const parsed = JSON.parse(raw);
|
|
4779
|
-
value = parsed?.mcpServers?.heyspex?.env?.[SATELLITE_VAULT_ENV];
|
|
4780
|
-
} catch {
|
|
4781
|
-
continue;
|
|
4782
|
-
}
|
|
4783
|
-
if (typeof value !== "string") continue;
|
|
4784
|
-
const trimmed = value.trim();
|
|
4785
|
-
if (!trimmed || trimmed.includes("${")) continue;
|
|
4786
|
-
if (!isAbsolutePathForCompare(trimmed)) continue;
|
|
4787
|
-
pins.push({ source: rel.join("/"), value: trimmed });
|
|
4788
|
-
}
|
|
4789
|
-
return pins;
|
|
4790
|
-
}
|
|
4791
|
-
|
|
4792
|
-
// spex-console/src/lib/vault-resolve/index.ts
|
|
4793
4982
|
var UNREGISTERED_VAULT_ID2 = "unregistered";
|
|
4794
4983
|
var NoActiveVaultError = class _NoActiveVaultError extends Error {
|
|
4795
4984
|
constructor(message2 = "resolveActiveVaultRoot: no active vault found -- no explicit vaultId, no heyspex.yaml or specs/ directory at or above cwd, no HEYSPEX_VAULT_ROOT/HEYSPEX_VAULT_DIR env, and no account active_vault_id. Run `hs vault register` to register a vault.") {
|
|
@@ -4823,22 +5012,40 @@ function parseMarkerVaultId(content) {
|
|
|
4823
5012
|
}
|
|
4824
5013
|
function readMarkerVaultId(dir) {
|
|
4825
5014
|
try {
|
|
4826
|
-
return parseMarkerVaultId(
|
|
5015
|
+
return parseMarkerVaultId(readFileSync10(path3.join(dir, "heyspex.yaml"), "utf8"));
|
|
4827
5016
|
} catch {
|
|
4828
5017
|
return null;
|
|
4829
5018
|
}
|
|
4830
5019
|
}
|
|
5020
|
+
function readMarkerSatellite(dir) {
|
|
5021
|
+
let raw;
|
|
5022
|
+
try {
|
|
5023
|
+
raw = readFileSync10(path3.join(dir, "heyspex.yaml"), "utf8");
|
|
5024
|
+
} catch {
|
|
5025
|
+
return null;
|
|
5026
|
+
}
|
|
5027
|
+
const vaultPath = parseMarkerVaultPath(raw);
|
|
5028
|
+
if (!vaultPath) return null;
|
|
5029
|
+
const vaultId = parseMarkerVaultId(raw);
|
|
5030
|
+
const candidate = path3.resolve(dir, vaultPath);
|
|
5031
|
+
let vaultRootAbs = null;
|
|
5032
|
+
if (candidate.toLowerCase() !== path3.resolve(dir).toLowerCase() && isVaultRootDir(candidate)) {
|
|
5033
|
+
const theirs = readMarkerVaultId(candidate);
|
|
5034
|
+
if (!vaultId || !theirs || theirs === vaultId) vaultRootAbs = candidate;
|
|
5035
|
+
}
|
|
5036
|
+
return { vaultId, vaultRootAbs };
|
|
5037
|
+
}
|
|
4831
5038
|
function isVaultRootDir(dir) {
|
|
4832
5039
|
const norm = (p) => path3.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
4833
5040
|
if (norm(dir) === norm(tmpdir2())) return false;
|
|
4834
|
-
return
|
|
5041
|
+
return existsSync7(path3.join(dir, "heyspex.yaml")) || existsSync7(path3.join(dir, "specs"));
|
|
4835
5042
|
}
|
|
4836
5043
|
function findCwdVaultOrAttachment(startDir, stopAt) {
|
|
4837
5044
|
const deps = {
|
|
4838
|
-
fileExists: (p) =>
|
|
5045
|
+
fileExists: (p) => existsSync7(p),
|
|
4839
5046
|
readFile: (p) => {
|
|
4840
5047
|
try {
|
|
4841
|
-
return
|
|
5048
|
+
return readFileSync10(p, "utf8");
|
|
4842
5049
|
} catch {
|
|
4843
5050
|
return null;
|
|
4844
5051
|
}
|
|
@@ -4848,6 +5055,14 @@ function findCwdVaultOrAttachment(startDir, stopAt) {
|
|
|
4848
5055
|
const boundary = stopAt ? path3.resolve(stopAt) : null;
|
|
4849
5056
|
let dir = startDir;
|
|
4850
5057
|
for (let i = 0; i < 256; i += 1) {
|
|
5058
|
+
const marked = readMarkerSatellite(dir);
|
|
5059
|
+
if (marked) {
|
|
5060
|
+
return {
|
|
5061
|
+
repoRootAbs: marked.vaultRootAbs ?? dir,
|
|
5062
|
+
satellite: true,
|
|
5063
|
+
markerVaultId: marked.vaultId
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
4851
5066
|
const pinned = readPinnedVaultRoot(dir, deps).find((pin) => !isAtOrUnder(pin.value, dir));
|
|
4852
5067
|
if (pinned) return { repoRootAbs: path3.resolve(pinned.value), satellite: true };
|
|
4853
5068
|
if (isVaultRootDir(dir)) return { repoRootAbs: dir, satellite: false };
|
|
@@ -4893,6 +5108,9 @@ async function resolveActiveVaultRoot(options = {}) {
|
|
|
4893
5108
|
const cwdMatch = findCwdVaultOrAttachment(cwdBase, options.cwdWalkRoot);
|
|
4894
5109
|
if (cwdMatch?.satellite) {
|
|
4895
5110
|
onTier({ tier: "satellite-pin", outcome: "resolved", detail: cwdMatch.repoRootAbs });
|
|
5111
|
+
if (cwdMatch.markerVaultId) {
|
|
5112
|
+
return finalize(cwdMatch.markerVaultId, path3.resolve(cwdMatch.repoRootAbs), "satellite-pin");
|
|
5113
|
+
}
|
|
4896
5114
|
return finalizePathTier(cwdMatch.repoRootAbs, "satellite-pin");
|
|
4897
5115
|
}
|
|
4898
5116
|
if (cwdMatch) {
|
|
@@ -5923,7 +6141,7 @@ function statusForVaultManageError(code) {
|
|
|
5923
6141
|
if (code === "workspace_owned_by_other_account" || code === "vault_owned_by_other_account") {
|
|
5924
6142
|
return EXIT.CONFLICT;
|
|
5925
6143
|
}
|
|
5926
|
-
if (code === "cloud_write_failed") return EXIT.CLOUD_WRITE_FAILED;
|
|
6144
|
+
if (code === "cloud_write_failed" || code === "neon_unwritten") return EXIT.CLOUD_WRITE_FAILED;
|
|
5927
6145
|
return EXIT.INVALID_ARG;
|
|
5928
6146
|
}
|
|
5929
6147
|
async function manageWithReceipt(verb, vaultId, effectiveAccountId, deps, retry, write, success, expected) {
|
|
@@ -6338,7 +6556,7 @@ function runEntry(name, main2, options = {}) {
|
|
|
6338
6556
|
// spex-console/bin/hs-vault.ts
|
|
6339
6557
|
function readFile(p) {
|
|
6340
6558
|
try {
|
|
6341
|
-
return
|
|
6559
|
+
return readFileSync11(p, "utf8");
|
|
6342
6560
|
} catch {
|
|
6343
6561
|
return null;
|
|
6344
6562
|
}
|
|
@@ -6397,7 +6615,7 @@ async function main() {
|
|
|
6397
6615
|
const result = await runVault(
|
|
6398
6616
|
parsed.args,
|
|
6399
6617
|
{
|
|
6400
|
-
fileExists: (p) =>
|
|
6618
|
+
fileExists: (p) => existsSync8(p),
|
|
6401
6619
|
readFile,
|
|
6402
6620
|
resolvePath: (p) => path4.resolve(invokedCwd, p),
|
|
6403
6621
|
joinPath: (...s) => path4.join(...s),
|
|
@@ -6405,7 +6623,7 @@ async function main() {
|
|
|
6405
6623
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
6406
6624
|
cwd,
|
|
6407
6625
|
conflictScan: {
|
|
6408
|
-
fileExists: (p) =>
|
|
6626
|
+
fileExists: (p) => existsSync8(p),
|
|
6409
6627
|
readFile,
|
|
6410
6628
|
joinPath: (...s) => path4.join(...s)
|
|
6411
6629
|
},
|