@hasna/instructions 0.4.36 → 0.4.39

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 (52) hide show
  1. package/README.md +9 -8
  2. package/assets/skills/inbox/SKILL.md +86 -0
  3. package/dashboard/README.md +34 -70
  4. package/dist/cli/index.js +1839 -721
  5. package/dist/cli/raw-store-root.test.d.ts +2 -0
  6. package/dist/cli/raw-store-root.test.d.ts.map +1 -0
  7. package/dist/data/config-store.d.ts +4 -4
  8. package/dist/data/config-store.d.ts.map +1 -1
  9. package/dist/db/configs.d.ts.map +1 -1
  10. package/dist/db/database.d.ts.map +1 -1
  11. package/dist/generated/storage-kit/backend.d.ts +20 -0
  12. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  13. package/dist/generated/storage-kit/index.d.ts +2 -2
  14. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  15. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  16. package/dist/generated/storage-kit/own.d.ts +11 -0
  17. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  18. package/dist/generated/storage-kit/pool.d.ts +7 -6
  19. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  20. package/dist/generated/storage-kit/tls.d.ts +30 -3
  21. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  22. package/dist/index.d.ts +4 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1237 -704
  25. package/dist/lib/managed-skill-runtimes.d.ts +80 -0
  26. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -0
  27. package/dist/lib/managed-skill-runtimes.test.d.ts +2 -0
  28. package/dist/lib/managed-skill-runtimes.test.d.ts.map +1 -0
  29. package/dist/lib/raw-store-root.d.ts +17 -0
  30. package/dist/lib/raw-store-root.d.ts.map +1 -0
  31. package/dist/lib/retired-storage-mode.d.ts +8 -0
  32. package/dist/lib/retired-storage-mode.d.ts.map +1 -0
  33. package/dist/lib/session-apply.d.ts.map +1 -1
  34. package/dist/lib/session-authority.d.ts +27 -0
  35. package/dist/lib/session-authority.d.ts.map +1 -0
  36. package/dist/lib/session-authority.test.d.ts +2 -0
  37. package/dist/lib/session-authority.test.d.ts.map +1 -0
  38. package/dist/lib/session-render.d.ts +5 -3
  39. package/dist/lib/session-render.d.ts.map +1 -1
  40. package/dist/mcp/index.js +303 -246
  41. package/dist/server/cloud.d.ts +2 -2
  42. package/dist/server/cloud.d.ts.map +1 -1
  43. package/dist/server/index.d.ts +1 -1
  44. package/dist/server/index.js +875 -496
  45. package/dist/status.d.ts +11 -1
  46. package/dist/status.d.ts.map +1 -1
  47. package/dist/storage/cloud-store.d.ts.map +1 -1
  48. package/dist/storage/cloud-store.test.d.ts +2 -0
  49. package/dist/storage/cloud-store.test.d.ts.map +1 -0
  50. package/package.json +10 -7
  51. package/dist/generated/storage-kit/mode.d.ts +0 -48
  52. package/dist/generated/storage-kit/mode.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -20,12 +20,14 @@ var __toESM = (mod, isNodeMode, target) => {
20
20
  }
21
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
28
- });
23
+ if (mod && typeof mod === "object" || typeof mod === "function") {
24
+ for (let key of __getOwnPropNames(mod))
25
+ if (!__hasOwnProp.call(to, key))
26
+ __defProp(to, key, {
27
+ get: __accessProp.bind(mod, key),
28
+ enumerable: true
29
+ });
30
+ }
29
31
  if (canCache)
30
32
  cache.set(mod, to);
31
33
  return to;
@@ -47,8 +49,8 @@ var __export = (target, all) => {
47
49
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
50
  var __require = import.meta.require;
49
51
 
50
- // node_modules/commander/lib/error.js
51
- var require_error = __commonJS((exports) => {
52
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
53
+ var require_error = __commonJS(function(exports) {
52
54
  class CommanderError extends Error {
53
55
  constructor(exitCode, code, message) {
54
56
  super(message);
@@ -71,8 +73,8 @@ var require_error = __commonJS((exports) => {
71
73
  exports.InvalidArgumentError = InvalidArgumentError;
72
74
  });
73
75
 
74
- // node_modules/commander/lib/argument.js
75
- var require_argument = __commonJS((exports) => {
76
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
77
+ var require_argument = __commonJS(function(exports) {
76
78
  var { InvalidArgumentError } = require_error();
77
79
 
78
80
  class Argument {
@@ -150,8 +152,8 @@ var require_argument = __commonJS((exports) => {
150
152
  exports.humanReadableArgName = humanReadableArgName;
151
153
  });
152
154
 
153
- // node_modules/commander/lib/help.js
154
- var require_help = __commonJS((exports) => {
155
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
156
+ var require_help = __commonJS(function(exports) {
155
157
  var { humanReadableArgName } = require_argument();
156
158
 
157
159
  class Help {
@@ -500,8 +502,8 @@ ${itemIndentStr}`);
500
502
  exports.stripColor = stripColor;
501
503
  });
502
504
 
503
- // node_modules/commander/lib/option.js
504
- var require_option = __commonJS((exports) => {
505
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
506
+ var require_option = __commonJS(function(exports) {
505
507
  var { InvalidArgumentError } = require_error();
506
508
 
507
509
  class Option {
@@ -678,8 +680,8 @@ var require_option = __commonJS((exports) => {
678
680
  exports.DualOptions = DualOptions;
679
681
  });
680
682
 
681
- // node_modules/commander/lib/suggestSimilar.js
682
- var require_suggestSimilar = __commonJS((exports) => {
683
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
684
+ var require_suggestSimilar = __commonJS(function(exports) {
683
685
  var maxDistance = 3;
684
686
  function editDistance(a, b) {
685
687
  if (Math.abs(a.length - b.length) > maxDistance)
@@ -751,8 +753,8 @@ var require_suggestSimilar = __commonJS((exports) => {
751
753
  exports.suggestSimilar = suggestSimilar;
752
754
  });
753
755
 
754
- // node_modules/commander/lib/command.js
755
- var require_command = __commonJS((exports) => {
756
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
757
+ var require_command = __commonJS(function(exports) {
756
758
  var EventEmitter = __require("events").EventEmitter;
757
759
  var childProcess = __require("child_process");
758
760
  var path = __require("path");
@@ -2061,8 +2063,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2061
2063
  exports.useColor = useColor;
2062
2064
  });
2063
2065
 
2064
- // node_modules/commander/index.js
2065
- var require_commander = __commonJS((exports) => {
2066
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
2067
+ var require_commander = __commonJS(function(exports) {
2066
2068
  var { Argument } = require_argument();
2067
2069
  var { Command } = require_command();
2068
2070
  var { CommanderError, InvalidArgumentError } = require_error();
@@ -2136,19 +2138,51 @@ var init_types = __esm(() => {
2136
2138
  };
2137
2139
  });
2138
2140
 
2141
+ // src/lib/retired-storage-mode.ts
2142
+ function firstDefinedEnvKey(env, keys) {
2143
+ for (const key of keys) {
2144
+ if (Object.hasOwn(env, key) && env[key] !== undefined)
2145
+ return key;
2146
+ }
2147
+ return null;
2148
+ }
2149
+ function assertNoLegacyStorageMode(env = process.env) {
2150
+ const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
2151
+ if (!legacyKey)
2152
+ return;
2153
+ 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.`);
2154
+ }
2155
+ var LEGACY_STORAGE_MODE_KEYS;
2156
+ var init_retired_storage_mode = __esm(() => {
2157
+ LEGACY_STORAGE_MODE_KEYS = [
2158
+ "HASNA_INSTRUCTIONS_STORAGE_MODE",
2159
+ "HASNA_INSTRUCTIONS_MODE",
2160
+ "INSTRUCTIONS_STORAGE_MODE",
2161
+ "INSTRUCTIONS_MODE"
2162
+ ];
2163
+ });
2164
+
2165
+ // src/lib/raw-store-root.ts
2166
+ import { homedir as homedir2 } from "os";
2167
+ import { join as join2, resolve } from "path";
2168
+ function getRawStoreRoot() {
2169
+ return resolve(process.env[RAW_STORE_ROOT_ENV] || join2(process.env["HOME"] || homedir2(), ".hasna", "instructions"));
2170
+ }
2171
+ var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME";
2172
+ var init_raw_store_root = () => {};
2173
+
2139
2174
  // src/db/database.ts
2140
2175
  import { Database } from "bun:sqlite";
2141
2176
  import { existsSync as existsSync2, mkdirSync, rmSync } from "fs";
2142
- import { join as join2 } from "path";
2177
+ import { join as join3 } from "path";
2143
2178
  import { randomUUID as randomUUID3 } from "crypto";
2144
2179
  function getDbPath() {
2145
2180
  if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
2146
2181
  return process.env["HASNA_INSTRUCTIONS_DB_PATH"];
2147
2182
  }
2148
- const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2149
- const dir = join2(home, ".hasna", "instructions");
2183
+ const dir = getRawStoreRoot();
2150
2184
  mkdirSync(dir, { recursive: true });
2151
- return join2(dir, "instructions.db");
2185
+ return join3(dir, "instructions.db");
2152
2186
  }
2153
2187
  function uuid() {
2154
2188
  return randomUUID3();
@@ -2162,8 +2196,9 @@ function slugify(name) {
2162
2196
  function getDatabase(path) {
2163
2197
  if (_db)
2164
2198
  return _db;
2199
+ assertNoLegacyStorageMode();
2165
2200
  if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
2166
- throw new Error("instructions is in self_hosted (cloud) mode: this command is not wired to the cloud API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
2201
+ 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.");
2167
2202
  }
2168
2203
  const dbPath = path || getDbPath();
2169
2204
  const db = new Database(dbPath);
@@ -2244,6 +2279,8 @@ function insertFeedback(input, db) {
2244
2279
  }
2245
2280
  var MIGRATIONS, _db = null;
2246
2281
  var init_database = __esm(() => {
2282
+ init_retired_storage_mode();
2283
+ init_raw_store_root();
2247
2284
  MIGRATIONS = [
2248
2285
  `
2249
2286
  CREATE TABLE IF NOT EXISTS configs (
@@ -2328,6 +2365,37 @@ var init_database = __esm(() => {
2328
2365
  ];
2329
2366
  });
2330
2367
 
2368
+ // src/db/snapshots.ts
2369
+ function createSnapshot(configId, content, version, db) {
2370
+ const d = db || getDatabase();
2371
+ const id = uuid();
2372
+ const ts = now2();
2373
+ d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
2374
+ return { id, config_id: configId, content, version, created_at: ts };
2375
+ }
2376
+ function listSnapshots(configId, db) {
2377
+ const d = db || getDatabase();
2378
+ return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
2379
+ }
2380
+ function getSnapshot(id, db) {
2381
+ const d = db || getDatabase();
2382
+ return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
2383
+ }
2384
+ function getSnapshotByVersion(configId, version, db) {
2385
+ const d = db || getDatabase();
2386
+ return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
2387
+ }
2388
+ function pruneSnapshots(configId, keep = 10, db) {
2389
+ const d = db || getDatabase();
2390
+ const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
2391
+ SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
2392
+ )`, [configId, configId, keep]);
2393
+ return result.changes;
2394
+ }
2395
+ var init_snapshots = __esm(() => {
2396
+ init_database();
2397
+ });
2398
+
2331
2399
  // src/db/configs.ts
2332
2400
  function rowToConfig(row) {
2333
2401
  let outputs = [];
@@ -2366,25 +2434,28 @@ function createConfig(input, db) {
2366
2434
  const slug = uniqueSlug(input.name, d);
2367
2435
  const tags = JSON.stringify(input.tags || []);
2368
2436
  const outputs = JSON.stringify(input.outputs || []);
2369
- d.run(`INSERT INTO configs (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at, synced_at)
2370
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
2371
- id,
2372
- input.name,
2373
- slug,
2374
- input.kind ?? "file",
2375
- input.category,
2376
- input.agent ?? "global",
2377
- input.target_path ?? null,
2378
- outputs,
2379
- input.format ?? "text",
2380
- input.content,
2381
- input.description ?? null,
2382
- tags,
2383
- input.is_template ? 1 : 0,
2384
- ts,
2385
- ts
2386
- ]);
2387
- return getConfig(id, d);
2437
+ return d.transaction(() => {
2438
+ d.run(`INSERT INTO configs (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at, synced_at)
2439
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
2440
+ id,
2441
+ input.name,
2442
+ slug,
2443
+ input.kind ?? "file",
2444
+ input.category,
2445
+ input.agent ?? "global",
2446
+ input.target_path ?? null,
2447
+ outputs,
2448
+ input.format ?? "text",
2449
+ input.content,
2450
+ input.description ?? null,
2451
+ tags,
2452
+ input.is_template ? 1 : 0,
2453
+ ts,
2454
+ ts
2455
+ ]);
2456
+ createSnapshot(id, input.content, 1, d);
2457
+ return getConfig(id, d);
2458
+ })();
2388
2459
  }
2389
2460
  function getConfig(idOrSlug, db) {
2390
2461
  const d = db || getDatabase();
@@ -2489,9 +2560,13 @@ function updateConfig(idOrSlug, input, db) {
2489
2560
  updates.push("synced_at = ?");
2490
2561
  params.push(input.synced_at);
2491
2562
  }
2492
- params.push(existing.id);
2493
- d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
2494
- return getConfigById(existing.id, d);
2563
+ return d.transaction(() => {
2564
+ params.push(existing.id);
2565
+ d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
2566
+ const updated = getConfigById(existing.id, d);
2567
+ createSnapshot(updated.id, updated.content, updated.version, d);
2568
+ return updated;
2569
+ })();
2495
2570
  }
2496
2571
  function deleteConfig(idOrSlug, db) {
2497
2572
  const d = db || getDatabase();
@@ -2511,16 +2586,17 @@ function getConfigStats(db) {
2511
2586
  var init_configs = __esm(() => {
2512
2587
  init_types();
2513
2588
  init_database();
2589
+ init_snapshots();
2514
2590
  });
2515
2591
 
2516
2592
  // src/lib/template.ts
2517
2593
  var exports_template = {};
2518
2594
  __export(exports_template, {
2519
- renderTemplatePreview: () => renderTemplatePreview,
2520
- renderTemplate: () => renderTemplate,
2521
- parseTemplateVars: () => parseTemplateVars,
2595
+ extractTemplateVars: () => extractTemplateVars,
2522
2596
  isTemplate: () => isTemplate,
2523
- extractTemplateVars: () => extractTemplateVars
2597
+ parseTemplateVars: () => parseTemplateVars,
2598
+ renderTemplate: () => renderTemplate,
2599
+ renderTemplatePreview: () => renderTemplatePreview
2524
2600
  });
2525
2601
  function parseTemplateVars(content) {
2526
2602
  const names = new Set;
@@ -2584,9 +2660,9 @@ var init_template = __esm(() => {
2584
2660
  });
2585
2661
 
2586
2662
  // src/lib/machine.ts
2587
- import { arch as currentArch, homedir as homedir2, hostname as currentHostname, type as currentOsType } from "os";
2663
+ import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
2588
2664
  import { existsSync as existsSync3 } from "fs";
2589
- import { join as join3 } from "path";
2665
+ import { join as join4 } from "path";
2590
2666
  function normalizeOsFamily(os) {
2591
2667
  const value = (os ?? "").trim().toLowerCase();
2592
2668
  if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
@@ -2598,11 +2674,11 @@ function normalizeOsFamily(os) {
2598
2674
  return value || "unknown";
2599
2675
  }
2600
2676
  function detectMachineContext(overrides = {}) {
2601
- const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir2();
2677
+ const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
2602
2678
  const os = overrides.os ?? currentOsType();
2603
2679
  const osFamily = normalizeOsFamily(os);
2604
- const bunBinDir = overrides.bun_bin_dir ?? join3(homeDir, ".bun", "bin");
2605
- const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join3(bunBinDir, "bun");
2680
+ const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir, ".bun", "bin");
2681
+ const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join4(bunBinDir, "bun");
2606
2682
  return {
2607
2683
  id: "current-machine",
2608
2684
  hostname: overrides.hostname ?? currentHostname(),
@@ -2612,10 +2688,10 @@ function detectMachineContext(overrides = {}) {
2612
2688
  created_at: "",
2613
2689
  os_family: osFamily,
2614
2690
  home_dir: homeDir,
2615
- workspace_root: overrides.workspace_root ?? join3(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
2691
+ workspace_root: overrides.workspace_root ?? join4(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
2616
2692
  bun_bin_dir: bunBinDir,
2617
2693
  bun_path: overrides.bun_path ?? defaultBunPath,
2618
- path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join3("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2694
+ path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join4("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2619
2695
  };
2620
2696
  }
2621
2697
  function machineContextToVariables(machine) {
@@ -3037,7 +3113,7 @@ Policy reference: \`${CODEWITH_SHARED_TODOS_STORAGE_POLICY_REFERENCE}\`
3037
3113
  `;
3038
3114
  });
3039
3115
 
3040
- // node_modules/zod/v3/helpers/util.js
3116
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
3041
3117
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
3042
3118
  const t = typeof data;
3043
3119
  switch (t) {
@@ -3168,7 +3244,7 @@ var init_util = __esm(() => {
3168
3244
  ]);
3169
3245
  });
3170
3246
 
3171
- // node_modules/zod/v3/ZodError.js
3247
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
3172
3248
  var ZodIssueCode, quotelessJson = (obj) => {
3173
3249
  const json = JSON.stringify(obj, null, 2);
3174
3250
  return json.replace(/"([^"]+)":/g, "$1:");
@@ -3289,7 +3365,7 @@ var init_ZodError = __esm(() => {
3289
3365
  };
3290
3366
  });
3291
3367
 
3292
- // node_modules/zod/v3/locales/en.js
3368
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
3293
3369
  var errorMap = (issue, _ctx) => {
3294
3370
  let message;
3295
3371
  switch (issue.code) {
@@ -3396,7 +3472,7 @@ var init_en = __esm(() => {
3396
3472
  en_default = errorMap;
3397
3473
  });
3398
3474
 
3399
- // node_modules/zod/v3/errors.js
3475
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
3400
3476
  function setErrorMap(map) {
3401
3477
  overrideErrorMap = map;
3402
3478
  }
@@ -3409,7 +3485,7 @@ var init_errors = __esm(() => {
3409
3485
  overrideErrorMap = en_default;
3410
3486
  });
3411
3487
 
3412
- // node_modules/zod/v3/helpers/parseUtil.js
3488
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
3413
3489
  function addIssueToContext(ctx, issueData) {
3414
3490
  const overrideMap = getErrorMap();
3415
3491
  const issue = makeIssue({
@@ -3514,10 +3590,10 @@ var init_parseUtil = __esm(() => {
3514
3590
  });
3515
3591
  });
3516
3592
 
3517
- // node_modules/zod/v3/helpers/typeAliases.js
3593
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
3518
3594
  var init_typeAliases = () => {};
3519
3595
 
3520
- // node_modules/zod/v3/helpers/errorUtil.js
3596
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
3521
3597
  var errorUtil;
3522
3598
  var init_errorUtil = __esm(() => {
3523
3599
  (function(errorUtil2) {
@@ -3526,7 +3602,7 @@ var init_errorUtil = __esm(() => {
3526
3602
  })(errorUtil || (errorUtil = {}));
3527
3603
  });
3528
3604
 
3529
- // node_modules/zod/v3/types.js
3605
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
3530
3606
  class ParseInputLazyPath {
3531
3607
  constructor(parent, value, path, key) {
3532
3608
  this._cachedPath = [];
@@ -6877,208 +6953,208 @@ var init_types2 = __esm(() => {
6877
6953
  NEVER = INVALID;
6878
6954
  });
6879
6955
 
6880
- // node_modules/zod/v3/external.js
6956
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
6881
6957
  var exports_external = {};
6882
6958
  __export(exports_external, {
6883
- void: () => voidType,
6884
- util: () => util,
6885
- unknown: () => unknownType,
6886
- union: () => unionType,
6887
- undefined: () => undefinedType,
6888
- tuple: () => tupleType,
6889
- transformer: () => effectsType,
6890
- symbol: () => symbolType,
6891
- string: () => stringType,
6892
- strictObject: () => strictObjectType,
6893
- setErrorMap: () => setErrorMap,
6894
- set: () => setType,
6895
- record: () => recordType,
6896
- quotelessJson: () => quotelessJson,
6897
- promise: () => promiseType,
6898
- preprocess: () => preprocessType,
6899
- pipeline: () => pipelineType,
6900
- ostring: () => ostring,
6901
- optional: () => optionalType,
6902
- onumber: () => onumber,
6903
- oboolean: () => oboolean,
6904
- objectUtil: () => objectUtil,
6905
- object: () => objectType,
6906
- number: () => numberType,
6907
- nullable: () => nullableType,
6908
- null: () => nullType,
6909
- never: () => neverType,
6910
- nativeEnum: () => nativeEnumType,
6911
- nan: () => nanType,
6912
- map: () => mapType,
6913
- makeIssue: () => makeIssue,
6914
- literal: () => literalType,
6915
- lazy: () => lazyType,
6916
- late: () => late,
6917
- isValid: () => isValid,
6918
- isDirty: () => isDirty,
6919
- isAsync: () => isAsync,
6920
- isAborted: () => isAborted,
6921
- intersection: () => intersectionType,
6922
- instanceof: () => instanceOfType,
6923
- getParsedType: () => getParsedType,
6924
- getErrorMap: () => getErrorMap,
6925
- function: () => functionType,
6926
- enum: () => enumType,
6927
- effect: () => effectsType,
6928
- discriminatedUnion: () => discriminatedUnionType,
6929
- defaultErrorMap: () => en_default,
6930
- datetimeRegex: () => datetimeRegex,
6931
- date: () => dateType,
6932
- custom: () => custom,
6933
- coerce: () => coerce,
6934
- boolean: () => booleanType,
6935
- bigint: () => bigIntType,
6936
- array: () => arrayType,
6937
- any: () => anyType,
6938
- addIssueToContext: () => addIssueToContext,
6939
- ZodVoid: () => ZodVoid,
6940
- ZodUnknown: () => ZodUnknown,
6941
- ZodUnion: () => ZodUnion,
6942
- ZodUndefined: () => ZodUndefined,
6943
- ZodType: () => ZodType,
6944
- ZodTuple: () => ZodTuple,
6945
- ZodTransformer: () => ZodEffects,
6946
- ZodSymbol: () => ZodSymbol,
6947
- ZodString: () => ZodString,
6948
- ZodSet: () => ZodSet,
6949
- ZodSchema: () => ZodType,
6950
- ZodRecord: () => ZodRecord,
6951
- ZodReadonly: () => ZodReadonly,
6952
- ZodPromise: () => ZodPromise,
6953
- ZodPipeline: () => ZodPipeline,
6954
- ZodParsedType: () => ZodParsedType,
6955
- ZodOptional: () => ZodOptional,
6956
- ZodObject: () => ZodObject,
6957
- ZodNumber: () => ZodNumber,
6958
- ZodNullable: () => ZodNullable,
6959
- ZodNull: () => ZodNull,
6960
- ZodNever: () => ZodNever,
6961
- ZodNativeEnum: () => ZodNativeEnum,
6962
- ZodNaN: () => ZodNaN,
6963
- ZodMap: () => ZodMap,
6964
- ZodLiteral: () => ZodLiteral,
6965
- ZodLazy: () => ZodLazy,
6966
- ZodIssueCode: () => ZodIssueCode,
6967
- ZodIntersection: () => ZodIntersection,
6968
- ZodFunction: () => ZodFunction,
6969
- ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
6970
- ZodError: () => ZodError,
6971
- ZodEnum: () => ZodEnum,
6972
- ZodEffects: () => ZodEffects,
6973
- ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
6974
- ZodDefault: () => ZodDefault,
6975
- ZodDate: () => ZodDate,
6976
- ZodCatch: () => ZodCatch,
6977
- ZodBranded: () => ZodBranded,
6978
- ZodBoolean: () => ZodBoolean,
6979
- ZodBigInt: () => ZodBigInt,
6980
- ZodArray: () => ZodArray,
6981
- ZodAny: () => ZodAny,
6982
- Schema: () => ZodType,
6983
- ParseStatus: () => ParseStatus,
6984
- OK: () => OK,
6985
- NEVER: () => NEVER,
6986
- INVALID: () => INVALID,
6987
- EMPTY_PATH: () => EMPTY_PATH,
6959
+ BRAND: () => BRAND,
6988
6960
  DIRTY: () => DIRTY,
6989
- BRAND: () => BRAND
6990
- });
6991
- var init_external = __esm(() => {
6992
- init_errors();
6993
- init_parseUtil();
6994
- init_typeAliases();
6995
- init_util();
6996
- init_types2();
6997
- init_ZodError();
6998
- });
6999
-
7000
- // node_modules/zod/index.js
7001
- var init_zod = __esm(() => {
7002
- init_external();
7003
- init_external();
7004
- });
7005
-
7006
- // src/lib/redact.ts
7007
- function redactShell(content) {
7008
- const redacted = [];
7009
- const lines = content.split(`
7010
- `);
7011
- const out = [];
7012
- for (let i = 0;i < lines.length; i++) {
7013
- const line = lines[i];
7014
- const m = line.match(/^(\s*(?:export\s+)?)([A-Z][A-Z0-9_]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
7015
- if (m) {
7016
- const [, prefix, key, eq, quote, value] = m;
7017
- if (shouldRedactKeyValue(key, value)) {
7018
- const reason = reasonFor(key, value);
7019
- redacted.push({ varName: key, line: i + 1, reason });
7020
- out.push(`${prefix}${key}${eq}${quote}{{${key}}}${quote}`);
7021
- continue;
7022
- }
7023
- }
7024
- out.push(line);
7025
- }
7026
- return { content: out.join(`
7027
- `), redacted, isTemplate: redacted.length > 0 };
7028
- }
7029
- function redactJson(content) {
7030
- const redacted = [];
7031
- const lines = content.split(`
7032
- `);
7033
- const out = [];
7034
- for (let i = 0;i < lines.length; i++) {
7035
- const line = lines[i];
7036
- const m = line.match(/^(\s*"([^"]+)"\s*:\s*)"([^"]+)"(,?)(\s*)$/);
7037
- if (m) {
7038
- const [, prefix, key, value, comma, trail] = m;
7039
- if (shouldRedactKeyValue(key, value)) {
7040
- const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
7041
- redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
7042
- out.push(`${prefix}"{{${varName}}}"${comma}${trail}`);
7043
- continue;
7044
- }
7045
- }
7046
- let newLine = line;
7047
- for (const { re, reason } of VALUE_PATTERNS) {
7048
- newLine = newLine.replace(re, (match) => {
7049
- const varName = `REDACTED_${reason.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
7050
- redacted.push({ varName, line: i + 1, reason });
7051
- return `{{${varName}}}`;
7052
- });
7053
- }
7054
- out.push(newLine);
7055
- }
7056
- return { content: out.join(`
7057
- `), redacted, isTemplate: redacted.length > 0 };
7058
- }
7059
- function redactToml(content) {
7060
- const redacted = [];
7061
- const lines = content.split(`
7062
- `);
7063
- const out = [];
7064
- for (let i = 0;i < lines.length; i++) {
7065
- const line = lines[i];
7066
- const m = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9_\-]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
7067
- if (m) {
7068
- const [, indent, key, eq, quote, value] = m;
7069
- if (shouldRedactKeyValue(key, value)) {
7070
- const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
7071
- redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
7072
- out.push(`${indent}${key}${eq}${quote}{{${varName}}}${quote}`);
7073
- continue;
7074
- }
7075
- }
7076
- out.push(line);
7077
- }
7078
- return { content: out.join(`
7079
- `), redacted, isTemplate: redacted.length > 0 };
7080
- }
7081
- function redactIni(content) {
6961
+ EMPTY_PATH: () => EMPTY_PATH,
6962
+ INVALID: () => INVALID,
6963
+ NEVER: () => NEVER,
6964
+ OK: () => OK,
6965
+ ParseStatus: () => ParseStatus,
6966
+ Schema: () => ZodType,
6967
+ ZodAny: () => ZodAny,
6968
+ ZodArray: () => ZodArray,
6969
+ ZodBigInt: () => ZodBigInt,
6970
+ ZodBoolean: () => ZodBoolean,
6971
+ ZodBranded: () => ZodBranded,
6972
+ ZodCatch: () => ZodCatch,
6973
+ ZodDate: () => ZodDate,
6974
+ ZodDefault: () => ZodDefault,
6975
+ ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
6976
+ ZodEffects: () => ZodEffects,
6977
+ ZodEnum: () => ZodEnum,
6978
+ ZodError: () => ZodError,
6979
+ ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
6980
+ ZodFunction: () => ZodFunction,
6981
+ ZodIntersection: () => ZodIntersection,
6982
+ ZodIssueCode: () => ZodIssueCode,
6983
+ ZodLazy: () => ZodLazy,
6984
+ ZodLiteral: () => ZodLiteral,
6985
+ ZodMap: () => ZodMap,
6986
+ ZodNaN: () => ZodNaN,
6987
+ ZodNativeEnum: () => ZodNativeEnum,
6988
+ ZodNever: () => ZodNever,
6989
+ ZodNull: () => ZodNull,
6990
+ ZodNullable: () => ZodNullable,
6991
+ ZodNumber: () => ZodNumber,
6992
+ ZodObject: () => ZodObject,
6993
+ ZodOptional: () => ZodOptional,
6994
+ ZodParsedType: () => ZodParsedType,
6995
+ ZodPipeline: () => ZodPipeline,
6996
+ ZodPromise: () => ZodPromise,
6997
+ ZodReadonly: () => ZodReadonly,
6998
+ ZodRecord: () => ZodRecord,
6999
+ ZodSchema: () => ZodType,
7000
+ ZodSet: () => ZodSet,
7001
+ ZodString: () => ZodString,
7002
+ ZodSymbol: () => ZodSymbol,
7003
+ ZodTransformer: () => ZodEffects,
7004
+ ZodTuple: () => ZodTuple,
7005
+ ZodType: () => ZodType,
7006
+ ZodUndefined: () => ZodUndefined,
7007
+ ZodUnion: () => ZodUnion,
7008
+ ZodUnknown: () => ZodUnknown,
7009
+ ZodVoid: () => ZodVoid,
7010
+ addIssueToContext: () => addIssueToContext,
7011
+ any: () => anyType,
7012
+ array: () => arrayType,
7013
+ bigint: () => bigIntType,
7014
+ boolean: () => booleanType,
7015
+ coerce: () => coerce,
7016
+ custom: () => custom,
7017
+ date: () => dateType,
7018
+ datetimeRegex: () => datetimeRegex,
7019
+ defaultErrorMap: () => en_default,
7020
+ discriminatedUnion: () => discriminatedUnionType,
7021
+ effect: () => effectsType,
7022
+ enum: () => enumType,
7023
+ function: () => functionType,
7024
+ getErrorMap: () => getErrorMap,
7025
+ getParsedType: () => getParsedType,
7026
+ instanceof: () => instanceOfType,
7027
+ intersection: () => intersectionType,
7028
+ isAborted: () => isAborted,
7029
+ isAsync: () => isAsync,
7030
+ isDirty: () => isDirty,
7031
+ isValid: () => isValid,
7032
+ late: () => late,
7033
+ lazy: () => lazyType,
7034
+ literal: () => literalType,
7035
+ makeIssue: () => makeIssue,
7036
+ map: () => mapType,
7037
+ nan: () => nanType,
7038
+ nativeEnum: () => nativeEnumType,
7039
+ never: () => neverType,
7040
+ null: () => nullType,
7041
+ nullable: () => nullableType,
7042
+ number: () => numberType,
7043
+ object: () => objectType,
7044
+ objectUtil: () => objectUtil,
7045
+ oboolean: () => oboolean,
7046
+ onumber: () => onumber,
7047
+ optional: () => optionalType,
7048
+ ostring: () => ostring,
7049
+ pipeline: () => pipelineType,
7050
+ preprocess: () => preprocessType,
7051
+ promise: () => promiseType,
7052
+ quotelessJson: () => quotelessJson,
7053
+ record: () => recordType,
7054
+ set: () => setType,
7055
+ setErrorMap: () => setErrorMap,
7056
+ strictObject: () => strictObjectType,
7057
+ string: () => stringType,
7058
+ symbol: () => symbolType,
7059
+ transformer: () => effectsType,
7060
+ tuple: () => tupleType,
7061
+ undefined: () => undefinedType,
7062
+ union: () => unionType,
7063
+ unknown: () => unknownType,
7064
+ util: () => util,
7065
+ void: () => voidType
7066
+ });
7067
+ var init_external = __esm(() => {
7068
+ init_errors();
7069
+ init_parseUtil();
7070
+ init_typeAliases();
7071
+ init_util();
7072
+ init_types2();
7073
+ init_ZodError();
7074
+ });
7075
+
7076
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
7077
+ var init_zod = __esm(() => {
7078
+ init_external();
7079
+ init_external();
7080
+ });
7081
+
7082
+ // src/lib/redact.ts
7083
+ function redactShell(content) {
7084
+ const redacted = [];
7085
+ const lines = content.split(`
7086
+ `);
7087
+ const out = [];
7088
+ for (let i = 0;i < lines.length; i++) {
7089
+ const line = lines[i];
7090
+ const m = line.match(/^(\s*(?:export\s+)?)([A-Z][A-Z0-9_]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
7091
+ if (m) {
7092
+ const [, prefix, key, eq, quote, value] = m;
7093
+ if (shouldRedactKeyValue(key, value)) {
7094
+ const reason = reasonFor(key, value);
7095
+ redacted.push({ varName: key, line: i + 1, reason });
7096
+ out.push(`${prefix}${key}${eq}${quote}{{${key}}}${quote}`);
7097
+ continue;
7098
+ }
7099
+ }
7100
+ out.push(line);
7101
+ }
7102
+ return { content: out.join(`
7103
+ `), redacted, isTemplate: redacted.length > 0 };
7104
+ }
7105
+ function redactJson(content) {
7106
+ const redacted = [];
7107
+ const lines = content.split(`
7108
+ `);
7109
+ const out = [];
7110
+ for (let i = 0;i < lines.length; i++) {
7111
+ const line = lines[i];
7112
+ const m = line.match(/^(\s*"([^"]+)"\s*:\s*)"([^"]+)"(,?)(\s*)$/);
7113
+ if (m) {
7114
+ const [, prefix, key, value, comma, trail] = m;
7115
+ if (shouldRedactKeyValue(key, value)) {
7116
+ const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
7117
+ redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
7118
+ out.push(`${prefix}"{{${varName}}}"${comma}${trail}`);
7119
+ continue;
7120
+ }
7121
+ }
7122
+ let newLine = line;
7123
+ for (const { re, reason } of VALUE_PATTERNS) {
7124
+ newLine = newLine.replace(re, (match) => {
7125
+ const varName = `REDACTED_${reason.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
7126
+ redacted.push({ varName, line: i + 1, reason });
7127
+ return `{{${varName}}}`;
7128
+ });
7129
+ }
7130
+ out.push(newLine);
7131
+ }
7132
+ return { content: out.join(`
7133
+ `), redacted, isTemplate: redacted.length > 0 };
7134
+ }
7135
+ function redactToml(content) {
7136
+ const redacted = [];
7137
+ const lines = content.split(`
7138
+ `);
7139
+ const out = [];
7140
+ for (let i = 0;i < lines.length; i++) {
7141
+ const line = lines[i];
7142
+ const m = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9_\-]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
7143
+ if (m) {
7144
+ const [, indent, key, eq, quote, value] = m;
7145
+ if (shouldRedactKeyValue(key, value)) {
7146
+ const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
7147
+ redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
7148
+ out.push(`${indent}${key}${eq}${quote}{{${varName}}}${quote}`);
7149
+ continue;
7150
+ }
7151
+ }
7152
+ out.push(line);
7153
+ }
7154
+ return { content: out.join(`
7155
+ `), redacted, isTemplate: redacted.length > 0 };
7156
+ }
7157
+ function redactIni(content) {
7082
7158
  const redacted = [];
7083
7159
  const lines = content.split(`
7084
7160
  `);
@@ -7189,7 +7265,7 @@ var init_redact = __esm(() => {
7189
7265
  VALUE_PATTERNS = [
7190
7266
  { re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
7191
7267
  { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
7192
- { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
7268
+ { re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
7193
7269
  { re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
7194
7270
  { re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
7195
7271
  { re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
@@ -7239,7 +7315,7 @@ import {
7239
7315
  statSync,
7240
7316
  writeFileSync
7241
7317
  } from "fs";
7242
- import { basename, dirname, isAbsolute, join as join4, parse, relative, resolve } from "path";
7318
+ import { basename, dirname, isAbsolute, join as join5, parse, relative, resolve as resolve2 } from "path";
7243
7319
  function managedObservationMaxBytes(relativePath) {
7244
7320
  return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
7245
7321
  }
@@ -7410,7 +7486,7 @@ function composeProjectContextSessionRender(input) {
7410
7486
  if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
7411
7487
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
7412
7488
  }
7413
- const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve(file.path) === paths.target);
7489
+ const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve2(file.path) === paths.target);
7414
7490
  if (plannedIndexes.length !== 1) {
7415
7491
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
7416
7492
  }
@@ -7468,7 +7544,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
7468
7544
  verify();
7469
7545
  return action(null);
7470
7546
  }
7471
- const lockPath = resolve(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7547
+ const lockPath = resolve2(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7472
7548
  const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
7473
7549
  try {
7474
7550
  verify();
@@ -7494,7 +7570,7 @@ function validateProjectContextSessionGuard(guard) {
7494
7570
  if (!isRecord(observed) || typeof observed.path !== "string") {
7495
7571
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
7496
7572
  }
7497
- const path = resolve(observed.path);
7573
+ const path = resolve2(observed.path);
7498
7574
  if (!allowedPaths.has(path) || observedPaths.has(path)) {
7499
7575
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
7500
7576
  }
@@ -7516,7 +7592,7 @@ function validateProjectContextSessionGuard(guard) {
7516
7592
  function applyProjectContext(options) {
7517
7593
  const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
7518
7594
  const now3 = options.now ?? new Date;
7519
- const lockPath = resolve(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7595
+ const lockPath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7520
7596
  const lock = options.dry_run ? null : acquireWorkspaceLock(workspaceRoot, lockPath, options.test_hooks?.after_lock_open, options.test_hooks?.before_stale_lock_remove, options.test_hooks?.process_start_identity);
7521
7597
  try {
7522
7598
  const resolved = resolveBundleForApply(options, workspaceRoot, now3);
@@ -7663,7 +7739,7 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
7663
7739
  if (!options.expected_project_id) {
7664
7740
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
7665
7741
  }
7666
- const cachePath = resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
7742
+ const cachePath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
7667
7743
  const cache = readProjectContextCache(cachePath, workspaceRoot);
7668
7744
  if (!cache)
7669
7745
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
@@ -8019,7 +8095,7 @@ function buildManifest(plan, now3) {
8019
8095
  function buildSessionCompatibilityManifest(plan, now3) {
8020
8096
  const paths = runtimePaths(plan.workspace_root, plan.runtime);
8021
8097
  const tool = manifestTool(plan.runtime);
8022
- const targetHome = plan.runtime === "codewith" ? resolve(plan.workspace_root, ".codewith") : plan.workspace_root;
8098
+ const targetHome = plan.runtime === "codewith" ? resolve2(plan.workspace_root, ".codewith") : plan.workspace_root;
8023
8099
  const targetRelativePath = sessionTargetRelativePath(plan.runtime);
8024
8100
  const existing = existsSync4(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
8025
8101
  schema: SESSION_RENDER_SCHEMA,
@@ -8041,7 +8117,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
8041
8117
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
8042
8118
  }
8043
8119
  const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
8044
- if (existingTargetHome !== null && resolve(existingTargetHome) !== targetHome) {
8120
+ if (existingTargetHome !== null && resolve2(existingTargetHome) !== targetHome) {
8045
8121
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
8046
8122
  }
8047
8123
  const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
@@ -8327,9 +8403,9 @@ function writeMetadataSnapshot(plan, now3) {
8327
8403
  const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
8328
8404
  if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
8329
8405
  return null;
8330
- const snapshotDir = resolve(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8406
+ const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8331
8407
  ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8332
- const snapshotPath = resolve(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8408
+ const snapshotPath = resolve2(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8333
8409
  const snapshot = {
8334
8410
  schema: "hasna.configs.session-render-snapshot/v1",
8335
8411
  kind: "project-context-metadata",
@@ -8345,8 +8421,8 @@ function writeMetadataSnapshot(plan, now3) {
8345
8421
  return snapshotPath;
8346
8422
  }
8347
8423
  function metadataSnapshotMatchesManifest(plan, manifest) {
8348
- const snapshotDir = resolve(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8349
- const snapshotPath = resolve(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8424
+ const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8425
+ const snapshotPath = resolve2(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8350
8426
  if (!existsSync4(snapshotPath))
8351
8427
  return false;
8352
8428
  const record = readJsonRecord(snapshotPath, plan.workspace_root);
@@ -8395,10 +8471,10 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
8395
8471
  sha256: nextHash
8396
8472
  };
8397
8473
  });
8398
- const snapshotDir = resolve(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
8474
+ const snapshotDir = resolve2(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
8399
8475
  ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8400
8476
  const timestamp = now3.toISOString().replace(/[:.]/g, "-");
8401
- const snapshotPath = resolve(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8477
+ const snapshotPath = resolve2(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8402
8478
  const snapshot = {
8403
8479
  schema: "hasna.configs.session-render-snapshot/v2",
8404
8480
  createdAt: now3.toISOString(),
@@ -8463,7 +8539,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
8463
8539
  }
8464
8540
  }
8465
8541
  function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
8466
- const dir = resolve(path, "..");
8542
+ const dir = resolve2(path, "..");
8467
8543
  ensureSafeDirectory(dir, workspaceRoot, 448);
8468
8544
  assertNoSymlinkSegments(workspaceRoot, path);
8469
8545
  const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
@@ -8480,7 +8556,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
8480
8556
  const previous = anchoredFileObservation(directory, targetName);
8481
8557
  const previousMode = previous?.mode ?? defaultMode;
8482
8558
  const tempName = `.project-context-${randomUUID5()}.tmp`;
8483
- const tempPath = join4(dir, tempName);
8559
+ const tempPath = join5(dir, tempName);
8484
8560
  let fd = null;
8485
8561
  let preserveTemp = false;
8486
8562
  let directoryChanged = false;
@@ -8611,7 +8687,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
8611
8687
  }
8612
8688
  const dir = dirname(path);
8613
8689
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8614
- const tempPath = join4(dir, `.project-context-${randomUUID5()}.tmp`);
8690
+ const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
8615
8691
  let fd = null;
8616
8692
  let tempIdentity = null;
8617
8693
  try {
@@ -8668,7 +8744,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
8668
8744
  }
8669
8745
  const dir = dirname(path);
8670
8746
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8671
- const tempPath = join4(dir, `.project-context-${randomUUID5()}.tmp`);
8747
+ const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
8672
8748
  const desiredHash = sha2562(content);
8673
8749
  let fd = null;
8674
8750
  let tempIdentity = null;
@@ -8737,11 +8813,11 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
8737
8813
  return createHash2("sha256").update(readFileSync(path)).digest("hex");
8738
8814
  }
8739
8815
  function writeProjectContextCoordinatedFile(input) {
8740
- atomicWriteFile(resolve(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);
8816
+ atomicWriteFile(resolve2(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);
8741
8817
  }
8742
8818
  function removeProjectContextCoordinatedFile(input) {
8743
8819
  const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
8744
- const path = resolve(input.path);
8820
+ const path = resolve2(input.path);
8745
8821
  assertNoSymlinkSegments(workspaceRoot, path);
8746
8822
  const dir = dirname(path);
8747
8823
  const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
@@ -8767,7 +8843,7 @@ function removeProjectContextCoordinatedFile(input) {
8767
8843
  throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
8768
8844
  }
8769
8845
  displaced = true;
8770
- input.test_hooks?.after_displace?.(join4(dir, displacedName));
8846
+ input.test_hooks?.after_displace?.(join5(dir, displacedName));
8771
8847
  const moved = anchoredFileObservation(directory, displacedName);
8772
8848
  if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
8773
8849
  throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
@@ -8813,7 +8889,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
8813
8889
  }
8814
8890
  const dir = dirname(path);
8815
8891
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8816
- const displacedPath = join4(dir, `.project-context-delete-${randomUUID5()}.tmp`);
8892
+ const displacedPath = join5(dir, `.project-context-delete-${randomUUID5()}.tmp`);
8817
8893
  let displaced = false;
8818
8894
  try {
8819
8895
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -8884,7 +8960,7 @@ function anchoredOpenExclusive(directory, name, mode) {
8884
8960
  const requestedMode = mode & 4095;
8885
8961
  let fd;
8886
8962
  try {
8887
- fd = openSync(join4(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
8963
+ fd = openSync(join5(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
8888
8964
  } catch {
8889
8965
  throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
8890
8966
  }
@@ -8927,7 +9003,7 @@ function anchoredFileObservation(directory, name) {
8927
9003
  const stat = fstatSync(fd);
8928
9004
  if (!stat.isFile())
8929
9005
  throw new ProjectContextHashRace("managed output is not a regular file");
8930
- const relativePath = relativePosix(directory.workspaceRoot, join4(directory.path, name));
9006
+ const relativePath = relativePosix(directory.workspaceRoot, join5(directory.path, name));
8931
9007
  const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
8932
9008
  if (maxBytes !== null && stat.size > maxBytes) {
8933
9009
  throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
@@ -8953,7 +9029,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
8953
9029
  return observed;
8954
9030
  }
8955
9031
  function captureManagedDirectoryIdentity(path, workspaceRoot) {
8956
- assertNoSymlinkSegments(workspaceRoot, join4(path, ".project-context-directory-guard"));
9032
+ assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
8957
9033
  let stat;
8958
9034
  try {
8959
9035
  stat = lstatSync(path);
@@ -8966,7 +9042,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
8966
9042
  return { dev: stat.dev, ino: stat.ino };
8967
9043
  }
8968
9044
  function assertManagedDirectoryStable(path, workspaceRoot, expected) {
8969
- assertNoSymlinkSegments(workspaceRoot, join4(path, ".project-context-directory-guard"));
9045
+ assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
8970
9046
  let current;
8971
9047
  try {
8972
9048
  current = lstatSync(path);
@@ -9099,10 +9175,10 @@ function resolveAnchoredFsOps() {
9099
9175
  return null;
9100
9176
  }
9101
9177
  function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
9102
- const lockDirectory = resolve(lockPath, "..");
9178
+ const lockDirectory = resolve2(lockPath, "..");
9103
9179
  ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
9104
9180
  assertNoSymlinkSegments(workspaceRoot, lockPath);
9105
- const tempPath = join4(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9181
+ const tempPath = join5(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9106
9182
  let fd = null;
9107
9183
  let openedIdentity = null;
9108
9184
  let openedContentHash = null;
@@ -9174,7 +9250,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
9174
9250
  if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
9175
9251
  return;
9176
9252
  rmSync2(lockPath);
9177
- fsyncDirectory(resolve(lockPath, ".."));
9253
+ fsyncDirectory(resolve2(lockPath, ".."));
9178
9254
  } catch {}
9179
9255
  }
9180
9256
  function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
@@ -9253,7 +9329,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
9253
9329
  throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
9254
9330
  }
9255
9331
  rmSync2(candidatePath);
9256
- fsyncDirectory(resolve(lockPath, ".."));
9332
+ fsyncDirectory(resolve2(lockPath, ".."));
9257
9333
  exchanged = false;
9258
9334
  return true;
9259
9335
  } catch (error) {
@@ -9332,8 +9408,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9332
9408
  }
9333
9409
  return;
9334
9410
  }
9335
- const lockDirectory = resolve(lockPath, "..");
9336
- const releasePath = join4(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9411
+ const lockDirectory = resolve2(lockPath, "..");
9412
+ const releasePath = join5(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9337
9413
  let releaseFd = null;
9338
9414
  let releaseIdentity = null;
9339
9415
  let releaseHash = null;
@@ -9413,7 +9489,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9413
9489
  const segments = rel.split(/[\\/]+/).filter(Boolean);
9414
9490
  let current = workspaceRoot;
9415
9491
  for (const segment of segments) {
9416
- current = join4(current, segment);
9492
+ current = join5(current, segment);
9417
9493
  if (existsSync4(current)) {
9418
9494
  if (lstatSync(current).isSymbolicLink())
9419
9495
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
@@ -9421,7 +9497,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9421
9497
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
9422
9498
  } else {
9423
9499
  mkdirSync2(current, { mode });
9424
- fsyncDirectory(resolve(current, ".."));
9500
+ fsyncDirectory(resolve2(current, ".."));
9425
9501
  }
9426
9502
  }
9427
9503
  }
@@ -9477,11 +9553,11 @@ function scanGeneratedContent(content) {
9477
9553
  function runtimePaths(workspaceRoot, runtime) {
9478
9554
  const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
9479
9555
  return {
9480
- target: resolve(workspaceRoot, ...relativeTarget.split("/")),
9481
- fragment: resolve(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9482
- manifest: resolve(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9483
- cache: resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9484
- sessionManifest: runtime === "codewith" ? resolve(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve(workspaceRoot, ".hasna", "session-render-manifest.json")
9556
+ target: resolve2(workspaceRoot, ...relativeTarget.split("/")),
9557
+ fragment: resolve2(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9558
+ manifest: resolve2(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9559
+ cache: resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9560
+ sessionManifest: runtime === "codewith" ? resolve2(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve2(workspaceRoot, ".hasna", "session-render-manifest.json")
9485
9561
  };
9486
9562
  }
9487
9563
  function projectContextSessionGuardPaths(paths, runtime) {
@@ -9491,7 +9567,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
9491
9567
  paths.fragment,
9492
9568
  paths.target,
9493
9569
  paths.sessionManifest,
9494
- ...runtime === "codewith" ? [resolve(paths.target, "..", "CODEWITH.override.md")] : []
9570
+ ...runtime === "codewith" ? [resolve2(paths.target, "..", "CODEWITH.override.md")] : []
9495
9571
  ];
9496
9572
  }
9497
9573
  function sessionTargetRelativePath(runtime) {
@@ -9511,12 +9587,12 @@ function projectContextRuntimeForSessionTool(tool) {
9511
9587
  return null;
9512
9588
  }
9513
9589
  function projectContextWorkspaceForSession(input, runtime) {
9514
- const targetHome = resolve(input.target_home);
9590
+ const targetHome = resolve2(input.target_home);
9515
9591
  if (runtime === "codewith") {
9516
9592
  const workspaceRoot = basename(targetHome) === ".codewith" ? dirname(targetHome) : null;
9517
9593
  if (!workspaceRoot)
9518
9594
  return null;
9519
- if (input.project_root && resolve(input.project_root) !== workspaceRoot) {
9595
+ if (input.project_root && resolve2(input.project_root) !== workspaceRoot) {
9520
9596
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
9521
9597
  }
9522
9598
  if (!existsSync4(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
@@ -9530,7 +9606,7 @@ function projectContextWorkspaceForSession(input, runtime) {
9530
9606
  function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9531
9607
  if (runtime !== "codewith")
9532
9608
  return;
9533
- const override = resolve(workspaceRoot, ".codewith", "CODEWITH.override.md");
9609
+ const override = resolve2(workspaceRoot, ".codewith", "CODEWITH.override.md");
9534
9610
  if (!existsSync4(override))
9535
9611
  return;
9536
9612
  assertNoSymlinkSegments(workspaceRoot, override);
@@ -9541,7 +9617,7 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9541
9617
  function assertSafeWorkspaceRoot(path) {
9542
9618
  if (!isAbsolute(path))
9543
9619
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
9544
- const normalized = resolve(path);
9620
+ const normalized = resolve2(path);
9545
9621
  if (normalized === parse(normalized).root)
9546
9622
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
9547
9623
  if (!existsSync4(normalized) || !lstatSync(normalized).isDirectory())
@@ -9558,17 +9634,17 @@ function assertNoSymlinkSegments(root, target) {
9558
9634
  }
9559
9635
  let current = root;
9560
9636
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
9561
- current = join4(current, segment);
9637
+ current = join5(current, segment);
9562
9638
  if (existsSync4(current) && lstatSync(current).isSymbolicLink()) {
9563
9639
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
9564
9640
  }
9565
9641
  }
9566
9642
  }
9567
9643
  function assertNoSymlinkAncestors(path) {
9568
- const normalized = resolve(path);
9644
+ const normalized = resolve2(path);
9569
9645
  let current = parse(normalized).root;
9570
9646
  for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
9571
- current = join4(current, segment);
9647
+ current = join5(current, segment);
9572
9648
  if (!existsSync4(current))
9573
9649
  return;
9574
9650
  if (lstatSync(current).isSymbolicLink())
@@ -9604,10 +9680,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
9604
9680
  }
9605
9681
  function durableSourcePath(path, workspaceRoot) {
9606
9682
  if (!path || path.startsWith("/dev/fd/"))
9607
- return resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9608
- const normalized = isAbsolute(path) ? resolve(path) : resolve(workspaceRoot, path);
9683
+ return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9684
+ const normalized = isAbsolute(path) ? resolve2(path) : resolve2(workspaceRoot, path);
9609
9685
  if (normalized.startsWith("/dev/fd/"))
9610
- return resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9686
+ return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9611
9687
  return normalized;
9612
9688
  }
9613
9689
  function compareRevisions(incoming, previous) {
@@ -10167,7 +10243,7 @@ function compareProviderVersions(left, right) {
10167
10243
 
10168
10244
  // src/lib/asset-plan.ts
10169
10245
  import { createHash as createHash3 } from "crypto";
10170
- import { isAbsolute as isAbsolute2, posix, resolve as resolve2 } from "path";
10246
+ import { isAbsolute as isAbsolute2, posix, resolve as resolve3 } from "path";
10171
10247
  function assetCapability(provider, surface, kind, support, strategies, note, providerVersionRange = "*") {
10172
10248
  return Object.freeze({
10173
10249
  schema: ASSET_CAPABILITY_SCHEMA,
@@ -10374,8 +10450,8 @@ function resolveAssetDestination(item, roots) {
10374
10450
  if (!isAbsolute2(root))
10375
10451
  throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
10376
10452
  const relativePath = safeRelativePath(item.destination.relativePath);
10377
- const target = resolve2(root, ...relativePath.split("/"));
10378
- const normalizedRoot = resolve2(root);
10453
+ const target = resolve3(root, ...relativePath.split("/"));
10454
+ const normalizedRoot = resolve3(root);
10379
10455
  if (target === normalizedRoot)
10380
10456
  throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
10381
10457
  if (!target.startsWith(`${normalizedRoot}/`))
@@ -10555,13 +10631,13 @@ var init_asset_plan = __esm(() => {
10555
10631
  // src/lib/cursor-authority.ts
10556
10632
  import { createHash as createHash4 } from "crypto";
10557
10633
  import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
10558
- import { homedir as homedir3 } from "os";
10559
- import { join as join5, resolve as resolve3 } from "path";
10634
+ import { homedir as homedir4 } from "os";
10635
+ import { join as join6, resolve as resolve4 } from "path";
10560
10636
  function sha2564(content) {
10561
10637
  return createHash4("sha256").update(content).digest("hex");
10562
10638
  }
10563
10639
  function homeDir() {
10564
- return process.env["HOME"] || homedir3();
10640
+ return process.env["HOME"] || homedir4();
10565
10641
  }
10566
10642
  function markerPayload(content, markerLine, markerIndex) {
10567
10643
  const index = markerIndex ?? content.indexOf(markerLine);
@@ -10577,12 +10653,12 @@ function baseObservation(path) {
10577
10653
  };
10578
10654
  }
10579
10655
  function observeCursorGlobalAuthority(options = {}) {
10580
- const authorityPath = resolve3(join5(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10656
+ const authorityPath = resolve4(join6(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10581
10657
  const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
10582
10658
  return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
10583
10659
  }
10584
10660
  function observeCursorGlobalAuthorityAtPath(authorityPath) {
10585
- return observeCursorGlobalAuthorityPath(resolve3(authorityPath), (path) => readFileSync2(path, "utf8"));
10661
+ return observeCursorGlobalAuthorityPath(resolve4(authorityPath), (path) => readFileSync2(path, "utf8"));
10586
10662
  }
10587
10663
  function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10588
10664
  const base = baseObservation(authorityPath);
@@ -10727,7 +10803,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10727
10803
  };
10728
10804
  }
10729
10805
  function isCursorGlobalAuthorityPath(path) {
10730
- return resolve3(path) === resolve3(join5(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10806
+ return resolve4(path) === resolve4(join6(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10731
10807
  }
10732
10808
  function stampCursorGlobalAuthorityMarker(content) {
10733
10809
  if (CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN.test(content))
@@ -10770,11 +10846,88 @@ var init_cursor_authority = __esm(() => {
10770
10846
  CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
10771
10847
  });
10772
10848
 
10773
- // src/lib/session-render.ts
10849
+ // src/lib/session-authority.ts
10774
10850
  import { createHash as createHash5 } from "crypto";
10775
- import { existsSync as existsSync5, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
10776
- import { homedir as homedir4 } from "os";
10777
- import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute3, join as join6, parse as parse2, posix as posix2, relative as relative2, resolve as resolve4 } from "path";
10851
+ import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
10852
+ import { join as join7, resolve as resolve5 } from "path";
10853
+ function sha2565(content) {
10854
+ return createHash5("sha256").update(content).digest("hex");
10855
+ }
10856
+ function detectClaudeAuthorityConflicts(targetHome) {
10857
+ const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
10858
+ let stat;
10859
+ try {
10860
+ stat = lstatSync3(authorityPath);
10861
+ } catch {
10862
+ return [];
10863
+ }
10864
+ const provenanceBase = {
10865
+ tool: "claude",
10866
+ relativePath: CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH,
10867
+ path: authorityPath
10868
+ };
10869
+ if (stat.isSymbolicLink() || !stat.isFile()) {
10870
+ return [{
10871
+ ...provenanceBase,
10872
+ kind: "invalid-unmanaged-authority",
10873
+ sha256: null,
10874
+ markers: [],
10875
+ provenance: {
10876
+ source: "filesystem",
10877
+ authority: "unmanaged",
10878
+ observedPath: authorityPath,
10879
+ detection: "non-regular-file"
10880
+ },
10881
+ reason: "Claude target contains unmanaged AGENTS.md that is not a regular file; authority cannot be verified safely."
10882
+ }];
10883
+ }
10884
+ if (statSync2(authorityPath).size > CLAUDE_LEGACY_AUTHORITY_MAX_BYTES) {
10885
+ return [{
10886
+ ...provenanceBase,
10887
+ kind: "invalid-unmanaged-authority",
10888
+ sha256: null,
10889
+ markers: [],
10890
+ provenance: {
10891
+ source: "filesystem",
10892
+ authority: "unmanaged",
10893
+ observedPath: authorityPath,
10894
+ detection: "oversized-file"
10895
+ },
10896
+ reason: `Claude target contains unmanaged AGENTS.md larger than ${CLAUDE_LEGACY_AUTHORITY_MAX_BYTES} bytes; authority cannot be classified safely.`
10897
+ }];
10898
+ }
10899
+ const content = readFileSync3(authorityPath, "utf8");
10900
+ const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
10901
+ const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
10902
+ return [{
10903
+ ...provenanceBase,
10904
+ kind: knownLegacy ? "known-legacy-no-worktree" : "unknown-unmanaged-authority",
10905
+ sha256: sha2565(content),
10906
+ markers,
10907
+ provenance: {
10908
+ source: "filesystem",
10909
+ authority: "unmanaged",
10910
+ observedPath: authorityPath,
10911
+ detection: knownLegacy ? "known-legacy-markers" : "unknown-content"
10912
+ },
10913
+ reason: knownLegacy ? "Claude target contains unmanaged legacy AGENTS.md with no-worktree directives; migrate or remove it through an owned authority path before applying." : "Claude target contains unmanaged AGENTS.md with unknown authority content; refusing to guess whether it conflicts with the managed Claude render."
10914
+ }];
10915
+ }
10916
+ var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md", CLAUDE_LEGACY_AUTHORITY_MAX_BYTES, CLAUDE_LEGACY_MARKERS;
10917
+ var init_session_authority = __esm(() => {
10918
+ CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
10919
+ CLAUDE_LEGACY_MARKERS = [
10920
+ { id: "claude-agent-rules-heading", pattern: /^# Agent Rules \(Claude\)/m },
10921
+ { id: "no-worktrees-heading", pattern: /^## No Worktrees/m },
10922
+ { id: "no-worktrees-directive", pattern: /\bNever use git worktrees\b/m }
10923
+ ];
10924
+ });
10925
+
10926
+ // src/lib/session-render.ts
10927
+ import { createHash as createHash6 } from "crypto";
10928
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "fs";
10929
+ import { homedir as homedir5 } from "os";
10930
+ import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute3, join as join8, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
10778
10931
  function normalizeSessionInstructionLayer(value) {
10779
10932
  if (value === "provider")
10780
10933
  return "tool";
@@ -10791,11 +10944,11 @@ function ensureTrailingNewline3(content) {
10791
10944
  `) ? content : `${content}
10792
10945
  `;
10793
10946
  }
10794
- function sha2565(content) {
10795
- return createHash5("sha256").update(content).digest("hex");
10947
+ function sha2566(content) {
10948
+ return createHash6("sha256").update(content).digest("hex");
10796
10949
  }
10797
10950
  function fingerprint(value) {
10798
- return sha2565(JSON.stringify(value));
10951
+ return sha2566(JSON.stringify(value));
10799
10952
  }
10800
10953
  function canonicalFingerprintValue(value) {
10801
10954
  if (Array.isArray(value))
@@ -10813,7 +10966,7 @@ function ruleAttestation(rule) {
10813
10966
  };
10814
10967
  const applied = metadata["payloadFloorApplied"];
10815
10968
  return {
10816
- contentSha256: sha2565(rule.content ?? ""),
10969
+ contentSha256: sha2566(rule.content ?? ""),
10817
10970
  payloadFloorApplied: typeof applied === "boolean" ? applied : null,
10818
10971
  flooredFromRulesVersion: read("flooredFromRulesVersion"),
10819
10972
  flooredFromPayloadSha256: read("flooredFromPayloadSha256"),
@@ -10859,16 +11012,14 @@ function slug(value) {
10859
11012
  function yamlQuote2(value) {
10860
11013
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
10861
11014
  }
10862
- function getRawStoreRoot() {
10863
- return resolve4(process.env[RAW_STORE_ROOT_ENV] || join6(process.env["HOME"] || homedir4(), ".hasna", "configs"));
10864
- }
10865
11015
  function defaultTargetHome(tool, profile, sessionId) {
10866
- return join6(getRawStoreRoot(), "sessions", tool, slug(profile), slug(sessionId || "latest"));
11016
+ const home = process.env["HOME"] || homedir5();
11017
+ return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
10867
11018
  }
10868
11019
  function joinTarget(targetHome, relativePath) {
10869
11020
  const safeTargetHome = assertSafeTargetRoot(targetHome);
10870
11021
  const safeRelativePath2 = assertSafeRelativePath(relativePath);
10871
- return join6(safeTargetHome, ...safeRelativePath2.split("/"));
11022
+ return join8(safeTargetHome, ...safeRelativePath2.split("/"));
10872
11023
  }
10873
11024
  function makeFile(targetHome, relativePath, role, content, sourceIds) {
10874
11025
  const safeTargetHome = assertSafeTargetRoot(targetHome);
@@ -10879,7 +11030,7 @@ function makeFile(targetHome, relativePath, role, content, sourceIds) {
10879
11030
  relativePath: safeRelativePath2,
10880
11031
  role,
10881
11032
  content: normalizedContent,
10882
- sha256: sha2565(normalizedContent),
11033
+ sha256: sha2566(normalizedContent),
10883
11034
  sourceIds
10884
11035
  };
10885
11036
  }
@@ -10913,7 +11064,7 @@ function applyAgentOperatingRulesFloor(source, content) {
10913
11064
  const floored = {
10914
11065
  payloadFloorApplied: true,
10915
11066
  flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
10916
- flooredFromPayloadSha256: sha2565(content)
11067
+ flooredFromPayloadSha256: sha2566(content)
10917
11068
  };
10918
11069
  return {
10919
11070
  content: payload.content,
@@ -10935,7 +11086,7 @@ function applyAgentOperatingRulesFloorToRule(source, rule, content) {
10935
11086
  const floored = {
10936
11087
  payloadFloorApplied: true,
10937
11088
  flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
10938
- flooredFromPayloadSha256: sha2565(content)
11089
+ flooredFromPayloadSha256: sha2566(content)
10939
11090
  };
10940
11091
  return {
10941
11092
  content: payload.content,
@@ -10954,7 +11105,7 @@ function skippedSource(source, reason) {
10954
11105
  order: source.resolvedOrder,
10955
11106
  path: source.path ?? null,
10956
11107
  hash: source.hash ?? null,
10957
- renderedPayloadSha256: sha2565(source.content),
11108
+ renderedPayloadSha256: sha2566(source.content),
10958
11109
  nonOverridable: source.nonOverridable === true,
10959
11110
  provenance: source.provenance ?? null
10960
11111
  }
@@ -10996,7 +11147,7 @@ function compareSessionInstructionSources(a, b) {
10996
11147
  return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
10997
11148
  }
10998
11149
  function semanticPolicyIntegrity(body) {
10999
- return sha2565(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
11150
+ return sha2566(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
11000
11151
  }
11001
11152
  function semanticPolicyDeclaration(source) {
11002
11153
  const normalize = (value) => value.replace(/\r\n/g, `
@@ -11221,7 +11372,7 @@ function composeSources(sources, tool) {
11221
11372
  targetSourceId: target.id,
11222
11373
  targetNormalizedSourceId: target.normalizedId,
11223
11374
  targetHash: target.hash ?? null,
11224
- targetRenderedPayloadSha256: sha2565(target.content),
11375
+ targetRenderedPayloadSha256: sha2566(target.content),
11225
11376
  targetNonOverridable: protectedReplacement,
11226
11377
  authority: protectedReplacement ? "canonical-identity-export/codewith-provider/v1" : "overridable-source/v1"
11227
11378
  }
@@ -11403,7 +11554,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
11403
11554
  ...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
11404
11555
  ]);
11405
11556
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
11406
- const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync3(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11557
+ const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11407
11558
  const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
11408
11559
  const config = {
11409
11560
  ...selectedConfig,
@@ -11552,7 +11703,7 @@ function buildAssetFiles(input, targetHome, blocked) {
11552
11703
  relativePath: assertSafeRelativePath(relativePath),
11553
11704
  role: "asset",
11554
11705
  content,
11555
- sha256: sha2565(content),
11706
+ sha256: sha2566(content),
11556
11707
  sourceIds: [item.sourceConfigId, item.assetId]
11557
11708
  };
11558
11709
  });
@@ -11592,7 +11743,7 @@ function adapterFor(input) {
11592
11743
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
11593
11744
  }
11594
11745
  function getHomeDir() {
11595
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir4();
11746
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
11596
11747
  }
11597
11748
  function cleanSessionPathInput(path) {
11598
11749
  const trimmed = path.trim();
@@ -11607,16 +11758,16 @@ function resolveSessionPath(path) {
11607
11758
  throw new Error("Session render path cannot be empty.");
11608
11759
  const home = getHomeDir();
11609
11760
  if (cleaned === "~")
11610
- return resolve4(home);
11761
+ return resolve6(home);
11611
11762
  if (cleaned.startsWith("~/"))
11612
- return resolve4(home, cleaned.slice(2));
11763
+ return resolve6(home, cleaned.slice(2));
11613
11764
  if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
11614
- return resolve4(home);
11765
+ return resolve6(home);
11615
11766
  if (cleaned.startsWith("{{HOME}}/"))
11616
- return resolve4(home, cleaned.slice("{{HOME}}/".length));
11767
+ return resolve6(home, cleaned.slice("{{HOME}}/".length));
11617
11768
  if (cleaned.startsWith("${HOME}/"))
11618
- return resolve4(home, cleaned.slice("${HOME}/".length));
11619
- return resolve4(cleaned);
11769
+ return resolve6(home, cleaned.slice("${HOME}/".length));
11770
+ return resolve6(cleaned);
11620
11771
  }
11621
11772
  function assertSafeRelativePath(relativePath) {
11622
11773
  if (!relativePath.trim())
@@ -11632,7 +11783,7 @@ function assertSafeRelativePath(relativePath) {
11632
11783
  function assertSafeTargetRoot(targetHome) {
11633
11784
  if (!isAbsolute3(targetHome))
11634
11785
  throw new Error(`Session render target must be an absolute path: ${targetHome}`);
11635
- const normalized = resolve4(targetHome);
11786
+ const normalized = resolve6(targetHome);
11636
11787
  if (normalized === parse2(normalized).root) {
11637
11788
  throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
11638
11789
  }
@@ -11730,7 +11881,7 @@ function planSessionRender(input) {
11730
11881
  blockers: targetBlockers
11731
11882
  } = resolveRenderTarget(input);
11732
11883
  const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
11733
- const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : [];
11884
+ const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
11734
11885
  const blockers = [
11735
11886
  ...targetBlockers,
11736
11887
  ...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
@@ -11828,7 +11979,7 @@ function planSessionRender(input) {
11828
11979
  hash: rule.hash ?? null,
11829
11980
  ...ruleAttestation(rule)
11830
11981
  })),
11831
- renderedPayloadSha256: sha2565(source.content),
11982
+ renderedPayloadSha256: sha2566(source.content),
11832
11983
  provenance: source.provenance ?? null,
11833
11984
  metadata: source.metadata ?? null
11834
11985
  })),
@@ -11866,8 +12017,8 @@ function planSessionRender(input) {
11866
12017
  ...input.providerConfig ? {
11867
12018
  providerConfig: {
11868
12019
  sourceId: input.providerConfig.sourceId,
11869
- selectedPayloadSha256: sha2565(input.providerConfig.content),
11870
- renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2565(input.providerConfig.content),
12020
+ selectedPayloadSha256: sha2566(input.providerConfig.content),
12021
+ renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
11871
12022
  selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
11872
12023
  }
11873
12024
  } : {},
@@ -12153,7 +12304,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12153
12304
  }
12154
12305
  return;
12155
12306
  }
12156
- const stat = statSync2(resolvedPath);
12307
+ const stat = statSync3(resolvedPath);
12157
12308
  if (!stat.isFile()) {
12158
12309
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
12159
12310
  }
@@ -12162,7 +12313,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12162
12313
  if (!pathIsInside(realPath, realBase)) {
12163
12314
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
12164
12315
  }
12165
- return readFileSync3(realPath, "utf-8");
12316
+ return readFileSync4(realPath, "utf-8");
12166
12317
  }
12167
12318
  function resolveIdentitySourcePath(path, baseDir, sourceId) {
12168
12319
  const cleaned = cleanSessionPathInput(path);
@@ -12170,8 +12321,8 @@ function resolveIdentitySourcePath(path, baseDir, sourceId) {
12170
12321
  throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
12171
12322
  if (cleaned.includes("\\"))
12172
12323
  throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
12173
- const resolvedPath = isAbsolute3(cleaned) ? resolve4(cleaned) : resolve4(baseDir, cleaned);
12174
- if (!pathIsInside(resolvedPath, resolve4(baseDir))) {
12324
+ const resolvedPath = isAbsolute3(cleaned) ? resolve6(cleaned) : resolve6(baseDir, cleaned);
12325
+ if (!pathIsInside(resolvedPath, resolve6(baseDir))) {
12175
12326
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
12176
12327
  }
12177
12328
  return resolvedPath;
@@ -12259,7 +12410,7 @@ function asStringArray(value) {
12259
12410
  return [];
12260
12411
  return value.filter((item) => typeof item === "string");
12261
12412
  }
12262
- var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME", ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, IDENTITY_EXPORT_AUTHORITY, CODEWITH_PROTECTED_REPLACEMENT_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CANONICAL_IDENTITY_EXPORT_PACKAGES;
12413
+ var ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, IDENTITY_EXPORT_AUTHORITY, CODEWITH_PROTECTED_REPLACEMENT_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CANONICAL_IDENTITY_EXPORT_PACKAGES;
12263
12414
  var init_session_render = __esm(() => {
12264
12415
  init_global_agent_rules_standard();
12265
12416
  init_codewith_shared_todos_storage_standard();
@@ -12268,8 +12419,11 @@ var init_session_render = __esm(() => {
12268
12419
  init_transforms();
12269
12420
  init_asset_plan();
12270
12421
  init_cursor_authority();
12422
+ init_session_authority();
12271
12423
  init_session_render_contract();
12272
12424
  init_session_render_contract();
12425
+ init_raw_store_root();
12426
+ init_raw_store_root();
12273
12427
  SESSION_RENDER_TOOLS = [
12274
12428
  "claude",
12275
12429
  "codex",
@@ -12470,7 +12624,7 @@ var init_session_render = __esm(() => {
12470
12624
  });
12471
12625
 
12472
12626
  // src/lib/instruction-graph.ts
12473
- import { createHash as createHash6 } from "crypto";
12627
+ import { createHash as createHash7 } from "crypto";
12474
12628
  function capability(provider, providerVersionRange, selectedRepresentation, loadingPath, options = {}) {
12475
12629
  return Object.freeze({
12476
12630
  schema: PROVIDER_CAPABILITY_SCHEMA,
@@ -12717,7 +12871,7 @@ function compileInstructionGraph(input) {
12717
12871
  effective_activation: effective.get(configId),
12718
12872
  fallback: row.binding.fallback,
12719
12873
  required: row.binding.required,
12720
- content_sha256: sha2566(config.content),
12874
+ content_sha256: sha2567(config.content),
12721
12875
  dependencies: dependencies.get(configId) ?? []
12722
12876
  };
12723
12877
  });
@@ -12754,7 +12908,7 @@ function compileInstructionGraph(input) {
12754
12908
  diagnostics
12755
12909
  };
12756
12910
  return {
12757
- plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2566(stableJson2(planWithoutHash)) }),
12911
+ plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2567(stableJson2(planWithoutHash)) }),
12758
12912
  sources,
12759
12913
  capability: capability2
12760
12914
  };
@@ -12928,8 +13082,8 @@ function assertExactOnce(units, artifacts) {
12928
13082
  function errorDiagnostic(configId, code, message) {
12929
13083
  return { severity: "error", code, config_id: configId, message };
12930
13084
  }
12931
- function sha2566(value) {
12932
- return createHash6("sha256").update(value).digest("hex");
13085
+ function sha2567(value) {
13086
+ return createHash7("sha256").update(value).digest("hex");
12933
13087
  }
12934
13088
  function stableJson2(value) {
12935
13089
  const canonical = (entry) => Array.isArray(entry) ? entry.map(canonical) : entry && typeof entry === "object" ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, canonical(child)])) : entry;
@@ -13228,37 +13382,6 @@ var init_profiles = __esm(() => {
13228
13382
  init_asset_plan();
13229
13383
  });
13230
13384
 
13231
- // src/db/snapshots.ts
13232
- function createSnapshot(configId, content, version, db) {
13233
- const d = db || getDatabase();
13234
- const id = uuid();
13235
- const ts = now2();
13236
- d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
13237
- return { id, config_id: configId, content, version, created_at: ts };
13238
- }
13239
- function listSnapshots(configId, db) {
13240
- const d = db || getDatabase();
13241
- return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
13242
- }
13243
- function getSnapshot(id, db) {
13244
- const d = db || getDatabase();
13245
- return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
13246
- }
13247
- function getSnapshotByVersion(configId, version, db) {
13248
- const d = db || getDatabase();
13249
- return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
13250
- }
13251
- function pruneSnapshots(configId, keep = 10, db) {
13252
- const d = db || getDatabase();
13253
- const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
13254
- SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
13255
- )`, [configId, configId, keep]);
13256
- return result.changes;
13257
- }
13258
- var init_snapshots = __esm(() => {
13259
- init_database();
13260
- });
13261
-
13262
13385
  // src/db/machines.ts
13263
13386
  import { arch, hostname, type } from "os";
13264
13387
  function currentHostname2() {
@@ -13350,16 +13473,17 @@ function formatCliError(err, env = process.env) {
13350
13473
  return err instanceof Error ? err.message : String(err);
13351
13474
  }
13352
13475
  function resolveCloudConfig(env = process.env) {
13476
+ assertNoLegacyStorageMode(env);
13353
13477
  const apiUrl = env[API_URL_ENV]?.trim();
13354
13478
  const apiKey = env[API_KEY_ENV]?.trim();
13355
13479
  if (!apiUrl && !apiKey)
13356
13480
  return null;
13357
13481
  if (!apiUrl || !apiKey) {
13358
- 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 cloud API, ` + `or unset both to use the local store.`);
13482
+ 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.`);
13359
13483
  }
13360
13484
  return { apiUrl, apiKey };
13361
13485
  }
13362
- function isCloudMode(env = process.env) {
13486
+ function isApiTransport(env = process.env) {
13363
13487
  return resolveCloudConfig(env) !== null;
13364
13488
  }
13365
13489
 
@@ -13845,6 +13969,7 @@ var init_config_store = __esm(() => {
13845
13969
  init_types();
13846
13970
  init_bounded_read();
13847
13971
  init_instruction_graph();
13972
+ init_retired_storage_mode();
13848
13973
  CloudHttpError = class CloudHttpError extends Error {
13849
13974
  status;
13850
13975
  body;
@@ -13858,8 +13983,8 @@ var init_config_store = __esm(() => {
13858
13983
  });
13859
13984
 
13860
13985
  // src/lib/session-render-ownership.ts
13861
- import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
13862
- import { dirname as dirname3, join as join7, parse as parse3, relative as relative3, sep } from "path";
13986
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
13987
+ import { dirname as dirname3, join as join9, parse as parse3, relative as relative3, sep } from "path";
13863
13988
  function toSegments(absolutePath2) {
13864
13989
  return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
13865
13990
  }
@@ -13880,7 +14005,7 @@ function readManifestRelativePaths(manifestPath) {
13880
14005
  try {
13881
14006
  if (!existsSync6(manifestPath))
13882
14007
  return null;
13883
- stats = statSync3(manifestPath);
14008
+ stats = statSync4(manifestPath);
13884
14009
  } catch {
13885
14010
  return null;
13886
14011
  }
@@ -13890,7 +14015,7 @@ function readManifestRelativePaths(manifestPath) {
13890
14015
  }
13891
14016
  let manifest;
13892
14017
  try {
13893
- manifest = JSON.parse(readFileSync4(manifestPath, "utf-8"));
14018
+ manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
13894
14019
  } catch {
13895
14020
  return null;
13896
14021
  }
@@ -13907,7 +14032,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
13907
14032
  const root = parse3(absolutePath2).root;
13908
14033
  let home = dirname3(absolutePath2);
13909
14034
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
13910
- const manifestPath = join7(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14035
+ const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
13911
14036
  const relativePaths = readManifestRelativePaths(manifestPath);
13912
14037
  if (relativePaths) {
13913
14038
  const claimed = relative3(home, absolutePath2).split(sep).join("/");
@@ -13934,25 +14059,25 @@ var init_session_render_ownership = __esm(() => {
13934
14059
  // src/lib/apply.ts
13935
14060
  var exports_apply = {};
13936
14061
  __export(exports_apply, {
13937
- previewConfigs: () => previewConfigs,
13938
- normalizeTargetPath: () => normalizeTargetPath,
13939
- getConfigHome: () => getConfigHome,
13940
- expandPath: () => expandPath,
13941
- applyConfigsWithReport: () => applyConfigsWithReport,
14062
+ applyConfig: () => applyConfig,
13942
14063
  applyConfigs: () => applyConfigs,
13943
- applyConfig: () => applyConfig
14064
+ applyConfigsWithReport: () => applyConfigsWithReport,
14065
+ expandPath: () => expandPath,
14066
+ getConfigHome: () => getConfigHome,
14067
+ normalizeTargetPath: () => normalizeTargetPath,
14068
+ previewConfigs: () => previewConfigs
13944
14069
  });
13945
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
13946
- import { basename as basename4, dirname as dirname4, join as join8, resolve as resolve5 } from "path";
13947
- import { homedir as homedir5 } from "os";
14070
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
14071
+ import { basename as basename4, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
14072
+ import { homedir as homedir6 } from "os";
13948
14073
  function getConfigHome() {
13949
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
14074
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
13950
14075
  }
13951
14076
  function expandPath(p) {
13952
14077
  if (p.startsWith("~/")) {
13953
- return resolve5(getConfigHome(), p.slice(2));
14078
+ return resolve7(getConfigHome(), p.slice(2));
13954
14079
  }
13955
- return resolve5(p);
14080
+ return resolve7(p);
13956
14081
  }
13957
14082
  function normalizeTargetPath(p) {
13958
14083
  const expanded = expandPath(p);
@@ -13964,7 +14089,7 @@ function normalizeTargetPath(p) {
13964
14089
  while (true) {
13965
14090
  if (existsSync7(current)) {
13966
14091
  try {
13967
- return resolve5(realpathSync2(current), ...missingSegments);
14092
+ return resolve7(realpathSync2(current), ...missingSegments);
13968
14093
  } catch {
13969
14094
  return expanded;
13970
14095
  }
@@ -13993,7 +14118,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
13993
14118
  }
13994
14119
  const path = expandPath(renderedTargetPath);
13995
14120
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
13996
- const previousContent = existsSync7(path) ? readFileSync5(path, "utf-8") : null;
14121
+ const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
13997
14122
  const changed = previousContent !== renderedForTarget;
13998
14123
  if (!opts.dryRun) {
13999
14124
  const dir = dirname4(path);
@@ -14033,7 +14158,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
14033
14158
  const path = expandPath(targetPath);
14034
14159
  if (!existsSync7(path))
14035
14160
  return [];
14036
- current = readFileSync5(path, "utf-8");
14161
+ current = readFileSync6(path, "utf-8");
14037
14162
  } catch {
14038
14163
  return secretTokens;
14039
14164
  }
@@ -14357,7 +14482,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
14357
14482
  getConfigHome(),
14358
14483
  opts.vars?.["HOME_DIR"]
14359
14484
  ].filter((home) => typeof home === "string" && home.length > 0));
14360
- if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join8(home, ...relativePath.split("/"))))))
14485
+ if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join10(home, ...relativePath.split("/"))))))
14361
14486
  return true;
14362
14487
  return sessionRenderOwnsPath(normalized);
14363
14488
  }
@@ -14375,9 +14500,9 @@ var init_apply = __esm(() => {
14375
14500
  });
14376
14501
 
14377
14502
  // src/lib/sync-dir.ts
14378
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
14379
- import { join as join9, relative as relative4 } from "path";
14380
- import { homedir as homedir6 } from "os";
14503
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
14504
+ import { join as join11, relative as relative4 } from "path";
14505
+ import { homedir as homedir7 } from "os";
14381
14506
  function shouldSkip(p) {
14382
14507
  return SKIP.some((s) => p.includes(s));
14383
14508
  }
@@ -14386,9 +14511,9 @@ async function syncFromDir(dir, opts = {}) {
14386
14511
  const absDir = expandPath(dir);
14387
14512
  if (!existsSync8(absDir))
14388
14513
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
14389
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join9(absDir, f)).filter((f) => statSync4(f).isFile());
14514
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
14390
14515
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
14391
- const home = homedir6();
14516
+ const home = homedir7();
14392
14517
  const allConfigs = await store.listConfigs();
14393
14518
  for (const file of files) {
14394
14519
  if (shouldSkip(file)) {
@@ -14396,7 +14521,7 @@ async function syncFromDir(dir, opts = {}) {
14396
14521
  continue;
14397
14522
  }
14398
14523
  try {
14399
- const content = readFileSync6(file, "utf-8");
14524
+ const content = readFileSync7(file, "utf-8");
14400
14525
  if (content.length > 500000) {
14401
14526
  result.skipped.push(file + " (too large)");
14402
14527
  continue;
@@ -14422,7 +14547,7 @@ async function syncFromDir(dir, opts = {}) {
14422
14547
  }
14423
14548
  async function syncToDir(dir, opts = {}) {
14424
14549
  const store = opts.store ?? resolveConfigStore();
14425
- const home = homedir6();
14550
+ const home = homedir7();
14426
14551
  const absDir = expandPath(dir);
14427
14552
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14428
14553
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14446,7 +14571,7 @@ async function syncToDir(dir, opts = {}) {
14446
14571
  }
14447
14572
  function walkDir(dir, files = []) {
14448
14573
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
14449
- const full = join9(dir, entry.name);
14574
+ const full = join11(dir, entry.name);
14450
14575
  if (shouldSkip(full))
14451
14576
  continue;
14452
14577
  if (entry.isDirectory())
@@ -14467,21 +14592,21 @@ var init_sync_dir = __esm(() => {
14467
14592
  // src/lib/sync.ts
14468
14593
  var exports_sync = {};
14469
14594
  __export(exports_sync, {
14470
- syncToDisk: () => syncToDisk,
14471
- syncToDir: () => syncToDir,
14472
- syncProject: () => syncProject,
14473
- syncKnown: () => syncKnown,
14474
- syncFromDir: () => syncFromDir,
14475
- diffConfig: () => diffConfig,
14476
- detectFormat: () => detectFormat,
14477
- detectCategory: () => detectCategory,
14478
- detectAgent: () => detectAgent,
14479
- PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
14595
+ CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS,
14480
14596
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14481
- CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14597
+ PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
14598
+ detectAgent: () => detectAgent,
14599
+ detectCategory: () => detectCategory,
14600
+ detectFormat: () => detectFormat,
14601
+ diffConfig: () => diffConfig,
14602
+ syncFromDir: () => syncFromDir,
14603
+ syncKnown: () => syncKnown,
14604
+ syncProject: () => syncProject,
14605
+ syncToDir: () => syncToDir,
14606
+ syncToDisk: () => syncToDisk
14482
14607
  });
14483
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
14484
- import { basename as basename5, extname as extname3, join as join10 } from "path";
14608
+ import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
14609
+ import { basename as basename5, extname as extname3, join as join12 } from "path";
14485
14610
  function claudeRuleOutputs(fileName) {
14486
14611
  const stem = basename5(fileName, extname3(fileName));
14487
14612
  return [
@@ -14545,11 +14670,11 @@ async function syncProject(opts) {
14545
14670
  const allConfigs = await store.listConfigs();
14546
14671
  const machine = detectMachineContext();
14547
14672
  for (const pf of PROJECT_CONFIG_FILES) {
14548
- const abs = join10(absDir, pf.file);
14673
+ const abs = join12(absDir, pf.file);
14549
14674
  if (!existsSync9(abs))
14550
14675
  continue;
14551
14676
  try {
14552
- const rawContent = readFileSync7(abs, "utf-8");
14677
+ const rawContent = readFileSync8(abs, "utf-8");
14553
14678
  if (rawContent.length > 500000) {
14554
14679
  result.skipped.push(pf.file);
14555
14680
  continue;
@@ -14578,20 +14703,20 @@ async function syncProject(opts) {
14578
14703
  }
14579
14704
  }
14580
14705
  for (const ruleDir of [
14581
- { dir: join10(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
14582
- { dir: join10(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
14583
- { dir: join10(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
14584
- { dir: join10(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
14585
- { dir: join10(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
14586
- { dir: join10(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14587
- { dir: join10(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14706
+ { dir: join12(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
14707
+ { dir: join12(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
14708
+ { dir: join12(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
14709
+ { dir: join12(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
14710
+ { dir: join12(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
14711
+ { dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14712
+ { dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14588
14713
  ]) {
14589
14714
  if (!existsSync9(ruleDir.dir))
14590
14715
  continue;
14591
14716
  const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14592
14717
  for (const f of mdFiles) {
14593
- const abs = join10(ruleDir.dir, f);
14594
- const raw = readFileSync7(abs, "utf-8");
14718
+ const abs = join12(ruleDir.dir, f);
14719
+ const raw = readFileSync8(abs, "utf-8");
14595
14720
  const redacted = redactContent(raw, "markdown");
14596
14721
  const machineAware = templateizeMachineContent(redacted.content, machine);
14597
14722
  const content = machineAware.content;
@@ -14637,13 +14762,13 @@ async function syncKnown(opts = {}) {
14637
14762
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
14638
14763
  const ruleFiles = readdirSync2(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
14639
14764
  for (const f of ruleFiles) {
14640
- const abs2 = join10(absDir, f);
14765
+ const abs2 = join12(absDir, f);
14641
14766
  const targetPath = abs2.replace(home, "~");
14642
14767
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
14643
14768
  result.skipped.push(`${targetPath} (generated output)`);
14644
14769
  continue;
14645
14770
  }
14646
- const raw = readFileSync7(abs2, "utf-8");
14771
+ const raw = readFileSync8(abs2, "utf-8");
14647
14772
  const redacted = redactContent(raw, "markdown");
14648
14773
  const machineAware = templateizeMachineContent(redacted.content, machine);
14649
14774
  const content = machineAware.content;
@@ -14676,7 +14801,7 @@ async function syncKnown(opts = {}) {
14676
14801
  continue;
14677
14802
  }
14678
14803
  try {
14679
- const rawContent = normalizeKnownConfigSource(known, readFileSync7(abs, "utf-8"));
14804
+ const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
14680
14805
  if (rawContent.length > 500000) {
14681
14806
  result.skipped.push(known.path + " (too large)");
14682
14807
  continue;
@@ -14779,7 +14904,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
14779
14904
  const path = expandPath(targetPath);
14780
14905
  if (!existsSync9(path))
14781
14906
  return `(file not found on disk: ${path})`;
14782
- const diskContent = readFileSync7(path, "utf-8");
14907
+ const diskContent = readFileSync8(path, "utf-8");
14783
14908
  if (diskContent === expectedContent)
14784
14909
  return "(no diff \u2014 identical)";
14785
14910
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -15012,18 +15137,18 @@ __export(exports_package_manager_guard, {
15012
15137
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15013
15138
  });
15014
15139
  import { execFileSync as execFileSync2 } from "child_process";
15015
- import { existsSync as existsSync15, lstatSync as lstatSync4, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
15016
- import { homedir as homedir7 } from "os";
15017
- import { basename as basename6, dirname as dirname7, isAbsolute as isAbsolute5, join as join15, relative as relative6, resolve as resolve9 } from "path";
15140
+ import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
15141
+ import { homedir as homedir9 } from "os";
15142
+ import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative7, resolve as resolve12 } from "path";
15018
15143
  function scanPackageManagerSecrets(options = {}) {
15019
- const cwd = options.cwd ? resolve9(options.cwd) : process.cwd();
15020
- const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve9(cwd, root));
15144
+ const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
15145
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve12(cwd, root));
15021
15146
  const findings = [];
15022
15147
  let scannedFiles = 0;
15023
15148
  for (const root of roots) {
15024
- if (!existsSync15(root))
15149
+ if (!existsSync16(root))
15025
15150
  continue;
15026
- const stat = lstatSync4(root);
15151
+ const stat = lstatSync6(root);
15027
15152
  if (stat.isFile()) {
15028
15153
  if (!shouldScanRepoFile(root))
15029
15154
  continue;
@@ -15031,14 +15156,14 @@ function scanPackageManagerSecrets(options = {}) {
15031
15156
  if (text === null)
15032
15157
  continue;
15033
15158
  scannedFiles++;
15034
- findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname7(root)));
15159
+ findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
15035
15160
  continue;
15036
15161
  }
15037
15162
  if (!stat.isDirectory())
15038
15163
  continue;
15039
15164
  const tracked = trackedFiles(root);
15040
15165
  for (const file of collectRepoFiles(root)) {
15041
- const rel = toPosix(relative6(root, file));
15166
+ const rel = toPosix(relative7(root, file));
15042
15167
  const isTracked = tracked.has(rel);
15043
15168
  const text = readTextFile(file);
15044
15169
  if (text === null)
@@ -15048,10 +15173,10 @@ function scanPackageManagerSecrets(options = {}) {
15048
15173
  }
15049
15174
  }
15050
15175
  if (options.includeHome) {
15051
- const home = homedir7();
15176
+ const home = homedir9();
15052
15177
  for (const name of HOME_FILES) {
15053
- const file = join15(home, name);
15054
- if (!existsSync15(file))
15178
+ const file = join18(home, name);
15179
+ if (!existsSync16(file))
15055
15180
  continue;
15056
15181
  const text = readTextFile(file);
15057
15182
  if (text === null)
@@ -15075,12 +15200,12 @@ function collectRepoFiles(root) {
15075
15200
  if (entry.isDirectory()) {
15076
15201
  if (SKIP_DIRS.has(entry.name))
15077
15202
  continue;
15078
- visit(join15(dir, entry.name));
15203
+ visit(join18(dir, entry.name));
15079
15204
  continue;
15080
15205
  }
15081
15206
  if (!entry.isFile())
15082
15207
  continue;
15083
- const file = join15(dir, entry.name);
15208
+ const file = join18(dir, entry.name);
15084
15209
  if (shouldScanRepoFile(file))
15085
15210
  out.push(file);
15086
15211
  }
@@ -15115,10 +15240,10 @@ function isNpmrcName(name) {
15115
15240
  }
15116
15241
  function readTextFile(file) {
15117
15242
  try {
15118
- const stat = lstatSync4(file);
15243
+ const stat = lstatSync6(file);
15119
15244
  if (!stat.isFile() || stat.size > 5000000)
15120
15245
  return null;
15121
- const buf = readFileSync12(file);
15246
+ const buf = readFileSync14(file);
15122
15247
  if (buf.includes(0))
15123
15248
  return null;
15124
15249
  return buf.toString("utf-8");
@@ -15318,11 +15443,11 @@ function trackedFiles(root) {
15318
15443
  }
15319
15444
  function isTrackedFile(file) {
15320
15445
  try {
15321
- const repoRoot = execFileSync2("git", ["-C", dirname7(file), "rev-parse", "--show-toplevel"], {
15446
+ const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
15322
15447
  encoding: "utf-8",
15323
15448
  stdio: ["ignore", "pipe", "ignore"]
15324
15449
  }).trim();
15325
- const rel = toPosix(relative6(repoRoot, file));
15450
+ const rel = toPosix(relative7(repoRoot, file));
15326
15451
  execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
15327
15452
  stdio: ["ignore", "ignore", "ignore"]
15328
15453
  });
@@ -15348,13 +15473,13 @@ function stripInlineComment(value) {
15348
15473
  return value.replace(/\s[#;].*$/, "").trim();
15349
15474
  }
15350
15475
  function displayPath(file, root) {
15351
- const home = homedir7();
15476
+ const home = homedir9();
15352
15477
  if (root === home && (file === home || file.startsWith(home + "/")))
15353
- return "~/" + toPosix(relative6(home, file));
15478
+ return "~/" + toPosix(relative7(home, file));
15354
15479
  if (isAbsolute5(root) && file.startsWith(root + "/"))
15355
- return toPosix(relative6(root, file));
15480
+ return toPosix(relative7(root, file));
15356
15481
  if (file === home || file.startsWith(home + "/"))
15357
- return "~/" + toPosix(relative6(home, file));
15482
+ return "~/" + toPosix(relative7(home, file));
15358
15483
  return file;
15359
15484
  }
15360
15485
  function toPosix(path) {
@@ -15392,15 +15517,16 @@ var init_package_manager_guard = __esm(() => {
15392
15517
  TOKEN_VALUE_PATTERNS = [
15393
15518
  { re: /npm_[A-Za-z0-9]{36,}/, rule: "literal-npm-token", detail: "literal npm token-like value" },
15394
15519
  { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, rule: "literal-github-token", detail: "literal GitHub token-like value" },
15395
- { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
15520
+ { re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
15396
15521
  { re: /sk-[A-Za-z0-9]{48,}/, rule: "literal-openai-key", detail: "literal OpenAI key-like value" },
15397
15522
  { re: /AKIA[0-9A-Z]{16}/, rule: "literal-aws-access-key", detail: "literal AWS access-key-like value" },
15398
15523
  { re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
15399
15524
  ];
15400
15525
  });
15401
15526
 
15402
- // node_modules/@hasna/events/dist/commander.js
15527
+ // ../events/dist/commander.js
15403
15528
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
15529
+ import { Buffer as Buffer2 } from "buffer";
15404
15530
  import { existsSync } from "fs";
15405
15531
  import { homedir } from "os";
15406
15532
  import { join } from "path";
@@ -15416,29 +15542,83 @@ function getPathValue(input, path) {
15416
15542
  return;
15417
15543
  }, input);
15418
15544
  }
15419
- function wildcardToRegExp(pattern) {
15420
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
15421
- return new RegExp(`^${escaped}$`);
15545
+ function getFieldValues(input, path) {
15546
+ const values = [];
15547
+ const push = (value) => {
15548
+ if (!values.some((item) => Object.is(item, value)))
15549
+ values.push(value);
15550
+ };
15551
+ if (path.includes(".") && path in input)
15552
+ push(input[path]);
15553
+ const nestedValue = getPathValue(input, path);
15554
+ if (nestedValue !== undefined || !path.includes("."))
15555
+ push(nestedValue);
15556
+ return values;
15557
+ }
15558
+ function wildcardToRegExp(pattern, options = {}) {
15559
+ let body = "";
15560
+ for (let index = 0;index < pattern.length; index += 1) {
15561
+ const char = pattern[index];
15562
+ if (char === "*") {
15563
+ if (pattern[index + 1] === "*") {
15564
+ body += ".*";
15565
+ index += 1;
15566
+ } else {
15567
+ body += options.segmentSafe ? "[^/]*" : ".*";
15568
+ }
15569
+ } else {
15570
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
15571
+ }
15572
+ }
15573
+ return new RegExp(`^${body}$`);
15422
15574
  }
15423
- function matchString(value, matcher) {
15575
+ function matchString(value, matcher, options = {}) {
15424
15576
  if (matcher === undefined)
15425
15577
  return true;
15426
15578
  if (value === undefined)
15427
15579
  return false;
15428
15580
  const matchers = Array.isArray(matcher) ? matcher : [matcher];
15429
- return matchers.some((item) => wildcardToRegExp(item).test(value));
15581
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
15430
15582
  }
15431
15583
  function matchRecord(input, matcher) {
15432
15584
  if (!matcher)
15433
15585
  return true;
15434
15586
  return Object.entries(matcher).every(([path, expected]) => {
15435
- const actual = getPathValue(input, path);
15436
- if (typeof expected === "string" || Array.isArray(expected)) {
15437
- return matchString(actual === undefined ? undefined : String(actual), expected);
15438
- }
15439
- return actual === expected;
15587
+ const actualValues = getFieldValues(input, path);
15588
+ return matchField(actualValues, expected, path);
15440
15589
  });
15441
15590
  }
15591
+ function matchField(actualValues, expected, path) {
15592
+ if (isNegativeMatcher(expected)) {
15593
+ return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
15594
+ }
15595
+ return actualValues.some((actual) => matchPositiveField(actual, expected, path));
15596
+ }
15597
+ function matchPositiveField(actual, expected, path) {
15598
+ if (typeof expected === "string" || Array.isArray(expected)) {
15599
+ return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
15600
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
15601
+ }));
15602
+ }
15603
+ if (Array.isArray(actual)) {
15604
+ return actual.some((item) => item === expected);
15605
+ }
15606
+ return actual === expected;
15607
+ }
15608
+ function stringCandidates(actual) {
15609
+ if (actual === undefined)
15610
+ return [];
15611
+ if (Array.isArray(actual)) {
15612
+ return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
15613
+ }
15614
+ return [String(actual)];
15615
+ }
15616
+ function isPrimitiveFieldValue(value) {
15617
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
15618
+ }
15619
+ function isNegativeMatcher(value) {
15620
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
15621
+ }
15442
15622
  function eventMatchesFilter(event, filter) {
15443
15623
  return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
15444
15624
  }
@@ -15451,16 +15631,29 @@ function channelMatchesEvent(channel, event) {
15451
15631
  }
15452
15632
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
15453
15633
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
15634
+ var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
15635
+ var DEFAULT_EVENT_PAGE_LIMIT = 100;
15636
+ var MAX_EVENT_PAGE_LIMIT = 1000;
15454
15637
  function getEventsDataDir(override) {
15455
15638
  return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
15456
15639
  }
15640
+ function getActiveEventsDirEnv() {
15641
+ if (process.env[HASNA_EVENTS_DIR_ENV])
15642
+ return HASNA_EVENTS_DIR_ENV;
15643
+ if (process.env[HASNA_EVENTS_HOME_ENV])
15644
+ return HASNA_EVENTS_HOME_ENV;
15645
+ return null;
15646
+ }
15647
+
15457
15648
  class JsonEventsStore {
15458
15649
  dataDir;
15650
+ runtime;
15459
15651
  channelsPath;
15460
15652
  eventsPath;
15461
15653
  deliveriesPath;
15462
15654
  constructor(dataDir = getEventsDataDir()) {
15463
15655
  this.dataDir = dataDir;
15656
+ this.runtime = localJsonRuntime(dataDir);
15464
15657
  this.channelsPath = join(dataDir, "channels.json");
15465
15658
  this.eventsPath = join(dataDir, "events.json");
15466
15659
  this.deliveriesPath = join(dataDir, "deliveries.json");
@@ -15508,13 +15701,58 @@ class JsonEventsStore {
15508
15701
  await this.writeJson(this.eventsPath, events);
15509
15702
  return event;
15510
15703
  }
15511
- async listEvents() {
15704
+ async appendEventOnce(event, options = {}) {
15705
+ await this.init();
15706
+ const events = await this.readJson(this.eventsPath, []);
15707
+ const dedupe = options.dedupe !== false;
15708
+ if (dedupe) {
15709
+ const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
15710
+ if (existing) {
15711
+ return {
15712
+ event: existing,
15713
+ stored: false,
15714
+ deduped: true,
15715
+ identity: { id: existing.id, dedupeKey: existing.dedupeKey }
15716
+ };
15717
+ }
15718
+ }
15719
+ events.push(event);
15720
+ await this.writeJson(this.eventsPath, events);
15721
+ return {
15722
+ event,
15723
+ stored: true,
15724
+ deduped: false,
15725
+ identity: { id: event.id, dedupeKey: event.dedupeKey }
15726
+ };
15727
+ }
15728
+ async listEvents(options = {}) {
15512
15729
  await this.init();
15513
- return this.readJson(this.eventsPath, []);
15730
+ const events = await this.readJson(this.eventsPath, []);
15731
+ return queryEvents(events, options);
15732
+ }
15733
+ async listEventsPage(options = {}) {
15734
+ await this.init();
15735
+ const events = await this.readJson(this.eventsPath, []);
15736
+ const queried = queryEvents(events, {
15737
+ eventId: options.eventId,
15738
+ source: options.source,
15739
+ type: options.type
15740
+ });
15741
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
15742
+ const limit = normalizeEventPageLimit(options.limit);
15743
+ const pageEvents = queried.slice(offset, offset + limit);
15744
+ const nextOffset = offset + pageEvents.length;
15745
+ const hasMore = nextOffset < queried.length;
15746
+ return {
15747
+ events: pageEvents,
15748
+ cursor: options.cursor,
15749
+ nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
15750
+ hasMore
15751
+ };
15514
15752
  }
15515
15753
  async findEventByIdentity(identity) {
15516
15754
  const events = await this.listEvents();
15517
- return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
15755
+ return findEventByIdentity(events, identity);
15518
15756
  }
15519
15757
  async appendDelivery(result) {
15520
15758
  await this.init();
@@ -15565,6 +15803,130 @@ class JsonEventsStore {
15565
15803
  });
15566
15804
  }
15567
15805
  }
15806
+ function localJsonRuntime(dataDir = getEventsDataDir()) {
15807
+ return {
15808
+ mode: "local-files",
15809
+ name: "json-events-store",
15810
+ remote: false,
15811
+ localFiles: true,
15812
+ localSqlite: false,
15813
+ postgres: false,
15814
+ s3: false,
15815
+ aws: false,
15816
+ durable: true,
15817
+ idempotency: "best-effort-local",
15818
+ replayCursors: true,
15819
+ description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
15820
+ };
15821
+ }
15822
+ function encodeLocalJsonEventCursor(offset, options = {}) {
15823
+ if (!Number.isInteger(offset) || offset < 0)
15824
+ throw new Error(`Invalid event cursor offset: ${offset}`);
15825
+ const payload = {
15826
+ offset,
15827
+ eventId: options.eventId,
15828
+ source: options.source,
15829
+ type: options.type
15830
+ };
15831
+ return `${LOCAL_JSON_EVENT_CURSOR_PREFIX}${Buffer2.from(JSON.stringify(payload), "utf-8").toString("base64url")}`;
15832
+ }
15833
+ function decodeLocalJsonEventCursor(cursor, options = {}) {
15834
+ if (!cursor)
15835
+ return 0;
15836
+ if (!cursor.startsWith(LOCAL_JSON_EVENT_CURSOR_PREFIX))
15837
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15838
+ const rawPayload = cursor.slice(LOCAL_JSON_EVENT_CURSOR_PREFIX.length);
15839
+ let payload;
15840
+ try {
15841
+ payload = JSON.parse(Buffer2.from(rawPayload, "base64url").toString("utf-8"));
15842
+ } catch {
15843
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15844
+ }
15845
+ const offset = payload.offset;
15846
+ if (!Number.isInteger(offset) || offset < 0)
15847
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15848
+ assertCursorFilter("eventId", payload.eventId, options.eventId);
15849
+ assertCursorFilter("source", payload.source, options.source);
15850
+ assertCursorFilter("type", payload.type, options.type);
15851
+ return offset;
15852
+ }
15853
+ function normalizeEventPageLimit(limit) {
15854
+ if (limit === undefined)
15855
+ return DEFAULT_EVENT_PAGE_LIMIT;
15856
+ if (!Number.isInteger(limit) || limit < 1)
15857
+ throw new Error(`Event page limit must be a positive integer, got ${limit}`);
15858
+ return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
15859
+ }
15860
+ function queryEvents(events, options) {
15861
+ let rows = events;
15862
+ if (options.eventId)
15863
+ rows = rows.filter((event) => event.id === options.eventId);
15864
+ if (options.source)
15865
+ rows = rows.filter((event) => event.source === options.source);
15866
+ if (options.type)
15867
+ rows = rows.filter((event) => event.type === options.type);
15868
+ if (options.cursor) {
15869
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
15870
+ rows = rows.slice(offset);
15871
+ }
15872
+ if (options.limit !== undefined)
15873
+ rows = rows.slice(0, normalizeEventPageLimit(options.limit));
15874
+ return rows;
15875
+ }
15876
+ function assertCursorFilter(name, cursorValue, optionValue) {
15877
+ if (cursorValue !== optionValue)
15878
+ throw new Error(`Local JSON event cursor ${name} filter mismatch`);
15879
+ }
15880
+ function findEventByIdentity(events, identity) {
15881
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
15882
+ }
15883
+ async function getEventsStatus(dataDir) {
15884
+ const store = new JsonEventsStore(dataDir);
15885
+ await store.init();
15886
+ const [channels, events, deliveries] = await Promise.all([
15887
+ store.listChannels(),
15888
+ store.listEvents(),
15889
+ store.listDeliveries()
15890
+ ]);
15891
+ const transports = channels.reduce((counts, channel) => {
15892
+ counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
15893
+ return counts;
15894
+ }, {});
15895
+ return {
15896
+ service: "events",
15897
+ schemaVersion: "1.0",
15898
+ dataDir: store.dataDir,
15899
+ storage: store.runtime,
15900
+ env: {
15901
+ primary: HASNA_EVENTS_DIR_ENV,
15902
+ fallback: HASNA_EVENTS_HOME_ENV,
15903
+ active: getActiveEventsDirEnv()
15904
+ },
15905
+ files: {
15906
+ channels: statusFile(store.dataDir, "channels.json", channels.length),
15907
+ events: statusFile(store.dataDir, "events.json", events.length),
15908
+ deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
15909
+ },
15910
+ counts: {
15911
+ channels: channels.length,
15912
+ enabledChannels: channels.filter((channel) => channel.enabled).length,
15913
+ disabledChannels: channels.filter((channel) => !channel.enabled).length,
15914
+ events: events.length,
15915
+ deliveries: deliveries.length
15916
+ },
15917
+ transports,
15918
+ safety: {
15919
+ includesEventPayloads: false,
15920
+ includesWebhookSecrets: false,
15921
+ listOutputsRedactSecrets: true,
15922
+ statusOutputIsMetadataOnly: true
15923
+ }
15924
+ };
15925
+ }
15926
+ function statusFile(dataDir, fileName, records) {
15927
+ const path = join(dataDir, fileName);
15928
+ return { path, exists: existsSync(path), records };
15929
+ }
15568
15930
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
15569
15931
  function buildSignatureBase(timestamp, body) {
15570
15932
  return `${timestamp}.${body}`;
@@ -15579,21 +15941,27 @@ function now() {
15579
15941
  function truncate(value, max = 4096) {
15580
15942
  return value.length > max ? `${value.slice(0, max)}...` : value;
15581
15943
  }
15582
- function buildWebhookRequest(event, channel) {
15944
+ function buildWebhookRequest(event, channel, options = {}) {
15583
15945
  if (!channel.webhook)
15584
15946
  throw new Error(`Channel ${channel.id} has no webhook config`);
15947
+ for (const name of Object.keys(channel.webhook.headers ?? {})) {
15948
+ if (/^x-hasna-/i.test(name)) {
15949
+ throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
15950
+ }
15951
+ }
15585
15952
  const body = JSON.stringify(event);
15586
- const timestamp = event.time;
15953
+ const timestamp = options.timestamp ?? new Date().toISOString();
15587
15954
  const headers = {
15588
15955
  "Content-Type": "application/json",
15589
15956
  "User-Agent": "@hasna/events",
15590
15957
  "X-Hasna-Event-Id": event.id,
15591
15958
  "X-Hasna-Event-Type": event.type,
15592
- "X-Hasna-Timestamp": timestamp,
15593
- ...channel.webhook.headers
15959
+ ...channel.webhook.headers,
15960
+ "X-Hasna-Timestamp": timestamp
15594
15961
  };
15595
- if (channel.webhook.secret) {
15596
- headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
15962
+ const secret = options.secret ?? channel.webhook.secret;
15963
+ if (secret) {
15964
+ headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
15597
15965
  }
15598
15966
  return { body, headers };
15599
15967
  }
@@ -15601,7 +15969,21 @@ async function dispatchWebhook(event, channel, options = {}) {
15601
15969
  if (!channel.webhook)
15602
15970
  throw new Error(`Channel ${channel.id} has no webhook config`);
15603
15971
  const startedAt = now();
15604
- const { body, headers } = buildWebhookRequest(event, channel);
15972
+ let secret = channel.webhook.secret;
15973
+ if (channel.webhook.secretRef) {
15974
+ if (!options.secretResolver) {
15975
+ return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
15976
+ }
15977
+ try {
15978
+ secret = await options.secretResolver(channel.webhook.secretRef);
15979
+ } catch {
15980
+ return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
15981
+ }
15982
+ if (!secret)
15983
+ return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
15984
+ }
15985
+ const timestamp = (options.now?.() ?? new Date).toISOString();
15986
+ const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
15605
15987
  const controller = new AbortController;
15606
15988
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
15607
15989
  try {
@@ -15633,6 +16015,15 @@ async function dispatchWebhook(event, channel, options = {}) {
15633
16015
  clearTimeout(timeout);
15634
16016
  }
15635
16017
  }
16018
+ function failedAttempt(startedAt, error) {
16019
+ return {
16020
+ attempt: 1,
16021
+ status: "failed",
16022
+ startedAt,
16023
+ completedAt: now(),
16024
+ error
16025
+ };
16026
+ }
15636
16027
  async function dispatchCommand(event, channel) {
15637
16028
  if (!channel.command)
15638
16029
  throw new Error(`Channel ${channel.id} has no command config`);
@@ -15721,6 +16112,90 @@ function createDeliveryResult(event, channel, attempts) {
15721
16112
  completedAt: attempts.at(-1)?.completedAt ?? now()
15722
16113
  };
15723
16114
  }
16115
+
16116
+ class EventValidationError extends Error {
16117
+ eventType;
16118
+ issues;
16119
+ constructor(eventType, issues) {
16120
+ const detail = issues.map((issue) => `${issue.path || "<root>"}: ${issue.message}`).join("; ");
16121
+ super(`Event validation failed for type "${eventType}": ${detail}`);
16122
+ this.name = "EventValidationError";
16123
+ this.eventType = eventType;
16124
+ this.issues = issues;
16125
+ }
16126
+ }
16127
+
16128
+ class EventTypeCatalog {
16129
+ definitions = new Map;
16130
+ register(definition) {
16131
+ this.definitions.set(definition.type, definition);
16132
+ return this;
16133
+ }
16134
+ unregister(type) {
16135
+ return this.definitions.delete(type);
16136
+ }
16137
+ has(type) {
16138
+ return this.definitions.has(type);
16139
+ }
16140
+ get(type) {
16141
+ return this.definitions.get(type);
16142
+ }
16143
+ list() {
16144
+ return [...this.definitions.values()];
16145
+ }
16146
+ validateEvent(event) {
16147
+ const definition = this.definitions.get(event.type);
16148
+ if (!definition)
16149
+ return { ok: true };
16150
+ return definition.validate(event.data, event);
16151
+ }
16152
+ assertEventValid(event) {
16153
+ const result = this.validateEvent(event);
16154
+ if (!result.ok) {
16155
+ throw new EventValidationError(event.type, result.issues);
16156
+ }
16157
+ }
16158
+ }
16159
+ var defaultEventTypeCatalog = new EventTypeCatalog;
16160
+ var APP_EVENT_V1_MAX_DATA_BYTES = 32 * 1024;
16161
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
16162
+ if (paths.length === 0)
16163
+ return event;
16164
+ const copy = structuredClone(event);
16165
+ for (const path of paths) {
16166
+ setPath(copy, path, replacement);
16167
+ }
16168
+ return copy;
16169
+ }
16170
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
16171
+ return redactValue(event, replacement);
16172
+ }
16173
+ function shouldRedactKey(key) {
16174
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
16175
+ }
16176
+ function redactValue(value, replacement) {
16177
+ if (Array.isArray(value))
16178
+ return value.map((item) => redactValue(item, replacement));
16179
+ if (!value || typeof value !== "object")
16180
+ return value;
16181
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
16182
+ key,
16183
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
16184
+ ]));
16185
+ }
16186
+ function setPath(input, path, replacement) {
16187
+ const parts = path.split(".");
16188
+ let cursor = input;
16189
+ for (const part of parts.slice(0, -1)) {
16190
+ const next = cursor[part];
16191
+ if (!next || typeof next !== "object")
16192
+ return;
16193
+ cursor = next;
16194
+ }
16195
+ const last = parts.at(-1);
16196
+ if (last && last in cursor)
16197
+ cursor[last] = replacement;
16198
+ }
15724
16199
  function createEvent(input) {
15725
16200
  return {
15726
16201
  id: input.id ?? randomUUID2(),
@@ -15741,10 +16216,18 @@ class EventsClient {
15741
16216
  store;
15742
16217
  redactors;
15743
16218
  transportOptions;
16219
+ catalog;
16220
+ validateCatalogTypes;
15744
16221
  constructor(options = {}) {
15745
16222
  this.store = options.store ?? new JsonEventsStore(options.dataDir);
15746
16223
  this.redactors = options.redactors ?? [];
15747
- this.transportOptions = { fetchImpl: options.fetchImpl };
16224
+ this.transportOptions = {
16225
+ fetchImpl: options.fetchImpl,
16226
+ secretResolver: options.secretResolver,
16227
+ now: options.now
16228
+ };
16229
+ this.catalog = options.catalog ?? defaultEventTypeCatalog;
16230
+ this.validateCatalogTypes = options.validateCatalogTypes ?? false;
15748
16231
  }
15749
16232
  async addChannel(input) {
15750
16233
  const timestamp = new Date().toISOString();
@@ -15762,18 +16245,40 @@ class EventsClient {
15762
16245
  }
15763
16246
  async emit(input, options = {}) {
15764
16247
  const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
15765
- if (options.dedupe !== false) {
15766
- const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
15767
- if (existing) {
15768
- return { event: existing, deliveries: [], deduped: true };
15769
- }
15770
- }
15771
- await this.store.appendEvent(event);
15772
- const deliveries = options.deliver === false ? [] : await this.deliver(event);
15773
- return { event, deliveries, deduped: false };
15774
- }
15775
- async listEvents() {
15776
- return this.store.listEvents();
16248
+ if (options.validate ?? this.validateCatalogTypes) {
16249
+ this.catalog.assertEventValid(event);
16250
+ }
16251
+ const append = await this.appendEvent(event, { dedupe: options.dedupe !== false });
16252
+ if (append.deduped) {
16253
+ return { event: append.event, deliveries: [], deduped: true };
16254
+ }
16255
+ const deliveries = options.deliver === false ? [] : await this.deliver(append.event);
16256
+ return { event: append.event, deliveries, deduped: false };
16257
+ }
16258
+ async listEvents(options = {}) {
16259
+ if (Object.keys(options).length === 0)
16260
+ return this.store.listEvents();
16261
+ return queryClientEvents(await this.store.listEvents(), options);
16262
+ }
16263
+ async listEventsPage(options = {}) {
16264
+ if (this.store.listEventsPage)
16265
+ return this.store.listEventsPage(options);
16266
+ const events = queryClientEvents(await this.store.listEvents(), {
16267
+ eventId: options.eventId,
16268
+ source: options.source,
16269
+ type: options.type
16270
+ });
16271
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
16272
+ const limit = normalizeEventPageLimit(options.limit);
16273
+ const pageEvents = events.slice(offset, offset + limit);
16274
+ const nextOffset = offset + pageEvents.length;
16275
+ const hasMore = nextOffset < events.length;
16276
+ return {
16277
+ events: pageEvents,
16278
+ cursor: options.cursor,
16279
+ nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
16280
+ hasMore
16281
+ };
15777
16282
  }
15778
16283
  async listDeliveries() {
15779
16284
  return this.store.listDeliveries();
@@ -15790,7 +16295,7 @@ class EventsClient {
15790
16295
  }
15791
16296
  return deliveries;
15792
16297
  }
15793
- async testChannel(id, input = {}) {
16298
+ async matchChannel(id, input = {}) {
15794
16299
  const channel = await this.store.getChannel(id);
15795
16300
  if (!channel)
15796
16301
  throw new Error(`Channel not found: ${id}`);
@@ -15807,28 +16312,71 @@ class EventsClient {
15807
16312
  time: input.time,
15808
16313
  id: input.id
15809
16314
  });
16315
+ const matched = channelMatchesEvent(channel, event);
16316
+ return {
16317
+ channelId: channel.id,
16318
+ matched,
16319
+ event,
16320
+ filters: channel.filters,
16321
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
16322
+ };
16323
+ }
16324
+ async testChannel(id, input = {}, options = {}) {
16325
+ const channel = await this.store.getChannel(id);
16326
+ if (!channel)
16327
+ throw new Error(`Channel not found: ${id}`);
16328
+ const match = await this.matchChannel(id, input);
16329
+ const event = match.event;
16330
+ if (options.honorFilters && !match.matched) {
16331
+ const timestamp = new Date().toISOString();
16332
+ const result2 = createDeliveryResult(event, channel, [{
16333
+ attempt: 1,
16334
+ status: "skipped",
16335
+ startedAt: timestamp,
16336
+ completedAt: timestamp,
16337
+ error: match.reason
16338
+ }]);
16339
+ result2.metadata = { reason: "filter_mismatch" };
16340
+ await this.store.appendDelivery(result2);
16341
+ return result2;
16342
+ }
15810
16343
  const eventForChannel = await this.applyRedaction(event, channel);
15811
16344
  const result = await this.deliverWithRetry(eventForChannel, channel);
15812
16345
  await this.store.appendDelivery(result);
15813
16346
  return result;
15814
16347
  }
15815
16348
  async replay(options = {}) {
15816
- const events = (await this.store.listEvents()).filter((event) => {
15817
- if (options.eventId && event.id !== options.eventId)
15818
- return false;
15819
- if (options.source && event.source !== options.source)
15820
- return false;
15821
- if (options.type && event.type !== options.type)
15822
- return false;
15823
- return true;
15824
- });
16349
+ const page = options.cursor || options.limit !== undefined ? await this.listEventsPage(options) : { events: await this.listEvents(options), hasMore: false };
15825
16350
  if (options.dryRun)
15826
- return { events, deliveries: [] };
16351
+ return { events: page.events, deliveries: [], cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
15827
16352
  const deliveries = [];
15828
- for (const event of events) {
16353
+ for (const event of page.events) {
15829
16354
  deliveries.push(...await this.deliver(event));
15830
16355
  }
15831
- return { events, deliveries };
16356
+ return { events: page.events, deliveries, cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
16357
+ }
16358
+ async appendEvent(event, options) {
16359
+ if (this.store.appendEventOnce) {
16360
+ return this.store.appendEventOnce(event, { dedupe: options.dedupe });
16361
+ }
16362
+ if (options.dedupe) {
16363
+ const existing = await this.store.findEventByIdentity({ id: event.id, dedupeKey: event.dedupeKey });
16364
+ if (existing) {
16365
+ return {
16366
+ event: existing,
16367
+ stored: false,
16368
+ deduped: true,
16369
+ identity: { id: existing.id, dedupeKey: existing.dedupeKey }
16370
+ };
16371
+ }
16372
+ }
16373
+ const stored = await this.store.appendEvent(event);
16374
+ return {
16375
+ event: stored,
16376
+ stored: true,
16377
+ deduped: false,
16378
+ identity: { id: stored.id, dedupeKey: stored.dedupeKey }
16379
+ };
15832
16380
  }
15833
16381
  async applyRedaction(event, channel) {
15834
16382
  let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
@@ -15855,15 +16403,6 @@ class EventsClient {
15855
16403
  return createDeliveryResult(event, channel, attempts);
15856
16404
  }
15857
16405
  }
15858
- function redactPaths(event, paths, replacement = "[REDACTED]") {
15859
- if (paths.length === 0)
15860
- return event;
15861
- const copy = structuredClone(event);
15862
- for (const path of paths) {
15863
- setPath(copy, path, replacement);
15864
- }
15865
- return copy;
15866
- }
15867
16406
  function sanitizeChannelForOutput(channel) {
15868
16407
  const copy = structuredClone(channel);
15869
16408
  if (copy.webhook?.secret)
@@ -15876,34 +16415,19 @@ function sanitizeChannelForOutput(channel) {
15876
16415
  function sanitizeChannelsForOutput(channels) {
15877
16416
  return channels.map(sanitizeChannelForOutput);
15878
16417
  }
15879
- function redactSensitiveKeys(event, replacement = "[REDACTED]") {
15880
- return redactValue(event, replacement);
15881
- }
15882
- function shouldRedactKey(key) {
15883
- return /secret|token|password|api[_-]?key|authorization/i.test(key);
15884
- }
15885
- function redactValue(value, replacement) {
15886
- if (Array.isArray(value))
15887
- return value.map((item) => redactValue(item, replacement));
15888
- if (!value || typeof value !== "object")
15889
- return value;
15890
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
15891
- key,
15892
- shouldRedactKey(key) ? replacement : redactValue(item, replacement)
15893
- ]));
15894
- }
15895
- function setPath(input, path, replacement) {
15896
- const parts = path.split(".");
15897
- let cursor = input;
15898
- for (const part of parts.slice(0, -1)) {
15899
- const next = cursor[part];
15900
- if (!next || typeof next !== "object")
15901
- return;
15902
- cursor = next;
15903
- }
15904
- const last = parts.at(-1);
15905
- if (last && last in cursor)
15906
- cursor[last] = replacement;
16418
+ function queryClientEvents(events, options) {
16419
+ let rows = events;
16420
+ if (options.eventId)
16421
+ rows = rows.filter((event) => event.id === options.eventId);
16422
+ if (options.source)
16423
+ rows = rows.filter((event) => event.source === options.source);
16424
+ if (options.type)
16425
+ rows = rows.filter((event) => event.type === options.type);
16426
+ if (options.cursor)
16427
+ rows = rows.slice(decodeLocalJsonEventCursor(options.cursor, options));
16428
+ if (options.limit !== undefined)
16429
+ rows = rows.slice(0, normalizeEventPageLimit(options.limit));
16430
+ return rows;
15907
16431
  }
15908
16432
  function normalizeTime(value) {
15909
16433
  if (!value)
@@ -15917,6 +16441,77 @@ function normalizeRetryPolicy(policy) {
15917
16441
  multiplier: Math.max(1, policy?.multiplier ?? 2)
15918
16442
  };
15919
16443
  }
16444
+ function parseFieldMatchers(values, label, typed = false) {
16445
+ if (!values?.length)
16446
+ return;
16447
+ const result = {};
16448
+ for (const value of values) {
16449
+ const parsed = parseMatcherExpression(value, label);
16450
+ const path = parsed.path;
16451
+ if (path in result)
16452
+ throw new Error(`Duplicate ${label} filter path: ${path}`);
16453
+ const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
16454
+ result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
16455
+ }
16456
+ return result;
16457
+ }
16458
+ function parseFilterOptions(options) {
16459
+ const filter2 = {};
16460
+ if (options.source)
16461
+ filter2.source = options.source;
16462
+ if (options.type)
16463
+ filter2.type = options.type;
16464
+ if (options.subject)
16465
+ filter2.subject = options.subject;
16466
+ if (options.severity)
16467
+ filter2.severity = options.severity;
16468
+ const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
16469
+ const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
16470
+ if (Object.keys(data).length > 0)
16471
+ filter2.data = data;
16472
+ if (Object.keys(metadata).length > 0)
16473
+ filter2.metadata = metadata;
16474
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
16475
+ }
16476
+ function mergeMatchers(...records) {
16477
+ const result = {};
16478
+ for (const record of records) {
16479
+ if (!record)
16480
+ continue;
16481
+ for (const [path, value] of Object.entries(record)) {
16482
+ if (path in result)
16483
+ throw new Error(`Duplicate filter path: ${path}`);
16484
+ result[path] = value;
16485
+ }
16486
+ }
16487
+ return result;
16488
+ }
16489
+ function parseTypedMatcherValue(value, label) {
16490
+ const parsed = JSON.parse(value);
16491
+ if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
16492
+ return parsed;
16493
+ }
16494
+ throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
16495
+ }
16496
+ function parseMatcherExpression(value, label) {
16497
+ const negativeSeparator = value.indexOf("!=");
16498
+ if (negativeSeparator > 0) {
16499
+ return {
16500
+ path: value.slice(0, negativeSeparator),
16501
+ rawValue: value.slice(negativeSeparator + 2),
16502
+ negated: true
16503
+ };
16504
+ }
16505
+ const separator = value.indexOf("=");
16506
+ if (separator <= 0)
16507
+ throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
16508
+ return {
16509
+ path: value.slice(0, separator),
16510
+ rawValue: value.slice(separator + 1),
16511
+ negated: false
16512
+ };
16513
+ }
16514
+ var DEFAULT_EVENT_LIST_LIMIT = 100;
15920
16515
  function parseJsonObject(value, fallback) {
15921
16516
  if (!value)
15922
16517
  return fallback;
@@ -15938,18 +16533,6 @@ function parseHeaders(values) {
15938
16533
  }
15939
16534
  return headers;
15940
16535
  }
15941
- function parseFilter(options) {
15942
- const filter2 = {};
15943
- if (options.source)
15944
- filter2.source = options.source;
15945
- if (options.type)
15946
- filter2.type = options.type;
15947
- if (options.subject)
15948
- filter2.subject = options.subject;
15949
- if (options.severity)
15950
- filter2.severity = options.severity;
15951
- return Object.keys(filter2).length > 0 ? [filter2] : undefined;
15952
- }
15953
16536
  function createClient(options) {
15954
16537
  if (options.createClient)
15955
16538
  return options.createClient();
@@ -15961,22 +16544,30 @@ function print(value, json, text) {
15961
16544
  else
15962
16545
  console.log(text);
15963
16546
  }
16547
+ function fail(error, json) {
16548
+ const message = error instanceof Error ? error.message : String(error);
16549
+ if (json)
16550
+ console.log(JSON.stringify({ error: message }, null, 2));
16551
+ else
16552
+ console.error(message);
16553
+ process.exitCode = 1;
16554
+ }
15964
16555
  function hasJsonOption(options) {
15965
16556
  return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
15966
16557
  }
15967
16558
  function wantsJson(actionOptions, command) {
15968
16559
  return hasJsonOption(actionOptions) || hasJsonOption(command);
15969
16560
  }
15970
- function registerWebhookCommands(program, options) {
15971
- const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
15972
- webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
16561
+ function registerChannelCommands(program, options) {
16562
+ const channels = program.command(options.channelsCommandName ?? "channels").description("Manage Hasna event channels");
16563
+ channels.command("add").description("Add or replace a channel").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--data <path=value...>", "Event data field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
15973
16564
  const timestamp = new Date().toISOString();
15974
16565
  const channel = {
15975
16566
  id: actionOptions.id,
15976
16567
  name: actionOptions.name,
15977
16568
  enabled: !actionOptions.disabled,
15978
16569
  transport: actionOptions.transport,
15979
- filters: parseFilter(actionOptions),
16570
+ filters: parseFilterOptions(actionOptions),
15980
16571
  retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
15981
16572
  redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
15982
16573
  createdAt: timestamp,
@@ -15992,35 +16583,61 @@ function registerWebhookCommands(program, options) {
15992
16583
  const saved = await createClient(options).addChannel(channel);
15993
16584
  print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
15994
16585
  });
15995
- webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
15996
- const channels = await createClient(options).listChannels();
16586
+ channels.command("list").description("List configured channels").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16587
+ const channels2 = await createClient(options).listChannels();
15997
16588
  if (wantsJson(actionOptions, command)) {
15998
- console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
16589
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
15999
16590
  return;
16000
16591
  }
16001
- if (!channels.length) {
16592
+ if (!channels2.length) {
16002
16593
  console.log("No channels configured.");
16003
16594
  return;
16004
16595
  }
16005
- for (const channel of channels) {
16596
+ for (const channel of channels2) {
16006
16597
  console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
16007
16598
  }
16008
16599
  });
16009
- webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16600
+ channels.command("status").description("Show events channel storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16601
+ const status = await getEventsStatus(options.dataDir);
16602
+ print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
16603
+ });
16604
+ channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16010
16605
  const removed = await createClient(options).removeChannel(id);
16011
16606
  print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
16012
16607
  });
16013
- webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16014
- const result = await createClient(options).testChannel(id, {
16015
- source: options.source,
16016
- type: actionOptions.type,
16017
- subject: actionOptions.subject ?? id,
16018
- message: actionOptions.message,
16019
- data: parseJsonObject(actionOptions.data, { test: true })
16020
- });
16021
- print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
16608
+ channels.command("test").description("Send a test event to one channel").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--honor-filters", "Skip delivery when the sample event does not match channel filters", false).option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16609
+ const json = wantsJson(actionOptions, command);
16610
+ try {
16611
+ const result = await createClient(options).testChannel(id, {
16612
+ source: actionOptions.source ?? options.source,
16613
+ type: actionOptions.type,
16614
+ subject: actionOptions.subject ?? id,
16615
+ message: actionOptions.message,
16616
+ data: parseJsonObject(actionOptions.data, { test: true }),
16617
+ metadata: parseJsonObject(actionOptions.metadata, {})
16618
+ }, { honorFilters: actionOptions.honorFilters });
16619
+ print(result, json, `${result.status}: ${result.channelId}`);
16620
+ } catch (error) {
16621
+ fail(error, json);
16622
+ }
16623
+ });
16624
+ channels.command("match").description("Check whether a sample event matches one channel without delivering").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events match preview").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16625
+ const json = wantsJson(actionOptions, command);
16626
+ try {
16627
+ const result = await createClient(options).matchChannel(id, {
16628
+ source: actionOptions.source ?? options.source,
16629
+ type: actionOptions.type,
16630
+ subject: actionOptions.subject ?? id,
16631
+ message: actionOptions.message,
16632
+ data: parseJsonObject(actionOptions.data, { test: true }),
16633
+ metadata: parseJsonObject(actionOptions.metadata, {})
16634
+ });
16635
+ print(result, json, `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
16636
+ } catch (error) {
16637
+ fail(error, json);
16638
+ }
16022
16639
  });
16023
- return webhooks;
16640
+ return channels;
16024
16641
  }
16025
16642
  function registerEventCommands(program, options) {
16026
16643
  const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
@@ -16037,7 +16654,8 @@ function registerEventCommands(program, options) {
16037
16654
  }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
16038
16655
  print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
16039
16656
  });
16040
- events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16657
+ const defaultListLimit = options.defaultEventListLimit ?? DEFAULT_EVENT_LIST_LIMIT;
16658
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", `Limit to the most recent <n> events (default ${defaultListLimit}; use 0 for all)`, parseNumber, defaultListLimit).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16041
16659
  let rows = await createClient(options).listEvents();
16042
16660
  if (actionOptions.source)
16043
16661
  rows = rows.filter((event) => event.source === actionOptions.source);
@@ -16056,19 +16674,21 @@ function registerEventCommands(program, options) {
16056
16674
  for (const event of rows)
16057
16675
  console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
16058
16676
  });
16059
- events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16677
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--cursor <cursor>", "Opaque replay cursor from a previous page").option("--limit <n>", "Maximum events to replay", parseNumber).option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16060
16678
  const result = await createClient(options).replay({
16061
16679
  eventId: actionOptions.id,
16062
16680
  source: actionOptions.source,
16063
16681
  type: actionOptions.type,
16682
+ cursor: actionOptions.cursor,
16683
+ limit: actionOptions.limit,
16064
16684
  dryRun: actionOptions.dryRun
16065
16685
  });
16066
- print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
16686
+ print(result, wantsJson(actionOptions, command), replaySummary(result.events.length, result.deliveries.length, result.nextCursor));
16067
16687
  });
16068
16688
  return events;
16069
16689
  }
16070
16690
  function registerEventsCommands(program, options) {
16071
- registerWebhookCommands(program, options);
16691
+ registerChannelCommands(program, options);
16072
16692
  registerEventCommands(program, options);
16073
16693
  }
16074
16694
  function parseNumber(value) {
@@ -16081,8 +16701,12 @@ function collectValues(value, previous) {
16081
16701
  previous.push(value);
16082
16702
  return previous;
16083
16703
  }
16704
+ function replaySummary(events, deliveries, nextCursor) {
16705
+ const suffix = nextCursor ? `, next cursor: ${nextCursor}` : "";
16706
+ return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
16707
+ }
16084
16708
 
16085
- // node_modules/commander/esm.mjs
16709
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
16086
16710
  var import__ = __toESM(require_commander(), 1);
16087
16711
  var {
16088
16712
  program,
@@ -16101,9 +16725,9 @@ var {
16101
16725
  // src/cli/index.tsx
16102
16726
  init_apply();
16103
16727
  import chalk from "chalk";
16104
- import { existsSync as existsSync16, lstatSync as lstatSync5, readFileSync as readFileSync13, readSync, writeSync } from "fs";
16105
- import { homedir as homedir8 } from "os";
16106
- import { basename as basename7, join as join16, resolve as resolve10 } from "path";
16728
+ import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
16729
+ import { homedir as homedir10 } from "os";
16730
+ import { basename as basename7, join as join19, resolve as resolve13 } from "path";
16107
16731
 
16108
16732
  // src/lib/config-target-identity.ts
16109
16733
  init_apply();
@@ -16150,14 +16774,14 @@ init_redact();
16150
16774
  // src/lib/export.ts
16151
16775
  init_config_store();
16152
16776
  import { existsSync as existsSync10, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
16153
- import { join as join11, resolve as resolve6 } from "path";
16777
+ import { join as join13, resolve as resolve8 } from "path";
16154
16778
  import { tmpdir } from "os";
16155
16779
  async function exportConfigs(outputPath, opts = {}) {
16156
16780
  const store = opts.store ?? resolveConfigStore();
16157
16781
  const configs = await store.listConfigs(opts.filter);
16158
- const absOutput = resolve6(outputPath);
16159
- const tmpDir = join11(tmpdir(), `configs-export-${Date.now()}`);
16160
- const contentsDir = join11(tmpDir, "contents");
16782
+ const absOutput = resolve8(outputPath);
16783
+ const tmpDir = join13(tmpdir(), `configs-export-${Date.now()}`);
16784
+ const contentsDir = join13(tmpDir, "contents");
16161
16785
  try {
16162
16786
  mkdirSync4(contentsDir, { recursive: true });
16163
16787
  const manifest = {
@@ -16165,10 +16789,10 @@ async function exportConfigs(outputPath, opts = {}) {
16165
16789
  exported_at: new Date().toISOString(),
16166
16790
  configs: configs.map(({ content: _content, ...meta }) => meta)
16167
16791
  };
16168
- writeFileSync3(join11(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
16792
+ writeFileSync3(join13(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
16169
16793
  for (const config of configs) {
16170
16794
  const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
16171
- writeFileSync3(join11(contentsDir, fileName), config.content, "utf-8");
16795
+ writeFileSync3(join13(contentsDir, fileName), config.content, "utf-8");
16172
16796
  }
16173
16797
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
16174
16798
  stdout: "pipe",
@@ -16189,14 +16813,14 @@ async function exportConfigs(outputPath, opts = {}) {
16189
16813
 
16190
16814
  // src/lib/import.ts
16191
16815
  init_config_store();
16192
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
16193
- import { join as join12, resolve as resolve7 } from "path";
16816
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
16817
+ import { join as join14, resolve as resolve9 } from "path";
16194
16818
  import { tmpdir as tmpdir2 } from "os";
16195
16819
  async function importConfigs(bundlePath, opts = {}) {
16196
16820
  const store = opts.store ?? resolveConfigStore();
16197
16821
  const conflict = opts.conflict ?? "skip";
16198
- const absPath = resolve7(bundlePath);
16199
- const tmpDir = join12(tmpdir2(), `configs-import-${Date.now()}`);
16822
+ const absPath = resolve9(bundlePath);
16823
+ const tmpDir = join14(tmpdir2(), `configs-import-${Date.now()}`);
16200
16824
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
16201
16825
  try {
16202
16826
  mkdirSync5(tmpDir, { recursive: true });
@@ -16209,15 +16833,15 @@ async function importConfigs(bundlePath, opts = {}) {
16209
16833
  const stderr = await new Response(proc.stderr).text();
16210
16834
  throw new Error(`tar extraction failed: ${stderr}`);
16211
16835
  }
16212
- const manifestPath = join12(tmpDir, "manifest.json");
16836
+ const manifestPath = join14(tmpDir, "manifest.json");
16213
16837
  if (!existsSync11(manifestPath))
16214
16838
  throw new Error("Invalid bundle: missing manifest.json");
16215
- const manifest = JSON.parse(readFileSync8(manifestPath, "utf-8"));
16839
+ const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
16216
16840
  for (const meta of manifest.configs) {
16217
16841
  try {
16218
16842
  const ext = meta.format === "text" ? "txt" : meta.format;
16219
- const contentFile = join12(tmpDir, "contents", `${meta.slug}.${ext}`);
16220
- const content = existsSync11(contentFile) ? readFileSync8(contentFile, "utf-8") : "";
16843
+ const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
16844
+ const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
16221
16845
  let existing = null;
16222
16846
  try {
16223
16847
  existing = await store.getConfig(meta.slug);
@@ -16265,16 +16889,17 @@ init_machine();
16265
16889
  init_project_context();
16266
16890
  init_session_render();
16267
16891
  init_cursor_authority();
16268
- import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
16892
+ init_session_authority();
16893
+ import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
16269
16894
  import {
16270
16895
  existsSync as existsSync12,
16271
- lstatSync as lstatSync3,
16896
+ lstatSync as lstatSync4,
16272
16897
  mkdirSync as mkdirSync6,
16273
- readFileSync as readFileSync9,
16898
+ readFileSync as readFileSync10,
16274
16899
  readdirSync as readdirSync3,
16275
- statSync as statSync5
16900
+ statSync as statSync6
16276
16901
  } from "fs";
16277
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, parse as parse4, relative as relative5, resolve as resolve8 } from "path";
16902
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
16278
16903
 
16279
16904
  class SessionApplyError extends Error {
16280
16905
  constructor(message) {
@@ -16295,6 +16920,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16295
16920
  }
16296
16921
  assertCursorAuthorityUnchanged(plan);
16297
16922
  const targetHome = assertSafeTargetHome(plan.targetHome);
16923
+ assertClaudeAuthorityStillClear(plan, targetHome);
16298
16924
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
16299
16925
  const files = [...payloadFiles, plan.manifestFile];
16300
16926
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
@@ -16395,6 +17021,15 @@ function assertCursorAuthorityUnchanged(plan) {
16395
17021
  throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
16396
17022
  }
16397
17023
  }
17024
+ function assertClaudeAuthorityStillClear(plan, targetHome) {
17025
+ if (plan.tool !== "claude" || plan.targetKind === "blocked")
17026
+ return;
17027
+ const conflicts = detectClaudeAuthorityConflicts(targetHome);
17028
+ if (conflicts.length === 0)
17029
+ return;
17030
+ const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
17031
+ throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
17032
+ }
16398
17033
  function ensureSessionTargetHome(targetHome) {
16399
17034
  if (!existsSync12(targetHome))
16400
17035
  mkdirSync6(targetHome, { recursive: true, mode: 448 });
@@ -16402,7 +17037,7 @@ function ensureSessionTargetHome(targetHome) {
16402
17037
  }
16403
17038
  function checkSessionRenderDrift(targetHome, manifestPath) {
16404
17039
  const safeTargetHome = assertSafeTargetHome(targetHome);
16405
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve8(manifestPath)), safeTargetHome) : resolve8(safeTargetHome, ".hasna", "session-render-manifest.json");
17040
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve10(manifestPath)), safeTargetHome) : resolve10(safeTargetHome, ".hasna", "session-render-manifest.json");
16406
17041
  const checkedAt = new Date().toISOString();
16407
17042
  const previousManifest = readPreviousManifest(resolvedManifestPath);
16408
17043
  if (!previousManifest) {
@@ -16429,7 +17064,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16429
17064
  });
16430
17065
  continue;
16431
17066
  }
16432
- const actualSha256 = sha2567(readFileSync9(target, "utf-8"));
17067
+ const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
16433
17068
  if (actualSha256 !== file.sha256) {
16434
17069
  drifted.push({
16435
17070
  path: target,
@@ -16452,7 +17087,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16452
17087
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
16453
17088
  const snapshot = readSessionRenderSnapshot(snapshotPath);
16454
17089
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
16455
- const resolvedSnapshotPath = resolve8(snapshotPath);
17090
+ const resolvedSnapshotPath = resolve10(snapshotPath);
16456
17091
  const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
16457
17092
  if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
16458
17093
  throw new SessionApplyError("Session snapshot must be stored inside its target home.");
@@ -16570,19 +17205,19 @@ function requiredRestoreHash(file) {
16570
17205
  return file.previousSha256;
16571
17206
  }
16572
17207
  function readSessionRenderSnapshot(snapshotPath) {
16573
- const resolved = resolve8(snapshotPath);
17208
+ const resolved = resolve10(snapshotPath);
16574
17209
  if (!existsSync12(resolved))
16575
17210
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
16576
- const stat = lstatSync3(resolved);
17211
+ const stat = lstatSync4(resolved);
16577
17212
  if (stat.isSymbolicLink() || !stat.isFile()) {
16578
17213
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
16579
17214
  }
16580
- if (statSync5(resolved).size > 32 * 1024 * 1024) {
17215
+ if (statSync6(resolved).size > 32 * 1024 * 1024) {
16581
17216
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
16582
17217
  }
16583
17218
  let parsed;
16584
17219
  try {
16585
- parsed = JSON.parse(readFileSync9(resolved, "utf8"));
17220
+ parsed = JSON.parse(readFileSync10(resolved, "utf8"));
16586
17221
  } catch {
16587
17222
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
16588
17223
  }
@@ -16604,7 +17239,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16604
17239
  const previousManifest = snapshot.previousManifest;
16605
17240
  const previousFiles = new Map;
16606
17241
  for (const file of snapshot.files) {
16607
- if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2567(file.content) !== file.sha256) {
17242
+ if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2568(file.content) !== file.sha256) {
16608
17243
  throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
16609
17244
  }
16610
17245
  resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
@@ -16651,7 +17286,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16651
17286
  }
16652
17287
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
16653
17288
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
16654
- const manifestPath = resolve8(snapshot.manifestPath);
17289
+ const manifestPath = resolve10(snapshot.manifestPath);
16655
17290
  const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
16656
17291
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
16657
17292
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
@@ -16660,7 +17295,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
16660
17295
  }
16661
17296
  let parsedManifest;
16662
17297
  try {
16663
- parsedManifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
17298
+ parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
16664
17299
  } catch {
16665
17300
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
16666
17301
  }
@@ -16668,7 +17303,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
16668
17303
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
16669
17304
  }
16670
17305
  const appliedManifest = parsedManifest;
16671
- if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve8(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
17306
+ if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve10(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
16672
17307
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
16673
17308
  }
16674
17309
  const afterFiles = [];
@@ -16753,16 +17388,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
16753
17388
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
16754
17389
  }
16755
17390
  for (const entry of readdirSync3(dirname5(snapshotPath))) {
16756
- const candidatePath = resolve8(dirname5(snapshotPath), entry);
16757
- if (candidatePath === resolve8(snapshotPath) || !entry.endsWith(".json"))
17391
+ const candidatePath = resolve10(dirname5(snapshotPath), entry);
17392
+ if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
16758
17393
  continue;
16759
- const candidateStat = lstatSync3(candidatePath);
17394
+ const candidateStat = lstatSync4(candidatePath);
16760
17395
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
16761
17396
  continue;
16762
17397
  try {
16763
- const candidate = JSON.parse(readFileSync9(candidatePath, "utf8"));
17398
+ const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
16764
17399
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
16765
- if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve8(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
17400
+ if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve10(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
16766
17401
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
16767
17402
  }
16768
17403
  } catch (error) {
@@ -16820,7 +17455,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
16820
17455
  return "create";
16821
17456
  }
16822
17457
  if (file.role === "manifest" && previousManifest) {
16823
- const previousManifestSha256 = sha2567(`${JSON.stringify(previousManifest, null, 2)}
17458
+ const previousManifestSha256 = sha2568(`${JSON.stringify(previousManifest, null, 2)}
16824
17459
  `);
16825
17460
  if (previousManifestSha256 !== file.sha256) {
16826
17461
  throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
@@ -16831,15 +17466,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
16831
17466
  }
16832
17467
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
16833
17468
  const path = resolveManifestRelativePath(relativePath, targetHome);
16834
- if (resolve8(recordedPath) !== path) {
17469
+ if (resolve10(recordedPath) !== path) {
16835
17470
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
16836
17471
  }
16837
17472
  return path;
16838
17473
  }
16839
17474
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
16840
17475
  const target = resolvePlannedFilePath(plan, file, targetHome);
16841
- const previousContent = existsSync12(target) ? readFileSync9(target, "utf-8") : null;
16842
- const previousSha256 = previousContent === null ? null : sha2567(previousContent);
17476
+ const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
17477
+ const previousSha256 = previousContent === null ? null : sha2568(previousContent);
16843
17478
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
16844
17479
  const changed = previousContent !== file.content;
16845
17480
  if (previousContent !== null && !options.force && !previouslyManaged) {
@@ -16936,8 +17571,8 @@ function planStaleFileResult(file, targetHome, options) {
16936
17571
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
16937
17572
  if (!existsSync12(target))
16938
17573
  return null;
16939
- const previousContent = readFileSync9(target, "utf-8");
16940
- const previousSha256 = sha2567(previousContent);
17574
+ const previousContent = readFileSync10(target, "utf-8");
17575
+ const previousSha256 = sha2568(previousContent);
16941
17576
  if (!options.force && previousSha256 !== file.sha256) {
16942
17577
  return {
16943
17578
  path: target,
@@ -16981,19 +17616,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
16981
17616
  return previousHashes.get(file.relativePath) === previousSha256;
16982
17617
  }
16983
17618
  function resolvePlannedFilePath(plan, file, targetHome) {
16984
- const target = resolve8(targetHome, ...file.relativePath.split("/"));
17619
+ const target = resolve10(targetHome, ...file.relativePath.split("/"));
16985
17620
  const rel = relative5(targetHome, target);
16986
17621
  if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16987
17622
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
16988
17623
  }
16989
- if (resolve8(file.path) !== target) {
17624
+ if (resolve10(file.path) !== target) {
16990
17625
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
16991
17626
  }
16992
17627
  assertNoSymlinkSegments2(targetHome, target);
16993
17628
  return target;
16994
17629
  }
16995
17630
  function resolveManifestRelativePath(relativePath, targetHome) {
16996
- const target = resolve8(targetHome, ...relativePath.split(/[\\/]+/));
17631
+ const target = resolve10(targetHome, ...relativePath.split(/[\\/]+/));
16997
17632
  const rel = relative5(targetHome, target);
16998
17633
  if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16999
17634
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
@@ -17005,7 +17640,7 @@ function readPreviousManifest(path) {
17005
17640
  if (!existsSync12(path))
17006
17641
  return null;
17007
17642
  try {
17008
- const parsed = JSON.parse(readFileSync9(path, "utf-8"));
17643
+ const parsed = JSON.parse(readFileSync10(path, "utf-8"));
17009
17644
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
17010
17645
  return null;
17011
17646
  if (!Array.isArray(parsed.files))
@@ -17046,11 +17681,11 @@ function currentSessionFileHash(path, targetHome) {
17046
17681
  assertNoSymlinkSegments2(targetHome, path);
17047
17682
  if (!existsSync12(path))
17048
17683
  return null;
17049
- const stat = lstatSync3(path);
17684
+ const stat = lstatSync4(path);
17050
17685
  if (stat.isSymbolicLink() || !stat.isFile()) {
17051
17686
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
17052
17687
  }
17053
- return sha2567(readFileSync9(path, "utf-8"));
17688
+ return sha2568(readFileSync10(path, "utf-8"));
17054
17689
  }
17055
17690
  function requiredPreviousHash(result) {
17056
17691
  if (result.previousSha256 === null) {
@@ -17060,12 +17695,12 @@ function requiredPreviousHash(result) {
17060
17695
  }
17061
17696
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
17062
17697
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync12(result.path)).map((result) => {
17063
- const content = readFileSync9(result.path, "utf-8");
17698
+ const content = readFileSync10(result.path, "utf-8");
17064
17699
  return {
17065
17700
  path: result.path,
17066
17701
  relativePath: result.relativePath,
17067
17702
  role: result.role,
17068
- sha256: sha2567(content),
17703
+ sha256: sha2568(content),
17069
17704
  content
17070
17705
  };
17071
17706
  });
@@ -17078,7 +17713,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17078
17713
  };
17079
17714
  }
17080
17715
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17081
- const snapshotPath = resolve8(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
17716
+ const snapshotPath = resolve10(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
17082
17717
  const afterFiles = results.map((result) => {
17083
17718
  if (result.action === "conflict") {
17084
17719
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -17126,12 +17761,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17126
17761
  function assertSafeTargetHome(targetHome) {
17127
17762
  if (!isAbsolute4(targetHome))
17128
17763
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
17129
- const normalized = resolve8(targetHome);
17764
+ const normalized = resolve10(targetHome);
17130
17765
  if (normalized === parse4(normalized).root) {
17131
17766
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17132
17767
  }
17133
17768
  assertNoSymlinkAncestors2(normalized);
17134
- if (existsSync12(normalized) && lstatSync3(normalized).isSymbolicLink()) {
17769
+ if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
17135
17770
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17136
17771
  }
17137
17772
  return normalized;
@@ -17141,32 +17776,33 @@ function assertNoSymlinkSegments2(root, target) {
17141
17776
  const rel = relative5(root, target);
17142
17777
  let current = root;
17143
17778
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17144
- current = join13(current, segment);
17145
- if (existsSync12(current) && lstatSync3(current).isSymbolicLink()) {
17779
+ current = join15(current, segment);
17780
+ if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
17146
17781
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17147
17782
  }
17148
17783
  }
17149
17784
  }
17150
17785
  function assertNoSymlinkAncestors2(path) {
17151
- const normalized = resolve8(path);
17786
+ const normalized = resolve10(path);
17152
17787
  const parsed = parse4(normalized);
17153
17788
  let current = parsed.root;
17154
17789
  const rel = relative5(parsed.root, normalized);
17155
17790
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17156
- current = join13(current, segment);
17791
+ current = join15(current, segment);
17157
17792
  if (!existsSync12(current))
17158
17793
  return;
17159
- if (lstatSync3(current).isSymbolicLink()) {
17794
+ if (lstatSync4(current).isSymbolicLink()) {
17160
17795
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
17161
17796
  }
17162
17797
  }
17163
17798
  }
17164
- function sha2567(content) {
17165
- return createHash7("sha256").update(content).digest("hex");
17799
+ function sha2568(content) {
17800
+ return createHash8("sha256").update(content).digest("hex");
17166
17801
  }
17167
17802
 
17168
17803
  // src/cli/index.tsx
17169
17804
  init_session_render();
17805
+ init_raw_store_root();
17170
17806
  init_asset_plan();
17171
17807
  init_instruction_graph();
17172
17808
 
@@ -17513,34 +18149,433 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
17513
18149
 
17514
18150
  // src/cli/index.tsx
17515
18151
  init_codewith_shared_todos_storage_standard();
18152
+
18153
+ // src/lib/managed-skill-runtimes.ts
18154
+ import { createHash as createHash9 } from "crypto";
18155
+ import { spawnSync } from "child_process";
18156
+ import {
18157
+ existsSync as existsSync13,
18158
+ lstatSync as lstatSync5,
18159
+ mkdirSync as mkdirSync7,
18160
+ readFileSync as readFileSync11,
18161
+ renameSync as renameSync2,
18162
+ rmSync as rmSync5,
18163
+ writeFileSync as writeFileSync4
18164
+ } from "fs";
18165
+ import { homedir as homedir8 } from "os";
18166
+ import { dirname as dirname6, join as join16, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
18167
+ var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
18168
+ var INBOX_SKILL_MARKERS = [
18169
+ [".claude", "skills", "inbox", "SKILL.md"],
18170
+ [".codex", "skills", "inbox", "SKILL.md"],
18171
+ [".codewith", "skills", "inbox", "SKILL.md"],
18172
+ [".config", "opencode", "skills", "inbox", "SKILL.md"],
18173
+ [".cursor", "skills", "inbox", "SKILL.md"]
18174
+ ];
18175
+ var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
18176
+ function sha2569(content) {
18177
+ return createHash9("sha256").update(content).digest("hex");
18178
+ }
18179
+ function lstatOrNull(path) {
18180
+ try {
18181
+ return lstatSync5(path);
18182
+ } catch {
18183
+ return null;
18184
+ }
18185
+ }
18186
+ function findSymlinkedAncestor(path) {
18187
+ const normalized = resolve11(path);
18188
+ const parsed = parse5(normalized);
18189
+ let current = parsed.root;
18190
+ const rel = relative6(parsed.root, normalized);
18191
+ for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18192
+ current = join16(current, segment);
18193
+ if (!existsSync13(current))
18194
+ return null;
18195
+ if (lstatSync5(current).isSymbolicLink())
18196
+ return current;
18197
+ }
18198
+ return null;
18199
+ }
18200
+ function assertNoSymlinkAncestors3(path) {
18201
+ const found = findSymlinkedAncestor(path);
18202
+ if (found !== null) {
18203
+ throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
18204
+ }
18205
+ }
18206
+ function packagedInboxSkillPath(explicitPath) {
18207
+ if (explicitPath)
18208
+ return explicitPath;
18209
+ const candidates = [
18210
+ join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
18211
+ join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
18212
+ join16(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
18213
+ ];
18214
+ const found = candidates.find((candidate) => existsSync13(candidate));
18215
+ if (!found) {
18216
+ throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
18217
+ }
18218
+ return found;
18219
+ }
18220
+ function readCanonicalSkill(explicitPath) {
18221
+ const assetPath = packagedInboxSkillPath(explicitPath);
18222
+ const stat = lstatOrNull(assetPath);
18223
+ if (!stat?.isFile()) {
18224
+ throw new Error("packaged inbox skill contract is not a regular file");
18225
+ }
18226
+ const content = readFileSync11(assetPath, "utf8");
18227
+ if (!content.includes("conversations watch --from <agent> --all")) {
18228
+ throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
18229
+ }
18230
+ if (!content.includes("There is no separate")) {
18231
+ throw new Error("packaged inbox skill contract does not retire the legacy executable");
18232
+ }
18233
+ return { content, sha256: sha2569(content) };
18234
+ }
18235
+ function runProbe(command, args) {
18236
+ const result = spawnSync(command, args, {
18237
+ encoding: "utf8",
18238
+ timeout: 5000,
18239
+ stdio: ["ignore", "pipe", "pipe"]
18240
+ });
18241
+ if (result.error || result.status !== 0) {
18242
+ return { ok: false, output: "" };
18243
+ }
18244
+ return {
18245
+ ok: true,
18246
+ output: `${result.stdout ?? ""}
18247
+ ${result.stderr ?? ""}`.trim()
18248
+ };
18249
+ }
18250
+ function parseVersion(output) {
18251
+ return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
18252
+ }
18253
+ function compareVersions(left, right) {
18254
+ const a = left.split(".").map(Number);
18255
+ const b = right.split(".").map(Number);
18256
+ for (let i = 0;i < Math.max(a.length, b.length); i++) {
18257
+ const delta = (a[i] ?? 0) - (b[i] ?? 0);
18258
+ if (delta !== 0)
18259
+ return delta;
18260
+ }
18261
+ return 0;
18262
+ }
18263
+ function inspectSkillMarkers(homeDir2) {
18264
+ return INBOX_SKILL_MARKERS.map((parts) => join16(homeDir2, ...parts)).map((path) => {
18265
+ const stat = lstatOrNull(path);
18266
+ if (!stat)
18267
+ return null;
18268
+ if (!stat.isFile()) {
18269
+ return { path, content: null, mode: null, regular: false };
18270
+ }
18271
+ return {
18272
+ path,
18273
+ content: readFileSync11(path, "utf8"),
18274
+ mode: stat.mode & 511,
18275
+ regular: true
18276
+ };
18277
+ }).filter((snapshot) => snapshot !== null);
18278
+ }
18279
+ function inspectInbox(options) {
18280
+ const homeDir2 = options.homeDir ?? homedir8();
18281
+ const runtimeCommand = options.conversationsCommand ?? "conversations";
18282
+ const snapshots = inspectSkillMarkers(homeDir2);
18283
+ const skillPresent = snapshots.length > 0;
18284
+ let canonicalContent = null;
18285
+ let canonicalSha256 = null;
18286
+ let assetError = null;
18287
+ try {
18288
+ const canonical = readCanonicalSkill(options.assetPath);
18289
+ canonicalContent = canonical.content;
18290
+ canonicalSha256 = canonical.sha256;
18291
+ } catch (error) {
18292
+ assetError = error instanceof Error ? error.message : String(error);
18293
+ }
18294
+ const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
18295
+ const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
18296
+ const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
18297
+ const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
18298
+ const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
18299
+ const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
18300
+ const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
18301
+ const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
18302
+ const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
18303
+ const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
18304
+ const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
18305
+ let reason = "skill not installed";
18306
+ if (skillPresent) {
18307
+ const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
18308
+ const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
18309
+ if (nonRegular)
18310
+ reason = "managed skill target is not a regular file";
18311
+ else if (symlinkAncestor)
18312
+ reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
18313
+ else if (assetError)
18314
+ reason = assetError;
18315
+ else if (!versionProbe.ok)
18316
+ reason = "conversations command unavailable";
18317
+ else if (!runtimeVersion)
18318
+ reason = "conversations version is unreadable";
18319
+ else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
18320
+ reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
18321
+ } else if (!helpProbe.ok)
18322
+ reason = "conversations watch help is unavailable";
18323
+ else if (!supportsFrom || !supportsAll || !supportsFullContent) {
18324
+ const missing = [
18325
+ !supportsFrom ? "--from" : null,
18326
+ !supportsAll ? "--all" : null,
18327
+ !supportsFullContent ? "--full-content" : null
18328
+ ].filter((flag) => flag !== null);
18329
+ reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
18330
+ } else if (staleMarkers.length > 0)
18331
+ reason = "skill contract stale";
18332
+ else if (hostedHeartbeat === "failed")
18333
+ reason = "hosted heartbeat failed; manual fallback required";
18334
+ else if (hostedHeartbeat === "unverified")
18335
+ reason = "hosted heartbeat unverified; manual fallback required";
18336
+ else if (!deliveryVerified)
18337
+ reason = "hosted heartbeat passed; channel and DM delivery verification required";
18338
+ else
18339
+ reason = "ready";
18340
+ }
18341
+ return {
18342
+ status: {
18343
+ skill: "inbox",
18344
+ runtime: "conversations watch",
18345
+ minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
18346
+ skill_present: skillPresent,
18347
+ skill_markers: snapshots.map((snapshot) => snapshot.path),
18348
+ skill_contracts_current: snapshots.length - staleMarkers.length,
18349
+ stale_skill_markers: staleMarkers,
18350
+ expected_skill_sha256: canonicalSha256,
18351
+ runtime_command: runtimeCommand,
18352
+ runtime_present: versionProbe.ok,
18353
+ runtime_version: runtimeVersion,
18354
+ watch_supports_from: supportsFrom,
18355
+ watch_supports_all: supportsAll,
18356
+ watch_supports_full_content: supportsFullContent,
18357
+ hosted_heartbeat: hostedHeartbeat,
18358
+ delivery_verified: deliveryVerified,
18359
+ manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
18360
+ healthy: !skillPresent || reason === "ready",
18361
+ reason
18362
+ },
18363
+ canonicalContent,
18364
+ snapshots
18365
+ };
18366
+ }
18367
+ function inspectManagedSkillRuntimes(options = {}) {
18368
+ const runtime = inspectInbox(options).status;
18369
+ const installed = runtime.skill_present ? [runtime] : [];
18370
+ return {
18371
+ runtimes: [runtime],
18372
+ skills_present: installed.length,
18373
+ healthy: installed.filter((item) => item.healthy).length,
18374
+ missing: installed.filter((item) => !item.healthy).length
18375
+ };
18376
+ }
18377
+ function runtimeReadyForWrite(status) {
18378
+ return status.runtime_present && status.runtime_version !== null && compareVersions(status.runtime_version, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && status.watch_supports_from && status.watch_supports_all && status.watch_supports_full_content;
18379
+ }
18380
+ function projectUpdatedStatus(status, contractCount) {
18381
+ const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
18382
+ return {
18383
+ ...status,
18384
+ skill_contracts_current: contractCount,
18385
+ stale_skill_markers: [],
18386
+ manual_fallback_ready: true,
18387
+ healthy,
18388
+ reason: healthy ? "ready" : status.hosted_heartbeat === "failed" ? "hosted heartbeat failed; manual fallback required" : status.hosted_heartbeat === "unverified" ? "hosted heartbeat unverified; manual fallback required" : "hosted heartbeat passed; channel and DM delivery verification required"
18389
+ };
18390
+ }
18391
+ function cleanup(path) {
18392
+ rmSync5(path, { force: true });
18393
+ }
18394
+ function writeAtomic(path, content, mode) {
18395
+ assertNoSymlinkAncestors3(dirname6(path));
18396
+ const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
18397
+ try {
18398
+ mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
18399
+ writeFileSync4(tempPath, content, { mode, flag: "wx" });
18400
+ renameSync2(tempPath, path);
18401
+ } finally {
18402
+ cleanup(tempPath);
18403
+ }
18404
+ }
18405
+ var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
18406
+ lstat: lstatOrNull,
18407
+ read: (path) => readFileSync11(path, "utf8"),
18408
+ write: writeAtomic
18409
+ };
18410
+ function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
18411
+ const written = [];
18412
+ try {
18413
+ for (const snapshot of snapshots) {
18414
+ const currentStat = fileOperations.lstat(snapshot.path);
18415
+ if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
18416
+ throw new Error("managed skill changed after inspection; refusing a stale write");
18417
+ }
18418
+ fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
18419
+ written.push(snapshot);
18420
+ }
18421
+ return { ok: true, error: null, rollback_conflicts: [] };
18422
+ } catch (error) {
18423
+ const rollbackConflicts = [];
18424
+ for (const snapshot of written.reverse()) {
18425
+ const currentStat = fileOperations.lstat(snapshot.path);
18426
+ if (!currentStat?.isFile()) {
18427
+ rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
18428
+ continue;
18429
+ }
18430
+ let currentContent;
18431
+ try {
18432
+ currentContent = fileOperations.read(snapshot.path);
18433
+ } catch {
18434
+ rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
18435
+ continue;
18436
+ }
18437
+ if (currentContent !== canonicalContent) {
18438
+ rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
18439
+ continue;
18440
+ }
18441
+ try {
18442
+ fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
18443
+ } catch {
18444
+ rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
18445
+ }
18446
+ }
18447
+ return {
18448
+ ok: false,
18449
+ error: error instanceof Error ? error.message : String(error),
18450
+ rollback_conflicts: rollbackConflicts
18451
+ };
18452
+ }
18453
+ }
18454
+ async function reconcileManagedSkillRuntimes(options = {}) {
18455
+ const dryRun = options.dryRun ?? false;
18456
+ const before = inspectInbox(options);
18457
+ const status = before.status;
18458
+ if (!status.skill_present) {
18459
+ return {
18460
+ runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
18461
+ changed: 0,
18462
+ failed: 0,
18463
+ dry_run: dryRun
18464
+ };
18465
+ }
18466
+ if (before.snapshots.some((snapshot) => !snapshot.regular)) {
18467
+ return {
18468
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18469
+ changed: 0,
18470
+ failed: 1,
18471
+ dry_run: dryRun
18472
+ };
18473
+ }
18474
+ const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
18475
+ if (symlinkedAncestor) {
18476
+ return {
18477
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18478
+ changed: 0,
18479
+ failed: 1,
18480
+ dry_run: dryRun
18481
+ };
18482
+ }
18483
+ if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
18484
+ return {
18485
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18486
+ changed: 0,
18487
+ failed: 1,
18488
+ dry_run: dryRun
18489
+ };
18490
+ }
18491
+ const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
18492
+ if (staleSnapshots.length === 0) {
18493
+ return {
18494
+ runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
18495
+ changed: 0,
18496
+ failed: 0,
18497
+ dry_run: dryRun
18498
+ };
18499
+ }
18500
+ if (dryRun) {
18501
+ const projected = projectUpdatedStatus(status, before.snapshots.length);
18502
+ return {
18503
+ runtimes: [{
18504
+ ...projected,
18505
+ action: "update",
18506
+ dry_run: true,
18507
+ skill_contracts_changed: staleSnapshots.length
18508
+ }],
18509
+ changed: 1,
18510
+ failed: 0,
18511
+ dry_run: true
18512
+ };
18513
+ }
18514
+ const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
18515
+ path: snapshot.path,
18516
+ content: snapshot.content,
18517
+ mode: snapshot.mode ?? 420
18518
+ })), before.canonicalContent);
18519
+ if (!transaction.ok) {
18520
+ const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
18521
+ const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
18522
+ return {
18523
+ runtimes: [{
18524
+ ...status,
18525
+ action: "failed",
18526
+ dry_run: false,
18527
+ skill_contracts_changed: 0,
18528
+ reason
18529
+ }],
18530
+ changed: 0,
18531
+ failed: 1,
18532
+ dry_run: false
18533
+ };
18534
+ }
18535
+ const after = inspectInbox(options).status;
18536
+ const accepted = after.healthy || after.manual_fallback_ready;
18537
+ return {
18538
+ runtimes: [{
18539
+ ...after,
18540
+ action: accepted ? "update" : "failed",
18541
+ dry_run: false,
18542
+ skill_contracts_changed: accepted ? staleSnapshots.length : 0
18543
+ }],
18544
+ changed: accepted ? 1 : 0,
18545
+ failed: accepted ? 0 : 1,
18546
+ dry_run: false
18547
+ };
18548
+ }
18549
+
18550
+ // src/cli/index.tsx
17516
18551
  init_project_context();
17517
18552
 
17518
18553
  // src/status.ts
17519
18554
  init_config_store();
17520
18555
  init_apply();
17521
18556
  init_config_agents();
17522
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
18557
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
17523
18558
 
17524
18559
  // src/lib/package-version.ts
17525
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
17526
- import { dirname as dirname6, join as join14 } from "path";
18560
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
18561
+ import { dirname as dirname7, join as join17 } from "path";
17527
18562
  import { fileURLToPath } from "url";
17528
18563
  var cached = null;
17529
18564
  function getPackageVersion() {
17530
18565
  if (cached)
17531
18566
  return cached;
17532
18567
  try {
17533
- let dir = dirname6(fileURLToPath(import.meta.url));
18568
+ let dir = dirname7(fileURLToPath(import.meta.url));
17534
18569
  for (let i = 0;i < 8; i++) {
17535
- const pkgPath = join14(dir, "package.json");
17536
- if (existsSync13(pkgPath)) {
17537
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
18570
+ const pkgPath = join17(dir, "package.json");
18571
+ if (existsSync14(pkgPath)) {
18572
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
17538
18573
  if (pkg.name === "@hasna/instructions" && pkg.version) {
17539
18574
  cached = pkg.version;
17540
18575
  return cached;
17541
18576
  }
17542
18577
  }
17543
- const parent = dirname6(dir);
18578
+ const parent = dirname7(dir);
17544
18579
  if (parent === dir)
17545
18580
  break;
17546
18581
  dir = parent;
@@ -17573,7 +18608,7 @@ function countBy(items, getValue) {
17573
18608
  }
17574
18609
  return counts;
17575
18610
  }
17576
- async function getConfigsStatus(store = resolveConfigStore()) {
18611
+ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
17577
18612
  let databaseReachable = true;
17578
18613
  let configs = [];
17579
18614
  let categoryStats = { total: 0 };
@@ -17597,11 +18632,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17597
18632
  continue;
17598
18633
  knownTargets += 1;
17599
18634
  const targetPath = expandPath(config.target_path);
17600
- if (!existsSync14(targetPath)) {
18635
+ if (!existsSync15(targetPath)) {
17601
18636
  missingTargets += 1;
17602
18637
  continue;
17603
18638
  }
17604
- const disk = readFileSync11(targetPath, "utf-8");
18639
+ const disk = readFileSync13(targetPath, "utf-8");
17605
18640
  const { content: redactedDisk } = redactContent(disk, config.format);
17606
18641
  if (redactedDisk !== config.content) {
17607
18642
  driftedTargets += 1;
@@ -17627,7 +18662,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17627
18662
  }
17628
18663
  }
17629
18664
  const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
17630
- const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 ? "ok" : "warn";
18665
+ const managedSkillRuntimes = inspectManagedSkillRuntimes({
18666
+ homeDir: options.homeDir,
18667
+ conversationsCommand: options.conversationsCommand
18668
+ });
18669
+ const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 && managedSkillRuntimes.missing === 0 ? "ok" : "warn";
17631
18670
  return {
17632
18671
  service: "configs",
17633
18672
  schemaVersion: "1.0",
@@ -17657,7 +18696,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17657
18696
  profileLinks,
17658
18697
  machines,
17659
18698
  snapshots,
17660
- knownTargets
18699
+ knownTargets,
18700
+ managedSkillRuntimes: {
18701
+ skillsPresent: managedSkillRuntimes.skills_present,
18702
+ healthy: managedSkillRuntimes.healthy,
18703
+ missing: managedSkillRuntimes.missing
18704
+ }
17661
18705
  },
17662
18706
  health: {
17663
18707
  status,
@@ -17666,10 +18710,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17666
18710
  missingTargets,
17667
18711
  unredactedSecretFindings,
17668
18712
  retiredAgentRows,
18713
+ missingManagedSkillRuntimes: managedSkillRuntimes.missing,
17669
18714
  hasDrift: driftedTargets > 0,
17670
18715
  hasMissingTargets: missingTargets > 0,
17671
18716
  hasUnredactedSecrets: unredactedSecretFindings > 0,
17672
- hasRetiredAgentRows: retiredAgentRows > 0
18717
+ hasRetiredAgentRows: retiredAgentRows > 0,
18718
+ hasMissingManagedSkillRuntimes: managedSkillRuntimes.missing > 0
17673
18719
  },
17674
18720
  safety: {
17675
18721
  includesConfigValues: false,
@@ -17711,6 +18757,17 @@ function printLine(text = "") {
17711
18757
  function printJson(value) {
17712
18758
  printLine(JSON.stringify(value, null, 2));
17713
18759
  }
18760
+ function printManagedSkillRuntimeReport(report) {
18761
+ for (const runtime of report.runtimes) {
18762
+ if (!runtime.skill_present)
18763
+ continue;
18764
+ const prefix = runtime.action === "failed" ? chalk.red("[failed]") : runtime.dry_run ? chalk.yellow("[dry-run]") : runtime.manual_fallback_ready && !runtime.healthy ? chalk.yellow("[degraded]") : runtime.action === "unchanged" ? chalk.dim("=") : chalk.green("\u2713");
18765
+ console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
18766
+ if (runtime.action === "update") {
18767
+ console.log(chalk.dim(` skill contracts: ${runtime.skill_contracts_changed}`));
18768
+ }
18769
+ }
18770
+ }
17714
18771
  function fmtConfig(c, format) {
17715
18772
  if (format === "json")
17716
18773
  return JSON.stringify(c, null, 2);
@@ -17777,7 +18834,7 @@ function parseSessionSource(value, order) {
17777
18834
  if (!path)
17778
18835
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
17779
18836
  const absPath = resolveSessionPath(path);
17780
- if (!existsSync16(absPath))
18837
+ if (!existsSync17(absPath))
17781
18838
  throw new Error(`Instruction source file not found: ${absPath}`);
17782
18839
  const content = readSessionInstructionSourceFile(absPath);
17783
18840
  const source = sourceFromFilePath(absPath, content, order);
@@ -17821,7 +18878,7 @@ function sessionSourceReplacements(values) {
17821
18878
  return replacements;
17822
18879
  }
17823
18880
  function readSessionInstructionSourceFile(path) {
17824
- const stat = lstatSync5(path);
18881
+ const stat = lstatSync7(path);
17825
18882
  if (stat.isSymbolicLink()) {
17826
18883
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
17827
18884
  }
@@ -17831,7 +18888,7 @@ function readSessionInstructionSourceFile(path) {
17831
18888
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
17832
18889
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
17833
18890
  }
17834
- return readFileSync13(path, "utf-8");
18891
+ return readFileSync15(path, "utf-8");
17835
18892
  }
17836
18893
  function parseLayeredReference(value) {
17837
18894
  const trimmed = value.trim();
@@ -17858,9 +18915,9 @@ async function collectSessionSources(opts, tool, store) {
17858
18915
  }
17859
18916
  for (const value of opts.identityExport ?? []) {
17860
18917
  const path = resolveSessionPath(value);
17861
- if (!existsSync16(path))
18918
+ if (!existsSync17(path))
17862
18919
  throw new Error(`Identity instruction export not found: ${path}`);
17863
- const parsed = JSON.parse(readFileSync13(path, "utf-8"));
18920
+ const parsed = JSON.parse(readFileSync15(path, "utf-8"));
17864
18921
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
17865
18922
  }
17866
18923
  return sources.map((source) => {
@@ -17981,19 +19038,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
17981
19038
  if (value === "-")
17982
19039
  return { json: readBoundedProjectContextStdin() };
17983
19040
  const path = resolveSessionPath(value);
17984
- if (!existsSync16(path)) {
19041
+ if (!existsSync17(path)) {
17985
19042
  if (allowMissing)
17986
19043
  return {};
17987
19044
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
17988
19045
  }
17989
- const stat = lstatSync5(path);
19046
+ const stat = lstatSync7(path);
17990
19047
  if (stat.isSymbolicLink() || !stat.isFile()) {
17991
19048
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
17992
19049
  }
17993
19050
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
17994
19051
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
17995
19052
  }
17996
- return { json: readFileSync13(path, "utf8"), sourcePath: path };
19053
+ return { json: readFileSync15(path, "utf8"), sourcePath: path };
17997
19054
  }
17998
19055
  function readBoundedProjectContextStdin() {
17999
19056
  const chunks = [];
@@ -18152,15 +19209,15 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
18152
19209
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
18153
19210
  });
18154
19211
  program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
18155
- const abs = resolve10(filePath);
18156
- if (!existsSync16(abs)) {
19212
+ const abs = resolve13(filePath);
19213
+ if (!existsSync17(abs)) {
18157
19214
  console.error(chalk.red(`File not found: ${abs}`));
18158
19215
  process.exit(1);
18159
19216
  }
18160
- const rawContent = readFileSync13(abs, "utf-8");
19217
+ const rawContent = readFileSync15(abs, "utf-8");
18161
19218
  const fmt = detectFormat(abs);
18162
19219
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
18163
- const targetPath = abs.startsWith(homedir8()) ? abs.replace(homedir8(), "~") : abs;
19220
+ const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
18164
19221
  const name = opts.name || filePath.split("/").pop();
18165
19222
  const store = resolveConfigStore();
18166
19223
  const allConfigs = await store.listConfigs();
@@ -18335,7 +19392,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
18335
19392
  for (const entry of entries) {
18336
19393
  if (!entry.isDirectory())
18337
19394
  continue;
18338
- const projDir = join16(absDir, entry.name);
19395
+ const projDir = join19(absDir, entry.name);
18339
19396
  const hasAgentConfig = [
18340
19397
  "CLAUDE.md",
18341
19398
  ".mcp.json",
@@ -18348,7 +19405,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
18348
19405
  ".aicopilot",
18349
19406
  ".cursor",
18350
19407
  ".agents"
18351
- ].some((marker) => existsSync16(join16(projDir, marker)));
19408
+ ].some((marker) => existsSync17(join19(projDir, marker)));
18352
19409
  if (!hasAgentConfig)
18353
19410
  continue;
18354
19411
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -18399,10 +19456,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
18399
19456
  });
18400
19457
  program.command("whoami").description("Show setup summary").action(async () => {
18401
19458
  const store = resolveConfigStore();
18402
- const dbPath = isCloudMode() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1 (self_hosted)` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join16(homedir8(), ".hasna", "instructions", "instructions.db");
19459
+ const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
18403
19460
  const stats = await store.getConfigStats();
18404
19461
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
18405
- console.log(chalk.cyan(isCloudMode() ? "API:" : "DB:") + " " + dbPath);
19462
+ console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
18406
19463
  console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
18407
19464
  console.log();
18408
19465
  console.log(chalk.bold("By category:"));
@@ -18622,7 +19679,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
18622
19679
  process.exit(1);
18623
19680
  }
18624
19681
  });
18625
- profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
19682
+ profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
18626
19683
  try {
18627
19684
  const store = resolveConfigStore();
18628
19685
  const { machine, profile } = await getMachineProfileContext(opts, store);
@@ -18659,6 +19716,14 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
18659
19716
  }
18660
19717
  if (report.failures.length > 0)
18661
19718
  process.exitCode = 1;
19719
+ const runtimeReport = await reconcileManagedSkillRuntimes({
19720
+ dryRun: opts.dryRun,
19721
+ agent: opts.from,
19722
+ deliveryVerified: opts.deliveryVerified
19723
+ });
19724
+ printManagedSkillRuntimeReport(runtimeReport);
19725
+ if (runtimeReport.failed > 0)
19726
+ process.exitCode = 1;
18662
19727
  console.log(chalk.dim(`
18663
19728
  ${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
18664
19729
  } catch (e) {
@@ -19146,14 +20211,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
19146
20211
  } else if (target === "codex") {
19147
20212
  const { appendFileSync, existsSync: ex } = await import("fs");
19148
20213
  const { join: j } = await import("path");
19149
- const configPath = j(homedir8(), ".codex", "config.toml");
20214
+ const configPath = j(homedir10(), ".codex", "config.toml");
19150
20215
  const block = `
19151
20216
  [mcp_servers.configs]
19152
20217
  command = "${mcpBinary}"
19153
20218
  args = []
19154
20219
  `;
19155
20220
  if (ex(configPath)) {
19156
- const content = readFileSync13(configPath, "utf-8");
20221
+ const content = readFileSync15(configPath, "utf-8");
19157
20222
  if (content.includes("[mcp_servers.configs]")) {
19158
20223
  console.log(chalk.dim("= Already installed in Codex"));
19159
20224
  continue;
@@ -19164,7 +20229,7 @@ args = []
19164
20229
  } else if (target === "antigravity") {
19165
20230
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
19166
20231
  const { dirname: dn, join: j } = await import("path");
19167
- const configPath = j(homedir8(), ".gemini", "config", "mcp_config.json");
20232
+ const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
19168
20233
  let settings = {};
19169
20234
  if (ex(configPath)) {
19170
20235
  try {
@@ -19248,9 +20313,9 @@ DB stats:`));
19248
20313
  if (count > 0)
19249
20314
  console.log(` ${key.padEnd(18)} ${count}`);
19250
20315
  }
19251
- const location = isCloudMode() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1 (self_hosted)` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join16(homedir8(), ".hasna", "instructions", "instructions.db");
20316
+ const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
19252
20317
  console.log(chalk.dim(`
19253
- ${isCloudMode() ? "API" : "DB"}: ${location}`));
20318
+ ${isApiTransport() ? "API" : "DB"}: ${location}`));
19254
20319
  });
19255
20320
  program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
19256
20321
  const status = await getConfigsStatus(resolveConfigStore());
@@ -19266,14 +20331,53 @@ program.command("status").description("Health check: total configs, drift from d
19266
20331
  console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
19267
20332
  console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
19268
20333
  console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
20334
+ console.log(chalk.cyan("Skill runtimes:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green(`${status.counts.managedSkillRuntimes.healthy} ready`) : chalk.yellow(`${status.health.missingManagedSkillRuntimes} missing`)} (${status.counts.managedSkillRuntimes.skillsPresent} managed skill(s) present)`);
19269
20335
  console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
19270
20336
  });
20337
+ var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
20338
+ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the full local runtime status as JSON").action((opts) => {
20339
+ const report = inspectManagedSkillRuntimes({
20340
+ agent: opts.from,
20341
+ deliveryVerified: opts.deliveryVerified
20342
+ });
20343
+ if (opts.json) {
20344
+ printJson(report);
20345
+ if (report.missing > 0)
20346
+ process.exitCode = 1;
20347
+ return;
20348
+ }
20349
+ if (report.skills_present === 0) {
20350
+ console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
20351
+ return;
20352
+ }
20353
+ for (const runtime of report.runtimes) {
20354
+ if (!runtime.skill_present)
20355
+ continue;
20356
+ const prefix = runtime.healthy ? chalk.green("\u2713") : chalk.yellow("!");
20357
+ console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
20358
+ }
20359
+ if (report.missing > 0)
20360
+ process.exitCode = 1;
20361
+ });
20362
+ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the reconcile report as JSON").action(async (opts) => {
20363
+ const report = await reconcileManagedSkillRuntimes({
20364
+ dryRun: opts.dryRun,
20365
+ agent: opts.from,
20366
+ deliveryVerified: opts.deliveryVerified
20367
+ });
20368
+ if (opts.json)
20369
+ printJson(report);
20370
+ else
20371
+ printManagedSkillRuntimeReport(report);
20372
+ if (report.failed > 0)
20373
+ process.exitCode = 1;
20374
+ });
19271
20375
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
19272
20376
  const { mkdirSync: mk } = await import("fs");
19273
- const backupDir = join16(homedir8(), ".hasna", "instructions", "backups");
20377
+ const backupDir = join19(getRawStoreRoot(), "backups");
19274
20378
  mk(backupDir, { recursive: true });
19275
20379
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
19276
- const outPath = join16(backupDir, `configs-${ts}.tar.gz`);
20380
+ const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
19277
20381
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
19278
20382
  const { statSync: st } = await import("fs");
19279
20383
  const size = st(outPath).size;
@@ -19291,7 +20395,7 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
19291
20395
  const store = resolveConfigStore();
19292
20396
  let issues = 0;
19293
20397
  const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
19294
- const fail = (msg) => {
20398
+ const fail2 = (msg) => {
19295
20399
  issues++;
19296
20400
  console.log(chalk.red(" \u2717 ") + msg);
19297
20401
  };
@@ -19301,9 +20405,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
19301
20405
  console.log(chalk.cyan("Known files on disk:"));
19302
20406
  for (const k of KNOWN_CONFIGS) {
19303
20407
  if (k.rulesDir) {
19304
- existsSync16(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail(`${k.rulesDir}/ not found`);
20408
+ existsSync17(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
19305
20409
  } else {
19306
- existsSync16(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail(`${k.path} not found`);
20410
+ existsSync17(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
19307
20411
  }
19308
20412
  }
19309
20413
  const allConfigs = await store.listConfigs();
@@ -19316,7 +20420,7 @@ Stored configs (${allConfigs.length}):`));
19316
20420
  JSON.parse(c.content);
19317
20421
  validCount++;
19318
20422
  } catch {
19319
- fail(`${c.slug}: invalid JSON`);
20423
+ fail2(`${c.slug}: invalid JSON`);
19320
20424
  }
19321
20425
  } else {
19322
20426
  validCount++;
@@ -19328,13 +20432,13 @@ Stored configs (${allConfigs.length}):`));
19328
20432
  const found = scanSecrets(c.content, c.format);
19329
20433
  secretCount += found.length;
19330
20434
  }
19331
- secretCount === 0 ? pass("No unredacted secrets") : fail(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
20435
+ secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
19332
20436
  const duplicateTargets = findDuplicateTargetPathGroups(allConfigs);
19333
20437
  if (duplicateTargets.length === 0) {
19334
20438
  pass("No target path is claimed by more than one config");
19335
20439
  } else {
19336
20440
  const rowCount = duplicateTargets.reduce((total, group) => total + group.configs.length, 0);
19337
- fail(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
20441
+ fail2(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
19338
20442
  for (const group of duplicateTargets) {
19339
20443
  console.log(chalk.yellow(` ${group.target_path}`));
19340
20444
  for (const c of group.configs) {
@@ -19348,7 +20452,7 @@ Stored configs (${allConfigs.length}):`));
19348
20452
  pass("No reference config name is claimed by more than one row");
19349
20453
  } else {
19350
20454
  const rowCount = duplicateReferenceNames.reduce((total, group) => total + group.configs.length, 0);
19351
- fail(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
20455
+ fail2(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
19352
20456
  for (const group of duplicateReferenceNames) {
19353
20457
  console.log(chalk.yellow(` ${group.name}`));
19354
20458
  for (const c of group.configs) {
@@ -19456,16 +20560,16 @@ program.command("watch").description("Watch known config files for changes and a
19456
20560
  for (const k of KNOWN_CONFIGS) {
19457
20561
  if (k.rulesDir) {
19458
20562
  const absDir = expandPath2(k.rulesDir);
19459
- if (!existsSync16(absDir))
20563
+ if (!existsSync17(absDir))
19460
20564
  continue;
19461
20565
  const { readdirSync: readdirSync5 } = await import("fs");
19462
20566
  for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
19463
- const abs = join16(absDir, f);
20567
+ const abs = join19(absDir, f);
19464
20568
  mtimes.set(abs, st(abs).mtimeMs);
19465
20569
  }
19466
20570
  } else {
19467
20571
  const abs = expandPath2(k.path);
19468
- if (existsSync16(abs))
20572
+ if (existsSync17(abs))
19469
20573
  mtimes.set(abs, st(abs).mtimeMs);
19470
20574
  }
19471
20575
  }
@@ -19473,7 +20577,7 @@ program.command("watch").description("Watch known config files for changes and a
19473
20577
  const tick = async () => {
19474
20578
  let changed = 0;
19475
20579
  for (const [abs, oldMtime] of mtimes) {
19476
- if (!existsSync16(abs))
20580
+ if (!existsSync17(abs))
19477
20581
  continue;
19478
20582
  const newMtime = st(abs).mtimeMs;
19479
20583
  if (newMtime !== oldMtime) {
@@ -19485,10 +20589,10 @@ program.command("watch").description("Watch known config files for changes and a
19485
20589
  for (const k of KNOWN_CONFIGS) {
19486
20590
  if (k.rulesDir) {
19487
20591
  const absDir = expandPath2(k.rulesDir);
19488
- if (!existsSync16(absDir))
20592
+ if (!existsSync17(absDir))
19489
20593
  continue;
19490
20594
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
19491
- const abs = join16(absDir, f);
20595
+ const abs = join19(absDir, f);
19492
20596
  if (!mtimes.has(abs)) {
19493
20597
  mtimes.set(abs, st(abs).mtimeMs);
19494
20598
  changed++;
@@ -19496,7 +20600,7 @@ program.command("watch").description("Watch known config files for changes and a
19496
20600
  }
19497
20601
  } else {
19498
20602
  const abs = expandPath2(k.path);
19499
- if (existsSync16(abs) && !mtimes.has(abs)) {
20603
+ if (existsSync17(abs) && !mtimes.has(abs)) {
19500
20604
  mtimes.set(abs, st(abs).mtimeMs);
19501
20605
  changed++;
19502
20606
  }
@@ -19524,11 +20628,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
19524
20628
  if (!c.target_path)
19525
20629
  continue;
19526
20630
  const abs = expandPath(c.target_path);
19527
- if (!existsSync16(abs)) {
20631
+ if (!existsSync17(abs)) {
19528
20632
  missing++;
19529
20633
  continue;
19530
20634
  }
19531
- const disk = readFileSync13(abs, "utf-8");
20635
+ const disk = readFileSync15(abs, "utf-8");
19532
20636
  const { content: redactedDisk } = redactContent(disk, c.format);
19533
20637
  if (redactedDisk !== c.content)
19534
20638
  drifted++;
@@ -19595,7 +20699,7 @@ program.command("clean").description("Remove configs from DB whose target files
19595
20699
  if (!c.target_path)
19596
20700
  continue;
19597
20701
  const abs = expandPath(c.target_path);
19598
- if (!existsSync16(abs)) {
20702
+ if (!existsSync17(abs)) {
19599
20703
  if (printed < maxPrinted) {
19600
20704
  if (opts.dryRun) {
19601
20705
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -19619,7 +20723,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
19619
20723
  console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
19620
20724
  }
19621
20725
  });
19622
- program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
20726
+ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
19623
20727
  const store = resolveConfigStore();
19624
20728
  const packages = [
19625
20729
  { name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
@@ -19682,8 +20786,22 @@ Initializing configs:`));
19682
20786
  } else {
19683
20787
  console.log(chalk.dim(" would run: configs init"));
19684
20788
  }
20789
+ console.log(chalk.cyan(`
20790
+ Reconciling managed skill runtimes:`));
20791
+ const runtimeReport = await reconcileManagedSkillRuntimes({
20792
+ dryRun: opts.dryRun,
20793
+ agent: opts.from,
20794
+ deliveryVerified: opts.deliveryVerified
20795
+ });
20796
+ printManagedSkillRuntimeReport(runtimeReport);
20797
+ if (runtimeReport.failed > 0) {
20798
+ console.error(chalk.red(`
20799
+ Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
20800
+ process.exitCode = 1;
20801
+ return;
20802
+ }
19685
20803
  console.log(chalk.bold(`
19686
- \u2713 Bootstrap complete.`) + chalk.dim(" Restart Claude Code for MCP servers to activate."));
20804
+ \u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
19687
20805
  });
19688
20806
  program.command("pull").description("Alias for sync (read from disk into DB)").option("-a, --agent <agent>", "only sync this agent").option("--dry-run", "preview without writing").action(async (opts) => {
19689
20807
  const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent, store: resolveConfigStore() });