@hasna/instructions 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +64 -20
  2. package/dist/cli/fail-closed-no-env.test.d.ts +2 -0
  3. package/dist/cli/fail-closed-no-env.test.d.ts.map +1 -0
  4. package/dist/cli/index.js +1910 -561
  5. package/dist/data/config-store.d.ts +62 -33
  6. package/dist/data/config-store.d.ts.map +1 -1
  7. package/dist/db/database.d.ts.map +1 -1
  8. package/dist/generated/storage-kit/backend.d.ts +4 -4
  9. package/dist/generated/storage-kit/backend.d.ts.map +1 -1
  10. package/dist/generated/storage-kit/index.d.ts +1 -1
  11. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  12. package/dist/generated/storage-kit/migrations.d.ts +21 -0
  13. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  14. package/dist/generated/storage-kit/pool.d.ts +2 -5
  15. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  16. package/dist/index.d.ts +5 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2702 -547
  19. package/dist/lib/app-home.d.ts +9 -0
  20. package/dist/lib/app-home.d.ts.map +1 -1
  21. package/dist/lib/client-types.d.ts +130 -0
  22. package/dist/lib/client-types.d.ts.map +1 -0
  23. package/dist/lib/client-types.test.d.ts +2 -0
  24. package/dist/lib/client-types.test.d.ts.map +1 -0
  25. package/dist/lib/local-opt-in.d.ts +82 -0
  26. package/dist/lib/local-opt-in.d.ts.map +1 -0
  27. package/dist/lib/project-context.d.ts +14 -14
  28. package/dist/lib/project-context.d.ts.map +1 -1
  29. package/dist/lib/project-dashboard-standard.d.ts +1 -1
  30. package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
  31. package/dist/lib/session-apply.d.ts.map +1 -1
  32. package/dist/lib/session-render-state-hermeticity.test.d.ts +2 -0
  33. package/dist/lib/session-render-state-hermeticity.test.d.ts.map +1 -0
  34. package/dist/lib/session-render-state.d.ts +57 -0
  35. package/dist/lib/session-render-state.d.ts.map +1 -0
  36. package/dist/lib/session-render-state.test.d.ts +2 -0
  37. package/dist/lib/session-render-state.test.d.ts.map +1 -0
  38. package/dist/lib/transport-resolver.d.ts +80 -0
  39. package/dist/lib/transport-resolver.d.ts.map +1 -0
  40. package/dist/lib/transport-resolver.test.d.ts +2 -0
  41. package/dist/lib/transport-resolver.test.d.ts.map +1 -0
  42. package/dist/mcp/index.d.ts.map +1 -1
  43. package/dist/mcp/index.js +1442 -192
  44. package/dist/mcp/server.d.ts.map +1 -1
  45. package/dist/sdk/index.d.ts +22 -0
  46. package/dist/sdk/index.d.ts.map +1 -0
  47. package/dist/sdk/index.js +1042 -0
  48. package/dist/sdk/resolve.d.ts +82 -0
  49. package/dist/sdk/resolve.d.ts.map +1 -0
  50. package/dist/sdk/resolve.test.d.ts +2 -0
  51. package/dist/sdk/resolve.test.d.ts.map +1 -0
  52. package/dist/sdk/sdk-bundle-self-contained.test.d.ts +2 -0
  53. package/dist/sdk/sdk-bundle-self-contained.test.d.ts.map +1 -0
  54. package/dist/sdk/v1.generated.d.ts +288 -0
  55. package/dist/sdk/v1.generated.d.ts.map +1 -0
  56. package/dist/server/cloud.d.ts.map +1 -1
  57. package/dist/server/index.d.ts.map +1 -1
  58. package/dist/server/index.js +55 -64
  59. package/dist/test-support/preload-state-home.d.ts +2 -0
  60. package/dist/test-support/preload-state-home.d.ts.map +1 -0
  61. package/package.json +12 -12
  62. package/dashboard/README.md +0 -37
  63. package/dist/lib/retired-storage-mode.d.ts +0 -8
  64. package/dist/lib/retired-storage-mode.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -1290,8 +1290,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1290
1290
  args = args.slice();
1291
1291
  let launchWithNode = false;
1292
1292
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1293
- function findFile(baseDir2, baseName) {
1294
- const localBin = path.resolve(baseDir2, baseName);
1293
+ function findFile(baseDir, baseName) {
1294
+ const localBin = path.resolve(baseDir, baseName);
1295
1295
  if (fs.existsSync(localBin))
1296
1296
  return localBin;
1297
1297
  if (sourceExt.includes(path.extname(baseName)))
@@ -2136,116 +2136,167 @@ var init_types = __esm(() => {
2136
2136
  };
2137
2137
  });
2138
2138
 
2139
- // src/lib/retired-storage-mode.ts
2140
- function firstDefinedEnvKey(env, keys) {
2141
- for (const key of keys) {
2142
- if (Object.hasOwn(env, key) && env[key] !== undefined)
2143
- return key;
2144
- }
2145
- return null;
2139
+ // ../contracts/dist/client/transport.js
2140
+ import { createRequire } from "module";
2141
+ function envToken(name) {
2142
+ return name.toUpperCase().replace(/-/g, "_");
2146
2143
  }
2147
- function assertNoLegacyStorageMode(env = process.env) {
2148
- const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
2149
- if (!legacyKey)
2150
- return;
2151
- throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_INSTRUCTIONS_API_URL + HASNA_INSTRUCTIONS_API_KEY. ` + `On the server, set HASNA_INSTRUCTIONS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
2152
- }
2153
- var LEGACY_STORAGE_MODE_KEYS;
2154
- var init_retired_storage_mode = __esm(() => {
2155
- LEGACY_STORAGE_MODE_KEYS = [
2156
- "HASNA_INSTRUCTIONS_STORAGE_MODE",
2157
- "HASNA_INSTRUCTIONS_MODE",
2158
- "INSTRUCTIONS_STORAGE_MODE",
2159
- "INSTRUCTIONS_MODE"
2144
+ function clientTransportEnvKeys(name) {
2145
+ const envSegment = envToken(name);
2146
+ return {
2147
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
2148
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
2149
+ };
2150
+ }
2151
+ function credentialOverrideEnvKey(name) {
2152
+ return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
2153
+ }
2154
+ function credentialPointerEnvKey(name) {
2155
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
2156
+ }
2157
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", MAX_CREDENTIAL_FILE_BYTES, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
2158
+ var init_transport = __esm(() => {
2159
+ MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
2160
+ INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
2161
+ CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
2162
+ AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
2163
+ SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
2164
+ requireSecretsSdk = createRequire(import.meta.url);
2165
+ IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
2166
+ AUTHORITY_OVERRIDE_HEADERS = new Set([
2167
+ "host",
2168
+ ":authority",
2169
+ "forwarded",
2170
+ "x-forwarded-host",
2171
+ "x-original-host"
2172
+ ]);
2173
+ });
2174
+
2175
+ // src/lib/local-opt-in.ts
2176
+ function instructionsLocalModeNotice() {
2177
+ return `instructions: local mode \u2014 ${INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 selects the on-box SQLite store, ` + `and no hosted authority was configured. Set HASNA_INSTRUCTIONS_API_KEY (or add the Keychain item ` + `hasna.credentials.instructions.api-key, or write ~/.hasna/instructions/config/credentials) to go hosted.`;
2178
+ }
2179
+ function isInstructionsLocalOptIn(env = process.env) {
2180
+ return INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() === "1");
2181
+ }
2182
+ function instructionsAuthorityEnvKeys() {
2183
+ const keys = clientTransportEnvKeys("instructions");
2184
+ return [
2185
+ ...keys.apiUrlKeys,
2186
+ ...keys.apiKeyKeys,
2187
+ credentialOverrideEnvKey("instructions"),
2188
+ credentialPointerEnvKey("instructions"),
2189
+ CREDENTIAL_PROFILE_ENV_KEY
2160
2190
  ];
2191
+ }
2192
+ function hasInstructionsEnvAuthorityIntent(env = process.env) {
2193
+ return instructionsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
2194
+ }
2195
+ function selectsInstructionsLocalStore(env = process.env) {
2196
+ return !hasInstructionsEnvAuthorityIntent(env) && isInstructionsLocalOptIn(env);
2197
+ }
2198
+ function instructionsResolverEnv(env) {
2199
+ const blanks = instructionsAuthorityEnvKeys().filter((key) => (key in env) && (env[key] ?? "").trim() === "");
2200
+ if (blanks.length === 0)
2201
+ return env;
2202
+ const next = { ...env };
2203
+ for (const key of blanks)
2204
+ delete next[key];
2205
+ return next;
2206
+ }
2207
+ function isAmbientInstructionsEnv(env) {
2208
+ if (typeof process !== "undefined" && env === process.env)
2209
+ return true;
2210
+ return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
2211
+ }
2212
+ function instructionsResolverInputs(env, credentials = {}) {
2213
+ const normalised = instructionsResolverEnv(env);
2214
+ if (normalised === env)
2215
+ return { env: normalised, credentials };
2216
+ const keychain = { ...credentials.keychain };
2217
+ if (keychain.enabled === undefined && keychain.run === undefined) {
2218
+ keychain.enabled = isAmbientInstructionsEnv(env);
2219
+ }
2220
+ return { env: normalised, credentials: { ...credentials, keychain } };
2221
+ }
2222
+ var INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS, CONTRACTS_AMBIENT_ENVIRONMENT;
2223
+ var init_local_opt_in = __esm(() => {
2224
+ init_transport();
2225
+ INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_INSTRUCTIONS_LOCAL"];
2226
+ CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
2161
2227
  });
2162
2228
 
2163
- // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
2164
- import { homedir as homedir3 } from "os";
2165
- import { join as join4 } from "path";
2166
- function assertApp2(app) {
2229
+ // src/lib/app-home.ts
2230
+ import { existsSync as existsSync3 } from "fs";
2231
+ import { homedir as homedir2 } from "os";
2232
+ import { join as join3, resolve as resolve2 } from "path";
2233
+ import { homedir as pathsResolverHomedir2 } from "os";
2234
+ import { join as pathsResolverJoin2 } from "path";
2235
+ function pathsResolverAssertApp2(app) {
2167
2236
  if (typeof app !== "string" || app.length === 0) {
2168
2237
  throw new TypeError("paths: app must be a non-empty string");
2169
2238
  }
2170
- if (!APP_SLUG_RE2.test(app)) {
2239
+ if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
2171
2240
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
2172
2241
  }
2173
2242
  }
2174
- function envOf2(options) {
2175
- return options.env ?? process.env;
2176
- }
2177
- function envValue2(options, kind) {
2178
- const value = envOf2(options)[KIND_ENV2[kind]];
2179
- return typeof value === "string" && value.length > 0 ? value : undefined;
2180
- }
2181
- function isMacOS2(platform) {
2182
- return platform === "darwin";
2243
+ function pathsResolverAssertKind2(kind) {
2244
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
2245
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
2246
+ }
2183
2247
  }
2184
- function baseDir2(kind, options) {
2185
- const override = envValue2(options, kind);
2186
- if (override)
2248
+ function pathsResolverBaseDir2(kind, options) {
2249
+ pathsResolverAssertKind2(kind);
2250
+ const env = options.env ?? process.env;
2251
+ const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
2252
+ if (typeof override === "string" && override.length > 0)
2187
2253
  return override;
2188
- const home = options.home ?? homedir3();
2254
+ const home = options.home ?? pathsResolverHomedir2();
2189
2255
  const platform = options.platform ?? process.platform;
2190
- if (isMacOS2(platform)) {
2256
+ if (platform === "darwin") {
2191
2257
  switch (kind) {
2192
2258
  case "config":
2193
2259
  case "data":
2194
- return join4(home, "Library", "Application Support", "Hasna");
2260
+ return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
2195
2261
  case "cache":
2196
- return join4(home, "Library", "Caches", "Hasna");
2262
+ return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
2197
2263
  case "state":
2198
- return join4(home, "Library", "Logs", "Hasna");
2264
+ return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
2199
2265
  }
2200
2266
  }
2201
2267
  switch (kind) {
2202
2268
  case "config":
2203
- return join4(home, ".config", "hasna");
2269
+ return pathsResolverJoin2(home, ".config", "hasna");
2204
2270
  case "data":
2205
- return join4(home, ".local", "share", "hasna");
2271
+ return pathsResolverJoin2(home, ".local", "share", "hasna");
2206
2272
  case "state":
2207
- return join4(home, ".local", "state", "hasna");
2273
+ return pathsResolverJoin2(home, ".local", "state", "hasna");
2208
2274
  case "cache":
2209
- return join4(home, ".cache", "hasna");
2275
+ return pathsResolverJoin2(home, ".cache", "hasna");
2210
2276
  }
2211
2277
  }
2212
- function resolvePath2(kind, options) {
2213
- assertApp2(options.app);
2214
- const appSegment = options.internal === true ? join4("internal", options.app) : options.app;
2215
- return join4(baseDir2(kind, options), appSegment);
2278
+ function pathsResolverResolve2(kind, options) {
2279
+ pathsResolverAssertApp2(options.app);
2280
+ const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
2281
+ return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
2216
2282
  }
2217
2283
  function configDir(options) {
2218
- return resolvePath2("config", options);
2284
+ return pathsResolverResolve2("config", options);
2219
2285
  }
2220
- var KIND_ENV2, APP_SLUG_RE2;
2221
- var init_dist = __esm(() => {
2222
- KIND_ENV2 = {
2223
- config: "HASNA_CONFIG_HOME",
2224
- data: "HASNA_DATA_HOME",
2225
- state: "HASNA_STATE_HOME",
2226
- cache: "HASNA_CACHE_HOME"
2227
- };
2228
- APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2229
- });
2230
-
2231
- // src/lib/app-home.ts
2232
- import { existsSync as existsSync3 } from "fs";
2233
- import { homedir as homedir5 } from "os";
2234
- import { join as join6, resolve as resolve2 } from "path";
2235
2286
  function homeDir(env = process.env) {
2236
- return env["HOME"] || env["USERPROFILE"] || homedir5();
2287
+ return env["HOME"] || env["USERPROFILE"] || homedir2();
2237
2288
  }
2238
2289
  function legacyStoreHome(env = process.env) {
2239
- return resolve2(join6(homeDir(env), ".hasna", "instructions"));
2290
+ return resolve2(join3(homeDir(env), ".hasna", "instructions"));
2240
2291
  }
2241
2292
  function resolverStoreHome(env = process.env) {
2242
- return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir5() });
2293
+ return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir2() });
2243
2294
  }
2244
2295
  function adoptResolverStoreHome(resolved, env = process.env) {
2245
2296
  const override = env.HASNA_CONFIG_HOME;
2246
2297
  if (typeof override === "string" && override.trim().length > 0)
2247
2298
  return true;
2248
- return existsSync3(join6(resolved, "instructions.db"));
2299
+ return existsSync3(join3(resolved, "instructions.db"));
2249
2300
  }
2250
2301
  function exactStoreHome(env = process.env) {
2251
2302
  const v = env[HASNA_CONFIGS_HOME_ENV];
@@ -2258,9 +2309,15 @@ function getConfigsStoreHome(env = process.env) {
2258
2309
  const resolved = resolverStoreHome(env);
2259
2310
  return adoptResolverStoreHome(resolved, env) ? resolve2(resolved) : legacyStoreHome(env);
2260
2311
  }
2261
- var HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
2312
+ var PATHS_RESOLVER_KIND_ENV2, PATHS_RESOLVER_APP_SLUG_RE2, HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
2262
2313
  var init_app_home = __esm(() => {
2263
- init_dist();
2314
+ PATHS_RESOLVER_KIND_ENV2 = {
2315
+ config: "HASNA_CONFIG_HOME",
2316
+ data: "HASNA_DATA_HOME",
2317
+ state: "HASNA_STATE_HOME",
2318
+ cache: "HASNA_CACHE_HOME"
2319
+ };
2320
+ PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2264
2321
  });
2265
2322
 
2266
2323
  // src/lib/raw-store-root.ts
@@ -2275,7 +2332,7 @@ var init_raw_store_root = __esm(() => {
2275
2332
  // src/db/database.ts
2276
2333
  import { Database } from "bun:sqlite";
2277
2334
  import { existsSync as existsSync5, mkdirSync, rmSync } from "fs";
2278
- import { join as join7 } from "path";
2335
+ import { join as join5 } from "path";
2279
2336
  import { randomUUID as randomUUID3 } from "crypto";
2280
2337
  function getDbPath() {
2281
2338
  if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
@@ -2283,7 +2340,7 @@ function getDbPath() {
2283
2340
  }
2284
2341
  const dir = getRawStoreRoot();
2285
2342
  mkdirSync(dir, { recursive: true });
2286
- return join7(dir, "instructions.db");
2343
+ return join5(dir, "instructions.db");
2287
2344
  }
2288
2345
  function uuid() {
2289
2346
  return randomUUID3();
@@ -2297,9 +2354,8 @@ function slugify(name) {
2297
2354
  function getDatabase(path) {
2298
2355
  if (_db)
2299
2356
  return _db;
2300
- assertNoLegacyStorageMode();
2301
- if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
2302
- throw new Error("instructions is using the HTTP API transport (HASNA_INSTRUCTIONS_API_URL set): this command is not wired to the API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
2357
+ if (!path && hasInstructionsEnvAuthorityIntent(process.env)) {
2358
+ throw new Error("instructions is using the hosted API transport (a HASNA_INSTRUCTIONS_* credential is configured): this command is not wired to the API yet. " + "Point this run at the local store (HASNA_INSTRUCTIONS_LOCAL=1 with no hosted credential) to use it against the local SQLite store.");
2303
2359
  }
2304
2360
  const dbPath = path || getDbPath();
2305
2361
  const db = new Database(dbPath);
@@ -2380,7 +2436,7 @@ function insertFeedback(input, db) {
2380
2436
  }
2381
2437
  var MIGRATIONS, _db = null;
2382
2438
  var init_database = __esm(() => {
2383
- init_retired_storage_mode();
2439
+ init_local_opt_in();
2384
2440
  init_raw_store_root();
2385
2441
  MIGRATIONS = [
2386
2442
  `
@@ -2761,9 +2817,9 @@ var init_template = __esm(() => {
2761
2817
  });
2762
2818
 
2763
2819
  // src/lib/machine.ts
2764
- import { arch as currentArch, homedir as homedir6, hostname as currentHostname, type as currentOsType } from "os";
2820
+ import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
2765
2821
  import { existsSync as existsSync6 } from "fs";
2766
- import { join as join8 } from "path";
2822
+ import { join as join6 } from "path";
2767
2823
  function normalizeOsFamily(os) {
2768
2824
  const value = (os ?? "").trim().toLowerCase();
2769
2825
  if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
@@ -2775,11 +2831,11 @@ function normalizeOsFamily(os) {
2775
2831
  return value || "unknown";
2776
2832
  }
2777
2833
  function detectMachineContext(overrides = {}) {
2778
- const homeDir2 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir6();
2834
+ const homeDir2 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
2779
2835
  const os = overrides.os ?? currentOsType();
2780
2836
  const osFamily = normalizeOsFamily(os);
2781
- const bunBinDir = overrides.bun_bin_dir ?? join8(homeDir2, ".bun", "bin");
2782
- const defaultBunPath = osFamily === "macos" && existsSync6(BREW_BUN_PATH) ? BREW_BUN_PATH : join8(bunBinDir, "bun");
2837
+ const bunBinDir = overrides.bun_bin_dir ?? join6(homeDir2, ".bun", "bin");
2838
+ const defaultBunPath = osFamily === "macos" && existsSync6(BREW_BUN_PATH) ? BREW_BUN_PATH : join6(bunBinDir, "bun");
2783
2839
  return {
2784
2840
  id: "current-machine",
2785
2841
  hostname: overrides.hostname ?? currentHostname(),
@@ -2789,10 +2845,10 @@ function detectMachineContext(overrides = {}) {
2789
2845
  created_at: "",
2790
2846
  os_family: osFamily,
2791
2847
  home_dir: homeDir2,
2792
- workspace_root: overrides.workspace_root ?? join8(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
2848
+ workspace_root: overrides.workspace_root ?? join6(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
2793
2849
  bun_bin_dir: bunBinDir,
2794
2850
  bun_path: overrides.bun_path ?? defaultBunPath,
2795
- path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join8("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2851
+ path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join6("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2796
2852
  };
2797
2853
  }
2798
2854
  function machineContextToVariables(machine) {
@@ -7420,6 +7476,114 @@ var init_session_render_contract = __esm(() => {
7420
7476
  ];
7421
7477
  });
7422
7478
 
7479
+ // src/lib/session-render-state.ts
7480
+ import { existsSync as existsSync7, readdirSync } from "fs";
7481
+ import { homedir as homedir4 } from "os";
7482
+ import { dirname, join as join7, resolve as resolve4 } from "path";
7483
+ import { homedir as pathsResolverHomedir3 } from "os";
7484
+ import { join as pathsResolverJoin3 } from "path";
7485
+ function pathsResolverAssertApp3(app) {
7486
+ if (typeof app !== "string" || app.length === 0) {
7487
+ throw new TypeError("paths: app must be a non-empty string");
7488
+ }
7489
+ if (!PATHS_RESOLVER_APP_SLUG_RE3.test(app)) {
7490
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
7491
+ }
7492
+ }
7493
+ function pathsResolverAssertKind3(kind) {
7494
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV3).includes(kind)) {
7495
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV3).join(", ")}`);
7496
+ }
7497
+ }
7498
+ function pathsResolverBaseDir3(kind, options) {
7499
+ pathsResolverAssertKind3(kind);
7500
+ const env = options.env ?? process.env;
7501
+ const override = env[PATHS_RESOLVER_KIND_ENV3[kind]];
7502
+ if (typeof override === "string" && override.length > 0)
7503
+ return override;
7504
+ const home = options.home ?? pathsResolverHomedir3();
7505
+ const platform = options.platform ?? process.platform;
7506
+ if (platform === "darwin") {
7507
+ switch (kind) {
7508
+ case "config":
7509
+ case "data":
7510
+ return pathsResolverJoin3(home, "Library", "Application Support", "Hasna");
7511
+ case "cache":
7512
+ return pathsResolverJoin3(home, "Library", "Caches", "Hasna");
7513
+ case "state":
7514
+ return pathsResolverJoin3(home, "Library", "Logs", "Hasna");
7515
+ }
7516
+ }
7517
+ switch (kind) {
7518
+ case "config":
7519
+ return pathsResolverJoin3(home, ".config", "hasna");
7520
+ case "data":
7521
+ return pathsResolverJoin3(home, ".local", "share", "hasna");
7522
+ case "state":
7523
+ return pathsResolverJoin3(home, ".local", "state", "hasna");
7524
+ case "cache":
7525
+ return pathsResolverJoin3(home, ".cache", "hasna");
7526
+ }
7527
+ }
7528
+ function pathsResolverResolve3(kind, options) {
7529
+ pathsResolverAssertApp3(options.app);
7530
+ const appSegment = options.internal === true ? pathsResolverJoin3("internal", options.app) : options.app;
7531
+ return pathsResolverJoin3(pathsResolverBaseDir3(kind, options), appSegment);
7532
+ }
7533
+ function stateDir(options) {
7534
+ return pathsResolverResolve3("state", options);
7535
+ }
7536
+ function homeDir2(env = process.env) {
7537
+ return env["HOME"] || env["USERPROFILE"] || homedir4();
7538
+ }
7539
+ function legacySnapshotDir(targetHome) {
7540
+ return resolve4(join7(targetHome, ".hasna", "session-render-snapshots"));
7541
+ }
7542
+ function resolverSnapshotDir(env = process.env) {
7543
+ const override = env.HASNA_STATE_HOME;
7544
+ if (typeof override === "string" && override.trim().length > 0 && !existsSync7(override)) {
7545
+ return stateDir({
7546
+ app: SESSION_RENDER_STATE_APP,
7547
+ env: { ...env, HASNA_STATE_HOME: undefined },
7548
+ home: homeDir2(env)
7549
+ });
7550
+ }
7551
+ return stateDir({ app: SESSION_RENDER_STATE_APP, env, home: homeDir2(env) });
7552
+ }
7553
+ function adoptResolverSnapshotDir(resolved, env = process.env) {
7554
+ const override = env.HASNA_STATE_HOME;
7555
+ if (typeof override === "string" && override.trim().length > 0 && existsSync7(override)) {
7556
+ return true;
7557
+ }
7558
+ if (!existsSync7(resolved))
7559
+ return false;
7560
+ return readdirSync(resolved).some((name) => name.endsWith(".json"));
7561
+ }
7562
+ function resolveSessionRenderSnapshotLocation(targetHome, env = process.env) {
7563
+ const resolved = resolverSnapshotDir(env);
7564
+ if (!adoptResolverSnapshotDir(resolved, env)) {
7565
+ return { dir: legacySnapshotDir(targetHome), workspaceRoot: targetHome, adopted: false };
7566
+ }
7567
+ const resolvedPath = resolve4(resolved);
7568
+ return { dir: resolvedPath, workspaceRoot: dirname(resolvedPath), adopted: true };
7569
+ }
7570
+ function getSessionRenderSnapshotDir(targetHome, env = process.env) {
7571
+ return resolveSessionRenderSnapshotLocation(targetHome, env).dir;
7572
+ }
7573
+ function sessionRenderSnapshotWorkspaceRoot(targetHome, env = process.env) {
7574
+ return resolveSessionRenderSnapshotLocation(targetHome, env).workspaceRoot;
7575
+ }
7576
+ var PATHS_RESOLVER_KIND_ENV3, PATHS_RESOLVER_APP_SLUG_RE3, SESSION_RENDER_STATE_APP = "instructions";
7577
+ var init_session_render_state = __esm(() => {
7578
+ PATHS_RESOLVER_KIND_ENV3 = {
7579
+ config: "HASNA_CONFIG_HOME",
7580
+ data: "HASNA_DATA_HOME",
7581
+ state: "HASNA_STATE_HOME",
7582
+ cache: "HASNA_CACHE_HOME"
7583
+ };
7584
+ PATHS_RESOLVER_APP_SLUG_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7585
+ });
7586
+
7423
7587
  // src/lib/project-context.ts
7424
7588
  import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
7425
7589
  import { execFileSync } from "child_process";
@@ -7427,7 +7591,7 @@ import { dlopen, FFIType } from "bun:ffi";
7427
7591
  import {
7428
7592
  closeSync,
7429
7593
  constants,
7430
- existsSync as existsSync7,
7594
+ existsSync as existsSync8,
7431
7595
  fstatSync,
7432
7596
  fsyncSync,
7433
7597
  lstatSync,
@@ -7440,7 +7604,7 @@ import {
7440
7604
  statSync,
7441
7605
  writeFileSync
7442
7606
  } from "fs";
7443
- import { basename, dirname, isAbsolute, join as join9, parse, relative, resolve as resolve4 } from "path";
7607
+ import { basename, dirname as dirname2, isAbsolute, join as join8, parse, relative, resolve as resolve5 } from "path";
7444
7608
  function managedObservationMaxBytes(relativePath) {
7445
7609
  return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
7446
7610
  }
@@ -7520,7 +7684,7 @@ function planProjectContext(input) {
7520
7684
  const inlineMarkerOverhead = nativeImports ? 0 : Buffer.byteLength(buildManagedBlock(bundle, "", `
7521
7685
  `), "utf8");
7522
7686
  const generated = buildCanonicalFragment(bundle, status, ageSeconds, PROJECT_CONTEXT_MAX_RENDERED_BYTES - Math.max(320, inlineMarkerOverhead), PROJECT_CONTEXT_MAX_APPROX_TOKENS - Math.max(80, Math.ceil(inlineMarkerOverhead / 4)));
7523
- const previousTargetContent = existsSync7(paths.target) ? readUtf8RegularFile(paths.target, workspaceRoot, managedObservationMaxBytes(relativePosix(workspaceRoot, paths.target))) : null;
7687
+ const previousTargetContent = existsSync8(paths.target) ? readUtf8RegularFile(paths.target, workspaceRoot, managedObservationMaxBytes(relativePosix(workspaceRoot, paths.target))) : null;
7524
7688
  const markerParse = parseManagedBlock(previousTargetContent ?? "", input.force === true);
7525
7689
  if (markerParse.block && markerParse.block.id !== bundle.project.id) {
7526
7690
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "managed block belongs to a different project");
@@ -7571,7 +7735,7 @@ function composeProjectContextSessionRender(input) {
7571
7735
  return null;
7572
7736
  const { runtime, workspace_root: workspaceRoot, observed_hashes: observedHashes } = guard;
7573
7737
  const paths = runtimePaths(workspaceRoot, runtime);
7574
- if (!existsSync7(paths.manifest))
7738
+ if (!existsSync8(paths.manifest))
7575
7739
  return null;
7576
7740
  assertCodewithTargetIsConsumed(workspaceRoot, runtime);
7577
7741
  const manifest = readProjectContextManifest(paths.manifest, workspaceRoot);
@@ -7600,7 +7764,7 @@ function composeProjectContextSessionRender(input) {
7600
7764
  }
7601
7765
  const fragment = readUtf8RegularFile(paths.fragment, workspaceRoot, PROJECT_CONTEXT_MAX_RENDERED_BYTES);
7602
7766
  scanGeneratedContent(fragment);
7603
- if (!existsSync7(paths.target)) {
7767
+ if (!existsSync8(paths.target)) {
7604
7768
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider target is missing while durable context is active");
7605
7769
  }
7606
7770
  const currentTarget = readUtf8RegularFile(paths.target, workspaceRoot, managedObservationMaxBytes(relativePosix(workspaceRoot, paths.target)));
@@ -7611,7 +7775,7 @@ function composeProjectContextSessionRender(input) {
7611
7775
  if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
7612
7776
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
7613
7777
  }
7614
- const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve4(file.path) === paths.target);
7778
+ const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve5(file.path) === paths.target);
7615
7779
  if (plannedIndexes.length !== 1) {
7616
7780
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
7617
7781
  }
@@ -7669,7 +7833,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
7669
7833
  verify();
7670
7834
  return action(null);
7671
7835
  }
7672
- const lockPath = resolve4(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7836
+ const lockPath = resolve5(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7673
7837
  const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
7674
7838
  try {
7675
7839
  verify();
@@ -7695,7 +7859,7 @@ function validateProjectContextSessionGuard(guard) {
7695
7859
  if (!isRecord(observed) || typeof observed.path !== "string") {
7696
7860
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
7697
7861
  }
7698
- const path = resolve4(observed.path);
7862
+ const path = resolve5(observed.path);
7699
7863
  if (!allowedPaths.has(path) || observedPaths.has(path)) {
7700
7864
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
7701
7865
  }
@@ -7717,7 +7881,7 @@ function validateProjectContextSessionGuard(guard) {
7717
7881
  function applyProjectContext(options) {
7718
7882
  const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
7719
7883
  const now3 = options.now ?? new Date;
7720
- const lockPath = resolve4(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7884
+ const lockPath = resolve5(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7721
7885
  const lock = options.dry_run ? null : acquireWorkspaceLock(workspaceRoot, lockPath, options.test_hooks?.after_lock_open, options.test_hooks?.before_stale_lock_remove, options.test_hooks?.process_start_identity);
7722
7886
  try {
7723
7887
  const resolved = resolveBundleForApply(options, workspaceRoot, now3);
@@ -7864,7 +8028,7 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
7864
8028
  if (!options.expected_project_id) {
7865
8029
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
7866
8030
  }
7867
- const cachePath = resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
8031
+ const cachePath = resolve5(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
7868
8032
  const cache = readProjectContextCache(cachePath, workspaceRoot);
7869
8033
  if (!cache)
7870
8034
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
@@ -8066,7 +8230,7 @@ function findLegacyCodewithWorkspaceSection(workspaceRoot, runtime, content, bun
8066
8230
  if (runtime !== "codewith" || !content)
8067
8231
  return null;
8068
8232
  const sessionManifestPath = runtimePaths(workspaceRoot, runtime).sessionManifest;
8069
- if (!existsSync7(sessionManifestPath))
8233
+ if (!existsSync8(sessionManifestPath))
8070
8234
  return null;
8071
8235
  const manifest = readSessionManifestRecord(sessionManifestPath, workspaceRoot);
8072
8236
  if (!manifest || manifest["schema"] !== SESSION_RENDER_SCHEMA) {
@@ -8117,7 +8281,7 @@ function assertRevisionOrdering(plan, force) {
8117
8281
  const manifest = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
8118
8282
  if (manifest) {
8119
8283
  const manifestHashHasRecoveryProof = manifest.projectContext.hash === plan.bundle.hash || metadataSnapshotMatchesManifest(plan, manifest);
8120
- const canonicalStateAlreadyInstalled = cache !== null && canonicalCacheHash === plan.bundle.hash && cache.project_id === plan.bundle.project.id && cache.revision === plan.bundle.revision && plan.marker !== null && plan.marker.id === plan.bundle.project.id && plan.marker.revision === plan.bundle.revision && plan.marker.hash === plan.bundle.hash && existsSync7(plan.fragment_path) && fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && manifestHashHasRecoveryProof;
8284
+ const canonicalStateAlreadyInstalled = cache !== null && canonicalCacheHash === plan.bundle.hash && cache.project_id === plan.bundle.project.id && cache.revision === plan.bundle.revision && plan.marker !== null && plan.marker.id === plan.bundle.project.id && plan.marker.revision === plan.bundle.revision && plan.marker.hash === plan.bundle.hash && existsSync8(plan.fragment_path) && fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && manifestHashHasRecoveryProof;
8121
8285
  observations.push({
8122
8286
  source: "manifest",
8123
8287
  id: manifest.projectContext.projectId,
@@ -8125,7 +8289,7 @@ function assertRevisionOrdering(plan, force) {
8125
8289
  hash: canonicalStateAlreadyInstalled && manifest.projectContext.projectId === plan.bundle.project.id && manifest.projectContext.revision === plan.bundle.revision ? plan.bundle.hash : normalizePersistedHash(manifest.projectContext.revision, manifest.projectContext.hash)
8126
8290
  });
8127
8291
  const fragmentEntry = manifest.files.find((file) => file.relativePath === PROJECT_CONTEXT_FRAGMENT_PATH);
8128
- if (fragmentEntry && existsSync7(plan.fragment_path)) {
8292
+ if (fragmentEntry && existsSync8(plan.fragment_path)) {
8129
8293
  const actual = currentFileHash(plan.fragment_path, plan.workspace_root);
8130
8294
  if (actual !== fragmentEntry.sha256 && !fragmentMatchesBundle(plan.fragment_path, plan.bundle, plan.workspace_root) && !force) {
8131
8295
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "canonical project-context fragment changed outside Instructions");
@@ -8220,9 +8384,9 @@ function buildManifest(plan, now3) {
8220
8384
  function buildSessionCompatibilityManifest(plan, now3) {
8221
8385
  const paths = runtimePaths(plan.workspace_root, plan.runtime);
8222
8386
  const tool = manifestTool(plan.runtime);
8223
- const targetHome = plan.runtime === "codewith" ? resolve4(plan.workspace_root, ".codewith") : plan.workspace_root;
8387
+ const targetHome = plan.runtime === "codewith" ? resolve5(plan.workspace_root, ".codewith") : plan.workspace_root;
8224
8388
  const targetRelativePath = sessionTargetRelativePath(plan.runtime);
8225
- const existing = existsSync7(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
8389
+ const existing = existsSync8(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
8226
8390
  schema: SESSION_RENDER_SCHEMA,
8227
8391
  tool,
8228
8392
  adapterMode: plan.native_imports ? "native-imports" : "flattened-markdown",
@@ -8242,7 +8406,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
8242
8406
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
8243
8407
  }
8244
8408
  const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
8245
- if (existingTargetHome !== null && resolve4(existingTargetHome) !== targetHome) {
8409
+ if (existingTargetHome !== null && resolve5(existingTargetHome) !== targetHome) {
8246
8410
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
8247
8411
  }
8248
8412
  const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
@@ -8528,9 +8692,9 @@ function writeMetadataSnapshot(plan, now3) {
8528
8692
  const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
8529
8693
  if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
8530
8694
  return null;
8531
- const snapshotDir = resolve4(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8695
+ const snapshotDir = resolve5(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8532
8696
  ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8533
- const snapshotPath = resolve4(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8697
+ const snapshotPath = resolve5(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8534
8698
  const snapshot = {
8535
8699
  schema: "hasna.configs.session-render-snapshot/v1",
8536
8700
  kind: "project-context-metadata",
@@ -8546,9 +8710,9 @@ function writeMetadataSnapshot(plan, now3) {
8546
8710
  return snapshotPath;
8547
8711
  }
8548
8712
  function metadataSnapshotMatchesManifest(plan, manifest) {
8549
- const snapshotDir = resolve4(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8550
- const snapshotPath = resolve4(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8551
- if (!existsSync7(snapshotPath))
8713
+ const snapshotDir = resolve5(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8714
+ const snapshotPath = resolve5(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8715
+ if (!existsSync8(snapshotPath))
8552
8716
  return false;
8553
8717
  const record = readJsonRecord(snapshotPath, plan.workspace_root);
8554
8718
  const result = projectContextMetadataSnapshotSchema.safeParse(record);
@@ -8596,10 +8760,11 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
8596
8760
  sha256: nextHash
8597
8761
  };
8598
8762
  });
8599
- const snapshotDir = resolve4(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
8600
- ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8763
+ const snapshotWorkspaceRoot = sessionRenderSnapshotWorkspaceRoot(plan.workspace_root);
8764
+ const snapshotDir = getSessionRenderSnapshotDir(plan.workspace_root);
8765
+ ensureSafeDirectory(snapshotDir, snapshotWorkspaceRoot, 448);
8601
8766
  const timestamp = now3.toISOString().replace(/[:.]/g, "-");
8602
- const snapshotPath = resolve4(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8767
+ const snapshotPath = resolve5(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8603
8768
  const snapshot = {
8604
8769
  schema: "hasna.configs.session-render-snapshot/v2",
8605
8770
  createdAt: now3.toISOString(),
@@ -8613,11 +8778,11 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
8613
8778
  afterFiles
8614
8779
  };
8615
8780
  atomicWriteFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}
8616
- `, plan.workspace_root, 384, null);
8781
+ `, snapshotWorkspaceRoot, 384, null);
8617
8782
  return snapshotPath;
8618
8783
  }
8619
8784
  function readProjectContextManifest(path, workspaceRoot) {
8620
- if (!existsSync7(path))
8785
+ if (!existsSync8(path))
8621
8786
  return null;
8622
8787
  const record = readJsonRecord(path, workspaceRoot);
8623
8788
  const result = storedManifestObservationSchema.safeParse(record);
@@ -8632,7 +8797,7 @@ function readProjectContextManifest(path, workspaceRoot) {
8632
8797
  };
8633
8798
  }
8634
8799
  function readProjectContextCache(path, workspaceRoot) {
8635
- if (!existsSync7(path))
8800
+ if (!existsSync8(path))
8636
8801
  return null;
8637
8802
  const record = readJsonRecord(path, workspaceRoot);
8638
8803
  const result = projectContextCacheSchema.safeParse(record);
@@ -8664,7 +8829,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
8664
8829
  }
8665
8830
  }
8666
8831
  function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
8667
- const dir = resolve4(path, "..");
8832
+ const dir = resolve5(path, "..");
8668
8833
  ensureSafeDirectory(dir, workspaceRoot, 448);
8669
8834
  assertNoSymlinkSegments(workspaceRoot, path);
8670
8835
  const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
@@ -8681,7 +8846,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
8681
8846
  const previous = anchoredFileObservation(directory, targetName);
8682
8847
  const previousMode = previous?.mode ?? defaultMode;
8683
8848
  const tempName = `.project-context-${randomUUID5()}.tmp`;
8684
- const tempPath = join9(dir, tempName);
8849
+ const tempPath = join8(dir, tempName);
8685
8850
  let fd = null;
8686
8851
  let preserveTemp = false;
8687
8852
  let directoryChanged = false;
@@ -8810,9 +8975,9 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
8810
8975
  if (currentHash !== null) {
8811
8976
  throw new ProjectContextHashRace(`managed path appeared before portable creation: ${relativePosix(workspaceRoot, path)}`);
8812
8977
  }
8813
- const dir = dirname(path);
8978
+ const dir = dirname2(path);
8814
8979
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8815
- const tempPath = join9(dir, `.project-context-${randomUUID5()}.tmp`);
8980
+ const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
8816
8981
  let fd = null;
8817
8982
  let tempIdentity = null;
8818
8983
  try {
@@ -8867,9 +9032,9 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
8867
9032
  if (current.isSymbolicLink() || !current.isFile()) {
8868
9033
  throw new ProjectContextHashRace(`managed path is not a regular file: ${relativePosix(workspaceRoot, path)}`);
8869
9034
  }
8870
- const dir = dirname(path);
9035
+ const dir = dirname2(path);
8871
9036
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8872
- const tempPath = join9(dir, `.project-context-${randomUUID5()}.tmp`);
9037
+ const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
8873
9038
  const desiredHash = sha2562(content);
8874
9039
  let fd = null;
8875
9040
  let tempIdentity = null;
@@ -8926,7 +9091,7 @@ function portablePreparedHash(tempPath, path, workspaceRoot, maxObservedBytes, s
8926
9091
  function portableFileHash(path, workspaceRoot, maxObservedBytes) {
8927
9092
  if (maxObservedBytes === undefined)
8928
9093
  return currentFileHash(path, workspaceRoot);
8929
- if (!existsSync7(path))
9094
+ if (!existsSync8(path))
8930
9095
  return null;
8931
9096
  assertNoSymlinkSegments(workspaceRoot, path);
8932
9097
  const stat = lstatSync(path);
@@ -8938,13 +9103,13 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
8938
9103
  return createHash2("sha256").update(readFileSync(path)).digest("hex");
8939
9104
  }
8940
9105
  function writeProjectContextCoordinatedFile(input) {
8941
- atomicWriteFile(resolve4(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
9106
+ atomicWriteFile(resolve5(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
8942
9107
  }
8943
9108
  function removeProjectContextCoordinatedFile(input) {
8944
9109
  const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
8945
- const path = resolve4(input.path);
9110
+ const path = resolve5(input.path);
8946
9111
  assertNoSymlinkSegments(workspaceRoot, path);
8947
- const dir = dirname(path);
9112
+ const dir = dirname2(path);
8948
9113
  const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
8949
9114
  if (!anchoredOps) {
8950
9115
  if (!input.allow_portable_removal) {
@@ -8968,7 +9133,7 @@ function removeProjectContextCoordinatedFile(input) {
8968
9133
  throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
8969
9134
  }
8970
9135
  displaced = true;
8971
- input.test_hooks?.after_displace?.(join9(dir, displacedName));
9136
+ input.test_hooks?.after_displace?.(join8(dir, displacedName));
8972
9137
  const moved = anchoredFileObservation(directory, displacedName);
8973
9138
  if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
8974
9139
  throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
@@ -9012,9 +9177,9 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
9012
9177
  if (observed.isSymbolicLink() || !observed.isFile()) {
9013
9178
  throw new ProjectContextHashRace(`managed path is not a regular file: ${relativePosix(workspaceRoot, path)}`);
9014
9179
  }
9015
- const dir = dirname(path);
9180
+ const dir = dirname2(path);
9016
9181
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
9017
- const displacedPath = join9(dir, `.project-context-delete-${randomUUID5()}.tmp`);
9182
+ const displacedPath = join8(dir, `.project-context-delete-${randomUUID5()}.tmp`);
9018
9183
  let displaced = false;
9019
9184
  try {
9020
9185
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -9025,7 +9190,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
9025
9190
  displaced = true;
9026
9191
  afterDisplace?.(displacedPath);
9027
9192
  const moved = lstatSync(displacedPath);
9028
- if (moved.isSymbolicLink() || !moved.isFile() || moved.dev !== observed.dev || moved.ino !== observed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash || existsSync7(path)) {
9193
+ if (moved.isSymbolicLink() || !moved.isFile() || moved.dev !== observed.dev || moved.ino !== observed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash || existsSync8(path)) {
9029
9194
  throw new ProjectContextHashRace(`managed path changed during portable deletion: ${relativePosix(workspaceRoot, path)}`);
9030
9195
  }
9031
9196
  rmSync2(displacedPath);
@@ -9051,7 +9216,7 @@ function restorePortableDisplacedFile(displacedPath, path, workspaceRoot, expect
9051
9216
  if (displaced.isSymbolicLink() || installed.isSymbolicLink() || !displaced.isFile() || !installed.isFile() || displaced.dev !== expected.dev || displaced.ino !== expected.ino || displaced.dev !== installed.dev || displaced.ino !== installed.ino || portableFileHash(displacedPath, workspaceRoot, maxObservedBytes) !== expectedHash || portableFileHash(path, workspaceRoot, maxObservedBytes) !== expectedHash)
9052
9217
  return false;
9053
9218
  rmSync2(displacedPath);
9054
- fsyncDirectory(dirname(path));
9219
+ fsyncDirectory(dirname2(path));
9055
9220
  return true;
9056
9221
  } catch {
9057
9222
  return false;
@@ -9085,7 +9250,7 @@ function anchoredOpenExclusive(directory, name, mode) {
9085
9250
  const requestedMode = mode & 4095;
9086
9251
  let fd;
9087
9252
  try {
9088
- fd = openSync(join9(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
9253
+ fd = openSync(join8(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
9089
9254
  } catch {
9090
9255
  throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
9091
9256
  }
@@ -9128,7 +9293,7 @@ function anchoredFileObservation(directory, name) {
9128
9293
  const stat = fstatSync(fd);
9129
9294
  if (!stat.isFile())
9130
9295
  throw new ProjectContextHashRace("managed output is not a regular file");
9131
- const relativePath = relativePosix(directory.workspaceRoot, join9(directory.path, name));
9296
+ const relativePath = relativePosix(directory.workspaceRoot, join8(directory.path, name));
9132
9297
  const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
9133
9298
  if (maxBytes !== null && stat.size > maxBytes) {
9134
9299
  throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
@@ -9154,7 +9319,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
9154
9319
  return observed;
9155
9320
  }
9156
9321
  function captureManagedDirectoryIdentity(path, workspaceRoot) {
9157
- assertNoSymlinkSegments(workspaceRoot, join9(path, ".project-context-directory-guard"));
9322
+ assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
9158
9323
  let stat;
9159
9324
  try {
9160
9325
  stat = lstatSync(path);
@@ -9167,7 +9332,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
9167
9332
  return { dev: stat.dev, ino: stat.ino };
9168
9333
  }
9169
9334
  function assertManagedDirectoryStable(path, workspaceRoot, expected) {
9170
- assertNoSymlinkSegments(workspaceRoot, join9(path, ".project-context-directory-guard"));
9335
+ assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
9171
9336
  let current;
9172
9337
  try {
9173
9338
  current = lstatSync(path);
@@ -9300,10 +9465,10 @@ function resolveAnchoredFsOps() {
9300
9465
  return null;
9301
9466
  }
9302
9467
  function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
9303
- const lockDirectory = resolve4(lockPath, "..");
9468
+ const lockDirectory = resolve5(lockPath, "..");
9304
9469
  ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
9305
9470
  assertNoSymlinkSegments(workspaceRoot, lockPath);
9306
- const tempPath = join9(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9471
+ const tempPath = join8(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9307
9472
  let fd = null;
9308
9473
  let openedIdentity = null;
9309
9474
  let openedContentHash = null;
@@ -9337,7 +9502,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
9337
9502
  linked = true;
9338
9503
  }
9339
9504
  fsyncDirectory(lockDirectory);
9340
- if (existsSync7(tempPath)) {
9505
+ if (existsSync8(tempPath)) {
9341
9506
  rmSync2(tempPath);
9342
9507
  fsyncDirectory(lockDirectory);
9343
9508
  }
@@ -9352,7 +9517,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
9352
9517
  if (linked && openedIdentity && openedContentHash) {
9353
9518
  removeOwnedLockByInode(lockPath, openedIdentity, openedContentHash);
9354
9519
  }
9355
- if (!preserveTemp && existsSync7(tempPath)) {
9520
+ if (!preserveTemp && existsSync8(tempPath)) {
9356
9521
  try {
9357
9522
  rmSync2(tempPath);
9358
9523
  } catch {}
@@ -9367,7 +9532,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
9367
9532
  }
9368
9533
  function removeOwnedLockByInode(lockPath, identity, expectedHash) {
9369
9534
  try {
9370
- if (!existsSync7(lockPath))
9535
+ if (!existsSync8(lockPath))
9371
9536
  return;
9372
9537
  const current = lstatSync(lockPath);
9373
9538
  if (current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino)
@@ -9375,7 +9540,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
9375
9540
  if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
9376
9541
  return;
9377
9542
  rmSync2(lockPath);
9378
- fsyncDirectory(resolve4(lockPath, ".."));
9543
+ fsyncDirectory(resolve5(lockPath, ".."));
9379
9544
  } catch {}
9380
9545
  }
9381
9546
  function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
@@ -9446,7 +9611,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
9446
9611
  const candidateInstalled = !current.isSymbolicLink() && current.dev === candidateIdentity.dev && current.ino === candidateIdentity.ino && currentFileHash(lockPath, workspaceRoot) === candidateHash;
9447
9612
  const staleDisplaced = !displaced.isSymbolicLink() && displaced.dev === stale.identity.dev && displaced.ino === stale.identity.ino && currentFileHash(candidatePath, workspaceRoot) === stale.contentHash;
9448
9613
  if (!candidateInstalled || !staleDisplaced) {
9449
- if (candidateInstalled && existsSync7(candidatePath)) {
9614
+ if (candidateInstalled && existsSync8(candidatePath)) {
9450
9615
  atomicExchangePaths(candidatePath, lockPath);
9451
9616
  exchanged = false;
9452
9617
  return false;
@@ -9454,13 +9619,13 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
9454
9619
  throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
9455
9620
  }
9456
9621
  rmSync2(candidatePath);
9457
- fsyncDirectory(resolve4(lockPath, ".."));
9622
+ fsyncDirectory(resolve5(lockPath, ".."));
9458
9623
  exchanged = false;
9459
9624
  return true;
9460
9625
  } catch (error) {
9461
9626
  if (exchanged) {
9462
9627
  try {
9463
- if (currentFileHash(lockPath, workspaceRoot) === candidateHash && existsSync7(candidatePath)) {
9628
+ if (currentFileHash(lockPath, workspaceRoot) === candidateHash && existsSync8(candidatePath)) {
9464
9629
  atomicExchangePaths(candidatePath, lockPath);
9465
9630
  exchanged = false;
9466
9631
  }
@@ -9473,7 +9638,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
9473
9638
  }
9474
9639
  }
9475
9640
  function assertWorkspaceLockHeld(lockPath, lock, workspaceRoot) {
9476
- if (!existsSync7(lockPath)) {
9641
+ if (!existsSync8(lockPath)) {
9477
9642
  throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace project-context lock changed during render");
9478
9643
  }
9479
9644
  const current = lstatSync(lockPath);
@@ -9533,8 +9698,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9533
9698
  }
9534
9699
  return;
9535
9700
  }
9536
- const lockDirectory = resolve4(lockPath, "..");
9537
- const releasePath = join9(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9701
+ const lockDirectory = resolve5(lockPath, "..");
9702
+ const releasePath = join8(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9538
9703
  let releaseFd = null;
9539
9704
  let releaseIdentity = null;
9540
9705
  let releaseHash = null;
@@ -9563,7 +9728,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9563
9728
  const releaseInstalled = !installed.isSymbolicLink() && installed.dev === releaseIdentity.dev && installed.ino === releaseIdentity.ino && currentFileHash(lockPath, workspaceRoot) === releaseHash;
9564
9729
  const ownedDisplaced = !displaced.isSymbolicLink() && displaced.dev === lock.identity.dev && displaced.ino === lock.identity.ino && currentFileHash(releasePath, workspaceRoot) === lock.contentHash;
9565
9730
  if (!releaseInstalled || !ownedDisplaced) {
9566
- if (releaseInstalled && existsSync7(releasePath)) {
9731
+ if (releaseInstalled && existsSync8(releasePath)) {
9567
9732
  atomicExchangePaths(releasePath, lockPath);
9568
9733
  exchanged = false;
9569
9734
  }
@@ -9576,7 +9741,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9576
9741
  } catch {
9577
9742
  if (exchanged) {
9578
9743
  try {
9579
- if (releaseHash && currentFileHash(lockPath, workspaceRoot) === releaseHash && existsSync7(releasePath)) {
9744
+ if (releaseHash && currentFileHash(lockPath, workspaceRoot) === releaseHash && existsSync8(releasePath)) {
9580
9745
  atomicExchangePaths(releasePath, lockPath);
9581
9746
  exchanged = false;
9582
9747
  }
@@ -9588,7 +9753,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9588
9753
  closeSync(releaseFd);
9589
9754
  } catch {}
9590
9755
  }
9591
- if (!exchanged && existsSync7(releasePath)) {
9756
+ if (!exchanged && existsSync8(releasePath)) {
9592
9757
  try {
9593
9758
  rmSync2(releasePath);
9594
9759
  } catch {}
@@ -9614,15 +9779,15 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9614
9779
  const segments = rel.split(/[\\/]+/).filter(Boolean);
9615
9780
  let current = workspaceRoot;
9616
9781
  for (const segment of segments) {
9617
- current = join9(current, segment);
9618
- if (existsSync7(current)) {
9782
+ current = join8(current, segment);
9783
+ if (existsSync8(current)) {
9619
9784
  if (lstatSync(current).isSymbolicLink())
9620
9785
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
9621
9786
  if (!statSync(current).isDirectory())
9622
9787
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
9623
9788
  } else {
9624
9789
  mkdirSync2(current, { mode });
9625
- fsyncDirectory(resolve4(current, ".."));
9790
+ fsyncDirectory(resolve5(current, ".."));
9626
9791
  }
9627
9792
  }
9628
9793
  }
@@ -9678,11 +9843,11 @@ function scanGeneratedContent(content) {
9678
9843
  function runtimePaths(workspaceRoot, runtime) {
9679
9844
  const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
9680
9845
  return {
9681
- target: resolve4(workspaceRoot, ...relativeTarget.split("/")),
9682
- fragment: resolve4(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9683
- manifest: resolve4(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9684
- cache: resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9685
- sessionManifest: runtime === "codewith" ? resolve4(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve4(workspaceRoot, ".hasna", "session-render-manifest.json")
9846
+ target: resolve5(workspaceRoot, ...relativeTarget.split("/")),
9847
+ fragment: resolve5(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9848
+ manifest: resolve5(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9849
+ cache: resolve5(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9850
+ sessionManifest: runtime === "codewith" ? resolve5(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve5(workspaceRoot, ".hasna", "session-render-manifest.json")
9686
9851
  };
9687
9852
  }
9688
9853
  function projectContextSessionGuardPaths(paths, runtime) {
@@ -9692,7 +9857,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
9692
9857
  paths.fragment,
9693
9858
  paths.target,
9694
9859
  paths.sessionManifest,
9695
- ...runtime === "codewith" ? [resolve4(paths.target, "..", "CODEWITH.override.md")] : []
9860
+ ...runtime === "codewith" ? [resolve5(paths.target, "..", "CODEWITH.override.md")] : []
9696
9861
  ];
9697
9862
  }
9698
9863
  function sessionTargetRelativePath(runtime) {
@@ -9712,27 +9877,27 @@ function projectContextRuntimeForSessionTool(tool) {
9712
9877
  return null;
9713
9878
  }
9714
9879
  function projectContextWorkspaceForSession(input, runtime) {
9715
- const targetHome = resolve4(input.target_home);
9880
+ const targetHome = resolve5(input.target_home);
9716
9881
  if (runtime === "codewith") {
9717
- const workspaceRoot = basename(targetHome) === ".codewith" ? dirname(targetHome) : null;
9882
+ const workspaceRoot = basename(targetHome) === ".codewith" ? dirname2(targetHome) : null;
9718
9883
  if (!workspaceRoot)
9719
9884
  return null;
9720
- if (input.project_root && resolve4(input.project_root) !== workspaceRoot) {
9885
+ if (input.project_root && resolve5(input.project_root) !== workspaceRoot) {
9721
9886
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
9722
9887
  }
9723
- if (!existsSync7(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
9888
+ if (!existsSync8(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
9724
9889
  return null;
9725
9890
  return assertSafeWorkspaceRoot(workspaceRoot);
9726
9891
  }
9727
- if (!existsSync7(targetHome) || !lstatSync(targetHome).isDirectory())
9892
+ if (!existsSync8(targetHome) || !lstatSync(targetHome).isDirectory())
9728
9893
  return null;
9729
9894
  return assertSafeWorkspaceRoot(targetHome);
9730
9895
  }
9731
9896
  function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9732
9897
  if (runtime !== "codewith")
9733
9898
  return;
9734
- const override = resolve4(workspaceRoot, ".codewith", "CODEWITH.override.md");
9735
- if (!existsSync7(override))
9899
+ const override = resolve5(workspaceRoot, ".codewith", "CODEWITH.override.md");
9900
+ if (!existsSync8(override))
9736
9901
  return;
9737
9902
  assertNoSymlinkSegments(workspaceRoot, override);
9738
9903
  if (!lstatSync(override).isFile())
@@ -9742,10 +9907,10 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9742
9907
  function assertSafeWorkspaceRoot(path) {
9743
9908
  if (!isAbsolute(path))
9744
9909
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
9745
- const normalized = resolve4(path);
9910
+ const normalized = resolve5(path);
9746
9911
  if (normalized === parse(normalized).root)
9747
9912
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
9748
- if (!existsSync7(normalized) || !lstatSync(normalized).isDirectory())
9913
+ if (!existsSync8(normalized) || !lstatSync(normalized).isDirectory())
9749
9914
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be an existing directory");
9750
9915
  assertNoSymlinkAncestors(normalized);
9751
9916
  if (lstatSync(normalized).isSymbolicLink())
@@ -9759,18 +9924,18 @@ function assertNoSymlinkSegments(root, target) {
9759
9924
  }
9760
9925
  let current = root;
9761
9926
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
9762
- current = join9(current, segment);
9763
- if (existsSync7(current) && lstatSync(current).isSymbolicLink()) {
9927
+ current = join8(current, segment);
9928
+ if (existsSync8(current) && lstatSync(current).isSymbolicLink()) {
9764
9929
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
9765
9930
  }
9766
9931
  }
9767
9932
  }
9768
9933
  function assertNoSymlinkAncestors(path) {
9769
- const normalized = resolve4(path);
9934
+ const normalized = resolve5(path);
9770
9935
  let current = parse(normalized).root;
9771
9936
  for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
9772
- current = join9(current, segment);
9773
- if (!existsSync7(current))
9937
+ current = join8(current, segment);
9938
+ if (!existsSync8(current))
9774
9939
  return;
9775
9940
  if (lstatSync(current).isSymbolicLink())
9776
9941
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `workspace ancestor is a symlink: ${current}`);
@@ -9786,7 +9951,7 @@ function readUtf8RegularFile(path, workspaceRoot, maxBytes = FOREIGN_INPUT_MAX_B
9786
9951
  return readFileSync(path, "utf8");
9787
9952
  }
9788
9953
  function currentFileHash(path, workspaceRoot) {
9789
- if (!existsSync7(path))
9954
+ if (!existsSync8(path))
9790
9955
  return null;
9791
9956
  const relativePath = relativePosix(workspaceRoot, path);
9792
9957
  return sha2562(readUtf8RegularFile(path, workspaceRoot, managedObservationMaxBytes(relativePath)));
@@ -9805,10 +9970,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
9805
9970
  }
9806
9971
  function durableSourcePath(path, workspaceRoot) {
9807
9972
  if (!path || path.startsWith("/dev/fd/"))
9808
- return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9809
- const normalized = isAbsolute(path) ? resolve4(path) : resolve4(workspaceRoot, path);
9973
+ return resolve5(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9974
+ const normalized = isAbsolute(path) ? resolve5(path) : resolve5(workspaceRoot, path);
9810
9975
  if (normalized.startsWith("/dev/fd/"))
9811
- return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9976
+ return resolve5(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9812
9977
  return normalized;
9813
9978
  }
9814
9979
  function compareRevisions(incoming, previous) {
@@ -9975,11 +10140,12 @@ function sha2562(content) {
9975
10140
  function isRecord(value) {
9976
10141
  return !!value && typeof value === "object" && !Array.isArray(value);
9977
10142
  }
9978
- var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
10143
+ var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/projects/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/projects/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/projects/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/projects/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
9979
10144
  var init_project_context = __esm(() => {
9980
10145
  init_zod();
9981
10146
  init_redact();
9982
10147
  init_session_render_contract();
10148
+ init_session_render_state();
9983
10149
  PROJECT_CONTEXT_SUPPORTED_SCHEMAS = [PROJECT_CONTEXT_SCHEMA, PROJECT_CONTEXT_SCHEMA_V2];
9984
10150
  projectContextSchema = exports_external.enum(PROJECT_CONTEXT_SUPPORTED_SCHEMAS);
9985
10151
  PROJECT_CONTEXT_MAX_INPUT_BYTES = 8 * 1024;
@@ -10368,7 +10534,7 @@ function compareProviderVersions(left, right) {
10368
10534
 
10369
10535
  // src/lib/asset-plan.ts
10370
10536
  import { createHash as createHash3 } from "crypto";
10371
- import { isAbsolute as isAbsolute2, posix, resolve as resolve5 } from "path";
10537
+ import { isAbsolute as isAbsolute2, posix, resolve as resolve6 } from "path";
10372
10538
  function assetCapability(provider, surface, kind, support, strategies, note, providerVersionRange = "*") {
10373
10539
  return Object.freeze({
10374
10540
  schema: ASSET_CAPABILITY_SCHEMA,
@@ -10575,8 +10741,8 @@ function resolveAssetDestination(item, roots) {
10575
10741
  if (!isAbsolute2(root))
10576
10742
  throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
10577
10743
  const relativePath = safeRelativePath(item.destination.relativePath);
10578
- const target = resolve5(root, ...relativePath.split("/"));
10579
- const normalizedRoot = resolve5(root);
10744
+ const target = resolve6(root, ...relativePath.split("/"));
10745
+ const normalizedRoot = resolve6(root);
10580
10746
  if (target === normalizedRoot)
10581
10747
  throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
10582
10748
  if (!target.startsWith(`${normalizedRoot}/`))
@@ -10756,13 +10922,13 @@ var init_asset_plan = __esm(() => {
10756
10922
  // src/lib/cursor-authority.ts
10757
10923
  import { createHash as createHash4 } from "crypto";
10758
10924
  import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
10759
- import { homedir as homedir7 } from "os";
10760
- import { join as join10, resolve as resolve6 } from "path";
10925
+ import { homedir as homedir5 } from "os";
10926
+ import { join as join9, resolve as resolve7 } from "path";
10761
10927
  function sha2564(content) {
10762
10928
  return createHash4("sha256").update(content).digest("hex");
10763
10929
  }
10764
- function homeDir2() {
10765
- return process.env["HOME"] || homedir7();
10930
+ function homeDir3() {
10931
+ return process.env["HOME"] || homedir5();
10766
10932
  }
10767
10933
  function markerPayload(content, markerLine, markerIndex) {
10768
10934
  const index = markerIndex ?? content.indexOf(markerLine);
@@ -10778,12 +10944,12 @@ function baseObservation(path) {
10778
10944
  };
10779
10945
  }
10780
10946
  function observeCursorGlobalAuthority(options = {}) {
10781
- const authorityPath = resolve6(join10(options.home ?? homeDir2(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10947
+ const authorityPath = resolve7(join9(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10782
10948
  const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
10783
10949
  return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
10784
10950
  }
10785
10951
  function observeCursorGlobalAuthorityAtPath(authorityPath) {
10786
- return observeCursorGlobalAuthorityPath(resolve6(authorityPath), (path) => readFileSync2(path, "utf8"));
10952
+ return observeCursorGlobalAuthorityPath(resolve7(authorityPath), (path) => readFileSync2(path, "utf8"));
10787
10953
  }
10788
10954
  function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10789
10955
  const base = baseObservation(authorityPath);
@@ -10928,7 +11094,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10928
11094
  };
10929
11095
  }
10930
11096
  function isCursorGlobalAuthorityPath(path) {
10931
- return resolve6(path) === resolve6(join10(homeDir2(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
11097
+ return resolve7(path) === resolve7(join9(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10932
11098
  }
10933
11099
  function stampCursorGlobalAuthorityMarker(content) {
10934
11100
  const existing = content.match(CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN);
@@ -10984,16 +11150,16 @@ var init_cursor_authority = __esm(() => {
10984
11150
  // src/lib/session-authority.ts
10985
11151
  import { createHash as createHash5 } from "crypto";
10986
11152
  import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
10987
- import { homedir as homedir8 } from "os";
10988
- import { join as join11, resolve as resolve7 } from "path";
11153
+ import { homedir as homedir6 } from "os";
11154
+ import { join as join10, resolve as resolve8 } from "path";
10989
11155
  function sha2565(content) {
10990
11156
  return createHash5("sha256").update(content).digest("hex");
10991
11157
  }
10992
11158
  function configHomeDir() {
10993
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
11159
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
10994
11160
  }
10995
11161
  function normalizeOwnedTargetPath(p) {
10996
- const expanded = p.startsWith("~/") ? resolve7(configHomeDir(), p.slice(2)) : resolve7(p);
11162
+ const expanded = p.startsWith("~/") ? resolve8(configHomeDir(), p.slice(2)) : resolve8(p);
10997
11163
  try {
10998
11164
  return realpathSync(expanded);
10999
11165
  } catch {
@@ -11001,7 +11167,7 @@ function normalizeOwnedTargetPath(p) {
11001
11167
  }
11002
11168
  }
11003
11169
  function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
11004
- const authorityPath = resolve7(join11(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
11170
+ const authorityPath = resolve8(join10(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
11005
11171
  let stat;
11006
11172
  try {
11007
11173
  stat = lstatSync3(authorityPath);
@@ -11090,9 +11256,9 @@ var init_session_authority = __esm(() => {
11090
11256
 
11091
11257
  // src/lib/session-render.ts
11092
11258
  import { createHash as createHash6 } from "crypto";
11093
- import { existsSync as existsSync9, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
11094
- import { homedir as homedir9 } from "os";
11095
- import { basename as basename4, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute3, join as join12, parse as parse2, posix as posix2, relative as relative2, resolve as resolve8 } from "path";
11259
+ import { existsSync as existsSync10, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
11260
+ import { homedir as homedir7 } from "os";
11261
+ import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join11, parse as parse2, posix as posix2, relative as relative2, resolve as resolve9 } from "path";
11096
11262
  function normalizeSessionInstructionLayer(value) {
11097
11263
  if (value === "provider")
11098
11264
  return "tool";
@@ -11178,13 +11344,13 @@ function yamlQuote2(value) {
11178
11344
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
11179
11345
  }
11180
11346
  function defaultTargetHome(tool, profile, sessionId) {
11181
- const home = process.env["HOME"] || homedir9();
11182
- return join12(home, ".hasna", "accounts", "profiles", tool, slug(profile));
11347
+ const home = process.env["HOME"] || homedir7();
11348
+ return join11(home, ".hasna", "accounts", "profiles", tool, slug(profile));
11183
11349
  }
11184
11350
  function joinTarget(targetHome, relativePath) {
11185
11351
  const safeTargetHome = assertSafeTargetRoot(targetHome);
11186
11352
  const safeRelativePath2 = assertSafeRelativePath(relativePath);
11187
- return join12(safeTargetHome, ...safeRelativePath2.split("/"));
11353
+ return join11(safeTargetHome, ...safeRelativePath2.split("/"));
11188
11354
  }
11189
11355
  function makeFile(targetHome, relativePath, role, content, sourceIds) {
11190
11356
  const safeTargetHome = assertSafeTargetRoot(targetHome);
@@ -11753,7 +11919,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
11753
11919
  ...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
11754
11920
  ]);
11755
11921
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
11756
- const selectedConfig = existsSync9(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11922
+ const selectedConfig = existsSync10(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11757
11923
  const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
11758
11924
  const config = {
11759
11925
  ...selectedConfig,
@@ -11942,7 +12108,7 @@ function adapterFor(input) {
11942
12108
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
11943
12109
  }
11944
12110
  function getHomeDir() {
11945
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir9();
12111
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
11946
12112
  }
11947
12113
  function cleanSessionPathInput(path) {
11948
12114
  const trimmed = path.trim();
@@ -11957,16 +12123,16 @@ function resolveSessionPath(path) {
11957
12123
  throw new Error("Session render path cannot be empty.");
11958
12124
  const home = getHomeDir();
11959
12125
  if (cleaned === "~")
11960
- return resolve8(home);
12126
+ return resolve9(home);
11961
12127
  if (cleaned.startsWith("~/"))
11962
- return resolve8(home, cleaned.slice(2));
12128
+ return resolve9(home, cleaned.slice(2));
11963
12129
  if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
11964
- return resolve8(home);
12130
+ return resolve9(home);
11965
12131
  if (cleaned.startsWith("{{HOME}}/"))
11966
- return resolve8(home, cleaned.slice("{{HOME}}/".length));
12132
+ return resolve9(home, cleaned.slice("{{HOME}}/".length));
11967
12133
  if (cleaned.startsWith("${HOME}/"))
11968
- return resolve8(home, cleaned.slice("${HOME}/".length));
11969
- return resolve8(cleaned);
12134
+ return resolve9(home, cleaned.slice("${HOME}/".length));
12135
+ return resolve9(cleaned);
11970
12136
  }
11971
12137
  function assertSafeRelativePath(relativePath) {
11972
12138
  if (!relativePath.trim())
@@ -11982,7 +12148,7 @@ function assertSafeRelativePath(relativePath) {
11982
12148
  function assertSafeTargetRoot(targetHome) {
11983
12149
  if (!isAbsolute3(targetHome))
11984
12150
  throw new Error(`Session render target must be an absolute path: ${targetHome}`);
11985
- const normalized = resolve8(targetHome);
12151
+ const normalized = resolve9(targetHome);
11986
12152
  if (normalized === parse2(normalized).root) {
11987
12153
  throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
11988
12154
  }
@@ -12218,7 +12384,7 @@ function planSessionRender(input) {
12218
12384
  sourceId: input.providerConfig.sourceId,
12219
12385
  selectedPayloadSha256: sha2566(input.providerConfig.content),
12220
12386
  renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
12221
- selected: !existsSync9(joinTarget(targetHome, adapter.configFile))
12387
+ selected: !existsSync10(joinTarget(targetHome, adapter.configFile))
12222
12388
  }
12223
12389
  } : {},
12224
12390
  ...projectContext ? {
@@ -12479,10 +12645,10 @@ function layerFromIdentityKind(kind, exportShape) {
12479
12645
  function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
12480
12646
  if (sourcePaths.length === 0 || !exportPath)
12481
12647
  return;
12482
- const baseDir3 = dirname3(resolveSessionPath(exportPath));
12648
+ const baseDir = dirname4(resolveSessionPath(exportPath));
12483
12649
  const contents = [];
12484
12650
  for (const sourcePath of sourcePaths) {
12485
- const content = readIdentitySourcePath(sourcePath, baseDir3, sourceId);
12651
+ const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
12486
12652
  if (content !== undefined)
12487
12653
  contents.push({ path: sourcePath.path, content });
12488
12654
  }
@@ -12495,9 +12661,9 @@ ${item.content.trimEnd()}`).join(`
12495
12661
 
12496
12662
  `));
12497
12663
  }
12498
- function readIdentitySourcePath(sourcePath, baseDir3, sourceId) {
12499
- const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir3, sourceId);
12500
- if (!existsSync9(resolvedPath)) {
12664
+ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12665
+ const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
12666
+ if (!existsSync10(resolvedPath)) {
12501
12667
  if (sourcePath.required) {
12502
12668
  throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
12503
12669
  }
@@ -12507,27 +12673,27 @@ function readIdentitySourcePath(sourcePath, baseDir3, sourceId) {
12507
12673
  if (!stat.isFile()) {
12508
12674
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
12509
12675
  }
12510
- const realBase = realpathSync2(baseDir3);
12676
+ const realBase = realpathSync2(baseDir);
12511
12677
  const realPath = realpathSync2(resolvedPath);
12512
12678
  if (!pathIsInside(realPath, realBase)) {
12513
12679
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
12514
12680
  }
12515
12681
  return readFileSync4(realPath, "utf-8");
12516
12682
  }
12517
- function resolveIdentitySourcePath(path, baseDir3, sourceId) {
12683
+ function resolveIdentitySourcePath(path, baseDir, sourceId) {
12518
12684
  const cleaned = cleanSessionPathInput(path);
12519
12685
  if (!cleaned)
12520
12686
  throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
12521
12687
  if (cleaned.includes("\\"))
12522
12688
  throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
12523
- const resolvedPath = isAbsolute3(cleaned) ? resolve8(cleaned) : resolve8(baseDir3, cleaned);
12524
- if (!pathIsInside(resolvedPath, resolve8(baseDir3))) {
12689
+ const resolvedPath = isAbsolute3(cleaned) ? resolve9(cleaned) : resolve9(baseDir, cleaned);
12690
+ if (!pathIsInside(resolvedPath, resolve9(baseDir))) {
12525
12691
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
12526
12692
  }
12527
12693
  return resolvedPath;
12528
12694
  }
12529
- function pathIsInside(path, baseDir3) {
12530
- const rel = relative2(baseDir3, path);
12695
+ function pathIsInside(path, baseDir) {
12696
+ const rel = relative2(baseDir, path);
12531
12697
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
12532
12698
  }
12533
12699
  function providerTargetsTool(targets, tool) {
@@ -13628,6 +13794,1222 @@ var init_machines = __esm(() => {
13628
13794
  init_database();
13629
13795
  });
13630
13796
 
13797
+ // ../contracts/dist/client/storage.js
13798
+ import { isIP as isIP2 } from "net";
13799
+ import { spawnSync } from "child_process";
13800
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync5 } from "fs";
13801
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
13802
+ import { createRequire as createRequire2 } from "module";
13803
+ import { hostname as osHostname } from "os";
13804
+ import { isAbsolute as isAbsolute4, join as join12 } from "path";
13805
+ function envToken2(name) {
13806
+ return name.toUpperCase().replace(/-/g, "_");
13807
+ }
13808
+ function clientTransportEnvKeys2(name) {
13809
+ const envSegment = envToken2(name);
13810
+ return {
13811
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
13812
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
13813
+ };
13814
+ }
13815
+ function credentialOverrideEnvKey2(name) {
13816
+ return `HASNA_${envToken2(name)}_API_KEY_OVERRIDE`;
13817
+ }
13818
+ function credentialPointerEnvKey2(name) {
13819
+ return `HASNA_${envToken2(name)}_API_KEY_REF`;
13820
+ }
13821
+ function homeDir4(env) {
13822
+ const home = env.HOME?.trim();
13823
+ return home ? home : null;
13824
+ }
13825
+ function absoluteOverride(env, key) {
13826
+ const value = env[key]?.trim();
13827
+ return value && isAbsolute4(value) ? value : null;
13828
+ }
13829
+ function hasnaHomeDir(env) {
13830
+ const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
13831
+ if (override)
13832
+ return override;
13833
+ const home = homeDir4(env);
13834
+ return home ? join12(home, HASNA_HOME_DIR) : null;
13835
+ }
13836
+ function appConfigDir(name, env) {
13837
+ const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
13838
+ if (configRoot)
13839
+ return join12(configRoot, name);
13840
+ const root = hasnaHomeDir(env);
13841
+ return root ? join12(root, name, CONFIG_SUBDIR) : null;
13842
+ }
13843
+ function credentialDiskSourceList(name, env, profile = null) {
13844
+ if (!SAFE_APP_SLUG.test(name))
13845
+ return [];
13846
+ const directory = appConfigDir(name, env);
13847
+ if (!directory)
13848
+ return [];
13849
+ const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
13850
+ return [{ path: join12(directory, file), tier: "disk" }];
13851
+ }
13852
+ function credentialDiskSources(name, env) {
13853
+ return credentialDiskSourceList(name, env, null).map((s) => s.path);
13854
+ }
13855
+ function profileDiskSources(name, env, profile) {
13856
+ return credentialDiskSourceList(name, env, profile).map((s) => s.path);
13857
+ }
13858
+ function parseEnvFile(text) {
13859
+ const values = new Map;
13860
+ const unusable = new Set;
13861
+ for (const rawLine of text.split(/\r?\n/)) {
13862
+ const line = rawLine.trim();
13863
+ if (line.length === 0 || line.startsWith("#"))
13864
+ continue;
13865
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
13866
+ const equals = withoutExport.indexOf("=");
13867
+ if (equals <= 0)
13868
+ continue;
13869
+ const key = withoutExport.slice(0, equals).trim();
13870
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
13871
+ continue;
13872
+ let value = withoutExport.slice(equals + 1).trim();
13873
+ const quote = value[0];
13874
+ if (quote === '"' || quote === "'") {
13875
+ if (value.length < 2 || !value.endsWith(quote)) {
13876
+ unusable.add(key);
13877
+ continue;
13878
+ }
13879
+ value = value.slice(1, -1);
13880
+ }
13881
+ if (value.trim().length === 0) {
13882
+ unusable.add(key);
13883
+ continue;
13884
+ }
13885
+ if (values.has(key) && values.get(key) !== value)
13886
+ unusable.add(key);
13887
+ values.set(key, value);
13888
+ }
13889
+ return { values, unusable };
13890
+ }
13891
+ function configFileModeAllowed(mode) {
13892
+ const permissions = mode & 4095;
13893
+ return permissions === 256 || permissions === 384;
13894
+ }
13895
+ function configFileReadsCoherent(before, after) {
13896
+ return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
13897
+ }
13898
+ function readAppConfigFile(path) {
13899
+ const unsafe = (reason) => {
13900
+ throw new CredentialFileUnsafeError(path, reason);
13901
+ };
13902
+ let fd = -1;
13903
+ try {
13904
+ fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
13905
+ } catch (error) {
13906
+ const code = error.code;
13907
+ if (code === "ENOENT" || code === "ENOTDIR")
13908
+ return null;
13909
+ if (code === "ELOOP")
13910
+ unsafe("the path is a symlink");
13911
+ unsafe(`the path could not be opened (${code ?? "unknown error"})`);
13912
+ }
13913
+ try {
13914
+ const before = fstatSync2(fd);
13915
+ if (!before.isFile())
13916
+ unsafe("the path is not a regular file");
13917
+ if (!configFileModeAllowed(before.mode)) {
13918
+ unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
13919
+ }
13920
+ const uid = process.getuid?.() ?? process.geteuid?.();
13921
+ if (uid !== undefined && before.uid !== uid)
13922
+ unsafe("the file is not owned by the current user");
13923
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
13924
+ unsafe("the file exceeds the size limit");
13925
+ const bytes = readFileSync5(fd);
13926
+ const after = fstatSync2(fd);
13927
+ if (!configFileReadsCoherent(before, after)) {
13928
+ unsafe("the file changed while being read");
13929
+ }
13930
+ return parseEnvFile(bytes.toString("utf8"));
13931
+ } finally {
13932
+ if (fd !== -1)
13933
+ closeSync2(fd);
13934
+ }
13935
+ }
13936
+ function readCredentialFile(path, apiKeyKeys) {
13937
+ const parsed = readAppConfigFile(path);
13938
+ if (!parsed)
13939
+ return null;
13940
+ for (const key of apiKeyKeys) {
13941
+ if (parsed.unusable.has(key)) {
13942
+ throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
13943
+ }
13944
+ }
13945
+ const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
13946
+ if (new Set(values).size > 1) {
13947
+ throw new CredentialFileUnsafeError(path, "credential aliases disagree");
13948
+ }
13949
+ return values[0] ?? null;
13950
+ }
13951
+ function appConfigDiskValue(name, env, keys) {
13952
+ const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
13953
+ if (wanted.length === 0)
13954
+ return null;
13955
+ for (const path of credentialDiskSources(name, env)) {
13956
+ const parsed = readAppConfigFile(path);
13957
+ if (!parsed)
13958
+ continue;
13959
+ if (wanted.some((key) => parsed.unusable.has(key))) {
13960
+ return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
13961
+ }
13962
+ const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
13963
+ if (new Set(values).size > 1)
13964
+ throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
13965
+ for (const key of wanted) {
13966
+ if (parsed.unusable.has(key))
13967
+ return { key, value: "", path, unusable: true };
13968
+ const value = parsed.values.get(key)?.trim();
13969
+ if (value)
13970
+ return { key, value, path };
13971
+ }
13972
+ }
13973
+ return null;
13974
+ }
13975
+ function assertUsableCredential(appName, source, value) {
13976
+ if (VAULT_POINTER_SHAPE.test(value)) {
13977
+ throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey2(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
13978
+ }
13979
+ if (!ILLEGAL_IN_HEADER_VALUE.test(value))
13980
+ return;
13981
+ throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
13982
+ }
13983
+ function sealCredential(fields) {
13984
+ const { apiKey } = fields;
13985
+ const visible = {
13986
+ tier: fields.tier,
13987
+ source: fields.source,
13988
+ deliberate: fields.deliberate,
13989
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
13990
+ warning: fields.warning
13991
+ };
13992
+ const sealed = { ...visible };
13993
+ Object.defineProperty(sealed, "apiKey", {
13994
+ value: apiKey,
13995
+ enumerable: false,
13996
+ writable: false,
13997
+ configurable: false
13998
+ });
13999
+ if (fields.pointerVaultKey !== undefined) {
14000
+ Object.defineProperty(sealed, "pointerVaultKey", {
14001
+ value: fields.pointerVaultKey,
14002
+ enumerable: false,
14003
+ writable: false,
14004
+ configurable: false
14005
+ });
14006
+ }
14007
+ Object.defineProperty(sealed, INSPECT_CUSTOM2, {
14008
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
14009
+ enumerable: false,
14010
+ writable: false,
14011
+ configurable: false
14012
+ });
14013
+ Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
14014
+ value: true,
14015
+ enumerable: false,
14016
+ writable: false,
14017
+ configurable: false
14018
+ });
14019
+ return Object.freeze(sealed);
14020
+ }
14021
+ function isSealedCredential(credential) {
14022
+ return credential[CREDENTIAL_SEAL2] === true;
14023
+ }
14024
+ function explicitCredential(appName, apiKey) {
14025
+ const source = "explicit apiKey option";
14026
+ assertUsableCredential(appName, source, apiKey);
14027
+ return sealCredential({
14028
+ apiKey,
14029
+ tier: "argument",
14030
+ source,
14031
+ deliberate: true,
14032
+ diskCandidates: [],
14033
+ warning: null
14034
+ });
14035
+ }
14036
+ function validateAndSealResolvedCredential(appName, credential) {
14037
+ const apiKey = credential.apiKey;
14038
+ assertUsableCredential(appName, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE, apiKey);
14039
+ if (!isSealedCredential(credential)) {
14040
+ return sealCredential({
14041
+ apiKey,
14042
+ tier: "argument",
14043
+ source: CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE,
14044
+ deliberate: true,
14045
+ diskCandidates: [],
14046
+ warning: null
14047
+ });
14048
+ }
14049
+ return sealCredential({
14050
+ apiKey,
14051
+ tier: credential.tier,
14052
+ source: credential.source,
14053
+ deliberate: credential.deliberate,
14054
+ diskCandidates: credential.diskCandidates,
14055
+ warning: credential.warning,
14056
+ ...credential.pointerVaultKey !== undefined ? { pointerVaultKey: credential.pointerVaultKey } : {}
14057
+ });
14058
+ }
14059
+ function firstEnvValue(env, keys) {
14060
+ for (const key of keys) {
14061
+ if (!Object.prototype.hasOwnProperty.call(env, key))
14062
+ continue;
14063
+ const value = env[key]?.trim();
14064
+ if (value)
14065
+ return { key, value };
14066
+ }
14067
+ return null;
14068
+ }
14069
+ function isAmbientEnvironment(env) {
14070
+ return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
14071
+ }
14072
+ function defaultKeychainRunner(argv) {
14073
+ const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
14074
+ encoding: "utf8",
14075
+ stdio: ["ignore", "pipe", "pipe"],
14076
+ timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
14077
+ });
14078
+ return {
14079
+ status: result.status,
14080
+ stdout: result.stdout ?? "",
14081
+ stderr: result.error ? result.error.message : result.stderr ?? ""
14082
+ };
14083
+ }
14084
+ function keychainTierEnabled(env, options) {
14085
+ if ((options.platform ?? process.platform) !== "darwin")
14086
+ return false;
14087
+ if (options.enabled !== undefined)
14088
+ return options.enabled;
14089
+ return options.run !== undefined || isAmbientEnvironment(env);
14090
+ }
14091
+ function keychainAccount(env, options) {
14092
+ const station = env[KEYCHAIN_STATION_ENV_KEY]?.trim();
14093
+ if (station)
14094
+ return station;
14095
+ const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
14096
+ if (host)
14097
+ return host;
14098
+ const user = env.USER?.trim();
14099
+ return user || null;
14100
+ }
14101
+ function keychainFailureHint(text) {
14102
+ const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
14103
+ const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
14104
+ return clean ? `: ${clean}` : "";
14105
+ }
14106
+ function readKeychainItem(name, env, kind, options) {
14107
+ if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env, options))
14108
+ return null;
14109
+ const account = keychainAccount(env, options);
14110
+ if (!account)
14111
+ return null;
14112
+ const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
14113
+ const source = `keychain:${service}@${account}`;
14114
+ const run = options.run ?? defaultKeychainRunner;
14115
+ let result;
14116
+ try {
14117
+ result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
14118
+ } catch (error) {
14119
+ const reason = keychainFailureHint(error instanceof Error ? error.message : String(error));
14120
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} could not run${reason}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
14121
+ }
14122
+ if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
14123
+ return null;
14124
+ if (result.status !== 0) {
14125
+ throw new CredentialResolutionError(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint(result.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
14126
+ }
14127
+ const value = result.stdout.trim();
14128
+ if (!value) {
14129
+ throw new CredentialResolutionError(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
14130
+ }
14131
+ return { value, source };
14132
+ }
14133
+ function keychainConfigValue(name, env, options = {}) {
14134
+ return readKeychainItem(name, env, "api-url", options);
14135
+ }
14136
+ function snapshotClientEnvironment(name, env) {
14137
+ const keys = clientTransportEnvKeys2(name);
14138
+ const ambient = isAmbientEnvironment(env);
14139
+ const snapshot = Object.create(null);
14140
+ for (const key of [
14141
+ ...keys.apiUrlKeys,
14142
+ ...keys.apiKeyKeys,
14143
+ credentialOverrideEnvKey2(name),
14144
+ credentialPointerEnvKey2(name),
14145
+ CREDENTIAL_PROFILE_ENV_KEY2,
14146
+ "HOME",
14147
+ HASNA_HOME_ENV_KEY,
14148
+ HASNA_CONFIG_HOME_ENV_KEY,
14149
+ KEYCHAIN_STATION_ENV_KEY,
14150
+ "USER"
14151
+ ]) {
14152
+ const descriptor = Object.getOwnPropertyDescriptor(env, key);
14153
+ if (!descriptor)
14154
+ continue;
14155
+ if (!("value" in descriptor)) {
14156
+ throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
14157
+ }
14158
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
14159
+ throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
14160
+ }
14161
+ snapshot[key] = descriptor.value;
14162
+ }
14163
+ if (ambient) {
14164
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT2, {
14165
+ value: true,
14166
+ enumerable: false,
14167
+ writable: false,
14168
+ configurable: false
14169
+ });
14170
+ }
14171
+ return Object.freeze(snapshot);
14172
+ }
14173
+ function resolveCredential(name, env, options = {}) {
14174
+ env = snapshotClientEnvironment(name, env);
14175
+ const { apiKeyKeys } = clientTransportEnvKeys2(name);
14176
+ const diskPaths = credentialDiskSources(name, env);
14177
+ if (options.apiKey !== undefined) {
14178
+ const explicitKey = options.apiKey.trim();
14179
+ if (!explicitKey) {
14180
+ throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
14181
+ }
14182
+ assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
14183
+ return sealCredential({
14184
+ apiKey: explicitKey,
14185
+ tier: "argument",
14186
+ source: "explicit apiKey argument",
14187
+ deliberate: true,
14188
+ diskCandidates: diskPaths,
14189
+ warning: null
14190
+ });
14191
+ }
14192
+ const overrideKeyName = credentialOverrideEnvKey2(name);
14193
+ const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
14194
+ if (overrideRaw !== undefined) {
14195
+ const override = overrideRaw.trim();
14196
+ if (!override) {
14197
+ throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
14198
+ }
14199
+ assertUsableCredential(name, overrideKeyName, override);
14200
+ return sealCredential({
14201
+ apiKey: override,
14202
+ tier: "override",
14203
+ source: overrideKeyName,
14204
+ deliberate: true,
14205
+ diskCandidates: diskPaths,
14206
+ warning: null
14207
+ });
14208
+ }
14209
+ const pointerKeyName = credentialPointerEnvKey2(name);
14210
+ const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
14211
+ if (pointerRaw !== undefined) {
14212
+ const pointer = pointerRaw.trim();
14213
+ if (!pointer) {
14214
+ throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
14215
+ }
14216
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
14217
+ throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
14218
+ }
14219
+ return sealCredential({
14220
+ apiKey: "",
14221
+ pointerVaultKey: pointer,
14222
+ tier: "pointer",
14223
+ source: pointerKeyName,
14224
+ deliberate: true,
14225
+ diskCandidates: diskPaths,
14226
+ warning: null
14227
+ });
14228
+ }
14229
+ if (options.profile !== undefined && !options.profile.trim()) {
14230
+ throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
14231
+ }
14232
+ const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY2) ? env[CREDENTIAL_PROFILE_ENV_KEY2] : undefined;
14233
+ if (profileRaw !== undefined && !profileRaw.trim()) {
14234
+ throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY2} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY2]);
14235
+ }
14236
+ const profile = options.profile?.trim() || profileRaw?.trim();
14237
+ if (profile) {
14238
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY2;
14239
+ if (!SAFE_PROFILE.test(profile)) {
14240
+ throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
14241
+ }
14242
+ const paths = profileDiskSources(name, env, profile);
14243
+ for (const path of paths) {
14244
+ const value = readCredentialFile(path, apiKeyKeys);
14245
+ if (value) {
14246
+ assertUsableCredential(name, path, value);
14247
+ return sealCredential({
14248
+ apiKey: value,
14249
+ tier: "profile",
14250
+ source: path,
14251
+ deliberate: true,
14252
+ diskCandidates: paths,
14253
+ warning: null
14254
+ });
14255
+ }
14256
+ }
14257
+ throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY2}.`, paths);
14258
+ }
14259
+ const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
14260
+ const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
14261
+ if (blankEnv) {
14262
+ throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
14263
+ }
14264
+ if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
14265
+ throw new CredentialResolutionError(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
14266
+ }
14267
+ const envHit = firstEnvValue(env, apiKeyKeys);
14268
+ const keychainHit = readKeychainItem(name, env, "api-key", options.keychain ?? {});
14269
+ if (keychainHit) {
14270
+ assertUsableCredential(name, keychainHit.source, keychainHit.value);
14271
+ const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
14272
+ return sealCredential({
14273
+ apiKey: keychainHit.value,
14274
+ tier: "keychain",
14275
+ source: keychainHit.source,
14276
+ deliberate: false,
14277
+ diskCandidates: diskPaths,
14278
+ warning
14279
+ });
14280
+ }
14281
+ const diskSourceList = credentialDiskSourceList(name, env, null);
14282
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
14283
+ if (diskHits.length > 0) {
14284
+ const winner = diskHits[0];
14285
+ assertUsableCredential(name, winner.src.path, winner.value);
14286
+ const divergentSources = [
14287
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
14288
+ ...envHit && envHit.value !== winner.value ? [envHit.key] : []
14289
+ ];
14290
+ const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
14291
+ return sealCredential({
14292
+ apiKey: winner.value,
14293
+ tier: winner.src.tier,
14294
+ source: winner.src.path,
14295
+ deliberate: false,
14296
+ diskCandidates: diskPaths,
14297
+ warning
14298
+ });
14299
+ }
14300
+ if (envHit) {
14301
+ assertUsableCredential(name, envHit.key, envHit.value);
14302
+ return sealCredential({
14303
+ apiKey: envHit.value,
14304
+ tier: "env",
14305
+ source: envHit.key,
14306
+ deliberate: false,
14307
+ diskCandidates: diskPaths,
14308
+ warning: null
14309
+ });
14310
+ }
14311
+ return null;
14312
+ }
14313
+ async function completePointerCredential(name, pointerResolution, env = process.env) {
14314
+ const vaultKey = pointerResolution.pointerVaultKey;
14315
+ const pointerEnvKey = pointerResolution.source;
14316
+ if (!vaultKey) {
14317
+ throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
14318
+ }
14319
+ let secretsSdk;
14320
+ try {
14321
+ secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER2);
14322
+ } catch {
14323
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
14324
+ }
14325
+ let client;
14326
+ try {
14327
+ client = secretsSdk.createSecretsClientFromEnv(env);
14328
+ } catch {
14329
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
14330
+ }
14331
+ let secret;
14332
+ try {
14333
+ secret = await client.getSecret({ key: vaultKey });
14334
+ } catch {
14335
+ throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
14336
+ }
14337
+ const value = secret.value;
14338
+ if (!value) {
14339
+ throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
14340
+ }
14341
+ assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
14342
+ return sealCredential({
14343
+ apiKey: value,
14344
+ tier: "pointer",
14345
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
14346
+ deliberate: true,
14347
+ diskCandidates: pointerResolution.diskCandidates,
14348
+ warning: null
14349
+ });
14350
+ }
14351
+ function defaultFleetGatewayBaseUrl(name) {
14352
+ return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
14353
+ }
14354
+ function isValidDnsDomain(value) {
14355
+ if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
14356
+ return false;
14357
+ }
14358
+ return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
14359
+ }
14360
+ function validateAppSlug(name) {
14361
+ if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
14362
+ throw new Error("App name must be one lowercase DNS label.");
14363
+ }
14364
+ return name;
14365
+ }
14366
+ function rawAuthority(value) {
14367
+ const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
14368
+ if (!match)
14369
+ throw new Error("API URL must be absolute.");
14370
+ const afterScheme = value.slice(match[0].length);
14371
+ const boundary = afterScheme.search(/[/?#]/);
14372
+ const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
14373
+ if (!authority)
14374
+ throw new Error("API URL must include a hostname.");
14375
+ return authority;
14376
+ }
14377
+ function assertCanonicalPort(port) {
14378
+ if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
14379
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
14380
+ }
14381
+ const numericPort = Number(port);
14382
+ if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
14383
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
14384
+ }
14385
+ }
14386
+ function canonicalAuthorityHostname(authority) {
14387
+ let rawHostname;
14388
+ if (authority.startsWith("[")) {
14389
+ const closingBracket = authority.indexOf("]");
14390
+ if (closingBracket === -1) {
14391
+ throw new Error("API URL authority must contain a canonical hostname.");
14392
+ }
14393
+ rawHostname = authority.slice(0, closingBracket + 1);
14394
+ const portSuffix = authority.slice(closingBracket + 1);
14395
+ if (portSuffix) {
14396
+ if (!portSuffix.startsWith(":")) {
14397
+ throw new Error("API URL authority must contain a canonical hostname and port.");
14398
+ }
14399
+ assertCanonicalPort(portSuffix.slice(1));
14400
+ }
14401
+ if (isIP2(rawHostname.slice(1, -1)) !== 6) {
14402
+ throw new Error("API URL authority must contain a canonical IPv6 literal.");
14403
+ }
14404
+ } else {
14405
+ const firstColon = authority.indexOf(":");
14406
+ const lastColon = authority.lastIndexOf(":");
14407
+ if (firstColon !== lastColon) {
14408
+ throw new Error("IPv6 API URL authorities must use brackets.");
14409
+ }
14410
+ if (lastColon !== -1) {
14411
+ const port = authority.slice(lastColon + 1);
14412
+ assertCanonicalPort(port);
14413
+ rawHostname = authority.slice(0, lastColon);
14414
+ } else {
14415
+ rawHostname = authority;
14416
+ }
14417
+ const ipVersion = isIP2(rawHostname);
14418
+ const numericAddressParts = rawHostname.split(".");
14419
+ const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
14420
+ if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
14421
+ throw new Error("API URL authority must contain a canonical ASCII hostname.");
14422
+ }
14423
+ }
14424
+ return rawHostname.toLowerCase();
14425
+ }
14426
+ function isDeliberateLoopbackHttpAuthority(authority) {
14427
+ return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
14428
+ }
14429
+ function toV1BaseUrl(apiUrl) {
14430
+ if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
14431
+ throw new Error("API URL must not contain ASCII control characters.");
14432
+ }
14433
+ const input = apiUrl.trim();
14434
+ const authority = rawAuthority(input);
14435
+ if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
14436
+ throw new Error("API URL authority must be canonical ASCII without credentials.");
14437
+ }
14438
+ const canonicalHostname = canonicalAuthorityHostname(authority);
14439
+ const url = new URL(input);
14440
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
14441
+ throw new Error("API URL must use http or https.");
14442
+ }
14443
+ if (url.username || url.password) {
14444
+ throw new Error("API URL must not include credentials.");
14445
+ }
14446
+ if (!url.hostname || url.hostname.endsWith(".")) {
14447
+ throw new Error("API URL must include a canonical hostname.");
14448
+ }
14449
+ if (url.hostname.toLowerCase() !== canonicalHostname) {
14450
+ throw new Error("API URL authority must not rely on parser hostname normalization.");
14451
+ }
14452
+ if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
14453
+ throw new Error("API URL must not use IDN or punycode hostnames.");
14454
+ }
14455
+ if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
14456
+ throw new Error("API URL may use http only for an exact loopback authority.");
14457
+ }
14458
+ if (url.search || url.hash) {
14459
+ throw new Error("API URL must not include a query string or fragment.");
14460
+ }
14461
+ let path = url.pathname.replace(/\/+$/, "");
14462
+ if (path.endsWith("/v1"))
14463
+ path = path.slice(0, -"/v1".length);
14464
+ url.pathname = `${path}/v1`;
14465
+ return url.toString().replace(/\/+$/, "");
14466
+ }
14467
+ function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
14468
+ env = snapshotClientEnvironment(name, env);
14469
+ const keys = clientTransportEnvKeys2(name);
14470
+ const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
14471
+ const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
14472
+ if (blankUrl) {
14473
+ throw new ClientTransportConfigurationError(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
14474
+ }
14475
+ const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
14476
+ if (controlledUrl) {
14477
+ throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
14478
+ }
14479
+ const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
14480
+ if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
14481
+ throw new ClientTransportConfigurationError(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must be identical or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
14482
+ }
14483
+ const envUrlHit = usableUrlEntries[0] ?? null;
14484
+ const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
14485
+ const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys);
14486
+ if (diskConfigUrlHit?.unusable) {
14487
+ throw new ClientTransportConfigurationError(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
14488
+ }
14489
+ const urlCandidates = [
14490
+ ...envUrlHit ? [envUrlHit] : [],
14491
+ ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
14492
+ ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
14493
+ ];
14494
+ const configuredUrl = urlCandidates[0] ?? null;
14495
+ const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
14496
+ if (configuredUrl && divergentUrls.length > 0) {
14497
+ throw new ClientTransportConfigurationError(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
14498
+ }
14499
+ const warnings = [];
14500
+ if (configuredUrl && !envUrlHit) {
14501
+ warnings.push(`No ${keys.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
14502
+ }
14503
+ const credential = resolveCredential(name, env, options.credentials);
14504
+ if (!credential) {
14505
+ const diskHint = credentialDiskSourcesForMessage(name, env);
14506
+ const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
14507
+ warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys.apiKeyKeys[0]} in the environment.`);
14508
+ throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
14509
+ }
14510
+ if (credential.warning)
14511
+ warnings.push(credential.warning);
14512
+ let urlHit;
14513
+ if (configuredUrl) {
14514
+ urlHit = configuredUrl;
14515
+ } else {
14516
+ try {
14517
+ urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
14518
+ } catch (error) {
14519
+ const message = error instanceof Error ? error.message : String(error);
14520
+ throw new ClientTransportConfigurationError(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
14521
+ }
14522
+ }
14523
+ const apiUrlSource = urlHit.key;
14524
+ let baseUrl;
14525
+ try {
14526
+ baseUrl = toV1BaseUrl(urlHit.value);
14527
+ } catch (error) {
14528
+ const message = error instanceof Error ? error.message : String(error);
14529
+ throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
14530
+ }
14531
+ return {
14532
+ resolution: {
14533
+ transport: "http",
14534
+ transportSource: urlHit.key,
14535
+ baseUrl,
14536
+ apiUrlSource,
14537
+ apiKeyPresent: true,
14538
+ apiKeySource: credential.source,
14539
+ apiKeyTier: credential.tier,
14540
+ misconfigured: false,
14541
+ warning: warnings.length > 0 ? warnings.join(" ") : null
14542
+ },
14543
+ credential
14544
+ };
14545
+ }
14546
+ function credentialDiskSourcesForMessage(name, env) {
14547
+ const paths = credentialDiskSources(name, env);
14548
+ return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
14549
+ }
14550
+ function currentCredential(name, apiKey) {
14551
+ if (typeof apiKey === "function") {
14552
+ return validateAndSealResolvedCredential(name, apiKey());
14553
+ }
14554
+ return explicitCredential(name, apiKey);
14555
+ }
14556
+ async function resolveRequestCredential(name, apiKey, env = process.env) {
14557
+ const resolved = currentCredential(name, apiKey);
14558
+ if (resolved.tier === "pointer") {
14559
+ return completePointerCredential(name, resolved, env);
14560
+ }
14561
+ return resolved;
14562
+ }
14563
+ function authFailureGuidance(credential) {
14564
+ const origin = `The API key for this request came from ${credential.source}`;
14565
+ if (credential.deliberate) {
14566
+ const remedy = credential.source === CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE ? `Fix that provider so it returns the current key, or replace it with resolveCredential() ` + `so diagnostics can name the original source.` : `Rotate that key, or unset the override to use the credential on disk.`;
14567
+ return `${origin} \u2014 a credential you selected deliberately. It was NOT substituted with any other key: ` + `falling back here would authenticate as a different principal than the one you named, which is ` + `exactly the failure an override exists to prevent. ${remedy}`;
14568
+ }
14569
+ if (credential.tier === "env") {
14570
+ const target = credential.diskCandidates[0];
14571
+ const remedy = target ? `Store the CURRENT key in the Keychain or write it to ${target} \u2014 both are re-read on every call, so ` + `rotations take effect immediately and in every shell. Do not simply unset ${credential.source}: ` + `nothing was found in the Keychain or on disk, so that would leave this client with no credential at all.` : `This environment has no HOME or HASNA_HOME, so no credential file could be consulted; the disk tier is ` + `unavailable here and there is nothing to fall back to. Set HOME, or supply the key explicitly.`;
14572
+ return `${origin}, a variable in this process's environment. If a wrapper injected it for this one process, the ` + `wrapper re-reads its store on every invocation and the stored key itself is being rejected \u2014 rotate it. ` + `If this SHELL exported it, the export is a snapshot taken when the shell started: a STALE SHELL that ` + `exported the key before it was rotated keeps sending the old one until it exits. ${remedy}`;
14573
+ }
14574
+ if (credential.tier === "keychain") {
14575
+ return `${origin}, which was re-read from the Keychain on this very call \u2014 so a stale shell is NOT the cause ` + `here. The stored item is genuinely being rejected: update it with the current key, or re-run the fleet ` + `key distribution so this machine gets the current key.`;
14576
+ }
14577
+ return `${origin}, which was re-read from disk on this very call \u2014 so a stale shell is NOT the cause here. ` + `The stored credential is genuinely being rejected: rotate it, or re-run the fleet key distribution ` + `so this machine gets the current key.`;
14578
+ }
14579
+ function assertNoAuthorityOverrideHeaders(headers, source) {
14580
+ if (!headers)
14581
+ return;
14582
+ const forbidden = Object.keys(headers).find((name) => AUTHORITY_OVERRIDE_HEADERS2.has(name.trim().toLowerCase()));
14583
+ if (forbidden) {
14584
+ throw new Error(`Authenticated ${source} headers must not set authority header '${forbidden}'.`);
14585
+ }
14586
+ }
14587
+ function appendQuery(path, query) {
14588
+ if (!query)
14589
+ return path;
14590
+ const params = query instanceof URLSearchParams ? query : new URLSearchParams;
14591
+ if (!(query instanceof URLSearchParams)) {
14592
+ for (const [key, value] of Object.entries(query)) {
14593
+ if (value === null || value === undefined)
14594
+ continue;
14595
+ if (Array.isArray(value)) {
14596
+ for (const v of value)
14597
+ params.append(key, String(v));
14598
+ } else {
14599
+ params.append(key, String(value));
14600
+ }
14601
+ }
14602
+ }
14603
+ const qs = params.toString();
14604
+ if (!qs)
14605
+ return path;
14606
+ return `${path}${path.includes("?") ? "&" : "?"}${qs}`;
14607
+ }
14608
+ function createHasnaHttpTransportInternal(options, requestBindingProvider) {
14609
+ const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
14610
+ const base = toV1BaseUrl(options.baseUrl);
14611
+ const timeoutMs = options.timeoutMs ?? 30000;
14612
+ const sleep = options.sleepImpl ?? defaultSleep;
14613
+ const defaultRetry = options.retry;
14614
+ function resolveRetry(callRetry) {
14615
+ const chosen = callRetry !== undefined ? callRetry : defaultRetry;
14616
+ if (chosen === false)
14617
+ return null;
14618
+ const r = chosen ?? {};
14619
+ return {
14620
+ retries: r.retries ?? 2,
14621
+ baseDelayMs: r.baseDelayMs ?? 200,
14622
+ maxDelayMs: r.maxDelayMs ?? 2000,
14623
+ retryStatuses: r.retryStatuses ?? [...DEFAULT_RETRY_STATUSES]
14624
+ };
14625
+ }
14626
+ async function once(method, rel, url, body, opts, credential) {
14627
+ assertNoAuthorityOverrideHeaders(options.headers, "transport");
14628
+ assertNoAuthorityOverrideHeaders(opts.headers, "request");
14629
+ const headers = {
14630
+ "x-api-key": credential.apiKey,
14631
+ Authorization: `Bearer ${credential.apiKey}`,
14632
+ Accept: "application/json",
14633
+ ...options.headers ?? {},
14634
+ ...opts.headers ?? {}
14635
+ };
14636
+ if (opts.idempotencyKey)
14637
+ headers["Idempotency-Key"] = opts.idempotencyKey;
14638
+ const init = {
14639
+ method,
14640
+ headers,
14641
+ redirect: "manual"
14642
+ };
14643
+ if (body !== undefined) {
14644
+ headers["Content-Type"] = "application/json";
14645
+ init.body = JSON.stringify(body);
14646
+ }
14647
+ const controller = new AbortController;
14648
+ const onAbort = () => controller.abort();
14649
+ if (opts.signal) {
14650
+ if (opts.signal.aborted)
14651
+ controller.abort();
14652
+ else
14653
+ opts.signal.addEventListener("abort", onAbort, { once: true });
14654
+ }
14655
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
14656
+ init.signal = controller.signal;
14657
+ let response;
14658
+ try {
14659
+ response = await fetchImpl(url, init);
14660
+ } catch (error) {
14661
+ const err = error instanceof Error ? error : new Error(String(error));
14662
+ if (opts.signal?.aborted)
14663
+ return { ok: false, retryable: false, error: err };
14664
+ return { ok: false, retryable: true, error: err };
14665
+ } finally {
14666
+ clearTimeout(timer);
14667
+ if (opts.signal)
14668
+ opts.signal.removeEventListener("abort", onAbort);
14669
+ }
14670
+ const authenticationFailure = response.status === 401 || response.status === 403;
14671
+ let parsed = undefined;
14672
+ if (authenticationFailure) {
14673
+ try {
14674
+ await response.body?.cancel();
14675
+ } catch {}
14676
+ } else {
14677
+ const text = await response.text();
14678
+ if (text.length > 0) {
14679
+ try {
14680
+ parsed = JSON.parse(text);
14681
+ } catch {
14682
+ parsed = text;
14683
+ }
14684
+ }
14685
+ }
14686
+ if (!response.ok) {
14687
+ if (response.status >= 300 && response.status < 400) {
14688
+ return {
14689
+ ok: false,
14690
+ retryable: false,
14691
+ error: new HasnaHttpError(method, rel, response.status, parsed)
14692
+ };
14693
+ }
14694
+ if (authenticationFailure) {
14695
+ return {
14696
+ ok: false,
14697
+ retryable: false,
14698
+ error: new HasnaHttpError(method, rel, response.status, undefined, {
14699
+ source: credential.source,
14700
+ tier: credential.tier,
14701
+ guidance: authFailureGuidance(credential)
14702
+ })
14703
+ };
14704
+ }
14705
+ const retry = resolveRetry(opts.retry);
14706
+ const retryable = retry ? retry.retryStatuses.includes(response.status) : false;
14707
+ return { ok: false, retryable, error: new HasnaHttpError(method, rel, response.status, parsed) };
14708
+ }
14709
+ return { ok: true, value: parsed };
14710
+ }
14711
+ async function request(method, path, body, opts = {}) {
14712
+ const upper = method.toUpperCase();
14713
+ const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
14714
+ const retry = resolveRetry(opts.retry);
14715
+ const methodRetryable = IDEMPOTENT_METHODS2.has(upper) || Boolean(opts.idempotencyKey);
14716
+ const maxAttempts = retry && methodRetryable ? retry.retries + 1 : 1;
14717
+ const binding = requestBindingProvider ? await requestBindingProvider() : {
14718
+ baseUrl: base,
14719
+ credential: await resolveRequestCredential(options.name, options.apiKey)
14720
+ };
14721
+ const url = `${binding.baseUrl}${rel}`;
14722
+ const credential = binding.credential;
14723
+ let last = null;
14724
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
14725
+ const result = await once(upper, rel, url, body, opts, credential);
14726
+ if (result.ok)
14727
+ return result.value;
14728
+ last = result;
14729
+ const canRetry = retry !== null && methodRetryable && result.retryable && attempt < maxAttempts;
14730
+ if (!canRetry)
14731
+ break;
14732
+ const backoff = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** (attempt - 1));
14733
+ const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
14734
+ await sleep(backoff + jitter);
14735
+ }
14736
+ throw last.error;
14737
+ }
14738
+ return {
14739
+ baseUrl: base,
14740
+ request,
14741
+ get: (path, opts) => request("GET", path, undefined, opts),
14742
+ post: (path, body, opts) => request("POST", path, body, opts),
14743
+ put: (path, body, opts) => request("PUT", path, body, opts),
14744
+ patch: (path, body, opts) => request("PATCH", path, body, opts),
14745
+ del: (path, body, opts) => request("DELETE", path, body, opts)
14746
+ };
14747
+ }
14748
+ function createClientTransport(name, env = process.env, overrides) {
14749
+ const credentialOptions = overrides?.credentials;
14750
+ const snapshotOptions = { ...credentialOptions ? { credentials: credentialOptions } : {} };
14751
+ const resolution = resolveClientTransportSnapshot(name, env, snapshotOptions).resolution;
14752
+ const sameBinding = (left, right) => left.resolution.baseUrl === right.resolution.baseUrl && left.credential.apiKey === right.credential.apiKey && left.credential.pointerVaultKey === right.credential.pointerVaultKey && left.credential.source === right.credential.source && left.credential.tier === right.credential.tier;
14753
+ const unstableConfiguration = () => new ClientTransportConfigurationError(name, "The configured service authority or credential changed while a request was being prepared; no authenticated request was sent.");
14754
+ const requestBindingProvider = async () => {
14755
+ const first = resolveClientTransportSnapshot(name, env, snapshotOptions);
14756
+ const reviewed = resolveClientTransportSnapshot(name, env, snapshotOptions);
14757
+ if (!sameBinding(first, reviewed))
14758
+ throw unstableConfiguration();
14759
+ if (reviewed.resolution.baseUrl !== resolution.baseUrl) {
14760
+ throw new ClientTransportConfigurationError(name, "The configured service authority changed; rebuild the client before sending credentials.");
14761
+ }
14762
+ const credential = await resolveRequestCredential(name, () => reviewed.credential, env);
14763
+ const immediatelyBeforeDispatch = resolveClientTransportSnapshot(name, env, snapshotOptions);
14764
+ if (!sameBinding(reviewed, immediatelyBeforeDispatch))
14765
+ throw unstableConfiguration();
14766
+ if (immediatelyBeforeDispatch.resolution.baseUrl !== resolution.baseUrl) {
14767
+ throw new ClientTransportConfigurationError(name, "The configured service authority changed; rebuild the client before sending credentials.");
14768
+ }
14769
+ return { baseUrl: immediatelyBeforeDispatch.resolution.baseUrl, credential };
14770
+ };
14771
+ return {
14772
+ transport: "http",
14773
+ client: createHasnaHttpTransportInternal({
14774
+ name,
14775
+ baseUrl: resolution.baseUrl,
14776
+ apiKey: () => {
14777
+ throw new Error("The authenticated request binding provider was not invoked.");
14778
+ },
14779
+ ...overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {},
14780
+ ...overrides?.headers ? { headers: overrides.headers } : {},
14781
+ ...overrides?.timeoutMs ? { timeoutMs: overrides.timeoutMs } : {},
14782
+ ...overrides?.retry !== undefined ? { retry: overrides.retry } : {},
14783
+ ...overrides?.sleepImpl ? { sleepImpl: overrides.sleepImpl } : {}
14784
+ }, requestBindingProvider),
14785
+ resolution
14786
+ };
14787
+ }
14788
+ function resourcePath(resource) {
14789
+ const trimmed = resource.replace(/^\/+|\/+$/g, "");
14790
+ if (!trimmed)
14791
+ throw new Error("resource must be a non-empty path segment");
14792
+ return `/${trimmed}`;
14793
+ }
14794
+ function entityPath(resource, id) {
14795
+ if (id === undefined || id === null || `${id}`.length === 0) {
14796
+ throw new Error("id must be a non-empty string");
14797
+ }
14798
+ return `${resourcePath(resource)}/${encodeURIComponent(String(id))}`;
14799
+ }
14800
+ function newIdempotencyKey() {
14801
+ const g = globalThis;
14802
+ if (g.crypto?.randomUUID)
14803
+ return g.crypto.randomUUID();
14804
+ return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
14805
+ }
14806
+ function extractItems(raw) {
14807
+ if (Array.isArray(raw))
14808
+ return raw;
14809
+ if (raw && typeof raw === "object") {
14810
+ const obj = raw;
14811
+ for (const key of ["items", "data", "results", "rows", "records"]) {
14812
+ if (Array.isArray(obj[key]))
14813
+ return obj[key];
14814
+ }
14815
+ }
14816
+ return [];
14817
+ }
14818
+ function extractTotal(raw) {
14819
+ if (raw && typeof raw === "object") {
14820
+ const obj = raw;
14821
+ for (const key of ["total", "count", "totalCount", "total_count"]) {
14822
+ if (typeof obj[key] === "number")
14823
+ return obj[key];
14824
+ }
14825
+ }
14826
+ return null;
14827
+ }
14828
+ function extractCursor(raw) {
14829
+ if (raw && typeof raw === "object") {
14830
+ const obj = raw;
14831
+ for (const key of ["cursor", "nextCursor", "next_cursor", "next"]) {
14832
+ if (typeof obj[key] === "string")
14833
+ return obj[key];
14834
+ }
14835
+ }
14836
+ return null;
14837
+ }
14838
+ function isNotFoundHttpError(error) {
14839
+ return typeof error === "object" && error !== null && error.name === "HasnaHttpError" && error.status === 404;
14840
+ }
14841
+ function createHasnaStorageClient(name, transport) {
14842
+ return {
14843
+ name,
14844
+ baseUrl: transport.baseUrl,
14845
+ transport,
14846
+ async list(resource, options = {}) {
14847
+ const raw = await transport.get(resourcePath(resource), options);
14848
+ return {
14849
+ items: extractItems(raw),
14850
+ total: extractTotal(raw),
14851
+ cursor: extractCursor(raw),
14852
+ raw
14853
+ };
14854
+ },
14855
+ async get(resource, id, options = {}) {
14856
+ try {
14857
+ return await transport.get(entityPath(resource, id), options);
14858
+ } catch (error) {
14859
+ if (isNotFoundHttpError(error))
14860
+ return null;
14861
+ throw error;
14862
+ }
14863
+ },
14864
+ async create(resource, body, options = {}) {
14865
+ const { idempotencyKey, ...rest } = options;
14866
+ return transport.post(resourcePath(resource), body, {
14867
+ ...rest,
14868
+ idempotencyKey: idempotencyKey ?? newIdempotencyKey()
14869
+ });
14870
+ },
14871
+ async update(resource, id, patch, options = {}) {
14872
+ const { method = "PATCH", idempotencyKey, ...rest } = options;
14873
+ const call = method === "PUT" ? transport.put : transport.patch;
14874
+ return call(entityPath(resource, id), patch, { ...rest, ...idempotencyKey ? { idempotencyKey } : {} });
14875
+ },
14876
+ async delete(resource, id, options = {}) {
14877
+ try {
14878
+ await transport.del(entityPath(resource, id), undefined, options);
14879
+ } catch (error) {
14880
+ if (isNotFoundHttpError(error))
14881
+ return;
14882
+ throw error;
14883
+ }
14884
+ }
14885
+ };
14886
+ }
14887
+ function resolveStorageClient(name, env = process.env, overrides) {
14888
+ const wired = createClientTransport(name, env, overrides);
14889
+ return { transport: "http", client: createHasnaStorageClient(name, wired.client) };
14890
+ }
14891
+ var CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider", AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, HasnaHttpError, DEFAULT_RETRY_STATUSES, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, defaultSleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
14892
+ var init_storage = __esm(() => {
14893
+ CredentialResolutionError = class CredentialResolutionError extends Error {
14894
+ appName;
14895
+ attempted;
14896
+ constructor(appName, message, attempted) {
14897
+ super(message);
14898
+ this.name = "CredentialResolutionError";
14899
+ this.appName = appName;
14900
+ this.attempted = attempted;
14901
+ }
14902
+ };
14903
+ CredentialFileUnsafeError = class CredentialFileUnsafeError extends Error {
14904
+ path;
14905
+ constructor(path, reason) {
14906
+ super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
14907
+ this.name = "CredentialFileUnsafeError";
14908
+ this.path = path;
14909
+ }
14910
+ };
14911
+ MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
14912
+ SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
14913
+ SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
14914
+ ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
14915
+ VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
14916
+ CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
14917
+ INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
14918
+ CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
14919
+ AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
14920
+ SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
14921
+ requireSecretsSdk2 = createRequire2(import.meta.url);
14922
+ ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
14923
+ DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
14924
+ ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
14925
+ appName;
14926
+ sources;
14927
+ constructor(appName, message, sources = []) {
14928
+ super(message);
14929
+ this.name = "ClientTransportConfigurationError";
14930
+ this.appName = appName;
14931
+ this.sources = Object.freeze([...sources]);
14932
+ }
14933
+ };
14934
+ HasnaHttpError = class HasnaHttpError extends Error {
14935
+ status;
14936
+ method;
14937
+ path;
14938
+ credentialSource;
14939
+ credentialTier;
14940
+ constructor(method, path, status, body, credential) {
14941
+ const guidance = credential ? `. ${credential.guidance}` : "";
14942
+ super(`Hasna cloud request failed: ${method} ${path} -> ${status}${guidance}`);
14943
+ this.name = "HasnaHttpError";
14944
+ this.status = status;
14945
+ this.method = method;
14946
+ this.path = path;
14947
+ Object.defineProperty(this, "body", {
14948
+ value: body,
14949
+ enumerable: status !== 401 && status !== 403,
14950
+ writable: false,
14951
+ configurable: false
14952
+ });
14953
+ this.credentialSource = credential?.source ?? null;
14954
+ this.credentialTier = credential?.tier ?? null;
14955
+ }
14956
+ };
14957
+ DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
14958
+ IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
14959
+ AUTHORITY_OVERRIDE_HEADERS2 = new Set([
14960
+ "host",
14961
+ ":authority",
14962
+ "forwarded",
14963
+ "x-forwarded-host",
14964
+ "x-original-host"
14965
+ ]);
14966
+ });
14967
+
14968
+ // src/lib/transport-resolver.ts
14969
+ function rethrowInstructionsAuthorityFailure(error) {
14970
+ const message = error instanceof Error ? error.message : String(error);
14971
+ const name = error instanceof Error ? error.name : "";
14972
+ const failure = (code, lead) => {
14973
+ throw new Error(`${code}: ${lead} ${message} There is no local fallback: local SQLite is opt-in only ` + `(${INSTRUCTIONS_LOCAL_OPT_IN_ENV}=1) and is disabled by default \u2014 failing closed`, { cause: error });
14974
+ };
14975
+ if (name === "CredentialResolutionError" || name === "CredentialFileUnsafeError") {
14976
+ return failure("REMOTE_API_CREDENTIAL_INVALID", "The configured Instructions credential could not be used.");
14977
+ }
14978
+ if (/no API key could be resolved/.test(message)) {
14979
+ if (/is not set and no API key could be resolved/.test(message)) {
14980
+ return failure("REMOTE_API_CONFIG_MISSING", "no Instructions credential resolved from the Keychain item " + "hasna.credentials.instructions.api-key, ~/.hasna/instructions/config/credentials, " + `or ${clientTransportEnvKeys(INSTRUCTIONS_APP).apiKeyKeys[0]}.`);
14981
+ }
14982
+ return failure("REMOTE_API_KEY_MISSING", "an Instructions authority is configured but no API key resolved \u2014 looked in " + "hasna.credentials.instructions.api-key, ~/.hasna/instructions/config/credentials, " + `and ${clientTransportEnvKeys(INSTRUCTIONS_APP).apiKeyKeys[0]}.`);
14983
+ }
14984
+ return failure("REMOTE_API_URL_INVALID", "the configured Instructions authority is invalid.");
14985
+ }
14986
+ function resolveInstructionsStorageClient(env = process.env, options = {}) {
14987
+ const inputs = instructionsResolverInputs(env, options.credentials);
14988
+ try {
14989
+ return resolveStorageClient(INSTRUCTIONS_APP, inputs.env, {
14990
+ fetchImpl: (input, init) => fetch(input, { ...init, redirect: "manual" }),
14991
+ credentials: inputs.credentials
14992
+ });
14993
+ } catch (error) {
14994
+ rethrowInstructionsAuthorityFailure(error);
14995
+ }
14996
+ }
14997
+ function announceLocalInstructionsMode(write = (line) => process.stderr.write(`${line}
14998
+ `)) {
14999
+ if (localNoticePrinted)
15000
+ return false;
15001
+ localNoticePrinted = true;
15002
+ write(instructionsLocalModeNotice());
15003
+ return true;
15004
+ }
15005
+ var INSTRUCTIONS_APP = "instructions", INSTRUCTIONS_LOCAL_OPT_IN_ENV, localNoticePrinted = false;
15006
+ var init_transport_resolver = __esm(() => {
15007
+ init_transport();
15008
+ init_storage();
15009
+ init_local_opt_in();
15010
+ INSTRUCTIONS_LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS[0];
15011
+ });
15012
+
13631
15013
  // src/data/config-store.ts
13632
15014
  import { randomUUID as randomUUID6 } from "crypto";
13633
15015
  function parseBoundedPagePayload(value, label) {
@@ -13635,7 +15017,7 @@ function parseBoundedPagePayload(value, label) {
13635
15017
  const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
13636
15018
  const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
13637
15019
  if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed)) {
13638
- throw new CloudHttpError(502, `${label} returned an invalid or truncated bounded-read envelope`, value);
15020
+ throw new Error(`${label} returned an invalid or truncated bounded-read envelope`);
13639
15021
  }
13640
15022
  return {
13641
15023
  ...page,
@@ -13650,50 +15032,38 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
13650
15032
  }
13651
15033
  }
13652
15034
  if (!Array.isArray(legacyItems)) {
13653
- throw new CloudHttpError(502, `${label} returned neither a bounded envelope nor a complete legacy array`, value);
15035
+ throw new Error(`${label} returned neither a bounded envelope nor a complete legacy array`);
13654
15036
  }
13655
15037
  const normalized = normalizeBoundedReadOptions(options);
13656
15038
  const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
13657
15039
  return { ...page, source_bounded: false };
13658
15040
  }
13659
15041
  function isCloudAuthError(err) {
13660
- return err instanceof CloudHttpError && (err.status === 401 || err.status === 403);
15042
+ return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && (err.status === 401 || err.status === 403);
15043
+ }
15044
+ function isNotFoundHttpError2(err) {
15045
+ return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && err.status === 404;
13661
15046
  }
13662
15047
  function formatCliError(err, env = process.env) {
13663
15048
  if (isCloudAuthError(err)) {
13664
- const apiUrl = env[API_URL_ENV]?.trim();
13665
- const detail = err.message?.trim();
13666
- const serverNote = detail && !/^HTTP \d+\b/.test(detail) ? ` Server said: ${detail}` : "";
13667
15049
  return [
13668
15050
  `Instructions cloud API rejected the request (HTTP ${err.status}: authentication failed).`,
13669
- serverNote,
13670
- ` The API key in ${API_KEY_ENV} is missing, expired, or revoked${apiUrl ? ` for ${apiUrl}` : ""}.`,
15051
+ ` The API key in use is missing, expired, or revoked (the transport never echoes the server's 401/403 body).`,
13671
15052
  ` To continue, either:`,
13672
- ` - set a valid key: export ${API_KEY_ENV}=<new-key>`,
13673
- ` - or use the local store instead: unset ${API_URL_ENV} ${API_KEY_ENV}`
13674
- ].filter(Boolean).join(`
15053
+ ` - set a valid key: export HASNA_INSTRUCTIONS_API_KEY=<new-key>`,
15054
+ ` (or add the Keychain item hasna.credentials.instructions.api-key, or write`,
15055
+ ` ~/.hasna/instructions/config/credentials)`,
15056
+ ` - or opt in to the local store explicitly: export ${LOCAL_OPT_IN_ENV}=1`
15057
+ ].join(`
13675
15058
  `);
13676
15059
  }
13677
15060
  return err instanceof Error ? err.message : String(err);
13678
15061
  }
13679
- function resolveCloudConfig(env = process.env) {
13680
- assertNoLegacyStorageMode(env);
13681
- const apiUrl = env[API_URL_ENV]?.trim();
13682
- const apiKey = env[API_KEY_ENV]?.trim();
13683
- if (!apiUrl && !apiKey)
13684
- return null;
13685
- if (!apiUrl || !apiKey) {
13686
- throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the HTTP API, ` + `or unset both to use the local store.`);
13687
- }
13688
- return { apiUrl, apiKey };
13689
- }
13690
- function isApiTransport(env = process.env) {
13691
- return resolveCloudConfig(env) !== null;
13692
- }
13693
15062
 
13694
15063
  class LocalConfigStore {
13695
15064
  db;
13696
15065
  mode = "local";
15066
+ v1BaseUrl = null;
13697
15067
  constructor(db) {
13698
15068
  this.db = db;
13699
15069
  }
@@ -13822,50 +15192,22 @@ class LocalConfigStore {
13822
15192
 
13823
15193
  class CloudConfigStore {
13824
15194
  mode = "api";
13825
- base;
13826
- apiKey;
13827
- timeoutMs;
13828
- constructor(config) {
13829
- this.base = `${config.apiUrl.replace(/\/+$/, "")}/v1`;
13830
- this.apiKey = config.apiKey;
13831
- this.timeoutMs = config.timeoutMs ?? 30000;
15195
+ v1BaseUrl;
15196
+ client;
15197
+ constructor(client) {
15198
+ this.client = client;
15199
+ this.v1BaseUrl = client.baseUrl;
13832
15200
  }
13833
15201
  async request(method, path, body, opts = {}) {
13834
- const controller = new AbortController;
13835
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
13836
- const headers = {
13837
- Authorization: `Bearer ${this.apiKey}`,
13838
- Accept: "application/json"
13839
- };
13840
- if (body !== undefined)
13841
- headers["Content-Type"] = "application/json";
13842
- if (opts.idempotent)
13843
- headers["Idempotency-Key"] = randomUUID6();
13844
15202
  try {
13845
- const res = await fetch(`${this.base}${path}`, {
13846
- method,
13847
- headers,
13848
- body: body === undefined ? undefined : JSON.stringify(body),
13849
- signal: controller.signal
15203
+ const data = await this.client.transport.request(method, path, body, {
15204
+ ...opts.idempotent ? { idempotencyKey: randomUUID6() } : {}
13850
15205
  });
13851
- if (res.status === 404 && opts.allow404)
15206
+ return { status: 200, data };
15207
+ } catch (err) {
15208
+ if (opts.allow404 && isNotFoundHttpError2(err))
13852
15209
  return { status: 404, data: null };
13853
- const text = await res.text();
13854
- let parsed = null;
13855
- if (text) {
13856
- try {
13857
- parsed = JSON.parse(text);
13858
- } catch {
13859
- parsed = text;
13860
- }
13861
- }
13862
- if (!res.ok) {
13863
- const message = (parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : undefined) ?? `HTTP ${res.status} on ${method} ${path}`;
13864
- throw new CloudHttpError(res.status, message, parsed);
13865
- }
13866
- return { status: res.status, data: parsed };
13867
- } finally {
13868
- clearTimeout(timer);
15210
+ throw err;
13869
15211
  }
13870
15212
  }
13871
15213
  async listConfigs(filter = {}) {
@@ -14021,7 +15363,7 @@ class CloudConfigStore {
14021
15363
  if (first.status !== 404) {
14022
15364
  if (isUsable(first.data))
14023
15365
  return first;
14024
- throw new CloudHttpError(502, "profile follow-up returned an invalid response", first.data);
15366
+ throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(idOrSlug)}`);
14025
15367
  }
14026
15368
  const profiles = await this.listProfiles();
14027
15369
  const profile = profiles.find((candidate) => candidate.id === idOrSlug || candidate.slug === idOrSlug);
@@ -14034,7 +15376,7 @@ class CloudConfigStore {
14034
15376
  if (response.status !== 404) {
14035
15377
  if (isUsable(response.data))
14036
15378
  return response;
14037
- throw new CloudHttpError(502, "profile follow-up returned an invalid response", response.data);
15379
+ throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(candidate)}`);
14038
15380
  }
14039
15381
  }
14040
15382
  return { status: 404, data: null };
@@ -14117,7 +15459,7 @@ class CloudConfigStore {
14117
15459
  }
14118
15460
  if (data && "complete" in data) {
14119
15461
  if (data.complete !== true || data.truncated !== false) {
14120
- throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
15462
+ throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
14121
15463
  }
14122
15464
  return { ...data, source_bounded: data.source_bounded ?? true };
14123
15465
  }
@@ -14133,7 +15475,7 @@ class CloudConfigStore {
14133
15475
  };
14134
15476
  }
14135
15477
  {
14136
- throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
15478
+ throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
14137
15479
  }
14138
15480
  }
14139
15481
  async registerMachine(hostname2, os, arch2) {
@@ -14156,14 +15498,19 @@ class CloudConfigStore {
14156
15498
  });
14157
15499
  }
14158
15500
  async reset() {
14159
- throw new Error("`init --force` cannot wipe the shared cloud store from a client. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to reset the local store instead.");
15501
+ throw new Error("`init --force` cannot wipe the shared cloud store from a client. " + "Point this run at the local store (HASNA_INSTRUCTIONS_LOCAL=1 with no hosted credential) to reset it instead.");
14160
15502
  }
14161
15503
  }
14162
- function resolveConfigStore(env = process.env) {
14163
- const cloud = resolveCloudConfig(env);
14164
- return cloud ? new CloudConfigStore(cloud) : new LocalConfigStore;
15504
+ function resolveConfigStore(env = process.env, options = {}) {
15505
+ if (selectsInstructionsLocalStore(env)) {
15506
+ if (env === process.env)
15507
+ announceLocalInstructionsMode();
15508
+ return new LocalConfigStore;
15509
+ }
15510
+ const { client } = resolveInstructionsStorageClient(env, options);
15511
+ return new CloudConfigStore(client);
14165
15512
  }
14166
- var CloudHttpError, API_URL_ENV = "HASNA_INSTRUCTIONS_API_URL", API_KEY_ENV = "HASNA_INSTRUCTIONS_API_KEY";
15513
+ var LOCAL_OPT_IN_ENV;
14167
15514
  var init_config_store = __esm(() => {
14168
15515
  init_configs();
14169
15516
  init_profiles();
@@ -14173,22 +15520,14 @@ var init_config_store = __esm(() => {
14173
15520
  init_types();
14174
15521
  init_bounded_read();
14175
15522
  init_instruction_graph();
14176
- init_retired_storage_mode();
14177
- CloudHttpError = class CloudHttpError extends Error {
14178
- status;
14179
- body;
14180
- constructor(status, message, body) {
14181
- super(message);
14182
- this.status = status;
14183
- this.body = body;
14184
- this.name = "CloudHttpError";
14185
- }
14186
- };
15523
+ init_transport_resolver();
15524
+ init_local_opt_in();
15525
+ LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV;
14187
15526
  });
14188
15527
 
14189
15528
  // src/lib/session-render-ownership.ts
14190
- import { existsSync as existsSync10, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
14191
- import { dirname as dirname4, join as join13, parse as parse3, relative as relative3, sep } from "path";
15529
+ import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
15530
+ import { dirname as dirname5, join as join13, parse as parse3, relative as relative3, sep } from "path";
14192
15531
  function toSegments(absolutePath2) {
14193
15532
  return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
14194
15533
  }
@@ -14207,7 +15546,7 @@ function pathIsSessionRenderManagedDir(absolutePath2) {
14207
15546
  function readManifestRelativePaths(manifestPath) {
14208
15547
  let stats;
14209
15548
  try {
14210
- if (!existsSync10(manifestPath))
15549
+ if (!existsSync11(manifestPath))
14211
15550
  return null;
14212
15551
  stats = statSync4(manifestPath);
14213
15552
  } catch {
@@ -14219,7 +15558,7 @@ function readManifestRelativePaths(manifestPath) {
14219
15558
  }
14220
15559
  let manifest;
14221
15560
  try {
14222
- manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
15561
+ manifest = JSON.parse(readFileSync6(manifestPath, "utf-8"));
14223
15562
  } catch {
14224
15563
  return null;
14225
15564
  }
@@ -14234,7 +15573,7 @@ function readManifestRelativePaths(manifestPath) {
14234
15573
  }
14235
15574
  function sessionRenderManifestClaimsPath(absolutePath2) {
14236
15575
  const root = parse3(absolutePath2).root;
14237
- let home = dirname4(absolutePath2);
15576
+ let home = dirname5(absolutePath2);
14238
15577
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
14239
15578
  const manifestPath = join13(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14240
15579
  const relativePaths = readManifestRelativePaths(manifestPath);
@@ -14243,7 +15582,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
14243
15582
  if (relativePaths.has(claimed))
14244
15583
  return true;
14245
15584
  }
14246
- const parent = dirname4(home);
15585
+ const parent = dirname5(home);
14247
15586
  if (parent === home || home === root)
14248
15587
  break;
14249
15588
  home = parent;
@@ -14271,17 +15610,17 @@ __export(exports_apply, {
14271
15610
  applyConfigs: () => applyConfigs,
14272
15611
  applyConfig: () => applyConfig
14273
15612
  });
14274
- import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
14275
- import { basename as basename5, dirname as dirname5, join as join14, resolve as resolve9 } from "path";
14276
- import { homedir as homedir10 } from "os";
15613
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync7, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
15614
+ import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve10 } from "path";
15615
+ import { homedir as homedir8 } from "os";
14277
15616
  function getConfigHome() {
14278
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir10();
15617
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
14279
15618
  }
14280
15619
  function expandPath(p) {
14281
15620
  if (p.startsWith("~/")) {
14282
- return resolve9(getConfigHome(), p.slice(2));
15621
+ return resolve10(getConfigHome(), p.slice(2));
14283
15622
  }
14284
- return resolve9(p);
15623
+ return resolve10(p);
14285
15624
  }
14286
15625
  function normalizeTargetPath(p) {
14287
15626
  const expanded = expandPath(p);
@@ -14291,14 +15630,14 @@ function normalizeTargetPath(p) {
14291
15630
  let current = expanded;
14292
15631
  const missingSegments = [];
14293
15632
  while (true) {
14294
- if (existsSync11(current)) {
15633
+ if (existsSync12(current)) {
14295
15634
  try {
14296
- return resolve9(realpathSync3(current), ...missingSegments);
15635
+ return resolve10(realpathSync3(current), ...missingSegments);
14297
15636
  } catch {
14298
15637
  return expanded;
14299
15638
  }
14300
15639
  }
14301
- const parent = dirname5(current);
15640
+ const parent = dirname6(current);
14302
15641
  const name = basename5(current);
14303
15642
  if (parent === current)
14304
15643
  return expanded;
@@ -14322,11 +15661,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
14322
15661
  }
14323
15662
  const path = expandPath(renderedTargetPath);
14324
15663
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
14325
- const previousContent = existsSync11(path) ? readFileSync6(path, "utf-8") : null;
15664
+ const previousContent = existsSync12(path) ? readFileSync7(path, "utf-8") : null;
14326
15665
  const changed = previousContent !== renderedForTarget;
14327
15666
  if (!opts.dryRun) {
14328
- const dir = dirname5(path);
14329
- if (!existsSync11(dir)) {
15667
+ const dir = dirname6(path);
15668
+ if (!existsSync12(dir)) {
14330
15669
  mkdirSync3(dir, { recursive: true });
14331
15670
  }
14332
15671
  if (previousContent !== null && changed) {
@@ -14360,9 +15699,9 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
14360
15699
  let current;
14361
15700
  try {
14362
15701
  const path = expandPath(targetPath);
14363
- if (!existsSync11(path))
15702
+ if (!existsSync12(path))
14364
15703
  return [];
14365
- current = readFileSync6(path, "utf-8");
15704
+ current = readFileSync7(path, "utf-8");
14366
15705
  } catch {
14367
15706
  return secretTokens;
14368
15707
  }
@@ -14704,20 +16043,20 @@ var init_apply = __esm(() => {
14704
16043
  });
14705
16044
 
14706
16045
  // src/lib/sync-dir.ts
14707
- import { existsSync as existsSync12, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
16046
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
14708
16047
  import { join as join15, relative as relative4 } from "path";
14709
- import { homedir as homedir11 } from "os";
16048
+ import { homedir as homedir9 } from "os";
14710
16049
  function shouldSkip(p) {
14711
16050
  return SKIP.some((s) => p.includes(s));
14712
16051
  }
14713
16052
  async function syncFromDir(dir, opts = {}) {
14714
16053
  const store = opts.store ?? resolveConfigStore();
14715
16054
  const absDir = expandPath(dir);
14716
- if (!existsSync12(absDir))
16055
+ if (!existsSync13(absDir))
14717
16056
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
14718
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join15(absDir, f)).filter((f) => statSync5(f).isFile());
16057
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join15(absDir, f)).filter((f) => statSync5(f).isFile());
14719
16058
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
14720
- const home = homedir11();
16059
+ const home = homedir9();
14721
16060
  const allConfigs = await store.listConfigs();
14722
16061
  for (const file of files) {
14723
16062
  if (shouldSkip(file)) {
@@ -14725,7 +16064,7 @@ async function syncFromDir(dir, opts = {}) {
14725
16064
  continue;
14726
16065
  }
14727
16066
  try {
14728
- const content = readFileSync7(file, "utf-8");
16067
+ const content = readFileSync8(file, "utf-8");
14729
16068
  if (content.length > 500000) {
14730
16069
  result.skipped.push(file + " (too large)");
14731
16070
  continue;
@@ -14752,7 +16091,7 @@ async function syncFromDir(dir, opts = {}) {
14752
16091
  }
14753
16092
  async function syncToDir(dir, opts = {}) {
14754
16093
  const store = opts.store ?? resolveConfigStore();
14755
- const home = homedir11();
16094
+ const home = homedir9();
14756
16095
  const absDir = expandPath(dir);
14757
16096
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14758
16097
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14775,7 +16114,7 @@ async function syncToDir(dir, opts = {}) {
14775
16114
  return result;
14776
16115
  }
14777
16116
  function walkDir(dir, files = []) {
14778
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
16117
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
14779
16118
  const full = join15(dir, entry.name);
14780
16119
  if (shouldSkip(full))
14781
16120
  continue;
@@ -14811,7 +16150,7 @@ __export(exports_sync, {
14811
16150
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14812
16151
  CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14813
16152
  });
14814
- import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
16153
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
14815
16154
  import { basename as basename6, extname as extname3, join as join16 } from "path";
14816
16155
  function claudeRuleOutputs(fileName) {
14817
16156
  const stem = basename6(fileName, extname3(fileName));
@@ -14851,7 +16190,7 @@ function isGeneratedOutputTarget2(config, owners) {
14851
16190
  return !!ownerIds && !ownerIds.has(config.id);
14852
16191
  }
14853
16192
  function hasClaudePromptSource() {
14854
- return existsSync13(expandPath("~/.claude/CLAUDE.md"));
16193
+ return existsSync14(expandPath("~/.claude/CLAUDE.md"));
14855
16194
  }
14856
16195
  function hasClaudeRuleSourceForCursorTarget(targetPath) {
14857
16196
  const absoluteTargetPath = expandPath(targetPath);
@@ -14859,7 +16198,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
14859
16198
  if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
14860
16199
  return false;
14861
16200
  const stem = basename6(absoluteTargetPath, ".mdc");
14862
- return existsSync13(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync13(expandPath(`~/.claude/rules/${stem}.mdc`));
16201
+ return existsSync14(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync14(expandPath(`~/.claude/rules/${stem}.mdc`));
14863
16202
  }
14864
16203
  function isKnownGeneratedTargetPath(targetPath) {
14865
16204
  const normalizedTargetPath = normalizeTargetPath(targetPath);
@@ -14877,10 +16216,10 @@ async function syncProject(opts) {
14877
16216
  const machine = detectMachineContext();
14878
16217
  for (const pf of PROJECT_CONFIG_FILES) {
14879
16218
  const abs = join16(absDir, pf.file);
14880
- if (!existsSync13(abs))
16219
+ if (!existsSync14(abs))
14881
16220
  continue;
14882
16221
  try {
14883
- const rawContent = readFileSync8(abs, "utf-8");
16222
+ const rawContent = readFileSync9(abs, "utf-8");
14884
16223
  if (rawContent.length > 500000) {
14885
16224
  result.skipped.push(pf.file);
14886
16225
  continue;
@@ -14917,12 +16256,12 @@ async function syncProject(opts) {
14917
16256
  { dir: join16(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14918
16257
  { dir: join16(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14919
16258
  ]) {
14920
- if (!existsSync13(ruleDir.dir))
16259
+ if (!existsSync14(ruleDir.dir))
14921
16260
  continue;
14922
- const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
16261
+ const mdFiles = readdirSync3(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14923
16262
  for (const f of mdFiles) {
14924
16263
  const abs = join16(ruleDir.dir, f);
14925
- const raw = readFileSync8(abs, "utf-8");
16264
+ const raw = readFileSync9(abs, "utf-8");
14926
16265
  const redacted = redactContent(raw, "markdown");
14927
16266
  const machineAware = templateizeMachineContent(redacted.content, machine);
14928
16267
  const content = machineAware.content;
@@ -14961,12 +16300,12 @@ async function syncKnown(opts = {}) {
14961
16300
  for (const known of targets) {
14962
16301
  if (known.rulesDir) {
14963
16302
  const absDir = expandPath(known.rulesDir);
14964
- if (!existsSync13(absDir)) {
16303
+ if (!existsSync14(absDir)) {
14965
16304
  result.skipped.push(known.rulesDir);
14966
16305
  continue;
14967
16306
  }
14968
16307
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
14969
- const ruleFiles = readdirSync2(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
16308
+ const ruleFiles = readdirSync3(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
14970
16309
  for (const f of ruleFiles) {
14971
16310
  const abs2 = join16(absDir, f);
14972
16311
  const targetPath = abs2.replace(home, "~");
@@ -14974,7 +16313,7 @@ async function syncKnown(opts = {}) {
14974
16313
  result.skipped.push(`${targetPath} (generated output)`);
14975
16314
  continue;
14976
16315
  }
14977
- const raw = readFileSync8(abs2, "utf-8");
16316
+ const raw = readFileSync9(abs2, "utf-8");
14978
16317
  const redacted = redactContent(raw, "markdown");
14979
16318
  const machineAware = templateizeMachineContent(redacted.content, machine);
14980
16319
  const content = machineAware.content;
@@ -15002,12 +16341,12 @@ async function syncKnown(opts = {}) {
15002
16341
  continue;
15003
16342
  }
15004
16343
  const abs = expandPath(known.path);
15005
- if (!existsSync13(abs)) {
16344
+ if (!existsSync14(abs)) {
15006
16345
  result.skipped.push(known.path);
15007
16346
  continue;
15008
16347
  }
15009
16348
  try {
15010
- const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
16349
+ const rawContent = normalizeKnownConfigSource(known, readFileSync9(abs, "utf-8"));
15011
16350
  if (rawContent.length > 500000) {
15012
16351
  result.skipped.push(known.path + " (too large)");
15013
16352
  continue;
@@ -15108,9 +16447,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
15108
16447
  }
15109
16448
  function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
15110
16449
  const path = expandPath(targetPath);
15111
- if (!existsSync13(path))
16450
+ if (!existsSync14(path))
15112
16451
  return `(file not found on disk: ${path})`;
15113
- const diskContent = readFileSync8(path, "utf-8");
16452
+ const diskContent = readFileSync9(path, "utf-8");
15114
16453
  if (diskContent === expectedContent)
15115
16454
  return "(no diff \u2014 identical)";
15116
16455
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -15343,16 +16682,16 @@ __export(exports_package_manager_guard, {
15343
16682
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15344
16683
  });
15345
16684
  import { execFileSync as execFileSync2 } from "child_process";
15346
- import { existsSync as existsSync22, lstatSync as lstatSync7, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
15347
- import { homedir as homedir14 } from "os";
15348
- import { basename as basename7, dirname as dirname10, isAbsolute as isAbsolute5, join as join24, relative as relative7, resolve as resolve14 } from "path";
16685
+ import { existsSync as existsSync23, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
16686
+ import { homedir as homedir12 } from "os";
16687
+ import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute6, join as join24, relative as relative7, resolve as resolve15 } from "path";
15349
16688
  function scanPackageManagerSecrets(options = {}) {
15350
- const cwd = options.cwd ? resolve14(options.cwd) : process.cwd();
15351
- const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve14(cwd, root));
16689
+ const cwd = options.cwd ? resolve15(options.cwd) : process.cwd();
16690
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve15(cwd, root));
15352
16691
  const findings = [];
15353
16692
  let scannedFiles = 0;
15354
16693
  for (const root of roots) {
15355
- if (!existsSync22(root))
16694
+ if (!existsSync23(root))
15356
16695
  continue;
15357
16696
  const stat = lstatSync7(root);
15358
16697
  if (stat.isFile()) {
@@ -15362,7 +16701,7 @@ function scanPackageManagerSecrets(options = {}) {
15362
16701
  if (text === null)
15363
16702
  continue;
15364
16703
  scannedFiles++;
15365
- findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
16704
+ findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname11(root)));
15366
16705
  continue;
15367
16706
  }
15368
16707
  if (!stat.isDirectory())
@@ -15379,10 +16718,10 @@ function scanPackageManagerSecrets(options = {}) {
15379
16718
  }
15380
16719
  }
15381
16720
  if (options.includeHome) {
15382
- const home = homedir14();
16721
+ const home = homedir12();
15383
16722
  for (const name of HOME_FILES) {
15384
16723
  const file = join24(home, name);
15385
- if (!existsSync22(file))
16724
+ if (!existsSync23(file))
15386
16725
  continue;
15387
16726
  const text = readTextFile(file);
15388
16727
  if (text === null)
@@ -15402,7 +16741,7 @@ function scanPackageManagerSecrets(options = {}) {
15402
16741
  function collectRepoFiles(root) {
15403
16742
  const out = [];
15404
16743
  const visit = (dir) => {
15405
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
16744
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
15406
16745
  if (entry.isDirectory()) {
15407
16746
  if (SKIP_DIRS.has(entry.name))
15408
16747
  continue;
@@ -15449,7 +16788,7 @@ function readTextFile(file) {
15449
16788
  const stat = lstatSync7(file);
15450
16789
  if (!stat.isFile() || stat.size > 5000000)
15451
16790
  return null;
15452
- const buf = readFileSync16(file);
16791
+ const buf = readFileSync17(file);
15453
16792
  if (buf.includes(0))
15454
16793
  return null;
15455
16794
  return buf.toString("utf-8");
@@ -15649,7 +16988,7 @@ function trackedFiles(root) {
15649
16988
  }
15650
16989
  function isTrackedFile(file) {
15651
16990
  try {
15652
- const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
16991
+ const repoRoot = execFileSync2("git", ["-C", dirname11(file), "rev-parse", "--show-toplevel"], {
15653
16992
  encoding: "utf-8",
15654
16993
  stdio: ["ignore", "pipe", "ignore"]
15655
16994
  }).trim();
@@ -15679,10 +17018,10 @@ function stripInlineComment(value) {
15679
17018
  return value.replace(/\s[#;].*$/, "").trim();
15680
17019
  }
15681
17020
  function displayPath(file, root) {
15682
- const home = homedir14();
17021
+ const home = homedir12();
15683
17022
  if (root === home && (file === home || file.startsWith(home + "/")))
15684
17023
  return "~/" + toPosix(relative7(home, file));
15685
- if (isAbsolute5(root) && file.startsWith(root + "/"))
17024
+ if (isAbsolute6(root) && file.startsWith(root + "/"))
15686
17025
  return toPosix(relative7(root, file));
15687
17026
  if (file === home || file.startsWith(home + "/"))
15688
17027
  return "~/" + toPosix(relative7(home, file));
@@ -15734,12 +17073,12 @@ var init_package_manager_guard = __esm(() => {
15734
17073
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
15735
17074
  import { Buffer as Buffer2 } from "buffer";
15736
17075
  import { existsSync as existsSync2 } from "fs";
15737
- import { join as join3 } from "path";
17076
+ import { join as join2 } from "path";
15738
17077
  import { existsSync } from "fs";
15739
- import { homedir as homedir2 } from "os";
15740
- import { join as join2, resolve } from "path";
15741
17078
  import { homedir } from "os";
15742
- import { join } from "path";
17079
+ import { join, resolve } from "path";
17080
+ import { homedir as pathsResolverHomedir } from "os";
17081
+ import { join as pathsResolverJoin } from "path";
15743
17082
  import { createHmac, timingSafeEqual } from "crypto";
15744
17083
  import { lookup as dnsLookup } from "dns/promises";
15745
17084
  import { isIP } from "net";
@@ -15843,75 +17182,72 @@ function channelMatchesEvent(channel, event) {
15843
17182
  return true;
15844
17183
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
15845
17184
  }
15846
- var KIND_ENV = {
17185
+ var PATHS_RESOLVER_KIND_ENV = {
15847
17186
  config: "HASNA_CONFIG_HOME",
15848
17187
  data: "HASNA_DATA_HOME",
15849
17188
  state: "HASNA_STATE_HOME",
15850
17189
  cache: "HASNA_CACHE_HOME"
15851
17190
  };
15852
- var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
15853
- function assertApp(app) {
17191
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
17192
+ function pathsResolverAssertApp(app) {
15854
17193
  if (typeof app !== "string" || app.length === 0) {
15855
17194
  throw new TypeError("paths: app must be a non-empty string");
15856
17195
  }
15857
- if (!APP_SLUG_RE.test(app)) {
17196
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
15858
17197
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
15859
17198
  }
15860
17199
  }
15861
- function envOf(options) {
15862
- return options.env ?? process.env;
15863
- }
15864
- function envValue(options, kind) {
15865
- const value = envOf(options)[KIND_ENV[kind]];
15866
- return typeof value === "string" && value.length > 0 ? value : undefined;
15867
- }
15868
- function isMacOS(platform) {
15869
- return platform === "darwin";
17200
+ function pathsResolverAssertKind(kind) {
17201
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
17202
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
17203
+ }
15870
17204
  }
15871
- function baseDir(kind, options) {
15872
- const override = envValue(options, kind);
15873
- if (override)
17205
+ function pathsResolverBaseDir(kind, options) {
17206
+ pathsResolverAssertKind(kind);
17207
+ const env = options.env ?? process.env;
17208
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
17209
+ if (typeof override === "string" && override.length > 0)
15874
17210
  return override;
15875
- const home = options.home ?? homedir();
17211
+ const home = options.home ?? pathsResolverHomedir();
15876
17212
  const platform = options.platform ?? process.platform;
15877
- if (isMacOS(platform)) {
17213
+ if (platform === "darwin") {
15878
17214
  switch (kind) {
15879
17215
  case "config":
15880
17216
  case "data":
15881
- return join(home, "Library", "Application Support", "Hasna");
17217
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
15882
17218
  case "cache":
15883
- return join(home, "Library", "Caches", "Hasna");
17219
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
15884
17220
  case "state":
15885
- return join(home, "Library", "Logs", "Hasna");
17221
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
15886
17222
  }
15887
17223
  }
15888
17224
  switch (kind) {
15889
17225
  case "config":
15890
- return join(home, ".config", "hasna");
17226
+ return pathsResolverJoin(home, ".config", "hasna");
15891
17227
  case "data":
15892
- return join(home, ".local", "share", "hasna");
17228
+ return pathsResolverJoin(home, ".local", "share", "hasna");
15893
17229
  case "state":
15894
- return join(home, ".local", "state", "hasna");
17230
+ return pathsResolverJoin(home, ".local", "state", "hasna");
15895
17231
  case "cache":
15896
- return join(home, ".cache", "hasna");
17232
+ return pathsResolverJoin(home, ".cache", "hasna");
15897
17233
  }
15898
17234
  }
15899
- function resolvePath(kind, options) {
15900
- assertApp(options.app);
15901
- const appSegment = options.internal === true ? join("internal", options.app) : options.app;
15902
- return join(baseDir(kind, options), appSegment);
17235
+ function pathsResolverResolve(kind, options) {
17236
+ pathsResolverAssertApp(options.app);
17237
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
17238
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
15903
17239
  }
15904
17240
  function dataDir(options) {
15905
- return resolvePath("data", options);
17241
+ return pathsResolverResolve("data", options);
15906
17242
  }
15907
17243
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
15908
17244
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
15909
17245
  var EVENTS_STORE_SENTINEL_FILE = "events.json";
15910
17246
  function effectiveHome() {
15911
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
17247
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
15912
17248
  }
15913
17249
  function legacyHomeDir() {
15914
- return join2(effectiveHome(), ".hasna", "events");
17250
+ return join(effectiveHome(), ".hasna", "events");
15915
17251
  }
15916
17252
  function resolverHome() {
15917
17253
  return dataDir({ app: "events", home: effectiveHome() || undefined });
@@ -15920,7 +17256,7 @@ function adoptResolverHome(resolved, env = process.env) {
15920
17256
  const dataOverride = env.HASNA_DATA_HOME;
15921
17257
  if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
15922
17258
  return true;
15923
- return existsSync(join2(resolved, EVENTS_STORE_SENTINEL_FILE));
17259
+ return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
15924
17260
  }
15925
17261
  function exactEventsHome() {
15926
17262
  const dir = process.env[HASNA_EVENTS_DIR_ENV];
@@ -15961,9 +17297,9 @@ class JsonEventsStore {
15961
17297
  constructor(dataDir2 = getEventsDataDir()) {
15962
17298
  this.dataDir = dataDir2;
15963
17299
  this.runtime = localJsonRuntime(dataDir2);
15964
- this.channelsPath = join3(dataDir2, "channels.json");
15965
- this.eventsPath = join3(dataDir2, "events.json");
15966
- this.deliveriesPath = join3(dataDir2, "deliveries.json");
17300
+ this.channelsPath = join2(dataDir2, "channels.json");
17301
+ this.eventsPath = join2(dataDir2, "events.json");
17302
+ this.deliveriesPath = join2(dataDir2, "deliveries.json");
15967
17303
  }
15968
17304
  async init() {
15969
17305
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -16231,7 +17567,7 @@ async function getEventsStatus(dataDir2) {
16231
17567
  };
16232
17568
  }
16233
17569
  function statusFile(dataDir2, fileName, records) {
16234
- const path = join3(dataDir2, fileName);
17570
+ const path = join2(dataDir2, fileName);
16235
17571
  return { path, exists: existsSync2(path), records };
16236
17572
  }
16237
17573
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
@@ -17165,6 +18501,13 @@ function parseMatcherExpression(value, label) {
17165
18501
  negated: false
17166
18502
  };
17167
18503
  }
18504
+ function webhookTargetPolicyFromEnv() {
18505
+ const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
18506
+ if (!value)
18507
+ return;
18508
+ const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
18509
+ return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
18510
+ }
17168
18511
  var DEFAULT_EVENT_LIST_LIMIT = 100;
17169
18512
  function parseJsonObject(value, fallback) {
17170
18513
  if (!value)
@@ -17190,7 +18533,7 @@ function parseHeaders(values) {
17190
18533
  function createClient(options) {
17191
18534
  if (options.createClient)
17192
18535
  return options.createClient();
17193
- return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
18536
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
17194
18537
  }
17195
18538
  function print(value, json, text) {
17196
18539
  if (json)
@@ -17271,6 +18614,8 @@ function registerChannelCommands(program, options) {
17271
18614
  metadata: parseJsonObject(actionOptions.metadata, {})
17272
18615
  }, { honorFilters: actionOptions.honorFilters });
17273
18616
  print(result, json, `${result.status}: ${result.channelId}`);
18617
+ if (result.status === "failed")
18618
+ process.exitCode = 1;
17274
18619
  } catch (error) {
17275
18620
  fail(error, json);
17276
18621
  }
@@ -17379,9 +18724,9 @@ var {
17379
18724
  // src/cli/index.tsx
17380
18725
  init_apply();
17381
18726
  import chalk from "chalk";
17382
- import { existsSync as existsSync23, lstatSync as lstatSync8, readFileSync as readFileSync17, readSync, writeSync } from "fs";
17383
- import { homedir as homedir15 } from "os";
17384
- import { basename as basename8, join as join25, resolve as resolve15 } from "path";
18727
+ import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as readFileSync18, readSync, writeSync } from "fs";
18728
+ import { homedir as homedir13 } from "os";
18729
+ import { basename as basename8, join as join25, resolve as resolve16 } from "path";
17385
18730
 
17386
18731
  // src/lib/config-target-identity.ts
17387
18732
  init_apply();
@@ -17427,13 +18772,13 @@ init_redact();
17427
18772
 
17428
18773
  // src/lib/export.ts
17429
18774
  init_config_store();
17430
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
17431
- import { join as join17, resolve as resolve10 } from "path";
18775
+ import { existsSync as existsSync15, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
18776
+ import { join as join17, resolve as resolve11 } from "path";
17432
18777
  import { tmpdir } from "os";
17433
18778
  async function exportConfigs(outputPath, opts = {}) {
17434
18779
  const store = opts.store ?? resolveConfigStore();
17435
18780
  const configs = await store.listConfigs(opts.filter);
17436
- const absOutput = resolve10(outputPath);
18781
+ const absOutput = resolve11(outputPath);
17437
18782
  const tmpDir = join17(tmpdir(), `configs-export-${Date.now()}`);
17438
18783
  const contentsDir = join17(tmpDir, "contents");
17439
18784
  try {
@@ -17459,7 +18804,7 @@ async function exportConfigs(outputPath, opts = {}) {
17459
18804
  }
17460
18805
  return { path: absOutput, count: configs.length };
17461
18806
  } finally {
17462
- if (existsSync14(tmpDir)) {
18807
+ if (existsSync15(tmpDir)) {
17463
18808
  rmSync3(tmpDir, { recursive: true, force: true });
17464
18809
  }
17465
18810
  }
@@ -17467,13 +18812,13 @@ async function exportConfigs(outputPath, opts = {}) {
17467
18812
 
17468
18813
  // src/lib/import.ts
17469
18814
  init_config_store();
17470
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
17471
- import { join as join18, resolve as resolve11 } from "path";
18815
+ import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync10, rmSync as rmSync4 } from "fs";
18816
+ import { join as join18, resolve as resolve12 } from "path";
17472
18817
  import { tmpdir as tmpdir2 } from "os";
17473
18818
  async function importConfigs(bundlePath, opts = {}) {
17474
18819
  const store = opts.store ?? resolveConfigStore();
17475
18820
  const conflict = opts.conflict ?? "skip";
17476
- const absPath = resolve11(bundlePath);
18821
+ const absPath = resolve12(bundlePath);
17477
18822
  const tmpDir = join18(tmpdir2(), `configs-import-${Date.now()}`);
17478
18823
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
17479
18824
  try {
@@ -17488,14 +18833,14 @@ async function importConfigs(bundlePath, opts = {}) {
17488
18833
  throw new Error(`tar extraction failed: ${stderr}`);
17489
18834
  }
17490
18835
  const manifestPath = join18(tmpDir, "manifest.json");
17491
- if (!existsSync15(manifestPath))
18836
+ if (!existsSync16(manifestPath))
17492
18837
  throw new Error("Invalid bundle: missing manifest.json");
17493
- const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
18838
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
17494
18839
  for (const meta of manifest.configs) {
17495
18840
  try {
17496
18841
  const ext = meta.format === "text" ? "txt" : meta.format;
17497
18842
  const contentFile = join18(tmpDir, "contents", `${meta.slug}.${ext}`);
17498
- const content = existsSync15(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
18843
+ const content = existsSync16(contentFile) ? readFileSync10(contentFile, "utf-8") : "";
17499
18844
  let existing = null;
17500
18845
  try {
17501
18846
  existing = await store.getConfig(meta.slug);
@@ -17529,7 +18874,7 @@ async function importConfigs(bundlePath, opts = {}) {
17529
18874
  }
17530
18875
  return result;
17531
18876
  } finally {
17532
- if (existsSync15(tmpDir)) {
18877
+ if (existsSync16(tmpDir)) {
17533
18878
  rmSync4(tmpDir, { recursive: true, force: true });
17534
18879
  }
17535
18880
  }
@@ -17541,19 +18886,20 @@ init_machine();
17541
18886
 
17542
18887
  // src/lib/session-apply.ts
17543
18888
  init_project_context();
18889
+ init_session_render_state();
17544
18890
  init_session_render();
17545
18891
  init_cursor_authority();
17546
18892
  init_session_authority();
17547
18893
  import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
17548
18894
  import {
17549
- existsSync as existsSync16,
18895
+ existsSync as existsSync17,
17550
18896
  lstatSync as lstatSync4,
17551
18897
  mkdirSync as mkdirSync6,
17552
- readFileSync as readFileSync10,
17553
- readdirSync as readdirSync3,
18898
+ readFileSync as readFileSync11,
18899
+ readdirSync as readdirSync4,
17554
18900
  statSync as statSync6
17555
18901
  } from "fs";
17556
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join19, parse as parse4, relative as relative5, resolve as resolve12 } from "path";
18902
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join19, parse as parse4, relative as relative5, resolve as resolve13 } from "path";
17557
18903
 
17558
18904
  class SessionApplyError extends Error {
17559
18905
  constructor(message) {
@@ -17685,13 +19031,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
17685
19031
  throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
17686
19032
  }
17687
19033
  function ensureSessionTargetHome(targetHome) {
17688
- if (!existsSync16(targetHome))
19034
+ if (!existsSync17(targetHome))
17689
19035
  mkdirSync6(targetHome, { recursive: true, mode: 448 });
17690
19036
  assertSafeTargetHome(targetHome);
17691
19037
  }
17692
19038
  function checkSessionRenderDrift(targetHome, manifestPath) {
17693
19039
  const safeTargetHome = assertSafeTargetHome(targetHome);
17694
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
19040
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve13(manifestPath)), safeTargetHome) : resolve13(safeTargetHome, ".hasna", "session-render-manifest.json");
17695
19041
  const checkedAt = new Date().toISOString();
17696
19042
  const previousManifest = readPreviousManifest(resolvedManifestPath);
17697
19043
  if (!previousManifest) {
@@ -17708,7 +19054,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17708
19054
  const drifted = [];
17709
19055
  for (const file of previousManifest.files) {
17710
19056
  const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
17711
- if (!existsSync16(target)) {
19057
+ if (!existsSync17(target)) {
17712
19058
  missing.push({
17713
19059
  path: target,
17714
19060
  relativePath: file.relativePath,
@@ -17718,7 +19064,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17718
19064
  });
17719
19065
  continue;
17720
19066
  }
17721
- const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
19067
+ const actualSha256 = sha2568(readFileSync11(target, "utf-8"));
17722
19068
  if (actualSha256 !== file.sha256) {
17723
19069
  drifted.push({
17724
19070
  path: target,
@@ -17741,12 +19087,17 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17741
19087
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
17742
19088
  const snapshot = readSessionRenderSnapshot(snapshotPath);
17743
19089
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
17744
- const resolvedSnapshotPath = resolve12(snapshotPath);
17745
- const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
17746
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
17747
- throw new SessionApplyError("Session snapshot must be stored inside its target home.");
19090
+ const resolvedSnapshotPath = resolve13(snapshotPath);
19091
+ const snapshotDir = getSessionRenderSnapshotDir(targetHome);
19092
+ const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
19093
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute5(snapshotDirRelative);
19094
+ if (!insideSnapshotDir) {
19095
+ const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
19096
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute5(snapshotRelativePath)) {
19097
+ throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
19098
+ }
17748
19099
  }
17749
- assertNoSymlinkSegments2(targetHome, resolvedSnapshotPath);
19100
+ assertNoSymlinkSegments2(sessionRenderSnapshotWorkspaceRoot(targetHome), resolvedSnapshotPath);
17750
19101
  const guard = observeProjectContextSessionGuard({
17751
19102
  tool: snapshot.tool,
17752
19103
  target_home: targetHome,
@@ -17859,8 +19210,8 @@ function requiredRestoreHash(file) {
17859
19210
  return file.previousSha256;
17860
19211
  }
17861
19212
  function readSessionRenderSnapshot(snapshotPath) {
17862
- const resolved = resolve12(snapshotPath);
17863
- if (!existsSync16(resolved))
19213
+ const resolved = resolve13(snapshotPath);
19214
+ if (!existsSync17(resolved))
17864
19215
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
17865
19216
  const stat = lstatSync4(resolved);
17866
19217
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -17871,7 +19222,7 @@ function readSessionRenderSnapshot(snapshotPath) {
17871
19222
  }
17872
19223
  let parsed;
17873
19224
  try {
17874
- parsed = JSON.parse(readFileSync10(resolved, "utf8"));
19225
+ parsed = JSON.parse(readFileSync11(resolved, "utf8"));
17875
19226
  } catch {
17876
19227
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
17877
19228
  }
@@ -17940,7 +19291,7 @@ function readSessionRenderSnapshot(snapshotPath) {
17940
19291
  }
17941
19292
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
17942
19293
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
17943
- const manifestPath = resolve12(snapshot.manifestPath);
19294
+ const manifestPath = resolve13(snapshot.manifestPath);
17944
19295
  const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
17945
19296
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
17946
19297
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
@@ -17949,7 +19300,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17949
19300
  }
17950
19301
  let parsedManifest;
17951
19302
  try {
17952
- parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
19303
+ parsedManifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
17953
19304
  } catch {
17954
19305
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
17955
19306
  }
@@ -17957,7 +19308,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17957
19308
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
17958
19309
  }
17959
19310
  const appliedManifest = parsedManifest;
17960
- if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve12(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
19311
+ if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve13(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
17961
19312
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
17962
19313
  }
17963
19314
  const afterFiles = [];
@@ -18041,17 +19392,17 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
18041
19392
  if (!Number.isFinite(createdAtMs)) {
18042
19393
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
18043
19394
  }
18044
- for (const entry of readdirSync3(dirname6(snapshotPath))) {
18045
- const candidatePath = resolve12(dirname6(snapshotPath), entry);
18046
- if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
19395
+ for (const entry of readdirSync4(dirname7(snapshotPath))) {
19396
+ const candidatePath = resolve13(dirname7(snapshotPath), entry);
19397
+ if (candidatePath === resolve13(snapshotPath) || !entry.endsWith(".json"))
18047
19398
  continue;
18048
19399
  const candidateStat = lstatSync4(candidatePath);
18049
19400
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
18050
19401
  continue;
18051
19402
  try {
18052
- const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
19403
+ const candidate = JSON.parse(readFileSync11(candidatePath, "utf8"));
18053
19404
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
18054
- if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve12(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
19405
+ if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve13(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
18055
19406
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
18056
19407
  }
18057
19408
  } catch (error) {
@@ -18120,14 +19471,14 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
18120
19471
  }
18121
19472
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
18122
19473
  const path = resolveManifestRelativePath(relativePath, targetHome);
18123
- if (resolve12(recordedPath) !== path) {
19474
+ if (resolve13(recordedPath) !== path) {
18124
19475
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
18125
19476
  }
18126
19477
  return path;
18127
19478
  }
18128
19479
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
18129
19480
  const target = resolvePlannedFilePath(plan, file, targetHome);
18130
- const previousContent = existsSync16(target) ? readFileSync10(target, "utf-8") : null;
19481
+ const previousContent = existsSync17(target) ? readFileSync11(target, "utf-8") : null;
18131
19482
  const previousSha256 = previousContent === null ? null : sha2568(previousContent);
18132
19483
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
18133
19484
  const changed = previousContent !== file.content;
@@ -18226,9 +19577,9 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
18226
19577
  }
18227
19578
  function planStaleFileResult(file, targetHome, options) {
18228
19579
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
18229
- if (!existsSync16(target))
19580
+ if (!existsSync17(target))
18230
19581
  return null;
18231
- const previousContent = readFileSync10(target, "utf-8");
19582
+ const previousContent = readFileSync11(target, "utf-8");
18232
19583
  const previousSha256 = sha2568(previousContent);
18233
19584
  if (!options.force && previousSha256 !== file.sha256) {
18234
19585
  return {
@@ -18273,31 +19624,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
18273
19624
  return previousHashes.get(file.relativePath) === previousSha256;
18274
19625
  }
18275
19626
  function resolvePlannedFilePath(plan, file, targetHome) {
18276
- const target = resolve12(targetHome, ...file.relativePath.split("/"));
19627
+ const target = resolve13(targetHome, ...file.relativePath.split("/"));
18277
19628
  const rel = relative5(targetHome, target);
18278
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
19629
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
18279
19630
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
18280
19631
  }
18281
- if (resolve12(file.path) !== target) {
19632
+ if (resolve13(file.path) !== target) {
18282
19633
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
18283
19634
  }
18284
19635
  assertNoSymlinkSegments2(targetHome, target);
18285
19636
  return target;
18286
19637
  }
18287
19638
  function resolveManifestRelativePath(relativePath, targetHome) {
18288
- const target = resolve12(targetHome, ...relativePath.split(/[\\/]+/));
19639
+ const target = resolve13(targetHome, ...relativePath.split(/[\\/]+/));
18289
19640
  const rel = relative5(targetHome, target);
18290
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
19641
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
18291
19642
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
18292
19643
  }
18293
19644
  assertNoSymlinkSegments2(targetHome, target);
18294
19645
  return target;
18295
19646
  }
18296
19647
  function readPreviousManifest(path) {
18297
- if (!existsSync16(path))
19648
+ if (!existsSync17(path))
18298
19649
  return null;
18299
19650
  try {
18300
- const parsed = JSON.parse(readFileSync10(path, "utf-8"));
19651
+ const parsed = JSON.parse(readFileSync11(path, "utf-8"));
18301
19652
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
18302
19653
  return null;
18303
19654
  if (!Array.isArray(parsed.files))
@@ -18336,13 +19687,13 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
18336
19687
  }
18337
19688
  function currentSessionFileHash(path, targetHome) {
18338
19689
  assertNoSymlinkSegments2(targetHome, path);
18339
- if (!existsSync16(path))
19690
+ if (!existsSync17(path))
18340
19691
  return null;
18341
19692
  const stat = lstatSync4(path);
18342
19693
  if (stat.isSymbolicLink() || !stat.isFile()) {
18343
19694
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
18344
19695
  }
18345
- return sha2568(readFileSync10(path, "utf-8"));
19696
+ return sha2568(readFileSync11(path, "utf-8"));
18346
19697
  }
18347
19698
  function requiredPreviousHash(result) {
18348
19699
  if (result.previousSha256 === null) {
@@ -18351,8 +19702,8 @@ function requiredPreviousHash(result) {
18351
19702
  return result.previousSha256;
18352
19703
  }
18353
19704
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
18354
- const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync16(result.path)).map((result) => {
18355
- const content = readFileSync10(result.path, "utf-8");
19705
+ const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync17(result.path)).map((result) => {
19706
+ const content = readFileSync11(result.path, "utf-8");
18356
19707
  return {
18357
19708
  path: result.path,
18358
19709
  relativePath: result.relativePath,
@@ -18370,7 +19721,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
18370
19721
  };
18371
19722
  }
18372
19723
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
18373
- const snapshotPath = resolve12(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
19724
+ const snapshotPath = join19(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID7()}.json`);
18374
19725
  const afterFiles = results.map((result) => {
18375
19726
  if (result.action === "conflict") {
18376
19727
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -18400,7 +19751,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
18400
19751
  path: snapshotPath,
18401
19752
  content: `${JSON.stringify(snapshot, null, 2)}
18402
19753
  `,
18403
- workspace_root: targetHome,
19754
+ workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
18404
19755
  default_mode: 384,
18405
19756
  expected_hash: null,
18406
19757
  max_observed_bytes: null,
@@ -18416,14 +19767,14 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
18416
19767
  };
18417
19768
  }
18418
19769
  function assertSafeTargetHome(targetHome) {
18419
- if (!isAbsolute4(targetHome))
19770
+ if (!isAbsolute5(targetHome))
18420
19771
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
18421
- const normalized = resolve12(targetHome);
19772
+ const normalized = resolve13(targetHome);
18422
19773
  if (normalized === parse4(normalized).root) {
18423
19774
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
18424
19775
  }
18425
19776
  assertNoSymlinkAncestors2(normalized);
18426
- if (existsSync16(normalized) && lstatSync4(normalized).isSymbolicLink()) {
19777
+ if (existsSync17(normalized) && lstatSync4(normalized).isSymbolicLink()) {
18427
19778
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
18428
19779
  }
18429
19780
  return normalized;
@@ -18434,19 +19785,19 @@ function assertNoSymlinkSegments2(root, target) {
18434
19785
  let current = root;
18435
19786
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18436
19787
  current = join19(current, segment);
18437
- if (existsSync16(current) && lstatSync4(current).isSymbolicLink()) {
19788
+ if (existsSync17(current) && lstatSync4(current).isSymbolicLink()) {
18438
19789
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
18439
19790
  }
18440
19791
  }
18441
19792
  }
18442
19793
  function assertNoSymlinkAncestors2(path) {
18443
- const normalized = resolve12(path);
19794
+ const normalized = resolve13(path);
18444
19795
  const parsed = parse4(normalized);
18445
19796
  let current = parsed.root;
18446
19797
  const rel = relative5(parsed.root, normalized);
18447
19798
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18448
19799
  current = join19(current, segment);
18449
- if (!existsSync16(current))
19800
+ if (!existsSync17(current))
18450
19801
  return;
18451
19802
  if (lstatSync4(current).isSymbolicLink()) {
18452
19803
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
@@ -18496,10 +19847,10 @@ function formatGlobalSourceCoverageWarnings(result) {
18496
19847
 
18497
19848
  // src/lib/station-profile.ts
18498
19849
  init_raw_store_root();
18499
- import { spawnSync } from "child_process";
18500
- import { existsSync as existsSync17, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync4 } from "fs";
18501
- import { arch as osArch, homedir as homedir12, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
18502
- import { dirname as dirname7, join as join20 } from "path";
19850
+ import { spawnSync as spawnSync2 } from "child_process";
19851
+ import { existsSync as existsSync18, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
19852
+ import { arch as osArch, homedir as homedir10, hostname as osHostname2, platform as osPlatform, userInfo as osUserInfo } from "os";
19853
+ import { dirname as dirname8, join as join20 } from "path";
18503
19854
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
18504
19855
  var STATION_PROFILE_SOURCE_ID = "station-profile";
18505
19856
  var STATION_PROFILE_LAYER = "machine";
@@ -18509,23 +19860,23 @@ var STATION_PROFILE_FULL_NAMES_MAX = 6;
18509
19860
  var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
18510
19861
  var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
18511
19862
  var BUN_INSTALL_ENV = "BUN_INSTALL";
18512
- function homeDir3(env = process.env) {
18513
- return env["HOME"] || env["USERPROFILE"] || homedir12();
19863
+ function homeDir5(env = process.env) {
19864
+ return env["HOME"] || env["USERPROFILE"] || homedir10();
18514
19865
  }
18515
19866
  function getStationProfileCachePath(env = process.env) {
18516
19867
  return join20(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
18517
19868
  }
18518
19869
  function getMachinesManifestPath(env = process.env) {
18519
- return env[MACHINES_MANIFEST_PATH_ENV] || join20(homeDir3(env), ".hasna", "machines", "machines.json");
19870
+ return env[MACHINES_MANIFEST_PATH_ENV] || join20(homeDir5(env), ".hasna", "machines", "machines.json");
18520
19871
  }
18521
19872
  function getBunGlobalModulesDir(env = process.env) {
18522
- return join20(env[BUN_INSTALL_ENV] || join20(homeDir3(env), ".bun"), "install", "global", "node_modules");
19873
+ return join20(env[BUN_INSTALL_ENV] || join20(homeDir5(env), ".bun"), "install", "global", "node_modules");
18523
19874
  }
18524
19875
  function readMachinesManifest(path) {
18525
19876
  try {
18526
- if (!existsSync17(path))
19877
+ if (!existsSync18(path))
18527
19878
  return null;
18528
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
19879
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
18529
19880
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
18530
19881
  return null;
18531
19882
  const machines = parsed["machines"];
@@ -18555,7 +19906,7 @@ function metadataUser(record) {
18555
19906
  }
18556
19907
  function probeMachineStatus(machineId) {
18557
19908
  try {
18558
- const result = spawnSync("machines", ["details", "--json", "--machine", machineId], {
19909
+ const result = spawnSync2("machines", ["details", "--json", "--machine", machineId], {
18559
19910
  encoding: "utf8",
18560
19911
  timeout: 3000,
18561
19912
  stdio: ["ignore", "pipe", "pipe"]
@@ -18577,9 +19928,9 @@ function probeMachineStatus(machineId) {
18577
19928
  }
18578
19929
  }
18579
19930
  function resolveStationProfileMachine(env = process.env, options = {}) {
18580
- const hostname2 = osHostname();
19931
+ const hostname2 = osHostname2();
18581
19932
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
18582
- const home = homeDir3(env);
19933
+ const home = homeDir5(env);
18583
19934
  const platform = stringField(record, "platform") ?? osPlatform();
18584
19935
  const workspacePath = stringField(record, "workspacePath") ?? join20(home, platform === "darwin" ? "Workspace" : "workspace");
18585
19936
  const machine = {
@@ -18600,7 +19951,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
18600
19951
  function scopedPackageNames(modulesDir, scope) {
18601
19952
  const scopeDir = join20(modulesDir, scope);
18602
19953
  try {
18603
- if (!existsSync17(scopeDir))
19954
+ if (!existsSync18(scopeDir))
18604
19955
  return null;
18605
19956
  return readdirNames(scopeDir).sort();
18606
19957
  } catch {
@@ -18608,7 +19959,7 @@ function scopedPackageNames(modulesDir, scope) {
18608
19959
  }
18609
19960
  }
18610
19961
  function readdirNames(dir) {
18611
- return readdirSync4(dir).filter((name) => {
19962
+ return readdirSync5(dir).filter((name) => {
18612
19963
  try {
18613
19964
  return lstatSync5(join20(dir, name)).isDirectory();
18614
19965
  } catch {
@@ -18620,7 +19971,7 @@ function resolveStationProfilePackages(env = process.env) {
18620
19971
  const modulesDir = getBunGlobalModulesDir(env);
18621
19972
  let scopeDirs;
18622
19973
  try {
18623
- if (!existsSync17(modulesDir))
19974
+ if (!existsSync18(modulesDir))
18624
19975
  return null;
18625
19976
  scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
18626
19977
  } catch {
@@ -18695,9 +20046,9 @@ function refreshStationProfile(options = {}) {
18695
20046
  const path = getStationProfileCachePath(env);
18696
20047
  const generatedAt = new Date().toISOString();
18697
20048
  if (!options.dryRun) {
18698
- const existing = existsSync17(path) ? readFileSync11(path, "utf8") : null;
20049
+ const existing = existsSync18(path) ? readFileSync12(path, "utf8") : null;
18699
20050
  if (existing !== content) {
18700
- mkdirSync7(dirname7(path), { recursive: true });
20051
+ mkdirSync7(dirname8(path), { recursive: true });
18701
20052
  writeFileSync4(path, content, "utf8");
18702
20053
  }
18703
20054
  }
@@ -18714,9 +20065,9 @@ function refreshStationProfile(options = {}) {
18714
20065
  function readStationProfile(env = process.env) {
18715
20066
  const path = getStationProfileCachePath(env);
18716
20067
  try {
18717
- if (!existsSync17(path))
20068
+ if (!existsSync18(path))
18718
20069
  return null;
18719
- return readFileSync11(path, "utf8");
20070
+ return readFileSync12(path, "utf8");
18720
20071
  } catch {
18721
20072
  return null;
18722
20073
  }
@@ -18743,9 +20094,6 @@ init_config_store();
18743
20094
  init_config_store();
18744
20095
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
18745
20096
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
18746
- PROJECT_DASHBOARD_DIR: ".hasna/project",
18747
- PROJECT_DASHBOARD_RENDER_MANIFEST: ".hasna/project/dashboard/render.json",
18748
- PROJECT_DASHBOARD_SNAPSHOTS_DIR: ".hasna/project/dashboard/snapshots",
18749
20097
  PROJECT_CHANNEL_PREFIX: ""
18750
20098
  };
18751
20099
  var PROJECT_DASHBOARD_STANDARD_CONTENT = `# Agent-Managed Project Dashboard Standard
@@ -18756,9 +20104,13 @@ evidence, tasks, knowledge, and dashboard output consistent.
18756
20104
 
18757
20105
  ## Canonical Files
18758
20106
 
18759
- - Project manifest root: \`.hasna/project/\`
18760
- - Dashboard render manifest: \`.hasna/project/dashboard/render.json\`
18761
- - Latest snapshot: \`.hasna/project/dashboard/snapshots/latest.snapshot.json\`
20107
+ There is exactly one project-layout convention: the canonical per-workspace
20108
+ store \`~/.hasna/projects/workspaces/<workspace_id>/\`. Never create a project
20109
+ layout directory inside the project folder itself.
20110
+
20111
+ - Per-workspace store root: \`~/.hasna/projects/workspaces/<workspace_id>/\`
20112
+ - Dashboard render manifest: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/render.json\`
20113
+ - Latest snapshot: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/snapshots/latest.snapshot.json\`
18762
20114
  - Dashboard schema ids come from \`@hasna/contracts\`.
18763
20115
  - Project folders may contain private documents, but render JSON must contain
18764
20116
  only ids, counts, statuses, resource refs, evidence refs, and redacted
@@ -19051,18 +20403,18 @@ init_codewith_shared_todos_storage_standard();
19051
20403
 
19052
20404
  // src/lib/managed-skill-runtimes.ts
19053
20405
  import { createHash as createHash9 } from "crypto";
19054
- import { spawnSync as spawnSync2 } from "child_process";
20406
+ import { spawnSync as spawnSync3 } from "child_process";
19055
20407
  import {
19056
- existsSync as existsSync18,
20408
+ existsSync as existsSync19,
19057
20409
  lstatSync as lstatSync6,
19058
20410
  mkdirSync as mkdirSync8,
19059
- readFileSync as readFileSync12,
20411
+ readFileSync as readFileSync13,
19060
20412
  renameSync as renameSync2,
19061
20413
  rmSync as rmSync5,
19062
20414
  writeFileSync as writeFileSync5
19063
20415
  } from "fs";
19064
- import { homedir as homedir13 } from "os";
19065
- import { dirname as dirname8, join as join21, parse as parse5, relative as relative6, resolve as resolve13 } from "path";
20416
+ import { homedir as homedir11 } from "os";
20417
+ import { dirname as dirname9, join as join21, parse as parse5, relative as relative6, resolve as resolve14 } from "path";
19066
20418
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
19067
20419
  var INBOX_SKILL_MARKERS = [
19068
20420
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -19083,13 +20435,13 @@ function lstatOrNull(path) {
19083
20435
  }
19084
20436
  }
19085
20437
  function findSymlinkedAncestor(path) {
19086
- const normalized = resolve13(path);
20438
+ const normalized = resolve14(path);
19087
20439
  const parsed = parse5(normalized);
19088
20440
  let current = parsed.root;
19089
20441
  const rel = relative6(parsed.root, normalized);
19090
20442
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
19091
20443
  current = join21(current, segment);
19092
- if (!existsSync18(current))
20444
+ if (!existsSync19(current))
19093
20445
  return null;
19094
20446
  if (lstatSync6(current).isSymbolicLink())
19095
20447
  return current;
@@ -19110,7 +20462,7 @@ function packagedInboxSkillPath(explicitPath) {
19110
20462
  join21(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
19111
20463
  join21(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
19112
20464
  ];
19113
- const found = candidates.find((candidate) => existsSync18(candidate));
20465
+ const found = candidates.find((candidate) => existsSync19(candidate));
19114
20466
  if (!found) {
19115
20467
  throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
19116
20468
  }
@@ -19122,7 +20474,7 @@ function readCanonicalSkill(explicitPath) {
19122
20474
  if (!stat?.isFile()) {
19123
20475
  throw new Error("packaged inbox skill contract is not a regular file");
19124
20476
  }
19125
- const content = readFileSync12(assetPath, "utf8");
20477
+ const content = readFileSync13(assetPath, "utf8");
19126
20478
  if (!content.includes("conversations watch --from <agent> --all")) {
19127
20479
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
19128
20480
  }
@@ -19132,7 +20484,7 @@ function readCanonicalSkill(explicitPath) {
19132
20484
  return { content, sha256: sha2569(content) };
19133
20485
  }
19134
20486
  function runProbe(command, args) {
19135
- const result = spawnSync2(command, args, {
20487
+ const result = spawnSync3(command, args, {
19136
20488
  encoding: "utf8",
19137
20489
  timeout: 5000,
19138
20490
  stdio: ["ignore", "pipe", "pipe"]
@@ -19159,8 +20511,8 @@ function compareVersions(left, right) {
19159
20511
  }
19160
20512
  return 0;
19161
20513
  }
19162
- function inspectSkillMarkers(homeDir4) {
19163
- return INBOX_SKILL_MARKERS.map((parts) => join21(homeDir4, ...parts)).map((path) => {
20514
+ function inspectSkillMarkers(homeDir6) {
20515
+ return INBOX_SKILL_MARKERS.map((parts) => join21(homeDir6, ...parts)).map((path) => {
19164
20516
  const stat = lstatOrNull(path);
19165
20517
  if (!stat)
19166
20518
  return null;
@@ -19169,16 +20521,16 @@ function inspectSkillMarkers(homeDir4) {
19169
20521
  }
19170
20522
  return {
19171
20523
  path,
19172
- content: readFileSync12(path, "utf8"),
20524
+ content: readFileSync13(path, "utf8"),
19173
20525
  mode: stat.mode & 511,
19174
20526
  regular: true
19175
20527
  };
19176
20528
  }).filter((snapshot) => snapshot !== null);
19177
20529
  }
19178
20530
  function inspectInbox(options) {
19179
- const homeDir4 = options.homeDir ?? homedir13();
20531
+ const homeDir6 = options.homeDir ?? homedir11();
19180
20532
  const runtimeCommand = options.conversationsCommand ?? "conversations";
19181
- const snapshots = inspectSkillMarkers(homeDir4);
20533
+ const snapshots = inspectSkillMarkers(homeDir6);
19182
20534
  const skillPresent = snapshots.length > 0;
19183
20535
  let canonicalContent = null;
19184
20536
  let canonicalSha256 = null;
@@ -19204,7 +20556,7 @@ function inspectInbox(options) {
19204
20556
  let reason = "skill not installed";
19205
20557
  if (skillPresent) {
19206
20558
  const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
19207
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
20559
+ const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname9(path))).find((found) => found !== null);
19208
20560
  if (nonRegular)
19209
20561
  reason = "managed skill target is not a regular file";
19210
20562
  else if (symlinkAncestor)
@@ -19291,10 +20643,10 @@ function cleanup(path) {
19291
20643
  rmSync5(path, { force: true });
19292
20644
  }
19293
20645
  function writeAtomic(path, content, mode) {
19294
- assertNoSymlinkAncestors3(dirname8(path));
20646
+ assertNoSymlinkAncestors3(dirname9(path));
19295
20647
  const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
19296
20648
  try {
19297
- mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
20649
+ mkdirSync8(dirname9(path), { recursive: true, mode: 493 });
19298
20650
  writeFileSync5(tempPath, content, { mode, flag: "wx" });
19299
20651
  renameSync2(tempPath, path);
19300
20652
  } finally {
@@ -19303,7 +20655,7 @@ function writeAtomic(path, content, mode) {
19303
20655
  }
19304
20656
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
19305
20657
  lstat: lstatOrNull,
19306
- read: (path) => readFileSync12(path, "utf8"),
20658
+ read: (path) => readFileSync13(path, "utf8"),
19307
20659
  write: writeAtomic
19308
20660
  };
19309
20661
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -19370,7 +20722,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
19370
20722
  dry_run: dryRun
19371
20723
  };
19372
20724
  }
19373
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
20725
+ const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname9(path))).find((found) => found !== null);
19374
20726
  if (symlinkedAncestor) {
19375
20727
  return {
19376
20728
  runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
@@ -19453,28 +20805,28 @@ init_project_context();
19453
20805
  init_config_store();
19454
20806
  init_apply();
19455
20807
  init_config_agents();
19456
- import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
20808
+ import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
19457
20809
 
19458
20810
  // src/lib/package-version.ts
19459
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
19460
- import { dirname as dirname9, join as join22 } from "path";
20811
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
20812
+ import { dirname as dirname10, join as join22 } from "path";
19461
20813
  import { fileURLToPath } from "url";
19462
20814
  var cached = null;
19463
20815
  function getPackageVersion() {
19464
20816
  if (cached)
19465
20817
  return cached;
19466
20818
  try {
19467
- let dir = dirname9(fileURLToPath(import.meta.url));
20819
+ let dir = dirname10(fileURLToPath(import.meta.url));
19468
20820
  for (let i = 0;i < 8; i++) {
19469
20821
  const pkgPath = join22(dir, "package.json");
19470
- if (existsSync19(pkgPath)) {
19471
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
20822
+ if (existsSync20(pkgPath)) {
20823
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
19472
20824
  if (pkg.name === "@hasna/instructions" && pkg.version) {
19473
20825
  cached = pkg.version;
19474
20826
  return cached;
19475
20827
  }
19476
20828
  }
19477
- const parent = dirname9(dir);
20829
+ const parent = dirname10(dir);
19478
20830
  if (parent === dir)
19479
20831
  break;
19480
20832
  dir = parent;
@@ -19531,11 +20883,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
19531
20883
  continue;
19532
20884
  knownTargets += 1;
19533
20885
  const targetPath = expandPath(config.target_path);
19534
- if (!existsSync20(targetPath)) {
20886
+ if (!existsSync21(targetPath)) {
19535
20887
  missingTargets += 1;
19536
20888
  continue;
19537
20889
  }
19538
- const disk = readFileSync14(targetPath, "utf-8");
20890
+ const disk = readFileSync15(targetPath, "utf-8");
19539
20891
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
19540
20892
  if (redactedDisk !== config.content) {
19541
20893
  driftedTargets += 1;
@@ -19631,7 +20983,7 @@ init_config_store();
19631
20983
 
19632
20984
  // src/lib/provider-context.ts
19633
20985
  import { createHash as createHash10 } from "crypto";
19634
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
20986
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
19635
20987
  import { join as join23 } from "path";
19636
20988
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
19637
20989
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
@@ -19778,7 +21130,7 @@ function resolveAndRenderProviderContext(opts) {
19778
21130
  const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
19779
21131
  const content = renderProviderFragment(entry);
19780
21132
  const dir = join23(opts.homeDir, PROVIDER_CONTEXT_DIR);
19781
- if (!existsSync21(dir))
21133
+ if (!existsSync22(dir))
19782
21134
  mkdirSync9(dir, { recursive: true });
19783
21135
  const filename = `${entry ? entry.key : "invariant"}.md`;
19784
21136
  const fragmentPath2 = join23(dir, filename);
@@ -19787,8 +21139,8 @@ function resolveAndRenderProviderContext(opts) {
19787
21139
  const manifestPath = join23(dir, PROVIDER_CONTEXT_MANIFEST);
19788
21140
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
19789
21141
  try {
19790
- if (existsSync21(manifestPath)) {
19791
- const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
21142
+ if (existsSync22(manifestPath)) {
21143
+ const parsed = JSON.parse(readFileSync16(manifestPath, "utf8"));
19792
21144
  if (parsed && typeof parsed === "object")
19793
21145
  manifest = parsed;
19794
21146
  }
@@ -19826,8 +21178,8 @@ function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
19826
21178
  }
19827
21179
 
19828
21180
  // src/cli/index.tsx
19829
- import { createRequire } from "module";
19830
- var pkg = createRequire(import.meta.url)("../../package.json");
21181
+ import { createRequire as createRequire3 } from "module";
21182
+ var pkg = createRequire3(import.meta.url)("../../package.json");
19831
21183
  var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
19832
21184
  function writeStdout(text) {
19833
21185
  const buf = Buffer.from(text, "utf8");
@@ -19931,7 +21283,7 @@ function parseSessionSource(value, order) {
19931
21283
  if (!path)
19932
21284
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
19933
21285
  const absPath = resolveSessionPath(path);
19934
- if (!existsSync23(absPath))
21286
+ if (!existsSync24(absPath))
19935
21287
  throw new Error(`Instruction source file not found: ${absPath}`);
19936
21288
  const content = readSessionInstructionSourceFile(absPath);
19937
21289
  const source = sourceFromFilePath(absPath, content, order);
@@ -19985,7 +21337,7 @@ function readSessionInstructionSourceFile(path) {
19985
21337
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
19986
21338
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
19987
21339
  }
19988
- return readFileSync17(path, "utf-8");
21340
+ return readFileSync18(path, "utf-8");
19989
21341
  }
19990
21342
  function parseLayeredReference(value) {
19991
21343
  const trimmed = value.trim();
@@ -20012,9 +21364,9 @@ async function collectSessionSources(opts, tool, store) {
20012
21364
  }
20013
21365
  for (const value of opts.identityExport ?? []) {
20014
21366
  const path = resolveSessionPath(value);
20015
- if (!existsSync23(path))
21367
+ if (!existsSync24(path))
20016
21368
  throw new Error(`Identity instruction export not found: ${path}`);
20017
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
21369
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
20018
21370
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
20019
21371
  }
20020
21372
  return sources.map((source) => {
@@ -20146,7 +21498,7 @@ function readProjectContextBundleOption(value, allowMissing = false) {
20146
21498
  if (value === "-")
20147
21499
  return { json: readBoundedProjectContextStdin() };
20148
21500
  const path = resolveSessionPath(value);
20149
- if (!existsSync23(path)) {
21501
+ if (!existsSync24(path)) {
20150
21502
  if (allowMissing)
20151
21503
  return {};
20152
21504
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
@@ -20158,7 +21510,7 @@ function readProjectContextBundleOption(value, allowMissing = false) {
20158
21510
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
20159
21511
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
20160
21512
  }
20161
- return { json: readFileSync17(path, "utf8"), sourcePath: path };
21513
+ return { json: readFileSync18(path, "utf8"), sourcePath: path };
20162
21514
  }
20163
21515
  function readBoundedProjectContextStdin() {
20164
21516
  const chunks = [];
@@ -20317,16 +21669,16 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
20317
21669
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
20318
21670
  });
20319
21671
  program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
20320
- const abs = resolve15(filePath);
20321
- if (!existsSync23(abs)) {
21672
+ const abs = resolve16(filePath);
21673
+ if (!existsSync24(abs)) {
20322
21674
  console.error(chalk.red(`File not found: ${abs}`));
20323
21675
  process.exit(1);
20324
21676
  }
20325
- const rawContent = readFileSync17(abs, "utf-8");
21677
+ const rawContent = readFileSync18(abs, "utf-8");
20326
21678
  const storedFmt = detectFormat(abs);
20327
21679
  const fmt = redactFormatForTarget(abs, storedFmt);
20328
21680
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
20329
- const targetPath = abs.startsWith(homedir15()) ? abs.replace(homedir15(), "~") : abs;
21681
+ const targetPath = abs.startsWith(homedir13()) ? abs.replace(homedir13(), "~") : abs;
20330
21682
  const name = opts.name || filePath.split("/").pop();
20331
21683
  const store = resolveConfigStore();
20332
21684
  const allConfigs = await store.listConfigs();
@@ -20494,9 +21846,9 @@ program.command("sync").description("Sync known AI coding configs from disk into
20494
21846
  if (opts.project) {
20495
21847
  const dir = typeof opts.project === "string" ? opts.project : process.cwd();
20496
21848
  if (opts.all) {
20497
- const { readdirSync: readdirSync6 } = await import("fs");
21849
+ const { readdirSync: readdirSync7 } = await import("fs");
20498
21850
  const absDir = expandPath(dir);
20499
- const entries = readdirSync6(absDir, { withFileTypes: true });
21851
+ const entries = readdirSync7(absDir, { withFileTypes: true });
20500
21852
  let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0, projects = 0;
20501
21853
  for (const entry of entries) {
20502
21854
  if (!entry.isDirectory())
@@ -20514,7 +21866,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
20514
21866
  ".aicopilot",
20515
21867
  ".cursor",
20516
21868
  ".agents"
20517
- ].some((marker) => existsSync23(join25(projDir, marker)));
21869
+ ].some((marker) => existsSync24(join25(projDir, marker)));
20518
21870
  if (!hasAgentConfig)
20519
21871
  continue;
20520
21872
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -20565,10 +21917,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
20565
21917
  });
20566
21918
  program.command("whoami").description("Show setup summary").action(async () => {
20567
21919
  const store = resolveConfigStore();
20568
- const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
21920
+ const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
20569
21921
  const stats = await store.getConfigStats();
20570
21922
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
20571
- console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
21923
+ console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
20572
21924
  console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
20573
21925
  console.log();
20574
21926
  console.log(chalk.bold("By category:"));
@@ -21444,14 +22796,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
21444
22796
  } else if (target === "codex") {
21445
22797
  const { appendFileSync, existsSync: ex } = await import("fs");
21446
22798
  const { join: j } = await import("path");
21447
- const configPath = j(homedir15(), ".codex", "config.toml");
22799
+ const configPath = j(homedir13(), ".codex", "config.toml");
21448
22800
  const block = `
21449
22801
  [mcp_servers.configs]
21450
22802
  command = "${mcpBinary}"
21451
22803
  args = []
21452
22804
  `;
21453
22805
  if (ex(configPath)) {
21454
- const content = readFileSync17(configPath, "utf-8");
22806
+ const content = readFileSync18(configPath, "utf-8");
21455
22807
  if (content.includes("[mcp_servers.configs]")) {
21456
22808
  console.log(chalk.dim("= Already installed in Codex"));
21457
22809
  continue;
@@ -21462,7 +22814,7 @@ args = []
21462
22814
  } else if (target === "antigravity") {
21463
22815
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
21464
22816
  const { dirname: dn, join: j } = await import("path");
21465
- const configPath = j(homedir15(), ".gemini", "config", "mcp_config.json");
22817
+ const configPath = j(homedir13(), ".gemini", "config", "mcp_config.json");
21466
22818
  let settings = {};
21467
22819
  if (ex(configPath)) {
21468
22820
  try {
@@ -21546,9 +22898,9 @@ DB stats:`));
21546
22898
  if (count > 0)
21547
22899
  console.log(` ${key.padEnd(18)} ${count}`);
21548
22900
  }
21549
- const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
22901
+ const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
21550
22902
  console.log(chalk.dim(`
21551
- ${isApiTransport() ? "API" : "DB"}: ${location}`));
22903
+ ${store.mode === "api" ? "API" : "DB"}: ${location}`));
21552
22904
  });
21553
22905
  program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
21554
22906
  const status = await getConfigsStatus(resolveConfigStore());
@@ -21638,9 +22990,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
21638
22990
  console.log(chalk.cyan("Known files on disk:"));
21639
22991
  for (const k of KNOWN_CONFIGS) {
21640
22992
  if (k.rulesDir) {
21641
- existsSync23(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
22993
+ existsSync24(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
21642
22994
  } else {
21643
- existsSync23(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
22995
+ existsSync24(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
21644
22996
  }
21645
22997
  }
21646
22998
  const allConfigs = await store.listConfigs();
@@ -21794,16 +23146,16 @@ program.command("watch").description("Watch known config files for changes and a
21794
23146
  for (const k of KNOWN_CONFIGS) {
21795
23147
  if (k.rulesDir) {
21796
23148
  const absDir = expandPath2(k.rulesDir);
21797
- if (!existsSync23(absDir))
23149
+ if (!existsSync24(absDir))
21798
23150
  continue;
21799
- const { readdirSync: readdirSync6 } = await import("fs");
21800
- for (const f of readdirSync6(absDir).filter((f2) => f2.endsWith(".md"))) {
23151
+ const { readdirSync: readdirSync7 } = await import("fs");
23152
+ for (const f of readdirSync7(absDir).filter((f2) => f2.endsWith(".md"))) {
21801
23153
  const abs = join25(absDir, f);
21802
23154
  mtimes.set(abs, st(abs).mtimeMs);
21803
23155
  }
21804
23156
  } else {
21805
23157
  const abs = expandPath2(k.path);
21806
- if (existsSync23(abs))
23158
+ if (existsSync24(abs))
21807
23159
  mtimes.set(abs, st(abs).mtimeMs);
21808
23160
  }
21809
23161
  }
@@ -21811,7 +23163,7 @@ program.command("watch").description("Watch known config files for changes and a
21811
23163
  const tick = async () => {
21812
23164
  let changed = 0;
21813
23165
  for (const [abs, oldMtime] of mtimes) {
21814
- if (!existsSync23(abs))
23166
+ if (!existsSync24(abs))
21815
23167
  continue;
21816
23168
  const newMtime = st(abs).mtimeMs;
21817
23169
  if (newMtime !== oldMtime) {
@@ -21823,7 +23175,7 @@ program.command("watch").description("Watch known config files for changes and a
21823
23175
  for (const k of KNOWN_CONFIGS) {
21824
23176
  if (k.rulesDir) {
21825
23177
  const absDir = expandPath2(k.rulesDir);
21826
- if (!existsSync23(absDir))
23178
+ if (!existsSync24(absDir))
21827
23179
  continue;
21828
23180
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
21829
23181
  const abs = join25(absDir, f);
@@ -21834,7 +23186,7 @@ program.command("watch").description("Watch known config files for changes and a
21834
23186
  }
21835
23187
  } else {
21836
23188
  const abs = expandPath2(k.path);
21837
- if (existsSync23(abs) && !mtimes.has(abs)) {
23189
+ if (existsSync24(abs) && !mtimes.has(abs)) {
21838
23190
  mtimes.set(abs, st(abs).mtimeMs);
21839
23191
  changed++;
21840
23192
  }
@@ -21862,11 +23214,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
21862
23214
  if (!c.target_path)
21863
23215
  continue;
21864
23216
  const abs = expandPath(c.target_path);
21865
- if (!existsSync23(abs)) {
23217
+ if (!existsSync24(abs)) {
21866
23218
  missing++;
21867
23219
  continue;
21868
23220
  }
21869
- const disk = readFileSync17(abs, "utf-8");
23221
+ const disk = readFileSync18(abs, "utf-8");
21870
23222
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
21871
23223
  if (redactedDisk !== c.content)
21872
23224
  drifted++;
@@ -21933,7 +23285,7 @@ program.command("clean").description("Remove configs from DB whose target files
21933
23285
  if (!c.target_path)
21934
23286
  continue;
21935
23287
  const abs = expandPath(c.target_path);
21936
- if (!existsSync23(abs)) {
23288
+ if (!existsSync24(abs)) {
21937
23289
  if (printed < maxPrinted) {
21938
23290
  if (opts.dryRun) {
21939
23291
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -21966,12 +23318,9 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
21966
23318
  { name: "@hasna/skills", bin: "skills", mcp: "skills-mcp" },
21967
23319
  { name: "@hasna/economy", bin: "economy", mcp: "economy-mcp" },
21968
23320
  { name: "@hasna/attachments", bin: "attachments", mcp: "attachments-mcp" },
21969
- { name: "@hasna/sessions", bin: "sessions", mcp: "sessions-mcp" },
21970
23321
  { name: "@hasna/emails", bin: "emails", mcp: "emails-mcp" },
21971
23322
  { name: "@hasna/recordings", bin: "recordings", mcp: "recordings-mcp" },
21972
- { name: "@hasna/testers", bin: "testers", mcp: "testers-mcp" },
21973
- { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" },
21974
- { name: "@hasna/brains", bin: "brains", mcp: "brains-mcp" }
23323
+ { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" }
21975
23324
  ];
21976
23325
  console.log(chalk.bold("@hasna/instructions bootstrap") + chalk.dim(` \u2014 installing ${packages.length} ecosystem packages
21977
23326
  `));
@@ -22079,13 +23428,13 @@ providerContextCmd.command("resolve").description("Resolve the endpoint to a pro
22079
23428
  try {
22080
23429
  const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
22081
23430
  const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
22082
- const homeDir4 = opts.home ?? homedir15();
23431
+ const homeDir6 = opts.home ?? homedir13();
22083
23432
  const origin = normalizeEndpointOrigin(rawEndpoint);
22084
23433
  const resolution = resolveAndRenderProviderContext({
22085
23434
  origin,
22086
23435
  rawEndpoint,
22087
23436
  rawModel,
22088
- homeDir: homeDir4
23437
+ homeDir: homeDir6
22089
23438
  });
22090
23439
  if (opts.json) {
22091
23440
  printJson({