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
|
@@ -5,11 +5,26 @@ var __esm = (fn, res) => function __init() {
|
|
|
5
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// scripts/lib/heyspex-home.mjs
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
function heyspexHome(env = process.env) {
|
|
11
|
+
const override = env?.HEYSPEX_HOME;
|
|
12
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
13
|
+
return override.trim();
|
|
14
|
+
}
|
|
15
|
+
return homedir();
|
|
16
|
+
}
|
|
17
|
+
var init_heyspex_home = __esm({
|
|
18
|
+
"scripts/lib/heyspex-home.mjs"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
8
23
|
// vendor/heyspex-mcp/credentials.mjs
|
|
9
24
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
|
-
import os, { homedir } from "node:os";
|
|
25
|
+
import os, { homedir as homedir2 } from "node:os";
|
|
11
26
|
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
12
|
-
function credentialsFilePath(homeDir =
|
|
27
|
+
function credentialsFilePath(homeDir = heyspexHome()) {
|
|
13
28
|
return join2(homeDir, ".heyspex", "credentials.yaml");
|
|
14
29
|
}
|
|
15
30
|
function parseMcpToken(raw) {
|
|
@@ -122,14 +137,14 @@ function readLegacyConfigAccountId(read, homeDir) {
|
|
|
122
137
|
const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
|
|
123
138
|
return value || null;
|
|
124
139
|
}
|
|
125
|
-
function readBearer({ env = process.env, homeDir =
|
|
140
|
+
function readBearer({ env = process.env, homeDir = heyspexHome(), readFile: readFile2, accountId: accountId2 } = {}) {
|
|
126
141
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
127
142
|
const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
|
|
128
143
|
const named = typeof accountId2 === "string" && accountId2.trim() ? accountId2.trim() : null;
|
|
129
144
|
const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
|
|
130
145
|
return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
|
|
131
146
|
}
|
|
132
|
-
function readCurrentAccountId({ homeDir =
|
|
147
|
+
function readCurrentAccountId({ homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
133
148
|
const read = readFile2 ?? ((p) => readFileSync(p, "utf8"));
|
|
134
149
|
const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
|
|
135
150
|
if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
|
|
@@ -147,6 +162,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
|
|
|
147
162
|
var init_credentials = __esm({
|
|
148
163
|
"vendor/heyspex-mcp/credentials.mjs"() {
|
|
149
164
|
"use strict";
|
|
165
|
+
init_heyspex_home();
|
|
150
166
|
MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
|
|
151
167
|
MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
|
|
152
168
|
CREDENTIALS_SCHEMA_VERSION = 2;
|
|
@@ -158,11 +174,11 @@ var init_credentials = __esm({
|
|
|
158
174
|
});
|
|
159
175
|
|
|
160
176
|
// scripts/lib/dev-mode.mjs
|
|
161
|
-
import { mkdirSync as
|
|
162
|
-
import { homedir as
|
|
163
|
-
import { join as
|
|
164
|
-
function devModeFilePath(homeDir =
|
|
165
|
-
return
|
|
177
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
178
|
+
import { homedir as homedir7 } from "node:os";
|
|
179
|
+
import { join as join8 } from "node:path";
|
|
180
|
+
function devModeFilePath(homeDir = heyspexHome()) {
|
|
181
|
+
return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
|
|
166
182
|
}
|
|
167
183
|
function normalizeMode(raw) {
|
|
168
184
|
const v = String(raw ?? "").trim().toLowerCase();
|
|
@@ -186,7 +202,7 @@ function deriveSemantics(mode, reliance) {
|
|
|
186
202
|
vaultWriteMode: local ? "local" : "cloud"
|
|
187
203
|
};
|
|
188
204
|
}
|
|
189
|
-
function readLocalDevModeRecord({ homeDir =
|
|
205
|
+
function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile } = {}) {
|
|
190
206
|
let raw;
|
|
191
207
|
try {
|
|
192
208
|
raw = readFile2(devModeFilePath(homeDir));
|
|
@@ -216,7 +232,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile: readFile2 = de
|
|
|
216
232
|
}
|
|
217
233
|
function resolveDevMode({
|
|
218
234
|
env = process.env,
|
|
219
|
-
homeDir =
|
|
235
|
+
homeDir = heyspexHome(),
|
|
220
236
|
readFile: readFile2 = defaultReadFile,
|
|
221
237
|
cloudSetting = void 0
|
|
222
238
|
} = {}) {
|
|
@@ -279,11 +295,12 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
|
|
|
279
295
|
var init_dev_mode = __esm({
|
|
280
296
|
"scripts/lib/dev-mode.mjs"() {
|
|
281
297
|
"use strict";
|
|
298
|
+
init_heyspex_home();
|
|
282
299
|
init_credentials();
|
|
283
300
|
CLOUD_CONSOLE_URL = "https://app.heyspex.com";
|
|
284
301
|
LOCAL_CONSOLE_URL = "http://localhost:3000";
|
|
285
302
|
DEV_MODE_FILENAME = "dev-mode.json";
|
|
286
|
-
defaultReadFile = (p) =>
|
|
303
|
+
defaultReadFile = (p) => readFileSync7(p, "utf8");
|
|
287
304
|
}
|
|
288
305
|
});
|
|
289
306
|
|
|
@@ -352,7 +369,7 @@ var init_interfaces = __esm({
|
|
|
352
369
|
});
|
|
353
370
|
|
|
354
371
|
// spex-console/src/lib/db/schema/postgres.ts
|
|
355
|
-
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;
|
|
372
|
+
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;
|
|
356
373
|
var init_postgres = __esm({
|
|
357
374
|
"spex-console/src/lib/db/schema/postgres.ts"() {
|
|
358
375
|
"use strict";
|
|
@@ -794,7 +811,13 @@ var init_postgres = __esm({
|
|
|
794
811
|
"priority_change",
|
|
795
812
|
"parent_change",
|
|
796
813
|
"claim_acquired",
|
|
797
|
-
"claim_released"
|
|
814
|
+
"claim_released",
|
|
815
|
+
// Collaborator write audit (HS-FWB6, migration 0077).
|
|
816
|
+
"spec_created",
|
|
817
|
+
"spec_edited",
|
|
818
|
+
"spec_deleted",
|
|
819
|
+
"capture_written",
|
|
820
|
+
"wip_recorded"
|
|
798
821
|
]);
|
|
799
822
|
focusPin = pgTable(
|
|
800
823
|
"focus_pin",
|
|
@@ -1987,20 +2010,52 @@ var init_postgres = __esm({
|
|
|
1987
2010
|
// `owner/name`
|
|
1988
2011
|
branch: text("branch").notNull(),
|
|
1989
2012
|
commitSha: text("commit_sha").notNull(),
|
|
1990
|
-
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`)
|
|
2013
|
+
settledAt: timestamp("settled_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2014
|
+
// The pull request that carries this settlement
|
|
2015
|
+
// ([[HS-9D25-CWPX-EPIC-branch-lifecycle-open-a-pull-request]]). NULL until
|
|
2016
|
+
// the branch has gone quiet and a PR was opened or found; a write made
|
|
2017
|
+
// after its PR merged stays NULL, which is what triggers the next PR.
|
|
2018
|
+
pullRequestNumber: integer("pull_request_number"),
|
|
2019
|
+
pullRequestUrl: text("pull_request_url")
|
|
1991
2020
|
},
|
|
1992
2021
|
(table) => [
|
|
1993
2022
|
// "latest settlement for this spec" — the read path's only query.
|
|
1994
|
-
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc())
|
|
2023
|
+
index("spec_settlements_spec_settled_idx").on(table.specId, table.settledAt.desc()),
|
|
2024
|
+
// The PR sweep's only query: branches with settlements not yet in a PR.
|
|
2025
|
+
index("spec_settlements_unproposed_idx").on(table.vaultId, table.repo, table.branch).where(sql`${table.pullRequestNumber} IS NULL`)
|
|
2026
|
+
]
|
|
2027
|
+
);
|
|
2028
|
+
sessionBranchDeclarations = pgTable(
|
|
2029
|
+
"session_branch_declarations",
|
|
2030
|
+
{
|
|
2031
|
+
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
|
|
2032
|
+
vaultId: text("vault_id").notNull().default(DEFAULT_VAULT_ID),
|
|
2033
|
+
actorKey: text("actor_key").notNull(),
|
|
2034
|
+
sessionId: text("session_id").notNull(),
|
|
2035
|
+
branch: text("branch").notNull(),
|
|
2036
|
+
declaredAt: timestamp("declared_at", { withTimezone: true }).notNull().default(sql`now()`),
|
|
2037
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull()
|
|
2038
|
+
},
|
|
2039
|
+
(table) => [
|
|
2040
|
+
uniqueIndex("session_branch_declarations_uq").on(
|
|
2041
|
+
table.vaultId,
|
|
2042
|
+
table.actorKey,
|
|
2043
|
+
table.sessionId
|
|
2044
|
+
),
|
|
2045
|
+
index("session_branch_declarations_lookup_idx").on(
|
|
2046
|
+
table.vaultId,
|
|
2047
|
+
table.actorKey,
|
|
2048
|
+
table.declaredAt.desc()
|
|
2049
|
+
)
|
|
1995
2050
|
]
|
|
1996
2051
|
);
|
|
1997
2052
|
}
|
|
1998
2053
|
});
|
|
1999
2054
|
|
|
2000
2055
|
// spex-console/bin/hs-workspace.ts
|
|
2001
|
-
import { readFileSync as
|
|
2056
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
2002
2057
|
import path3 from "node:path";
|
|
2003
|
-
import
|
|
2058
|
+
import process4 from "node:process";
|
|
2004
2059
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
2005
2060
|
|
|
2006
2061
|
// spex-console/src/lib/cli/control-shared.ts
|
|
@@ -2025,17 +2080,19 @@ function shellQuote(p) {
|
|
|
2025
2080
|
var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER_PATH)}` : HS_BIN_NAME;
|
|
2026
2081
|
|
|
2027
2082
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
2083
|
+
init_heyspex_home();
|
|
2028
2084
|
init_credentials();
|
|
2029
|
-
import { readFileSync as
|
|
2030
|
-
import { homedir as
|
|
2031
|
-
import { join as
|
|
2085
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2086
|
+
import { homedir as homedir5 } from "node:os";
|
|
2087
|
+
import { join as join6 } from "node:path";
|
|
2032
2088
|
|
|
2033
2089
|
// vendor/heyspex-mcp/session-owner.mjs
|
|
2090
|
+
init_heyspex_home();
|
|
2034
2091
|
init_credentials();
|
|
2035
2092
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
2036
|
-
import { homedir as
|
|
2093
|
+
import { homedir as homedir3 } from "node:os";
|
|
2037
2094
|
import { join as join3 } from "node:path";
|
|
2038
|
-
function resolveSessionOwnerUncached({ env = process.env, homeDir =
|
|
2095
|
+
function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile: readFile2 } = {}) {
|
|
2039
2096
|
const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
|
|
2040
2097
|
if (fromEnv) return fromEnv;
|
|
2041
2098
|
return readCurrentAccountId({ homeDir, readFile: readFile2 });
|
|
@@ -2065,10 +2122,41 @@ function isVaultLessEndpoint(method, path4) {
|
|
|
2065
2122
|
});
|
|
2066
2123
|
}
|
|
2067
2124
|
|
|
2125
|
+
// vendor/heyspex-mcp/write-outbox.mjs
|
|
2126
|
+
import {
|
|
2127
|
+
readdirSync,
|
|
2128
|
+
readFileSync as readFileSync3,
|
|
2129
|
+
writeFileSync as writeFileSync2,
|
|
2130
|
+
mkdirSync as mkdirSync2,
|
|
2131
|
+
existsSync as existsSync2,
|
|
2132
|
+
renameSync as renameSync2,
|
|
2133
|
+
rmSync
|
|
2134
|
+
} from "node:fs";
|
|
2135
|
+
import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
2136
|
+
|
|
2137
|
+
// scripts/lib/shared-path-helpers.mjs
|
|
2138
|
+
import { isAbsolute, resolve as resolve2, sep } from "node:path";
|
|
2139
|
+
function resolveCodeRoot({
|
|
2140
|
+
explicit,
|
|
2141
|
+
packageRoot,
|
|
2142
|
+
vaultRoot,
|
|
2143
|
+
env = process.env
|
|
2144
|
+
} = {}) {
|
|
2145
|
+
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
2146
|
+
if (fromExplicit) return fromExplicit;
|
|
2147
|
+
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
2148
|
+
if (fromEnv) return fromEnv;
|
|
2149
|
+
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
2150
|
+
if (fromPackage) return fromPackage;
|
|
2151
|
+
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
2152
|
+
return fromVault;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2068
2155
|
// vendor/heyspex-mcp/vault-resolve.mjs
|
|
2069
|
-
|
|
2070
|
-
import {
|
|
2071
|
-
import {
|
|
2156
|
+
init_heyspex_home();
|
|
2157
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
2158
|
+
import { homedir as homedir4, tmpdir } from "node:os";
|
|
2159
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
|
|
2072
2160
|
|
|
2073
2161
|
// vendor/heyspex-mcp/console-fetch.mjs
|
|
2074
2162
|
var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
|
|
@@ -2077,9 +2165,18 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
|
|
|
2077
2165
|
init_credentials();
|
|
2078
2166
|
|
|
2079
2167
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
2080
|
-
import { existsSync as
|
|
2081
|
-
import {
|
|
2082
|
-
|
|
2168
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2169
|
+
import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
2170
|
+
|
|
2171
|
+
// spex-console/src/lib/heyspex-home.ts
|
|
2172
|
+
import { homedir as homedir6 } from "node:os";
|
|
2173
|
+
function heyspexHome2(env = process.env) {
|
|
2174
|
+
const override = env?.HEYSPEX_HOME;
|
|
2175
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
2176
|
+
return override.trim();
|
|
2177
|
+
}
|
|
2178
|
+
return homedir6();
|
|
2179
|
+
}
|
|
2083
2180
|
|
|
2084
2181
|
// spex-console/src/lib/vault-registry/types.ts
|
|
2085
2182
|
var LEGACY_REGISTRY_ACCOUNT_ID = "legacy";
|
|
@@ -2096,10 +2193,10 @@ function emptyVaultRegistry() {
|
|
|
2096
2193
|
|
|
2097
2194
|
// spex-console/src/lib/vault-registry/registry-file.ts
|
|
2098
2195
|
function vaultRegistryDir(opts = {}) {
|
|
2099
|
-
return opts.registryDir ??
|
|
2196
|
+
return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
|
|
2100
2197
|
}
|
|
2101
2198
|
function vaultRegistryFilePath(opts = {}) {
|
|
2102
|
-
return
|
|
2199
|
+
return join7(vaultRegistryDir(opts), "registry.json");
|
|
2103
2200
|
}
|
|
2104
2201
|
function isNonEmptyString(v) {
|
|
2105
2202
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -2200,9 +2297,9 @@ function parseVaultRegistryFile(json) {
|
|
|
2200
2297
|
}
|
|
2201
2298
|
function readRegistryJson(opts) {
|
|
2202
2299
|
const filePath = vaultRegistryFilePath(opts);
|
|
2203
|
-
if (!
|
|
2300
|
+
if (!existsSync4(filePath)) return { kind: "missing" };
|
|
2204
2301
|
try {
|
|
2205
|
-
const raw =
|
|
2302
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
2206
2303
|
const json = JSON.parse(raw);
|
|
2207
2304
|
if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
|
|
2208
2305
|
return { kind: "ok", raw, json };
|
|
@@ -2256,14 +2353,14 @@ function underTestRunner() {
|
|
|
2256
2353
|
}
|
|
2257
2354
|
function samePathLoose(a, b) {
|
|
2258
2355
|
const norm = (p) => {
|
|
2259
|
-
const r =
|
|
2356
|
+
const r = resolve4(p).replace(/[\\/]+$/, "");
|
|
2260
2357
|
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
2261
2358
|
};
|
|
2262
2359
|
return norm(a) === norm(b);
|
|
2263
2360
|
}
|
|
2264
2361
|
function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
2265
2362
|
if (!underTestRunner()) return;
|
|
2266
|
-
if (samePathLoose(dirname4(filePath),
|
|
2363
|
+
if (samePathLoose(dirname4(filePath), join7(heyspexHome2(), ".heyspex"))) {
|
|
2267
2364
|
throw new Error(
|
|
2268
2365
|
`registry-file: refusing to write ${filePath} under a test runner \u2014 that is the real ~/.heyspex registry. Pass opts.registryDir (a temp dir).`
|
|
2269
2366
|
);
|
|
@@ -2272,10 +2369,10 @@ function assertNotRealHomeRegistryUnderTest(filePath) {
|
|
|
2272
2369
|
function writeVaultRegistry(registry, opts = {}) {
|
|
2273
2370
|
const filePath = vaultRegistryFilePath(opts);
|
|
2274
2371
|
assertNotRealHomeRegistryUnderTest(filePath);
|
|
2275
|
-
|
|
2372
|
+
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
2276
2373
|
const tmpPath = `${filePath}.tmp`;
|
|
2277
|
-
|
|
2278
|
-
|
|
2374
|
+
writeFileSync3(tmpPath, serializeVaultRegistry(registry), "utf8");
|
|
2375
|
+
renameSync3(tmpPath, filePath);
|
|
2279
2376
|
}
|
|
2280
2377
|
function isValidId(id) {
|
|
2281
2378
|
if (!isNonEmptyString(id)) return false;
|
|
@@ -2463,10 +2560,11 @@ function withCloudScope(deps, accountId2, vaultId) {
|
|
|
2463
2560
|
}
|
|
2464
2561
|
|
|
2465
2562
|
// scripts/lib/console-url-resolver.mjs
|
|
2563
|
+
init_heyspex_home();
|
|
2466
2564
|
init_dev_mode();
|
|
2467
|
-
import { readFileSync as
|
|
2468
|
-
import { homedir as
|
|
2469
|
-
import { join as
|
|
2565
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2566
|
+
import { homedir as homedir8 } from "node:os";
|
|
2567
|
+
import { join as join9 } from "node:path";
|
|
2470
2568
|
var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
|
|
2471
2569
|
function trimTrailingSlash(url) {
|
|
2472
2570
|
return url.replace(/\/+$/, "");
|
|
@@ -2492,9 +2590,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
2492
2590
|
} catch {
|
|
2493
2591
|
}
|
|
2494
2592
|
}
|
|
2495
|
-
function readCredentialsConsoleUrl({ homeDir =
|
|
2593
|
+
function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile: readFile2 = defaultReadFile2 } = {}) {
|
|
2496
2594
|
try {
|
|
2497
|
-
const raw = readFile2(
|
|
2595
|
+
const raw = readFile2(join9(homeDir, ".heyspex", "credentials.yaml"));
|
|
2498
2596
|
const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
2499
2597
|
const value = match?.[1]?.trim();
|
|
2500
2598
|
return value && value.length > 0 ? trimTrailingSlash(value) : null;
|
|
@@ -2503,12 +2601,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile: readFile2 =
|
|
|
2503
2601
|
}
|
|
2504
2602
|
}
|
|
2505
2603
|
function defaultReadFile2(p) {
|
|
2506
|
-
return
|
|
2604
|
+
return readFileSync8(p, "utf8");
|
|
2507
2605
|
}
|
|
2508
2606
|
function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
|
|
2509
2607
|
if (!repoRoot) return null;
|
|
2510
2608
|
try {
|
|
2511
|
-
const discovery = String(readDiscovery(
|
|
2609
|
+
const discovery = String(readDiscovery(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
|
|
2512
2610
|
if (discovery.length > 0) return trimTrailingSlash(discovery);
|
|
2513
2611
|
} catch {
|
|
2514
2612
|
}
|
|
@@ -2536,7 +2634,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
2536
2634
|
function resolveConsoleUrl({
|
|
2537
2635
|
repoRoot,
|
|
2538
2636
|
env = process.env,
|
|
2539
|
-
homeDir =
|
|
2637
|
+
homeDir = heyspexHome(),
|
|
2540
2638
|
strict = false,
|
|
2541
2639
|
devMode,
|
|
2542
2640
|
log = (line) => process.stderr.write(line),
|
|
@@ -2601,35 +2699,15 @@ var HELP = [
|
|
|
2601
2699
|
|
|
2602
2700
|
// spex-console/src/lib/cli/install-identity.ts
|
|
2603
2701
|
import { spawnSync } from "node:child_process";
|
|
2604
|
-
import { existsSync as
|
|
2605
|
-
import {
|
|
2606
|
-
import { join as join9 } from "node:path";
|
|
2702
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2703
|
+
import { join as join10 } from "node:path";
|
|
2607
2704
|
import { createInterface } from "node:readline/promises";
|
|
2608
|
-
|
|
2609
|
-
// scripts/lib/shared-path-helpers.mjs
|
|
2610
|
-
import { isAbsolute, resolve as resolve4, sep } from "node:path";
|
|
2611
|
-
function resolveCodeRoot({
|
|
2612
|
-
explicit,
|
|
2613
|
-
packageRoot,
|
|
2614
|
-
vaultRoot,
|
|
2615
|
-
env = process.env
|
|
2616
|
-
} = {}) {
|
|
2617
|
-
const fromExplicit = typeof explicit === "string" ? explicit.trim() : "";
|
|
2618
|
-
if (fromExplicit) return fromExplicit;
|
|
2619
|
-
const fromEnv = typeof env?.HS_PKG_ROOT === "string" ? env.HS_PKG_ROOT.trim() : "";
|
|
2620
|
-
if (fromEnv) return fromEnv;
|
|
2621
|
-
const fromPackage = typeof packageRoot === "string" ? packageRoot.trim() : "";
|
|
2622
|
-
if (fromPackage) return fromPackage;
|
|
2623
|
-
const fromVault = typeof vaultRoot === "string" ? vaultRoot.trim() : "";
|
|
2624
|
-
return fromVault;
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
// spex-console/src/lib/cli/install-identity.ts
|
|
2628
2705
|
init_credentials();
|
|
2629
2706
|
|
|
2630
2707
|
// vendor/heyspex-mcp/whoami.mjs
|
|
2708
|
+
init_heyspex_home();
|
|
2631
2709
|
init_credentials();
|
|
2632
|
-
import { homedir as
|
|
2710
|
+
import { homedir as homedir9 } from "node:os";
|
|
2633
2711
|
var SESSION_PATH = "/api/mcp/session";
|
|
2634
2712
|
async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
|
|
2635
2713
|
if (!token) return { kind: "no_credential" };
|
|
@@ -2724,6 +2802,10 @@ var HELP2 = [
|
|
|
2724
2802
|
"Exit codes: 0 ok \xB7 1 usage \xB7 11 bad-arg \xB7 13 write-failed"
|
|
2725
2803
|
].join("\n");
|
|
2726
2804
|
|
|
2805
|
+
// spex-console/src/lib/cli/satellite.ts
|
|
2806
|
+
import { resolve as resolveNodePath } from "node:path";
|
|
2807
|
+
import process2 from "node:process";
|
|
2808
|
+
|
|
2727
2809
|
// spex-console/src/lib/cli/install-identity.ts
|
|
2728
2810
|
var SWITCH_HINT = "Sign in as another account with `hs login --switch-account` (or create one with `hs login --create-account`).";
|
|
2729
2811
|
var ATTACH_WORDING = {
|
|
@@ -2958,7 +3040,7 @@ async function gateWriteVerb(opts) {
|
|
|
2958
3040
|
if (!opts.writeVerbs.has(opts.sub)) return null;
|
|
2959
3041
|
return runIdentityGate(opts.io, opts.flags, `${opts.namespace} ${opts.sub}`, WRITE_WORDING);
|
|
2960
3042
|
}
|
|
2961
|
-
function readConfiguredAccountId(env = process.env, homeDir =
|
|
3043
|
+
function readConfiguredAccountId(env = process.env, homeDir = heyspexHome2()) {
|
|
2962
3044
|
return resolveSessionOwnerUncached({ env, homeDir });
|
|
2963
3045
|
}
|
|
2964
3046
|
function isInteractiveTerminal() {
|
|
@@ -2966,7 +3048,7 @@ function isInteractiveTerminal() {
|
|
|
2966
3048
|
}
|
|
2967
3049
|
function defaultIdentityGateIo(opts) {
|
|
2968
3050
|
const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
|
|
2969
|
-
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) =>
|
|
3051
|
+
const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join10(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync5(p));
|
|
2970
3052
|
return {
|
|
2971
3053
|
consoleUrl,
|
|
2972
3054
|
interactive: opts.interactive ?? isInteractiveTerminal(),
|
|
@@ -3014,8 +3096,7 @@ function defaultIdentityGateIo(opts) {
|
|
|
3014
3096
|
init_sql_orm_inert_cli_shim();
|
|
3015
3097
|
|
|
3016
3098
|
// spex-console/src/lib/cli/vault-workspace-shared.ts
|
|
3017
|
-
import { readFileSync as
|
|
3018
|
-
import os2 from "node:os";
|
|
3099
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3019
3100
|
|
|
3020
3101
|
// spex-console/src/lib/cli/default-workspace.ts
|
|
3021
3102
|
function defaultWorkspaceId(accountId2) {
|
|
@@ -3027,8 +3108,8 @@ function defaultWorkspaceId(accountId2) {
|
|
|
3027
3108
|
function resolveConfiguredAccountId(opts = {}) {
|
|
3028
3109
|
return resolveSessionOwnerUncached({
|
|
3029
3110
|
env: opts.env ?? process.env,
|
|
3030
|
-
homeDir: opts.homeDir ??
|
|
3031
|
-
readFile: opts.readFile ?? ((p) =>
|
|
3111
|
+
homeDir: opts.homeDir ?? heyspexHome2(),
|
|
3112
|
+
readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
|
|
3032
3113
|
});
|
|
3033
3114
|
}
|
|
3034
3115
|
var 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.";
|
|
@@ -3036,7 +3117,11 @@ function resolveCloudAccountIdOrRefuse(opts = {}) {
|
|
|
3036
3117
|
const accountId2 = resolveConfiguredAccountId(opts);
|
|
3037
3118
|
return accountId2 ? { ok: true, accountId: accountId2 } : { ok: false, message: CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE };
|
|
3038
3119
|
}
|
|
3120
|
+
var VAULT_OWNED_BY_OTHER_ACCOUNT = "vault_owned_by_other_account";
|
|
3039
3121
|
var WORKSPACE_OWNED_BY_OTHER_ACCOUNT = "workspace_owned_by_other_account";
|
|
3122
|
+
function workspaceNotOwnedByCallerMessage(id) {
|
|
3123
|
+
return `workspace "${id}" belongs to another HeySpex account, so nothing was changed. Only the account that owns a workspace can rename, archive, restore or unregister it (\`hs login --switch-account\` to act as that account).`;
|
|
3124
|
+
}
|
|
3040
3125
|
function workspaceOwnedByOtherAccountMessage(id) {
|
|
3041
3126
|
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.`;
|
|
3042
3127
|
}
|
|
@@ -3105,8 +3190,14 @@ async function mutateHttp(http, method, path4, body, landed) {
|
|
|
3105
3190
|
const res = await cloudHttpRequest(http, method, path4, body);
|
|
3106
3191
|
if (res.ok) return landed;
|
|
3107
3192
|
if (res.status === 404) return "not_found";
|
|
3193
|
+
if (res.status === 403 && isOwnedByOtherAccountCode(errorCodeOf(res.json))) {
|
|
3194
|
+
return "owned_by_other_account";
|
|
3195
|
+
}
|
|
3108
3196
|
return cloudWriteFailure(http, res);
|
|
3109
3197
|
}
|
|
3198
|
+
function isOwnedByOtherAccountCode(code) {
|
|
3199
|
+
return code === VAULT_OWNED_BY_OTHER_ACCOUNT || code === WORKSPACE_OWNED_BY_OTHER_ACCOUNT;
|
|
3200
|
+
}
|
|
3110
3201
|
function manageVerbPath(kind, id) {
|
|
3111
3202
|
return `/api/${kind}/${encodeURIComponent(id)}`;
|
|
3112
3203
|
}
|
|
@@ -3463,18 +3554,31 @@ async function listWorkspacesNeon(accountId2, deps = {}) {
|
|
|
3463
3554
|
id: workspaces.id,
|
|
3464
3555
|
displayName: workspaces.displayName,
|
|
3465
3556
|
githubOrg: workspaces.githubOrg,
|
|
3466
|
-
createdAt: workspaces.createdAt
|
|
3557
|
+
createdAt: workspaces.createdAt,
|
|
3558
|
+
archived: workspaces.archived
|
|
3467
3559
|
}).from(workspaces).where(eq(workspaces.accountId, accountId2));
|
|
3468
3560
|
return rows.map((r) => ({
|
|
3469
3561
|
id: r.id,
|
|
3470
3562
|
display_name: r.displayName,
|
|
3471
3563
|
created_at: (r.createdAt instanceof Date ? r.createdAt : /* @__PURE__ */ new Date()).toISOString(),
|
|
3564
|
+
// The flag `setWorkspaceArchivedNeon` writes; `list.ts` filters on it.
|
|
3565
|
+
// On a cloud host this row IS the roster (no registry.json to carry the
|
|
3566
|
+
// flag), so leaving it out made an archived workspace list forever.
|
|
3567
|
+
archived: r.archived,
|
|
3472
3568
|
...r.githubOrg ? { github_org: r.githubOrg } : {}
|
|
3473
3569
|
}));
|
|
3474
3570
|
} catch {
|
|
3475
3571
|
return null;
|
|
3476
3572
|
}
|
|
3477
3573
|
}
|
|
3574
|
+
async function classifyZeroRowWorkspaceWrite(db, id, accountId2) {
|
|
3575
|
+
try {
|
|
3576
|
+
const rows = await db.select({ accountId: workspaces.accountId }).from(workspaces).where(eq(workspaces.id, id)).limit(1);
|
|
3577
|
+
return rows[0] && rows[0].accountId !== accountId2 ? "owned_by_other_account" : "not_found";
|
|
3578
|
+
} catch {
|
|
3579
|
+
return "not_found";
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3478
3582
|
async function renameWorkspaceNeon(id, displayName, accountId2, deps = {}) {
|
|
3479
3583
|
if (shouldUseCloudHttp(deps)) {
|
|
3480
3584
|
return mutateHttp(
|
|
@@ -3489,7 +3593,7 @@ async function renameWorkspaceNeon(id, displayName, accountId2, deps = {}) {
|
|
|
3489
3593
|
try {
|
|
3490
3594
|
const db = deps.db ?? getPostgresDb();
|
|
3491
3595
|
const rows = await db.update(workspaces).set({ displayName }).where(and(eq(workspaces.id, id), eq(workspaces.accountId, accountId2))).returning({ id: workspaces.id });
|
|
3492
|
-
return rows.length > 0 ? "updated" :
|
|
3596
|
+
return rows.length > 0 ? "updated" : classifyZeroRowWorkspaceWrite(db, id, accountId2);
|
|
3493
3597
|
} catch {
|
|
3494
3598
|
return "unreachable";
|
|
3495
3599
|
}
|
|
@@ -3508,7 +3612,7 @@ async function setWorkspaceArchivedNeon(id, archived, accountId2, deps = {}) {
|
|
|
3508
3612
|
try {
|
|
3509
3613
|
const db = deps.db ?? getPostgresDb();
|
|
3510
3614
|
const rows = await db.update(workspaces).set({ archived }).where(and(eq(workspaces.id, id), eq(workspaces.accountId, accountId2))).returning({ id: workspaces.id });
|
|
3511
|
-
return rows.length > 0 ? "updated" :
|
|
3615
|
+
return rows.length > 0 ? "updated" : classifyZeroRowWorkspaceWrite(db, id, accountId2);
|
|
3512
3616
|
} catch {
|
|
3513
3617
|
return "unreachable";
|
|
3514
3618
|
}
|
|
@@ -3521,7 +3625,7 @@ async function deleteWorkspaceNeon(id, accountId2, deps = {}) {
|
|
|
3521
3625
|
try {
|
|
3522
3626
|
const db = deps.db ?? getPostgresDb();
|
|
3523
3627
|
const rows = await db.delete(workspaces).where(and(eq(workspaces.id, id), eq(workspaces.accountId, accountId2))).returning({ id: workspaces.id });
|
|
3524
|
-
return rows.length > 0 ? "deleted" :
|
|
3628
|
+
return rows.length > 0 ? "deleted" : classifyZeroRowWorkspaceWrite(db, id, accountId2);
|
|
3525
3629
|
} catch {
|
|
3526
3630
|
return "unreachable";
|
|
3527
3631
|
}
|
|
@@ -3561,17 +3665,57 @@ async function repointActiveWorkspaceOnRemoval(accountId2, removedWorkspaceId, d
|
|
|
3561
3665
|
return { repointed: true, newActiveId: candidate.id };
|
|
3562
3666
|
}
|
|
3563
3667
|
|
|
3668
|
+
// spex-console/src/lib/vault-registry/side-effects.ts
|
|
3669
|
+
function isLocalRegistryHost() {
|
|
3670
|
+
const override = process.env.HEYSPEX_VAULT_WRITE_MODE?.trim().toLowerCase();
|
|
3671
|
+
if (override === "cloud") return false;
|
|
3672
|
+
if (override === "local") return true;
|
|
3673
|
+
return process.env.VERCEL !== "1";
|
|
3674
|
+
}
|
|
3675
|
+
function sideEffectErrorMessage(err) {
|
|
3676
|
+
return err instanceof Error ? err.message : String(err);
|
|
3677
|
+
}
|
|
3678
|
+
function runRegistrySideEffect(write) {
|
|
3679
|
+
const step = "registry_json";
|
|
3680
|
+
if (!isLocalRegistryHost()) return { step, ok: true, skipped: true };
|
|
3681
|
+
try {
|
|
3682
|
+
write();
|
|
3683
|
+
return { step, ok: true };
|
|
3684
|
+
} catch (err) {
|
|
3685
|
+
const error = sideEffectErrorMessage(err);
|
|
3686
|
+
console.error(`[vault-registry] post-write side step "${step}" failed: ${error}`);
|
|
3687
|
+
return { step, ok: false, error };
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
function registryWritten(effect) {
|
|
3691
|
+
return effect.ok && effect.skipped !== true;
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3564
3694
|
// spex-console/src/lib/vault-registry/archive.ts
|
|
3565
3695
|
function cloudWriteFailed(failure) {
|
|
3566
3696
|
return { ok: false, code: "cloud_write_failed", message: failure.message };
|
|
3567
3697
|
}
|
|
3698
|
+
function neonUnwritten(kind, id) {
|
|
3699
|
+
return {
|
|
3700
|
+
ok: false,
|
|
3701
|
+
code: "neon_unwritten",
|
|
3702
|
+
message: `${kind} "${id}" was not written to the cloud registry (database unreachable); nothing was changed. Retry once the console can reach its database.`
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3568
3705
|
function workspaceNotFound(id) {
|
|
3569
3706
|
return { ok: false, code: "workspace_not_found", message: `workspace "${id}" not found` };
|
|
3570
3707
|
}
|
|
3708
|
+
function workspaceNotOwned(id) {
|
|
3709
|
+
return {
|
|
3710
|
+
ok: false,
|
|
3711
|
+
code: WORKSPACE_OWNED_BY_OTHER_ACCOUNT,
|
|
3712
|
+
message: workspaceNotOwnedByCallerMessage(id)
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3571
3715
|
async function setWorkspaceArchivedState(input, archived, deps = {}) {
|
|
3572
|
-
const
|
|
3573
|
-
const existing =
|
|
3574
|
-
if (!existing) return workspaceNotFound(input.id);
|
|
3716
|
+
const local = isLocalRegistryHost();
|
|
3717
|
+
const existing = local ? loadVaultRegistry(deps.registryOpts).workspaces.find((w) => w.id === input.id) : void 0;
|
|
3718
|
+
if (local && !existing) return workspaceNotFound(input.id);
|
|
3575
3719
|
const neonResult = await setWorkspaceArchivedNeon(
|
|
3576
3720
|
input.id,
|
|
3577
3721
|
archived,
|
|
@@ -3579,21 +3723,23 @@ async function setWorkspaceArchivedState(input, archived, deps = {}) {
|
|
|
3579
3723
|
deps.neon ?? {}
|
|
3580
3724
|
);
|
|
3581
3725
|
if (neonResult === "not_found") return workspaceNotFound(input.id);
|
|
3726
|
+
if (neonResult === "owned_by_other_account") return workspaceNotOwned(input.id);
|
|
3582
3727
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed(neonResult);
|
|
3728
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten("workspace", input.id);
|
|
3583
3729
|
if (archived) {
|
|
3584
3730
|
await repointActiveWorkspaceOnRemoval(input.accountId, input.id, {
|
|
3585
3731
|
registryOpts: deps.registryOpts,
|
|
3586
3732
|
accountPointer: { ...deps.neon, registryOpts: deps.registryOpts }
|
|
3587
3733
|
});
|
|
3588
3734
|
}
|
|
3589
|
-
registerWorkspace({ ...existing, archived }, deps.registryOpts);
|
|
3735
|
+
if (existing) registerWorkspace({ ...existing, archived }, deps.registryOpts);
|
|
3590
3736
|
return {
|
|
3591
3737
|
ok: true,
|
|
3592
3738
|
workspace: {
|
|
3593
3739
|
id: input.id,
|
|
3594
3740
|
archived,
|
|
3595
3741
|
neon_written: neonResult === "updated",
|
|
3596
|
-
registry_written:
|
|
3742
|
+
registry_written: existing !== void 0
|
|
3597
3743
|
}
|
|
3598
3744
|
};
|
|
3599
3745
|
}
|
|
@@ -3632,35 +3778,7 @@ function buildCloseNotDestroyConfirm(kind, verb, displayName) {
|
|
|
3632
3778
|
}
|
|
3633
3779
|
|
|
3634
3780
|
// spex-console/src/lib/vault-registry/list.ts
|
|
3635
|
-
import { existsSync as
|
|
3636
|
-
|
|
3637
|
-
// spex-console/src/lib/vault-registry/side-effects.ts
|
|
3638
|
-
function isLocalRegistryHost() {
|
|
3639
|
-
const override = process.env.HEYSPEX_VAULT_WRITE_MODE?.trim().toLowerCase();
|
|
3640
|
-
if (override === "cloud") return false;
|
|
3641
|
-
if (override === "local") return true;
|
|
3642
|
-
return process.env.VERCEL !== "1";
|
|
3643
|
-
}
|
|
3644
|
-
function sideEffectErrorMessage(err) {
|
|
3645
|
-
return err instanceof Error ? err.message : String(err);
|
|
3646
|
-
}
|
|
3647
|
-
function runRegistrySideEffect(write) {
|
|
3648
|
-
const step = "registry_json";
|
|
3649
|
-
if (!isLocalRegistryHost()) return { step, ok: true, skipped: true };
|
|
3650
|
-
try {
|
|
3651
|
-
write();
|
|
3652
|
-
return { step, ok: true };
|
|
3653
|
-
} catch (err) {
|
|
3654
|
-
const error = sideEffectErrorMessage(err);
|
|
3655
|
-
console.error(`[vault-registry] post-write side step "${step}" failed: ${error}`);
|
|
3656
|
-
return { step, ok: false, error };
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
function registryWritten(effect) {
|
|
3660
|
-
return effect.ok && effect.skipped !== true;
|
|
3661
|
-
}
|
|
3662
|
-
|
|
3663
|
-
// spex-console/src/lib/vault-registry/list.ts
|
|
3781
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
3664
3782
|
function pointerDeps2(deps) {
|
|
3665
3783
|
return deps.accountPointer ?? { registryOpts: deps.registryOpts };
|
|
3666
3784
|
}
|
|
@@ -3902,6 +4020,25 @@ var MAX_DISPLAY_NAME_LEN2 = 60;
|
|
|
3902
4020
|
function notFound(id) {
|
|
3903
4021
|
return { ok: false, code: "workspace_not_found", message: `workspace "${id}" not found` };
|
|
3904
4022
|
}
|
|
4023
|
+
function notOwned(id) {
|
|
4024
|
+
return {
|
|
4025
|
+
ok: false,
|
|
4026
|
+
code: WORKSPACE_OWNED_BY_OTHER_ACCOUNT,
|
|
4027
|
+
message: workspaceNotOwnedByCallerMessage(id)
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
function neonUnwritten2(id) {
|
|
4031
|
+
return {
|
|
4032
|
+
ok: false,
|
|
4033
|
+
code: "neon_unwritten",
|
|
4034
|
+
message: `workspace "${id}" was not written to the cloud registry (database unreachable); nothing was changed. Retry once the console can reach its database.`
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
function localRow(id, registryOpts) {
|
|
4038
|
+
const local = isLocalRegistryHost();
|
|
4039
|
+
const existing = local ? loadVaultRegistry(registryOpts).workspaces.find((w) => w.id === id) : void 0;
|
|
4040
|
+
return { local, existing };
|
|
4041
|
+
}
|
|
3905
4042
|
function cloudWriteFailed2(failure) {
|
|
3906
4043
|
return { ok: false, code: "cloud_write_failed", message: failure.message };
|
|
3907
4044
|
}
|
|
@@ -3914,9 +4051,8 @@ async function renameWorkspace(input, deps = {}) {
|
|
|
3914
4051
|
message: `display name must be 1-${MAX_DISPLAY_NAME_LEN2} characters (trimmed)`
|
|
3915
4052
|
};
|
|
3916
4053
|
}
|
|
3917
|
-
const
|
|
3918
|
-
|
|
3919
|
-
if (!existing) return notFound(input.id);
|
|
4054
|
+
const { local, existing } = localRow(input.id, deps.registryOpts);
|
|
4055
|
+
if (local && !existing) return notFound(input.id);
|
|
3920
4056
|
const neonResult = await renameWorkspaceNeon(
|
|
3921
4057
|
input.id,
|
|
3922
4058
|
trimmedName,
|
|
@@ -3924,42 +4060,41 @@ async function renameWorkspace(input, deps = {}) {
|
|
|
3924
4060
|
deps.neon ?? {}
|
|
3925
4061
|
);
|
|
3926
4062
|
if (neonResult === "not_found") return notFound(input.id);
|
|
4063
|
+
if (neonResult === "owned_by_other_account") return notOwned(input.id);
|
|
3927
4064
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed2(neonResult);
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
},
|
|
3933
|
-
deps.registryOpts
|
|
3934
|
-
);
|
|
4065
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten2(input.id);
|
|
4066
|
+
if (existing) {
|
|
4067
|
+
registerWorkspace({ ...existing, display_name: trimmedName }, deps.registryOpts);
|
|
4068
|
+
}
|
|
3935
4069
|
return {
|
|
3936
4070
|
ok: true,
|
|
3937
4071
|
workspace: {
|
|
3938
4072
|
id: input.id,
|
|
3939
4073
|
display_name: trimmedName,
|
|
3940
4074
|
neon_written: neonResult === "updated",
|
|
3941
|
-
registry_written:
|
|
4075
|
+
registry_written: existing !== void 0
|
|
3942
4076
|
}
|
|
3943
4077
|
};
|
|
3944
4078
|
}
|
|
3945
4079
|
async function unregisterWorkspace(input, deps = {}) {
|
|
3946
|
-
const
|
|
3947
|
-
|
|
3948
|
-
if (!existing) return notFound(input.id);
|
|
4080
|
+
const { local, existing } = localRow(input.id, deps.registryOpts);
|
|
4081
|
+
if (local && !existing) return notFound(input.id);
|
|
3949
4082
|
const neonResult = await deleteWorkspaceNeon(input.id, input.accountId, deps.neon ?? {});
|
|
3950
4083
|
if (neonResult === "not_found") return notFound(input.id);
|
|
4084
|
+
if (neonResult === "owned_by_other_account") return notOwned(input.id);
|
|
3951
4085
|
if (isCloudWriteFailure(neonResult)) return cloudWriteFailed2(neonResult);
|
|
4086
|
+
if (neonResult === "unreachable" && !existing) return neonUnwritten2(input.id);
|
|
3952
4087
|
await repointActiveWorkspaceOnRemoval(input.accountId, input.id, {
|
|
3953
4088
|
registryOpts: deps.registryOpts,
|
|
3954
4089
|
accountPointer: { ...deps.neon, registryOpts: deps.registryOpts }
|
|
3955
4090
|
});
|
|
3956
|
-
removeWorkspace(input.id, deps.registryOpts);
|
|
4091
|
+
if (existing) removeWorkspace(input.id, deps.registryOpts);
|
|
3957
4092
|
return {
|
|
3958
4093
|
ok: true,
|
|
3959
4094
|
workspace: {
|
|
3960
4095
|
id: input.id,
|
|
3961
4096
|
neon_written: neonResult === "deleted",
|
|
3962
|
-
registry_written:
|
|
4097
|
+
registry_written: existing !== void 0
|
|
3963
4098
|
}
|
|
3964
4099
|
};
|
|
3965
4100
|
}
|
|
@@ -4632,7 +4767,10 @@ async function activeWorkspaceSelection(deps, effectiveAccountId = accountId(dep
|
|
|
4632
4767
|
}
|
|
4633
4768
|
function statusForWorkspaceManageError(code) {
|
|
4634
4769
|
if (code === "workspace_not_found" || code === "vault_not_found") return EXIT.NOT_FOUND;
|
|
4635
|
-
if (code === "
|
|
4770
|
+
if (code === "workspace_owned_by_other_account" || code === "vault_owned_by_other_account") {
|
|
4771
|
+
return EXIT.CONFLICT;
|
|
4772
|
+
}
|
|
4773
|
+
if (code === "cloud_write_failed" || code === "neon_unwritten") return EXIT.CLOUD_WRITE_FAILED;
|
|
4636
4774
|
return EXIT.INVALID_ARG;
|
|
4637
4775
|
}
|
|
4638
4776
|
async function runRename(args, deps) {
|
|
@@ -4827,7 +4965,7 @@ var HELP3 = [
|
|
|
4827
4965
|
].join("\n");
|
|
4828
4966
|
|
|
4829
4967
|
// spex-console/bin/run-entry.ts
|
|
4830
|
-
import
|
|
4968
|
+
import process3 from "node:process";
|
|
4831
4969
|
var BinExit = class extends Error {
|
|
4832
4970
|
code;
|
|
4833
4971
|
constructor(code) {
|
|
@@ -4843,13 +4981,13 @@ function runEntry(name, main2, options = {}) {
|
|
|
4843
4981
|
await main2();
|
|
4844
4982
|
} catch (err) {
|
|
4845
4983
|
if (err instanceof BinExit) {
|
|
4846
|
-
|
|
4984
|
+
process3.exitCode = err.code;
|
|
4847
4985
|
return;
|
|
4848
4986
|
}
|
|
4849
4987
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
4850
|
-
|
|
4988
|
+
process3.stderr.write(`${name}: ${unhandledLabel}: ${message2}
|
|
4851
4989
|
`);
|
|
4852
|
-
|
|
4990
|
+
process3.exitCode = fallbackCode;
|
|
4853
4991
|
}
|
|
4854
4992
|
})();
|
|
4855
4993
|
}
|
|
@@ -4857,15 +4995,15 @@ function runEntry(name, main2, options = {}) {
|
|
|
4857
4995
|
// spex-console/bin/hs-workspace.ts
|
|
4858
4996
|
function readFile(p) {
|
|
4859
4997
|
try {
|
|
4860
|
-
return
|
|
4998
|
+
return readFileSync10(p, "utf8");
|
|
4861
4999
|
} catch {
|
|
4862
5000
|
return null;
|
|
4863
5001
|
}
|
|
4864
5002
|
}
|
|
4865
|
-
var INTERACTIVE =
|
|
5003
|
+
var INTERACTIVE = process4.stdin.isTTY === true;
|
|
4866
5004
|
async function confirm(question) {
|
|
4867
5005
|
if (!INTERACTIVE) return false;
|
|
4868
|
-
const rl = createInterface2({ input:
|
|
5006
|
+
const rl = createInterface2({ input: process4.stdin, output: process4.stdout });
|
|
4869
5007
|
try {
|
|
4870
5008
|
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
4871
5009
|
return answer === "y" || answer === "yes" || answer === "proceed";
|
|
@@ -4874,41 +5012,41 @@ async function confirm(question) {
|
|
|
4874
5012
|
}
|
|
4875
5013
|
}
|
|
4876
5014
|
async function main() {
|
|
4877
|
-
const identityArgv = extractWriteIdentityFlags(
|
|
5015
|
+
const identityArgv = extractWriteIdentityFlags(process4.argv.slice(3));
|
|
4878
5016
|
if (identityArgv.error) {
|
|
4879
|
-
|
|
5017
|
+
process4.stderr.write(`hs workspace: ${identityArgv.error}
|
|
4880
5018
|
`);
|
|
4881
|
-
|
|
5019
|
+
process4.exitCode = EXIT.INVALID_ARG;
|
|
4882
5020
|
return;
|
|
4883
5021
|
}
|
|
4884
5022
|
const parsed = parseWorkspaceArgs(identityArgv.rest);
|
|
4885
5023
|
if (parsed.help) {
|
|
4886
|
-
|
|
5024
|
+
process4.stdout.write(`${HELP3}
|
|
4887
5025
|
`);
|
|
4888
|
-
|
|
5026
|
+
process4.exitCode = EXIT.OK;
|
|
4889
5027
|
return;
|
|
4890
5028
|
}
|
|
4891
5029
|
if (parsed.error) {
|
|
4892
|
-
|
|
5030
|
+
process4.stderr.write(`hs workspace: ${parsed.error.message}
|
|
4893
5031
|
`);
|
|
4894
|
-
|
|
5032
|
+
process4.exitCode = parsed.error.code;
|
|
4895
5033
|
return;
|
|
4896
5034
|
}
|
|
4897
|
-
const vaultRoot =
|
|
5035
|
+
const vaultRoot = process4.env.HS_VAULT_ROOT ? path3.resolve(process4.env.HS_VAULT_ROOT) : process4.cwd();
|
|
4898
5036
|
const consoleUrl = resolveSpexConsoleUrl(vaultRoot, readFile);
|
|
4899
5037
|
const gate = await gateWriteVerb({
|
|
4900
5038
|
namespace: "hs workspace",
|
|
4901
5039
|
sub: parsed.args.sub,
|
|
4902
5040
|
writeVerbs: WORKSPACE_WRITE_VERBS,
|
|
4903
5041
|
flags: identityArgv.flags,
|
|
4904
|
-
io: defaultIdentityGateIo({ consoleUrl, platformRoot: path3.resolve(
|
|
5042
|
+
io: defaultIdentityGateIo({ consoleUrl, platformRoot: path3.resolve(process4.cwd(), "..") })
|
|
4905
5043
|
});
|
|
4906
5044
|
if (gate && !gate.ok) {
|
|
4907
|
-
|
|
5045
|
+
process4.stderr.write(
|
|
4908
5046
|
`${parsed.args.json ? JSON.stringify(gate.result.payload) : gate.result.message}
|
|
4909
5047
|
`
|
|
4910
5048
|
);
|
|
4911
|
-
|
|
5049
|
+
process4.exitCode = gate.result.code;
|
|
4912
5050
|
return;
|
|
4913
5051
|
}
|
|
4914
5052
|
const result = await runWorkspace(parsed.args, {
|
|
@@ -4916,9 +5054,9 @@ async function main() {
|
|
|
4916
5054
|
confirm,
|
|
4917
5055
|
neon: { http: { fetchImpl: fetch, consoleUrl, token: readPairedToken() } }
|
|
4918
5056
|
});
|
|
4919
|
-
const sink = result.isError ?
|
|
5057
|
+
const sink = result.isError ? process4.stderr : process4.stdout;
|
|
4920
5058
|
sink.write(`${parsed.args.json ? JSON.stringify(result.payload) : result.message}
|
|
4921
5059
|
`);
|
|
4922
|
-
|
|
5060
|
+
process4.exitCode = result.code;
|
|
4923
5061
|
}
|
|
4924
5062
|
runEntry("hs workspace", main, { fallbackCode: EXIT.WRITE_FAILED });
|