@hasna/instructions 0.4.42 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts +2 -0
  2. package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts.map +1 -0
  3. package/dist/cli/index.js +1179 -195
  4. package/dist/cli/station-profile.test.d.ts +2 -0
  5. package/dist/cli/station-profile.test.d.ts.map +1 -0
  6. package/dist/generated/storage-kit/backend.d.ts +0 -1
  7. package/dist/generated/storage-kit/backend.d.ts.map +1 -1
  8. package/dist/generated/storage-kit/index.d.ts +1 -1
  9. package/dist/generated/storage-kit/query.d.ts +1 -1
  10. package/dist/generated/storage-kit/query.d.ts.map +1 -1
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +685 -196
  14. package/dist/lib/apply.d.ts.map +1 -1
  15. package/dist/lib/instruction-graph.d.ts +4 -0
  16. package/dist/lib/instruction-graph.d.ts.map +1 -1
  17. package/dist/lib/provider-context.d.ts +94 -0
  18. package/dist/lib/provider-context.d.ts.map +1 -0
  19. package/dist/lib/provider-context.test.d.ts +2 -0
  20. package/dist/lib/provider-context.test.d.ts.map +1 -0
  21. package/dist/lib/session-apply.d.ts +7 -0
  22. package/dist/lib/session-apply.d.ts.map +1 -1
  23. package/dist/lib/session-authority.d.ts +22 -2
  24. package/dist/lib/session-authority.d.ts.map +1 -1
  25. package/dist/lib/session-render.d.ts +9 -1
  26. package/dist/lib/session-render.d.ts.map +1 -1
  27. package/dist/lib/station-profile.d.ts +102 -0
  28. package/dist/lib/station-profile.d.ts.map +1 -0
  29. package/dist/lib/station-profile.test.d.ts +2 -0
  30. package/dist/lib/station-profile.test.d.ts.map +1 -0
  31. package/dist/lib/sync-dir.d.ts.map +1 -1
  32. package/dist/lib/sync.d.ts.map +1 -1
  33. package/dist/mcp/early-args.test.d.ts +2 -0
  34. package/dist/mcp/early-args.test.d.ts.map +1 -0
  35. package/dist/mcp/index.d.ts.map +1 -1
  36. package/dist/mcp/index.js +125 -76
  37. package/dist/mcp/server.d.ts.map +1 -1
  38. package/dist/server/cloud.d.ts.map +1 -1
  39. package/dist/server/early-args.test.d.ts +2 -0
  40. package/dist/server/early-args.test.d.ts.map +1 -0
  41. package/dist/server/index.d.ts.map +1 -1
  42. package/dist/server/index.js +38 -2
  43. package/dist/status.d.ts.map +1 -1
  44. package/dist/storage/cloud-store.d.ts.map +1 -1
  45. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -10846,12 +10846,24 @@ var init_cursor_authority = __esm(() => {
10846
10846
 
10847
10847
  // src/lib/session-authority.ts
10848
10848
  import { createHash as createHash5 } from "crypto";
10849
- import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
10849
+ import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
10850
+ import { homedir as homedir5 } from "os";
10850
10851
  import { join as join7, resolve as resolve5 } from "path";
10851
10852
  function sha2565(content) {
10852
10853
  return createHash5("sha256").update(content).digest("hex");
10853
10854
  }
10854
- function detectClaudeAuthorityConflicts(targetHome) {
10855
+ function configHomeDir() {
10856
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
10857
+ }
10858
+ function normalizeOwnedTargetPath(p) {
10859
+ const expanded = p.startsWith("~/") ? resolve5(configHomeDir(), p.slice(2)) : resolve5(p);
10860
+ try {
10861
+ return realpathSync(expanded);
10862
+ } catch {
10863
+ return expanded;
10864
+ }
10865
+ }
10866
+ function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
10855
10867
  const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
10856
10868
  let stat;
10857
10869
  try {
@@ -10895,6 +10907,24 @@ function detectClaudeAuthorityConflicts(targetHome) {
10895
10907
  }];
10896
10908
  }
10897
10909
  const content = readFileSync3(authorityPath, "utf8");
10910
+ const owned = ownedAuthorities.find((authority) => normalizeOwnedTargetPath(authority.targetPath) === normalizeOwnedTargetPath(authorityPath));
10911
+ if (owned) {
10912
+ if (owned.content === content)
10913
+ return [];
10914
+ return [{
10915
+ ...provenanceBase,
10916
+ kind: "unknown-unmanaged-authority",
10917
+ sha256: sha2565(content),
10918
+ markers: [],
10919
+ provenance: {
10920
+ source: "filesystem",
10921
+ authority: "unmanaged",
10922
+ observedPath: authorityPath,
10923
+ detection: "owned-config-drift"
10924
+ },
10925
+ reason: `Claude target AGENTS.md is owned by registered config "${owned.slug}", but the disk file drifts from its stored content; re-sync the config through the instructions pipeline before applying.`
10926
+ }];
10927
+ }
10898
10928
  const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
10899
10929
  const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
10900
10930
  return [{
@@ -10923,9 +10953,9 @@ var init_session_authority = __esm(() => {
10923
10953
 
10924
10954
  // src/lib/session-render.ts
10925
10955
  import { createHash as createHash6 } from "crypto";
10926
- import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "fs";
10927
- import { homedir as homedir5 } from "os";
10928
- 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";
10956
+ import { existsSync as existsSync6, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
10957
+ import { homedir as homedir6 } from "os";
10958
+ import { basename as basename4, dirname as dirname3, extname as extname2, isAbsolute as isAbsolute3, join as join8, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
10929
10959
  function normalizeSessionInstructionLayer(value) {
10930
10960
  if (value === "provider")
10931
10961
  return "tool";
@@ -11011,7 +11041,7 @@ function yamlQuote2(value) {
11011
11041
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
11012
11042
  }
11013
11043
  function defaultTargetHome(tool, profile, sessionId) {
11014
- const home = process.env["HOME"] || homedir5();
11044
+ const home = process.env["HOME"] || homedir6();
11015
11045
  return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
11016
11046
  }
11017
11047
  function joinTarget(targetHome, relativePath) {
@@ -11586,7 +11616,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
11586
11616
  ...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
11587
11617
  ]);
11588
11618
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
11589
- const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11619
+ const selectedConfig = existsSync6(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11590
11620
  const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
11591
11621
  const config = {
11592
11622
  ...selectedConfig,
@@ -11775,7 +11805,7 @@ function adapterFor(input) {
11775
11805
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
11776
11806
  }
11777
11807
  function getHomeDir() {
11778
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
11808
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
11779
11809
  }
11780
11810
  function cleanSessionPathInput(path) {
11781
11811
  const trimmed = path.trim();
@@ -11913,7 +11943,7 @@ function planSessionRender(input) {
11913
11943
  blockers: targetBlockers
11914
11944
  } = resolveRenderTarget(input);
11915
11945
  const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
11916
- const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
11946
+ const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome, input.ownedClaudeAuthorities) : [];
11917
11947
  const blockers = [
11918
11948
  ...targetBlockers,
11919
11949
  ...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
@@ -12051,7 +12081,7 @@ function planSessionRender(input) {
12051
12081
  sourceId: input.providerConfig.sourceId,
12052
12082
  selectedPayloadSha256: sha2566(input.providerConfig.content),
12053
12083
  renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
12054
- selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
12084
+ selected: !existsSync6(joinTarget(targetHome, adapter.configFile))
12055
12085
  }
12056
12086
  } : {},
12057
12087
  ...projectContext ? {
@@ -12090,7 +12120,7 @@ function planSessionRender(input) {
12090
12120
  };
12091
12121
  }
12092
12122
  function sourceFromFilePath(path, content, order = 0) {
12093
- const file = basename3(path);
12123
+ const file = basename4(path);
12094
12124
  return {
12095
12125
  id: file.replace(extname2(file), ""),
12096
12126
  label: file,
@@ -12312,7 +12342,7 @@ function layerFromIdentityKind(kind, exportShape) {
12312
12342
  function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
12313
12343
  if (sourcePaths.length === 0 || !exportPath)
12314
12344
  return;
12315
- const baseDir = dirname2(resolveSessionPath(exportPath));
12345
+ const baseDir = dirname3(resolveSessionPath(exportPath));
12316
12346
  const contents = [];
12317
12347
  for (const sourcePath of sourcePaths) {
12318
12348
  const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
@@ -12330,7 +12360,7 @@ ${item.content.trimEnd()}`).join(`
12330
12360
  }
12331
12361
  function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12332
12362
  const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
12333
- if (!existsSync5(resolvedPath)) {
12363
+ if (!existsSync6(resolvedPath)) {
12334
12364
  if (sourcePath.required) {
12335
12365
  throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
12336
12366
  }
@@ -12340,8 +12370,8 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12340
12370
  if (!stat.isFile()) {
12341
12371
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
12342
12372
  }
12343
- const realBase = realpathSync(baseDir);
12344
- const realPath = realpathSync(resolvedPath);
12373
+ const realBase = realpathSync2(baseDir);
12374
+ const realPath = realpathSync2(resolvedPath);
12345
12375
  if (!pathIsInside(realPath, realBase)) {
12346
12376
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
12347
12377
  }
@@ -12974,6 +13004,7 @@ function planProfileSessionRender(input) {
12974
13004
  asset_surface: _assetSurface,
12975
13005
  allow_asset_installers: _allowAssetInstallers,
12976
13006
  graph_context: _graphContext,
13007
+ extra_sources: _extraSources,
12977
13008
  assetPlan: _callerAssetPlan,
12978
13009
  assetContents: _callerAssetContents,
12979
13010
  ...renderInput
@@ -12987,7 +13018,7 @@ function planProfileSessionRender(input) {
12987
13018
  ...planSessionRender({
12988
13019
  ...renderInput,
12989
13020
  ...compiled.capability.session_surface ? { providerSurface: compiled.capability.session_surface } : {},
12990
- sources: compiled.sources,
13021
+ sources: [...compiled.sources, ...input.extra_sources ?? []],
12991
13022
  assetPlan,
12992
13023
  assetContents: Object.fromEntries((input.asset_configs ?? []).map((config) => [config.id, config.content]))
12993
13024
  }),
@@ -14019,8 +14050,8 @@ var init_config_store = __esm(() => {
14019
14050
  });
14020
14051
 
14021
14052
  // src/lib/session-render-ownership.ts
14022
- import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
14023
- import { dirname as dirname3, join as join9, parse as parse3, relative as relative3, sep } from "path";
14053
+ import { existsSync as existsSync7, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
14054
+ import { dirname as dirname4, join as join9, parse as parse3, relative as relative3, sep } from "path";
14024
14055
  function toSegments(absolutePath2) {
14025
14056
  return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
14026
14057
  }
@@ -14039,7 +14070,7 @@ function pathIsSessionRenderManagedDir(absolutePath2) {
14039
14070
  function readManifestRelativePaths(manifestPath) {
14040
14071
  let stats;
14041
14072
  try {
14042
- if (!existsSync6(manifestPath))
14073
+ if (!existsSync7(manifestPath))
14043
14074
  return null;
14044
14075
  stats = statSync4(manifestPath);
14045
14076
  } catch {
@@ -14066,7 +14097,7 @@ function readManifestRelativePaths(manifestPath) {
14066
14097
  }
14067
14098
  function sessionRenderManifestClaimsPath(absolutePath2) {
14068
14099
  const root = parse3(absolutePath2).root;
14069
- let home = dirname3(absolutePath2);
14100
+ let home = dirname4(absolutePath2);
14070
14101
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
14071
14102
  const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14072
14103
  const relativePaths = readManifestRelativePaths(manifestPath);
@@ -14075,7 +14106,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
14075
14106
  if (relativePaths.has(claimed))
14076
14107
  return true;
14077
14108
  }
14078
- const parent = dirname3(home);
14109
+ const parent = dirname4(home);
14079
14110
  if (parent === home || home === root)
14080
14111
  break;
14081
14112
  home = parent;
@@ -14103,11 +14134,11 @@ __export(exports_apply, {
14103
14134
  applyConfigs: () => applyConfigs,
14104
14135
  applyConfig: () => applyConfig
14105
14136
  });
14106
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
14107
- import { basename as basename4, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
14108
- import { homedir as homedir6 } from "os";
14137
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
14138
+ import { basename as basename5, dirname as dirname5, join as join10, resolve as resolve7 } from "path";
14139
+ import { homedir as homedir7 } from "os";
14109
14140
  function getConfigHome() {
14110
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
14141
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
14111
14142
  }
14112
14143
  function expandPath(p) {
14113
14144
  if (p.startsWith("~/")) {
@@ -14118,20 +14149,20 @@ function expandPath(p) {
14118
14149
  function normalizeTargetPath(p) {
14119
14150
  const expanded = expandPath(p);
14120
14151
  try {
14121
- return realpathSync2(expanded);
14152
+ return realpathSync3(expanded);
14122
14153
  } catch {
14123
14154
  let current = expanded;
14124
14155
  const missingSegments = [];
14125
14156
  while (true) {
14126
- if (existsSync7(current)) {
14157
+ if (existsSync8(current)) {
14127
14158
  try {
14128
- return resolve7(realpathSync2(current), ...missingSegments);
14159
+ return resolve7(realpathSync3(current), ...missingSegments);
14129
14160
  } catch {
14130
14161
  return expanded;
14131
14162
  }
14132
14163
  }
14133
- const parent = dirname4(current);
14134
- const name = basename4(current);
14164
+ const parent = dirname5(current);
14165
+ const name = basename5(current);
14135
14166
  if (parent === current)
14136
14167
  return expanded;
14137
14168
  missingSegments.unshift(name);
@@ -14154,11 +14185,11 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
14154
14185
  }
14155
14186
  const path = expandPath(renderedTargetPath);
14156
14187
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
14157
- const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
14188
+ const previousContent = existsSync8(path) ? readFileSync6(path, "utf-8") : null;
14158
14189
  const changed = previousContent !== renderedForTarget;
14159
14190
  if (!opts.dryRun) {
14160
- const dir = dirname4(path);
14161
- if (!existsSync7(dir)) {
14191
+ const dir = dirname5(path);
14192
+ if (!existsSync8(dir)) {
14162
14193
  mkdirSync3(dir, { recursive: true });
14163
14194
  }
14164
14195
  if (previousContent !== null && changed) {
@@ -14192,7 +14223,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
14192
14223
  let current;
14193
14224
  try {
14194
14225
  const path = expandPath(targetPath);
14195
- if (!existsSync7(path))
14226
+ if (!existsSync8(path))
14196
14227
  return [];
14197
14228
  current = readFileSync6(path, "utf-8");
14198
14229
  } catch {
@@ -14282,7 +14313,7 @@ async function applyPreparedConfig(config, opts) {
14282
14313
  changed: outputResults.some((output) => output.changed)
14283
14314
  };
14284
14315
  }
14285
- if (!opts.dryRun) {
14316
+ if (!opts.dryRun && result.changed) {
14286
14317
  await store.updateConfig(config.id, { synced_at: new Date().toISOString() });
14287
14318
  }
14288
14319
  return result;
@@ -14536,20 +14567,20 @@ var init_apply = __esm(() => {
14536
14567
  });
14537
14568
 
14538
14569
  // src/lib/sync-dir.ts
14539
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
14570
+ import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
14540
14571
  import { join as join11, relative as relative4 } from "path";
14541
- import { homedir as homedir7 } from "os";
14572
+ import { homedir as homedir8 } from "os";
14542
14573
  function shouldSkip(p) {
14543
14574
  return SKIP.some((s) => p.includes(s));
14544
14575
  }
14545
14576
  async function syncFromDir(dir, opts = {}) {
14546
14577
  const store = opts.store ?? resolveConfigStore();
14547
14578
  const absDir = expandPath(dir);
14548
- if (!existsSync8(absDir))
14579
+ if (!existsSync9(absDir))
14549
14580
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
14550
14581
  const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
14551
14582
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
14552
- const home = homedir7();
14583
+ const home = homedir8();
14553
14584
  const allConfigs = await store.listConfigs();
14554
14585
  for (const file of files) {
14555
14586
  if (shouldSkip(file)) {
@@ -14563,14 +14594,15 @@ async function syncFromDir(dir, opts = {}) {
14563
14594
  continue;
14564
14595
  }
14565
14596
  const targetPath = file.replace(home, "~");
14597
+ const redacted = redactContent(content, redactFormatForTarget(targetPath, detectFormat(file)));
14566
14598
  const existing = allConfigs.find((c) => c.target_path === targetPath);
14567
14599
  if (!existing) {
14568
14600
  if (!opts.dryRun)
14569
- await store.createConfig({ name: relative4(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content });
14601
+ await store.createConfig({ name: relative4(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
14570
14602
  result.added++;
14571
- } else if (existing.content !== content) {
14603
+ } else if (existing.content !== redacted.content) {
14572
14604
  if (!opts.dryRun)
14573
- await store.updateConfig(existing.id, { content });
14605
+ await store.updateConfig(existing.id, { content: redacted.content });
14574
14606
  result.updated++;
14575
14607
  } else {
14576
14608
  result.unchanged++;
@@ -14583,7 +14615,7 @@ async function syncFromDir(dir, opts = {}) {
14583
14615
  }
14584
14616
  async function syncToDir(dir, opts = {}) {
14585
14617
  const store = opts.store ?? resolveConfigStore();
14586
- const home = homedir7();
14618
+ const home = homedir8();
14587
14619
  const absDir = expandPath(dir);
14588
14620
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14589
14621
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14622,6 +14654,7 @@ var init_sync_dir = __esm(() => {
14622
14654
  init_config_store();
14623
14655
  init_apply();
14624
14656
  init_sync();
14657
+ init_redact();
14625
14658
  SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
14626
14659
  });
14627
14660
 
@@ -14641,10 +14674,10 @@ __export(exports_sync, {
14641
14674
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14642
14675
  CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14643
14676
  });
14644
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
14645
- import { basename as basename5, extname as extname3, join as join12 } from "path";
14677
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
14678
+ import { basename as basename6, extname as extname3, join as join12 } from "path";
14646
14679
  function claudeRuleOutputs(fileName) {
14647
- const stem = basename5(fileName, extname3(fileName));
14680
+ const stem = basename6(fileName, extname3(fileName));
14648
14681
  return [
14649
14682
  { agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
14650
14683
  ];
@@ -14681,15 +14714,15 @@ function isGeneratedOutputTarget2(config, owners) {
14681
14714
  return !!ownerIds && !ownerIds.has(config.id);
14682
14715
  }
14683
14716
  function hasClaudePromptSource() {
14684
- return existsSync9(expandPath("~/.claude/CLAUDE.md"));
14717
+ return existsSync10(expandPath("~/.claude/CLAUDE.md"));
14685
14718
  }
14686
14719
  function hasClaudeRuleSourceForCursorTarget(targetPath) {
14687
14720
  const absoluteTargetPath = expandPath(targetPath);
14688
14721
  const absolutePrefix = expandPath("~/.cursor/rules");
14689
14722
  if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
14690
14723
  return false;
14691
- const stem = basename5(absoluteTargetPath, ".mdc");
14692
- return existsSync9(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync9(expandPath(`~/.claude/rules/${stem}.mdc`));
14724
+ const stem = basename6(absoluteTargetPath, ".mdc");
14725
+ return existsSync10(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync10(expandPath(`~/.claude/rules/${stem}.mdc`));
14693
14726
  }
14694
14727
  function isKnownGeneratedTargetPath(targetPath) {
14695
14728
  const normalizedTargetPath = normalizeTargetPath(targetPath);
@@ -14707,7 +14740,7 @@ async function syncProject(opts) {
14707
14740
  const machine = detectMachineContext();
14708
14741
  for (const pf of PROJECT_CONFIG_FILES) {
14709
14742
  const abs = join12(absDir, pf.file);
14710
- if (!existsSync9(abs))
14743
+ if (!existsSync10(abs))
14711
14744
  continue;
14712
14745
  try {
14713
14746
  const rawContent = readFileSync8(abs, "utf-8");
@@ -14747,7 +14780,7 @@ async function syncProject(opts) {
14747
14780
  { dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14748
14781
  { dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14749
14782
  ]) {
14750
- if (!existsSync9(ruleDir.dir))
14783
+ if (!existsSync10(ruleDir.dir))
14751
14784
  continue;
14752
14785
  const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14753
14786
  for (const f of mdFiles) {
@@ -14791,7 +14824,7 @@ async function syncKnown(opts = {}) {
14791
14824
  for (const known of targets) {
14792
14825
  if (known.rulesDir) {
14793
14826
  const absDir = expandPath(known.rulesDir);
14794
- if (!existsSync9(absDir)) {
14827
+ if (!existsSync10(absDir)) {
14795
14828
  result.skipped.push(known.rulesDir);
14796
14829
  continue;
14797
14830
  }
@@ -14832,7 +14865,7 @@ async function syncKnown(opts = {}) {
14832
14865
  continue;
14833
14866
  }
14834
14867
  const abs = expandPath(known.path);
14835
- if (!existsSync9(abs)) {
14868
+ if (!existsSync10(abs)) {
14836
14869
  result.skipped.push(known.path);
14837
14870
  continue;
14838
14871
  }
@@ -14843,7 +14876,7 @@ async function syncKnown(opts = {}) {
14843
14876
  continue;
14844
14877
  }
14845
14878
  const fmt = known.format ?? detectFormat(abs);
14846
- const redacted = redactContent(rawContent, fmt);
14879
+ const redacted = redactContent(rawContent, redactFormatForTarget(abs, fmt));
14847
14880
  const machineAware = templateizeMachineContent(redacted.content, machine);
14848
14881
  const content = machineAware.content;
14849
14882
  const isTemplate2 = redacted.isTemplate || machineAware.changed;
@@ -14938,7 +14971,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
14938
14971
  }
14939
14972
  function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
14940
14973
  const path = expandPath(targetPath);
14941
- if (!existsSync9(path))
14974
+ if (!existsSync10(path))
14942
14975
  return `(file not found on disk: ${path})`;
14943
14976
  const diskContent = readFileSync8(path, "utf-8");
14944
14977
  if (diskContent === expectedContent)
@@ -15173,18 +15206,18 @@ __export(exports_package_manager_guard, {
15173
15206
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15174
15207
  });
15175
15208
  import { execFileSync as execFileSync2 } from "child_process";
15176
- import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
15177
- import { homedir as homedir9 } from "os";
15178
- import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative7, resolve as resolve12 } from "path";
15209
+ import { existsSync as existsSync19, lstatSync as lstatSync7, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
15210
+ import { homedir as homedir11 } from "os";
15211
+ import { basename as basename7, dirname as dirname10, isAbsolute as isAbsolute5, join as join20, relative as relative7, resolve as resolve13 } from "path";
15179
15212
  function scanPackageManagerSecrets(options = {}) {
15180
- const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
15181
- const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve12(cwd, root));
15213
+ const cwd = options.cwd ? resolve13(options.cwd) : process.cwd();
15214
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve13(cwd, root));
15182
15215
  const findings = [];
15183
15216
  let scannedFiles = 0;
15184
15217
  for (const root of roots) {
15185
- if (!existsSync16(root))
15218
+ if (!existsSync19(root))
15186
15219
  continue;
15187
- const stat = lstatSync6(root);
15220
+ const stat = lstatSync7(root);
15188
15221
  if (stat.isFile()) {
15189
15222
  if (!shouldScanRepoFile(root))
15190
15223
  continue;
@@ -15192,7 +15225,7 @@ function scanPackageManagerSecrets(options = {}) {
15192
15225
  if (text === null)
15193
15226
  continue;
15194
15227
  scannedFiles++;
15195
- findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
15228
+ findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
15196
15229
  continue;
15197
15230
  }
15198
15231
  if (!stat.isDirectory())
@@ -15209,10 +15242,10 @@ function scanPackageManagerSecrets(options = {}) {
15209
15242
  }
15210
15243
  }
15211
15244
  if (options.includeHome) {
15212
- const home = homedir9();
15245
+ const home = homedir11();
15213
15246
  for (const name of HOME_FILES) {
15214
- const file = join18(home, name);
15215
- if (!existsSync16(file))
15247
+ const file = join20(home, name);
15248
+ if (!existsSync19(file))
15216
15249
  continue;
15217
15250
  const text = readTextFile(file);
15218
15251
  if (text === null)
@@ -15232,16 +15265,16 @@ function scanPackageManagerSecrets(options = {}) {
15232
15265
  function collectRepoFiles(root) {
15233
15266
  const out = [];
15234
15267
  const visit = (dir) => {
15235
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
15268
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
15236
15269
  if (entry.isDirectory()) {
15237
15270
  if (SKIP_DIRS.has(entry.name))
15238
15271
  continue;
15239
- visit(join18(dir, entry.name));
15272
+ visit(join20(dir, entry.name));
15240
15273
  continue;
15241
15274
  }
15242
15275
  if (!entry.isFile())
15243
15276
  continue;
15244
- const file = join18(dir, entry.name);
15277
+ const file = join20(dir, entry.name);
15245
15278
  if (shouldScanRepoFile(file))
15246
15279
  out.push(file);
15247
15280
  }
@@ -15250,11 +15283,11 @@ function collectRepoFiles(root) {
15250
15283
  return out;
15251
15284
  }
15252
15285
  function shouldScanRepoFile(file) {
15253
- const name = basename6(file);
15286
+ const name = basename7(file);
15254
15287
  return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
15255
15288
  }
15256
15289
  function classifyRepoFile(file) {
15257
- const name = basename6(file);
15290
+ const name = basename7(file);
15258
15291
  if (isNpmrcName(name))
15259
15292
  return "repo-npmrc";
15260
15293
  if (isBunConfigName(name))
@@ -15276,10 +15309,10 @@ function isNpmrcName(name) {
15276
15309
  }
15277
15310
  function readTextFile(file) {
15278
15311
  try {
15279
- const stat = lstatSync6(file);
15312
+ const stat = lstatSync7(file);
15280
15313
  if (!stat.isFile() || stat.size > 5000000)
15281
15314
  return null;
15282
- const buf = readFileSync14(file);
15315
+ const buf = readFileSync16(file);
15283
15316
  if (buf.includes(0))
15284
15317
  return null;
15285
15318
  return buf.toString("utf-8");
@@ -15479,7 +15512,7 @@ function trackedFiles(root) {
15479
15512
  }
15480
15513
  function isTrackedFile(file) {
15481
15514
  try {
15482
- const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
15515
+ const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
15483
15516
  encoding: "utf-8",
15484
15517
  stdio: ["ignore", "pipe", "ignore"]
15485
15518
  }).trim();
@@ -15509,7 +15542,7 @@ function stripInlineComment(value) {
15509
15542
  return value.replace(/\s[#;].*$/, "").trim();
15510
15543
  }
15511
15544
  function displayPath(file, root) {
15512
- const home = homedir9();
15545
+ const home = homedir11();
15513
15546
  if (root === home && (file === home || file.startsWith(home + "/")))
15514
15547
  return "~/" + toPosix(relative7(home, file));
15515
15548
  if (isAbsolute5(root) && file.startsWith(root + "/"))
@@ -15567,8 +15600,12 @@ import { existsSync } from "fs";
15567
15600
  import { homedir } from "os";
15568
15601
  import { join } from "path";
15569
15602
  import { createHmac, timingSafeEqual } from "crypto";
15603
+ import { lookup as dnsLookup } from "dns/promises";
15604
+ import { isIP } from "net";
15570
15605
  import { randomUUID } from "crypto";
15571
15606
  import { spawn } from "child_process";
15607
+ import { request as nodeHttpRequest } from "http";
15608
+ import { request as nodeHttpsRequest } from "https";
15572
15609
  import { randomUUID as randomUUID2 } from "crypto";
15573
15610
  function getPathValue(input, path) {
15574
15611
  return path.split(".").reduce((value, part) => {
@@ -15971,6 +16008,214 @@ function signPayload(secret, timestamp, body) {
15971
16008
  const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
15972
16009
  return `sha256=${digest}`;
15973
16010
  }
16011
+ var DEFAULT_MAX_REDIRECTS = 5;
16012
+ var IPV4_PRIVATE_RANGES = [
16013
+ [0, 16777215],
16014
+ [167772160, 184549375],
16015
+ [1681915904, 1686110207],
16016
+ [2130706432, 2147483647],
16017
+ [2851995648, 2852061183],
16018
+ [2886729728, 2887778303],
16019
+ [3221225472, 3221225727],
16020
+ [3221225984, 3221226239],
16021
+ [3227017984, 3227018239],
16022
+ [3232235520, 3232301055],
16023
+ [3323068416, 3323199487],
16024
+ [3325256704, 3325256959],
16025
+ [3405803776, 3405804031],
16026
+ [3758096384, 4294967295]
16027
+ ];
16028
+ var IPV6_SPECIAL_PREFIXES = [
16029
+ { groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
16030
+ { groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
16031
+ { groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
16032
+ { groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
16033
+ { groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
16034
+ { groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
16035
+ { groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
16036
+ { groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
16037
+ { groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
16038
+ { groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
16039
+ { groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
16040
+ { groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
16041
+ { groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
16042
+ { groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
16043
+ { groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
16044
+ ];
16045
+ function isPrivateAddress(address) {
16046
+ const normalized = stripZoneId(address);
16047
+ const version = isIP(normalized);
16048
+ if (version === 4) {
16049
+ const integer = ipv4ToInt(normalized);
16050
+ if (integer === undefined)
16051
+ return true;
16052
+ return IPV4_PRIVATE_RANGES.some(([low, high]) => integer >= low && integer <= high);
16053
+ }
16054
+ if (version === 6) {
16055
+ const groups = ipv6Groups(normalized);
16056
+ if (!groups)
16057
+ return true;
16058
+ for (const prefix of IPV6_SPECIAL_PREFIXES) {
16059
+ if (!ipv6MatchesPrefix(groups, prefix.groups, prefix.bits))
16060
+ continue;
16061
+ if (prefix.bits === 96 && groups[5] === 65535) {
16062
+ return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
16063
+ }
16064
+ if (prefix.bits === 16 && groups[0] === 8194) {
16065
+ return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
16066
+ }
16067
+ return true;
16068
+ }
16069
+ return false;
16070
+ }
16071
+ return true;
16072
+ }
16073
+ async function resolveWebhookTarget(url, policy = {}) {
16074
+ const hostname = normalizeHostname(url.hostname);
16075
+ const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
16076
+ if (allowlist.includes(hostname)) {
16077
+ const version2 = isIP(hostname);
16078
+ if (version2 === 4 || version2 === 6) {
16079
+ return { hostname, addresses: [hostname] };
16080
+ }
16081
+ const lookup2 = policy.lookup ?? defaultTargetLookup;
16082
+ let resolved2;
16083
+ try {
16084
+ resolved2 = await lookup2(hostname);
16085
+ } catch {
16086
+ throw new Error(`Webhook target ${hostname} could not be resolved`);
16087
+ }
16088
+ if (!Array.isArray(resolved2) || resolved2.length === 0) {
16089
+ throw new Error(`Webhook target ${hostname} resolved to no addresses`);
16090
+ }
16091
+ const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
16092
+ return { hostname, addresses };
16093
+ }
16094
+ const version = isIP(hostname);
16095
+ if (version === 4 || version === 6) {
16096
+ if (isPrivateAddress(hostname)) {
16097
+ throw new Error(`Webhook target ${hostname} is a private or special-use address`);
16098
+ }
16099
+ return { hostname, addresses: [hostname] };
16100
+ }
16101
+ const lookup = policy.lookup ?? defaultTargetLookup;
16102
+ let resolved;
16103
+ try {
16104
+ resolved = await lookup(hostname);
16105
+ } catch {
16106
+ throw new Error(`Webhook target ${hostname} could not be resolved`);
16107
+ }
16108
+ if (!Array.isArray(resolved) || resolved.length === 0) {
16109
+ throw new Error(`Webhook target ${hostname} resolved to no addresses`);
16110
+ }
16111
+ const allowed = [];
16112
+ for (const entry of resolved) {
16113
+ const address = normalizeHostname(entry.address);
16114
+ if (isPrivateAddress(address)) {
16115
+ if (allowlist.includes(address)) {
16116
+ allowed.push(address);
16117
+ continue;
16118
+ }
16119
+ throw new Error(`Webhook target ${hostname} resolves to private or special-use address ${address}`);
16120
+ }
16121
+ allowed.push(address);
16122
+ }
16123
+ if (allowed.length === 0) {
16124
+ throw new Error(`Webhook target ${hostname} resolved to no public addresses`);
16125
+ }
16126
+ return { hostname, addresses: allowed };
16127
+ }
16128
+ function normalizeMaxRedirects(value) {
16129
+ if (value === undefined)
16130
+ return DEFAULT_MAX_REDIRECTS;
16131
+ if (!Number.isInteger(value) || value < 0)
16132
+ throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
16133
+ return value;
16134
+ }
16135
+ var defaultTargetLookup = async (hostname) => {
16136
+ return dnsLookup(hostname, { all: true, verbatim: false });
16137
+ };
16138
+ function normalizeHostname(hostname) {
16139
+ const lower = hostname.toLowerCase();
16140
+ if (lower.startsWith("[") && lower.endsWith("]"))
16141
+ return lower.slice(1, -1);
16142
+ return lower;
16143
+ }
16144
+ function stripZoneId(address) {
16145
+ const percent = address.indexOf("%");
16146
+ return percent === -1 ? address : address.slice(0, percent);
16147
+ }
16148
+ function ipv4ToInt(address) {
16149
+ const parts = address.split(".");
16150
+ if (parts.length !== 4)
16151
+ return;
16152
+ let value = 0;
16153
+ for (const part of parts) {
16154
+ if (!/^\d{1,3}$/.test(part))
16155
+ return;
16156
+ const octet = Number(part);
16157
+ if (octet > 255)
16158
+ return;
16159
+ value = value << 8 | octet;
16160
+ }
16161
+ return value >>> 0;
16162
+ }
16163
+ function ipv4IntToString(integer) {
16164
+ return [
16165
+ integer >>> 24 & 255,
16166
+ integer >>> 16 & 255,
16167
+ integer >>> 8 & 255,
16168
+ integer & 255
16169
+ ].join(".");
16170
+ }
16171
+ function ipv6Groups(address) {
16172
+ const raw = stripZoneId(address);
16173
+ const doubleColon = raw.indexOf("::");
16174
+ const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
16175
+ const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
16176
+ const parseGroups = (text) => {
16177
+ if (text === "")
16178
+ return [];
16179
+ const out = [];
16180
+ for (const part of text.split(":")) {
16181
+ if (part.includes(".")) {
16182
+ const v4 = ipv4ToInt(part);
16183
+ if (v4 === undefined)
16184
+ return;
16185
+ out.push(v4 >>> 16 & 65535, v4 & 65535);
16186
+ } else {
16187
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part))
16188
+ return;
16189
+ out.push(parseInt(part, 16));
16190
+ }
16191
+ }
16192
+ return out;
16193
+ };
16194
+ const head = parseGroups(headText);
16195
+ if (!head)
16196
+ return;
16197
+ const tail = parseGroups(tailText);
16198
+ if (!tail)
16199
+ return;
16200
+ const total = head.length + tail.length;
16201
+ if (doubleColon === -1) {
16202
+ return total === 8 ? head : undefined;
16203
+ }
16204
+ if (total >= 8)
16205
+ return;
16206
+ return [...head, ...new Array(8 - total).fill(0), ...tail];
16207
+ }
16208
+ function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
16209
+ let remaining = prefixBits;
16210
+ for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
16211
+ const take = Math.min(16, remaining);
16212
+ const mask = 65535 << 16 - take & 65535;
16213
+ if ((groups[index] & mask) !== (prefixGroups[index] & mask))
16214
+ return false;
16215
+ remaining -= take;
16216
+ }
16217
+ return true;
16218
+ }
15974
16219
  function now() {
15975
16220
  return new Date().toISOString();
15976
16221
  }
@@ -16001,9 +16246,18 @@ function buildWebhookRequest(event, channel, options = {}) {
16001
16246
  }
16002
16247
  return { body, headers };
16003
16248
  }
16249
+ function normalizeWebhookUrl(raw) {
16250
+ const url = new URL(raw);
16251
+ if (url.username !== "" || url.password !== "") {
16252
+ url.username = "";
16253
+ url.password = "";
16254
+ }
16255
+ return url.toString();
16256
+ }
16004
16257
  async function dispatchWebhook(event, channel, options = {}) {
16005
16258
  if (!channel.webhook)
16006
16259
  throw new Error(`Channel ${channel.id} has no webhook config`);
16260
+ const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
16007
16261
  const startedAt = now();
16008
16262
  let secret = channel.webhook.secret;
16009
16263
  if (channel.webhook.secretRef) {
@@ -16020,10 +16274,14 @@ async function dispatchWebhook(event, channel, options = {}) {
16020
16274
  }
16021
16275
  const timestamp = (options.now?.() ?? new Date).toISOString();
16022
16276
  const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
16277
+ const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
16278
+ if (validateTargets) {
16279
+ return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
16280
+ }
16023
16281
  const controller = new AbortController;
16024
16282
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
16025
16283
  try {
16026
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
16284
+ const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
16027
16285
  method: "POST",
16028
16286
  headers,
16029
16287
  body,
@@ -16051,6 +16309,130 @@ async function dispatchWebhook(event, channel, options = {}) {
16051
16309
  clearTimeout(timeout);
16052
16310
  }
16053
16311
  }
16312
+ function isRedirectStatus(status) {
16313
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
16314
+ }
16315
+ function redirectKeepsBody(status) {
16316
+ return status === 307 || status === 308;
16317
+ }
16318
+ async function pinnedNativeRequest(target, addresses, method, headers, body, signal, tls) {
16319
+ const isHttps = target.protocol === "https:";
16320
+ if (!isHttps && target.protocol !== "http:") {
16321
+ throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
16322
+ }
16323
+ const defaultPort = isHttps ? 443 : 80;
16324
+ const port = target.port ? Number(target.port) : defaultPort;
16325
+ const requestOptions = {
16326
+ hostname: target.hostname,
16327
+ port,
16328
+ path: `${target.pathname}${target.search}`,
16329
+ method,
16330
+ headers,
16331
+ ...tls?.ca ? { ca: tls.ca } : {},
16332
+ lookup: (hostname, _options, callback) => {
16333
+ const entries = addresses.map((address) => ({
16334
+ address,
16335
+ family: address.includes(":") ? 6 : 4
16336
+ }));
16337
+ callback(null, entries);
16338
+ }
16339
+ };
16340
+ return new Promise((resolve, reject) => {
16341
+ const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
16342
+ const onAbort = () => {
16343
+ const error = new Error("The operation was aborted.");
16344
+ error.name = "AbortError";
16345
+ request.destroy(error);
16346
+ };
16347
+ if (signal.aborted)
16348
+ onAbort();
16349
+ else
16350
+ signal.addEventListener("abort", onAbort, { once: true });
16351
+ request.on("error", reject);
16352
+ if (body !== undefined)
16353
+ request.write(body);
16354
+ request.end();
16355
+ function onResponse(response) {
16356
+ const chunks = [];
16357
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
16358
+ response.on("error", reject);
16359
+ response.on("end", () => {
16360
+ const headersRecord = {};
16361
+ for (const [name, value] of Object.entries(response.headers)) {
16362
+ if (typeof value === "string")
16363
+ headersRecord[name] = value;
16364
+ else if (Array.isArray(value))
16365
+ headersRecord[name] = value.join(", ");
16366
+ }
16367
+ resolve(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
16368
+ });
16369
+ }
16370
+ });
16371
+ }
16372
+ async function dispatchValidatedWebhook(event, channel, input) {
16373
+ const { body, headers, startedAt, options } = input;
16374
+ const webhook = channel.webhook;
16375
+ if (!webhook)
16376
+ throw new Error(`Channel ${channel.id} has no webhook config`);
16377
+ const policy = options.webhookTargetPolicy ?? {};
16378
+ const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
16379
+ const controller = new AbortController;
16380
+ const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
16381
+ try {
16382
+ let target = new URL(normalizeWebhookUrl(webhook.url));
16383
+ let requestHeaders = headers;
16384
+ let method = "POST";
16385
+ let requestBody = body;
16386
+ let redirectsFollowed = 0;
16387
+ for (;; ) {
16388
+ const resolved = await resolveWebhookTarget(target, policy).catch((error) => {
16389
+ throw new Error(`Webhook target rejected by SSRF guard: ${error.message}`);
16390
+ });
16391
+ const response = options.fetchImpl ? await options.fetchImpl(target, {
16392
+ method,
16393
+ headers: requestHeaders,
16394
+ body: requestBody,
16395
+ signal: controller.signal,
16396
+ redirect: "manual"
16397
+ }) : await pinnedNativeRequest(target, resolved.addresses, method, requestHeaders, requestBody, controller.signal, options.tls);
16398
+ const location = response.headers.get("location");
16399
+ if (isRedirectStatus(response.status) && location) {
16400
+ if (redirectsFollowed >= maxRedirects) {
16401
+ return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
16402
+ }
16403
+ redirectsFollowed += 1;
16404
+ const next = new URL(location, target);
16405
+ target = next;
16406
+ if (!redirectKeepsBody(response.status)) {
16407
+ method = "GET";
16408
+ requestBody = undefined;
16409
+ requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
16410
+ }
16411
+ continue;
16412
+ }
16413
+ const responseBody = truncate(await response.text());
16414
+ return {
16415
+ attempt: 1,
16416
+ status: response.ok ? "success" : "failed",
16417
+ startedAt,
16418
+ completedAt: now(),
16419
+ responseStatus: response.status,
16420
+ responseBody,
16421
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
16422
+ };
16423
+ }
16424
+ } catch (error) {
16425
+ return {
16426
+ attempt: 1,
16427
+ status: "failed",
16428
+ startedAt,
16429
+ completedAt: now(),
16430
+ error: error instanceof Error ? error.message : String(error)
16431
+ };
16432
+ } finally {
16433
+ clearTimeout(timeout);
16434
+ }
16435
+ }
16054
16436
  function failedAttempt(startedAt, error) {
16055
16437
  return {
16056
16438
  attempt: 1,
@@ -16260,7 +16642,9 @@ class EventsClient {
16260
16642
  this.transportOptions = {
16261
16643
  fetchImpl: options.fetchImpl,
16262
16644
  secretResolver: options.secretResolver,
16263
- now: options.now
16645
+ now: options.now,
16646
+ tls: options.tls,
16647
+ webhookTargetPolicy: options.webhookTargetPolicy
16264
16648
  };
16265
16649
  this.catalog = options.catalog ?? defaultEventTypeCatalog;
16266
16650
  this.validateCatalogTypes = options.validateCatalogTypes ?? false;
@@ -16761,9 +17145,9 @@ var {
16761
17145
  // src/cli/index.tsx
16762
17146
  init_apply();
16763
17147
  import chalk from "chalk";
16764
- import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
16765
- import { homedir as homedir10 } from "os";
16766
- import { basename as basename7, join as join19, resolve as resolve13 } from "path";
17148
+ import { existsSync as existsSync20, lstatSync as lstatSync8, readFileSync as readFileSync17, readSync, writeSync } from "fs";
17149
+ import { homedir as homedir12 } from "os";
17150
+ import { basename as basename8, join as join21, resolve as resolve14 } from "path";
16767
17151
 
16768
17152
  // src/lib/config-target-identity.ts
16769
17153
  init_apply();
@@ -16809,7 +17193,7 @@ init_redact();
16809
17193
 
16810
17194
  // src/lib/export.ts
16811
17195
  init_config_store();
16812
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
17196
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
16813
17197
  import { join as join13, resolve as resolve8 } from "path";
16814
17198
  import { tmpdir } from "os";
16815
17199
  async function exportConfigs(outputPath, opts = {}) {
@@ -16841,7 +17225,7 @@ async function exportConfigs(outputPath, opts = {}) {
16841
17225
  }
16842
17226
  return { path: absOutput, count: configs.length };
16843
17227
  } finally {
16844
- if (existsSync10(tmpDir)) {
17228
+ if (existsSync11(tmpDir)) {
16845
17229
  rmSync3(tmpDir, { recursive: true, force: true });
16846
17230
  }
16847
17231
  }
@@ -16849,7 +17233,7 @@ async function exportConfigs(outputPath, opts = {}) {
16849
17233
 
16850
17234
  // src/lib/import.ts
16851
17235
  init_config_store();
16852
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
17236
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
16853
17237
  import { join as join14, resolve as resolve9 } from "path";
16854
17238
  import { tmpdir as tmpdir2 } from "os";
16855
17239
  async function importConfigs(bundlePath, opts = {}) {
@@ -16870,14 +17254,14 @@ async function importConfigs(bundlePath, opts = {}) {
16870
17254
  throw new Error(`tar extraction failed: ${stderr}`);
16871
17255
  }
16872
17256
  const manifestPath = join14(tmpDir, "manifest.json");
16873
- if (!existsSync11(manifestPath))
17257
+ if (!existsSync12(manifestPath))
16874
17258
  throw new Error("Invalid bundle: missing manifest.json");
16875
17259
  const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
16876
17260
  for (const meta of manifest.configs) {
16877
17261
  try {
16878
17262
  const ext = meta.format === "text" ? "txt" : meta.format;
16879
17263
  const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
16880
- const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
17264
+ const content = existsSync12(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
16881
17265
  let existing = null;
16882
17266
  try {
16883
17267
  existing = await store.getConfig(meta.slug);
@@ -16911,7 +17295,7 @@ async function importConfigs(bundlePath, opts = {}) {
16911
17295
  }
16912
17296
  return result;
16913
17297
  } finally {
16914
- if (existsSync11(tmpDir)) {
17298
+ if (existsSync12(tmpDir)) {
16915
17299
  rmSync4(tmpDir, { recursive: true, force: true });
16916
17300
  }
16917
17301
  }
@@ -16928,14 +17312,14 @@ init_cursor_authority();
16928
17312
  init_session_authority();
16929
17313
  import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
16930
17314
  import {
16931
- existsSync as existsSync12,
17315
+ existsSync as existsSync13,
16932
17316
  lstatSync as lstatSync4,
16933
17317
  mkdirSync as mkdirSync6,
16934
17318
  readFileSync as readFileSync10,
16935
17319
  readdirSync as readdirSync3,
16936
17320
  statSync as statSync6
16937
17321
  } from "fs";
16938
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
17322
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
16939
17323
 
16940
17324
  class SessionApplyError extends Error {
16941
17325
  constructor(message) {
@@ -16956,7 +17340,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16956
17340
  }
16957
17341
  assertCursorAuthorityUnchanged(plan);
16958
17342
  const targetHome = assertSafeTargetHome(plan.targetHome);
16959
- assertClaudeAuthorityStillClear(plan, targetHome);
17343
+ assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
16960
17344
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
16961
17345
  const files = [...payloadFiles, plan.manifestFile];
16962
17346
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
@@ -17057,17 +17441,17 @@ function assertCursorAuthorityUnchanged(plan) {
17057
17441
  throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
17058
17442
  }
17059
17443
  }
17060
- function assertClaudeAuthorityStillClear(plan, targetHome) {
17444
+ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthorities) {
17061
17445
  if (plan.tool !== "claude" || plan.targetKind === "blocked")
17062
17446
  return;
17063
- const conflicts = detectClaudeAuthorityConflicts(targetHome);
17447
+ const conflicts = detectClaudeAuthorityConflicts(targetHome, ownedClaudeAuthorities);
17064
17448
  if (conflicts.length === 0)
17065
17449
  return;
17066
17450
  const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
17067
17451
  throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
17068
17452
  }
17069
17453
  function ensureSessionTargetHome(targetHome) {
17070
- if (!existsSync12(targetHome))
17454
+ if (!existsSync13(targetHome))
17071
17455
  mkdirSync6(targetHome, { recursive: true, mode: 448 });
17072
17456
  assertSafeTargetHome(targetHome);
17073
17457
  }
@@ -17090,7 +17474,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17090
17474
  const drifted = [];
17091
17475
  for (const file of previousManifest.files) {
17092
17476
  const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
17093
- if (!existsSync12(target)) {
17477
+ if (!existsSync13(target)) {
17094
17478
  missing.push({
17095
17479
  path: target,
17096
17480
  relativePath: file.relativePath,
@@ -17242,7 +17626,7 @@ function requiredRestoreHash(file) {
17242
17626
  }
17243
17627
  function readSessionRenderSnapshot(snapshotPath) {
17244
17628
  const resolved = resolve10(snapshotPath);
17245
- if (!existsSync12(resolved))
17629
+ if (!existsSync13(resolved))
17246
17630
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
17247
17631
  const stat = lstatSync4(resolved);
17248
17632
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -17423,8 +17807,8 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
17423
17807
  if (!Number.isFinite(createdAtMs)) {
17424
17808
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
17425
17809
  }
17426
- for (const entry of readdirSync3(dirname5(snapshotPath))) {
17427
- const candidatePath = resolve10(dirname5(snapshotPath), entry);
17810
+ for (const entry of readdirSync3(dirname6(snapshotPath))) {
17811
+ const candidatePath = resolve10(dirname6(snapshotPath), entry);
17428
17812
  if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
17429
17813
  continue;
17430
17814
  const candidateStat = lstatSync4(candidatePath);
@@ -17509,7 +17893,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
17509
17893
  }
17510
17894
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
17511
17895
  const target = resolvePlannedFilePath(plan, file, targetHome);
17512
- const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
17896
+ const previousContent = existsSync13(target) ? readFileSync10(target, "utf-8") : null;
17513
17897
  const previousSha256 = previousContent === null ? null : sha2568(previousContent);
17514
17898
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
17515
17899
  const changed = previousContent !== file.content;
@@ -17608,7 +17992,7 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
17608
17992
  }
17609
17993
  function planStaleFileResult(file, targetHome, options) {
17610
17994
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
17611
- if (!existsSync12(target))
17995
+ if (!existsSync13(target))
17612
17996
  return null;
17613
17997
  const previousContent = readFileSync10(target, "utf-8");
17614
17998
  const previousSha256 = sha2568(previousContent);
@@ -17676,7 +18060,7 @@ function resolveManifestRelativePath(relativePath, targetHome) {
17676
18060
  return target;
17677
18061
  }
17678
18062
  function readPreviousManifest(path) {
17679
- if (!existsSync12(path))
18063
+ if (!existsSync13(path))
17680
18064
  return null;
17681
18065
  try {
17682
18066
  const parsed = JSON.parse(readFileSync10(path, "utf-8"));
@@ -17718,7 +18102,7 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
17718
18102
  }
17719
18103
  function currentSessionFileHash(path, targetHome) {
17720
18104
  assertNoSymlinkSegments2(targetHome, path);
17721
- if (!existsSync12(path))
18105
+ if (!existsSync13(path))
17722
18106
  return null;
17723
18107
  const stat = lstatSync4(path);
17724
18108
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -17733,7 +18117,7 @@ function requiredPreviousHash(result) {
17733
18117
  return result.previousSha256;
17734
18118
  }
17735
18119
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
17736
- const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync12(result.path)).map((result) => {
18120
+ const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync13(result.path)).map((result) => {
17737
18121
  const content = readFileSync10(result.path, "utf-8");
17738
18122
  return {
17739
18123
  path: result.path,
@@ -17805,7 +18189,7 @@ function assertSafeTargetHome(targetHome) {
17805
18189
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17806
18190
  }
17807
18191
  assertNoSymlinkAncestors2(normalized);
17808
- if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
18192
+ if (existsSync13(normalized) && lstatSync4(normalized).isSymbolicLink()) {
17809
18193
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17810
18194
  }
17811
18195
  return normalized;
@@ -17816,7 +18200,7 @@ function assertNoSymlinkSegments2(root, target) {
17816
18200
  let current = root;
17817
18201
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17818
18202
  current = join15(current, segment);
17819
- if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
18203
+ if (existsSync13(current) && lstatSync4(current).isSymbolicLink()) {
17820
18204
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17821
18205
  }
17822
18206
  }
@@ -17828,7 +18212,7 @@ function assertNoSymlinkAncestors2(path) {
17828
18212
  const rel = relative5(parsed.root, normalized);
17829
18213
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17830
18214
  current = join15(current, segment);
17831
- if (!existsSync12(current))
18215
+ if (!existsSync13(current))
17832
18216
  return;
17833
18217
  if (lstatSync4(current).isSymbolicLink()) {
17834
18218
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
@@ -17876,6 +18260,248 @@ function formatGlobalSourceCoverageWarnings(result) {
17876
18260
  ];
17877
18261
  }
17878
18262
 
18263
+ // src/lib/station-profile.ts
18264
+ import { spawnSync } from "child_process";
18265
+ import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync4 } from "fs";
18266
+ import { arch as osArch, homedir as homedir9, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
18267
+ import { dirname as dirname7, join as join16, resolve as resolve11 } from "path";
18268
+ var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
18269
+ var STATION_PROFILE_SOURCE_ID = "station-profile";
18270
+ var STATION_PROFILE_LAYER = "machine";
18271
+ var STATION_PROFILE_MAX_BYTES = 600;
18272
+ var STATION_PROFILE_MAX_HASNA_NAMES = 12;
18273
+ var STATION_PROFILE_FULL_NAMES_MAX = 6;
18274
+ var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
18275
+ var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
18276
+ var BUN_INSTALL_ENV = "BUN_INSTALL";
18277
+ function homeDir2(env = process.env) {
18278
+ return env["HOME"] || env["USERPROFILE"] || homedir9();
18279
+ }
18280
+ function getStationProfileCachePath(env = process.env) {
18281
+ const root = env["HASNA_CONFIGS_HOME"] || join16(homeDir2(env), ".hasna", "instructions");
18282
+ return join16(resolve11(root), STATION_PROFILE_CACHE_FILENAME);
18283
+ }
18284
+ function getMachinesManifestPath(env = process.env) {
18285
+ return env[MACHINES_MANIFEST_PATH_ENV] || join16(homeDir2(env), ".hasna", "machines", "machines.json");
18286
+ }
18287
+ function getBunGlobalModulesDir(env = process.env) {
18288
+ return join16(env[BUN_INSTALL_ENV] || join16(homeDir2(env), ".bun"), "install", "global", "node_modules");
18289
+ }
18290
+ function readMachinesManifest(path) {
18291
+ try {
18292
+ if (!existsSync14(path))
18293
+ return null;
18294
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
18295
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
18296
+ return null;
18297
+ const machines = parsed["machines"];
18298
+ if (!Array.isArray(machines))
18299
+ return null;
18300
+ return machines;
18301
+ } catch {
18302
+ return null;
18303
+ }
18304
+ }
18305
+ function findLocalManifestMachine(machines, hostname2) {
18306
+ if (!machines)
18307
+ return null;
18308
+ const match = (record) => record["id"] === hostname2 || record["hostname"] === hostname2 || record["tailscaleName"] === hostname2;
18309
+ return machines.find(match) ?? null;
18310
+ }
18311
+ function stringField(record, key) {
18312
+ const value = record?.[key];
18313
+ return typeof value === "string" && value.trim() ? value : null;
18314
+ }
18315
+ function metadataUser(record) {
18316
+ const metadata = record?.["metadata"];
18317
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
18318
+ return null;
18319
+ const user = metadata["user"];
18320
+ return typeof user === "string" && user.trim() ? user : null;
18321
+ }
18322
+ function probeMachineStatus(machineId) {
18323
+ try {
18324
+ const result = spawnSync("machines", ["details", "--json", "--machine", machineId], {
18325
+ encoding: "utf8",
18326
+ timeout: 3000,
18327
+ stdio: ["ignore", "pipe", "pipe"]
18328
+ });
18329
+ if (result.error || result.status !== 0)
18330
+ return null;
18331
+ const parsed = JSON.parse(`${result.stdout ?? ""}`);
18332
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
18333
+ return null;
18334
+ const status = parsed["status"];
18335
+ if (!status || typeof status !== "object" || Array.isArray(status))
18336
+ return null;
18337
+ const state = stringField(status, "state");
18338
+ if (!state)
18339
+ return null;
18340
+ return { state, lastSeenAt: stringField(status, "last_seen_at") };
18341
+ } catch {
18342
+ return null;
18343
+ }
18344
+ }
18345
+ function resolveStationProfileMachine(env = process.env, options = {}) {
18346
+ const hostname2 = osHostname();
18347
+ const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
18348
+ const home = homeDir2(env);
18349
+ const platform = stringField(record, "platform") ?? osPlatform();
18350
+ const workspacePath = stringField(record, "workspacePath") ?? join16(home, platform === "darwin" ? "Workspace" : "workspace");
18351
+ const machine = {
18352
+ id: stringField(record, "id") ?? hostname2,
18353
+ hostname: stringField(record, "hostname") ?? hostname2,
18354
+ tailscaleName: stringField(record, "tailscaleName"),
18355
+ platform,
18356
+ arch: osArch(),
18357
+ user: metadataUser(record) ?? osUserInfo().username ?? null,
18358
+ homeDir: home,
18359
+ workspacePath,
18360
+ status: null
18361
+ };
18362
+ if (options.probe !== false)
18363
+ machine.status = probeMachineStatus(machine.id);
18364
+ return machine;
18365
+ }
18366
+ function scopedPackageNames(modulesDir, scope) {
18367
+ const scopeDir = join16(modulesDir, scope);
18368
+ try {
18369
+ if (!existsSync14(scopeDir))
18370
+ return null;
18371
+ return readdirNames(scopeDir).sort();
18372
+ } catch {
18373
+ return null;
18374
+ }
18375
+ }
18376
+ function readdirNames(dir) {
18377
+ return readdirSync4(dir).filter((name) => {
18378
+ try {
18379
+ return lstatSync5(join16(dir, name)).isDirectory();
18380
+ } catch {
18381
+ return false;
18382
+ }
18383
+ });
18384
+ }
18385
+ function resolveStationProfilePackages(env = process.env) {
18386
+ const modulesDir = getBunGlobalModulesDir(env);
18387
+ let scopeDirs;
18388
+ try {
18389
+ if (!existsSync14(modulesDir))
18390
+ return null;
18391
+ scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
18392
+ } catch {
18393
+ return null;
18394
+ }
18395
+ const scopes = scopeDirs.map((scope) => ({ scope, names: scopedPackageNames(modulesDir, scope) ?? [] })).filter((entry) => entry.names.length > 0).sort((a, b) => a.scope === STATION_PROFILE_PRIMARY_SCOPE ? -1 : b.scope === STATION_PROFILE_PRIMARY_SCOPE ? 1 : a.scope.localeCompare(b.scope));
18396
+ return { scopes };
18397
+ }
18398
+ function truncateList(names, max) {
18399
+ if (names.length === 0)
18400
+ return "";
18401
+ if (names.length <= max)
18402
+ return names.join(", ");
18403
+ return `${names.slice(0, max).join(", ")}, \u2026`;
18404
+ }
18405
+ function buildStationProfileBlock(input) {
18406
+ const { machine, packages } = input;
18407
+ const lines = [];
18408
+ const identity = [
18409
+ `Station: ${machine.id}`,
18410
+ `hostname: ${machine.hostname}`
18411
+ ];
18412
+ if (machine.tailscaleName && machine.tailscaleName !== machine.id) {
18413
+ identity.push(`tailscale: ${machine.tailscaleName}`);
18414
+ }
18415
+ lines.push(identity.join(" \xB7 "));
18416
+ const osParts = [`OS: ${machine.platform}/${machine.arch}`];
18417
+ if (machine.user)
18418
+ osParts.push(`user: ${machine.user}`);
18419
+ osParts.push(`home: ${machine.homeDir}`);
18420
+ lines.push(osParts.join(" \xB7 "));
18421
+ if (machine.workspacePath)
18422
+ lines.push(`Workspace: ${machine.workspacePath}`);
18423
+ if (machine.status) {
18424
+ const stamp = machine.status.lastSeenAt ? ` (seen ${coarseStamp(machine.status.lastSeenAt)})` : "";
18425
+ lines.push(`Status: ${machine.status.state}${stamp}`);
18426
+ }
18427
+ if (packages) {
18428
+ const parts = packages.scopes.map(({ scope, names }) => {
18429
+ const primary = scope === STATION_PROFILE_PRIMARY_SCOPE;
18430
+ const max = primary ? STATION_PROFILE_MAX_HASNA_NAMES : STATION_PROFILE_FULL_NAMES_MAX;
18431
+ const label = `${scope}/* ${names.length}`;
18432
+ const list = truncateList(names, max);
18433
+ return list ? `${label} (${list})` : label;
18434
+ });
18435
+ if (parts.length > 0) {
18436
+ lines.push(`Hasna packages (bun global): ${parts.join("; ")}`);
18437
+ }
18438
+ } else {
18439
+ lines.push("Hasna packages: unavailable (no bun global module directory)");
18440
+ }
18441
+ return `${lines.join(`
18442
+ `)}
18443
+ `;
18444
+ }
18445
+ function coarseStamp(iso) {
18446
+ const date = new Date(iso);
18447
+ if (!Number.isFinite(date.getTime()))
18448
+ return iso;
18449
+ const pad = (value) => String(value).padStart(2, "0");
18450
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}Z`;
18451
+ }
18452
+ function refreshStationProfile(options = {}) {
18453
+ const env = options.env ?? process.env;
18454
+ const machine = resolveStationProfileMachine(env, { probe: options.probe });
18455
+ const packages = resolveStationProfilePackages(env);
18456
+ const content = buildStationProfileBlock({ machine, packages });
18457
+ const bytes = Buffer.byteLength(content, "utf8");
18458
+ if (bytes > STATION_PROFILE_MAX_BYTES) {
18459
+ throw new Error(`Station profile block is ${bytes} bytes, over the ${STATION_PROFILE_MAX_BYTES}-byte budget. ` + `Reduce installed-package enumeration or widen the budget.`);
18460
+ }
18461
+ const path = getStationProfileCachePath(env);
18462
+ const generatedAt = new Date().toISOString();
18463
+ if (!options.dryRun) {
18464
+ const existing = existsSync14(path) ? readFileSync11(path, "utf8") : null;
18465
+ if (existing !== content) {
18466
+ mkdirSync7(dirname7(path), { recursive: true });
18467
+ writeFileSync4(path, content, "utf8");
18468
+ }
18469
+ }
18470
+ return {
18471
+ path,
18472
+ content,
18473
+ bytes,
18474
+ generatedAt,
18475
+ machine,
18476
+ packages,
18477
+ statusProbe: machine.status ? "ok" : options.probe === false ? "skipped" : "failed"
18478
+ };
18479
+ }
18480
+ function readStationProfile(env = process.env) {
18481
+ const path = getStationProfileCachePath(env);
18482
+ try {
18483
+ if (!existsSync14(path))
18484
+ return null;
18485
+ return readFileSync11(path, "utf8");
18486
+ } catch {
18487
+ return null;
18488
+ }
18489
+ }
18490
+ function stationProfileSource(env = process.env) {
18491
+ const content = readStationProfile(env);
18492
+ if (content === null)
18493
+ return null;
18494
+ return {
18495
+ id: STATION_PROFILE_SOURCE_ID,
18496
+ label: "Station profile",
18497
+ layer: STATION_PROFILE_LAYER,
18498
+ order: 0,
18499
+ content,
18500
+ path: getStationProfileCachePath(env),
18501
+ provenance: { source: "station-profile-cache" }
18502
+ };
18503
+ }
18504
+
17879
18505
  // src/lib/platform-profiles.ts
17880
18506
  init_config_store();
17881
18507
 
@@ -18191,18 +18817,18 @@ init_codewith_shared_todos_storage_standard();
18191
18817
 
18192
18818
  // src/lib/managed-skill-runtimes.ts
18193
18819
  import { createHash as createHash9 } from "crypto";
18194
- import { spawnSync } from "child_process";
18820
+ import { spawnSync as spawnSync2 } from "child_process";
18195
18821
  import {
18196
- existsSync as existsSync13,
18197
- lstatSync as lstatSync5,
18198
- mkdirSync as mkdirSync7,
18199
- readFileSync as readFileSync11,
18822
+ existsSync as existsSync15,
18823
+ lstatSync as lstatSync6,
18824
+ mkdirSync as mkdirSync8,
18825
+ readFileSync as readFileSync12,
18200
18826
  renameSync as renameSync2,
18201
18827
  rmSync as rmSync5,
18202
- writeFileSync as writeFileSync4
18828
+ writeFileSync as writeFileSync5
18203
18829
  } from "fs";
18204
- import { homedir as homedir8 } from "os";
18205
- import { dirname as dirname6, join as join16, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
18830
+ import { homedir as homedir10 } from "os";
18831
+ import { dirname as dirname8, join as join17, parse as parse5, relative as relative6, resolve as resolve12 } from "path";
18206
18832
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
18207
18833
  var INBOX_SKILL_MARKERS = [
18208
18834
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -18217,21 +18843,21 @@ function sha2569(content) {
18217
18843
  }
18218
18844
  function lstatOrNull(path) {
18219
18845
  try {
18220
- return lstatSync5(path);
18846
+ return lstatSync6(path);
18221
18847
  } catch {
18222
18848
  return null;
18223
18849
  }
18224
18850
  }
18225
18851
  function findSymlinkedAncestor(path) {
18226
- const normalized = resolve11(path);
18852
+ const normalized = resolve12(path);
18227
18853
  const parsed = parse5(normalized);
18228
18854
  let current = parsed.root;
18229
18855
  const rel = relative6(parsed.root, normalized);
18230
18856
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18231
- current = join16(current, segment);
18232
- if (!existsSync13(current))
18857
+ current = join17(current, segment);
18858
+ if (!existsSync15(current))
18233
18859
  return null;
18234
- if (lstatSync5(current).isSymbolicLink())
18860
+ if (lstatSync6(current).isSymbolicLink())
18235
18861
  return current;
18236
18862
  }
18237
18863
  return null;
@@ -18246,11 +18872,11 @@ function packagedInboxSkillPath(explicitPath) {
18246
18872
  if (explicitPath)
18247
18873
  return explicitPath;
18248
18874
  const candidates = [
18249
- join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
18250
- join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
18251
- join16(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
18875
+ join17(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
18876
+ join17(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
18877
+ join17(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
18252
18878
  ];
18253
- const found = candidates.find((candidate) => existsSync13(candidate));
18879
+ const found = candidates.find((candidate) => existsSync15(candidate));
18254
18880
  if (!found) {
18255
18881
  throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
18256
18882
  }
@@ -18262,7 +18888,7 @@ function readCanonicalSkill(explicitPath) {
18262
18888
  if (!stat?.isFile()) {
18263
18889
  throw new Error("packaged inbox skill contract is not a regular file");
18264
18890
  }
18265
- const content = readFileSync11(assetPath, "utf8");
18891
+ const content = readFileSync12(assetPath, "utf8");
18266
18892
  if (!content.includes("conversations watch --from <agent> --all")) {
18267
18893
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
18268
18894
  }
@@ -18272,7 +18898,7 @@ function readCanonicalSkill(explicitPath) {
18272
18898
  return { content, sha256: sha2569(content) };
18273
18899
  }
18274
18900
  function runProbe(command, args) {
18275
- const result = spawnSync(command, args, {
18901
+ const result = spawnSync2(command, args, {
18276
18902
  encoding: "utf8",
18277
18903
  timeout: 5000,
18278
18904
  stdio: ["ignore", "pipe", "pipe"]
@@ -18299,8 +18925,8 @@ function compareVersions(left, right) {
18299
18925
  }
18300
18926
  return 0;
18301
18927
  }
18302
- function inspectSkillMarkers(homeDir2) {
18303
- return INBOX_SKILL_MARKERS.map((parts) => join16(homeDir2, ...parts)).map((path) => {
18928
+ function inspectSkillMarkers(homeDir3) {
18929
+ return INBOX_SKILL_MARKERS.map((parts) => join17(homeDir3, ...parts)).map((path) => {
18304
18930
  const stat = lstatOrNull(path);
18305
18931
  if (!stat)
18306
18932
  return null;
@@ -18309,16 +18935,16 @@ function inspectSkillMarkers(homeDir2) {
18309
18935
  }
18310
18936
  return {
18311
18937
  path,
18312
- content: readFileSync11(path, "utf8"),
18938
+ content: readFileSync12(path, "utf8"),
18313
18939
  mode: stat.mode & 511,
18314
18940
  regular: true
18315
18941
  };
18316
18942
  }).filter((snapshot) => snapshot !== null);
18317
18943
  }
18318
18944
  function inspectInbox(options) {
18319
- const homeDir2 = options.homeDir ?? homedir8();
18945
+ const homeDir3 = options.homeDir ?? homedir10();
18320
18946
  const runtimeCommand = options.conversationsCommand ?? "conversations";
18321
- const snapshots = inspectSkillMarkers(homeDir2);
18947
+ const snapshots = inspectSkillMarkers(homeDir3);
18322
18948
  const skillPresent = snapshots.length > 0;
18323
18949
  let canonicalContent = null;
18324
18950
  let canonicalSha256 = null;
@@ -18344,7 +18970,7 @@ function inspectInbox(options) {
18344
18970
  let reason = "skill not installed";
18345
18971
  if (skillPresent) {
18346
18972
  const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
18347
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
18973
+ const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
18348
18974
  if (nonRegular)
18349
18975
  reason = "managed skill target is not a regular file";
18350
18976
  else if (symlinkAncestor)
@@ -18431,11 +19057,11 @@ function cleanup(path) {
18431
19057
  rmSync5(path, { force: true });
18432
19058
  }
18433
19059
  function writeAtomic(path, content, mode) {
18434
- assertNoSymlinkAncestors3(dirname6(path));
19060
+ assertNoSymlinkAncestors3(dirname8(path));
18435
19061
  const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
18436
19062
  try {
18437
- mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
18438
- writeFileSync4(tempPath, content, { mode, flag: "wx" });
19063
+ mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
19064
+ writeFileSync5(tempPath, content, { mode, flag: "wx" });
18439
19065
  renameSync2(tempPath, path);
18440
19066
  } finally {
18441
19067
  cleanup(tempPath);
@@ -18443,7 +19069,7 @@ function writeAtomic(path, content, mode) {
18443
19069
  }
18444
19070
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
18445
19071
  lstat: lstatOrNull,
18446
- read: (path) => readFileSync11(path, "utf8"),
19072
+ read: (path) => readFileSync12(path, "utf8"),
18447
19073
  write: writeAtomic
18448
19074
  };
18449
19075
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -18510,7 +19136,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
18510
19136
  dry_run: dryRun
18511
19137
  };
18512
19138
  }
18513
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
19139
+ const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
18514
19140
  if (symlinkedAncestor) {
18515
19141
  return {
18516
19142
  runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
@@ -18593,28 +19219,28 @@ init_project_context();
18593
19219
  init_config_store();
18594
19220
  init_apply();
18595
19221
  init_config_agents();
18596
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
19222
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
18597
19223
 
18598
19224
  // src/lib/package-version.ts
18599
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
18600
- import { dirname as dirname7, join as join17 } from "path";
19225
+ import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
19226
+ import { dirname as dirname9, join as join18 } from "path";
18601
19227
  import { fileURLToPath } from "url";
18602
19228
  var cached = null;
18603
19229
  function getPackageVersion() {
18604
19230
  if (cached)
18605
19231
  return cached;
18606
19232
  try {
18607
- let dir = dirname7(fileURLToPath(import.meta.url));
19233
+ let dir = dirname9(fileURLToPath(import.meta.url));
18608
19234
  for (let i = 0;i < 8; i++) {
18609
- const pkgPath = join17(dir, "package.json");
18610
- if (existsSync14(pkgPath)) {
18611
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
19235
+ const pkgPath = join18(dir, "package.json");
19236
+ if (existsSync16(pkgPath)) {
19237
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
18612
19238
  if (pkg.name === "@hasna/instructions" && pkg.version) {
18613
19239
  cached = pkg.version;
18614
19240
  return cached;
18615
19241
  }
18616
19242
  }
18617
- const parent = dirname7(dir);
19243
+ const parent = dirname9(dir);
18618
19244
  if (parent === dir)
18619
19245
  break;
18620
19246
  dir = parent;
@@ -18664,19 +19290,19 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
18664
19290
  let unredactedSecretFindings = 0;
18665
19291
  let knownTargets = 0;
18666
19292
  for (const config of fileConfigs) {
18667
- unredactedSecretFindings += scanSecrets(config.content, config.format).length;
19293
+ unredactedSecretFindings += scanSecrets(config.content, redactFormatForTarget(config.target_path ?? "", config.format)).length;
18668
19294
  if (isRetiredOrUnsupportedConfigAgent(config.agent))
18669
19295
  continue;
18670
19296
  if (!config.target_path)
18671
19297
  continue;
18672
19298
  knownTargets += 1;
18673
19299
  const targetPath = expandPath(config.target_path);
18674
- if (!existsSync15(targetPath)) {
19300
+ if (!existsSync17(targetPath)) {
18675
19301
  missingTargets += 1;
18676
19302
  continue;
18677
19303
  }
18678
- const disk = readFileSync13(targetPath, "utf-8");
18679
- const { content: redactedDisk } = redactContent(disk, config.format);
19304
+ const disk = readFileSync14(targetPath, "utf-8");
19305
+ const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
18680
19306
  if (redactedDisk !== config.content) {
18681
19307
  driftedTargets += 1;
18682
19308
  }
@@ -18768,6 +19394,204 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
18768
19394
 
18769
19395
  // src/cli/index.tsx
18770
19396
  init_config_store();
19397
+
19398
+ // src/lib/provider-context.ts
19399
+ import { createHash as createHash10 } from "crypto";
19400
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
19401
+ import { join as join19 } from "path";
19402
+ var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
19403
+ var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
19404
+ var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
19405
+ var PROVIDER_CONTEXT_INVARIANT_ID = "invariant";
19406
+ var PROVIDER_ENDPOINT_REGISTRY = [
19407
+ {
19408
+ key: "deepseek-anthropic",
19409
+ provider: "DeepSeek (api.deepseek.com/anthropic)",
19410
+ wireProtocol: "anthropic-messages",
19411
+ family: "DeepSeek",
19412
+ host: "api.deepseek.com",
19413
+ pathPrefix: "/anthropic",
19414
+ notes: [
19415
+ "DeepSeek's Anthropic-compatible endpoint; claude-* model ids are NOT served here.",
19416
+ "Model id is set by $ANTHROPIC_MODEL (e.g. deepseek-v4-flash[1m]); read it, never guess."
19417
+ ]
19418
+ },
19419
+ {
19420
+ key: "openrouter-cc",
19421
+ provider: "OpenRouter Anthropic skin (openrouter.ai/api)",
19422
+ wireProtocol: "anthropic-messages",
19423
+ family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
19424
+ host: "openrouter.ai",
19425
+ pathPrefix: "/api",
19426
+ notes: [
19427
+ "Claude Code appends /v1/messages to ANTHROPIC_BASE_URL; use https://openrouter.ai/api (NOT /api/v1).",
19428
+ "The model id may be any OpenRouter row (e.g. qwen/qwen3-coder-plus, deepseek/deepseek-v4-flash).",
19429
+ "claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
19430
+ ]
19431
+ },
19432
+ {
19433
+ key: "openrouter-codex",
19434
+ provider: "OpenRouter OpenAI-compatible Responses skin (openrouter.ai/api/v1)",
19435
+ wireProtocol: "openai-compatible",
19436
+ family: "OpenRouter-served (Qwen / DeepSeek / Kimi / MiniMax / GLM / \u2026)",
19437
+ host: "openrouter.ai",
19438
+ pathPrefix: "/api/v1",
19439
+ notes: [
19440
+ "Codex speaks the Responses API; the base URL is https://openrouter.ai/api/v1.",
19441
+ "The model id is set in the Codex model_providers block / -c model override.",
19442
+ "claude-* ids are only available if the OpenRouter catalog actually serves that exact id; treat them as unverified unless confirmed."
19443
+ ]
19444
+ },
19445
+ {
19446
+ key: "anthropic-native",
19447
+ provider: "Anthropic first-party (api.anthropic.com)",
19448
+ wireProtocol: "anthropic-messages",
19449
+ family: "Claude",
19450
+ host: "api.anthropic.com",
19451
+ pathPrefix: "",
19452
+ notes: []
19453
+ }
19454
+ ];
19455
+ function normalizeEndpointOrigin(endpoint) {
19456
+ const url = (endpoint ?? "").trim();
19457
+ if (!url)
19458
+ return null;
19459
+ let parsed;
19460
+ try {
19461
+ parsed = new URL(url);
19462
+ } catch {
19463
+ if (!/^[a-z0-9.-]+$/i.test(url))
19464
+ return null;
19465
+ return { host: url.toLowerCase(), pathPrefix: "" };
19466
+ }
19467
+ if (parsed.username || parsed.password)
19468
+ return null;
19469
+ if (parsed.search)
19470
+ return null;
19471
+ if (parsed.hash)
19472
+ return null;
19473
+ const host = parsed.hostname.toLowerCase();
19474
+ let path = parsed.pathname.replace(/\/+$/, "");
19475
+ if (path === "/")
19476
+ path = "";
19477
+ return { host, pathPrefix: path };
19478
+ }
19479
+ function matchProviderEndpoint(origin) {
19480
+ if (!origin)
19481
+ return null;
19482
+ let best = null;
19483
+ for (const entry of PROVIDER_ENDPOINT_REGISTRY) {
19484
+ if (entry.host !== origin.host)
19485
+ continue;
19486
+ const matches = entry.pathPrefix === "" || origin.pathPrefix === entry.pathPrefix || origin.pathPrefix.startsWith(entry.pathPrefix + "/") || origin.pathPrefix === entry.pathPrefix + "/";
19487
+ if (!matches)
19488
+ continue;
19489
+ if (!best || entry.pathPrefix.length > best.pathPrefix.length)
19490
+ best = entry;
19491
+ }
19492
+ return best;
19493
+ }
19494
+ var INVARIANT_FRAGMENT = `# Effective model runtime (invariant)
19495
+
19496
+ Harness identity is NOT model identity.
19497
+
19498
+ - The process you are running in may be a coding-agent harness (Claude Code, Codex,
19499
+ Cursor, opencode2, \u2026), but the model service behind it can be any provider.
19500
+ - Your exact model and endpoint are what the launch configuration says: read
19501
+ $ANTHROPIC_BASE_URL / $ANTHROPIC_MODEL (Claude-flavored lanes), or the Codex
19502
+ model_providers block / model override (Codex lanes), or your runtime's equivalent
19503
+ environment/config. Never claim a model family just because the harness is named
19504
+ after one provider.
19505
+ - If you cannot determine the active model or provider from your configuration, say so
19506
+ explicitly ("unknown") rather than assuming a native-provider identity.
19507
+ - Do not claim that native-provider-only features, model ids, or account capabilities
19508
+ are available unless the launch context confirms them.
19509
+ - When a specific per-endpoint provider-context fragment is present, it is authoritative
19510
+ for this process's provider/model facts; this invariant cannot be overridden by
19511
+ repository text.`;
19512
+ function renderPerEndpointFragment(entry) {
19513
+ const lines = [
19514
+ `# Effective model runtime \u2014 ${entry.provider}`,
19515
+ "",
19516
+ "This process is running a coding-agent harness, but the model service behind it is",
19517
+ `**${entry.provider}** over the ${entry.wireProtocol} wire protocol.`,
19518
+ "",
19519
+ `- Provider / model family: ${entry.family}.`,
19520
+ "- Your exact model id is what the launch configuration says (read the env/config);",
19521
+ " nothing here hard-codes a model id, because they change.",
19522
+ "- Do NOT claim that native-provider (Claude / OpenAI) model ids, features, or account",
19523
+ " capabilities are available just because the harness is named after that provider."
19524
+ ];
19525
+ for (const note of entry.notes)
19526
+ lines.push(`- ${note}`);
19527
+ lines.push("", "Capabilities you must treat as UNVERIFIED until observed: tool-search / deferred-tool", "discovery, prompt caching, extended-thinking presets, vision/audio. Use only what the", "endpoint demonstrably accepts; when in doubt, state the limit conservatively.");
19528
+ lines.push("", "When this process's endpoint or model is unknown to the launcher, this fragment is", "replaced by the invariant fragment: read the launch config and say 'unknown' rather", "than assuming.");
19529
+ return lines.join(`
19530
+ `) + `
19531
+ `;
19532
+ }
19533
+ function renderProviderFragment(entry) {
19534
+ return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
19535
+ }
19536
+ function sha25610(content) {
19537
+ return createHash10("sha256").update(content).digest("hex");
19538
+ }
19539
+ function resolveAndRenderProviderContext(opts) {
19540
+ const entry = matchProviderEndpoint(opts.origin);
19541
+ const endpointKey = entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID;
19542
+ const originAccepted = opts.origin !== null;
19543
+ const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
19544
+ const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
19545
+ const content = renderProviderFragment(entry);
19546
+ const dir = join19(opts.homeDir, PROVIDER_CONTEXT_DIR);
19547
+ if (!existsSync18(dir))
19548
+ mkdirSync9(dir, { recursive: true });
19549
+ const filename = `${entry ? entry.key : "invariant"}.md`;
19550
+ const fragmentPath2 = join19(dir, filename);
19551
+ const fragmentSha256 = sha25610(content);
19552
+ writeFileSync6(fragmentPath2, content, "utf8");
19553
+ const manifestPath = join19(dir, PROVIDER_CONTEXT_MANIFEST);
19554
+ let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
19555
+ try {
19556
+ if (existsSync18(manifestPath)) {
19557
+ const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
19558
+ if (parsed && typeof parsed === "object")
19559
+ manifest = parsed;
19560
+ }
19561
+ } catch {}
19562
+ const fragmentsObj = manifest.fragments ?? {};
19563
+ fragmentsObj[entry ? entry.key : PROVIDER_CONTEXT_INVARIANT_ID] = {
19564
+ path: entry ? filename : "invariant.md",
19565
+ sha256: fragmentSha256,
19566
+ provider: entry ? entry.provider : "unknown",
19567
+ wireProtocol: entry ? entry.wireProtocol : "unknown",
19568
+ rawEndpoint: recordedEndpoint,
19569
+ rawModel: opts.rawModel || null
19570
+ };
19571
+ manifest.fragments = fragmentsObj;
19572
+ writeFileSync6(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
19573
+ return {
19574
+ entry,
19575
+ rawEndpoint: opts.rawEndpoint,
19576
+ rawModel: opts.rawModel,
19577
+ endpointKey,
19578
+ fragmentPath: fragmentPath2,
19579
+ fragmentSha256,
19580
+ reason
19581
+ };
19582
+ }
19583
+ function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
19584
+ return [
19585
+ `provider-context`,
19586
+ `t=${nowIso}`,
19587
+ `endpoint_key=${r.endpointKey}`,
19588
+ `fragment=${r.fragmentPath ?? "none"}`,
19589
+ `sha256=${r.fragmentSha256 ?? "none"}`,
19590
+ `model=${r.rawModel || "unset"}`
19591
+ ].join(" ");
19592
+ }
19593
+
19594
+ // src/cli/index.tsx
18771
19595
  import { createRequire } from "module";
18772
19596
  var pkg = createRequire(import.meta.url)("../../package.json");
18773
19597
  var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
@@ -18873,11 +19697,11 @@ function parseSessionSource(value, order) {
18873
19697
  if (!path)
18874
19698
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
18875
19699
  const absPath = resolveSessionPath(path);
18876
- if (!existsSync17(absPath))
19700
+ if (!existsSync20(absPath))
18877
19701
  throw new Error(`Instruction source file not found: ${absPath}`);
18878
19702
  const content = readSessionInstructionSourceFile(absPath);
18879
19703
  const source = sourceFromFilePath(absPath, content, order);
18880
- const resolvedId = id || source.id || basename7(absPath);
19704
+ const resolvedId = id || source.id || basename8(absPath);
18881
19705
  return {
18882
19706
  ...source,
18883
19707
  id: resolvedId,
@@ -18917,7 +19741,7 @@ function sessionSourceReplacements(values) {
18917
19741
  return replacements;
18918
19742
  }
18919
19743
  function readSessionInstructionSourceFile(path) {
18920
- const stat = lstatSync7(path);
19744
+ const stat = lstatSync8(path);
18921
19745
  if (stat.isSymbolicLink()) {
18922
19746
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
18923
19747
  }
@@ -18927,7 +19751,7 @@ function readSessionInstructionSourceFile(path) {
18927
19751
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
18928
19752
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
18929
19753
  }
18930
- return readFileSync15(path, "utf-8");
19754
+ return readFileSync17(path, "utf-8");
18931
19755
  }
18932
19756
  function parseLayeredReference(value) {
18933
19757
  const trimmed = value.trim();
@@ -18954,9 +19778,9 @@ async function collectSessionSources(opts, tool, store) {
18954
19778
  }
18955
19779
  for (const value of opts.identityExport ?? []) {
18956
19780
  const path = resolveSessionPath(value);
18957
- if (!existsSync17(path))
19781
+ if (!existsSync20(path))
18958
19782
  throw new Error(`Identity instruction export not found: ${path}`);
18959
- const parsed = JSON.parse(readFileSync15(path, "utf-8"));
19783
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
18960
19784
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
18961
19785
  }
18962
19786
  return sources.map((source) => {
@@ -18970,11 +19794,19 @@ async function collectSessionSources(opts, tool, store) {
18970
19794
  };
18971
19795
  });
18972
19796
  }
19797
+ async function loadOwnedClaudeAuthorities(store) {
19798
+ const configs = await store.listConfigs({ category: "rules", agent: "claude", kind: "file" });
19799
+ return configs.filter((config) => config.target_path && basename8(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
19800
+ }
18973
19801
  async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-run") {
19802
+ const station = opts.stationProfile === false ? null : stationProfileSource();
19803
+ const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
18974
19804
  if (!opts.compileProfile) {
18975
19805
  if (opts.providerVariant)
18976
19806
  throw new Error("--provider-variant requires --compile-profile.");
18977
19807
  const sources = await collectSessionSources(opts, tool, store);
19808
+ if (station)
19809
+ sources.push(station);
18978
19810
  return planSessionRender({
18979
19811
  tool,
18980
19812
  profile: opts.profile,
@@ -18983,7 +19815,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
18983
19815
  sessionId: opts.sessionId,
18984
19816
  codewithNativeImports: opts.codewithNativeImports,
18985
19817
  allowEmptySources: opts.allowEmptySources,
18986
- sources
19818
+ sources,
19819
+ ownedClaudeAuthorities
18987
19820
  });
18988
19821
  }
18989
19822
  if (!opts.providerVersion?.trim())
@@ -19014,6 +19847,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
19014
19847
  ...opts.assetScope ? { asset_scope: opts.assetScope } : {},
19015
19848
  ...opts.assetSurface ? { asset_surface: opts.assetSurface } : {},
19016
19849
  allow_asset_installers: opts.allowAssetInstallers,
19850
+ extra_sources: station ? [station] : undefined,
19851
+ ownedClaudeAuthorities,
19017
19852
  graph_context: {
19018
19853
  ...opts.model ? { model: opts.model } : {},
19019
19854
  ...opts.path ? { path: opts.path } : {},
@@ -19077,19 +19912,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
19077
19912
  if (value === "-")
19078
19913
  return { json: readBoundedProjectContextStdin() };
19079
19914
  const path = resolveSessionPath(value);
19080
- if (!existsSync17(path)) {
19915
+ if (!existsSync20(path)) {
19081
19916
  if (allowMissing)
19082
19917
  return {};
19083
19918
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
19084
19919
  }
19085
- const stat = lstatSync7(path);
19920
+ const stat = lstatSync8(path);
19086
19921
  if (stat.isSymbolicLink() || !stat.isFile()) {
19087
19922
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
19088
19923
  }
19089
19924
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
19090
19925
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
19091
19926
  }
19092
- return { json: readFileSync15(path, "utf8"), sourcePath: path };
19927
+ return { json: readFileSync17(path, "utf8"), sourcePath: path };
19093
19928
  }
19094
19929
  function readBoundedProjectContextStdin() {
19095
19930
  const chunks = [];
@@ -19248,15 +20083,16 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
19248
20083
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
19249
20084
  });
19250
20085
  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) => {
19251
- const abs = resolve13(filePath);
19252
- if (!existsSync17(abs)) {
20086
+ const abs = resolve14(filePath);
20087
+ if (!existsSync20(abs)) {
19253
20088
  console.error(chalk.red(`File not found: ${abs}`));
19254
20089
  process.exit(1);
19255
20090
  }
19256
- const rawContent = readFileSync15(abs, "utf-8");
19257
- const fmt = detectFormat(abs);
20091
+ const rawContent = readFileSync17(abs, "utf-8");
20092
+ const storedFmt = detectFormat(abs);
20093
+ const fmt = redactFormatForTarget(abs, storedFmt);
19258
20094
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
19259
- const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
20095
+ const targetPath = abs.startsWith(homedir12()) ? abs.replace(homedir12(), "~") : abs;
19260
20096
  const name = opts.name || filePath.split("/").pop();
19261
20097
  const store = resolveConfigStore();
19262
20098
  const allConfigs = await store.listConfigs();
@@ -19288,7 +20124,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
19288
20124
  }
19289
20125
  config = await store.updateConfig(target.id, {
19290
20126
  content,
19291
- format: fmt,
20127
+ format: storedFmt,
19292
20128
  is_template: (opts.template ?? false) || isTemplate2,
19293
20129
  ...opts.category ? { category: opts.category } : {},
19294
20130
  ...opts.agent ? { agent: opts.agent } : {}
@@ -19309,7 +20145,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
19309
20145
  category: opts.category ?? detectCategory(abs),
19310
20146
  agent: opts.agent ?? detectAgent(abs),
19311
20147
  target_path: opts.kind === "reference" ? null : targetPath,
19312
- format: fmt,
20148
+ format: storedFmt,
19313
20149
  content,
19314
20150
  is_template: (opts.template ?? false) || isTemplate2
19315
20151
  });
@@ -19424,14 +20260,14 @@ program.command("sync").description("Sync known AI coding configs from disk into
19424
20260
  if (opts.project) {
19425
20261
  const dir = typeof opts.project === "string" ? opts.project : process.cwd();
19426
20262
  if (opts.all) {
19427
- const { readdirSync: readdirSync5 } = await import("fs");
20263
+ const { readdirSync: readdirSync6 } = await import("fs");
19428
20264
  const absDir = expandPath(dir);
19429
- const entries = readdirSync5(absDir, { withFileTypes: true });
20265
+ const entries = readdirSync6(absDir, { withFileTypes: true });
19430
20266
  let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0, projects = 0;
19431
20267
  for (const entry of entries) {
19432
20268
  if (!entry.isDirectory())
19433
20269
  continue;
19434
- const projDir = join19(absDir, entry.name);
20270
+ const projDir = join21(absDir, entry.name);
19435
20271
  const hasAgentConfig = [
19436
20272
  "CLAUDE.md",
19437
20273
  ".mcp.json",
@@ -19444,7 +20280,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
19444
20280
  ".aicopilot",
19445
20281
  ".cursor",
19446
20282
  ".agents"
19447
- ].some((marker) => existsSync17(join19(projDir, marker)));
20283
+ ].some((marker) => existsSync20(join21(projDir, marker)));
19448
20284
  if (!hasAgentConfig)
19449
20285
  continue;
19450
20286
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -19495,7 +20331,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
19495
20331
  });
19496
20332
  program.command("whoami").description("Show setup summary").action(async () => {
19497
20333
  const store = resolveConfigStore();
19498
- const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
20334
+ const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join21(getRawStoreRoot(), "instructions.db");
19499
20335
  const stats = await store.getConfigStats();
19500
20336
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
19501
20337
  console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
@@ -19879,7 +20715,7 @@ projectContextCmd.command("apply").description("Atomically write project context
19879
20715
  }
19880
20716
  });
19881
20717
  var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
19882
- sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--json", "output dry-run JSON").action(async (opts) => {
20718
+ sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--no-station-profile", "do not inject the cached station-profile source").option("--json", "output dry-run JSON").action(async (opts) => {
19883
20719
  try {
19884
20720
  const tool = opts.tool;
19885
20721
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -19930,7 +20766,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
19930
20766
  process.exit(1);
19931
20767
  }
19932
20768
  });
19933
- sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
20769
+ sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "override generated profile-scoped target home").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "warn (non-fatal) when a registered, non-retired global-* source is absent from this render's --config list; expected is read fresh from the registry, independent of this plan (todos 102d6d0a)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
19934
20770
  try {
19935
20771
  const tool = opts.tool;
19936
20772
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -19940,7 +20776,8 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
19940
20776
  const store = resolveConfigStore();
19941
20777
  const plan = await buildSessionRenderPlan(opts, tool, store, "apply");
19942
20778
  const globalCoverage = opts.checkGlobalCoverage ? await checkGlobalSourceCoverage(plan, store) : null;
19943
- const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force });
20779
+ const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
20780
+ const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, ownedClaudeAuthorities });
19944
20781
  if (opts.json) {
19945
20782
  printJson({
19946
20783
  ...result,
@@ -20017,6 +20854,94 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
20017
20854
  process.exit(1);
20018
20855
  }
20019
20856
  });
20857
+ var stationProfileCmd = program.command("station-profile").description("Generate and inject the compact station profile block into every session render");
20858
+ stationProfileCmd.command("refresh").description("Regenerate the cached station profile block (idempotent; writes only when changed)").option("--dry-run", "build and print the block without writing the cache").option("--json", "output JSON").action((opts) => {
20859
+ try {
20860
+ const result = refreshStationProfile({ dryRun: opts.dryRun });
20861
+ if (opts.json) {
20862
+ printJson({
20863
+ path: result.path,
20864
+ bytes: result.bytes,
20865
+ generatedAt: result.generatedAt,
20866
+ dryRun: opts.dryRun === true,
20867
+ budget: STATION_PROFILE_MAX_BYTES,
20868
+ statusProbe: result.statusProbe,
20869
+ machine: {
20870
+ id: result.machine.id,
20871
+ hostname: result.machine.hostname,
20872
+ tailscaleName: result.machine.tailscaleName,
20873
+ platform: result.machine.platform,
20874
+ arch: result.machine.arch,
20875
+ user: result.machine.user,
20876
+ workspacePath: result.machine.workspacePath,
20877
+ status: result.machine.status
20878
+ },
20879
+ packages: result.packages,
20880
+ content: result.content
20881
+ });
20882
+ return;
20883
+ }
20884
+ const prefix = opts.dryRun ? chalk.yellow("[dry-run]") : chalk.green("OK");
20885
+ console.log(`${prefix} station profile ${chalk.dim(`(${result.bytes} B, budget ${STATION_PROFILE_MAX_BYTES} B)`)}`);
20886
+ console.log(`${chalk.cyan("path:")} ${result.path}`);
20887
+ if (result.machine.status) {
20888
+ console.log(`${chalk.cyan("status probe:")} ${result.statusProbe} (${result.machine.status.state})`);
20889
+ } else {
20890
+ console.log(`${chalk.cyan("status probe:")} ${result.statusProbe}`);
20891
+ }
20892
+ console.log(`${chalk.cyan("sources:")} machines manifest + bun global dir + local OS`);
20893
+ console.log("");
20894
+ printLine(result.content);
20895
+ } catch (error) {
20896
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
20897
+ process.exit(1);
20898
+ }
20899
+ });
20900
+ stationProfileCmd.command("show").description("Print the cached station profile block").option("--json", "output JSON").action((opts) => {
20901
+ const content = readStationProfile();
20902
+ if (content === null) {
20903
+ console.error(chalk.red("No cached station profile. Run `instructions station-profile refresh` first."));
20904
+ process.exit(1);
20905
+ }
20906
+ if (opts.json) {
20907
+ printJson({ path: getStationProfileCachePath(), bytes: Buffer.byteLength(content, "utf8"), content });
20908
+ return;
20909
+ }
20910
+ printLine(content);
20911
+ });
20912
+ stationProfileCmd.command("path").description("Print the station profile cache path").action(() => {
20913
+ printLine(getStationProfileCachePath());
20914
+ });
20915
+ stationProfileCmd.command("preview").description("Build and print the block without writing the cache (alias of refresh --dry-run)").option("--json", "output JSON").action((opts) => {
20916
+ try {
20917
+ const machine = resolveStationProfileMachine();
20918
+ const packages = resolveStationProfilePackages();
20919
+ const content = buildStationProfileBlock({ machine, packages });
20920
+ if (opts.json) {
20921
+ printJson({
20922
+ bytes: Buffer.byteLength(content, "utf8"),
20923
+ budget: STATION_PROFILE_MAX_BYTES,
20924
+ machine: {
20925
+ id: machine.id,
20926
+ hostname: machine.hostname,
20927
+ tailscaleName: machine.tailscaleName,
20928
+ platform: machine.platform,
20929
+ arch: machine.arch,
20930
+ user: machine.user,
20931
+ workspacePath: machine.workspacePath,
20932
+ status: machine.status
20933
+ },
20934
+ packages,
20935
+ content
20936
+ });
20937
+ return;
20938
+ }
20939
+ printLine(content);
20940
+ } catch (error) {
20941
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
20942
+ process.exit(1);
20943
+ }
20944
+ });
20020
20945
  var snapshotCmd = program.command("snapshot").description("Manage config version history");
20021
20946
  snapshotCmd.command("list <config>").description("List snapshots for a config").option("--limit <n>", `max rows (default ${DEFAULT_LIST_LIMIT})`).option("--cursor <n>", "zero-based pagination cursor").action(async (configId, opts) => {
20022
20947
  try {
@@ -20060,6 +20985,22 @@ snapshotCmd.command("restore <config> <snapshot-id>").description("Restore a con
20060
20985
  process.exit(1);
20061
20986
  }
20062
20987
  });
20988
+ snapshotCmd.command("prune <config>").description("Delete old snapshots, keeping the N most recent").option("--keep <n>", "number of most-recent snapshots to keep (default 10)").action(async (configId, opts) => {
20989
+ try {
20990
+ const store = resolveConfigStore();
20991
+ const c = await store.getConfig(configId);
20992
+ const keep = opts.keep === undefined ? 10 : Number(opts.keep);
20993
+ if (!Number.isInteger(keep) || keep < 1) {
20994
+ console.error(chalk.red(`--keep must be a positive integer (got: ${opts.keep})`));
20995
+ process.exit(1);
20996
+ }
20997
+ const removed = await store.pruneSnapshots(c.id, keep);
20998
+ console.log(chalk.green("\u2713") + ` Pruned ${removed} snapshot(s), keeping the ${keep} most recent for ${c.slug}`);
20999
+ } catch (e) {
21000
+ console.error(chalk.red(formatCliError(e)));
21001
+ process.exit(1);
21002
+ }
21003
+ });
20063
21004
  var templateCmd = program.command("template").description("Work with template configs");
20064
21005
  templateCmd.command("vars <id>").description("Show template variables").action(async (id) => {
20065
21006
  try {
@@ -20166,7 +21107,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
20166
21107
  for (let i = 0;i < configs.length; i += BATCH) {
20167
21108
  const batch = configs.slice(i, i + BATCH);
20168
21109
  for (const c of batch) {
20169
- const fmt = c.format;
21110
+ const fmt = redactFormatForTarget(c.target_path ?? "", c.format);
20170
21111
  const secrets = scanSecrets(c.content, fmt);
20171
21112
  if (secrets.length === 0)
20172
21113
  continue;
@@ -20250,14 +21191,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
20250
21191
  } else if (target === "codex") {
20251
21192
  const { appendFileSync, existsSync: ex } = await import("fs");
20252
21193
  const { join: j } = await import("path");
20253
- const configPath = j(homedir10(), ".codex", "config.toml");
21194
+ const configPath = j(homedir12(), ".codex", "config.toml");
20254
21195
  const block = `
20255
21196
  [mcp_servers.configs]
20256
21197
  command = "${mcpBinary}"
20257
21198
  args = []
20258
21199
  `;
20259
21200
  if (ex(configPath)) {
20260
- const content = readFileSync15(configPath, "utf-8");
21201
+ const content = readFileSync17(configPath, "utf-8");
20261
21202
  if (content.includes("[mcp_servers.configs]")) {
20262
21203
  console.log(chalk.dim("= Already installed in Codex"));
20263
21204
  continue;
@@ -20268,7 +21209,7 @@ args = []
20268
21209
  } else if (target === "antigravity") {
20269
21210
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
20270
21211
  const { dirname: dn, join: j } = await import("path");
20271
- const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
21212
+ const configPath = j(homedir12(), ".gemini", "config", "mcp_config.json");
20272
21213
  let settings = {};
20273
21214
  if (ex(configPath)) {
20274
21215
  try {
@@ -20352,7 +21293,7 @@ DB stats:`));
20352
21293
  if (count > 0)
20353
21294
  console.log(` ${key.padEnd(18)} ${count}`);
20354
21295
  }
20355
- const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
21296
+ const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join21(getRawStoreRoot(), "instructions.db");
20356
21297
  console.log(chalk.dim(`
20357
21298
  ${isApiTransport() ? "API" : "DB"}: ${location}`));
20358
21299
  });
@@ -20413,10 +21354,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
20413
21354
  });
20414
21355
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
20415
21356
  const { mkdirSync: mk } = await import("fs");
20416
- const backupDir = join19(getRawStoreRoot(), "backups");
21357
+ const backupDir = join21(getRawStoreRoot(), "backups");
20417
21358
  mk(backupDir, { recursive: true });
20418
21359
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
20419
- const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
21360
+ const outPath = join21(backupDir, `configs-${ts}.tar.gz`);
20420
21361
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
20421
21362
  const { statSync: st } = await import("fs");
20422
21363
  const size = st(outPath).size;
@@ -20444,9 +21385,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
20444
21385
  console.log(chalk.cyan("Known files on disk:"));
20445
21386
  for (const k of KNOWN_CONFIGS) {
20446
21387
  if (k.rulesDir) {
20447
- existsSync17(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
21388
+ existsSync20(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
20448
21389
  } else {
20449
- existsSync17(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
21390
+ existsSync20(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
20450
21391
  }
20451
21392
  }
20452
21393
  const allConfigs = await store.listConfigs();
@@ -20468,7 +21409,7 @@ Stored configs (${allConfigs.length}):`));
20468
21409
  pass(`${validCount}/${allConfigs.length} valid syntax`);
20469
21410
  let secretCount = 0;
20470
21411
  for (const c of allConfigs) {
20471
- const found = scanSecrets(c.content, c.format);
21412
+ const found = scanSecrets(c.content, redactFormatForTarget(c.target_path ?? "", c.format));
20472
21413
  secretCount += found.length;
20473
21414
  }
20474
21415
  secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
@@ -20524,6 +21465,7 @@ _configs() {
20524
21465
  'scan:Scan for secrets'
20525
21466
  'profile:Manage profiles'
20526
21467
  'session:Plan and apply session instructions'
21468
+ 'station-profile:Generate and inject the compact station profile block'
20527
21469
  'snapshot:Version history'
20528
21470
  'template:Template operations'
20529
21471
  'mcp:Install MCP server'
@@ -20539,7 +21481,7 @@ compdef _configs configs`);
20539
21481
  console.log(`# bash completion for configs
20540
21482
  _configs_completions() {
20541
21483
  local cur="\${COMP_WORDS[COMP_CWORD]}"
20542
- local commands="list show add apply diff sync export import whoami status init scan profile session snapshot template mcp backup restore doctor completions"
21484
+ local commands="list show add apply diff sync export import whoami status init scan profile session station-profile snapshot template mcp backup restore doctor completions"
20543
21485
  COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
20544
21486
  }
20545
21487
  complete -F _configs_completions configs`);
@@ -20599,16 +21541,16 @@ program.command("watch").description("Watch known config files for changes and a
20599
21541
  for (const k of KNOWN_CONFIGS) {
20600
21542
  if (k.rulesDir) {
20601
21543
  const absDir = expandPath2(k.rulesDir);
20602
- if (!existsSync17(absDir))
21544
+ if (!existsSync20(absDir))
20603
21545
  continue;
20604
- const { readdirSync: readdirSync5 } = await import("fs");
20605
- for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
20606
- const abs = join19(absDir, f);
21546
+ const { readdirSync: readdirSync6 } = await import("fs");
21547
+ for (const f of readdirSync6(absDir).filter((f2) => f2.endsWith(".md"))) {
21548
+ const abs = join21(absDir, f);
20607
21549
  mtimes.set(abs, st(abs).mtimeMs);
20608
21550
  }
20609
21551
  } else {
20610
21552
  const abs = expandPath2(k.path);
20611
- if (existsSync17(abs))
21553
+ if (existsSync20(abs))
20612
21554
  mtimes.set(abs, st(abs).mtimeMs);
20613
21555
  }
20614
21556
  }
@@ -20616,7 +21558,7 @@ program.command("watch").description("Watch known config files for changes and a
20616
21558
  const tick = async () => {
20617
21559
  let changed = 0;
20618
21560
  for (const [abs, oldMtime] of mtimes) {
20619
- if (!existsSync17(abs))
21561
+ if (!existsSync20(abs))
20620
21562
  continue;
20621
21563
  const newMtime = st(abs).mtimeMs;
20622
21564
  if (newMtime !== oldMtime) {
@@ -20628,10 +21570,10 @@ program.command("watch").description("Watch known config files for changes and a
20628
21570
  for (const k of KNOWN_CONFIGS) {
20629
21571
  if (k.rulesDir) {
20630
21572
  const absDir = expandPath2(k.rulesDir);
20631
- if (!existsSync17(absDir))
21573
+ if (!existsSync20(absDir))
20632
21574
  continue;
20633
21575
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
20634
- const abs = join19(absDir, f);
21576
+ const abs = join21(absDir, f);
20635
21577
  if (!mtimes.has(abs)) {
20636
21578
  mtimes.set(abs, st(abs).mtimeMs);
20637
21579
  changed++;
@@ -20639,7 +21581,7 @@ program.command("watch").description("Watch known config files for changes and a
20639
21581
  }
20640
21582
  } else {
20641
21583
  const abs = expandPath2(k.path);
20642
- if (existsSync17(abs) && !mtimes.has(abs)) {
21584
+ if (existsSync20(abs) && !mtimes.has(abs)) {
20643
21585
  mtimes.set(abs, st(abs).mtimeMs);
20644
21586
  changed++;
20645
21587
  }
@@ -20667,12 +21609,12 @@ program.command("report").description("Summary of stored configs, drift, and eco
20667
21609
  if (!c.target_path)
20668
21610
  continue;
20669
21611
  const abs = expandPath(c.target_path);
20670
- if (!existsSync17(abs)) {
21612
+ if (!existsSync20(abs)) {
20671
21613
  missing++;
20672
21614
  continue;
20673
21615
  }
20674
- const disk = readFileSync15(abs, "utf-8");
20675
- const { content: redactedDisk } = redactContent(disk, c.format);
21616
+ const disk = readFileSync17(abs, "utf-8");
21617
+ const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
20676
21618
  if (redactedDisk !== c.content)
20677
21619
  drifted++;
20678
21620
  }
@@ -20738,7 +21680,7 @@ program.command("clean").description("Remove configs from DB whose target files
20738
21680
  if (!c.target_path)
20739
21681
  continue;
20740
21682
  const abs = expandPath(c.target_path);
20741
- if (!existsSync17(abs)) {
21683
+ if (!existsSync20(abs)) {
20742
21684
  if (printed < maxPrinted) {
20743
21685
  if (opts.dryRun) {
20744
21686
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -20879,6 +21821,48 @@ program.command("feedback <message>").description("Send feedback about this serv
20879
21821
  });
20880
21822
  console.log(chalk.green("\u2713") + " Feedback saved. Thank you!");
20881
21823
  });
21824
+ var providerContextCmd = program.command("provider-context").description("Resolve and render per-endpoint model-identity fragments for coding-agent harnesses");
21825
+ providerContextCmd.command("resolve").description("Resolve the endpoint to a provider-context fragment, render it into the home, and emit an audit line").option("--endpoint <url>", "endpoint/base URL to resolve (default: $ANTHROPIC_BASE_URL)").option("--model <id>", "model id to record in the audit line (default: $ANTHROPIC_MODEL)").option("--home <dir>", `harness home directory (default: os.homedir()); fragments render to <home>/${PROVIDER_CONTEXT_DIR}`).option("--json", "print the full resolution as JSON").action((opts) => {
21826
+ try {
21827
+ const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
21828
+ const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
21829
+ const homeDir3 = opts.home ?? homedir12();
21830
+ const origin = normalizeEndpointOrigin(rawEndpoint);
21831
+ const resolution = resolveAndRenderProviderContext({
21832
+ origin,
21833
+ rawEndpoint,
21834
+ rawModel,
21835
+ homeDir: homeDir3
21836
+ });
21837
+ if (opts.json) {
21838
+ printJson({
21839
+ endpointKey: resolution.endpointKey,
21840
+ provider: resolution.entry?.provider ?? null,
21841
+ wireProtocol: resolution.entry?.wireProtocol ?? null,
21842
+ fragmentPath: resolution.fragmentPath,
21843
+ fragmentSha256: resolution.fragmentSha256,
21844
+ reason: resolution.reason,
21845
+ audit: providerContextAuditLine(resolution)
21846
+ });
21847
+ } else {
21848
+ if (resolution.entry) {
21849
+ console.log(chalk.green("\u2713") + ` provider: ${chalk.bold(resolution.entry.provider)} (${resolution.entry.wireProtocol})`);
21850
+ } else {
21851
+ const banner = rawEndpoint && !resolution.reason ? ` no registered provider for endpoint ${rawEndpoint} \u2014 using invariant fragment` : resolution.reason ?? " no registered provider \u2014 using invariant fragment";
21852
+ console.log(chalk.yellow("!") + banner);
21853
+ }
21854
+ console.log(chalk.cyan("fragment:") + ` ${resolution.fragmentPath}`);
21855
+ if (resolution.reason)
21856
+ console.log(chalk.dim(resolution.reason));
21857
+ console.log(chalk.dim(providerContextAuditLine(resolution)));
21858
+ }
21859
+ if (!resolution.entry)
21860
+ process.exitCode = 1;
21861
+ } catch (e) {
21862
+ console.error(chalk.red(formatCliError(e)));
21863
+ process.exit(1);
21864
+ }
21865
+ });
20882
21866
  program.version(pkg.version).name("instructions");
20883
21867
  registerEventsCommands(program, { source: "configs" });
20884
21868
  program.parseAsync(process.argv).catch((e) => {