@hasna/instructions 0.5.6 → 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 (61) 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 +1711 -422
  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 +2486 -386
  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-render-state-hermeticity.test.d.ts +2 -0
  32. package/dist/lib/session-render-state-hermeticity.test.d.ts.map +1 -0
  33. package/dist/lib/session-render-state.d.ts +9 -0
  34. package/dist/lib/session-render-state.d.ts.map +1 -1
  35. package/dist/lib/transport-resolver.d.ts +80 -0
  36. package/dist/lib/transport-resolver.d.ts.map +1 -0
  37. package/dist/lib/transport-resolver.test.d.ts +2 -0
  38. package/dist/lib/transport-resolver.test.d.ts.map +1 -0
  39. package/dist/mcp/index.d.ts.map +1 -1
  40. package/dist/mcp/index.js +1439 -195
  41. package/dist/mcp/server.d.ts.map +1 -1
  42. package/dist/sdk/index.d.ts +22 -0
  43. package/dist/sdk/index.d.ts.map +1 -0
  44. package/dist/sdk/index.js +1042 -0
  45. package/dist/sdk/resolve.d.ts +82 -0
  46. package/dist/sdk/resolve.d.ts.map +1 -0
  47. package/dist/sdk/resolve.test.d.ts +2 -0
  48. package/dist/sdk/resolve.test.d.ts.map +1 -0
  49. package/dist/sdk/sdk-bundle-self-contained.test.d.ts +2 -0
  50. package/dist/sdk/sdk-bundle-self-contained.test.d.ts.map +1 -0
  51. package/dist/sdk/v1.generated.d.ts +288 -0
  52. package/dist/sdk/v1.generated.d.ts.map +1 -0
  53. package/dist/server/cloud.d.ts.map +1 -1
  54. package/dist/server/index.d.ts.map +1 -1
  55. package/dist/server/index.js +55 -64
  56. package/dist/test-support/preload-state-home.d.ts +2 -0
  57. package/dist/test-support/preload-state-home.d.ts.map +1 -0
  58. package/package.json +12 -12
  59. package/dashboard/README.md +0 -37
  60. package/dist/lib/retired-storage-mode.d.ts +0 -8
  61. 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,119 +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);
2219
- }
2220
- function stateDir(options) {
2221
- return resolvePath2("state", options);
2284
+ return pathsResolverResolve2("config", options);
2222
2285
  }
2223
- var KIND_ENV2, APP_SLUG_RE2;
2224
- var init_dist = __esm(() => {
2225
- KIND_ENV2 = {
2226
- config: "HASNA_CONFIG_HOME",
2227
- data: "HASNA_DATA_HOME",
2228
- state: "HASNA_STATE_HOME",
2229
- cache: "HASNA_CACHE_HOME"
2230
- };
2231
- APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2232
- });
2233
-
2234
- // src/lib/app-home.ts
2235
- import { existsSync as existsSync3 } from "fs";
2236
- import { homedir as homedir5 } from "os";
2237
- import { join as join6, resolve as resolve2 } from "path";
2238
2286
  function homeDir(env = process.env) {
2239
- return env["HOME"] || env["USERPROFILE"] || homedir5();
2287
+ return env["HOME"] || env["USERPROFILE"] || homedir2();
2240
2288
  }
2241
2289
  function legacyStoreHome(env = process.env) {
2242
- return resolve2(join6(homeDir(env), ".hasna", "instructions"));
2290
+ return resolve2(join3(homeDir(env), ".hasna", "instructions"));
2243
2291
  }
2244
2292
  function resolverStoreHome(env = process.env) {
2245
- return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir5() });
2293
+ return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir2() });
2246
2294
  }
2247
2295
  function adoptResolverStoreHome(resolved, env = process.env) {
2248
2296
  const override = env.HASNA_CONFIG_HOME;
2249
2297
  if (typeof override === "string" && override.trim().length > 0)
2250
2298
  return true;
2251
- return existsSync3(join6(resolved, "instructions.db"));
2299
+ return existsSync3(join3(resolved, "instructions.db"));
2252
2300
  }
2253
2301
  function exactStoreHome(env = process.env) {
2254
2302
  const v = env[HASNA_CONFIGS_HOME_ENV];
@@ -2261,9 +2309,15 @@ function getConfigsStoreHome(env = process.env) {
2261
2309
  const resolved = resolverStoreHome(env);
2262
2310
  return adoptResolverStoreHome(resolved, env) ? resolve2(resolved) : legacyStoreHome(env);
2263
2311
  }
2264
- 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";
2265
2313
  var init_app_home = __esm(() => {
2266
- 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]+)*$/;
2267
2321
  });
2268
2322
 
2269
2323
  // src/lib/raw-store-root.ts
@@ -2278,7 +2332,7 @@ var init_raw_store_root = __esm(() => {
2278
2332
  // src/db/database.ts
2279
2333
  import { Database } from "bun:sqlite";
2280
2334
  import { existsSync as existsSync5, mkdirSync, rmSync } from "fs";
2281
- import { join as join7 } from "path";
2335
+ import { join as join5 } from "path";
2282
2336
  import { randomUUID as randomUUID3 } from "crypto";
2283
2337
  function getDbPath() {
2284
2338
  if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
@@ -2286,7 +2340,7 @@ function getDbPath() {
2286
2340
  }
2287
2341
  const dir = getRawStoreRoot();
2288
2342
  mkdirSync(dir, { recursive: true });
2289
- return join7(dir, "instructions.db");
2343
+ return join5(dir, "instructions.db");
2290
2344
  }
2291
2345
  function uuid() {
2292
2346
  return randomUUID3();
@@ -2300,9 +2354,8 @@ function slugify(name) {
2300
2354
  function getDatabase(path) {
2301
2355
  if (_db)
2302
2356
  return _db;
2303
- assertNoLegacyStorageMode();
2304
- if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
2305
- 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.");
2306
2359
  }
2307
2360
  const dbPath = path || getDbPath();
2308
2361
  const db = new Database(dbPath);
@@ -2383,7 +2436,7 @@ function insertFeedback(input, db) {
2383
2436
  }
2384
2437
  var MIGRATIONS, _db = null;
2385
2438
  var init_database = __esm(() => {
2386
- init_retired_storage_mode();
2439
+ init_local_opt_in();
2387
2440
  init_raw_store_root();
2388
2441
  MIGRATIONS = [
2389
2442
  `
@@ -2764,9 +2817,9 @@ var init_template = __esm(() => {
2764
2817
  });
2765
2818
 
2766
2819
  // src/lib/machine.ts
2767
- 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";
2768
2821
  import { existsSync as existsSync6 } from "fs";
2769
- import { join as join8 } from "path";
2822
+ import { join as join6 } from "path";
2770
2823
  function normalizeOsFamily(os) {
2771
2824
  const value = (os ?? "").trim().toLowerCase();
2772
2825
  if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
@@ -2778,11 +2831,11 @@ function normalizeOsFamily(os) {
2778
2831
  return value || "unknown";
2779
2832
  }
2780
2833
  function detectMachineContext(overrides = {}) {
2781
- 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();
2782
2835
  const os = overrides.os ?? currentOsType();
2783
2836
  const osFamily = normalizeOsFamily(os);
2784
- const bunBinDir = overrides.bun_bin_dir ?? join8(homeDir2, ".bun", "bin");
2785
- 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");
2786
2839
  return {
2787
2840
  id: "current-machine",
2788
2841
  hostname: overrides.hostname ?? currentHostname(),
@@ -2792,10 +2845,10 @@ function detectMachineContext(overrides = {}) {
2792
2845
  created_at: "",
2793
2846
  os_family: osFamily,
2794
2847
  home_dir: homeDir2,
2795
- workspace_root: overrides.workspace_root ?? join8(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
2848
+ workspace_root: overrides.workspace_root ?? join6(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
2796
2849
  bun_bin_dir: bunBinDir,
2797
2850
  bun_path: overrides.bun_path ?? defaultBunPath,
2798
- 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)
2799
2852
  };
2800
2853
  }
2801
2854
  function machineContextToVariables(machine) {
@@ -7425,13 +7478,66 @@ var init_session_render_contract = __esm(() => {
7425
7478
 
7426
7479
  // src/lib/session-render-state.ts
7427
7480
  import { existsSync as existsSync7, readdirSync } from "fs";
7428
- import { homedir as homedir7 } from "os";
7429
- import { dirname, join as join9, resolve as resolve4 } from "path";
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
+ }
7430
7536
  function homeDir2(env = process.env) {
7431
- return env["HOME"] || env["USERPROFILE"] || homedir7();
7537
+ return env["HOME"] || env["USERPROFILE"] || homedir4();
7432
7538
  }
7433
7539
  function legacySnapshotDir(targetHome) {
7434
- return resolve4(join9(targetHome, ".hasna", "session-render-snapshots"));
7540
+ return resolve4(join7(targetHome, ".hasna", "session-render-snapshots"));
7435
7541
  }
7436
7542
  function resolverSnapshotDir(env = process.env) {
7437
7543
  const override = env.HASNA_STATE_HOME;
@@ -7467,9 +7573,15 @@ function getSessionRenderSnapshotDir(targetHome, env = process.env) {
7467
7573
  function sessionRenderSnapshotWorkspaceRoot(targetHome, env = process.env) {
7468
7574
  return resolveSessionRenderSnapshotLocation(targetHome, env).workspaceRoot;
7469
7575
  }
7470
- var SESSION_RENDER_STATE_APP = "instructions";
7576
+ var PATHS_RESOLVER_KIND_ENV3, PATHS_RESOLVER_APP_SLUG_RE3, SESSION_RENDER_STATE_APP = "instructions";
7471
7577
  var init_session_render_state = __esm(() => {
7472
- init_dist();
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]+)*$/;
7473
7585
  });
7474
7586
 
7475
7587
  // src/lib/project-context.ts
@@ -7492,7 +7604,7 @@ import {
7492
7604
  statSync,
7493
7605
  writeFileSync
7494
7606
  } from "fs";
7495
- import { basename, dirname as dirname2, isAbsolute, join as join10, parse, relative, resolve as resolve5 } from "path";
7607
+ import { basename, dirname as dirname2, isAbsolute, join as join8, parse, relative, resolve as resolve5 } from "path";
7496
7608
  function managedObservationMaxBytes(relativePath) {
7497
7609
  return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
7498
7610
  }
@@ -8734,7 +8846,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
8734
8846
  const previous = anchoredFileObservation(directory, targetName);
8735
8847
  const previousMode = previous?.mode ?? defaultMode;
8736
8848
  const tempName = `.project-context-${randomUUID5()}.tmp`;
8737
- const tempPath = join10(dir, tempName);
8849
+ const tempPath = join8(dir, tempName);
8738
8850
  let fd = null;
8739
8851
  let preserveTemp = false;
8740
8852
  let directoryChanged = false;
@@ -8865,7 +8977,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
8865
8977
  }
8866
8978
  const dir = dirname2(path);
8867
8979
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8868
- const tempPath = join10(dir, `.project-context-${randomUUID5()}.tmp`);
8980
+ const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
8869
8981
  let fd = null;
8870
8982
  let tempIdentity = null;
8871
8983
  try {
@@ -8922,7 +9034,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
8922
9034
  }
8923
9035
  const dir = dirname2(path);
8924
9036
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8925
- const tempPath = join10(dir, `.project-context-${randomUUID5()}.tmp`);
9037
+ const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
8926
9038
  const desiredHash = sha2562(content);
8927
9039
  let fd = null;
8928
9040
  let tempIdentity = null;
@@ -9021,7 +9133,7 @@ function removeProjectContextCoordinatedFile(input) {
9021
9133
  throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
9022
9134
  }
9023
9135
  displaced = true;
9024
- input.test_hooks?.after_displace?.(join10(dir, displacedName));
9136
+ input.test_hooks?.after_displace?.(join8(dir, displacedName));
9025
9137
  const moved = anchoredFileObservation(directory, displacedName);
9026
9138
  if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
9027
9139
  throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
@@ -9067,7 +9179,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
9067
9179
  }
9068
9180
  const dir = dirname2(path);
9069
9181
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
9070
- const displacedPath = join10(dir, `.project-context-delete-${randomUUID5()}.tmp`);
9182
+ const displacedPath = join8(dir, `.project-context-delete-${randomUUID5()}.tmp`);
9071
9183
  let displaced = false;
9072
9184
  try {
9073
9185
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -9138,7 +9250,7 @@ function anchoredOpenExclusive(directory, name, mode) {
9138
9250
  const requestedMode = mode & 4095;
9139
9251
  let fd;
9140
9252
  try {
9141
- fd = openSync(join10(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);
9142
9254
  } catch {
9143
9255
  throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
9144
9256
  }
@@ -9181,7 +9293,7 @@ function anchoredFileObservation(directory, name) {
9181
9293
  const stat = fstatSync(fd);
9182
9294
  if (!stat.isFile())
9183
9295
  throw new ProjectContextHashRace("managed output is not a regular file");
9184
- const relativePath = relativePosix(directory.workspaceRoot, join10(directory.path, name));
9296
+ const relativePath = relativePosix(directory.workspaceRoot, join8(directory.path, name));
9185
9297
  const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
9186
9298
  if (maxBytes !== null && stat.size > maxBytes) {
9187
9299
  throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
@@ -9207,7 +9319,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
9207
9319
  return observed;
9208
9320
  }
9209
9321
  function captureManagedDirectoryIdentity(path, workspaceRoot) {
9210
- assertNoSymlinkSegments(workspaceRoot, join10(path, ".project-context-directory-guard"));
9322
+ assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
9211
9323
  let stat;
9212
9324
  try {
9213
9325
  stat = lstatSync(path);
@@ -9220,7 +9332,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
9220
9332
  return { dev: stat.dev, ino: stat.ino };
9221
9333
  }
9222
9334
  function assertManagedDirectoryStable(path, workspaceRoot, expected) {
9223
- assertNoSymlinkSegments(workspaceRoot, join10(path, ".project-context-directory-guard"));
9335
+ assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
9224
9336
  let current;
9225
9337
  try {
9226
9338
  current = lstatSync(path);
@@ -9356,7 +9468,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
9356
9468
  const lockDirectory = resolve5(lockPath, "..");
9357
9469
  ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
9358
9470
  assertNoSymlinkSegments(workspaceRoot, lockPath);
9359
- const tempPath = join10(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9471
+ const tempPath = join8(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9360
9472
  let fd = null;
9361
9473
  let openedIdentity = null;
9362
9474
  let openedContentHash = null;
@@ -9587,7 +9699,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9587
9699
  return;
9588
9700
  }
9589
9701
  const lockDirectory = resolve5(lockPath, "..");
9590
- const releasePath = join10(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9702
+ const releasePath = join8(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9591
9703
  let releaseFd = null;
9592
9704
  let releaseIdentity = null;
9593
9705
  let releaseHash = null;
@@ -9667,7 +9779,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9667
9779
  const segments = rel.split(/[\\/]+/).filter(Boolean);
9668
9780
  let current = workspaceRoot;
9669
9781
  for (const segment of segments) {
9670
- current = join10(current, segment);
9782
+ current = join8(current, segment);
9671
9783
  if (existsSync8(current)) {
9672
9784
  if (lstatSync(current).isSymbolicLink())
9673
9785
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
@@ -9812,7 +9924,7 @@ function assertNoSymlinkSegments(root, target) {
9812
9924
  }
9813
9925
  let current = root;
9814
9926
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
9815
- current = join10(current, segment);
9927
+ current = join8(current, segment);
9816
9928
  if (existsSync8(current) && lstatSync(current).isSymbolicLink()) {
9817
9929
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
9818
9930
  }
@@ -9822,7 +9934,7 @@ function assertNoSymlinkAncestors(path) {
9822
9934
  const normalized = resolve5(path);
9823
9935
  let current = parse(normalized).root;
9824
9936
  for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
9825
- current = join10(current, segment);
9937
+ current = join8(current, segment);
9826
9938
  if (!existsSync8(current))
9827
9939
  return;
9828
9940
  if (lstatSync(current).isSymbolicLink())
@@ -10028,7 +10140,7 @@ function sha2562(content) {
10028
10140
  function isRecord(value) {
10029
10141
  return !!value && typeof value === "object" && !Array.isArray(value);
10030
10142
  }
10031
- 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;
10032
10144
  var init_project_context = __esm(() => {
10033
10145
  init_zod();
10034
10146
  init_redact();
@@ -10810,13 +10922,13 @@ var init_asset_plan = __esm(() => {
10810
10922
  // src/lib/cursor-authority.ts
10811
10923
  import { createHash as createHash4 } from "crypto";
10812
10924
  import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
10813
- import { homedir as homedir8 } from "os";
10814
- import { join as join11, resolve as resolve7 } from "path";
10925
+ import { homedir as homedir5 } from "os";
10926
+ import { join as join9, resolve as resolve7 } from "path";
10815
10927
  function sha2564(content) {
10816
10928
  return createHash4("sha256").update(content).digest("hex");
10817
10929
  }
10818
10930
  function homeDir3() {
10819
- return process.env["HOME"] || homedir8();
10931
+ return process.env["HOME"] || homedir5();
10820
10932
  }
10821
10933
  function markerPayload(content, markerLine, markerIndex) {
10822
10934
  const index = markerIndex ?? content.indexOf(markerLine);
@@ -10832,7 +10944,7 @@ function baseObservation(path) {
10832
10944
  };
10833
10945
  }
10834
10946
  function observeCursorGlobalAuthority(options = {}) {
10835
- const authorityPath = resolve7(join11(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10947
+ const authorityPath = resolve7(join9(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10836
10948
  const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
10837
10949
  return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
10838
10950
  }
@@ -10982,7 +11094,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10982
11094
  };
10983
11095
  }
10984
11096
  function isCursorGlobalAuthorityPath(path) {
10985
- return resolve7(path) === resolve7(join11(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
11097
+ return resolve7(path) === resolve7(join9(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10986
11098
  }
10987
11099
  function stampCursorGlobalAuthorityMarker(content) {
10988
11100
  const existing = content.match(CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN);
@@ -11038,13 +11150,13 @@ var init_cursor_authority = __esm(() => {
11038
11150
  // src/lib/session-authority.ts
11039
11151
  import { createHash as createHash5 } from "crypto";
11040
11152
  import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
11041
- import { homedir as homedir9 } from "os";
11042
- import { join as join12, resolve as resolve8 } from "path";
11153
+ import { homedir as homedir6 } from "os";
11154
+ import { join as join10, resolve as resolve8 } from "path";
11043
11155
  function sha2565(content) {
11044
11156
  return createHash5("sha256").update(content).digest("hex");
11045
11157
  }
11046
11158
  function configHomeDir() {
11047
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir9();
11159
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
11048
11160
  }
11049
11161
  function normalizeOwnedTargetPath(p) {
11050
11162
  const expanded = p.startsWith("~/") ? resolve8(configHomeDir(), p.slice(2)) : resolve8(p);
@@ -11055,7 +11167,7 @@ function normalizeOwnedTargetPath(p) {
11055
11167
  }
11056
11168
  }
11057
11169
  function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
11058
- const authorityPath = resolve8(join12(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
11170
+ const authorityPath = resolve8(join10(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
11059
11171
  let stat;
11060
11172
  try {
11061
11173
  stat = lstatSync3(authorityPath);
@@ -11145,8 +11257,8 @@ var init_session_authority = __esm(() => {
11145
11257
  // src/lib/session-render.ts
11146
11258
  import { createHash as createHash6 } from "crypto";
11147
11259
  import { existsSync as existsSync10, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
11148
- import { homedir as homedir10 } from "os";
11149
- import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join13, parse as parse2, posix as posix2, relative as relative2, resolve as resolve9 } from "path";
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";
11150
11262
  function normalizeSessionInstructionLayer(value) {
11151
11263
  if (value === "provider")
11152
11264
  return "tool";
@@ -11232,13 +11344,13 @@ function yamlQuote2(value) {
11232
11344
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
11233
11345
  }
11234
11346
  function defaultTargetHome(tool, profile, sessionId) {
11235
- const home = process.env["HOME"] || homedir10();
11236
- return join13(home, ".hasna", "accounts", "profiles", tool, slug(profile));
11347
+ const home = process.env["HOME"] || homedir7();
11348
+ return join11(home, ".hasna", "accounts", "profiles", tool, slug(profile));
11237
11349
  }
11238
11350
  function joinTarget(targetHome, relativePath) {
11239
11351
  const safeTargetHome = assertSafeTargetRoot(targetHome);
11240
11352
  const safeRelativePath2 = assertSafeRelativePath(relativePath);
11241
- return join13(safeTargetHome, ...safeRelativePath2.split("/"));
11353
+ return join11(safeTargetHome, ...safeRelativePath2.split("/"));
11242
11354
  }
11243
11355
  function makeFile(targetHome, relativePath, role, content, sourceIds) {
11244
11356
  const safeTargetHome = assertSafeTargetRoot(targetHome);
@@ -11996,7 +12108,7 @@ function adapterFor(input) {
11996
12108
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
11997
12109
  }
11998
12110
  function getHomeDir() {
11999
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir10();
12111
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
12000
12112
  }
12001
12113
  function cleanSessionPathInput(path) {
12002
12114
  const trimmed = path.trim();
@@ -12533,10 +12645,10 @@ function layerFromIdentityKind(kind, exportShape) {
12533
12645
  function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
12534
12646
  if (sourcePaths.length === 0 || !exportPath)
12535
12647
  return;
12536
- const baseDir3 = dirname4(resolveSessionPath(exportPath));
12648
+ const baseDir = dirname4(resolveSessionPath(exportPath));
12537
12649
  const contents = [];
12538
12650
  for (const sourcePath of sourcePaths) {
12539
- const content = readIdentitySourcePath(sourcePath, baseDir3, sourceId);
12651
+ const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
12540
12652
  if (content !== undefined)
12541
12653
  contents.push({ path: sourcePath.path, content });
12542
12654
  }
@@ -12549,8 +12661,8 @@ ${item.content.trimEnd()}`).join(`
12549
12661
 
12550
12662
  `));
12551
12663
  }
12552
- function readIdentitySourcePath(sourcePath, baseDir3, sourceId) {
12553
- const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir3, sourceId);
12664
+ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12665
+ const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
12554
12666
  if (!existsSync10(resolvedPath)) {
12555
12667
  if (sourcePath.required) {
12556
12668
  throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
@@ -12561,27 +12673,27 @@ function readIdentitySourcePath(sourcePath, baseDir3, sourceId) {
12561
12673
  if (!stat.isFile()) {
12562
12674
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
12563
12675
  }
12564
- const realBase = realpathSync2(baseDir3);
12676
+ const realBase = realpathSync2(baseDir);
12565
12677
  const realPath = realpathSync2(resolvedPath);
12566
12678
  if (!pathIsInside(realPath, realBase)) {
12567
12679
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
12568
12680
  }
12569
12681
  return readFileSync4(realPath, "utf-8");
12570
12682
  }
12571
- function resolveIdentitySourcePath(path, baseDir3, sourceId) {
12683
+ function resolveIdentitySourcePath(path, baseDir, sourceId) {
12572
12684
  const cleaned = cleanSessionPathInput(path);
12573
12685
  if (!cleaned)
12574
12686
  throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
12575
12687
  if (cleaned.includes("\\"))
12576
12688
  throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
12577
- const resolvedPath = isAbsolute3(cleaned) ? resolve9(cleaned) : resolve9(baseDir3, cleaned);
12578
- if (!pathIsInside(resolvedPath, resolve9(baseDir3))) {
12689
+ const resolvedPath = isAbsolute3(cleaned) ? resolve9(cleaned) : resolve9(baseDir, cleaned);
12690
+ if (!pathIsInside(resolvedPath, resolve9(baseDir))) {
12579
12691
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
12580
12692
  }
12581
12693
  return resolvedPath;
12582
12694
  }
12583
- function pathIsInside(path, baseDir3) {
12584
- const rel = relative2(baseDir3, path);
12695
+ function pathIsInside(path, baseDir) {
12696
+ const rel = relative2(baseDir, path);
12585
12697
  return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
12586
12698
  }
12587
12699
  function providerTargetsTool(targets, tool) {
@@ -13682,6 +13794,1222 @@ var init_machines = __esm(() => {
13682
13794
  init_database();
13683
13795
  });
13684
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
+
13685
15013
  // src/data/config-store.ts
13686
15014
  import { randomUUID as randomUUID6 } from "crypto";
13687
15015
  function parseBoundedPagePayload(value, label) {
@@ -13689,7 +15017,7 @@ function parseBoundedPagePayload(value, label) {
13689
15017
  const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
13690
15018
  const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
13691
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)) {
13692
- 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`);
13693
15021
  }
13694
15022
  return {
13695
15023
  ...page,
@@ -13704,50 +15032,38 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
13704
15032
  }
13705
15033
  }
13706
15034
  if (!Array.isArray(legacyItems)) {
13707
- 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`);
13708
15036
  }
13709
15037
  const normalized = normalizeBoundedReadOptions(options);
13710
15038
  const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
13711
15039
  return { ...page, source_bounded: false };
13712
15040
  }
13713
15041
  function isCloudAuthError(err) {
13714
- 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;
13715
15046
  }
13716
15047
  function formatCliError(err, env = process.env) {
13717
15048
  if (isCloudAuthError(err)) {
13718
- const apiUrl = env[API_URL_ENV]?.trim();
13719
- const detail = err.message?.trim();
13720
- const serverNote = detail && !/^HTTP \d+\b/.test(detail) ? ` Server said: ${detail}` : "";
13721
15049
  return [
13722
15050
  `Instructions cloud API rejected the request (HTTP ${err.status}: authentication failed).`,
13723
- serverNote,
13724
- ` 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).`,
13725
15052
  ` To continue, either:`,
13726
- ` - set a valid key: export ${API_KEY_ENV}=<new-key>`,
13727
- ` - or use the local store instead: unset ${API_URL_ENV} ${API_KEY_ENV}`
13728
- ].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(`
13729
15058
  `);
13730
15059
  }
13731
15060
  return err instanceof Error ? err.message : String(err);
13732
15061
  }
13733
- function resolveCloudConfig(env = process.env) {
13734
- assertNoLegacyStorageMode(env);
13735
- const apiUrl = env[API_URL_ENV]?.trim();
13736
- const apiKey = env[API_KEY_ENV]?.trim();
13737
- if (!apiUrl && !apiKey)
13738
- return null;
13739
- if (!apiUrl || !apiKey) {
13740
- 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.`);
13741
- }
13742
- return { apiUrl, apiKey };
13743
- }
13744
- function isApiTransport(env = process.env) {
13745
- return resolveCloudConfig(env) !== null;
13746
- }
13747
15062
 
13748
15063
  class LocalConfigStore {
13749
15064
  db;
13750
15065
  mode = "local";
15066
+ v1BaseUrl = null;
13751
15067
  constructor(db) {
13752
15068
  this.db = db;
13753
15069
  }
@@ -13876,50 +15192,22 @@ class LocalConfigStore {
13876
15192
 
13877
15193
  class CloudConfigStore {
13878
15194
  mode = "api";
13879
- base;
13880
- apiKey;
13881
- timeoutMs;
13882
- constructor(config) {
13883
- this.base = `${config.apiUrl.replace(/\/+$/, "")}/v1`;
13884
- this.apiKey = config.apiKey;
13885
- this.timeoutMs = config.timeoutMs ?? 30000;
15195
+ v1BaseUrl;
15196
+ client;
15197
+ constructor(client) {
15198
+ this.client = client;
15199
+ this.v1BaseUrl = client.baseUrl;
13886
15200
  }
13887
15201
  async request(method, path, body, opts = {}) {
13888
- const controller = new AbortController;
13889
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
13890
- const headers = {
13891
- Authorization: `Bearer ${this.apiKey}`,
13892
- Accept: "application/json"
13893
- };
13894
- if (body !== undefined)
13895
- headers["Content-Type"] = "application/json";
13896
- if (opts.idempotent)
13897
- headers["Idempotency-Key"] = randomUUID6();
13898
15202
  try {
13899
- const res = await fetch(`${this.base}${path}`, {
13900
- method,
13901
- headers,
13902
- body: body === undefined ? undefined : JSON.stringify(body),
13903
- signal: controller.signal
15203
+ const data = await this.client.transport.request(method, path, body, {
15204
+ ...opts.idempotent ? { idempotencyKey: randomUUID6() } : {}
13904
15205
  });
13905
- if (res.status === 404 && opts.allow404)
15206
+ return { status: 200, data };
15207
+ } catch (err) {
15208
+ if (opts.allow404 && isNotFoundHttpError2(err))
13906
15209
  return { status: 404, data: null };
13907
- const text = await res.text();
13908
- let parsed = null;
13909
- if (text) {
13910
- try {
13911
- parsed = JSON.parse(text);
13912
- } catch {
13913
- parsed = text;
13914
- }
13915
- }
13916
- if (!res.ok) {
13917
- const message = (parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : undefined) ?? `HTTP ${res.status} on ${method} ${path}`;
13918
- throw new CloudHttpError(res.status, message, parsed);
13919
- }
13920
- return { status: res.status, data: parsed };
13921
- } finally {
13922
- clearTimeout(timer);
15210
+ throw err;
13923
15211
  }
13924
15212
  }
13925
15213
  async listConfigs(filter = {}) {
@@ -14075,7 +15363,7 @@ class CloudConfigStore {
14075
15363
  if (first.status !== 404) {
14076
15364
  if (isUsable(first.data))
14077
15365
  return first;
14078
- 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)}`);
14079
15367
  }
14080
15368
  const profiles = await this.listProfiles();
14081
15369
  const profile = profiles.find((candidate) => candidate.id === idOrSlug || candidate.slug === idOrSlug);
@@ -14088,7 +15376,7 @@ class CloudConfigStore {
14088
15376
  if (response.status !== 404) {
14089
15377
  if (isUsable(response.data))
14090
15378
  return response;
14091
- 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)}`);
14092
15380
  }
14093
15381
  }
14094
15382
  return { status: 404, data: null };
@@ -14171,7 +15459,7 @@ class CloudConfigStore {
14171
15459
  }
14172
15460
  if (data && "complete" in data) {
14173
15461
  if (data.complete !== true || data.truncated !== false) {
14174
- 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");
14175
15463
  }
14176
15464
  return { ...data, source_bounded: data.source_bounded ?? true };
14177
15465
  }
@@ -14187,7 +15475,7 @@ class CloudConfigStore {
14187
15475
  };
14188
15476
  }
14189
15477
  {
14190
- 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");
14191
15479
  }
14192
15480
  }
14193
15481
  async registerMachine(hostname2, os, arch2) {
@@ -14210,14 +15498,19 @@ class CloudConfigStore {
14210
15498
  });
14211
15499
  }
14212
15500
  async reset() {
14213
- 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.");
14214
15502
  }
14215
15503
  }
14216
- function resolveConfigStore(env = process.env) {
14217
- const cloud = resolveCloudConfig(env);
14218
- 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);
14219
15512
  }
14220
- var CloudHttpError, API_URL_ENV = "HASNA_INSTRUCTIONS_API_URL", API_KEY_ENV = "HASNA_INSTRUCTIONS_API_KEY";
15513
+ var LOCAL_OPT_IN_ENV;
14221
15514
  var init_config_store = __esm(() => {
14222
15515
  init_configs();
14223
15516
  init_profiles();
@@ -14227,22 +15520,14 @@ var init_config_store = __esm(() => {
14227
15520
  init_types();
14228
15521
  init_bounded_read();
14229
15522
  init_instruction_graph();
14230
- init_retired_storage_mode();
14231
- CloudHttpError = class CloudHttpError extends Error {
14232
- status;
14233
- body;
14234
- constructor(status, message, body) {
14235
- super(message);
14236
- this.status = status;
14237
- this.body = body;
14238
- this.name = "CloudHttpError";
14239
- }
14240
- };
15523
+ init_transport_resolver();
15524
+ init_local_opt_in();
15525
+ LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV;
14241
15526
  });
14242
15527
 
14243
15528
  // src/lib/session-render-ownership.ts
14244
- import { existsSync as existsSync11, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
14245
- import { dirname as dirname5, join as join14, 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";
14246
15531
  function toSegments(absolutePath2) {
14247
15532
  return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
14248
15533
  }
@@ -14273,7 +15558,7 @@ function readManifestRelativePaths(manifestPath) {
14273
15558
  }
14274
15559
  let manifest;
14275
15560
  try {
14276
- manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
15561
+ manifest = JSON.parse(readFileSync6(manifestPath, "utf-8"));
14277
15562
  } catch {
14278
15563
  return null;
14279
15564
  }
@@ -14290,7 +15575,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
14290
15575
  const root = parse3(absolutePath2).root;
14291
15576
  let home = dirname5(absolutePath2);
14292
15577
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
14293
- const manifestPath = join14(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
15578
+ const manifestPath = join13(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14294
15579
  const relativePaths = readManifestRelativePaths(manifestPath);
14295
15580
  if (relativePaths) {
14296
15581
  const claimed = relative3(home, absolutePath2).split(sep).join("/");
@@ -14325,11 +15610,11 @@ __export(exports_apply, {
14325
15610
  applyConfigs: () => applyConfigs,
14326
15611
  applyConfig: () => applyConfig
14327
15612
  });
14328
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
14329
- import { basename as basename5, dirname as dirname6, join as join15, resolve as resolve10 } from "path";
14330
- import { homedir as homedir11 } 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";
14331
15616
  function getConfigHome() {
14332
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir11();
15617
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
14333
15618
  }
14334
15619
  function expandPath(p) {
14335
15620
  if (p.startsWith("~/")) {
@@ -14376,7 +15661,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
14376
15661
  }
14377
15662
  const path = expandPath(renderedTargetPath);
14378
15663
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
14379
- const previousContent = existsSync12(path) ? readFileSync6(path, "utf-8") : null;
15664
+ const previousContent = existsSync12(path) ? readFileSync7(path, "utf-8") : null;
14380
15665
  const changed = previousContent !== renderedForTarget;
14381
15666
  if (!opts.dryRun) {
14382
15667
  const dir = dirname6(path);
@@ -14416,7 +15701,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
14416
15701
  const path = expandPath(targetPath);
14417
15702
  if (!existsSync12(path))
14418
15703
  return [];
14419
- current = readFileSync6(path, "utf-8");
15704
+ current = readFileSync7(path, "utf-8");
14420
15705
  } catch {
14421
15706
  return secretTokens;
14422
15707
  }
@@ -14740,7 +16025,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
14740
16025
  getConfigHome(),
14741
16026
  opts.vars?.["HOME_DIR"]
14742
16027
  ].filter((home) => typeof home === "string" && home.length > 0));
14743
- if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join15(home, ...relativePath.split("/"))))))
16028
+ if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join14(home, ...relativePath.split("/"))))))
14744
16029
  return true;
14745
16030
  return sessionRenderOwnsPath(normalized);
14746
16031
  }
@@ -14758,9 +16043,9 @@ var init_apply = __esm(() => {
14758
16043
  });
14759
16044
 
14760
16045
  // src/lib/sync-dir.ts
14761
- import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
14762
- import { join as join16, relative as relative4 } from "path";
14763
- import { homedir as homedir12 } from "os";
16046
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
16047
+ import { join as join15, relative as relative4 } from "path";
16048
+ import { homedir as homedir9 } from "os";
14764
16049
  function shouldSkip(p) {
14765
16050
  return SKIP.some((s) => p.includes(s));
14766
16051
  }
@@ -14769,9 +16054,9 @@ async function syncFromDir(dir, opts = {}) {
14769
16054
  const absDir = expandPath(dir);
14770
16055
  if (!existsSync13(absDir))
14771
16056
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
14772
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join16(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());
14773
16058
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
14774
- const home = homedir12();
16059
+ const home = homedir9();
14775
16060
  const allConfigs = await store.listConfigs();
14776
16061
  for (const file of files) {
14777
16062
  if (shouldSkip(file)) {
@@ -14779,7 +16064,7 @@ async function syncFromDir(dir, opts = {}) {
14779
16064
  continue;
14780
16065
  }
14781
16066
  try {
14782
- const content = readFileSync7(file, "utf-8");
16067
+ const content = readFileSync8(file, "utf-8");
14783
16068
  if (content.length > 500000) {
14784
16069
  result.skipped.push(file + " (too large)");
14785
16070
  continue;
@@ -14806,7 +16091,7 @@ async function syncFromDir(dir, opts = {}) {
14806
16091
  }
14807
16092
  async function syncToDir(dir, opts = {}) {
14808
16093
  const store = opts.store ?? resolveConfigStore();
14809
- const home = homedir12();
16094
+ const home = homedir9();
14810
16095
  const absDir = expandPath(dir);
14811
16096
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14812
16097
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14830,7 +16115,7 @@ async function syncToDir(dir, opts = {}) {
14830
16115
  }
14831
16116
  function walkDir(dir, files = []) {
14832
16117
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
14833
- const full = join16(dir, entry.name);
16118
+ const full = join15(dir, entry.name);
14834
16119
  if (shouldSkip(full))
14835
16120
  continue;
14836
16121
  if (entry.isDirectory())
@@ -14865,8 +16150,8 @@ __export(exports_sync, {
14865
16150
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14866
16151
  CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14867
16152
  });
14868
- import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
14869
- import { basename as basename6, extname as extname3, join as join17 } from "path";
16153
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
16154
+ import { basename as basename6, extname as extname3, join as join16 } from "path";
14870
16155
  function claudeRuleOutputs(fileName) {
14871
16156
  const stem = basename6(fileName, extname3(fileName));
14872
16157
  return [
@@ -14930,11 +16215,11 @@ async function syncProject(opts) {
14930
16215
  const allConfigs = await store.listConfigs();
14931
16216
  const machine = detectMachineContext();
14932
16217
  for (const pf of PROJECT_CONFIG_FILES) {
14933
- const abs = join17(absDir, pf.file);
16218
+ const abs = join16(absDir, pf.file);
14934
16219
  if (!existsSync14(abs))
14935
16220
  continue;
14936
16221
  try {
14937
- const rawContent = readFileSync8(abs, "utf-8");
16222
+ const rawContent = readFileSync9(abs, "utf-8");
14938
16223
  if (rawContent.length > 500000) {
14939
16224
  result.skipped.push(pf.file);
14940
16225
  continue;
@@ -14963,20 +16248,20 @@ async function syncProject(opts) {
14963
16248
  }
14964
16249
  }
14965
16250
  for (const ruleDir of [
14966
- { dir: join17(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
14967
- { dir: join17(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
14968
- { dir: join17(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
14969
- { dir: join17(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
14970
- { dir: join17(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
14971
- { dir: join17(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14972
- { dir: join17(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
16251
+ { dir: join16(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
16252
+ { dir: join16(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
16253
+ { dir: join16(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
16254
+ { dir: join16(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
16255
+ { dir: join16(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
16256
+ { dir: join16(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
16257
+ { dir: join16(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14973
16258
  ]) {
14974
16259
  if (!existsSync14(ruleDir.dir))
14975
16260
  continue;
14976
16261
  const mdFiles = readdirSync3(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14977
16262
  for (const f of mdFiles) {
14978
- const abs = join17(ruleDir.dir, f);
14979
- const raw = readFileSync8(abs, "utf-8");
16263
+ const abs = join16(ruleDir.dir, f);
16264
+ const raw = readFileSync9(abs, "utf-8");
14980
16265
  const redacted = redactContent(raw, "markdown");
14981
16266
  const machineAware = templateizeMachineContent(redacted.content, machine);
14982
16267
  const content = machineAware.content;
@@ -15022,13 +16307,13 @@ async function syncKnown(opts = {}) {
15022
16307
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
15023
16308
  const ruleFiles = readdirSync3(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
15024
16309
  for (const f of ruleFiles) {
15025
- const abs2 = join17(absDir, f);
16310
+ const abs2 = join16(absDir, f);
15026
16311
  const targetPath = abs2.replace(home, "~");
15027
16312
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
15028
16313
  result.skipped.push(`${targetPath} (generated output)`);
15029
16314
  continue;
15030
16315
  }
15031
- const raw = readFileSync8(abs2, "utf-8");
16316
+ const raw = readFileSync9(abs2, "utf-8");
15032
16317
  const redacted = redactContent(raw, "markdown");
15033
16318
  const machineAware = templateizeMachineContent(redacted.content, machine);
15034
16319
  const content = machineAware.content;
@@ -15061,7 +16346,7 @@ async function syncKnown(opts = {}) {
15061
16346
  continue;
15062
16347
  }
15063
16348
  try {
15064
- const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
16349
+ const rawContent = normalizeKnownConfigSource(known, readFileSync9(abs, "utf-8"));
15065
16350
  if (rawContent.length > 500000) {
15066
16351
  result.skipped.push(known.path + " (too large)");
15067
16352
  continue;
@@ -15164,7 +16449,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
15164
16449
  const path = expandPath(targetPath);
15165
16450
  if (!existsSync14(path))
15166
16451
  return `(file not found on disk: ${path})`;
15167
- const diskContent = readFileSync8(path, "utf-8");
16452
+ const diskContent = readFileSync9(path, "utf-8");
15168
16453
  if (diskContent === expectedContent)
15169
16454
  return "(no diff \u2014 identical)";
15170
16455
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -15397,9 +16682,9 @@ __export(exports_package_manager_guard, {
15397
16682
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15398
16683
  });
15399
16684
  import { execFileSync as execFileSync2 } from "child_process";
15400
- import { existsSync as existsSync23, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
15401
- import { homedir as homedir15 } from "os";
15402
- import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute5, join as join25, relative as relative7, resolve as resolve15 } 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";
15403
16688
  function scanPackageManagerSecrets(options = {}) {
15404
16689
  const cwd = options.cwd ? resolve15(options.cwd) : process.cwd();
15405
16690
  const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve15(cwd, root));
@@ -15433,9 +16718,9 @@ function scanPackageManagerSecrets(options = {}) {
15433
16718
  }
15434
16719
  }
15435
16720
  if (options.includeHome) {
15436
- const home = homedir15();
16721
+ const home = homedir12();
15437
16722
  for (const name of HOME_FILES) {
15438
- const file = join25(home, name);
16723
+ const file = join24(home, name);
15439
16724
  if (!existsSync23(file))
15440
16725
  continue;
15441
16726
  const text = readTextFile(file);
@@ -15460,12 +16745,12 @@ function collectRepoFiles(root) {
15460
16745
  if (entry.isDirectory()) {
15461
16746
  if (SKIP_DIRS.has(entry.name))
15462
16747
  continue;
15463
- visit(join25(dir, entry.name));
16748
+ visit(join24(dir, entry.name));
15464
16749
  continue;
15465
16750
  }
15466
16751
  if (!entry.isFile())
15467
16752
  continue;
15468
- const file = join25(dir, entry.name);
16753
+ const file = join24(dir, entry.name);
15469
16754
  if (shouldScanRepoFile(file))
15470
16755
  out.push(file);
15471
16756
  }
@@ -15503,7 +16788,7 @@ function readTextFile(file) {
15503
16788
  const stat = lstatSync7(file);
15504
16789
  if (!stat.isFile() || stat.size > 5000000)
15505
16790
  return null;
15506
- const buf = readFileSync16(file);
16791
+ const buf = readFileSync17(file);
15507
16792
  if (buf.includes(0))
15508
16793
  return null;
15509
16794
  return buf.toString("utf-8");
@@ -15733,10 +17018,10 @@ function stripInlineComment(value) {
15733
17018
  return value.replace(/\s[#;].*$/, "").trim();
15734
17019
  }
15735
17020
  function displayPath(file, root) {
15736
- const home = homedir15();
17021
+ const home = homedir12();
15737
17022
  if (root === home && (file === home || file.startsWith(home + "/")))
15738
17023
  return "~/" + toPosix(relative7(home, file));
15739
- if (isAbsolute5(root) && file.startsWith(root + "/"))
17024
+ if (isAbsolute6(root) && file.startsWith(root + "/"))
15740
17025
  return toPosix(relative7(root, file));
15741
17026
  if (file === home || file.startsWith(home + "/"))
15742
17027
  return "~/" + toPosix(relative7(home, file));
@@ -15788,12 +17073,12 @@ var init_package_manager_guard = __esm(() => {
15788
17073
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
15789
17074
  import { Buffer as Buffer2 } from "buffer";
15790
17075
  import { existsSync as existsSync2 } from "fs";
15791
- import { join as join3 } from "path";
17076
+ import { join as join2 } from "path";
15792
17077
  import { existsSync } from "fs";
15793
- import { homedir as homedir2 } from "os";
15794
- import { join as join2, resolve } from "path";
15795
17078
  import { homedir } from "os";
15796
- import { join } from "path";
17079
+ import { join, resolve } from "path";
17080
+ import { homedir as pathsResolverHomedir } from "os";
17081
+ import { join as pathsResolverJoin } from "path";
15797
17082
  import { createHmac, timingSafeEqual } from "crypto";
15798
17083
  import { lookup as dnsLookup } from "dns/promises";
15799
17084
  import { isIP } from "net";
@@ -15897,75 +17182,72 @@ function channelMatchesEvent(channel, event) {
15897
17182
  return true;
15898
17183
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
15899
17184
  }
15900
- var KIND_ENV = {
17185
+ var PATHS_RESOLVER_KIND_ENV = {
15901
17186
  config: "HASNA_CONFIG_HOME",
15902
17187
  data: "HASNA_DATA_HOME",
15903
17188
  state: "HASNA_STATE_HOME",
15904
17189
  cache: "HASNA_CACHE_HOME"
15905
17190
  };
15906
- var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
15907
- function assertApp(app) {
17191
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
17192
+ function pathsResolverAssertApp(app) {
15908
17193
  if (typeof app !== "string" || app.length === 0) {
15909
17194
  throw new TypeError("paths: app must be a non-empty string");
15910
17195
  }
15911
- if (!APP_SLUG_RE.test(app)) {
17196
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
15912
17197
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
15913
17198
  }
15914
17199
  }
15915
- function envOf(options) {
15916
- return options.env ?? process.env;
15917
- }
15918
- function envValue(options, kind) {
15919
- const value = envOf(options)[KIND_ENV[kind]];
15920
- return typeof value === "string" && value.length > 0 ? value : undefined;
15921
- }
15922
- function isMacOS(platform) {
15923
- 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
+ }
15924
17204
  }
15925
- function baseDir(kind, options) {
15926
- const override = envValue(options, kind);
15927
- 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)
15928
17210
  return override;
15929
- const home = options.home ?? homedir();
17211
+ const home = options.home ?? pathsResolverHomedir();
15930
17212
  const platform = options.platform ?? process.platform;
15931
- if (isMacOS(platform)) {
17213
+ if (platform === "darwin") {
15932
17214
  switch (kind) {
15933
17215
  case "config":
15934
17216
  case "data":
15935
- return join(home, "Library", "Application Support", "Hasna");
17217
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
15936
17218
  case "cache":
15937
- return join(home, "Library", "Caches", "Hasna");
17219
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
15938
17220
  case "state":
15939
- return join(home, "Library", "Logs", "Hasna");
17221
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
15940
17222
  }
15941
17223
  }
15942
17224
  switch (kind) {
15943
17225
  case "config":
15944
- return join(home, ".config", "hasna");
17226
+ return pathsResolverJoin(home, ".config", "hasna");
15945
17227
  case "data":
15946
- return join(home, ".local", "share", "hasna");
17228
+ return pathsResolverJoin(home, ".local", "share", "hasna");
15947
17229
  case "state":
15948
- return join(home, ".local", "state", "hasna");
17230
+ return pathsResolverJoin(home, ".local", "state", "hasna");
15949
17231
  case "cache":
15950
- return join(home, ".cache", "hasna");
17232
+ return pathsResolverJoin(home, ".cache", "hasna");
15951
17233
  }
15952
17234
  }
15953
- function resolvePath(kind, options) {
15954
- assertApp(options.app);
15955
- const appSegment = options.internal === true ? join("internal", options.app) : options.app;
15956
- 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);
15957
17239
  }
15958
17240
  function dataDir(options) {
15959
- return resolvePath("data", options);
17241
+ return pathsResolverResolve("data", options);
15960
17242
  }
15961
17243
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
15962
17244
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
15963
17245
  var EVENTS_STORE_SENTINEL_FILE = "events.json";
15964
17246
  function effectiveHome() {
15965
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
17247
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
15966
17248
  }
15967
17249
  function legacyHomeDir() {
15968
- return join2(effectiveHome(), ".hasna", "events");
17250
+ return join(effectiveHome(), ".hasna", "events");
15969
17251
  }
15970
17252
  function resolverHome() {
15971
17253
  return dataDir({ app: "events", home: effectiveHome() || undefined });
@@ -15974,7 +17256,7 @@ function adoptResolverHome(resolved, env = process.env) {
15974
17256
  const dataOverride = env.HASNA_DATA_HOME;
15975
17257
  if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
15976
17258
  return true;
15977
- return existsSync(join2(resolved, EVENTS_STORE_SENTINEL_FILE));
17259
+ return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
15978
17260
  }
15979
17261
  function exactEventsHome() {
15980
17262
  const dir = process.env[HASNA_EVENTS_DIR_ENV];
@@ -16015,9 +17297,9 @@ class JsonEventsStore {
16015
17297
  constructor(dataDir2 = getEventsDataDir()) {
16016
17298
  this.dataDir = dataDir2;
16017
17299
  this.runtime = localJsonRuntime(dataDir2);
16018
- this.channelsPath = join3(dataDir2, "channels.json");
16019
- this.eventsPath = join3(dataDir2, "events.json");
16020
- 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");
16021
17303
  }
16022
17304
  async init() {
16023
17305
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -16285,7 +17567,7 @@ async function getEventsStatus(dataDir2) {
16285
17567
  };
16286
17568
  }
16287
17569
  function statusFile(dataDir2, fileName, records) {
16288
- const path = join3(dataDir2, fileName);
17570
+ const path = join2(dataDir2, fileName);
16289
17571
  return { path, exists: existsSync2(path), records };
16290
17572
  }
16291
17573
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
@@ -17219,6 +18501,13 @@ function parseMatcherExpression(value, label) {
17219
18501
  negated: false
17220
18502
  };
17221
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
+ }
17222
18511
  var DEFAULT_EVENT_LIST_LIMIT = 100;
17223
18512
  function parseJsonObject(value, fallback) {
17224
18513
  if (!value)
@@ -17244,7 +18533,7 @@ function parseHeaders(values) {
17244
18533
  function createClient(options) {
17245
18534
  if (options.createClient)
17246
18535
  return options.createClient();
17247
- return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
18536
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
17248
18537
  }
17249
18538
  function print(value, json, text) {
17250
18539
  if (json)
@@ -17325,6 +18614,8 @@ function registerChannelCommands(program, options) {
17325
18614
  metadata: parseJsonObject(actionOptions.metadata, {})
17326
18615
  }, { honorFilters: actionOptions.honorFilters });
17327
18616
  print(result, json, `${result.status}: ${result.channelId}`);
18617
+ if (result.status === "failed")
18618
+ process.exitCode = 1;
17328
18619
  } catch (error) {
17329
18620
  fail(error, json);
17330
18621
  }
@@ -17433,9 +18724,9 @@ var {
17433
18724
  // src/cli/index.tsx
17434
18725
  init_apply();
17435
18726
  import chalk from "chalk";
17436
- import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as readFileSync17, readSync, writeSync } from "fs";
17437
- import { homedir as homedir16 } from "os";
17438
- import { basename as basename8, join as join26, resolve as resolve16 } 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";
17439
18730
 
17440
18731
  // src/lib/config-target-identity.ts
17441
18732
  init_apply();
@@ -17482,14 +18773,14 @@ init_redact();
17482
18773
  // src/lib/export.ts
17483
18774
  init_config_store();
17484
18775
  import { existsSync as existsSync15, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
17485
- import { join as join18, resolve as resolve11 } from "path";
18776
+ import { join as join17, resolve as resolve11 } from "path";
17486
18777
  import { tmpdir } from "os";
17487
18778
  async function exportConfigs(outputPath, opts = {}) {
17488
18779
  const store = opts.store ?? resolveConfigStore();
17489
18780
  const configs = await store.listConfigs(opts.filter);
17490
18781
  const absOutput = resolve11(outputPath);
17491
- const tmpDir = join18(tmpdir(), `configs-export-${Date.now()}`);
17492
- const contentsDir = join18(tmpDir, "contents");
18782
+ const tmpDir = join17(tmpdir(), `configs-export-${Date.now()}`);
18783
+ const contentsDir = join17(tmpDir, "contents");
17493
18784
  try {
17494
18785
  mkdirSync4(contentsDir, { recursive: true });
17495
18786
  const manifest = {
@@ -17497,10 +18788,10 @@ async function exportConfigs(outputPath, opts = {}) {
17497
18788
  exported_at: new Date().toISOString(),
17498
18789
  configs: configs.map(({ content: _content, ...meta }) => meta)
17499
18790
  };
17500
- writeFileSync3(join18(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
18791
+ writeFileSync3(join17(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
17501
18792
  for (const config of configs) {
17502
18793
  const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
17503
- writeFileSync3(join18(contentsDir, fileName), config.content, "utf-8");
18794
+ writeFileSync3(join17(contentsDir, fileName), config.content, "utf-8");
17504
18795
  }
17505
18796
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
17506
18797
  stdout: "pipe",
@@ -17521,14 +18812,14 @@ async function exportConfigs(outputPath, opts = {}) {
17521
18812
 
17522
18813
  // src/lib/import.ts
17523
18814
  init_config_store();
17524
- import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
17525
- import { join as join19, resolve as resolve12 } 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";
17526
18817
  import { tmpdir as tmpdir2 } from "os";
17527
18818
  async function importConfigs(bundlePath, opts = {}) {
17528
18819
  const store = opts.store ?? resolveConfigStore();
17529
18820
  const conflict = opts.conflict ?? "skip";
17530
18821
  const absPath = resolve12(bundlePath);
17531
- const tmpDir = join19(tmpdir2(), `configs-import-${Date.now()}`);
18822
+ const tmpDir = join18(tmpdir2(), `configs-import-${Date.now()}`);
17532
18823
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
17533
18824
  try {
17534
18825
  mkdirSync5(tmpDir, { recursive: true });
@@ -17541,15 +18832,15 @@ async function importConfigs(bundlePath, opts = {}) {
17541
18832
  const stderr = await new Response(proc.stderr).text();
17542
18833
  throw new Error(`tar extraction failed: ${stderr}`);
17543
18834
  }
17544
- const manifestPath = join19(tmpDir, "manifest.json");
18835
+ const manifestPath = join18(tmpDir, "manifest.json");
17545
18836
  if (!existsSync16(manifestPath))
17546
18837
  throw new Error("Invalid bundle: missing manifest.json");
17547
- const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
18838
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
17548
18839
  for (const meta of manifest.configs) {
17549
18840
  try {
17550
18841
  const ext = meta.format === "text" ? "txt" : meta.format;
17551
- const contentFile = join19(tmpDir, "contents", `${meta.slug}.${ext}`);
17552
- const content = existsSync16(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
18842
+ const contentFile = join18(tmpDir, "contents", `${meta.slug}.${ext}`);
18843
+ const content = existsSync16(contentFile) ? readFileSync10(contentFile, "utf-8") : "";
17553
18844
  let existing = null;
17554
18845
  try {
17555
18846
  existing = await store.getConfig(meta.slug);
@@ -17604,11 +18895,11 @@ import {
17604
18895
  existsSync as existsSync17,
17605
18896
  lstatSync as lstatSync4,
17606
18897
  mkdirSync as mkdirSync6,
17607
- readFileSync as readFileSync10,
18898
+ readFileSync as readFileSync11,
17608
18899
  readdirSync as readdirSync4,
17609
18900
  statSync as statSync6
17610
18901
  } from "fs";
17611
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join20, parse as parse4, relative as relative5, resolve as resolve13 } from "path";
18902
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join19, parse as parse4, relative as relative5, resolve as resolve13 } from "path";
17612
18903
 
17613
18904
  class SessionApplyError extends Error {
17614
18905
  constructor(message) {
@@ -17773,7 +19064,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17773
19064
  });
17774
19065
  continue;
17775
19066
  }
17776
- const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
19067
+ const actualSha256 = sha2568(readFileSync11(target, "utf-8"));
17777
19068
  if (actualSha256 !== file.sha256) {
17778
19069
  drifted.push({
17779
19070
  path: target,
@@ -17799,10 +19090,10 @@ function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
17799
19090
  const resolvedSnapshotPath = resolve13(snapshotPath);
17800
19091
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
17801
19092
  const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
17802
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute4(snapshotDirRelative);
19093
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute5(snapshotDirRelative);
17803
19094
  if (!insideSnapshotDir) {
17804
19095
  const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
17805
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
19096
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute5(snapshotRelativePath)) {
17806
19097
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
17807
19098
  }
17808
19099
  }
@@ -17931,7 +19222,7 @@ function readSessionRenderSnapshot(snapshotPath) {
17931
19222
  }
17932
19223
  let parsed;
17933
19224
  try {
17934
- parsed = JSON.parse(readFileSync10(resolved, "utf8"));
19225
+ parsed = JSON.parse(readFileSync11(resolved, "utf8"));
17935
19226
  } catch {
17936
19227
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
17937
19228
  }
@@ -18009,7 +19300,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
18009
19300
  }
18010
19301
  let parsedManifest;
18011
19302
  try {
18012
- parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
19303
+ parsedManifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
18013
19304
  } catch {
18014
19305
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
18015
19306
  }
@@ -18109,7 +19400,7 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
18109
19400
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
18110
19401
  continue;
18111
19402
  try {
18112
- const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
19403
+ const candidate = JSON.parse(readFileSync11(candidatePath, "utf8"));
18113
19404
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
18114
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) {
18115
19406
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
@@ -18187,7 +19478,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
18187
19478
  }
18188
19479
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
18189
19480
  const target = resolvePlannedFilePath(plan, file, targetHome);
18190
- const previousContent = existsSync17(target) ? readFileSync10(target, "utf-8") : null;
19481
+ const previousContent = existsSync17(target) ? readFileSync11(target, "utf-8") : null;
18191
19482
  const previousSha256 = previousContent === null ? null : sha2568(previousContent);
18192
19483
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
18193
19484
  const changed = previousContent !== file.content;
@@ -18288,7 +19579,7 @@ function planStaleFileResult(file, targetHome, options) {
18288
19579
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
18289
19580
  if (!existsSync17(target))
18290
19581
  return null;
18291
- const previousContent = readFileSync10(target, "utf-8");
19582
+ const previousContent = readFileSync11(target, "utf-8");
18292
19583
  const previousSha256 = sha2568(previousContent);
18293
19584
  if (!options.force && previousSha256 !== file.sha256) {
18294
19585
  return {
@@ -18335,7 +19626,7 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
18335
19626
  function resolvePlannedFilePath(plan, file, targetHome) {
18336
19627
  const target = resolve13(targetHome, ...file.relativePath.split("/"));
18337
19628
  const rel = relative5(targetHome, target);
18338
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
19629
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
18339
19630
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
18340
19631
  }
18341
19632
  if (resolve13(file.path) !== target) {
@@ -18347,7 +19638,7 @@ function resolvePlannedFilePath(plan, file, targetHome) {
18347
19638
  function resolveManifestRelativePath(relativePath, targetHome) {
18348
19639
  const target = resolve13(targetHome, ...relativePath.split(/[\\/]+/));
18349
19640
  const rel = relative5(targetHome, target);
18350
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
19641
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
18351
19642
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
18352
19643
  }
18353
19644
  assertNoSymlinkSegments2(targetHome, target);
@@ -18357,7 +19648,7 @@ function readPreviousManifest(path) {
18357
19648
  if (!existsSync17(path))
18358
19649
  return null;
18359
19650
  try {
18360
- const parsed = JSON.parse(readFileSync10(path, "utf-8"));
19651
+ const parsed = JSON.parse(readFileSync11(path, "utf-8"));
18361
19652
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
18362
19653
  return null;
18363
19654
  if (!Array.isArray(parsed.files))
@@ -18402,7 +19693,7 @@ function currentSessionFileHash(path, targetHome) {
18402
19693
  if (stat.isSymbolicLink() || !stat.isFile()) {
18403
19694
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
18404
19695
  }
18405
- return sha2568(readFileSync10(path, "utf-8"));
19696
+ return sha2568(readFileSync11(path, "utf-8"));
18406
19697
  }
18407
19698
  function requiredPreviousHash(result) {
18408
19699
  if (result.previousSha256 === null) {
@@ -18412,7 +19703,7 @@ function requiredPreviousHash(result) {
18412
19703
  }
18413
19704
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
18414
19705
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync17(result.path)).map((result) => {
18415
- const content = readFileSync10(result.path, "utf-8");
19706
+ const content = readFileSync11(result.path, "utf-8");
18416
19707
  return {
18417
19708
  path: result.path,
18418
19709
  relativePath: result.relativePath,
@@ -18430,7 +19721,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
18430
19721
  };
18431
19722
  }
18432
19723
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
18433
- const snapshotPath = join20(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID7()}.json`);
19724
+ const snapshotPath = join19(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID7()}.json`);
18434
19725
  const afterFiles = results.map((result) => {
18435
19726
  if (result.action === "conflict") {
18436
19727
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -18476,7 +19767,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
18476
19767
  };
18477
19768
  }
18478
19769
  function assertSafeTargetHome(targetHome) {
18479
- if (!isAbsolute4(targetHome))
19770
+ if (!isAbsolute5(targetHome))
18480
19771
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
18481
19772
  const normalized = resolve13(targetHome);
18482
19773
  if (normalized === parse4(normalized).root) {
@@ -18493,7 +19784,7 @@ function assertNoSymlinkSegments2(root, target) {
18493
19784
  const rel = relative5(root, target);
18494
19785
  let current = root;
18495
19786
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18496
- current = join20(current, segment);
19787
+ current = join19(current, segment);
18497
19788
  if (existsSync17(current) && lstatSync4(current).isSymbolicLink()) {
18498
19789
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
18499
19790
  }
@@ -18505,7 +19796,7 @@ function assertNoSymlinkAncestors2(path) {
18505
19796
  let current = parsed.root;
18506
19797
  const rel = relative5(parsed.root, normalized);
18507
19798
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18508
- current = join20(current, segment);
19799
+ current = join19(current, segment);
18509
19800
  if (!existsSync17(current))
18510
19801
  return;
18511
19802
  if (lstatSync4(current).isSymbolicLink()) {
@@ -18556,10 +19847,10 @@ function formatGlobalSourceCoverageWarnings(result) {
18556
19847
 
18557
19848
  // src/lib/station-profile.ts
18558
19849
  init_raw_store_root();
18559
- import { spawnSync } from "child_process";
18560
- import { existsSync as existsSync18, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
18561
- import { arch as osArch, homedir as homedir13, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
18562
- import { dirname as dirname8, join as join21 } 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";
18563
19854
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
18564
19855
  var STATION_PROFILE_SOURCE_ID = "station-profile";
18565
19856
  var STATION_PROFILE_LAYER = "machine";
@@ -18569,23 +19860,23 @@ var STATION_PROFILE_FULL_NAMES_MAX = 6;
18569
19860
  var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
18570
19861
  var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
18571
19862
  var BUN_INSTALL_ENV = "BUN_INSTALL";
18572
- function homeDir4(env = process.env) {
18573
- return env["HOME"] || env["USERPROFILE"] || homedir13();
19863
+ function homeDir5(env = process.env) {
19864
+ return env["HOME"] || env["USERPROFILE"] || homedir10();
18574
19865
  }
18575
19866
  function getStationProfileCachePath(env = process.env) {
18576
- return join21(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
19867
+ return join20(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
18577
19868
  }
18578
19869
  function getMachinesManifestPath(env = process.env) {
18579
- return env[MACHINES_MANIFEST_PATH_ENV] || join21(homeDir4(env), ".hasna", "machines", "machines.json");
19870
+ return env[MACHINES_MANIFEST_PATH_ENV] || join20(homeDir5(env), ".hasna", "machines", "machines.json");
18580
19871
  }
18581
19872
  function getBunGlobalModulesDir(env = process.env) {
18582
- return join21(env[BUN_INSTALL_ENV] || join21(homeDir4(env), ".bun"), "install", "global", "node_modules");
19873
+ return join20(env[BUN_INSTALL_ENV] || join20(homeDir5(env), ".bun"), "install", "global", "node_modules");
18583
19874
  }
18584
19875
  function readMachinesManifest(path) {
18585
19876
  try {
18586
19877
  if (!existsSync18(path))
18587
19878
  return null;
18588
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
19879
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
18589
19880
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
18590
19881
  return null;
18591
19882
  const machines = parsed["machines"];
@@ -18615,7 +19906,7 @@ function metadataUser(record) {
18615
19906
  }
18616
19907
  function probeMachineStatus(machineId) {
18617
19908
  try {
18618
- const result = spawnSync("machines", ["details", "--json", "--machine", machineId], {
19909
+ const result = spawnSync2("machines", ["details", "--json", "--machine", machineId], {
18619
19910
  encoding: "utf8",
18620
19911
  timeout: 3000,
18621
19912
  stdio: ["ignore", "pipe", "pipe"]
@@ -18637,11 +19928,11 @@ function probeMachineStatus(machineId) {
18637
19928
  }
18638
19929
  }
18639
19930
  function resolveStationProfileMachine(env = process.env, options = {}) {
18640
- const hostname2 = osHostname();
19931
+ const hostname2 = osHostname2();
18641
19932
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
18642
- const home = homeDir4(env);
19933
+ const home = homeDir5(env);
18643
19934
  const platform = stringField(record, "platform") ?? osPlatform();
18644
- const workspacePath = stringField(record, "workspacePath") ?? join21(home, platform === "darwin" ? "Workspace" : "workspace");
19935
+ const workspacePath = stringField(record, "workspacePath") ?? join20(home, platform === "darwin" ? "Workspace" : "workspace");
18645
19936
  const machine = {
18646
19937
  id: stringField(record, "id") ?? hostname2,
18647
19938
  hostname: stringField(record, "hostname") ?? hostname2,
@@ -18658,7 +19949,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
18658
19949
  return machine;
18659
19950
  }
18660
19951
  function scopedPackageNames(modulesDir, scope) {
18661
- const scopeDir = join21(modulesDir, scope);
19952
+ const scopeDir = join20(modulesDir, scope);
18662
19953
  try {
18663
19954
  if (!existsSync18(scopeDir))
18664
19955
  return null;
@@ -18670,7 +19961,7 @@ function scopedPackageNames(modulesDir, scope) {
18670
19961
  function readdirNames(dir) {
18671
19962
  return readdirSync5(dir).filter((name) => {
18672
19963
  try {
18673
- return lstatSync5(join21(dir, name)).isDirectory();
19964
+ return lstatSync5(join20(dir, name)).isDirectory();
18674
19965
  } catch {
18675
19966
  return false;
18676
19967
  }
@@ -18755,7 +20046,7 @@ function refreshStationProfile(options = {}) {
18755
20046
  const path = getStationProfileCachePath(env);
18756
20047
  const generatedAt = new Date().toISOString();
18757
20048
  if (!options.dryRun) {
18758
- const existing = existsSync18(path) ? readFileSync11(path, "utf8") : null;
20049
+ const existing = existsSync18(path) ? readFileSync12(path, "utf8") : null;
18759
20050
  if (existing !== content) {
18760
20051
  mkdirSync7(dirname8(path), { recursive: true });
18761
20052
  writeFileSync4(path, content, "utf8");
@@ -18776,7 +20067,7 @@ function readStationProfile(env = process.env) {
18776
20067
  try {
18777
20068
  if (!existsSync18(path))
18778
20069
  return null;
18779
- return readFileSync11(path, "utf8");
20070
+ return readFileSync12(path, "utf8");
18780
20071
  } catch {
18781
20072
  return null;
18782
20073
  }
@@ -18803,9 +20094,6 @@ init_config_store();
18803
20094
  init_config_store();
18804
20095
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
18805
20096
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
18806
- PROJECT_DASHBOARD_DIR: ".hasna/project",
18807
- PROJECT_DASHBOARD_RENDER_MANIFEST: ".hasna/project/dashboard/render.json",
18808
- PROJECT_DASHBOARD_SNAPSHOTS_DIR: ".hasna/project/dashboard/snapshots",
18809
20097
  PROJECT_CHANNEL_PREFIX: ""
18810
20098
  };
18811
20099
  var PROJECT_DASHBOARD_STANDARD_CONTENT = `# Agent-Managed Project Dashboard Standard
@@ -18816,9 +20104,13 @@ evidence, tasks, knowledge, and dashboard output consistent.
18816
20104
 
18817
20105
  ## Canonical Files
18818
20106
 
18819
- - Project manifest root: \`.hasna/project/\`
18820
- - Dashboard render manifest: \`.hasna/project/dashboard/render.json\`
18821
- - 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\`
18822
20114
  - Dashboard schema ids come from \`@hasna/contracts\`.
18823
20115
  - Project folders may contain private documents, but render JSON must contain
18824
20116
  only ids, counts, statuses, resource refs, evidence refs, and redacted
@@ -19111,18 +20403,18 @@ init_codewith_shared_todos_storage_standard();
19111
20403
 
19112
20404
  // src/lib/managed-skill-runtimes.ts
19113
20405
  import { createHash as createHash9 } from "crypto";
19114
- import { spawnSync as spawnSync2 } from "child_process";
20406
+ import { spawnSync as spawnSync3 } from "child_process";
19115
20407
  import {
19116
20408
  existsSync as existsSync19,
19117
20409
  lstatSync as lstatSync6,
19118
20410
  mkdirSync as mkdirSync8,
19119
- readFileSync as readFileSync12,
20411
+ readFileSync as readFileSync13,
19120
20412
  renameSync as renameSync2,
19121
20413
  rmSync as rmSync5,
19122
20414
  writeFileSync as writeFileSync5
19123
20415
  } from "fs";
19124
- import { homedir as homedir14 } from "os";
19125
- import { dirname as dirname9, join as join22, parse as parse5, relative as relative6, resolve as resolve14 } 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";
19126
20418
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
19127
20419
  var INBOX_SKILL_MARKERS = [
19128
20420
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -19148,7 +20440,7 @@ function findSymlinkedAncestor(path) {
19148
20440
  let current = parsed.root;
19149
20441
  const rel = relative6(parsed.root, normalized);
19150
20442
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
19151
- current = join22(current, segment);
20443
+ current = join21(current, segment);
19152
20444
  if (!existsSync19(current))
19153
20445
  return null;
19154
20446
  if (lstatSync6(current).isSymbolicLink())
@@ -19166,9 +20458,9 @@ function packagedInboxSkillPath(explicitPath) {
19166
20458
  if (explicitPath)
19167
20459
  return explicitPath;
19168
20460
  const candidates = [
19169
- join22(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
19170
- join22(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
19171
- join22(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
20461
+ join21(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
20462
+ join21(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
20463
+ join21(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
19172
20464
  ];
19173
20465
  const found = candidates.find((candidate) => existsSync19(candidate));
19174
20466
  if (!found) {
@@ -19182,7 +20474,7 @@ function readCanonicalSkill(explicitPath) {
19182
20474
  if (!stat?.isFile()) {
19183
20475
  throw new Error("packaged inbox skill contract is not a regular file");
19184
20476
  }
19185
- const content = readFileSync12(assetPath, "utf8");
20477
+ const content = readFileSync13(assetPath, "utf8");
19186
20478
  if (!content.includes("conversations watch --from <agent> --all")) {
19187
20479
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
19188
20480
  }
@@ -19192,7 +20484,7 @@ function readCanonicalSkill(explicitPath) {
19192
20484
  return { content, sha256: sha2569(content) };
19193
20485
  }
19194
20486
  function runProbe(command, args) {
19195
- const result = spawnSync2(command, args, {
20487
+ const result = spawnSync3(command, args, {
19196
20488
  encoding: "utf8",
19197
20489
  timeout: 5000,
19198
20490
  stdio: ["ignore", "pipe", "pipe"]
@@ -19219,8 +20511,8 @@ function compareVersions(left, right) {
19219
20511
  }
19220
20512
  return 0;
19221
20513
  }
19222
- function inspectSkillMarkers(homeDir5) {
19223
- return INBOX_SKILL_MARKERS.map((parts) => join22(homeDir5, ...parts)).map((path) => {
20514
+ function inspectSkillMarkers(homeDir6) {
20515
+ return INBOX_SKILL_MARKERS.map((parts) => join21(homeDir6, ...parts)).map((path) => {
19224
20516
  const stat = lstatOrNull(path);
19225
20517
  if (!stat)
19226
20518
  return null;
@@ -19229,16 +20521,16 @@ function inspectSkillMarkers(homeDir5) {
19229
20521
  }
19230
20522
  return {
19231
20523
  path,
19232
- content: readFileSync12(path, "utf8"),
20524
+ content: readFileSync13(path, "utf8"),
19233
20525
  mode: stat.mode & 511,
19234
20526
  regular: true
19235
20527
  };
19236
20528
  }).filter((snapshot) => snapshot !== null);
19237
20529
  }
19238
20530
  function inspectInbox(options) {
19239
- const homeDir5 = options.homeDir ?? homedir14();
20531
+ const homeDir6 = options.homeDir ?? homedir11();
19240
20532
  const runtimeCommand = options.conversationsCommand ?? "conversations";
19241
- const snapshots = inspectSkillMarkers(homeDir5);
20533
+ const snapshots = inspectSkillMarkers(homeDir6);
19242
20534
  const skillPresent = snapshots.length > 0;
19243
20535
  let canonicalContent = null;
19244
20536
  let canonicalSha256 = null;
@@ -19363,7 +20655,7 @@ function writeAtomic(path, content, mode) {
19363
20655
  }
19364
20656
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
19365
20657
  lstat: lstatOrNull,
19366
- read: (path) => readFileSync12(path, "utf8"),
20658
+ read: (path) => readFileSync13(path, "utf8"),
19367
20659
  write: writeAtomic
19368
20660
  };
19369
20661
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -19513,11 +20805,11 @@ init_project_context();
19513
20805
  init_config_store();
19514
20806
  init_apply();
19515
20807
  init_config_agents();
19516
- import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
20808
+ import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
19517
20809
 
19518
20810
  // src/lib/package-version.ts
19519
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
19520
- import { dirname as dirname10, join as join23 } from "path";
20811
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
20812
+ import { dirname as dirname10, join as join22 } from "path";
19521
20813
  import { fileURLToPath } from "url";
19522
20814
  var cached = null;
19523
20815
  function getPackageVersion() {
@@ -19526,9 +20818,9 @@ function getPackageVersion() {
19526
20818
  try {
19527
20819
  let dir = dirname10(fileURLToPath(import.meta.url));
19528
20820
  for (let i = 0;i < 8; i++) {
19529
- const pkgPath = join23(dir, "package.json");
20821
+ const pkgPath = join22(dir, "package.json");
19530
20822
  if (existsSync20(pkgPath)) {
19531
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
20823
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
19532
20824
  if (pkg.name === "@hasna/instructions" && pkg.version) {
19533
20825
  cached = pkg.version;
19534
20826
  return cached;
@@ -19595,7 +20887,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
19595
20887
  missingTargets += 1;
19596
20888
  continue;
19597
20889
  }
19598
- const disk = readFileSync14(targetPath, "utf-8");
20890
+ const disk = readFileSync15(targetPath, "utf-8");
19599
20891
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
19600
20892
  if (redactedDisk !== config.content) {
19601
20893
  driftedTargets += 1;
@@ -19691,8 +20983,8 @@ init_config_store();
19691
20983
 
19692
20984
  // src/lib/provider-context.ts
19693
20985
  import { createHash as createHash10 } from "crypto";
19694
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
19695
- import { join as join24 } from "path";
20986
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
20987
+ import { join as join23 } from "path";
19696
20988
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
19697
20989
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
19698
20990
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -19837,18 +21129,18 @@ function resolveAndRenderProviderContext(opts) {
19837
21129
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
19838
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;
19839
21131
  const content = renderProviderFragment(entry);
19840
- const dir = join24(opts.homeDir, PROVIDER_CONTEXT_DIR);
21132
+ const dir = join23(opts.homeDir, PROVIDER_CONTEXT_DIR);
19841
21133
  if (!existsSync22(dir))
19842
21134
  mkdirSync9(dir, { recursive: true });
19843
21135
  const filename = `${entry ? entry.key : "invariant"}.md`;
19844
- const fragmentPath2 = join24(dir, filename);
21136
+ const fragmentPath2 = join23(dir, filename);
19845
21137
  const fragmentSha256 = sha25610(content);
19846
21138
  writeFileSync6(fragmentPath2, content, "utf8");
19847
- const manifestPath = join24(dir, PROVIDER_CONTEXT_MANIFEST);
21139
+ const manifestPath = join23(dir, PROVIDER_CONTEXT_MANIFEST);
19848
21140
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
19849
21141
  try {
19850
21142
  if (existsSync22(manifestPath)) {
19851
- const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
21143
+ const parsed = JSON.parse(readFileSync16(manifestPath, "utf8"));
19852
21144
  if (parsed && typeof parsed === "object")
19853
21145
  manifest = parsed;
19854
21146
  }
@@ -19886,8 +21178,8 @@ function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
19886
21178
  }
19887
21179
 
19888
21180
  // src/cli/index.tsx
19889
- import { createRequire } from "module";
19890
- var pkg = createRequire(import.meta.url)("../../package.json");
21181
+ import { createRequire as createRequire3 } from "module";
21182
+ var pkg = createRequire3(import.meta.url)("../../package.json");
19891
21183
  var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
19892
21184
  function writeStdout(text) {
19893
21185
  const buf = Buffer.from(text, "utf8");
@@ -20045,7 +21337,7 @@ function readSessionInstructionSourceFile(path) {
20045
21337
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
20046
21338
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
20047
21339
  }
20048
- return readFileSync17(path, "utf-8");
21340
+ return readFileSync18(path, "utf-8");
20049
21341
  }
20050
21342
  function parseLayeredReference(value) {
20051
21343
  const trimmed = value.trim();
@@ -20074,7 +21366,7 @@ async function collectSessionSources(opts, tool, store) {
20074
21366
  const path = resolveSessionPath(value);
20075
21367
  if (!existsSync24(path))
20076
21368
  throw new Error(`Identity instruction export not found: ${path}`);
20077
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
21369
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
20078
21370
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
20079
21371
  }
20080
21372
  return sources.map((source) => {
@@ -20218,7 +21510,7 @@ function readProjectContextBundleOption(value, allowMissing = false) {
20218
21510
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
20219
21511
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
20220
21512
  }
20221
- return { json: readFileSync17(path, "utf8"), sourcePath: path };
21513
+ return { json: readFileSync18(path, "utf8"), sourcePath: path };
20222
21514
  }
20223
21515
  function readBoundedProjectContextStdin() {
20224
21516
  const chunks = [];
@@ -20382,11 +21674,11 @@ program.command("add <path>").description("Ingest a file into the config DB").op
20382
21674
  console.error(chalk.red(`File not found: ${abs}`));
20383
21675
  process.exit(1);
20384
21676
  }
20385
- const rawContent = readFileSync17(abs, "utf-8");
21677
+ const rawContent = readFileSync18(abs, "utf-8");
20386
21678
  const storedFmt = detectFormat(abs);
20387
21679
  const fmt = redactFormatForTarget(abs, storedFmt);
20388
21680
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
20389
- const targetPath = abs.startsWith(homedir16()) ? abs.replace(homedir16(), "~") : abs;
21681
+ const targetPath = abs.startsWith(homedir13()) ? abs.replace(homedir13(), "~") : abs;
20390
21682
  const name = opts.name || filePath.split("/").pop();
20391
21683
  const store = resolveConfigStore();
20392
21684
  const allConfigs = await store.listConfigs();
@@ -20561,7 +21853,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
20561
21853
  for (const entry of entries) {
20562
21854
  if (!entry.isDirectory())
20563
21855
  continue;
20564
- const projDir = join26(absDir, entry.name);
21856
+ const projDir = join25(absDir, entry.name);
20565
21857
  const hasAgentConfig = [
20566
21858
  "CLAUDE.md",
20567
21859
  ".mcp.json",
@@ -20574,7 +21866,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
20574
21866
  ".aicopilot",
20575
21867
  ".cursor",
20576
21868
  ".agents"
20577
- ].some((marker) => existsSync24(join26(projDir, marker)));
21869
+ ].some((marker) => existsSync24(join25(projDir, marker)));
20578
21870
  if (!hasAgentConfig)
20579
21871
  continue;
20580
21872
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -20625,10 +21917,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
20625
21917
  });
20626
21918
  program.command("whoami").description("Show setup summary").action(async () => {
20627
21919
  const store = resolveConfigStore();
20628
- const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join26(getRawStoreRoot(), "instructions.db");
21920
+ const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
20629
21921
  const stats = await store.getConfigStats();
20630
21922
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
20631
- console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
21923
+ console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
20632
21924
  console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
20633
21925
  console.log();
20634
21926
  console.log(chalk.bold("By category:"));
@@ -21504,14 +22796,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
21504
22796
  } else if (target === "codex") {
21505
22797
  const { appendFileSync, existsSync: ex } = await import("fs");
21506
22798
  const { join: j } = await import("path");
21507
- const configPath = j(homedir16(), ".codex", "config.toml");
22799
+ const configPath = j(homedir13(), ".codex", "config.toml");
21508
22800
  const block = `
21509
22801
  [mcp_servers.configs]
21510
22802
  command = "${mcpBinary}"
21511
22803
  args = []
21512
22804
  `;
21513
22805
  if (ex(configPath)) {
21514
- const content = readFileSync17(configPath, "utf-8");
22806
+ const content = readFileSync18(configPath, "utf-8");
21515
22807
  if (content.includes("[mcp_servers.configs]")) {
21516
22808
  console.log(chalk.dim("= Already installed in Codex"));
21517
22809
  continue;
@@ -21522,7 +22814,7 @@ args = []
21522
22814
  } else if (target === "antigravity") {
21523
22815
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
21524
22816
  const { dirname: dn, join: j } = await import("path");
21525
- const configPath = j(homedir16(), ".gemini", "config", "mcp_config.json");
22817
+ const configPath = j(homedir13(), ".gemini", "config", "mcp_config.json");
21526
22818
  let settings = {};
21527
22819
  if (ex(configPath)) {
21528
22820
  try {
@@ -21606,9 +22898,9 @@ DB stats:`));
21606
22898
  if (count > 0)
21607
22899
  console.log(` ${key.padEnd(18)} ${count}`);
21608
22900
  }
21609
- const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join26(getRawStoreRoot(), "instructions.db");
22901
+ const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
21610
22902
  console.log(chalk.dim(`
21611
- ${isApiTransport() ? "API" : "DB"}: ${location}`));
22903
+ ${store.mode === "api" ? "API" : "DB"}: ${location}`));
21612
22904
  });
21613
22905
  program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
21614
22906
  const status = await getConfigsStatus(resolveConfigStore());
@@ -21667,10 +22959,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
21667
22959
  });
21668
22960
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
21669
22961
  const { mkdirSync: mk } = await import("fs");
21670
- const backupDir = join26(getRawStoreRoot(), "backups");
22962
+ const backupDir = join25(getRawStoreRoot(), "backups");
21671
22963
  mk(backupDir, { recursive: true });
21672
22964
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
21673
- const outPath = join26(backupDir, `configs-${ts}.tar.gz`);
22965
+ const outPath = join25(backupDir, `configs-${ts}.tar.gz`);
21674
22966
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
21675
22967
  const { statSync: st } = await import("fs");
21676
22968
  const size = st(outPath).size;
@@ -21858,7 +23150,7 @@ program.command("watch").description("Watch known config files for changes and a
21858
23150
  continue;
21859
23151
  const { readdirSync: readdirSync7 } = await import("fs");
21860
23152
  for (const f of readdirSync7(absDir).filter((f2) => f2.endsWith(".md"))) {
21861
- const abs = join26(absDir, f);
23153
+ const abs = join25(absDir, f);
21862
23154
  mtimes.set(abs, st(abs).mtimeMs);
21863
23155
  }
21864
23156
  } else {
@@ -21886,7 +23178,7 @@ program.command("watch").description("Watch known config files for changes and a
21886
23178
  if (!existsSync24(absDir))
21887
23179
  continue;
21888
23180
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
21889
- const abs = join26(absDir, f);
23181
+ const abs = join25(absDir, f);
21890
23182
  if (!mtimes.has(abs)) {
21891
23183
  mtimes.set(abs, st(abs).mtimeMs);
21892
23184
  changed++;
@@ -21926,7 +23218,7 @@ program.command("report").description("Summary of stored configs, drift, and eco
21926
23218
  missing++;
21927
23219
  continue;
21928
23220
  }
21929
- const disk = readFileSync17(abs, "utf-8");
23221
+ const disk = readFileSync18(abs, "utf-8");
21930
23222
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
21931
23223
  if (redactedDisk !== c.content)
21932
23224
  drifted++;
@@ -22026,12 +23318,9 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
22026
23318
  { name: "@hasna/skills", bin: "skills", mcp: "skills-mcp" },
22027
23319
  { name: "@hasna/economy", bin: "economy", mcp: "economy-mcp" },
22028
23320
  { name: "@hasna/attachments", bin: "attachments", mcp: "attachments-mcp" },
22029
- { name: "@hasna/sessions", bin: "sessions", mcp: "sessions-mcp" },
22030
23321
  { name: "@hasna/emails", bin: "emails", mcp: "emails-mcp" },
22031
23322
  { name: "@hasna/recordings", bin: "recordings", mcp: "recordings-mcp" },
22032
- { name: "@hasna/testers", bin: "testers", mcp: "testers-mcp" },
22033
- { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" },
22034
- { name: "@hasna/brains", bin: "brains", mcp: "brains-mcp" }
23323
+ { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" }
22035
23324
  ];
22036
23325
  console.log(chalk.bold("@hasna/instructions bootstrap") + chalk.dim(` \u2014 installing ${packages.length} ecosystem packages
22037
23326
  `));
@@ -22139,13 +23428,13 @@ providerContextCmd.command("resolve").description("Resolve the endpoint to a pro
22139
23428
  try {
22140
23429
  const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
22141
23430
  const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
22142
- const homeDir5 = opts.home ?? homedir16();
23431
+ const homeDir6 = opts.home ?? homedir13();
22143
23432
  const origin = normalizeEndpointOrigin(rawEndpoint);
22144
23433
  const resolution = resolveAndRenderProviderContext({
22145
23434
  origin,
22146
23435
  rawEndpoint,
22147
23436
  rawModel,
22148
- homeDir: homeDir5
23437
+ homeDir: homeDir6
22149
23438
  });
22150
23439
  if (opts.json) {
22151
23440
  printJson({