@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/index.js CHANGED
@@ -107,25 +107,799 @@ import { existsSync as existsSync2, mkdirSync, rmSync } from "fs";
107
107
  import { join as join3 } from "path";
108
108
  import { randomUUID } from "crypto";
109
109
 
110
- // src/lib/retired-storage-mode.ts
111
- var LEGACY_STORAGE_MODE_KEYS = [
112
- "HASNA_INSTRUCTIONS_STORAGE_MODE",
113
- "HASNA_INSTRUCTIONS_MODE",
114
- "INSTRUCTIONS_STORAGE_MODE",
115
- "INSTRUCTIONS_MODE"
116
- ];
117
- function firstDefinedEnvKey(env, keys) {
118
- for (const key of keys) {
119
- if (Object.hasOwn(env, key) && env[key] !== undefined)
120
- return key;
110
+ // ../contracts/dist/client/transport.js
111
+ import { isIP } from "net";
112
+ import { spawnSync } from "child_process";
113
+ import { closeSync, fstatSync, openSync, readFileSync } from "fs";
114
+ import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
115
+ import { createRequire } from "module";
116
+ import { hostname as osHostname } from "os";
117
+ import { isAbsolute, join } from "path";
118
+ function envToken(name) {
119
+ return name.toUpperCase().replace(/-/g, "_");
120
+ }
121
+ function clientTransportEnvKeys(name) {
122
+ const envSegment = envToken(name);
123
+ return {
124
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
125
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
126
+ };
127
+ }
128
+ function credentialOverrideEnvKey(name) {
129
+ return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
130
+ }
131
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE";
132
+ function credentialPointerEnvKey(name) {
133
+ return `HASNA_${envToken(name)}_API_KEY_REF`;
134
+ }
135
+
136
+ class CredentialResolutionError extends Error {
137
+ appName;
138
+ attempted;
139
+ constructor(appName, message, attempted) {
140
+ super(message);
141
+ this.name = "CredentialResolutionError";
142
+ this.appName = appName;
143
+ this.attempted = attempted;
144
+ }
145
+ }
146
+
147
+ class CredentialFileUnsafeError extends Error {
148
+ path;
149
+ constructor(path, reason) {
150
+ super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
151
+ this.name = "CredentialFileUnsafeError";
152
+ this.path = path;
153
+ }
154
+ }
155
+ var HASNA_HOME_ENV_KEY = "HASNA_HOME";
156
+ var HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME";
157
+ var KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION";
158
+ var HASNA_HOME_DIR = ".hasna";
159
+ var CONFIG_SUBDIR = "config";
160
+ var CREDENTIALS_FILE = "credentials";
161
+ var KEYCHAIN_SECURITY_BIN = "/usr/bin/security";
162
+ var KEYCHAIN_SERVICE_PREFIX = "hasna.credentials";
163
+ var KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44;
164
+ var KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4;
165
+ var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
166
+ var SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
167
+ var SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
168
+ var ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
169
+ var VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
170
+ function homeDir(env) {
171
+ const home = env.HOME?.trim();
172
+ return home ? home : null;
173
+ }
174
+ function absoluteOverride(env, key) {
175
+ const value = env[key]?.trim();
176
+ return value && isAbsolute(value) ? value : null;
177
+ }
178
+ function hasnaHomeDir(env) {
179
+ const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
180
+ if (override)
181
+ return override;
182
+ const home = homeDir(env);
183
+ return home ? join(home, HASNA_HOME_DIR) : null;
184
+ }
185
+ function appConfigDir(name, env) {
186
+ const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
187
+ if (configRoot)
188
+ return join(configRoot, name);
189
+ const root = hasnaHomeDir(env);
190
+ return root ? join(root, name, CONFIG_SUBDIR) : null;
191
+ }
192
+ function credentialDiskSourceList(name, env, profile = null) {
193
+ if (!SAFE_APP_SLUG.test(name))
194
+ return [];
195
+ const directory = appConfigDir(name, env);
196
+ if (!directory)
197
+ return [];
198
+ const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
199
+ return [{ path: join(directory, file), tier: "disk" }];
200
+ }
201
+ function credentialDiskSources(name, env) {
202
+ return credentialDiskSourceList(name, env, null).map((s) => s.path);
203
+ }
204
+ function profileDiskSources(name, env, profile) {
205
+ return credentialDiskSourceList(name, env, profile).map((s) => s.path);
206
+ }
207
+ function parseEnvFile(text) {
208
+ const values = new Map;
209
+ const unusable = new Set;
210
+ for (const rawLine of text.split(/\r?\n/)) {
211
+ const line = rawLine.trim();
212
+ if (line.length === 0 || line.startsWith("#"))
213
+ continue;
214
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
215
+ const equals = withoutExport.indexOf("=");
216
+ if (equals <= 0)
217
+ continue;
218
+ const key = withoutExport.slice(0, equals).trim();
219
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
220
+ continue;
221
+ let value = withoutExport.slice(equals + 1).trim();
222
+ const quote = value[0];
223
+ if (quote === '"' || quote === "'") {
224
+ if (value.length < 2 || !value.endsWith(quote)) {
225
+ unusable.add(key);
226
+ continue;
227
+ }
228
+ value = value.slice(1, -1);
229
+ }
230
+ if (value.trim().length === 0) {
231
+ unusable.add(key);
232
+ continue;
233
+ }
234
+ if (values.has(key) && values.get(key) !== value)
235
+ unusable.add(key);
236
+ values.set(key, value);
237
+ }
238
+ return { values, unusable };
239
+ }
240
+ function configFileModeAllowed(mode) {
241
+ const permissions = mode & 4095;
242
+ return permissions === 256 || permissions === 384;
243
+ }
244
+ function configFileReadsCoherent(before, after) {
245
+ return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
246
+ }
247
+ function readAppConfigFile(path) {
248
+ const unsafe = (reason) => {
249
+ throw new CredentialFileUnsafeError(path, reason);
250
+ };
251
+ let fd = -1;
252
+ try {
253
+ fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
254
+ } catch (error) {
255
+ const code = error.code;
256
+ if (code === "ENOENT" || code === "ENOTDIR")
257
+ return null;
258
+ if (code === "ELOOP")
259
+ unsafe("the path is a symlink");
260
+ unsafe(`the path could not be opened (${code ?? "unknown error"})`);
261
+ }
262
+ try {
263
+ const before = fstatSync(fd);
264
+ if (!before.isFile())
265
+ unsafe("the path is not a regular file");
266
+ if (!configFileModeAllowed(before.mode)) {
267
+ unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
268
+ }
269
+ const uid = process.getuid?.() ?? process.geteuid?.();
270
+ if (uid !== undefined && before.uid !== uid)
271
+ unsafe("the file is not owned by the current user");
272
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES)
273
+ unsafe("the file exceeds the size limit");
274
+ const bytes = readFileSync(fd);
275
+ const after = fstatSync(fd);
276
+ if (!configFileReadsCoherent(before, after)) {
277
+ unsafe("the file changed while being read");
278
+ }
279
+ return parseEnvFile(bytes.toString("utf8"));
280
+ } finally {
281
+ if (fd !== -1)
282
+ closeSync(fd);
283
+ }
284
+ }
285
+ function readCredentialFile(path, apiKeyKeys) {
286
+ const parsed = readAppConfigFile(path);
287
+ if (!parsed)
288
+ return null;
289
+ for (const key of apiKeyKeys) {
290
+ if (parsed.unusable.has(key)) {
291
+ throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
292
+ }
293
+ }
294
+ const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
295
+ if (new Set(values).size > 1) {
296
+ throw new CredentialFileUnsafeError(path, "credential aliases disagree");
297
+ }
298
+ return values[0] ?? null;
299
+ }
300
+ var CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
301
+ function appConfigDiskValue(name, env, keys) {
302
+ const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
303
+ if (wanted.length === 0)
304
+ return null;
305
+ for (const path of credentialDiskSources(name, env)) {
306
+ const parsed = readAppConfigFile(path);
307
+ if (!parsed)
308
+ continue;
309
+ if (wanted.some((key) => parsed.unusable.has(key))) {
310
+ return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
311
+ }
312
+ const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
313
+ if (new Set(values).size > 1)
314
+ throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
315
+ for (const key of wanted) {
316
+ if (parsed.unusable.has(key))
317
+ return { key, value: "", path, unusable: true };
318
+ const value = parsed.values.get(key)?.trim();
319
+ if (value)
320
+ return { key, value, path };
321
+ }
121
322
  }
122
323
  return null;
123
324
  }
124
- function assertNoLegacyStorageMode(env = process.env) {
125
- const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
126
- if (!legacyKey)
325
+ function assertUsableCredential(appName, source, value) {
326
+ if (VAULT_POINTER_SHAPE.test(value)) {
327
+ 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 ${credentialPointerEnvKey(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
328
+ }
329
+ if (!ILLEGAL_IN_HEADER_VALUE.test(value))
127
330
  return;
128
- 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.`);
331
+ 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]);
332
+ }
333
+ var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
334
+ var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
335
+ function sealCredential(fields) {
336
+ const { apiKey } = fields;
337
+ const visible = {
338
+ tier: fields.tier,
339
+ source: fields.source,
340
+ deliberate: fields.deliberate,
341
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
342
+ warning: fields.warning
343
+ };
344
+ const sealed = { ...visible };
345
+ Object.defineProperty(sealed, "apiKey", {
346
+ value: apiKey,
347
+ enumerable: false,
348
+ writable: false,
349
+ configurable: false
350
+ });
351
+ if (fields.pointerVaultKey !== undefined) {
352
+ Object.defineProperty(sealed, "pointerVaultKey", {
353
+ value: fields.pointerVaultKey,
354
+ enumerable: false,
355
+ writable: false,
356
+ configurable: false
357
+ });
358
+ }
359
+ Object.defineProperty(sealed, INSPECT_CUSTOM, {
360
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
361
+ enumerable: false,
362
+ writable: false,
363
+ configurable: false
364
+ });
365
+ Object.defineProperty(sealed, CREDENTIAL_SEAL, {
366
+ value: true,
367
+ enumerable: false,
368
+ writable: false,
369
+ configurable: false
370
+ });
371
+ return Object.freeze(sealed);
372
+ }
373
+ function firstEnvValue(env, keys) {
374
+ for (const key of keys) {
375
+ if (!Object.prototype.hasOwnProperty.call(env, key))
376
+ continue;
377
+ const value = env[key]?.trim();
378
+ if (value)
379
+ return { key, value };
380
+ }
381
+ return null;
382
+ }
383
+ var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
384
+ function isAmbientEnvironment(env) {
385
+ return env === process.env || env[AMBIENT_ENVIRONMENT] === true;
386
+ }
387
+ function defaultKeychainRunner(argv) {
388
+ const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
389
+ encoding: "utf8",
390
+ stdio: ["ignore", "pipe", "pipe"],
391
+ timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
392
+ });
393
+ return {
394
+ status: result.status,
395
+ stdout: result.stdout ?? "",
396
+ stderr: result.error ? result.error.message : result.stderr ?? ""
397
+ };
398
+ }
399
+ function keychainTierEnabled(env, options) {
400
+ if ((options.platform ?? process.platform) !== "darwin")
401
+ return false;
402
+ if (options.enabled !== undefined)
403
+ return options.enabled;
404
+ return options.run !== undefined || isAmbientEnvironment(env);
405
+ }
406
+ function keychainAccount(env, options) {
407
+ const station = env[KEYCHAIN_STATION_ENV_KEY]?.trim();
408
+ if (station)
409
+ return station;
410
+ const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
411
+ if (host)
412
+ return host;
413
+ const user = env.USER?.trim();
414
+ return user || null;
415
+ }
416
+ function keychainFailureHint(text) {
417
+ const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
418
+ const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
419
+ return clean ? `: ${clean}` : "";
420
+ }
421
+ function readKeychainItem(name, env, kind, options) {
422
+ if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env, options))
423
+ return null;
424
+ const account = keychainAccount(env, options);
425
+ if (!account)
426
+ return null;
427
+ const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
428
+ const source = `keychain:${service}@${account}`;
429
+ const run = options.run ?? defaultKeychainRunner;
430
+ let result;
431
+ try {
432
+ result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
433
+ } catch (error) {
434
+ const reason = keychainFailureHint(error instanceof Error ? error.message : String(error));
435
+ 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]);
436
+ }
437
+ if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
438
+ return null;
439
+ if (result.status !== 0) {
440
+ 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]);
441
+ }
442
+ const value = result.stdout.trim();
443
+ if (!value) {
444
+ 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]);
445
+ }
446
+ return { value, source };
447
+ }
448
+ function keychainConfigValue(name, env, options = {}) {
449
+ return readKeychainItem(name, env, "api-url", options);
450
+ }
451
+ function snapshotClientEnvironment(name, env) {
452
+ const keys = clientTransportEnvKeys(name);
453
+ const ambient = isAmbientEnvironment(env);
454
+ const snapshot = Object.create(null);
455
+ for (const key of [
456
+ ...keys.apiUrlKeys,
457
+ ...keys.apiKeyKeys,
458
+ credentialOverrideEnvKey(name),
459
+ credentialPointerEnvKey(name),
460
+ CREDENTIAL_PROFILE_ENV_KEY,
461
+ "HOME",
462
+ HASNA_HOME_ENV_KEY,
463
+ HASNA_CONFIG_HOME_ENV_KEY,
464
+ KEYCHAIN_STATION_ENV_KEY,
465
+ "USER"
466
+ ]) {
467
+ const descriptor = Object.getOwnPropertyDescriptor(env, key);
468
+ if (!descriptor)
469
+ continue;
470
+ if (!("value" in descriptor)) {
471
+ throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
472
+ }
473
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
474
+ throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
475
+ }
476
+ snapshot[key] = descriptor.value;
477
+ }
478
+ if (ambient) {
479
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT, {
480
+ value: true,
481
+ enumerable: false,
482
+ writable: false,
483
+ configurable: false
484
+ });
485
+ }
486
+ return Object.freeze(snapshot);
487
+ }
488
+ function resolveCredential(name, env, options = {}) {
489
+ env = snapshotClientEnvironment(name, env);
490
+ const { apiKeyKeys } = clientTransportEnvKeys(name);
491
+ const diskPaths = credentialDiskSources(name, env);
492
+ if (options.apiKey !== undefined) {
493
+ const explicitKey = options.apiKey.trim();
494
+ if (!explicitKey) {
495
+ throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
496
+ }
497
+ assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
498
+ return sealCredential({
499
+ apiKey: explicitKey,
500
+ tier: "argument",
501
+ source: "explicit apiKey argument",
502
+ deliberate: true,
503
+ diskCandidates: diskPaths,
504
+ warning: null
505
+ });
506
+ }
507
+ const overrideKeyName = credentialOverrideEnvKey(name);
508
+ const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
509
+ if (overrideRaw !== undefined) {
510
+ const override = overrideRaw.trim();
511
+ if (!override) {
512
+ 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]);
513
+ }
514
+ assertUsableCredential(name, overrideKeyName, override);
515
+ return sealCredential({
516
+ apiKey: override,
517
+ tier: "override",
518
+ source: overrideKeyName,
519
+ deliberate: true,
520
+ diskCandidates: diskPaths,
521
+ warning: null
522
+ });
523
+ }
524
+ const pointerKeyName = credentialPointerEnvKey(name);
525
+ const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
526
+ if (pointerRaw !== undefined) {
527
+ const pointer = pointerRaw.trim();
528
+ if (!pointer) {
529
+ 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]);
530
+ }
531
+ if (!VAULT_POINTER_SHAPE.test(pointer)) {
532
+ 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]);
533
+ }
534
+ return sealCredential({
535
+ apiKey: "",
536
+ pointerVaultKey: pointer,
537
+ tier: "pointer",
538
+ source: pointerKeyName,
539
+ deliberate: true,
540
+ diskCandidates: diskPaths,
541
+ warning: null
542
+ });
543
+ }
544
+ if (options.profile !== undefined && !options.profile.trim()) {
545
+ throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
546
+ }
547
+ const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY) ? env[CREDENTIAL_PROFILE_ENV_KEY] : undefined;
548
+ if (profileRaw !== undefined && !profileRaw.trim()) {
549
+ throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY]);
550
+ }
551
+ const profile = options.profile?.trim() || profileRaw?.trim();
552
+ if (profile) {
553
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
554
+ if (!SAFE_PROFILE.test(profile)) {
555
+ throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
556
+ }
557
+ const paths = profileDiskSources(name, env, profile);
558
+ for (const path of paths) {
559
+ const value = readCredentialFile(path, apiKeyKeys);
560
+ if (value) {
561
+ assertUsableCredential(name, path, value);
562
+ return sealCredential({
563
+ apiKey: value,
564
+ tier: "profile",
565
+ source: path,
566
+ deliberate: true,
567
+ diskCandidates: paths,
568
+ warning: null
569
+ });
570
+ }
571
+ }
572
+ 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_KEY}.`, paths);
573
+ }
574
+ const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
575
+ const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
576
+ if (blankEnv) {
577
+ throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
578
+ }
579
+ if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
580
+ 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));
581
+ }
582
+ const envHit = firstEnvValue(env, apiKeyKeys);
583
+ const keychainHit = readKeychainItem(name, env, "api-key", options.keychain ?? {});
584
+ if (keychainHit) {
585
+ assertUsableCredential(name, keychainHit.source, keychainHit.value);
586
+ 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;
587
+ return sealCredential({
588
+ apiKey: keychainHit.value,
589
+ tier: "keychain",
590
+ source: keychainHit.source,
591
+ deliberate: false,
592
+ diskCandidates: diskPaths,
593
+ warning
594
+ });
595
+ }
596
+ const diskSourceList = credentialDiskSourceList(name, env, null);
597
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
598
+ if (diskHits.length > 0) {
599
+ const winner = diskHits[0];
600
+ assertUsableCredential(name, winner.src.path, winner.value);
601
+ const divergentSources = [
602
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
603
+ ...envHit && envHit.value !== winner.value ? [envHit.key] : []
604
+ ];
605
+ 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;
606
+ return sealCredential({
607
+ apiKey: winner.value,
608
+ tier: winner.src.tier,
609
+ source: winner.src.path,
610
+ deliberate: false,
611
+ diskCandidates: diskPaths,
612
+ warning
613
+ });
614
+ }
615
+ if (envHit) {
616
+ assertUsableCredential(name, envHit.key, envHit.value);
617
+ return sealCredential({
618
+ apiKey: envHit.value,
619
+ tier: "env",
620
+ source: envHit.key,
621
+ deliberate: false,
622
+ diskCandidates: diskPaths,
623
+ warning: null
624
+ });
625
+ }
626
+ return null;
627
+ }
628
+ var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
629
+ var requireSecretsSdk = createRequire(import.meta.url);
630
+ var DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com";
631
+ var DEFAULT_AUTHORITY_SOURCE = "default";
632
+ function defaultFleetGatewayBaseUrl(name) {
633
+ return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
634
+ }
635
+ var ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
636
+ var DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
637
+ function isValidDnsDomain(value) {
638
+ if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
639
+ return false;
640
+ }
641
+ return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
642
+ }
643
+ function validateAppSlug(name) {
644
+ if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
645
+ throw new Error("App name must be one lowercase DNS label.");
646
+ }
647
+ return name;
648
+ }
649
+ function rawAuthority(value) {
650
+ const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
651
+ if (!match)
652
+ throw new Error("API URL must be absolute.");
653
+ const afterScheme = value.slice(match[0].length);
654
+ const boundary = afterScheme.search(/[/?#]/);
655
+ const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
656
+ if (!authority)
657
+ throw new Error("API URL must include a hostname.");
658
+ return authority;
659
+ }
660
+ function assertCanonicalPort(port) {
661
+ if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
662
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
663
+ }
664
+ const numericPort = Number(port);
665
+ if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
666
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
667
+ }
668
+ }
669
+ function canonicalAuthorityHostname(authority) {
670
+ let rawHostname;
671
+ if (authority.startsWith("[")) {
672
+ const closingBracket = authority.indexOf("]");
673
+ if (closingBracket === -1) {
674
+ throw new Error("API URL authority must contain a canonical hostname.");
675
+ }
676
+ rawHostname = authority.slice(0, closingBracket + 1);
677
+ const portSuffix = authority.slice(closingBracket + 1);
678
+ if (portSuffix) {
679
+ if (!portSuffix.startsWith(":")) {
680
+ throw new Error("API URL authority must contain a canonical hostname and port.");
681
+ }
682
+ assertCanonicalPort(portSuffix.slice(1));
683
+ }
684
+ if (isIP(rawHostname.slice(1, -1)) !== 6) {
685
+ throw new Error("API URL authority must contain a canonical IPv6 literal.");
686
+ }
687
+ } else {
688
+ const firstColon = authority.indexOf(":");
689
+ const lastColon = authority.lastIndexOf(":");
690
+ if (firstColon !== lastColon) {
691
+ throw new Error("IPv6 API URL authorities must use brackets.");
692
+ }
693
+ if (lastColon !== -1) {
694
+ const port = authority.slice(lastColon + 1);
695
+ assertCanonicalPort(port);
696
+ rawHostname = authority.slice(0, lastColon);
697
+ } else {
698
+ rawHostname = authority;
699
+ }
700
+ const ipVersion = isIP(rawHostname);
701
+ const numericAddressParts = rawHostname.split(".");
702
+ const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
703
+ if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
704
+ throw new Error("API URL authority must contain a canonical ASCII hostname.");
705
+ }
706
+ }
707
+ return rawHostname.toLowerCase();
708
+ }
709
+ function isDeliberateLoopbackHttpAuthority(authority) {
710
+ return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
711
+ }
712
+ function toV1BaseUrl(apiUrl) {
713
+ if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
714
+ throw new Error("API URL must not contain ASCII control characters.");
715
+ }
716
+ const input = apiUrl.trim();
717
+ const authority = rawAuthority(input);
718
+ if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
719
+ throw new Error("API URL authority must be canonical ASCII without credentials.");
720
+ }
721
+ const canonicalHostname = canonicalAuthorityHostname(authority);
722
+ const url = new URL(input);
723
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
724
+ throw new Error("API URL must use http or https.");
725
+ }
726
+ if (url.username || url.password) {
727
+ throw new Error("API URL must not include credentials.");
728
+ }
729
+ if (!url.hostname || url.hostname.endsWith(".")) {
730
+ throw new Error("API URL must include a canonical hostname.");
731
+ }
732
+ if (url.hostname.toLowerCase() !== canonicalHostname) {
733
+ throw new Error("API URL authority must not rely on parser hostname normalization.");
734
+ }
735
+ if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
736
+ throw new Error("API URL must not use IDN or punycode hostnames.");
737
+ }
738
+ if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
739
+ throw new Error("API URL may use http only for an exact loopback authority.");
740
+ }
741
+ if (url.search || url.hash) {
742
+ throw new Error("API URL must not include a query string or fragment.");
743
+ }
744
+ let path = url.pathname.replace(/\/+$/, "");
745
+ if (path.endsWith("/v1"))
746
+ path = path.slice(0, -"/v1".length);
747
+ url.pathname = `${path}/v1`;
748
+ return url.toString().replace(/\/+$/, "");
749
+ }
750
+ class ClientTransportConfigurationError extends Error {
751
+ appName;
752
+ sources;
753
+ constructor(appName, message, sources = []) {
754
+ super(message);
755
+ this.name = "ClientTransportConfigurationError";
756
+ this.appName = appName;
757
+ this.sources = Object.freeze([...sources]);
758
+ }
759
+ }
760
+ function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
761
+ env = snapshotClientEnvironment(name, env);
762
+ const keys = clientTransportEnvKeys(name);
763
+ const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
764
+ const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
765
+ if (blankUrl) {
766
+ 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]);
767
+ }
768
+ const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
769
+ if (controlledUrl) {
770
+ throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
771
+ }
772
+ const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
773
+ if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
774
+ 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));
775
+ }
776
+ const envUrlHit = usableUrlEntries[0] ?? null;
777
+ const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
778
+ const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys);
779
+ if (diskConfigUrlHit?.unusable) {
780
+ 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]);
781
+ }
782
+ const urlCandidates = [
783
+ ...envUrlHit ? [envUrlHit] : [],
784
+ ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
785
+ ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
786
+ ];
787
+ const configuredUrl = urlCandidates[0] ?? null;
788
+ const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
789
+ if (configuredUrl && divergentUrls.length > 0) {
790
+ 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));
791
+ }
792
+ const warnings = [];
793
+ if (configuredUrl && !envUrlHit) {
794
+ 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.`);
795
+ }
796
+ const credential = resolveCredential(name, env, options.credentials);
797
+ if (!credential) {
798
+ const diskHint = credentialDiskSourcesForMessage(name, env);
799
+ 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`;
800
+ 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.`);
801
+ throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
802
+ }
803
+ if (credential.warning)
804
+ warnings.push(credential.warning);
805
+ let urlHit;
806
+ if (configuredUrl) {
807
+ urlHit = configuredUrl;
808
+ } else {
809
+ try {
810
+ urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
811
+ } catch (error) {
812
+ const message = error instanceof Error ? error.message : String(error);
813
+ 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]]);
814
+ }
815
+ }
816
+ const apiUrlSource = urlHit.key;
817
+ let baseUrl;
818
+ try {
819
+ baseUrl = toV1BaseUrl(urlHit.value);
820
+ } catch (error) {
821
+ const message = error instanceof Error ? error.message : String(error);
822
+ throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
823
+ }
824
+ return {
825
+ resolution: {
826
+ transport: "http",
827
+ transportSource: urlHit.key,
828
+ baseUrl,
829
+ apiUrlSource,
830
+ apiKeyPresent: true,
831
+ apiKeySource: credential.source,
832
+ apiKeyTier: credential.tier,
833
+ misconfigured: false,
834
+ warning: warnings.length > 0 ? warnings.join(" ") : null
835
+ },
836
+ credential
837
+ };
838
+ }
839
+ function resolveClientTransport(name, env = process.env, options = {}) {
840
+ return resolveClientTransportSnapshot(name, env, options).resolution;
841
+ }
842
+ function credentialDiskSourcesForMessage(name, env) {
843
+ const paths = credentialDiskSources(name, env);
844
+ return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
845
+ }
846
+ var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
847
+ var AUTHORITY_OVERRIDE_HEADERS = new Set([
848
+ "host",
849
+ ":authority",
850
+ "forwarded",
851
+ "x-forwarded-host",
852
+ "x-original-host"
853
+ ]);
854
+
855
+ // src/lib/local-opt-in.ts
856
+ var INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_INSTRUCTIONS_LOCAL"];
857
+ function instructionsLocalModeNotice() {
858
+ 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.`;
859
+ }
860
+ function isInstructionsLocalOptIn(env = process.env) {
861
+ return INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() === "1");
862
+ }
863
+ function instructionsAuthorityEnvKeys() {
864
+ const keys = clientTransportEnvKeys("instructions");
865
+ return [
866
+ ...keys.apiUrlKeys,
867
+ ...keys.apiKeyKeys,
868
+ credentialOverrideEnvKey("instructions"),
869
+ credentialPointerEnvKey("instructions"),
870
+ CREDENTIAL_PROFILE_ENV_KEY
871
+ ];
872
+ }
873
+ function hasInstructionsEnvAuthorityIntent(env = process.env) {
874
+ return instructionsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
875
+ }
876
+ function selectsInstructionsLocalStore(env = process.env) {
877
+ return !hasInstructionsEnvAuthorityIntent(env) && isInstructionsLocalOptIn(env);
878
+ }
879
+ function instructionsResolverEnv(env) {
880
+ const blanks = instructionsAuthorityEnvKeys().filter((key) => (key in env) && (env[key] ?? "").trim() === "");
881
+ if (blanks.length === 0)
882
+ return env;
883
+ const next = { ...env };
884
+ for (const key of blanks)
885
+ delete next[key];
886
+ return next;
887
+ }
888
+ var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
889
+ function isAmbientInstructionsEnv(env) {
890
+ if (typeof process !== "undefined" && env === process.env)
891
+ return true;
892
+ return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
893
+ }
894
+ function instructionsResolverInputs(env, credentials = {}) {
895
+ const normalised = instructionsResolverEnv(env);
896
+ if (normalised === env)
897
+ return { env: normalised, credentials };
898
+ const keychain = { ...credentials.keychain };
899
+ if (keychain.enabled === undefined && keychain.run === undefined) {
900
+ keychain.enabled = isAmbientInstructionsEnv(env);
901
+ }
902
+ return { env: normalised, credentials: { ...credentials, keychain } };
129
903
  }
130
904
 
131
905
  // src/lib/raw-store-root.ts
@@ -133,87 +907,77 @@ import { resolve as resolve2 } from "path";
133
907
 
134
908
  // src/lib/app-home.ts
135
909
  import { existsSync } from "fs";
136
- import { homedir as homedir2 } from "os";
137
- import { join as join2, resolve } from "path";
138
-
139
- // ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
140
910
  import { homedir } from "os";
141
- import { join } from "path";
142
- var KIND_ENV = {
911
+ import { join as join2, resolve } from "path";
912
+ import { homedir as pathsResolverHomedir } from "os";
913
+ import { join as pathsResolverJoin } from "path";
914
+ var PATHS_RESOLVER_KIND_ENV = {
143
915
  config: "HASNA_CONFIG_HOME",
144
916
  data: "HASNA_DATA_HOME",
145
917
  state: "HASNA_STATE_HOME",
146
918
  cache: "HASNA_CACHE_HOME"
147
919
  };
148
- var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
149
- function assertApp(app) {
920
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
921
+ function pathsResolverAssertApp(app) {
150
922
  if (typeof app !== "string" || app.length === 0) {
151
923
  throw new TypeError("paths: app must be a non-empty string");
152
924
  }
153
- if (!APP_SLUG_RE.test(app)) {
925
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
154
926
  throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
155
927
  }
156
928
  }
157
- function envOf(options) {
158
- return options.env ?? process.env;
159
- }
160
- function envValue(options, kind) {
161
- const value = envOf(options)[KIND_ENV[kind]];
162
- return typeof value === "string" && value.length > 0 ? value : undefined;
163
- }
164
- function isMacOS(platform) {
165
- return platform === "darwin";
929
+ function pathsResolverAssertKind(kind) {
930
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
931
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
932
+ }
166
933
  }
167
- function baseDir(kind, options) {
168
- const override = envValue(options, kind);
169
- if (override)
934
+ function pathsResolverBaseDir(kind, options) {
935
+ pathsResolverAssertKind(kind);
936
+ const env = options.env ?? process.env;
937
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
938
+ if (typeof override === "string" && override.length > 0)
170
939
  return override;
171
- const home = options.home ?? homedir();
940
+ const home = options.home ?? pathsResolverHomedir();
172
941
  const platform = options.platform ?? process.platform;
173
- if (isMacOS(platform)) {
942
+ if (platform === "darwin") {
174
943
  switch (kind) {
175
944
  case "config":
176
945
  case "data":
177
- return join(home, "Library", "Application Support", "Hasna");
946
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
178
947
  case "cache":
179
- return join(home, "Library", "Caches", "Hasna");
948
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
180
949
  case "state":
181
- return join(home, "Library", "Logs", "Hasna");
950
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
182
951
  }
183
952
  }
184
953
  switch (kind) {
185
954
  case "config":
186
- return join(home, ".config", "hasna");
955
+ return pathsResolverJoin(home, ".config", "hasna");
187
956
  case "data":
188
- return join(home, ".local", "share", "hasna");
957
+ return pathsResolverJoin(home, ".local", "share", "hasna");
189
958
  case "state":
190
- return join(home, ".local", "state", "hasna");
959
+ return pathsResolverJoin(home, ".local", "state", "hasna");
191
960
  case "cache":
192
- return join(home, ".cache", "hasna");
961
+ return pathsResolverJoin(home, ".cache", "hasna");
193
962
  }
194
963
  }
195
- function resolvePath(kind, options) {
196
- assertApp(options.app);
197
- const appSegment = options.internal === true ? join("internal", options.app) : options.app;
198
- return join(baseDir(kind, options), appSegment);
964
+ function pathsResolverResolve(kind, options) {
965
+ pathsResolverAssertApp(options.app);
966
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
967
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
199
968
  }
200
969
  function configDir(options) {
201
- return resolvePath("config", options);
970
+ return pathsResolverResolve("config", options);
202
971
  }
203
- function stateDir(options) {
204
- return resolvePath("state", options);
205
- }
206
-
207
- // src/lib/app-home.ts
208
972
  var HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
209
- function homeDir(env = process.env) {
210
- return env["HOME"] || env["USERPROFILE"] || homedir2();
973
+ function homeDir2(env = process.env) {
974
+ return env["HOME"] || env["USERPROFILE"] || homedir();
211
975
  }
212
976
  function legacyStoreHome(env = process.env) {
213
- return resolve(join2(homeDir(env), ".hasna", "instructions"));
977
+ return resolve(join2(homeDir2(env), ".hasna", "instructions"));
214
978
  }
215
979
  function resolverStoreHome(env = process.env) {
216
- return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir2() });
980
+ return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir() });
217
981
  }
218
982
  function adoptResolverStoreHome(resolved, env = process.env) {
219
983
  const override = env.HASNA_CONFIG_HOME;
@@ -343,9 +1107,8 @@ var _db = null;
343
1107
  function getDatabase(path) {
344
1108
  if (_db)
345
1109
  return _db;
346
- assertNoLegacyStorageMode();
347
- if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
348
- 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.");
1110
+ if (!path && hasInstructionsEnvAuthorityIntent(process.env)) {
1111
+ 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.");
349
1112
  }
350
1113
  const dbPath = path || getDbPath();
351
1114
  const db = new Database(dbPath);
@@ -642,7 +1405,7 @@ function getConfigStats(db) {
642
1405
  }
643
1406
 
644
1407
  // src/lib/machine.ts
645
- import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
1408
+ import { arch as currentArch, homedir as homedir2, hostname as currentHostname, type as currentOsType } from "os";
646
1409
  import { existsSync as existsSync3 } from "fs";
647
1410
  import { join as join4 } from "path";
648
1411
 
@@ -717,10 +1480,10 @@ function normalizeOsFamily(os) {
717
1480
  return value || "unknown";
718
1481
  }
719
1482
  function detectMachineContext(overrides = {}) {
720
- const homeDir2 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
1483
+ const homeDir3 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir2();
721
1484
  const os = overrides.os ?? currentOsType();
722
1485
  const osFamily = normalizeOsFamily(os);
723
- const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir2, ".bun", "bin");
1486
+ const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir3, ".bun", "bin");
724
1487
  const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join4(bunBinDir, "bun");
725
1488
  return {
726
1489
  id: "current-machine",
@@ -730,8 +1493,8 @@ function detectMachineContext(overrides = {}) {
730
1493
  last_applied_at: null,
731
1494
  created_at: "",
732
1495
  os_family: osFamily,
733
- home_dir: homeDir2,
734
- workspace_root: overrides.workspace_root ?? join4(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
1496
+ home_dir: homeDir3,
1497
+ workspace_root: overrides.workspace_root ?? join4(homeDir3, osFamily === "macos" ? "Workspace" : "workspace"),
735
1498
  bun_bin_dir: bunBinDir,
736
1499
  bun_path: overrides.bun_path ?? defaultBunPath,
737
1500
  path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join4("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
@@ -852,9 +1615,9 @@ import { createHash as createHash7 } from "crypto";
852
1615
 
853
1616
  // src/lib/session-render.ts
854
1617
  import { createHash as createHash6 } from "crypto";
855
- import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
856
- import { homedir as homedir7 } from "os";
857
- import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join9, parse as parse2, posix as posix2, relative as relative2, resolve as resolve8 } from "path";
1618
+ import { existsSync as existsSync7, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
1619
+ import { homedir as homedir6 } from "os";
1620
+ import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute4, join as join9, parse as parse2, posix as posix2, relative as relative2, resolve as resolve8 } from "path";
858
1621
 
859
1622
  // src/lib/global-agent-rules-standard.ts
860
1623
  import { createHash } from "crypto";
@@ -1165,22 +1928,22 @@ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
1165
1928
  import { execFileSync } from "child_process";
1166
1929
  import { dlopen, FFIType } from "bun:ffi";
1167
1930
  import {
1168
- closeSync,
1931
+ closeSync as closeSync2,
1169
1932
  constants,
1170
1933
  existsSync as existsSync5,
1171
- fstatSync,
1934
+ fstatSync as fstatSync2,
1172
1935
  fsyncSync,
1173
1936
  lstatSync,
1174
1937
  linkSync,
1175
1938
  mkdirSync as mkdirSync2,
1176
- openSync,
1177
- readFileSync,
1939
+ openSync as openSync2,
1940
+ readFileSync as readFileSync2,
1178
1941
  renameSync,
1179
1942
  rmSync as rmSync2,
1180
1943
  statSync,
1181
1944
  writeFileSync
1182
1945
  } from "fs";
1183
- import { basename, dirname as dirname2, isAbsolute, join as join6, parse, relative, resolve as resolve4 } from "path";
1946
+ import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join6, parse, relative, resolve as resolve4 } from "path";
1184
1947
 
1185
1948
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
1186
1949
  var exports_external = {};
@@ -5375,11 +6138,71 @@ var SESSION_INSTRUCTION_LAYERS = [
5375
6138
 
5376
6139
  // src/lib/session-render-state.ts
5377
6140
  import { existsSync as existsSync4, readdirSync } from "fs";
5378
- import { homedir as homedir4 } from "os";
6141
+ import { homedir as homedir3 } from "os";
5379
6142
  import { dirname, join as join5, resolve as resolve3 } from "path";
6143
+ import { homedir as pathsResolverHomedir2 } from "os";
6144
+ import { join as pathsResolverJoin2 } from "path";
6145
+ var PATHS_RESOLVER_KIND_ENV2 = {
6146
+ config: "HASNA_CONFIG_HOME",
6147
+ data: "HASNA_DATA_HOME",
6148
+ state: "HASNA_STATE_HOME",
6149
+ cache: "HASNA_CACHE_HOME"
6150
+ };
6151
+ var PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6152
+ function pathsResolverAssertApp2(app) {
6153
+ if (typeof app !== "string" || app.length === 0) {
6154
+ throw new TypeError("paths: app must be a non-empty string");
6155
+ }
6156
+ if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
6157
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
6158
+ }
6159
+ }
6160
+ function pathsResolverAssertKind2(kind) {
6161
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
6162
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
6163
+ }
6164
+ }
6165
+ function pathsResolverBaseDir2(kind, options) {
6166
+ pathsResolverAssertKind2(kind);
6167
+ const env = options.env ?? process.env;
6168
+ const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
6169
+ if (typeof override === "string" && override.length > 0)
6170
+ return override;
6171
+ const home = options.home ?? pathsResolverHomedir2();
6172
+ const platform = options.platform ?? process.platform;
6173
+ if (platform === "darwin") {
6174
+ switch (kind) {
6175
+ case "config":
6176
+ case "data":
6177
+ return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
6178
+ case "cache":
6179
+ return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
6180
+ case "state":
6181
+ return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
6182
+ }
6183
+ }
6184
+ switch (kind) {
6185
+ case "config":
6186
+ return pathsResolverJoin2(home, ".config", "hasna");
6187
+ case "data":
6188
+ return pathsResolverJoin2(home, ".local", "share", "hasna");
6189
+ case "state":
6190
+ return pathsResolverJoin2(home, ".local", "state", "hasna");
6191
+ case "cache":
6192
+ return pathsResolverJoin2(home, ".cache", "hasna");
6193
+ }
6194
+ }
6195
+ function pathsResolverResolve2(kind, options) {
6196
+ pathsResolverAssertApp2(options.app);
6197
+ const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
6198
+ return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
6199
+ }
6200
+ function stateDir(options) {
6201
+ return pathsResolverResolve2("state", options);
6202
+ }
5380
6203
  var SESSION_RENDER_STATE_APP = "instructions";
5381
- function homeDir2(env = process.env) {
5382
- return env["HOME"] || env["USERPROFILE"] || homedir4();
6204
+ function homeDir3(env = process.env) {
6205
+ return env["HOME"] || env["USERPROFILE"] || homedir3();
5383
6206
  }
5384
6207
  function legacySnapshotDir(targetHome) {
5385
6208
  return resolve3(join5(targetHome, ".hasna", "session-render-snapshots"));
@@ -5390,10 +6213,10 @@ function resolverSnapshotDir(env = process.env) {
5390
6213
  return stateDir({
5391
6214
  app: SESSION_RENDER_STATE_APP,
5392
6215
  env: { ...env, HASNA_STATE_HOME: undefined },
5393
- home: homeDir2(env)
6216
+ home: homeDir3(env)
5394
6217
  });
5395
6218
  }
5396
- return stateDir({ app: SESSION_RENDER_STATE_APP, env, home: homeDir2(env) });
6219
+ return stateDir({ app: SESSION_RENDER_STATE_APP, env, home: homeDir3(env) });
5397
6220
  }
5398
6221
  function adoptResolverSnapshotDir(resolved, env = process.env) {
5399
6222
  const override = env.HASNA_STATE_HOME;
@@ -5430,10 +6253,10 @@ var PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000;
5430
6253
  var PROJECT_CONTEXT_MAX_COMMANDS = 6;
5431
6254
  var PROJECT_CONTEXT_MAX_WARNINGS = 3;
5432
6255
  var PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md";
5433
- var PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/project-context-manifest.json";
5434
- var PROJECT_CONTEXT_CACHE_PATH = ".hasna/project-context-cache.json";
5435
- var PROJECT_CONTEXT_LOCK_PATH = ".hasna/project-context.lock";
5436
- var PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/project-context-snapshots";
6256
+ var PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/projects/project-context-manifest.json";
6257
+ var PROJECT_CONTEXT_CACHE_PATH = ".hasna/projects/project-context-cache.json";
6258
+ var PROJECT_CONTEXT_LOCK_PATH = ".hasna/projects/project-context.lock";
6259
+ var PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/projects/project-context-snapshots";
5437
6260
  var PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1";
5438
6261
  var PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context";
5439
6262
  var SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES = 8 * 1024 * 1024;
@@ -5483,7 +6306,7 @@ var safeOptionalDisplay = exports_external.string().min(1).max(512).refine(isSaf
5483
6306
  var isoTimestamp = exports_external.string().min(20).max(40).refine(isStrictIsoTimestamp, "must be a strict ISO timestamp with timezone");
5484
6307
  var revisionSchema = exports_external.string().min(1).max(512).refine((value) => revisionKey(value) !== null, "must be a monotonic rev-N or timestamp revision");
5485
6308
  var hashSchema = exports_external.string().regex(/^sha256:[a-f0-9]{64}$/);
5486
- var absolutePath = exports_external.string().min(1).max(4096).refine((value) => isAbsolute(value), "must be absolute").refine(isSafeSingleLine, "must be safe").nullable();
6309
+ var absolutePath = exports_external.string().min(1).max(4096).refine((value) => isAbsolute2(value), "must be absolute").refine(isSafeSingleLine, "must be safe").nullable();
5487
6310
  var commandArg = exports_external.string().min(1).max(1024).refine((value) => isSafeCommandArgument(value), "unsafe argv item");
5488
6311
  var financeText = exports_external.string().min(1).max(512).refine((value) => value === value.trim(), "must be normalized");
5489
6312
  var financeLegalEntity = exports_external.string().min(1).max(256).refine((value) => value === value.trim(), "must be normalized");
@@ -6561,7 +7384,7 @@ function sanitizeLegacyEnvironment(value) {
6561
7384
  }
6562
7385
  const result = {};
6563
7386
  for (const [key, item] of Object.entries(value)) {
6564
- if (!/^[A-Z][A-Z0-9_]{0,63}$/.test(key) || typeof item !== "string" || !isAbsolute(item) || !isSafeSingleLine(item) || item.length > 4096) {
7387
+ if (!/^[A-Z][A-Z0-9_]{0,63}$/.test(key) || typeof item !== "string" || !isAbsolute2(item) || !isSafeSingleLine(item) || item.length > 4096) {
6565
7388
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session environment metadata contains an unsafe entry");
6566
7389
  }
6567
7390
  if (scanSecrets(`${key}=${item}`, "text").length > 0) {
@@ -6636,7 +7459,7 @@ function sanitizeLegacyFiles(value) {
6636
7459
  return value.map((entry) => {
6637
7460
  const file = entry;
6638
7461
  const relativePath = safeLegacyMetadataString(file["relativePath"], "");
6639
- if (!relativePath || isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith("../") || relativePath.includes("/../")) {
7462
+ if (!relativePath || isAbsolute2(relativePath) || relativePath === ".." || relativePath.startsWith("../") || relativePath.includes("/../")) {
6640
7463
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session file inventory contains an unsafe relative path");
6641
7464
  }
6642
7465
  const sha = safeLegacyMetadataString(file["sha256"], "");
@@ -6893,7 +7716,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
6893
7716
  fd = anchoredOpenExclusive(directory, tempName, previousMode);
6894
7717
  writeFileSync(fd, content, { encoding: "utf8" });
6895
7718
  fsyncSync(fd);
6896
- closeSync(fd);
7719
+ closeSync2(fd);
6897
7720
  fd = null;
6898
7721
  beforeInstall?.(tempPath);
6899
7722
  assertManagedDirectoryStable(dir, workspaceRoot, directory.identity);
@@ -6988,14 +7811,14 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
6988
7811
  fsyncSync(directory.fd);
6989
7812
  } catch (error) {
6990
7813
  if (fd !== null)
6991
- closeSync(fd);
7814
+ closeSync2(fd);
6992
7815
  if (!preserveTemp)
6993
7816
  directory.ops.unlinkat(directory.fd, tempName);
6994
7817
  if (directoryChanged)
6995
7818
  fsyncSync(directory.fd);
6996
7819
  throw error;
6997
7820
  } finally {
6998
- closeSync(directory.fd);
7821
+ closeSync2(directory.fd);
6999
7822
  }
7000
7823
  }
7001
7824
  function atomicWritePortable(path, content, workspaceRoot, defaultMode, expectedHash, beforeInstall, maxObservedBytes, allowReplacement = false) {
@@ -7019,12 +7842,12 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
7019
7842
  let fd = null;
7020
7843
  let tempIdentity = null;
7021
7844
  try {
7022
- fd = openSync(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, defaultMode);
7023
- const opened = fstatSync(fd);
7845
+ fd = openSync2(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, defaultMode);
7846
+ const opened = fstatSync2(fd);
7024
7847
  tempIdentity = { dev: opened.dev, ino: opened.ino };
7025
7848
  writeFileSync(fd, content, { encoding: "utf8" });
7026
7849
  fsyncSync(fd);
7027
- closeSync(fd);
7850
+ closeSync2(fd);
7028
7851
  fd = null;
7029
7852
  beforeInstall?.(tempPath);
7030
7853
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -7049,7 +7872,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
7049
7872
  fsyncDirectory(dir);
7050
7873
  } catch (error) {
7051
7874
  if (fd !== null)
7052
- closeSync(fd);
7875
+ closeSync2(fd);
7053
7876
  if (tempIdentity && managedDirectoryMatches(dir, workspaceRoot, directoryIdentity)) {
7054
7877
  try {
7055
7878
  const current = lstatSync(tempPath);
@@ -7077,12 +7900,12 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
7077
7900
  let fd = null;
7078
7901
  let tempIdentity = null;
7079
7902
  try {
7080
- fd = openSync(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, current.mode & 511);
7081
- const opened = fstatSync(fd);
7903
+ fd = openSync2(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, current.mode & 511);
7904
+ const opened = fstatSync2(fd);
7082
7905
  tempIdentity = { dev: opened.dev, ino: opened.ino };
7083
7906
  writeFileSync(fd, content, { encoding: "utf8" });
7084
7907
  fsyncSync(fd);
7085
- closeSync(fd);
7908
+ closeSync2(fd);
7086
7909
  fd = null;
7087
7910
  beforeInstall?.(tempPath);
7088
7911
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -7100,7 +7923,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
7100
7923
  fsyncDirectory(dir);
7101
7924
  } catch (error) {
7102
7925
  if (fd !== null)
7103
- closeSync(fd);
7926
+ closeSync2(fd);
7104
7927
  if (tempIdentity && managedDirectoryMatches(dir, workspaceRoot, directoryIdentity)) {
7105
7928
  try {
7106
7929
  const candidate = lstatSync(tempPath);
@@ -7138,7 +7961,7 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
7138
7961
  if (maxObservedBytes !== null && stat.size > maxObservedBytes) {
7139
7962
  throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePosix(workspaceRoot, path)}`);
7140
7963
  }
7141
- return createHash2("sha256").update(readFileSync(path)).digest("hex");
7964
+ return createHash2("sha256").update(readFileSync2(path)).digest("hex");
7142
7965
  }
7143
7966
  function writeProjectContextCoordinatedFile(input) {
7144
7967
  atomicWriteFile(resolve4(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
@@ -7188,7 +8011,7 @@ function removeProjectContextCoordinatedFile(input) {
7188
8011
  }
7189
8012
  throw error;
7190
8013
  } finally {
7191
- closeSync(directory.fd);
8014
+ closeSync2(directory.fd);
7192
8015
  }
7193
8016
  }
7194
8017
  function restoreAnchoredDisplacedFile(directory, displacedName, targetName, expected) {
@@ -7269,19 +8092,19 @@ function openAnchoredDirectory(path, workspaceRoot, providedOps, maxObservedByte
7269
8092
  const identity = captureManagedDirectoryIdentity(path, workspaceRoot);
7270
8093
  let fd;
7271
8094
  try {
7272
- fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
8095
+ fd = openSync2(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
7273
8096
  } catch {
7274
8097
  throw new ProjectContextHashRace(`managed parent directory changed while opening: ${relativePosix(workspaceRoot, path)}`);
7275
8098
  }
7276
8099
  try {
7277
- const opened = fstatSync(fd);
8100
+ const opened = fstatSync2(fd);
7278
8101
  if (!opened.isDirectory() || opened.dev !== identity.dev || opened.ino !== identity.ino) {
7279
8102
  throw new ProjectContextHashRace(`managed parent directory changed while opening: ${relativePosix(workspaceRoot, path)}`);
7280
8103
  }
7281
8104
  assertManagedDirectoryStable(path, workspaceRoot, identity);
7282
8105
  return { fd, path, workspaceRoot, identity, ops, maxObservedBytes };
7283
8106
  } catch (error) {
7284
- closeSync(fd);
8107
+ closeSync2(fd);
7285
8108
  throw error;
7286
8109
  }
7287
8110
  }
@@ -7289,12 +8112,12 @@ function anchoredOpenExclusive(directory, name, mode) {
7289
8112
  const requestedMode = mode & 4095;
7290
8113
  let fd;
7291
8114
  try {
7292
- fd = openSync(join6(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
8115
+ fd = openSync2(join6(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
7293
8116
  } catch {
7294
8117
  throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
7295
8118
  }
7296
8119
  try {
7297
- const opened = fstatSync(fd);
8120
+ const opened = fstatSync2(fd);
7298
8121
  if (!opened.isFile()) {
7299
8122
  throw new ProjectContextHashRace("prepared managed output is not a regular file");
7300
8123
  }
@@ -7308,7 +8131,7 @@ function anchoredOpenExclusive(directory, name, mode) {
7308
8131
  }
7309
8132
  return fd;
7310
8133
  } catch (error) {
7311
- closeSync(fd);
8134
+ closeSync2(fd);
7312
8135
  throw error;
7313
8136
  }
7314
8137
  }
@@ -7329,7 +8152,7 @@ function anchoredFileObservation(directory, name) {
7329
8152
  if (fd < 0)
7330
8153
  return null;
7331
8154
  try {
7332
- const stat = fstatSync(fd);
8155
+ const stat = fstatSync2(fd);
7333
8156
  if (!stat.isFile())
7334
8157
  throw new ProjectContextHashRace("managed output is not a regular file");
7335
8158
  const relativePath = relativePosix(directory.workspaceRoot, join6(directory.path, name));
@@ -7340,11 +8163,11 @@ function anchoredFileObservation(directory, name) {
7340
8163
  return {
7341
8164
  dev: stat.dev,
7342
8165
  ino: stat.ino,
7343
- hash: createHash2("sha256").update(readFileSync(fd)).digest("hex"),
8166
+ hash: createHash2("sha256").update(readFileSync2(fd)).digest("hex"),
7344
8167
  mode: stat.mode & 511
7345
8168
  };
7346
8169
  } finally {
7347
- closeSync(fd);
8170
+ closeSync2(fd);
7348
8171
  }
7349
8172
  }
7350
8173
  function anchoredFileHash(directory, name) {
@@ -7516,8 +8339,8 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
7516
8339
  let linked = false;
7517
8340
  let preserveTemp = false;
7518
8341
  try {
7519
- fd = openSync(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
7520
- const opened = fstatSync(fd);
8342
+ fd = openSync2(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
8343
+ const opened = fstatSync2(fd);
7521
8344
  openedIdentity = { dev: opened.dev, ino: opened.ino };
7522
8345
  const content = `${JSON.stringify({
7523
8346
  schema: "hasna.instructions.project-context-lock/v1",
@@ -7565,7 +8388,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
7565
8388
  }
7566
8389
  if (fd !== null) {
7567
8390
  try {
7568
- closeSync(fd);
8391
+ closeSync2(fd);
7569
8392
  } catch {}
7570
8393
  }
7571
8394
  throw error;
@@ -7578,7 +8401,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
7578
8401
  const current = lstatSync(lockPath);
7579
8402
  if (current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino)
7580
8403
  return;
7581
- if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
8404
+ if (expectedHash !== undefined && sha2562(readFileSync2(lockPath, "utf8")) !== expectedHash)
7582
8405
  return;
7583
8406
  rmSync2(lockPath);
7584
8407
  fsyncDirectory(resolve4(lockPath, ".."));
@@ -7700,7 +8523,7 @@ function processStartIdentity(pid) {
7700
8523
  return null;
7701
8524
  if (process.platform === "linux") {
7702
8525
  try {
7703
- const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
8526
+ const stat = readFileSync2(`/proc/${pid}/stat`, "utf8");
7704
8527
  const close = stat.lastIndexOf(")");
7705
8528
  if (close < 0)
7706
8529
  return null;
@@ -7708,7 +8531,7 @@ function processStartIdentity(pid) {
7708
8531
  const startTicks = fields[19];
7709
8532
  if (!startTicks || !/^[0-9]+$/.test(startTicks))
7710
8533
  return null;
7711
- const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
8534
+ const bootId = readFileSync2("/proc/sys/kernel/random/boot_id", "utf8").trim();
7712
8535
  return /^[a-f0-9-]{36}$/i.test(bootId) ? `linux:${bootId}:${startTicks}` : null;
7713
8536
  } catch {
7714
8537
  return null;
@@ -7734,7 +8557,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
7734
8557
  removeOwnedLockByInode(lockPath, lock.identity, lock.contentHash);
7735
8558
  } finally {
7736
8559
  try {
7737
- closeSync(lock.fd);
8560
+ closeSync2(lock.fd);
7738
8561
  } catch {}
7739
8562
  }
7740
8563
  return;
@@ -7746,8 +8569,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
7746
8569
  let releaseHash = null;
7747
8570
  let exchanged = false;
7748
8571
  try {
7749
- releaseFd = openSync(releasePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
7750
- const opened = fstatSync(releaseFd);
8572
+ releaseFd = openSync2(releasePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
8573
+ const opened = fstatSync2(releaseFd);
7751
8574
  releaseIdentity = { dev: opened.dev, ino: opened.ino };
7752
8575
  const releaseContent = `${JSON.stringify({
7753
8576
  schema: "hasna.instructions.project-context-lock/v1",
@@ -7760,7 +8583,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
7760
8583
  releaseHash = sha2562(releaseContent);
7761
8584
  writeFileSync(releaseFd, releaseContent);
7762
8585
  fsyncSync(releaseFd);
7763
- closeSync(releaseFd);
8586
+ closeSync2(releaseFd);
7764
8587
  releaseFd = null;
7765
8588
  atomicExchangePaths(releasePath, lockPath);
7766
8589
  exchanged = true;
@@ -7791,7 +8614,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
7791
8614
  } finally {
7792
8615
  if (releaseFd !== null) {
7793
8616
  try {
7794
- closeSync(releaseFd);
8617
+ closeSync2(releaseFd);
7795
8618
  } catch {}
7796
8619
  }
7797
8620
  if (!exchanged && existsSync5(releasePath)) {
@@ -7800,21 +8623,21 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
7800
8623
  } catch {}
7801
8624
  }
7802
8625
  try {
7803
- closeSync(lock.fd);
8626
+ closeSync2(lock.fd);
7804
8627
  } catch {}
7805
8628
  }
7806
8629
  }
7807
8630
  function fsyncDirectory(path) {
7808
- const fd = openSync(path, constants.O_RDONLY);
8631
+ const fd = openSync2(path, constants.O_RDONLY);
7809
8632
  try {
7810
8633
  fsyncSync(fd);
7811
8634
  } finally {
7812
- closeSync(fd);
8635
+ closeSync2(fd);
7813
8636
  }
7814
8637
  }
7815
8638
  function ensureSafeDirectory(path, workspaceRoot, mode) {
7816
8639
  const rel = relative(workspaceRoot, path);
7817
- if (rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
8640
+ if (rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
7818
8641
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_ESCAPE", "managed directory escapes the workspace root");
7819
8642
  }
7820
8643
  const segments = rel.split(/[\\/]+/).filter(Boolean);
@@ -7946,7 +8769,7 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
7946
8769
  throw new ProjectContextError("PROJECT_CONTEXT_SHADOWED", ".codewith/CODEWITH.override.md shadows .codewith/CODEWITH.md");
7947
8770
  }
7948
8771
  function assertSafeWorkspaceRoot(path) {
7949
- if (!isAbsolute(path))
8772
+ if (!isAbsolute2(path))
7950
8773
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
7951
8774
  const normalized = resolve4(path);
7952
8775
  if (normalized === parse(normalized).root)
@@ -7960,7 +8783,7 @@ function assertSafeWorkspaceRoot(path) {
7960
8783
  }
7961
8784
  function assertNoSymlinkSegments(root, target) {
7962
8785
  const rel = relative(root, target);
7963
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
8786
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
7964
8787
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_ESCAPE", "managed path escapes workspace root");
7965
8788
  }
7966
8789
  let current = root;
@@ -7989,7 +8812,7 @@ function readUtf8RegularFile(path, workspaceRoot, maxBytes = FOREIGN_INPUT_MAX_B
7989
8812
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a regular file: ${path}`);
7990
8813
  if (stat.size > maxBytes)
7991
8814
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `managed input exceeds ${maxBytes} bytes`);
7992
- return readFileSync(path, "utf8");
8815
+ return readFileSync2(path, "utf8");
7993
8816
  }
7994
8817
  function currentFileHash(path, workspaceRoot) {
7995
8818
  if (!existsSync5(path))
@@ -8012,7 +8835,7 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
8012
8835
  function durableSourcePath(path, workspaceRoot) {
8013
8836
  if (!path || path.startsWith("/dev/fd/"))
8014
8837
  return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
8015
- const normalized = isAbsolute(path) ? resolve4(path) : resolve4(workspaceRoot, path);
8838
+ const normalized = isAbsolute2(path) ? resolve4(path) : resolve4(workspaceRoot, path);
8016
8839
  if (normalized.startsWith("/dev/fd/"))
8017
8840
  return resolve4(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
8018
8841
  return normalized;
@@ -8355,7 +9178,7 @@ function compareProviderVersions(left, right) {
8355
9178
 
8356
9179
  // src/lib/asset-plan.ts
8357
9180
  import { createHash as createHash3 } from "crypto";
8358
- import { isAbsolute as isAbsolute2, posix, resolve as resolve5 } from "path";
9181
+ import { isAbsolute as isAbsolute3, posix, resolve as resolve5 } from "path";
8359
9182
  var ASSET_PLAN_SCHEMA = "hasna.instructions.asset-plan/v1";
8360
9183
  var ASSET_CAPABILITY_SCHEMA = "hasna.instructions.asset-capability/v1";
8361
9184
  var ASSET_BUNDLE_SCHEMA = "hasna.instructions.asset-bundle/v1";
@@ -8617,7 +9440,7 @@ function resolveAssetDestination(item, roots) {
8617
9440
  const root = item.destination.root === "target-home" ? roots.targetHome : roots.projectRoot;
8618
9441
  if (!root)
8619
9442
  throw new Error(`Asset ${item.assetKey} requires an explicit project root.`);
8620
- if (!isAbsolute2(root))
9443
+ if (!isAbsolute3(root))
8621
9444
  throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
8622
9445
  const relativePath = safeRelativePath(item.destination.relativePath);
8623
9446
  const target = resolve5(root, ...relativePath.split("/"));
@@ -8742,8 +9565,8 @@ function deepFreeze(value) {
8742
9565
 
8743
9566
  // src/lib/cursor-authority.ts
8744
9567
  import { createHash as createHash4 } from "crypto";
8745
- import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
8746
- import { homedir as homedir5 } from "os";
9568
+ import { lstatSync as lstatSync2, readFileSync as readFileSync3 } from "fs";
9569
+ import { homedir as homedir4 } from "os";
8747
9570
  import { join as join7, resolve as resolve6 } from "path";
8748
9571
  var CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH = ".cursor/rules/hasna-global.mdc";
8749
9572
  var CURSOR_GLOBAL_AUTHORITY_MAX_BYTES = 256 * 1024;
@@ -8753,8 +9576,8 @@ var CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
8753
9576
  function sha2564(content) {
8754
9577
  return createHash4("sha256").update(content).digest("hex");
8755
9578
  }
8756
- function homeDir3() {
8757
- return process.env["HOME"] || homedir5();
9579
+ function homeDir4() {
9580
+ return process.env["HOME"] || homedir4();
8758
9581
  }
8759
9582
  function markerPayload(content, markerLine, markerIndex) {
8760
9583
  const index = markerIndex ?? content.indexOf(markerLine);
@@ -8770,12 +9593,12 @@ function baseObservation(path) {
8770
9593
  };
8771
9594
  }
8772
9595
  function observeCursorGlobalAuthority(options = {}) {
8773
- const authorityPath = resolve6(join7(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
8774
- const readFile = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
9596
+ const authorityPath = resolve6(join7(options.home ?? homeDir4(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
9597
+ const readFile = options.readFile ?? ((path) => readFileSync3(path, "utf8"));
8775
9598
  return observeCursorGlobalAuthorityPath(authorityPath, readFile);
8776
9599
  }
8777
9600
  function observeCursorGlobalAuthorityAtPath(authorityPath) {
8778
- return observeCursorGlobalAuthorityPath(resolve6(authorityPath), (path) => readFileSync2(path, "utf8"));
9601
+ return observeCursorGlobalAuthorityPath(resolve6(authorityPath), (path) => readFileSync3(path, "utf8"));
8779
9602
  }
8780
9603
  function observeCursorGlobalAuthorityPath(authorityPath, readFile) {
8781
9604
  const base = baseObservation(authorityPath);
@@ -8920,7 +9743,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile) {
8920
9743
  };
8921
9744
  }
8922
9745
  function isCursorGlobalAuthorityPath(path) {
8923
- return resolve6(path) === resolve6(join7(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
9746
+ return resolve6(path) === resolve6(join7(homeDir4(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
8924
9747
  }
8925
9748
  function stampCursorGlobalAuthorityMarker(content) {
8926
9749
  const existing = content.match(CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN);
@@ -8969,8 +9792,8 @@ function detectCursorAuthorityConflicts(observation = observeCursorGlobalAuthori
8969
9792
 
8970
9793
  // src/lib/session-authority.ts
8971
9794
  import { createHash as createHash5 } from "crypto";
8972
- import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
8973
- import { homedir as homedir6 } from "os";
9795
+ import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "fs";
9796
+ import { homedir as homedir5 } from "os";
8974
9797
  import { join as join8, resolve as resolve7 } from "path";
8975
9798
  var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md";
8976
9799
  var CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
@@ -8983,7 +9806,7 @@ function sha2565(content) {
8983
9806
  return createHash5("sha256").update(content).digest("hex");
8984
9807
  }
8985
9808
  function configHomeDir() {
8986
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
9809
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
8987
9810
  }
8988
9811
  function normalizeOwnedTargetPath(p) {
8989
9812
  const expanded = p.startsWith("~/") ? resolve7(configHomeDir(), p.slice(2)) : resolve7(p);
@@ -9036,7 +9859,7 @@ function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
9036
9859
  reason: `Claude target contains unmanaged AGENTS.md larger than ${CLAUDE_LEGACY_AUTHORITY_MAX_BYTES} bytes; authority cannot be classified safely.`
9037
9860
  }];
9038
9861
  }
9039
- const content = readFileSync3(authorityPath, "utf8");
9862
+ const content = readFileSync4(authorityPath, "utf8");
9040
9863
  const owned = ownedAuthorities.find((authority) => normalizeOwnedTargetPath(authority.targetPath) === normalizeOwnedTargetPath(authorityPath));
9041
9864
  if (owned) {
9042
9865
  if (owned.content === content)
@@ -9353,7 +10176,7 @@ function yamlQuote2(value) {
9353
10176
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
9354
10177
  }
9355
10178
  function defaultTargetHome(tool, profile, sessionId) {
9356
- const home = process.env["HOME"] || homedir7();
10179
+ const home = process.env["HOME"] || homedir6();
9357
10180
  return join9(home, ".hasna", "accounts", "profiles", tool, slug(profile));
9358
10181
  }
9359
10182
  function joinTarget(targetHome, relativePath) {
@@ -9929,7 +10752,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
9929
10752
  ...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
9930
10753
  ]);
9931
10754
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
9932
- const selectedConfig = existsSync7(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
10755
+ const selectedConfig = existsSync7(existingConfigPath) ? readOpenCodeConfig(readFileSync5(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
9933
10756
  const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
9934
10757
  const config = {
9935
10758
  ...selectedConfig,
@@ -10070,7 +10893,7 @@ function buildAssetFiles(input, targetHome, blocked) {
10070
10893
  ...input.projectRoot ? { projectRoot: resolveSessionPath(input.projectRoot) } : {}
10071
10894
  });
10072
10895
  const relativePath = relative2(targetHome, path).replaceAll("\\", "/");
10073
- if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
10896
+ if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute4(relativePath)) {
10074
10897
  throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
10075
10898
  }
10076
10899
  return {
@@ -10118,7 +10941,7 @@ function adapterFor(input) {
10118
10941
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
10119
10942
  }
10120
10943
  function getHomeDir() {
10121
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
10944
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
10122
10945
  }
10123
10946
  function cleanSessionPathInput(path) {
10124
10947
  const trimmed = path.trim();
@@ -10156,7 +10979,7 @@ function assertSafeRelativePath(relativePath) {
10156
10979
  return normalized;
10157
10980
  }
10158
10981
  function assertSafeTargetRoot(targetHome) {
10159
- if (!isAbsolute3(targetHome))
10982
+ if (!isAbsolute4(targetHome))
10160
10983
  throw new Error(`Session render target must be an absolute path: ${targetHome}`);
10161
10984
  const normalized = resolve8(targetHome);
10162
10985
  if (normalized === parse2(normalized).root) {
@@ -10756,10 +11579,10 @@ function layerFromIdentityKind(kind, exportShape) {
10756
11579
  function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
10757
11580
  if (sourcePaths.length === 0 || !exportPath)
10758
11581
  return;
10759
- const baseDir2 = dirname4(resolveSessionPath(exportPath));
11582
+ const baseDir = dirname4(resolveSessionPath(exportPath));
10760
11583
  const contents = [];
10761
11584
  for (const sourcePath of sourcePaths) {
10762
- const content = readIdentitySourcePath(sourcePath, baseDir2, sourceId);
11585
+ const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
10763
11586
  if (content !== undefined)
10764
11587
  contents.push({ path: sourcePath.path, content });
10765
11588
  }
@@ -10772,8 +11595,8 @@ ${item.content.trimEnd()}`).join(`
10772
11595
 
10773
11596
  `));
10774
11597
  }
10775
- function readIdentitySourcePath(sourcePath, baseDir2, sourceId) {
10776
- const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir2, sourceId);
11598
+ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
11599
+ const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
10777
11600
  if (!existsSync7(resolvedPath)) {
10778
11601
  if (sourcePath.required) {
10779
11602
  throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
@@ -10784,28 +11607,28 @@ function readIdentitySourcePath(sourcePath, baseDir2, sourceId) {
10784
11607
  if (!stat.isFile()) {
10785
11608
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
10786
11609
  }
10787
- const realBase = realpathSync2(baseDir2);
11610
+ const realBase = realpathSync2(baseDir);
10788
11611
  const realPath = realpathSync2(resolvedPath);
10789
11612
  if (!pathIsInside(realPath, realBase)) {
10790
11613
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
10791
11614
  }
10792
- return readFileSync4(realPath, "utf-8");
11615
+ return readFileSync5(realPath, "utf-8");
10793
11616
  }
10794
- function resolveIdentitySourcePath(path, baseDir2, sourceId) {
11617
+ function resolveIdentitySourcePath(path, baseDir, sourceId) {
10795
11618
  const cleaned = cleanSessionPathInput(path);
10796
11619
  if (!cleaned)
10797
11620
  throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
10798
11621
  if (cleaned.includes("\\"))
10799
11622
  throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
10800
- const resolvedPath = isAbsolute3(cleaned) ? resolve8(cleaned) : resolve8(baseDir2, cleaned);
10801
- if (!pathIsInside(resolvedPath, resolve8(baseDir2))) {
11623
+ const resolvedPath = isAbsolute4(cleaned) ? resolve8(cleaned) : resolve8(baseDir, cleaned);
11624
+ if (!pathIsInside(resolvedPath, resolve8(baseDir))) {
10802
11625
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
10803
11626
  }
10804
11627
  return resolvedPath;
10805
11628
  }
10806
- function pathIsInside(path, baseDir2) {
10807
- const rel = relative2(baseDir2, path);
10808
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
11629
+ function pathIsInside(path, baseDir) {
11630
+ const rel = relative2(baseDir, path);
11631
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
10809
11632
  }
10810
11633
  function providerTargetsTool(targets, tool) {
10811
11634
  return targets.map((target) => target.toLowerCase()).some((target) => target === tool || target === "all" || target === "generic");
@@ -11678,63 +12501,1354 @@ function listMachines(db) {
11678
12501
  return d.query("SELECT * FROM machines ORDER BY last_applied_at DESC NULLS LAST").all();
11679
12502
  }
11680
12503
 
11681
- // src/data/config-store.ts
11682
- class CloudHttpError extends Error {
11683
- status;
11684
- body;
11685
- constructor(status, message, body) {
11686
- super(message);
11687
- this.status = status;
11688
- this.body = body;
11689
- this.name = "CloudHttpError";
11690
- }
11691
- }
11692
- function parseBoundedPagePayload(value, label) {
11693
- const page = value;
11694
- const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
11695
- const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
11696
- 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)) {
11697
- throw new CloudHttpError(502, `${label} returned an invalid or truncated bounded-read envelope`, value);
11698
- }
12504
+ // ../contracts/dist/client/storage.js
12505
+ import { isIP as isIP2 } from "net";
12506
+ import { spawnSync as spawnSync2 } from "child_process";
12507
+ import { closeSync as closeSync3, fstatSync as fstatSync3, openSync as openSync3, readFileSync as readFileSync6 } from "fs";
12508
+ import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
12509
+ import { createRequire as createRequire2 } from "module";
12510
+ import { hostname as osHostname2 } from "os";
12511
+ import { isAbsolute as isAbsolute5, join as join10 } from "path";
12512
+ function envToken2(name) {
12513
+ return name.toUpperCase().replace(/-/g, "_");
12514
+ }
12515
+ function clientTransportEnvKeys2(name) {
12516
+ const envSegment = envToken2(name);
11699
12517
  return {
11700
- ...page,
11701
- source_bounded: page.source_bounded ?? true
12518
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
12519
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
11702
12520
  };
11703
12521
  }
11704
- function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
11705
- if (value && typeof value === "object") {
11706
- const candidate = value;
11707
- if ("items" in candidate || "total" in candidate || "complete" in candidate || "truncated" in candidate || "next_cursor" in candidate) {
11708
- return parseBoundedPagePayload(value, label);
11709
- }
11710
- }
11711
- if (!Array.isArray(legacyItems)) {
11712
- throw new CloudHttpError(502, `${label} returned neither a bounded envelope nor a complete legacy array`, value);
11713
- }
11714
- const normalized = normalizeBoundedReadOptions(options);
11715
- const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
11716
- return { ...page, source_bounded: false };
12522
+ function credentialOverrideEnvKey2(name) {
12523
+ return `HASNA_${envToken2(name)}_API_KEY_OVERRIDE`;
11717
12524
  }
11718
- var API_URL_ENV = "HASNA_INSTRUCTIONS_API_URL";
11719
- var API_KEY_ENV = "HASNA_INSTRUCTIONS_API_KEY";
11720
- function resolveCloudConfig(env = process.env) {
11721
- assertNoLegacyStorageMode(env);
11722
- const apiUrl = env[API_URL_ENV]?.trim();
11723
- const apiKey = env[API_KEY_ENV]?.trim();
11724
- if (!apiUrl && !apiKey)
11725
- return null;
11726
- if (!apiUrl || !apiKey) {
11727
- 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.`);
11728
- }
11729
- return { apiUrl, apiKey };
12525
+ var CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE";
12526
+ function credentialPointerEnvKey2(name) {
12527
+ return `HASNA_${envToken2(name)}_API_KEY_REF`;
11730
12528
  }
11731
- function isApiTransport(env = process.env) {
11732
- return resolveCloudConfig(env) !== null;
12529
+
12530
+ class CredentialResolutionError2 extends Error {
12531
+ appName;
12532
+ attempted;
12533
+ constructor(appName, message, attempted) {
12534
+ super(message);
12535
+ this.name = "CredentialResolutionError";
12536
+ this.appName = appName;
12537
+ this.attempted = attempted;
12538
+ }
11733
12539
  }
11734
12540
 
11735
- class LocalConfigStore {
11736
- db;
12541
+ class CredentialFileUnsafeError2 extends Error {
12542
+ path;
12543
+ constructor(path, reason) {
12544
+ super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
12545
+ this.name = "CredentialFileUnsafeError";
12546
+ this.path = path;
12547
+ }
12548
+ }
12549
+ var HASNA_HOME_ENV_KEY2 = "HASNA_HOME";
12550
+ var HASNA_CONFIG_HOME_ENV_KEY2 = "HASNA_CONFIG_HOME";
12551
+ var KEYCHAIN_STATION_ENV_KEY2 = "HASNA_STATION";
12552
+ var HASNA_HOME_DIR2 = ".hasna";
12553
+ var CONFIG_SUBDIR2 = "config";
12554
+ var CREDENTIALS_FILE2 = "credentials";
12555
+ var KEYCHAIN_SECURITY_BIN2 = "/usr/bin/security";
12556
+ var KEYCHAIN_SERVICE_PREFIX2 = "hasna.credentials";
12557
+ var KEYCHAIN_ITEM_NOT_FOUND_STATUS2 = 44;
12558
+ var KEYCHAIN_SPAWN_TIMEOUT_MS2 = 1e4;
12559
+ var MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
12560
+ var SAFE_APP_SLUG2 = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
12561
+ var SAFE_PROFILE2 = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
12562
+ var ILLEGAL_IN_HEADER_VALUE2 = /[^\t\x20-\x7e]/;
12563
+ var VAULT_POINTER_SHAPE2 = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
12564
+ function homeDir5(env) {
12565
+ const home = env.HOME?.trim();
12566
+ return home ? home : null;
12567
+ }
12568
+ function absoluteOverride2(env, key) {
12569
+ const value = env[key]?.trim();
12570
+ return value && isAbsolute5(value) ? value : null;
12571
+ }
12572
+ function hasnaHomeDir2(env) {
12573
+ const override = absoluteOverride2(env, HASNA_HOME_ENV_KEY2);
12574
+ if (override)
12575
+ return override;
12576
+ const home = homeDir5(env);
12577
+ return home ? join10(home, HASNA_HOME_DIR2) : null;
12578
+ }
12579
+ function appConfigDir2(name, env) {
12580
+ const configRoot = absoluteOverride2(env, HASNA_CONFIG_HOME_ENV_KEY2);
12581
+ if (configRoot)
12582
+ return join10(configRoot, name);
12583
+ const root = hasnaHomeDir2(env);
12584
+ return root ? join10(root, name, CONFIG_SUBDIR2) : null;
12585
+ }
12586
+ function credentialDiskSourceList2(name, env, profile = null) {
12587
+ if (!SAFE_APP_SLUG2.test(name))
12588
+ return [];
12589
+ const directory = appConfigDir2(name, env);
12590
+ if (!directory)
12591
+ return [];
12592
+ const file = profile ? `${CREDENTIALS_FILE2}-${profile}` : CREDENTIALS_FILE2;
12593
+ return [{ path: join10(directory, file), tier: "disk" }];
12594
+ }
12595
+ function credentialDiskSources2(name, env) {
12596
+ return credentialDiskSourceList2(name, env, null).map((s) => s.path);
12597
+ }
12598
+ function profileDiskSources2(name, env, profile) {
12599
+ return credentialDiskSourceList2(name, env, profile).map((s) => s.path);
12600
+ }
12601
+ function parseEnvFile2(text) {
12602
+ const values = new Map;
12603
+ const unusable = new Set;
12604
+ for (const rawLine of text.split(/\r?\n/)) {
12605
+ const line = rawLine.trim();
12606
+ if (line.length === 0 || line.startsWith("#"))
12607
+ continue;
12608
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
12609
+ const equals = withoutExport.indexOf("=");
12610
+ if (equals <= 0)
12611
+ continue;
12612
+ const key = withoutExport.slice(0, equals).trim();
12613
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
12614
+ continue;
12615
+ let value = withoutExport.slice(equals + 1).trim();
12616
+ const quote = value[0];
12617
+ if (quote === '"' || quote === "'") {
12618
+ if (value.length < 2 || !value.endsWith(quote)) {
12619
+ unusable.add(key);
12620
+ continue;
12621
+ }
12622
+ value = value.slice(1, -1);
12623
+ }
12624
+ if (value.trim().length === 0) {
12625
+ unusable.add(key);
12626
+ continue;
12627
+ }
12628
+ if (values.has(key) && values.get(key) !== value)
12629
+ unusable.add(key);
12630
+ values.set(key, value);
12631
+ }
12632
+ return { values, unusable };
12633
+ }
12634
+ function configFileModeAllowed2(mode) {
12635
+ const permissions = mode & 4095;
12636
+ return permissions === 256 || permissions === 384;
12637
+ }
12638
+ function configFileReadsCoherent2(before, after) {
12639
+ return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
12640
+ }
12641
+ function readAppConfigFile2(path) {
12642
+ const unsafe = (reason) => {
12643
+ throw new CredentialFileUnsafeError2(path, reason);
12644
+ };
12645
+ let fd = -1;
12646
+ try {
12647
+ fd = openSync3(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
12648
+ } catch (error) {
12649
+ const code = error.code;
12650
+ if (code === "ENOENT" || code === "ENOTDIR")
12651
+ return null;
12652
+ if (code === "ELOOP")
12653
+ unsafe("the path is a symlink");
12654
+ unsafe(`the path could not be opened (${code ?? "unknown error"})`);
12655
+ }
12656
+ try {
12657
+ const before = fstatSync3(fd);
12658
+ if (!before.isFile())
12659
+ unsafe("the path is not a regular file");
12660
+ if (!configFileModeAllowed2(before.mode)) {
12661
+ unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
12662
+ }
12663
+ const uid = process.getuid?.() ?? process.geteuid?.();
12664
+ if (uid !== undefined && before.uid !== uid)
12665
+ unsafe("the file is not owned by the current user");
12666
+ if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
12667
+ unsafe("the file exceeds the size limit");
12668
+ const bytes = readFileSync6(fd);
12669
+ const after = fstatSync3(fd);
12670
+ if (!configFileReadsCoherent2(before, after)) {
12671
+ unsafe("the file changed while being read");
12672
+ }
12673
+ return parseEnvFile2(bytes.toString("utf8"));
12674
+ } finally {
12675
+ if (fd !== -1)
12676
+ closeSync3(fd);
12677
+ }
12678
+ }
12679
+ function readCredentialFile2(path, apiKeyKeys) {
12680
+ const parsed = readAppConfigFile2(path);
12681
+ if (!parsed)
12682
+ return null;
12683
+ for (const key of apiKeyKeys) {
12684
+ if (parsed.unusable.has(key)) {
12685
+ throw new CredentialFileUnsafeError2(path, `${key} is declared but blank or malformed`);
12686
+ }
12687
+ }
12688
+ const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
12689
+ if (new Set(values).size > 1) {
12690
+ throw new CredentialFileUnsafeError2(path, "credential aliases disagree");
12691
+ }
12692
+ return values[0] ?? null;
12693
+ }
12694
+ var CREDENTIAL_SHAPED_KEY2 = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
12695
+ function appConfigDiskValue2(name, env, keys) {
12696
+ const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY2.test(key));
12697
+ if (wanted.length === 0)
12698
+ return null;
12699
+ for (const path of credentialDiskSources2(name, env)) {
12700
+ const parsed = readAppConfigFile2(path);
12701
+ if (!parsed)
12702
+ continue;
12703
+ if (wanted.some((key) => parsed.unusable.has(key))) {
12704
+ return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
12705
+ }
12706
+ const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
12707
+ if (new Set(values).size > 1)
12708
+ throw new CredentialFileUnsafeError2(path, "configuration aliases disagree");
12709
+ for (const key of wanted) {
12710
+ if (parsed.unusable.has(key))
12711
+ return { key, value: "", path, unusable: true };
12712
+ const value = parsed.values.get(key)?.trim();
12713
+ if (value)
12714
+ return { key, value, path };
12715
+ }
12716
+ }
12717
+ return null;
12718
+ }
12719
+ function assertUsableCredential2(appName, source, value) {
12720
+ if (VAULT_POINTER_SHAPE2.test(value)) {
12721
+ throw new CredentialResolutionError2(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]);
12722
+ }
12723
+ if (!ILLEGAL_IN_HEADER_VALUE2.test(value))
12724
+ return;
12725
+ throw new CredentialResolutionError2(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]);
12726
+ }
12727
+ var INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
12728
+ var CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
12729
+ var CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider";
12730
+ function sealCredential2(fields) {
12731
+ const { apiKey } = fields;
12732
+ const visible = {
12733
+ tier: fields.tier,
12734
+ source: fields.source,
12735
+ deliberate: fields.deliberate,
12736
+ diskCandidates: Object.freeze([...fields.diskCandidates]),
12737
+ warning: fields.warning
12738
+ };
12739
+ const sealed = { ...visible };
12740
+ Object.defineProperty(sealed, "apiKey", {
12741
+ value: apiKey,
12742
+ enumerable: false,
12743
+ writable: false,
12744
+ configurable: false
12745
+ });
12746
+ if (fields.pointerVaultKey !== undefined) {
12747
+ Object.defineProperty(sealed, "pointerVaultKey", {
12748
+ value: fields.pointerVaultKey,
12749
+ enumerable: false,
12750
+ writable: false,
12751
+ configurable: false
12752
+ });
12753
+ }
12754
+ Object.defineProperty(sealed, INSPECT_CUSTOM2, {
12755
+ value: () => ({ ...visible, apiKey: "[redacted]" }),
12756
+ enumerable: false,
12757
+ writable: false,
12758
+ configurable: false
12759
+ });
12760
+ Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
12761
+ value: true,
12762
+ enumerable: false,
12763
+ writable: false,
12764
+ configurable: false
12765
+ });
12766
+ return Object.freeze(sealed);
12767
+ }
12768
+ function isSealedCredential(credential) {
12769
+ return credential[CREDENTIAL_SEAL2] === true;
12770
+ }
12771
+ function explicitCredential(appName, apiKey) {
12772
+ const source = "explicit apiKey option";
12773
+ assertUsableCredential2(appName, source, apiKey);
12774
+ return sealCredential2({
12775
+ apiKey,
12776
+ tier: "argument",
12777
+ source,
12778
+ deliberate: true,
12779
+ diskCandidates: [],
12780
+ warning: null
12781
+ });
12782
+ }
12783
+ function validateAndSealResolvedCredential(appName, credential) {
12784
+ const apiKey = credential.apiKey;
12785
+ assertUsableCredential2(appName, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE, apiKey);
12786
+ if (!isSealedCredential(credential)) {
12787
+ return sealCredential2({
12788
+ apiKey,
12789
+ tier: "argument",
12790
+ source: CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE,
12791
+ deliberate: true,
12792
+ diskCandidates: [],
12793
+ warning: null
12794
+ });
12795
+ }
12796
+ return sealCredential2({
12797
+ apiKey,
12798
+ tier: credential.tier,
12799
+ source: credential.source,
12800
+ deliberate: credential.deliberate,
12801
+ diskCandidates: credential.diskCandidates,
12802
+ warning: credential.warning,
12803
+ ...credential.pointerVaultKey !== undefined ? { pointerVaultKey: credential.pointerVaultKey } : {}
12804
+ });
12805
+ }
12806
+ function firstEnvValue2(env, keys) {
12807
+ for (const key of keys) {
12808
+ if (!Object.prototype.hasOwnProperty.call(env, key))
12809
+ continue;
12810
+ const value = env[key]?.trim();
12811
+ if (value)
12812
+ return { key, value };
12813
+ }
12814
+ return null;
12815
+ }
12816
+ var AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
12817
+ function isAmbientEnvironment2(env) {
12818
+ return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
12819
+ }
12820
+ function defaultKeychainRunner2(argv) {
12821
+ const result = spawnSync2(KEYCHAIN_SECURITY_BIN2, [...argv], {
12822
+ encoding: "utf8",
12823
+ stdio: ["ignore", "pipe", "pipe"],
12824
+ timeout: KEYCHAIN_SPAWN_TIMEOUT_MS2
12825
+ });
12826
+ return {
12827
+ status: result.status,
12828
+ stdout: result.stdout ?? "",
12829
+ stderr: result.error ? result.error.message : result.stderr ?? ""
12830
+ };
12831
+ }
12832
+ function keychainTierEnabled2(env, options) {
12833
+ if ((options.platform ?? process.platform) !== "darwin")
12834
+ return false;
12835
+ if (options.enabled !== undefined)
12836
+ return options.enabled;
12837
+ return options.run !== undefined || isAmbientEnvironment2(env);
12838
+ }
12839
+ function keychainAccount2(env, options) {
12840
+ const station = env[KEYCHAIN_STATION_ENV_KEY2]?.trim();
12841
+ if (station)
12842
+ return station;
12843
+ const host = (options.hostname ?? osHostname2)().split(".")[0]?.trim() ?? "";
12844
+ if (host)
12845
+ return host;
12846
+ const user = env.USER?.trim();
12847
+ return user || null;
12848
+ }
12849
+ function keychainFailureHint2(text) {
12850
+ const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
12851
+ const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
12852
+ return clean ? `: ${clean}` : "";
12853
+ }
12854
+ function readKeychainItem2(name, env, kind, options) {
12855
+ if (!SAFE_APP_SLUG2.test(name) || !keychainTierEnabled2(env, options))
12856
+ return null;
12857
+ const account = keychainAccount2(env, options);
12858
+ if (!account)
12859
+ return null;
12860
+ const service = `${KEYCHAIN_SERVICE_PREFIX2}.${name}.${kind}`;
12861
+ const source = `keychain:${service}@${account}`;
12862
+ const run = options.run ?? defaultKeychainRunner2;
12863
+ let result;
12864
+ try {
12865
+ result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
12866
+ } catch (error) {
12867
+ const reason = keychainFailureHint2(error instanceof Error ? error.message : String(error));
12868
+ throw new CredentialResolutionError2(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]);
12869
+ }
12870
+ if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS2)
12871
+ return null;
12872
+ if (result.status !== 0) {
12873
+ throw new CredentialResolutionError2(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint2(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]);
12874
+ }
12875
+ const value = result.stdout.trim();
12876
+ if (!value) {
12877
+ throw new CredentialResolutionError2(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]);
12878
+ }
12879
+ return { value, source };
12880
+ }
12881
+ function keychainConfigValue2(name, env, options = {}) {
12882
+ return readKeychainItem2(name, env, "api-url", options);
12883
+ }
12884
+ function snapshotClientEnvironment2(name, env) {
12885
+ const keys = clientTransportEnvKeys2(name);
12886
+ const ambient = isAmbientEnvironment2(env);
12887
+ const snapshot = Object.create(null);
12888
+ for (const key of [
12889
+ ...keys.apiUrlKeys,
12890
+ ...keys.apiKeyKeys,
12891
+ credentialOverrideEnvKey2(name),
12892
+ credentialPointerEnvKey2(name),
12893
+ CREDENTIAL_PROFILE_ENV_KEY2,
12894
+ "HOME",
12895
+ HASNA_HOME_ENV_KEY2,
12896
+ HASNA_CONFIG_HOME_ENV_KEY2,
12897
+ KEYCHAIN_STATION_ENV_KEY2,
12898
+ "USER"
12899
+ ]) {
12900
+ const descriptor = Object.getOwnPropertyDescriptor(env, key);
12901
+ if (!descriptor)
12902
+ continue;
12903
+ if (!("value" in descriptor)) {
12904
+ throw new CredentialResolutionError2(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
12905
+ }
12906
+ if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
12907
+ throw new CredentialResolutionError2(name, `${key} must be a string data property.`, [key]);
12908
+ }
12909
+ snapshot[key] = descriptor.value;
12910
+ }
12911
+ if (ambient) {
12912
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT2, {
12913
+ value: true,
12914
+ enumerable: false,
12915
+ writable: false,
12916
+ configurable: false
12917
+ });
12918
+ }
12919
+ return Object.freeze(snapshot);
12920
+ }
12921
+ function resolveCredential2(name, env, options = {}) {
12922
+ env = snapshotClientEnvironment2(name, env);
12923
+ const { apiKeyKeys } = clientTransportEnvKeys2(name);
12924
+ const diskPaths = credentialDiskSources2(name, env);
12925
+ if (options.apiKey !== undefined) {
12926
+ const explicitKey = options.apiKey.trim();
12927
+ if (!explicitKey) {
12928
+ throw new CredentialResolutionError2(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
12929
+ }
12930
+ assertUsableCredential2(name, "the explicit apiKey argument", explicitKey);
12931
+ return sealCredential2({
12932
+ apiKey: explicitKey,
12933
+ tier: "argument",
12934
+ source: "explicit apiKey argument",
12935
+ deliberate: true,
12936
+ diskCandidates: diskPaths,
12937
+ warning: null
12938
+ });
12939
+ }
12940
+ const overrideKeyName = credentialOverrideEnvKey2(name);
12941
+ const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
12942
+ if (overrideRaw !== undefined) {
12943
+ const override = overrideRaw.trim();
12944
+ if (!override) {
12945
+ throw new CredentialResolutionError2(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]);
12946
+ }
12947
+ assertUsableCredential2(name, overrideKeyName, override);
12948
+ return sealCredential2({
12949
+ apiKey: override,
12950
+ tier: "override",
12951
+ source: overrideKeyName,
12952
+ deliberate: true,
12953
+ diskCandidates: diskPaths,
12954
+ warning: null
12955
+ });
12956
+ }
12957
+ const pointerKeyName = credentialPointerEnvKey2(name);
12958
+ const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
12959
+ if (pointerRaw !== undefined) {
12960
+ const pointer = pointerRaw.trim();
12961
+ if (!pointer) {
12962
+ throw new CredentialResolutionError2(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]);
12963
+ }
12964
+ if (!VAULT_POINTER_SHAPE2.test(pointer)) {
12965
+ throw new CredentialResolutionError2(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]);
12966
+ }
12967
+ return sealCredential2({
12968
+ apiKey: "",
12969
+ pointerVaultKey: pointer,
12970
+ tier: "pointer",
12971
+ source: pointerKeyName,
12972
+ deliberate: true,
12973
+ diskCandidates: diskPaths,
12974
+ warning: null
12975
+ });
12976
+ }
12977
+ if (options.profile !== undefined && !options.profile.trim()) {
12978
+ throw new CredentialResolutionError2(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
12979
+ }
12980
+ const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY2) ? env[CREDENTIAL_PROFILE_ENV_KEY2] : undefined;
12981
+ if (profileRaw !== undefined && !profileRaw.trim()) {
12982
+ throw new CredentialResolutionError2(name, `${CREDENTIAL_PROFILE_ENV_KEY2} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY2]);
12983
+ }
12984
+ const profile = options.profile?.trim() || profileRaw?.trim();
12985
+ if (profile) {
12986
+ const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY2;
12987
+ if (!SAFE_PROFILE2.test(profile)) {
12988
+ throw new CredentialResolutionError2(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
12989
+ }
12990
+ const paths = profileDiskSources2(name, env, profile);
12991
+ for (const path of paths) {
12992
+ const value = readCredentialFile2(path, apiKeyKeys);
12993
+ if (value) {
12994
+ assertUsableCredential2(name, path, value);
12995
+ return sealCredential2({
12996
+ apiKey: value,
12997
+ tier: "profile",
12998
+ source: path,
12999
+ deliberate: true,
13000
+ diskCandidates: paths,
13001
+ warning: null
13002
+ });
13003
+ }
13004
+ }
13005
+ throw new CredentialResolutionError2(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);
13006
+ }
13007
+ const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
13008
+ const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
13009
+ if (blankEnv) {
13010
+ throw new CredentialResolutionError2(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
13011
+ }
13012
+ if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
13013
+ throw new CredentialResolutionError2(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));
13014
+ }
13015
+ const envHit = firstEnvValue2(env, apiKeyKeys);
13016
+ const keychainHit = readKeychainItem2(name, env, "api-key", options.keychain ?? {});
13017
+ if (keychainHit) {
13018
+ assertUsableCredential2(name, keychainHit.source, keychainHit.value);
13019
+ 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;
13020
+ return sealCredential2({
13021
+ apiKey: keychainHit.value,
13022
+ tier: "keychain",
13023
+ source: keychainHit.source,
13024
+ deliberate: false,
13025
+ diskCandidates: diskPaths,
13026
+ warning
13027
+ });
13028
+ }
13029
+ const diskSourceList = credentialDiskSourceList2(name, env, null);
13030
+ const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile2(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
13031
+ if (diskHits.length > 0) {
13032
+ const winner = diskHits[0];
13033
+ assertUsableCredential2(name, winner.src.path, winner.value);
13034
+ const divergentSources = [
13035
+ ...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
13036
+ ...envHit && envHit.value !== winner.value ? [envHit.key] : []
13037
+ ];
13038
+ 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;
13039
+ return sealCredential2({
13040
+ apiKey: winner.value,
13041
+ tier: winner.src.tier,
13042
+ source: winner.src.path,
13043
+ deliberate: false,
13044
+ diskCandidates: diskPaths,
13045
+ warning
13046
+ });
13047
+ }
13048
+ if (envHit) {
13049
+ assertUsableCredential2(name, envHit.key, envHit.value);
13050
+ return sealCredential2({
13051
+ apiKey: envHit.value,
13052
+ tier: "env",
13053
+ source: envHit.key,
13054
+ deliberate: false,
13055
+ diskCandidates: diskPaths,
13056
+ warning: null
13057
+ });
13058
+ }
13059
+ return null;
13060
+ }
13061
+ var SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
13062
+ var requireSecretsSdk2 = createRequire2(import.meta.url);
13063
+ async function completePointerCredential(name, pointerResolution, env = process.env) {
13064
+ const vaultKey = pointerResolution.pointerVaultKey;
13065
+ const pointerEnvKey = pointerResolution.source;
13066
+ if (!vaultKey) {
13067
+ throw new CredentialResolutionError2(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
13068
+ }
13069
+ let secretsSdk;
13070
+ try {
13071
+ secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER2);
13072
+ } catch {
13073
+ throw new CredentialResolutionError2(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]);
13074
+ }
13075
+ let client;
13076
+ try {
13077
+ client = secretsSdk.createSecretsClientFromEnv(env);
13078
+ } catch {
13079
+ throw new CredentialResolutionError2(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]);
13080
+ }
13081
+ let secret;
13082
+ try {
13083
+ secret = await client.getSecret({ key: vaultKey });
13084
+ } catch {
13085
+ throw new CredentialResolutionError2(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]);
13086
+ }
13087
+ const value = secret.value;
13088
+ if (!value) {
13089
+ throw new CredentialResolutionError2(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
13090
+ }
13091
+ assertUsableCredential2(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
13092
+ return sealCredential2({
13093
+ apiKey: value,
13094
+ tier: "pointer",
13095
+ source: `${pointerEnvKey} -> vault:${vaultKey}`,
13096
+ deliberate: true,
13097
+ diskCandidates: pointerResolution.diskCandidates,
13098
+ warning: null
13099
+ });
13100
+ }
13101
+ var DEFAULT_FLEET_GATEWAY_ORIGIN2 = "https://api.hasna.com";
13102
+ var DEFAULT_AUTHORITY_SOURCE2 = "default";
13103
+ function defaultFleetGatewayBaseUrl2(name) {
13104
+ return `${DEFAULT_FLEET_GATEWAY_ORIGIN2}/${validateAppSlug2(name)}`;
13105
+ }
13106
+ var ASCII_CONTROL_PATTERN2 = /[\u0000-\u001f\u007f]/;
13107
+ var DNS_LABEL_PATTERN2 = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
13108
+ function isValidDnsDomain2(value) {
13109
+ if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN2.test(value) || /[^\x00-\x7f]/.test(value)) {
13110
+ return false;
13111
+ }
13112
+ return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN2.test(label));
13113
+ }
13114
+ function validateAppSlug2(name) {
13115
+ if (name.length > 63 || !DNS_LABEL_PATTERN2.test(name)) {
13116
+ throw new Error("App name must be one lowercase DNS label.");
13117
+ }
13118
+ return name;
13119
+ }
13120
+ function rawAuthority2(value) {
13121
+ const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
13122
+ if (!match)
13123
+ throw new Error("API URL must be absolute.");
13124
+ const afterScheme = value.slice(match[0].length);
13125
+ const boundary = afterScheme.search(/[/?#]/);
13126
+ const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
13127
+ if (!authority)
13128
+ throw new Error("API URL must include a hostname.");
13129
+ return authority;
13130
+ }
13131
+ function assertCanonicalPort2(port) {
13132
+ if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
13133
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
13134
+ }
13135
+ const numericPort = Number(port);
13136
+ if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
13137
+ throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
13138
+ }
13139
+ }
13140
+ function canonicalAuthorityHostname2(authority) {
13141
+ let rawHostname;
13142
+ if (authority.startsWith("[")) {
13143
+ const closingBracket = authority.indexOf("]");
13144
+ if (closingBracket === -1) {
13145
+ throw new Error("API URL authority must contain a canonical hostname.");
13146
+ }
13147
+ rawHostname = authority.slice(0, closingBracket + 1);
13148
+ const portSuffix = authority.slice(closingBracket + 1);
13149
+ if (portSuffix) {
13150
+ if (!portSuffix.startsWith(":")) {
13151
+ throw new Error("API URL authority must contain a canonical hostname and port.");
13152
+ }
13153
+ assertCanonicalPort2(portSuffix.slice(1));
13154
+ }
13155
+ if (isIP2(rawHostname.slice(1, -1)) !== 6) {
13156
+ throw new Error("API URL authority must contain a canonical IPv6 literal.");
13157
+ }
13158
+ } else {
13159
+ const firstColon = authority.indexOf(":");
13160
+ const lastColon = authority.lastIndexOf(":");
13161
+ if (firstColon !== lastColon) {
13162
+ throw new Error("IPv6 API URL authorities must use brackets.");
13163
+ }
13164
+ if (lastColon !== -1) {
13165
+ const port = authority.slice(lastColon + 1);
13166
+ assertCanonicalPort2(port);
13167
+ rawHostname = authority.slice(0, lastColon);
13168
+ } else {
13169
+ rawHostname = authority;
13170
+ }
13171
+ const ipVersion = isIP2(rawHostname);
13172
+ const numericAddressParts = rawHostname.split(".");
13173
+ const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
13174
+ if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain2(rawHostname.toLowerCase())) {
13175
+ throw new Error("API URL authority must contain a canonical ASCII hostname.");
13176
+ }
13177
+ }
13178
+ return rawHostname.toLowerCase();
13179
+ }
13180
+ function isDeliberateLoopbackHttpAuthority2(authority) {
13181
+ return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
13182
+ }
13183
+ function toV1BaseUrl2(apiUrl) {
13184
+ if (ASCII_CONTROL_PATTERN2.test(apiUrl)) {
13185
+ throw new Error("API URL must not contain ASCII control characters.");
13186
+ }
13187
+ const input = apiUrl.trim();
13188
+ const authority = rawAuthority2(input);
13189
+ if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
13190
+ throw new Error("API URL authority must be canonical ASCII without credentials.");
13191
+ }
13192
+ const canonicalHostname = canonicalAuthorityHostname2(authority);
13193
+ const url = new URL(input);
13194
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
13195
+ throw new Error("API URL must use http or https.");
13196
+ }
13197
+ if (url.username || url.password) {
13198
+ throw new Error("API URL must not include credentials.");
13199
+ }
13200
+ if (!url.hostname || url.hostname.endsWith(".")) {
13201
+ throw new Error("API URL must include a canonical hostname.");
13202
+ }
13203
+ if (url.hostname.toLowerCase() !== canonicalHostname) {
13204
+ throw new Error("API URL authority must not rely on parser hostname normalization.");
13205
+ }
13206
+ if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
13207
+ throw new Error("API URL must not use IDN or punycode hostnames.");
13208
+ }
13209
+ if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority2(authority)) {
13210
+ throw new Error("API URL may use http only for an exact loopback authority.");
13211
+ }
13212
+ if (url.search || url.hash) {
13213
+ throw new Error("API URL must not include a query string or fragment.");
13214
+ }
13215
+ let path = url.pathname.replace(/\/+$/, "");
13216
+ if (path.endsWith("/v1"))
13217
+ path = path.slice(0, -"/v1".length);
13218
+ url.pathname = `${path}/v1`;
13219
+ return url.toString().replace(/\/+$/, "");
13220
+ }
13221
+ class ClientTransportConfigurationError2 extends Error {
13222
+ appName;
13223
+ sources;
13224
+ constructor(appName, message, sources = []) {
13225
+ super(message);
13226
+ this.name = "ClientTransportConfigurationError";
13227
+ this.appName = appName;
13228
+ this.sources = Object.freeze([...sources]);
13229
+ }
13230
+ }
13231
+ function resolveClientTransportSnapshot2(name, env = process.env, options = {}) {
13232
+ env = snapshotClientEnvironment2(name, env);
13233
+ const keys = clientTransportEnvKeys2(name);
13234
+ const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
13235
+ const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
13236
+ if (blankUrl) {
13237
+ throw new ClientTransportConfigurationError2(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
13238
+ }
13239
+ const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN2.test(entry.raw));
13240
+ if (controlledUrl) {
13241
+ throw new ClientTransportConfigurationError2(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
13242
+ }
13243
+ const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
13244
+ if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
13245
+ throw new ClientTransportConfigurationError2(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));
13246
+ }
13247
+ const envUrlHit = usableUrlEntries[0] ?? null;
13248
+ const keychainUrlHit = keychainConfigValue2(name, env, options.credentials?.keychain);
13249
+ const diskConfigUrlHit = appConfigDiskValue2(name, env, keys.apiUrlKeys);
13250
+ if (diskConfigUrlHit?.unusable) {
13251
+ throw new ClientTransportConfigurationError2(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
13252
+ }
13253
+ const urlCandidates = [
13254
+ ...envUrlHit ? [envUrlHit] : [],
13255
+ ...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
13256
+ ...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
13257
+ ];
13258
+ const configuredUrl = urlCandidates[0] ?? null;
13259
+ const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
13260
+ if (configuredUrl && divergentUrls.length > 0) {
13261
+ throw new ClientTransportConfigurationError2(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));
13262
+ }
13263
+ const warnings = [];
13264
+ if (configuredUrl && !envUrlHit) {
13265
+ 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.`);
13266
+ }
13267
+ const credential = resolveCredential2(name, env, options.credentials);
13268
+ if (!credential) {
13269
+ const diskHint = credentialDiskSourcesForMessage2(name, env);
13270
+ 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`;
13271
+ 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.`);
13272
+ throw new ClientTransportConfigurationError2(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
13273
+ }
13274
+ if (credential.warning)
13275
+ warnings.push(credential.warning);
13276
+ let urlHit;
13277
+ if (configuredUrl) {
13278
+ urlHit = configuredUrl;
13279
+ } else {
13280
+ try {
13281
+ urlHit = { key: DEFAULT_AUTHORITY_SOURCE2, value: defaultFleetGatewayBaseUrl2(name) };
13282
+ } catch (error) {
13283
+ const message = error instanceof Error ? error.message : String(error);
13284
+ throw new ClientTransportConfigurationError2(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
13285
+ }
13286
+ }
13287
+ const apiUrlSource = urlHit.key;
13288
+ let baseUrl;
13289
+ try {
13290
+ baseUrl = toV1BaseUrl2(urlHit.value);
13291
+ } catch (error) {
13292
+ const message = error instanceof Error ? error.message : String(error);
13293
+ throw new ClientTransportConfigurationError2(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
13294
+ }
13295
+ return {
13296
+ resolution: {
13297
+ transport: "http",
13298
+ transportSource: urlHit.key,
13299
+ baseUrl,
13300
+ apiUrlSource,
13301
+ apiKeyPresent: true,
13302
+ apiKeySource: credential.source,
13303
+ apiKeyTier: credential.tier,
13304
+ misconfigured: false,
13305
+ warning: warnings.length > 0 ? warnings.join(" ") : null
13306
+ },
13307
+ credential
13308
+ };
13309
+ }
13310
+ function credentialDiskSourcesForMessage2(name, env) {
13311
+ const paths = credentialDiskSources2(name, env);
13312
+ return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
13313
+ }
13314
+
13315
+ class HasnaHttpError extends Error {
13316
+ status;
13317
+ method;
13318
+ path;
13319
+ credentialSource;
13320
+ credentialTier;
13321
+ constructor(method, path, status, body, credential) {
13322
+ const guidance = credential ? `. ${credential.guidance}` : "";
13323
+ super(`Hasna cloud request failed: ${method} ${path} -> ${status}${guidance}`);
13324
+ this.name = "HasnaHttpError";
13325
+ this.status = status;
13326
+ this.method = method;
13327
+ this.path = path;
13328
+ Object.defineProperty(this, "body", {
13329
+ value: body,
13330
+ enumerable: status !== 401 && status !== 403,
13331
+ writable: false,
13332
+ configurable: false
13333
+ });
13334
+ this.credentialSource = credential?.source ?? null;
13335
+ this.credentialTier = credential?.tier ?? null;
13336
+ }
13337
+ }
13338
+ function currentCredential(name, apiKey) {
13339
+ if (typeof apiKey === "function") {
13340
+ return validateAndSealResolvedCredential(name, apiKey());
13341
+ }
13342
+ return explicitCredential(name, apiKey);
13343
+ }
13344
+ async function resolveRequestCredential(name, apiKey, env = process.env) {
13345
+ const resolved = currentCredential(name, apiKey);
13346
+ if (resolved.tier === "pointer") {
13347
+ return completePointerCredential(name, resolved, env);
13348
+ }
13349
+ return resolved;
13350
+ }
13351
+ function authFailureGuidance(credential) {
13352
+ const origin = `The API key for this request came from ${credential.source}`;
13353
+ if (credential.deliberate) {
13354
+ 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.`;
13355
+ 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}`;
13356
+ }
13357
+ if (credential.tier === "env") {
13358
+ const target = credential.diskCandidates[0];
13359
+ 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.`;
13360
+ 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}`;
13361
+ }
13362
+ if (credential.tier === "keychain") {
13363
+ 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.`;
13364
+ }
13365
+ 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.`;
13366
+ }
13367
+ var DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
13368
+ var IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
13369
+ var AUTHORITY_OVERRIDE_HEADERS2 = new Set([
13370
+ "host",
13371
+ ":authority",
13372
+ "forwarded",
13373
+ "x-forwarded-host",
13374
+ "x-original-host"
13375
+ ]);
13376
+ function assertNoAuthorityOverrideHeaders(headers, source) {
13377
+ if (!headers)
13378
+ return;
13379
+ const forbidden = Object.keys(headers).find((name) => AUTHORITY_OVERRIDE_HEADERS2.has(name.trim().toLowerCase()));
13380
+ if (forbidden) {
13381
+ throw new Error(`Authenticated ${source} headers must not set authority header '${forbidden}'.`);
13382
+ }
13383
+ }
13384
+ function appendQuery(path, query) {
13385
+ if (!query)
13386
+ return path;
13387
+ const params = query instanceof URLSearchParams ? query : new URLSearchParams;
13388
+ if (!(query instanceof URLSearchParams)) {
13389
+ for (const [key, value] of Object.entries(query)) {
13390
+ if (value === null || value === undefined)
13391
+ continue;
13392
+ if (Array.isArray(value)) {
13393
+ for (const v of value)
13394
+ params.append(key, String(v));
13395
+ } else {
13396
+ params.append(key, String(value));
13397
+ }
13398
+ }
13399
+ }
13400
+ const qs = params.toString();
13401
+ if (!qs)
13402
+ return path;
13403
+ return `${path}${path.includes("?") ? "&" : "?"}${qs}`;
13404
+ }
13405
+ var defaultSleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
13406
+ function createHasnaHttpTransportInternal(options, requestBindingProvider) {
13407
+ const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
13408
+ const base = toV1BaseUrl2(options.baseUrl);
13409
+ const timeoutMs = options.timeoutMs ?? 30000;
13410
+ const sleep = options.sleepImpl ?? defaultSleep;
13411
+ const defaultRetry = options.retry;
13412
+ function resolveRetry(callRetry) {
13413
+ const chosen = callRetry !== undefined ? callRetry : defaultRetry;
13414
+ if (chosen === false)
13415
+ return null;
13416
+ const r = chosen ?? {};
13417
+ return {
13418
+ retries: r.retries ?? 2,
13419
+ baseDelayMs: r.baseDelayMs ?? 200,
13420
+ maxDelayMs: r.maxDelayMs ?? 2000,
13421
+ retryStatuses: r.retryStatuses ?? [...DEFAULT_RETRY_STATUSES]
13422
+ };
13423
+ }
13424
+ async function once(method, rel, url, body, opts, credential) {
13425
+ assertNoAuthorityOverrideHeaders(options.headers, "transport");
13426
+ assertNoAuthorityOverrideHeaders(opts.headers, "request");
13427
+ const headers = {
13428
+ "x-api-key": credential.apiKey,
13429
+ Authorization: `Bearer ${credential.apiKey}`,
13430
+ Accept: "application/json",
13431
+ ...options.headers ?? {},
13432
+ ...opts.headers ?? {}
13433
+ };
13434
+ if (opts.idempotencyKey)
13435
+ headers["Idempotency-Key"] = opts.idempotencyKey;
13436
+ const init = {
13437
+ method,
13438
+ headers,
13439
+ redirect: "manual"
13440
+ };
13441
+ if (body !== undefined) {
13442
+ headers["Content-Type"] = "application/json";
13443
+ init.body = JSON.stringify(body);
13444
+ }
13445
+ const controller = new AbortController;
13446
+ const onAbort = () => controller.abort();
13447
+ if (opts.signal) {
13448
+ if (opts.signal.aborted)
13449
+ controller.abort();
13450
+ else
13451
+ opts.signal.addEventListener("abort", onAbort, { once: true });
13452
+ }
13453
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
13454
+ init.signal = controller.signal;
13455
+ let response;
13456
+ try {
13457
+ response = await fetchImpl(url, init);
13458
+ } catch (error) {
13459
+ const err = error instanceof Error ? error : new Error(String(error));
13460
+ if (opts.signal?.aborted)
13461
+ return { ok: false, retryable: false, error: err };
13462
+ return { ok: false, retryable: true, error: err };
13463
+ } finally {
13464
+ clearTimeout(timer);
13465
+ if (opts.signal)
13466
+ opts.signal.removeEventListener("abort", onAbort);
13467
+ }
13468
+ const authenticationFailure = response.status === 401 || response.status === 403;
13469
+ let parsed = undefined;
13470
+ if (authenticationFailure) {
13471
+ try {
13472
+ await response.body?.cancel();
13473
+ } catch {}
13474
+ } else {
13475
+ const text = await response.text();
13476
+ if (text.length > 0) {
13477
+ try {
13478
+ parsed = JSON.parse(text);
13479
+ } catch {
13480
+ parsed = text;
13481
+ }
13482
+ }
13483
+ }
13484
+ if (!response.ok) {
13485
+ if (response.status >= 300 && response.status < 400) {
13486
+ return {
13487
+ ok: false,
13488
+ retryable: false,
13489
+ error: new HasnaHttpError(method, rel, response.status, parsed)
13490
+ };
13491
+ }
13492
+ if (authenticationFailure) {
13493
+ return {
13494
+ ok: false,
13495
+ retryable: false,
13496
+ error: new HasnaHttpError(method, rel, response.status, undefined, {
13497
+ source: credential.source,
13498
+ tier: credential.tier,
13499
+ guidance: authFailureGuidance(credential)
13500
+ })
13501
+ };
13502
+ }
13503
+ const retry = resolveRetry(opts.retry);
13504
+ const retryable = retry ? retry.retryStatuses.includes(response.status) : false;
13505
+ return { ok: false, retryable, error: new HasnaHttpError(method, rel, response.status, parsed) };
13506
+ }
13507
+ return { ok: true, value: parsed };
13508
+ }
13509
+ async function request(method, path, body, opts = {}) {
13510
+ const upper = method.toUpperCase();
13511
+ const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
13512
+ const retry = resolveRetry(opts.retry);
13513
+ const methodRetryable = IDEMPOTENT_METHODS2.has(upper) || Boolean(opts.idempotencyKey);
13514
+ const maxAttempts = retry && methodRetryable ? retry.retries + 1 : 1;
13515
+ const binding = requestBindingProvider ? await requestBindingProvider() : {
13516
+ baseUrl: base,
13517
+ credential: await resolveRequestCredential(options.name, options.apiKey)
13518
+ };
13519
+ const url = `${binding.baseUrl}${rel}`;
13520
+ const credential = binding.credential;
13521
+ let last = null;
13522
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
13523
+ const result = await once(upper, rel, url, body, opts, credential);
13524
+ if (result.ok)
13525
+ return result.value;
13526
+ last = result;
13527
+ const canRetry = retry !== null && methodRetryable && result.retryable && attempt < maxAttempts;
13528
+ if (!canRetry)
13529
+ break;
13530
+ const backoff = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** (attempt - 1));
13531
+ const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
13532
+ await sleep(backoff + jitter);
13533
+ }
13534
+ throw last.error;
13535
+ }
13536
+ return {
13537
+ baseUrl: base,
13538
+ request,
13539
+ get: (path, opts) => request("GET", path, undefined, opts),
13540
+ post: (path, body, opts) => request("POST", path, body, opts),
13541
+ put: (path, body, opts) => request("PUT", path, body, opts),
13542
+ patch: (path, body, opts) => request("PATCH", path, body, opts),
13543
+ del: (path, body, opts) => request("DELETE", path, body, opts)
13544
+ };
13545
+ }
13546
+ function createClientTransport(name, env = process.env, overrides) {
13547
+ const credentialOptions = overrides?.credentials;
13548
+ const snapshotOptions = { ...credentialOptions ? { credentials: credentialOptions } : {} };
13549
+ const resolution = resolveClientTransportSnapshot2(name, env, snapshotOptions).resolution;
13550
+ 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;
13551
+ const unstableConfiguration = () => new ClientTransportConfigurationError2(name, "The configured service authority or credential changed while a request was being prepared; no authenticated request was sent.");
13552
+ const requestBindingProvider = async () => {
13553
+ const first = resolveClientTransportSnapshot2(name, env, snapshotOptions);
13554
+ const reviewed = resolveClientTransportSnapshot2(name, env, snapshotOptions);
13555
+ if (!sameBinding(first, reviewed))
13556
+ throw unstableConfiguration();
13557
+ if (reviewed.resolution.baseUrl !== resolution.baseUrl) {
13558
+ throw new ClientTransportConfigurationError2(name, "The configured service authority changed; rebuild the client before sending credentials.");
13559
+ }
13560
+ const credential = await resolveRequestCredential(name, () => reviewed.credential, env);
13561
+ const immediatelyBeforeDispatch = resolveClientTransportSnapshot2(name, env, snapshotOptions);
13562
+ if (!sameBinding(reviewed, immediatelyBeforeDispatch))
13563
+ throw unstableConfiguration();
13564
+ if (immediatelyBeforeDispatch.resolution.baseUrl !== resolution.baseUrl) {
13565
+ throw new ClientTransportConfigurationError2(name, "The configured service authority changed; rebuild the client before sending credentials.");
13566
+ }
13567
+ return { baseUrl: immediatelyBeforeDispatch.resolution.baseUrl, credential };
13568
+ };
13569
+ return {
13570
+ transport: "http",
13571
+ client: createHasnaHttpTransportInternal({
13572
+ name,
13573
+ baseUrl: resolution.baseUrl,
13574
+ apiKey: () => {
13575
+ throw new Error("The authenticated request binding provider was not invoked.");
13576
+ },
13577
+ ...overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {},
13578
+ ...overrides?.headers ? { headers: overrides.headers } : {},
13579
+ ...overrides?.timeoutMs ? { timeoutMs: overrides.timeoutMs } : {},
13580
+ ...overrides?.retry !== undefined ? { retry: overrides.retry } : {},
13581
+ ...overrides?.sleepImpl ? { sleepImpl: overrides.sleepImpl } : {}
13582
+ }, requestBindingProvider),
13583
+ resolution
13584
+ };
13585
+ }
13586
+ function resourcePath(resource) {
13587
+ const trimmed = resource.replace(/^\/+|\/+$/g, "");
13588
+ if (!trimmed)
13589
+ throw new Error("resource must be a non-empty path segment");
13590
+ return `/${trimmed}`;
13591
+ }
13592
+ function entityPath(resource, id) {
13593
+ if (id === undefined || id === null || `${id}`.length === 0) {
13594
+ throw new Error("id must be a non-empty string");
13595
+ }
13596
+ return `${resourcePath(resource)}/${encodeURIComponent(String(id))}`;
13597
+ }
13598
+ function newIdempotencyKey() {
13599
+ const g = globalThis;
13600
+ if (g.crypto?.randomUUID)
13601
+ return g.crypto.randomUUID();
13602
+ return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
13603
+ }
13604
+ function extractItems(raw) {
13605
+ if (Array.isArray(raw))
13606
+ return raw;
13607
+ if (raw && typeof raw === "object") {
13608
+ const obj = raw;
13609
+ for (const key of ["items", "data", "results", "rows", "records"]) {
13610
+ if (Array.isArray(obj[key]))
13611
+ return obj[key];
13612
+ }
13613
+ }
13614
+ return [];
13615
+ }
13616
+ function extractTotal(raw) {
13617
+ if (raw && typeof raw === "object") {
13618
+ const obj = raw;
13619
+ for (const key of ["total", "count", "totalCount", "total_count"]) {
13620
+ if (typeof obj[key] === "number")
13621
+ return obj[key];
13622
+ }
13623
+ }
13624
+ return null;
13625
+ }
13626
+ function extractCursor(raw) {
13627
+ if (raw && typeof raw === "object") {
13628
+ const obj = raw;
13629
+ for (const key of ["cursor", "nextCursor", "next_cursor", "next"]) {
13630
+ if (typeof obj[key] === "string")
13631
+ return obj[key];
13632
+ }
13633
+ }
13634
+ return null;
13635
+ }
13636
+ function isNotFoundHttpError(error) {
13637
+ return typeof error === "object" && error !== null && error.name === "HasnaHttpError" && error.status === 404;
13638
+ }
13639
+ function createHasnaStorageClient(name, transport) {
13640
+ return {
13641
+ name,
13642
+ baseUrl: transport.baseUrl,
13643
+ transport,
13644
+ async list(resource, options = {}) {
13645
+ const raw = await transport.get(resourcePath(resource), options);
13646
+ return {
13647
+ items: extractItems(raw),
13648
+ total: extractTotal(raw),
13649
+ cursor: extractCursor(raw),
13650
+ raw
13651
+ };
13652
+ },
13653
+ async get(resource, id, options = {}) {
13654
+ try {
13655
+ return await transport.get(entityPath(resource, id), options);
13656
+ } catch (error) {
13657
+ if (isNotFoundHttpError(error))
13658
+ return null;
13659
+ throw error;
13660
+ }
13661
+ },
13662
+ async create(resource, body, options = {}) {
13663
+ const { idempotencyKey, ...rest } = options;
13664
+ return transport.post(resourcePath(resource), body, {
13665
+ ...rest,
13666
+ idempotencyKey: idempotencyKey ?? newIdempotencyKey()
13667
+ });
13668
+ },
13669
+ async update(resource, id, patch, options = {}) {
13670
+ const { method = "PATCH", idempotencyKey, ...rest } = options;
13671
+ const call = method === "PUT" ? transport.put : transport.patch;
13672
+ return call(entityPath(resource, id), patch, { ...rest, ...idempotencyKey ? { idempotencyKey } : {} });
13673
+ },
13674
+ async delete(resource, id, options = {}) {
13675
+ try {
13676
+ await transport.del(entityPath(resource, id), undefined, options);
13677
+ } catch (error) {
13678
+ if (isNotFoundHttpError(error))
13679
+ return;
13680
+ throw error;
13681
+ }
13682
+ }
13683
+ };
13684
+ }
13685
+ function resolveStorageClient(name, env = process.env, overrides) {
13686
+ const wired = createClientTransport(name, env, overrides);
13687
+ return { transport: "http", client: createHasnaStorageClient(name, wired.client) };
13688
+ }
13689
+
13690
+ // src/lib/transport-resolver.ts
13691
+ var INSTRUCTIONS_APP = "instructions";
13692
+ var INSTRUCTIONS_LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS[0];
13693
+ function rethrowInstructionsAuthorityFailure(error) {
13694
+ const message = error instanceof Error ? error.message : String(error);
13695
+ const name = error instanceof Error ? error.name : "";
13696
+ const failure = (code, lead) => {
13697
+ 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 });
13698
+ };
13699
+ if (name === "CredentialResolutionError" || name === "CredentialFileUnsafeError") {
13700
+ return failure("REMOTE_API_CREDENTIAL_INVALID", "The configured Instructions credential could not be used.");
13701
+ }
13702
+ if (/no API key could be resolved/.test(message)) {
13703
+ if (/is not set and no API key could be resolved/.test(message)) {
13704
+ 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]}.`);
13705
+ }
13706
+ 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]}.`);
13707
+ }
13708
+ return failure("REMOTE_API_URL_INVALID", "the configured Instructions authority is invalid.");
13709
+ }
13710
+ function resolveInstructionsClientTransport(env = process.env, options = {}) {
13711
+ const inputs = instructionsResolverInputs(env, options.credentials);
13712
+ try {
13713
+ return resolveClientTransport(INSTRUCTIONS_APP, inputs.env, {
13714
+ credentials: inputs.credentials
13715
+ });
13716
+ } catch (error) {
13717
+ rethrowInstructionsAuthorityFailure(error);
13718
+ }
13719
+ }
13720
+ function resolveInstructionsStorageClient(env = process.env, options = {}) {
13721
+ const inputs = instructionsResolverInputs(env, options.credentials);
13722
+ try {
13723
+ return resolveStorageClient(INSTRUCTIONS_APP, inputs.env, {
13724
+ fetchImpl: (input, init) => fetch(input, { ...init, redirect: "manual" }),
13725
+ credentials: inputs.credentials
13726
+ });
13727
+ } catch (error) {
13728
+ rethrowInstructionsAuthorityFailure(error);
13729
+ }
13730
+ }
13731
+ function getInstructionsTransportStatus(env = process.env, options = {}) {
13732
+ if (selectsInstructionsLocalStore(env)) {
13733
+ return {
13734
+ selected: false,
13735
+ ok: true,
13736
+ transport: "local",
13737
+ api_url_configured: false,
13738
+ api_key_configured: false,
13739
+ api_url_source: null,
13740
+ api_key_source: null,
13741
+ api_key_tier: null,
13742
+ v1_base_url: null,
13743
+ issues: [],
13744
+ local_fallback: false
13745
+ };
13746
+ }
13747
+ try {
13748
+ const resolution = resolveInstructionsClientTransport(env, options);
13749
+ return {
13750
+ selected: true,
13751
+ ok: true,
13752
+ transport: "http",
13753
+ api_url_configured: resolution.apiUrlSource !== null && resolution.apiUrlSource !== "default",
13754
+ api_key_configured: resolution.apiKeyPresent,
13755
+ api_url_source: resolution.apiUrlSource,
13756
+ api_key_source: resolution.apiKeySource,
13757
+ api_key_tier: resolution.apiKeyTier,
13758
+ v1_base_url: resolution.baseUrl,
13759
+ issues: [],
13760
+ local_fallback: false
13761
+ };
13762
+ } catch (error) {
13763
+ const issue = error instanceof Error ? error.message : String(error);
13764
+ const keys = clientTransportEnvKeys(INSTRUCTIONS_APP);
13765
+ const declared = (names) => names.some((key) => (env[key] ?? "").trim() !== "");
13766
+ return {
13767
+ selected: true,
13768
+ ok: false,
13769
+ transport: "invalid",
13770
+ api_url_configured: declared(keys.apiUrlKeys),
13771
+ api_key_configured: declared(keys.apiKeyKeys),
13772
+ api_url_source: null,
13773
+ api_key_source: null,
13774
+ api_key_tier: null,
13775
+ v1_base_url: null,
13776
+ issues: [issue],
13777
+ local_fallback: false
13778
+ };
13779
+ }
13780
+ }
13781
+ var localNoticePrinted = false;
13782
+ function announceLocalInstructionsMode(write = (line) => process.stderr.write(`${line}
13783
+ `)) {
13784
+ if (localNoticePrinted)
13785
+ return false;
13786
+ localNoticePrinted = true;
13787
+ write(instructionsLocalModeNotice());
13788
+ return true;
13789
+ }
13790
+
13791
+ // src/data/config-store.ts
13792
+ function parseBoundedPagePayload(value, label) {
13793
+ const page = value;
13794
+ const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
13795
+ const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
13796
+ 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)) {
13797
+ throw new Error(`${label} returned an invalid or truncated bounded-read envelope`);
13798
+ }
13799
+ return {
13800
+ ...page,
13801
+ source_bounded: page.source_bounded ?? true
13802
+ };
13803
+ }
13804
+ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
13805
+ if (value && typeof value === "object") {
13806
+ const candidate = value;
13807
+ if ("items" in candidate || "total" in candidate || "complete" in candidate || "truncated" in candidate || "next_cursor" in candidate) {
13808
+ return parseBoundedPagePayload(value, label);
13809
+ }
13810
+ }
13811
+ if (!Array.isArray(legacyItems)) {
13812
+ throw new Error(`${label} returned neither a bounded envelope nor a complete legacy array`);
13813
+ }
13814
+ const normalized = normalizeBoundedReadOptions(options);
13815
+ const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
13816
+ return { ...page, source_bounded: false };
13817
+ }
13818
+ var LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV;
13819
+ function isLocalOptIn(env = process.env) {
13820
+ return isInstructionsLocalOptIn(env);
13821
+ }
13822
+ function isApiTransport(env = process.env) {
13823
+ const { transport } = resolveInstructionsStorageClient(env);
13824
+ return transport === "http";
13825
+ }
13826
+ function isCloudAuthError(err) {
13827
+ return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && (err.status === 401 || err.status === 403);
13828
+ }
13829
+ function isNotFoundHttpError2(err) {
13830
+ return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && err.status === 404;
13831
+ }
13832
+ function formatCliError(err, env = process.env) {
13833
+ if (isCloudAuthError(err)) {
13834
+ return [
13835
+ `Instructions cloud API rejected the request (HTTP ${err.status}: authentication failed).`,
13836
+ ` The API key in use is missing, expired, or revoked (the transport never echoes the server's 401/403 body).`,
13837
+ ` To continue, either:`,
13838
+ ` - set a valid key: export HASNA_INSTRUCTIONS_API_KEY=<new-key>`,
13839
+ ` (or add the Keychain item hasna.credentials.instructions.api-key, or write`,
13840
+ ` ~/.hasna/instructions/config/credentials)`,
13841
+ ` - or opt in to the local store explicitly: export ${LOCAL_OPT_IN_ENV}=1`
13842
+ ].join(`
13843
+ `);
13844
+ }
13845
+ return err instanceof Error ? err.message : String(err);
13846
+ }
13847
+
13848
+ class LocalConfigStore {
13849
+ db;
11737
13850
  mode = "local";
13851
+ v1BaseUrl = null;
11738
13852
  constructor(db) {
11739
13853
  this.db = db;
11740
13854
  }
@@ -11863,50 +13977,22 @@ class LocalConfigStore {
11863
13977
 
11864
13978
  class CloudConfigStore {
11865
13979
  mode = "api";
11866
- base;
11867
- apiKey;
11868
- timeoutMs;
11869
- constructor(config) {
11870
- this.base = `${config.apiUrl.replace(/\/+$/, "")}/v1`;
11871
- this.apiKey = config.apiKey;
11872
- this.timeoutMs = config.timeoutMs ?? 30000;
13980
+ v1BaseUrl;
13981
+ client;
13982
+ constructor(client) {
13983
+ this.client = client;
13984
+ this.v1BaseUrl = client.baseUrl;
11873
13985
  }
11874
13986
  async request(method, path, body, opts = {}) {
11875
- const controller = new AbortController;
11876
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
11877
- const headers = {
11878
- Authorization: `Bearer ${this.apiKey}`,
11879
- Accept: "application/json"
11880
- };
11881
- if (body !== undefined)
11882
- headers["Content-Type"] = "application/json";
11883
- if (opts.idempotent)
11884
- headers["Idempotency-Key"] = randomUUID3();
11885
13987
  try {
11886
- const res = await fetch(`${this.base}${path}`, {
11887
- method,
11888
- headers,
11889
- body: body === undefined ? undefined : JSON.stringify(body),
11890
- signal: controller.signal
13988
+ const data = await this.client.transport.request(method, path, body, {
13989
+ ...opts.idempotent ? { idempotencyKey: randomUUID3() } : {}
11891
13990
  });
11892
- if (res.status === 404 && opts.allow404)
13991
+ return { status: 200, data };
13992
+ } catch (err) {
13993
+ if (opts.allow404 && isNotFoundHttpError2(err))
11893
13994
  return { status: 404, data: null };
11894
- const text = await res.text();
11895
- let parsed = null;
11896
- if (text) {
11897
- try {
11898
- parsed = JSON.parse(text);
11899
- } catch {
11900
- parsed = text;
11901
- }
11902
- }
11903
- if (!res.ok) {
11904
- const message = (parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : undefined) ?? `HTTP ${res.status} on ${method} ${path}`;
11905
- throw new CloudHttpError(res.status, message, parsed);
11906
- }
11907
- return { status: res.status, data: parsed };
11908
- } finally {
11909
- clearTimeout(timer);
13995
+ throw err;
11910
13996
  }
11911
13997
  }
11912
13998
  async listConfigs(filter = {}) {
@@ -12062,7 +14148,7 @@ class CloudConfigStore {
12062
14148
  if (first.status !== 404) {
12063
14149
  if (isUsable(first.data))
12064
14150
  return first;
12065
- throw new CloudHttpError(502, "profile follow-up returned an invalid response", first.data);
14151
+ throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(idOrSlug)}`);
12066
14152
  }
12067
14153
  const profiles = await this.listProfiles();
12068
14154
  const profile = profiles.find((candidate) => candidate.id === idOrSlug || candidate.slug === idOrSlug);
@@ -12075,7 +14161,7 @@ class CloudConfigStore {
12075
14161
  if (response.status !== 404) {
12076
14162
  if (isUsable(response.data))
12077
14163
  return response;
12078
- throw new CloudHttpError(502, "profile follow-up returned an invalid response", response.data);
14164
+ throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(candidate)}`);
12079
14165
  }
12080
14166
  }
12081
14167
  return { status: 404, data: null };
@@ -12158,7 +14244,7 @@ class CloudConfigStore {
12158
14244
  }
12159
14245
  if (data && "complete" in data) {
12160
14246
  if (data.complete !== true || data.truncated !== false) {
12161
- throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
14247
+ throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
12162
14248
  }
12163
14249
  return { ...data, source_bounded: data.source_bounded ?? true };
12164
14250
  }
@@ -12174,7 +14260,7 @@ class CloudConfigStore {
12174
14260
  };
12175
14261
  }
12176
14262
  {
12177
- throw new CloudHttpError(502, "profile resolve returned an incomplete or truncated read", data);
14263
+ throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
12178
14264
  }
12179
14265
  }
12180
14266
  async registerMachine(hostname2, os, arch2) {
@@ -12197,24 +14283,29 @@ class CloudConfigStore {
12197
14283
  });
12198
14284
  }
12199
14285
  async reset() {
12200
- 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.");
14286
+ 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.");
12201
14287
  }
12202
14288
  }
12203
- function resolveConfigStore(env = process.env) {
12204
- const cloud = resolveCloudConfig(env);
12205
- return cloud ? new CloudConfigStore(cloud) : new LocalConfigStore;
14289
+ function resolveConfigStore(env = process.env, options = {}) {
14290
+ if (selectsInstructionsLocalStore(env)) {
14291
+ if (env === process.env)
14292
+ announceLocalInstructionsMode();
14293
+ return new LocalConfigStore;
14294
+ }
14295
+ const { client } = resolveInstructionsStorageClient(env, options);
14296
+ return new CloudConfigStore(client);
12206
14297
  }
12207
14298
  // src/status.ts
12208
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
14299
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
12209
14300
 
12210
14301
  // src/lib/apply.ts
12211
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
12212
- import { basename as basename5, dirname as dirname6, join as join11, resolve as resolve9 } from "path";
12213
- import { homedir as homedir8 } from "os";
14302
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
14303
+ import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve9 } from "path";
14304
+ import { homedir as homedir7 } from "os";
12214
14305
 
12215
14306
  // src/lib/session-render-ownership.ts
12216
- import { existsSync as existsSync8, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
12217
- import { dirname as dirname5, join as join10, parse as parse3, relative as relative3, sep } from "path";
14307
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
14308
+ import { dirname as dirname5, join as join11, parse as parse3, relative as relative3, sep } from "path";
12218
14309
  var MANIFEST_ANCESTOR_LIMIT = 24;
12219
14310
  var MANAGED_PATH_SEGMENTS = SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS.map((managedPath) => managedPath.split("/").filter(Boolean));
12220
14311
  var manifestCache = new Map;
@@ -12248,7 +14339,7 @@ function readManifestRelativePaths(manifestPath) {
12248
14339
  }
12249
14340
  let manifest;
12250
14341
  try {
12251
- manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
14342
+ manifest = JSON.parse(readFileSync7(manifestPath, "utf-8"));
12252
14343
  } catch {
12253
14344
  return null;
12254
14345
  }
@@ -12265,7 +14356,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
12265
14356
  const root = parse3(absolutePath2).root;
12266
14357
  let home = dirname5(absolutePath2);
12267
14358
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
12268
- const manifestPath = join10(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14359
+ const manifestPath = join11(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
12269
14360
  const relativePaths = readManifestRelativePaths(manifestPath);
12270
14361
  if (relativePaths) {
12271
14362
  const claimed = relative3(home, absolutePath2).split(sep).join("/");
@@ -12285,7 +14376,7 @@ function sessionRenderOwnsPath(absolutePath2) {
12285
14376
 
12286
14377
  // src/lib/apply.ts
12287
14378
  function getConfigHome() {
12288
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
14379
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
12289
14380
  }
12290
14381
  function expandPath(p) {
12291
14382
  if (p.startsWith("~/")) {
@@ -12332,7 +14423,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
12332
14423
  }
12333
14424
  const path = expandPath(renderedTargetPath);
12334
14425
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
12335
- const previousContent = existsSync9(path) ? readFileSync6(path, "utf-8") : null;
14426
+ const previousContent = existsSync9(path) ? readFileSync8(path, "utf-8") : null;
12336
14427
  const changed = previousContent !== renderedForTarget;
12337
14428
  if (!opts.dryRun) {
12338
14429
  const dir = dirname6(path);
@@ -12372,7 +14463,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
12372
14463
  const path = expandPath(targetPath);
12373
14464
  if (!existsSync9(path))
12374
14465
  return [];
12375
- current = readFileSync6(path, "utf-8");
14466
+ current = readFileSync8(path, "utf-8");
12376
14467
  } catch {
12377
14468
  return secretTokens;
12378
14469
  }
@@ -12696,14 +14787,14 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
12696
14787
  getConfigHome(),
12697
14788
  opts.vars?.["HOME_DIR"]
12698
14789
  ].filter((home) => typeof home === "string" && home.length > 0));
12699
- if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join11(home, ...relativePath.split("/"))))))
14790
+ if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join12(home, ...relativePath.split("/"))))))
12700
14791
  return true;
12701
14792
  return sessionRenderOwnsPath(normalized);
12702
14793
  }
12703
14794
 
12704
14795
  // src/lib/package-version.ts
12705
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
12706
- import { dirname as dirname7, join as join12 } from "path";
14796
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
14797
+ import { dirname as dirname7, join as join13 } from "path";
12707
14798
  import { fileURLToPath } from "url";
12708
14799
  var cached = null;
12709
14800
  function getPackageVersion() {
@@ -12712,9 +14803,9 @@ function getPackageVersion() {
12712
14803
  try {
12713
14804
  let dir = dirname7(fileURLToPath(import.meta.url));
12714
14805
  for (let i = 0;i < 8; i++) {
12715
- const pkgPath = join12(dir, "package.json");
14806
+ const pkgPath = join13(dir, "package.json");
12716
14807
  if (existsSync10(pkgPath)) {
12717
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
14808
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
12718
14809
  if (pkg.name === "@hasna/instructions" && pkg.version) {
12719
14810
  cached = pkg.version;
12720
14811
  return cached;
@@ -12732,18 +14823,18 @@ function getPackageVersion() {
12732
14823
 
12733
14824
  // src/lib/managed-skill-runtimes.ts
12734
14825
  import { createHash as createHash8 } from "crypto";
12735
- import { spawnSync } from "child_process";
14826
+ import { spawnSync as spawnSync3 } from "child_process";
12736
14827
  import {
12737
14828
  existsSync as existsSync11,
12738
14829
  lstatSync as lstatSync4,
12739
14830
  mkdirSync as mkdirSync4,
12740
- readFileSync as readFileSync8,
14831
+ readFileSync as readFileSync10,
12741
14832
  renameSync as renameSync2,
12742
14833
  rmSync as rmSync3,
12743
14834
  writeFileSync as writeFileSync3
12744
14835
  } from "fs";
12745
- import { homedir as homedir9 } from "os";
12746
- import { dirname as dirname8, join as join13, parse as parse4, relative as relative4, resolve as resolve10 } from "path";
14836
+ import { homedir as homedir8 } from "os";
14837
+ import { dirname as dirname8, join as join14, parse as parse4, relative as relative4, resolve as resolve10 } from "path";
12747
14838
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
12748
14839
  var INBOX_SKILL_MARKERS = [
12749
14840
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -12769,7 +14860,7 @@ function findSymlinkedAncestor(path) {
12769
14860
  let current = parsed.root;
12770
14861
  const rel = relative4(parsed.root, normalized);
12771
14862
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
12772
- current = join13(current, segment);
14863
+ current = join14(current, segment);
12773
14864
  if (!existsSync11(current))
12774
14865
  return null;
12775
14866
  if (lstatSync4(current).isSymbolicLink())
@@ -12787,9 +14878,9 @@ function packagedInboxSkillPath(explicitPath) {
12787
14878
  if (explicitPath)
12788
14879
  return explicitPath;
12789
14880
  const candidates = [
12790
- join13(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
12791
- join13(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
12792
- join13(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
14881
+ join14(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
14882
+ join14(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
14883
+ join14(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
12793
14884
  ];
12794
14885
  const found = candidates.find((candidate) => existsSync11(candidate));
12795
14886
  if (!found) {
@@ -12803,7 +14894,7 @@ function readCanonicalSkill(explicitPath) {
12803
14894
  if (!stat?.isFile()) {
12804
14895
  throw new Error("packaged inbox skill contract is not a regular file");
12805
14896
  }
12806
- const content = readFileSync8(assetPath, "utf8");
14897
+ const content = readFileSync10(assetPath, "utf8");
12807
14898
  if (!content.includes("conversations watch --from <agent> --all")) {
12808
14899
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
12809
14900
  }
@@ -12813,7 +14904,7 @@ function readCanonicalSkill(explicitPath) {
12813
14904
  return { content, sha256: sha2568(content) };
12814
14905
  }
12815
14906
  function runProbe(command, args) {
12816
- const result = spawnSync(command, args, {
14907
+ const result = spawnSync3(command, args, {
12817
14908
  encoding: "utf8",
12818
14909
  timeout: 5000,
12819
14910
  stdio: ["ignore", "pipe", "pipe"]
@@ -12840,8 +14931,8 @@ function compareVersions(left, right) {
12840
14931
  }
12841
14932
  return 0;
12842
14933
  }
12843
- function inspectSkillMarkers(homeDir4) {
12844
- return INBOX_SKILL_MARKERS.map((parts) => join13(homeDir4, ...parts)).map((path) => {
14934
+ function inspectSkillMarkers(homeDir6) {
14935
+ return INBOX_SKILL_MARKERS.map((parts) => join14(homeDir6, ...parts)).map((path) => {
12845
14936
  const stat = lstatOrNull(path);
12846
14937
  if (!stat)
12847
14938
  return null;
@@ -12850,16 +14941,16 @@ function inspectSkillMarkers(homeDir4) {
12850
14941
  }
12851
14942
  return {
12852
14943
  path,
12853
- content: readFileSync8(path, "utf8"),
14944
+ content: readFileSync10(path, "utf8"),
12854
14945
  mode: stat.mode & 511,
12855
14946
  regular: true
12856
14947
  };
12857
14948
  }).filter((snapshot) => snapshot !== null);
12858
14949
  }
12859
14950
  function inspectInbox(options) {
12860
- const homeDir4 = options.homeDir ?? homedir9();
14951
+ const homeDir6 = options.homeDir ?? homedir8();
12861
14952
  const runtimeCommand = options.conversationsCommand ?? "conversations";
12862
- const snapshots = inspectSkillMarkers(homeDir4);
14953
+ const snapshots = inspectSkillMarkers(homeDir6);
12863
14954
  const skillPresent = snapshots.length > 0;
12864
14955
  let canonicalContent = null;
12865
14956
  let canonicalSha256 = null;
@@ -12984,7 +15075,7 @@ function writeAtomic(path, content, mode) {
12984
15075
  }
12985
15076
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
12986
15077
  lstat: lstatOrNull,
12987
- read: (path) => readFileSync8(path, "utf8"),
15078
+ read: (path) => readFileSync10(path, "utf8"),
12988
15079
  write: writeAtomic
12989
15080
  };
12990
15081
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -13177,7 +15268,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
13177
15268
  missingTargets += 1;
13178
15269
  continue;
13179
15270
  }
13180
- const disk = readFileSync9(targetPath, "utf-8");
15271
+ const disk = readFileSync11(targetPath, "utf-8");
13181
15272
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
13182
15273
  if (redactedDisk !== config.content) {
13183
15274
  driftedTargets += 1;
@@ -13269,8 +15360,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
13269
15360
  }
13270
15361
  // src/lib/provider-context.ts
13271
15362
  import { createHash as createHash9 } from "crypto";
13272
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
13273
- import { join as join14 } from "path";
15363
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
15364
+ import { join as join15 } from "path";
13274
15365
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
13275
15366
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
13276
15367
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -13415,18 +15506,18 @@ function resolveAndRenderProviderContext(opts) {
13415
15506
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
13416
15507
  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;
13417
15508
  const content = renderProviderFragment(entry);
13418
- const dir = join14(opts.homeDir, PROVIDER_CONTEXT_DIR);
15509
+ const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
13419
15510
  if (!existsSync13(dir))
13420
15511
  mkdirSync5(dir, { recursive: true });
13421
15512
  const filename = `${entry ? entry.key : "invariant"}.md`;
13422
- const fragmentPath2 = join14(dir, filename);
15513
+ const fragmentPath2 = join15(dir, filename);
13423
15514
  const fragmentSha256 = sha2569(content);
13424
15515
  writeFileSync4(fragmentPath2, content, "utf8");
13425
- const manifestPath = join14(dir, PROVIDER_CONTEXT_MANIFEST);
15516
+ const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
13426
15517
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
13427
15518
  try {
13428
15519
  if (existsSync13(manifestPath)) {
13429
- const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
15520
+ const parsed = JSON.parse(readFileSync12(manifestPath, "utf8"));
13430
15521
  if (parsed && typeof parsed === "object")
13431
15522
  manifest = parsed;
13432
15523
  }
@@ -13536,10 +15627,10 @@ var PG_MIGRATIONS = [
13536
15627
  `CREATE INDEX IF NOT EXISTS profile_assets_source_config_idx ON profile_assets (source_config_id)`
13537
15628
  ];
13538
15629
  // src/lib/station-profile.ts
13539
- import { spawnSync as spawnSync2 } from "child_process";
13540
- import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
13541
- import { arch as osArch, homedir as homedir10, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
13542
- import { dirname as dirname9, join as join15 } from "path";
15630
+ import { spawnSync as spawnSync4 } from "child_process";
15631
+ import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync13, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
15632
+ import { arch as osArch, homedir as homedir9, hostname as osHostname3, platform as osPlatform, userInfo as osUserInfo } from "os";
15633
+ import { dirname as dirname9, join as join16 } from "path";
13543
15634
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
13544
15635
  var STATION_PROFILE_SOURCE_ID = "station-profile";
13545
15636
  var STATION_PROFILE_LAYER = "machine";
@@ -13549,23 +15640,23 @@ var STATION_PROFILE_FULL_NAMES_MAX = 6;
13549
15640
  var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
13550
15641
  var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
13551
15642
  var BUN_INSTALL_ENV = "BUN_INSTALL";
13552
- function homeDir4(env = process.env) {
13553
- return env["HOME"] || env["USERPROFILE"] || homedir10();
15643
+ function homeDir6(env = process.env) {
15644
+ return env["HOME"] || env["USERPROFILE"] || homedir9();
13554
15645
  }
13555
15646
  function getStationProfileCachePath(env = process.env) {
13556
- return join15(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
15647
+ return join16(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
13557
15648
  }
13558
15649
  function getMachinesManifestPath(env = process.env) {
13559
- return env[MACHINES_MANIFEST_PATH_ENV] || join15(homeDir4(env), ".hasna", "machines", "machines.json");
15650
+ return env[MACHINES_MANIFEST_PATH_ENV] || join16(homeDir6(env), ".hasna", "machines", "machines.json");
13560
15651
  }
13561
15652
  function getBunGlobalModulesDir(env = process.env) {
13562
- return join15(env[BUN_INSTALL_ENV] || join15(homeDir4(env), ".bun"), "install", "global", "node_modules");
15653
+ return join16(env[BUN_INSTALL_ENV] || join16(homeDir6(env), ".bun"), "install", "global", "node_modules");
13563
15654
  }
13564
15655
  function readMachinesManifest(path) {
13565
15656
  try {
13566
15657
  if (!existsSync14(path))
13567
15658
  return null;
13568
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
15659
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
13569
15660
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
13570
15661
  return null;
13571
15662
  const machines = parsed["machines"];
@@ -13595,7 +15686,7 @@ function metadataUser(record) {
13595
15686
  }
13596
15687
  function probeMachineStatus(machineId) {
13597
15688
  try {
13598
- const result = spawnSync2("machines", ["details", "--json", "--machine", machineId], {
15689
+ const result = spawnSync4("machines", ["details", "--json", "--machine", machineId], {
13599
15690
  encoding: "utf8",
13600
15691
  timeout: 3000,
13601
15692
  stdio: ["ignore", "pipe", "pipe"]
@@ -13617,11 +15708,11 @@ function probeMachineStatus(machineId) {
13617
15708
  }
13618
15709
  }
13619
15710
  function resolveStationProfileMachine(env = process.env, options = {}) {
13620
- const hostname2 = osHostname();
15711
+ const hostname2 = osHostname3();
13621
15712
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
13622
- const home = homeDir4(env);
15713
+ const home = homeDir6(env);
13623
15714
  const platform = stringField(record, "platform") ?? osPlatform();
13624
- const workspacePath = stringField(record, "workspacePath") ?? join15(home, platform === "darwin" ? "Workspace" : "workspace");
15715
+ const workspacePath = stringField(record, "workspacePath") ?? join16(home, platform === "darwin" ? "Workspace" : "workspace");
13625
15716
  const machine = {
13626
15717
  id: stringField(record, "id") ?? hostname2,
13627
15718
  hostname: stringField(record, "hostname") ?? hostname2,
@@ -13638,7 +15729,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
13638
15729
  return machine;
13639
15730
  }
13640
15731
  function scopedPackageNames(modulesDir, scope) {
13641
- const scopeDir = join15(modulesDir, scope);
15732
+ const scopeDir = join16(modulesDir, scope);
13642
15733
  try {
13643
15734
  if (!existsSync14(scopeDir))
13644
15735
  return null;
@@ -13650,7 +15741,7 @@ function scopedPackageNames(modulesDir, scope) {
13650
15741
  function readdirNames(dir) {
13651
15742
  return readdirSync2(dir).filter((name) => {
13652
15743
  try {
13653
- return lstatSync5(join15(dir, name)).isDirectory();
15744
+ return lstatSync5(join16(dir, name)).isDirectory();
13654
15745
  } catch {
13655
15746
  return false;
13656
15747
  }
@@ -13735,7 +15826,7 @@ function refreshStationProfile(options = {}) {
13735
15826
  const path = getStationProfileCachePath(env);
13736
15827
  const generatedAt = new Date().toISOString();
13737
15828
  if (!options.dryRun) {
13738
- const existing = existsSync14(path) ? readFileSync11(path, "utf8") : null;
15829
+ const existing = existsSync14(path) ? readFileSync13(path, "utf8") : null;
13739
15830
  if (existing !== content) {
13740
15831
  mkdirSync6(dirname9(path), { recursive: true });
13741
15832
  writeFileSync5(path, content, "utf8");
@@ -13756,7 +15847,7 @@ function readStationProfile(env = process.env) {
13756
15847
  try {
13757
15848
  if (!existsSync14(path))
13758
15849
  return null;
13759
- return readFileSync11(path, "utf8");
15850
+ return readFileSync13(path, "utf8");
13760
15851
  } catch {
13761
15852
  return null;
13762
15853
  }
@@ -13781,11 +15872,11 @@ import {
13781
15872
  existsSync as existsSync15,
13782
15873
  lstatSync as lstatSync6,
13783
15874
  mkdirSync as mkdirSync7,
13784
- readFileSync as readFileSync12,
15875
+ readFileSync as readFileSync14,
13785
15876
  readdirSync as readdirSync3,
13786
15877
  statSync as statSync5
13787
15878
  } from "fs";
13788
- import { dirname as dirname10, isAbsolute as isAbsolute4, join as join16, parse as parse5, relative as relative5, resolve as resolve11 } from "path";
15879
+ import { dirname as dirname10, isAbsolute as isAbsolute6, join as join17, parse as parse5, relative as relative5, resolve as resolve11 } from "path";
13789
15880
  class SessionApplyError extends Error {
13790
15881
  constructor(message) {
13791
15882
  super(message);
@@ -13949,7 +16040,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
13949
16040
  });
13950
16041
  continue;
13951
16042
  }
13952
- const actualSha256 = sha25610(readFileSync12(target, "utf-8"));
16043
+ const actualSha256 = sha25610(readFileSync14(target, "utf-8"));
13953
16044
  if (actualSha256 !== file.sha256) {
13954
16045
  drifted.push({
13955
16046
  path: target,
@@ -13975,10 +16066,10 @@ function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
13975
16066
  const resolvedSnapshotPath = resolve11(snapshotPath);
13976
16067
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
13977
16068
  const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
13978
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute4(snapshotDirRelative);
16069
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute6(snapshotDirRelative);
13979
16070
  if (!insideSnapshotDir) {
13980
16071
  const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
13981
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
16072
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute6(snapshotRelativePath)) {
13982
16073
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
13983
16074
  }
13984
16075
  }
@@ -14107,7 +16198,7 @@ function readSessionRenderSnapshot(snapshotPath) {
14107
16198
  }
14108
16199
  let parsed;
14109
16200
  try {
14110
- parsed = JSON.parse(readFileSync12(resolved, "utf8"));
16201
+ parsed = JSON.parse(readFileSync14(resolved, "utf8"));
14111
16202
  } catch {
14112
16203
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
14113
16204
  }
@@ -14185,7 +16276,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
14185
16276
  }
14186
16277
  let parsedManifest;
14187
16278
  try {
14188
- parsedManifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
16279
+ parsedManifest = JSON.parse(readFileSync14(manifestPath, "utf8"));
14189
16280
  } catch {
14190
16281
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
14191
16282
  }
@@ -14285,7 +16376,7 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
14285
16376
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
14286
16377
  continue;
14287
16378
  try {
14288
- const candidate = JSON.parse(readFileSync12(candidatePath, "utf8"));
16379
+ const candidate = JSON.parse(readFileSync14(candidatePath, "utf8"));
14289
16380
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
14290
16381
  if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve11(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
14291
16382
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
@@ -14363,7 +16454,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
14363
16454
  }
14364
16455
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
14365
16456
  const target = resolvePlannedFilePath(plan, file, targetHome);
14366
- const previousContent = existsSync15(target) ? readFileSync12(target, "utf-8") : null;
16457
+ const previousContent = existsSync15(target) ? readFileSync14(target, "utf-8") : null;
14367
16458
  const previousSha256 = previousContent === null ? null : sha25610(previousContent);
14368
16459
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
14369
16460
  const changed = previousContent !== file.content;
@@ -14464,7 +16555,7 @@ function planStaleFileResult(file, targetHome, options) {
14464
16555
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
14465
16556
  if (!existsSync15(target))
14466
16557
  return null;
14467
- const previousContent = readFileSync12(target, "utf-8");
16558
+ const previousContent = readFileSync14(target, "utf-8");
14468
16559
  const previousSha256 = sha25610(previousContent);
14469
16560
  if (!options.force && previousSha256 !== file.sha256) {
14470
16561
  return {
@@ -14511,7 +16602,7 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
14511
16602
  function resolvePlannedFilePath(plan, file, targetHome) {
14512
16603
  const target = resolve11(targetHome, ...file.relativePath.split("/"));
14513
16604
  const rel = relative5(targetHome, target);
14514
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16605
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute6(rel)) {
14515
16606
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
14516
16607
  }
14517
16608
  if (resolve11(file.path) !== target) {
@@ -14523,7 +16614,7 @@ function resolvePlannedFilePath(plan, file, targetHome) {
14523
16614
  function resolveManifestRelativePath(relativePath, targetHome) {
14524
16615
  const target = resolve11(targetHome, ...relativePath.split(/[\\/]+/));
14525
16616
  const rel = relative5(targetHome, target);
14526
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16617
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute6(rel)) {
14527
16618
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
14528
16619
  }
14529
16620
  assertNoSymlinkSegments2(targetHome, target);
@@ -14533,7 +16624,7 @@ function readPreviousManifest(path) {
14533
16624
  if (!existsSync15(path))
14534
16625
  return null;
14535
16626
  try {
14536
- const parsed = JSON.parse(readFileSync12(path, "utf-8"));
16627
+ const parsed = JSON.parse(readFileSync14(path, "utf-8"));
14537
16628
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
14538
16629
  return null;
14539
16630
  if (!Array.isArray(parsed.files))
@@ -14578,7 +16669,7 @@ function currentSessionFileHash(path, targetHome) {
14578
16669
  if (stat.isSymbolicLink() || !stat.isFile()) {
14579
16670
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
14580
16671
  }
14581
- return sha25610(readFileSync12(path, "utf-8"));
16672
+ return sha25610(readFileSync14(path, "utf-8"));
14582
16673
  }
14583
16674
  function requiredPreviousHash(result) {
14584
16675
  if (result.previousSha256 === null) {
@@ -14588,7 +16679,7 @@ function requiredPreviousHash(result) {
14588
16679
  }
14589
16680
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
14590
16681
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync15(result.path)).map((result) => {
14591
- const content = readFileSync12(result.path, "utf-8");
16682
+ const content = readFileSync14(result.path, "utf-8");
14592
16683
  return {
14593
16684
  path: result.path,
14594
16685
  relativePath: result.relativePath,
@@ -14606,7 +16697,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
14606
16697
  };
14607
16698
  }
14608
16699
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
14609
- const snapshotPath = join16(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
16700
+ const snapshotPath = join17(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
14610
16701
  const afterFiles = results.map((result) => {
14611
16702
  if (result.action === "conflict") {
14612
16703
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -14652,7 +16743,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
14652
16743
  };
14653
16744
  }
14654
16745
  function assertSafeTargetHome(targetHome) {
14655
- if (!isAbsolute4(targetHome))
16746
+ if (!isAbsolute6(targetHome))
14656
16747
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
14657
16748
  const normalized = resolve11(targetHome);
14658
16749
  if (normalized === parse5(normalized).root) {
@@ -14669,7 +16760,7 @@ function assertNoSymlinkSegments2(root, target) {
14669
16760
  const rel = relative5(root, target);
14670
16761
  let current = root;
14671
16762
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
14672
- current = join16(current, segment);
16763
+ current = join17(current, segment);
14673
16764
  if (existsSync15(current) && lstatSync6(current).isSymbolicLink()) {
14674
16765
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
14675
16766
  }
@@ -14681,7 +16772,7 @@ function assertNoSymlinkAncestors3(path) {
14681
16772
  let current = parsed.root;
14682
16773
  const rel = relative5(parsed.root, normalized);
14683
16774
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
14684
- current = join16(current, segment);
16775
+ current = join17(current, segment);
14685
16776
  if (!existsSync15(current))
14686
16777
  return;
14687
16778
  if (lstatSync6(current).isSymbolicLink()) {
@@ -14695,9 +16786,6 @@ function sha25610(content) {
14695
16786
  // src/lib/project-dashboard-standard.ts
14696
16787
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
14697
16788
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
14698
- PROJECT_DASHBOARD_DIR: ".hasna/project",
14699
- PROJECT_DASHBOARD_RENDER_MANIFEST: ".hasna/project/dashboard/render.json",
14700
- PROJECT_DASHBOARD_SNAPSHOTS_DIR: ".hasna/project/dashboard/snapshots",
14701
16789
  PROJECT_CHANNEL_PREFIX: ""
14702
16790
  };
14703
16791
  var PROJECT_DASHBOARD_STANDARD_CONTENT = `# Agent-Managed Project Dashboard Standard
@@ -14708,9 +16796,13 @@ evidence, tasks, knowledge, and dashboard output consistent.
14708
16796
 
14709
16797
  ## Canonical Files
14710
16798
 
14711
- - Project manifest root: \`.hasna/project/\`
14712
- - Dashboard render manifest: \`.hasna/project/dashboard/render.json\`
14713
- - Latest snapshot: \`.hasna/project/dashboard/snapshots/latest.snapshot.json\`
16799
+ There is exactly one project-layout convention: the canonical per-workspace
16800
+ store \`~/.hasna/projects/workspaces/<workspace_id>/\`. Never create a project
16801
+ layout directory inside the project folder itself.
16802
+
16803
+ - Per-workspace store root: \`~/.hasna/projects/workspaces/<workspace_id>/\`
16804
+ - Dashboard render manifest: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/render.json\`
16805
+ - Latest snapshot: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/snapshots/latest.snapshot.json\`
14714
16806
  - Dashboard schema ids come from \`@hasna/contracts\`.
14715
16807
  - Project folders may contain private documents, but render JSON must contain
14716
16808
  only ids, counts, statuses, resource refs, evidence refs, and redacted
@@ -14993,13 +17085,13 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
14993
17085
  }
14994
17086
  }
14995
17087
  // src/lib/sync.ts
14996
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
14997
- import { basename as basename6, extname as extname3, join as join18 } from "path";
17088
+ import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
17089
+ import { basename as basename6, extname as extname3, join as join19 } from "path";
14998
17090
 
14999
17091
  // src/lib/sync-dir.ts
15000
- import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync6 } from "fs";
15001
- import { join as join17, relative as relative6 } from "path";
15002
- import { homedir as homedir11 } from "os";
17092
+ import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
17093
+ import { join as join18, relative as relative6 } from "path";
17094
+ import { homedir as homedir10 } from "os";
15003
17095
  var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
15004
17096
  function shouldSkip(p) {
15005
17097
  return SKIP.some((s) => p.includes(s));
@@ -15009,9 +17101,9 @@ async function syncFromDir(dir, opts = {}) {
15009
17101
  const absDir = expandPath(dir);
15010
17102
  if (!existsSync16(absDir))
15011
17103
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
15012
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join17(absDir, f)).filter((f) => statSync6(f).isFile());
17104
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join18(absDir, f)).filter((f) => statSync6(f).isFile());
15013
17105
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
15014
- const home = homedir11();
17106
+ const home = homedir10();
15015
17107
  const allConfigs = await store.listConfigs();
15016
17108
  for (const file of files) {
15017
17109
  if (shouldSkip(file)) {
@@ -15019,7 +17111,7 @@ async function syncFromDir(dir, opts = {}) {
15019
17111
  continue;
15020
17112
  }
15021
17113
  try {
15022
- const content = readFileSync13(file, "utf-8");
17114
+ const content = readFileSync15(file, "utf-8");
15023
17115
  if (content.length > 500000) {
15024
17116
  result.skipped.push(file + " (too large)");
15025
17117
  continue;
@@ -15046,7 +17138,7 @@ async function syncFromDir(dir, opts = {}) {
15046
17138
  }
15047
17139
  async function syncToDir(dir, opts = {}) {
15048
17140
  const store = opts.store ?? resolveConfigStore();
15049
- const home = homedir11();
17141
+ const home = homedir10();
15050
17142
  const absDir = expandPath(dir);
15051
17143
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
15052
17144
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -15070,7 +17162,7 @@ async function syncToDir(dir, opts = {}) {
15070
17162
  }
15071
17163
  function walkDir(dir, files = []) {
15072
17164
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
15073
- const full = join17(dir, entry.name);
17165
+ const full = join18(dir, entry.name);
15074
17166
  if (shouldSkip(full))
15075
17167
  continue;
15076
17168
  if (entry.isDirectory())
@@ -15204,11 +17296,11 @@ async function syncProject(opts) {
15204
17296
  const allConfigs = await store.listConfigs();
15205
17297
  const machine = detectMachineContext();
15206
17298
  for (const pf of PROJECT_CONFIG_FILES) {
15207
- const abs = join18(absDir, pf.file);
17299
+ const abs = join19(absDir, pf.file);
15208
17300
  if (!existsSync17(abs))
15209
17301
  continue;
15210
17302
  try {
15211
- const rawContent = readFileSync14(abs, "utf-8");
17303
+ const rawContent = readFileSync16(abs, "utf-8");
15212
17304
  if (rawContent.length > 500000) {
15213
17305
  result.skipped.push(pf.file);
15214
17306
  continue;
@@ -15237,20 +17329,20 @@ async function syncProject(opts) {
15237
17329
  }
15238
17330
  }
15239
17331
  for (const ruleDir of [
15240
- { dir: join18(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
15241
- { dir: join18(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
15242
- { dir: join18(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
15243
- { dir: join18(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
15244
- { dir: join18(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
15245
- { dir: join18(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
15246
- { dir: join18(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
17332
+ { dir: join19(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
17333
+ { dir: join19(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
17334
+ { dir: join19(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
17335
+ { dir: join19(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
17336
+ { dir: join19(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
17337
+ { dir: join19(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
17338
+ { dir: join19(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
15247
17339
  ]) {
15248
17340
  if (!existsSync17(ruleDir.dir))
15249
17341
  continue;
15250
17342
  const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
15251
17343
  for (const f of mdFiles) {
15252
- const abs = join18(ruleDir.dir, f);
15253
- const raw = readFileSync14(abs, "utf-8");
17344
+ const abs = join19(ruleDir.dir, f);
17345
+ const raw = readFileSync16(abs, "utf-8");
15254
17346
  const redacted = redactContent(raw, "markdown");
15255
17347
  const machineAware = templateizeMachineContent(redacted.content, machine);
15256
17348
  const content = machineAware.content;
@@ -15296,13 +17388,13 @@ async function syncKnown(opts = {}) {
15296
17388
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
15297
17389
  const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
15298
17390
  for (const f of ruleFiles) {
15299
- const abs2 = join18(absDir, f);
17391
+ const abs2 = join19(absDir, f);
15300
17392
  const targetPath = abs2.replace(home, "~");
15301
17393
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
15302
17394
  result.skipped.push(`${targetPath} (generated output)`);
15303
17395
  continue;
15304
17396
  }
15305
- const raw = readFileSync14(abs2, "utf-8");
17397
+ const raw = readFileSync16(abs2, "utf-8");
15306
17398
  const redacted = redactContent(raw, "markdown");
15307
17399
  const machineAware = templateizeMachineContent(redacted.content, machine);
15308
17400
  const content = machineAware.content;
@@ -15335,7 +17427,7 @@ async function syncKnown(opts = {}) {
15335
17427
  continue;
15336
17428
  }
15337
17429
  try {
15338
- const rawContent = normalizeKnownConfigSource(known, readFileSync14(abs, "utf-8"));
17430
+ const rawContent = normalizeKnownConfigSource(known, readFileSync16(abs, "utf-8"));
15339
17431
  if (rawContent.length > 500000) {
15340
17432
  result.skipped.push(known.path + " (too large)");
15341
17433
  continue;
@@ -15440,7 +17532,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
15440
17532
  const path = expandPath(targetPath);
15441
17533
  if (!existsSync17(path))
15442
17534
  return `(file not found on disk: ${path})`;
15443
- const diskContent = readFileSync14(path, "utf-8");
17535
+ const diskContent = readFileSync16(path, "utf-8");
15444
17536
  if (diskContent === expectedContent)
15445
17537
  return "(no diff \u2014 identical)";
15446
17538
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -15599,14 +17691,14 @@ function detectFormat(filePath) {
15599
17691
  }
15600
17692
  // src/lib/export.ts
15601
17693
  import { existsSync as existsSync18, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
15602
- import { join as join19, resolve as resolve12 } from "path";
17694
+ import { join as join20, resolve as resolve12 } from "path";
15603
17695
  import { tmpdir } from "os";
15604
17696
  async function exportConfigs(outputPath, opts = {}) {
15605
17697
  const store = opts.store ?? resolveConfigStore();
15606
17698
  const configs = await store.listConfigs(opts.filter);
15607
17699
  const absOutput = resolve12(outputPath);
15608
- const tmpDir = join19(tmpdir(), `configs-export-${Date.now()}`);
15609
- const contentsDir = join19(tmpDir, "contents");
17700
+ const tmpDir = join20(tmpdir(), `configs-export-${Date.now()}`);
17701
+ const contentsDir = join20(tmpDir, "contents");
15610
17702
  try {
15611
17703
  mkdirSync8(contentsDir, { recursive: true });
15612
17704
  const manifest = {
@@ -15614,10 +17706,10 @@ async function exportConfigs(outputPath, opts = {}) {
15614
17706
  exported_at: new Date().toISOString(),
15615
17707
  configs: configs.map(({ content: _content, ...meta }) => meta)
15616
17708
  };
15617
- writeFileSync6(join19(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
17709
+ writeFileSync6(join20(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
15618
17710
  for (const config of configs) {
15619
17711
  const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
15620
- writeFileSync6(join19(contentsDir, fileName), config.content, "utf-8");
17712
+ writeFileSync6(join20(contentsDir, fileName), config.content, "utf-8");
15621
17713
  }
15622
17714
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
15623
17715
  stdout: "pipe",
@@ -15636,14 +17728,14 @@ async function exportConfigs(outputPath, opts = {}) {
15636
17728
  }
15637
17729
  }
15638
17730
  // src/lib/import.ts
15639
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync15, rmSync as rmSync5 } from "fs";
15640
- import { join as join20, resolve as resolve13 } from "path";
17731
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync17, rmSync as rmSync5 } from "fs";
17732
+ import { join as join21, resolve as resolve13 } from "path";
15641
17733
  import { tmpdir as tmpdir2 } from "os";
15642
17734
  async function importConfigs(bundlePath, opts = {}) {
15643
17735
  const store = opts.store ?? resolveConfigStore();
15644
17736
  const conflict = opts.conflict ?? "skip";
15645
17737
  const absPath = resolve13(bundlePath);
15646
- const tmpDir = join20(tmpdir2(), `configs-import-${Date.now()}`);
17738
+ const tmpDir = join21(tmpdir2(), `configs-import-${Date.now()}`);
15647
17739
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
15648
17740
  try {
15649
17741
  mkdirSync9(tmpDir, { recursive: true });
@@ -15656,15 +17748,15 @@ async function importConfigs(bundlePath, opts = {}) {
15656
17748
  const stderr = await new Response(proc.stderr).text();
15657
17749
  throw new Error(`tar extraction failed: ${stderr}`);
15658
17750
  }
15659
- const manifestPath = join20(tmpDir, "manifest.json");
17751
+ const manifestPath = join21(tmpDir, "manifest.json");
15660
17752
  if (!existsSync19(manifestPath))
15661
17753
  throw new Error("Invalid bundle: missing manifest.json");
15662
- const manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
17754
+ const manifest = JSON.parse(readFileSync17(manifestPath, "utf-8"));
15663
17755
  for (const meta of manifest.configs) {
15664
17756
  try {
15665
17757
  const ext = meta.format === "text" ? "txt" : meta.format;
15666
- const contentFile = join20(tmpDir, "contents", `${meta.slug}.${ext}`);
15667
- const content = existsSync19(contentFile) ? readFileSync15(contentFile, "utf-8") : "";
17758
+ const contentFile = join21(tmpDir, "contents", `${meta.slug}.${ext}`);
17759
+ const content = existsSync19(contentFile) ? readFileSync17(contentFile, "utf-8") : "";
15668
17760
  let existing = null;
15669
17761
  try {
15670
17762
  existing = await store.getConfig(meta.slug);
@@ -15705,9 +17797,9 @@ async function importConfigs(bundlePath, opts = {}) {
15705
17797
  }
15706
17798
  // src/lib/package-manager-guard.ts
15707
17799
  import { execFileSync as execFileSync2 } from "child_process";
15708
- import { existsSync as existsSync20, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
15709
- import { homedir as homedir12 } from "os";
15710
- import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute5, join as join21, relative as relative7, resolve as resolve14 } from "path";
17800
+ import { existsSync as existsSync20, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync18 } from "fs";
17801
+ import { homedir as homedir11 } from "os";
17802
+ import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute7, join as join22, relative as relative7, resolve as resolve14 } from "path";
15711
17803
  var SKIP_DIRS = new Set([
15712
17804
  ".git",
15713
17805
  "node_modules",
@@ -15776,9 +17868,9 @@ function scanPackageManagerSecrets(options = {}) {
15776
17868
  }
15777
17869
  }
15778
17870
  if (options.includeHome) {
15779
- const home = homedir12();
17871
+ const home = homedir11();
15780
17872
  for (const name of HOME_FILES) {
15781
- const file = join21(home, name);
17873
+ const file = join22(home, name);
15782
17874
  if (!existsSync20(file))
15783
17875
  continue;
15784
17876
  const text = readTextFile(file);
@@ -15803,12 +17895,12 @@ function collectRepoFiles(root) {
15803
17895
  if (entry.isDirectory()) {
15804
17896
  if (SKIP_DIRS.has(entry.name))
15805
17897
  continue;
15806
- visit(join21(dir, entry.name));
17898
+ visit(join22(dir, entry.name));
15807
17899
  continue;
15808
17900
  }
15809
17901
  if (!entry.isFile())
15810
17902
  continue;
15811
- const file = join21(dir, entry.name);
17903
+ const file = join22(dir, entry.name);
15812
17904
  if (shouldScanRepoFile(file))
15813
17905
  out.push(file);
15814
17906
  }
@@ -15846,7 +17938,7 @@ function readTextFile(file) {
15846
17938
  const stat = lstatSync7(file);
15847
17939
  if (!stat.isFile() || stat.size > 5000000)
15848
17940
  return null;
15849
- const buf = readFileSync16(file);
17941
+ const buf = readFileSync18(file);
15850
17942
  if (buf.includes(0))
15851
17943
  return null;
15852
17944
  return buf.toString("utf-8");
@@ -16076,10 +18168,10 @@ function stripInlineComment(value) {
16076
18168
  return value.replace(/\s[#;].*$/, "").trim();
16077
18169
  }
16078
18170
  function displayPath(file, root) {
16079
- const home = homedir12();
18171
+ const home = homedir11();
16080
18172
  if (root === home && (file === home || file.startsWith(home + "/")))
16081
18173
  return "~/" + toPosix(relative7(home, file));
16082
- if (isAbsolute5(root) && file.startsWith(root + "/"))
18174
+ if (isAbsolute7(root) && file.startsWith(root + "/"))
16083
18175
  return toPosix(relative7(root, file));
16084
18176
  if (file === home || file.startsWith(home + "/"))
16085
18177
  return "~/" + toPosix(relative7(home, file));
@@ -16115,7 +18207,6 @@ export {
16115
18207
  resolveSessionPath,
16116
18208
  resolveProfileVariables,
16117
18209
  resolveConfigStore,
16118
- resolveCloudConfig,
16119
18210
  resolveAssetDestination,
16120
18211
  resolveAndRenderProviderContext,
16121
18212
  resolveAgentOperatingRulesPayload,
@@ -16147,13 +18238,19 @@ export {
16147
18238
  machineContextToVariables,
16148
18239
  legacyProfileConfigBinding,
16149
18240
  isTemplate,
18241
+ isLocalOptIn,
18242
+ isInstructionsLocalOptIn,
18243
+ isCloudAuthError,
16150
18244
  isApiTransport,
18245
+ instructionsLocalModeNotice,
16151
18246
  inspectManagedSkillRuntimes,
16152
18247
  importConfigs,
16153
18248
  hasSecrets,
16154
18249
  getStationProfileCachePath,
16155
18250
  getRawStoreRoot,
18251
+ getInstructionsTransportStatus,
16156
18252
  getConfigsStatus,
18253
+ formatCliError,
16157
18254
  extractTemplateVars,
16158
18255
  exportConfigs,
16159
18256
  expandPath,
@@ -16190,6 +18287,7 @@ export {
16190
18287
  applyConfigsWithReport,
16191
18288
  applyConfigs,
16192
18289
  applyConfig,
18290
+ announceLocalInstructionsMode,
16193
18291
  TemplateRenderError,
16194
18292
  SessionApplyError,
16195
18293
  STATION_PROFILE_SOURCE_ID,
@@ -16231,6 +18329,7 @@ export {
16231
18329
  PLATFORM_PROFILE_PRESETS,
16232
18330
  PG_MIGRATIONS,
16233
18331
  LocalConfigStore,
18332
+ LOCAL_OPT_IN_ENV,
16234
18333
  LEGACY_CONFIGS_PACKAGE,
16235
18334
  LEGACY_CONFIGS_EXECUTABLE,
16236
18335
  LEGACY_CONFIGS_COMPAT_VERSION,
@@ -16239,6 +18338,8 @@ export {
16239
18338
  INSTRUCTION_GRAPH_PLAN_SCHEMA,
16240
18339
  INSTRUCTION_FALLBACKS,
16241
18340
  INSTRUCTION_ACTIVATION_MODES,
18341
+ INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS,
18342
+ INSTRUCTIONS_LOCAL_OPT_IN_ENV,
16242
18343
  INBOX_CONVERSATIONS_MINIMUM_VERSION,
16243
18344
  GLOBAL_AGENT_RULES_STANDARD_SLUG,
16244
18345
  GLOBAL_AGENT_RULES_STANDARD_CONTENT,
@@ -16246,7 +18347,6 @@ export {
16246
18347
  DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
16247
18348
  ConfigNotFoundError,
16248
18349
  ConfigApplyError,
16249
- CloudHttpError,
16250
18350
  CloudConfigStore,
16251
18351
  CONFIG_TRANSFORMS,
16252
18352
  CONFIG_KINDS,