@hasna/instructions 0.4.43 → 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.
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 {
@@ -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)) {
@@ -14584,7 +14615,7 @@ async function syncFromDir(dir, opts = {}) {
14584
14615
  }
14585
14616
  async function syncToDir(dir, opts = {}) {
14586
14617
  const store = opts.store ?? resolveConfigStore();
14587
- const home = homedir7();
14618
+ const home = homedir8();
14588
14619
  const absDir = expandPath(dir);
14589
14620
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14590
14621
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14643,10 +14674,10 @@ __export(exports_sync, {
14643
14674
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14644
14675
  CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14645
14676
  });
14646
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
14647
- 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";
14648
14679
  function claudeRuleOutputs(fileName) {
14649
- const stem = basename5(fileName, extname3(fileName));
14680
+ const stem = basename6(fileName, extname3(fileName));
14650
14681
  return [
14651
14682
  { agent: "cursor", target_path: `~/.cursor/rules/${stem}.mdc`, transform: "cursor-mdc" }
14652
14683
  ];
@@ -14683,15 +14714,15 @@ function isGeneratedOutputTarget2(config, owners) {
14683
14714
  return !!ownerIds && !ownerIds.has(config.id);
14684
14715
  }
14685
14716
  function hasClaudePromptSource() {
14686
- return existsSync9(expandPath("~/.claude/CLAUDE.md"));
14717
+ return existsSync10(expandPath("~/.claude/CLAUDE.md"));
14687
14718
  }
14688
14719
  function hasClaudeRuleSourceForCursorTarget(targetPath) {
14689
14720
  const absoluteTargetPath = expandPath(targetPath);
14690
14721
  const absolutePrefix = expandPath("~/.cursor/rules");
14691
14722
  if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
14692
14723
  return false;
14693
- const stem = basename5(absoluteTargetPath, ".mdc");
14694
- 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`));
14695
14726
  }
14696
14727
  function isKnownGeneratedTargetPath(targetPath) {
14697
14728
  const normalizedTargetPath = normalizeTargetPath(targetPath);
@@ -14709,7 +14740,7 @@ async function syncProject(opts) {
14709
14740
  const machine = detectMachineContext();
14710
14741
  for (const pf of PROJECT_CONFIG_FILES) {
14711
14742
  const abs = join12(absDir, pf.file);
14712
- if (!existsSync9(abs))
14743
+ if (!existsSync10(abs))
14713
14744
  continue;
14714
14745
  try {
14715
14746
  const rawContent = readFileSync8(abs, "utf-8");
@@ -14749,7 +14780,7 @@ async function syncProject(opts) {
14749
14780
  { dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14750
14781
  { dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14751
14782
  ]) {
14752
- if (!existsSync9(ruleDir.dir))
14783
+ if (!existsSync10(ruleDir.dir))
14753
14784
  continue;
14754
14785
  const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14755
14786
  for (const f of mdFiles) {
@@ -14793,7 +14824,7 @@ async function syncKnown(opts = {}) {
14793
14824
  for (const known of targets) {
14794
14825
  if (known.rulesDir) {
14795
14826
  const absDir = expandPath(known.rulesDir);
14796
- if (!existsSync9(absDir)) {
14827
+ if (!existsSync10(absDir)) {
14797
14828
  result.skipped.push(known.rulesDir);
14798
14829
  continue;
14799
14830
  }
@@ -14834,7 +14865,7 @@ async function syncKnown(opts = {}) {
14834
14865
  continue;
14835
14866
  }
14836
14867
  const abs = expandPath(known.path);
14837
- if (!existsSync9(abs)) {
14868
+ if (!existsSync10(abs)) {
14838
14869
  result.skipped.push(known.path);
14839
14870
  continue;
14840
14871
  }
@@ -14940,7 +14971,7 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
14940
14971
  }
14941
14972
  function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
14942
14973
  const path = expandPath(targetPath);
14943
- if (!existsSync9(path))
14974
+ if (!existsSync10(path))
14944
14975
  return `(file not found on disk: ${path})`;
14945
14976
  const diskContent = readFileSync8(path, "utf-8");
14946
14977
  if (diskContent === expectedContent)
@@ -15175,18 +15206,18 @@ __export(exports_package_manager_guard, {
15175
15206
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15176
15207
  });
15177
15208
  import { execFileSync as execFileSync2 } from "child_process";
15178
- import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
15179
- import { homedir as homedir9 } from "os";
15180
- 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";
15181
15212
  function scanPackageManagerSecrets(options = {}) {
15182
- const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
15183
- 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));
15184
15215
  const findings = [];
15185
15216
  let scannedFiles = 0;
15186
15217
  for (const root of roots) {
15187
- if (!existsSync16(root))
15218
+ if (!existsSync19(root))
15188
15219
  continue;
15189
- const stat = lstatSync6(root);
15220
+ const stat = lstatSync7(root);
15190
15221
  if (stat.isFile()) {
15191
15222
  if (!shouldScanRepoFile(root))
15192
15223
  continue;
@@ -15194,7 +15225,7 @@ function scanPackageManagerSecrets(options = {}) {
15194
15225
  if (text === null)
15195
15226
  continue;
15196
15227
  scannedFiles++;
15197
- findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
15228
+ findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname10(root)));
15198
15229
  continue;
15199
15230
  }
15200
15231
  if (!stat.isDirectory())
@@ -15211,10 +15242,10 @@ function scanPackageManagerSecrets(options = {}) {
15211
15242
  }
15212
15243
  }
15213
15244
  if (options.includeHome) {
15214
- const home = homedir9();
15245
+ const home = homedir11();
15215
15246
  for (const name of HOME_FILES) {
15216
- const file = join18(home, name);
15217
- if (!existsSync16(file))
15247
+ const file = join20(home, name);
15248
+ if (!existsSync19(file))
15218
15249
  continue;
15219
15250
  const text = readTextFile(file);
15220
15251
  if (text === null)
@@ -15234,16 +15265,16 @@ function scanPackageManagerSecrets(options = {}) {
15234
15265
  function collectRepoFiles(root) {
15235
15266
  const out = [];
15236
15267
  const visit = (dir) => {
15237
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
15268
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
15238
15269
  if (entry.isDirectory()) {
15239
15270
  if (SKIP_DIRS.has(entry.name))
15240
15271
  continue;
15241
- visit(join18(dir, entry.name));
15272
+ visit(join20(dir, entry.name));
15242
15273
  continue;
15243
15274
  }
15244
15275
  if (!entry.isFile())
15245
15276
  continue;
15246
- const file = join18(dir, entry.name);
15277
+ const file = join20(dir, entry.name);
15247
15278
  if (shouldScanRepoFile(file))
15248
15279
  out.push(file);
15249
15280
  }
@@ -15252,11 +15283,11 @@ function collectRepoFiles(root) {
15252
15283
  return out;
15253
15284
  }
15254
15285
  function shouldScanRepoFile(file) {
15255
- const name = basename6(file);
15286
+ const name = basename7(file);
15256
15287
  return isNpmrcName(name) || isBunConfigName(name) || LOCKFILE_NAMES.has(name);
15257
15288
  }
15258
15289
  function classifyRepoFile(file) {
15259
- const name = basename6(file);
15290
+ const name = basename7(file);
15260
15291
  if (isNpmrcName(name))
15261
15292
  return "repo-npmrc";
15262
15293
  if (isBunConfigName(name))
@@ -15278,10 +15309,10 @@ function isNpmrcName(name) {
15278
15309
  }
15279
15310
  function readTextFile(file) {
15280
15311
  try {
15281
- const stat = lstatSync6(file);
15312
+ const stat = lstatSync7(file);
15282
15313
  if (!stat.isFile() || stat.size > 5000000)
15283
15314
  return null;
15284
- const buf = readFileSync14(file);
15315
+ const buf = readFileSync16(file);
15285
15316
  if (buf.includes(0))
15286
15317
  return null;
15287
15318
  return buf.toString("utf-8");
@@ -15481,7 +15512,7 @@ function trackedFiles(root) {
15481
15512
  }
15482
15513
  function isTrackedFile(file) {
15483
15514
  try {
15484
- const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
15515
+ const repoRoot = execFileSync2("git", ["-C", dirname10(file), "rev-parse", "--show-toplevel"], {
15485
15516
  encoding: "utf-8",
15486
15517
  stdio: ["ignore", "pipe", "ignore"]
15487
15518
  }).trim();
@@ -15511,7 +15542,7 @@ function stripInlineComment(value) {
15511
15542
  return value.replace(/\s[#;].*$/, "").trim();
15512
15543
  }
15513
15544
  function displayPath(file, root) {
15514
- const home = homedir9();
15545
+ const home = homedir11();
15515
15546
  if (root === home && (file === home || file.startsWith(home + "/")))
15516
15547
  return "~/" + toPosix(relative7(home, file));
15517
15548
  if (isAbsolute5(root) && file.startsWith(root + "/"))
@@ -15569,8 +15600,12 @@ import { existsSync } from "fs";
15569
15600
  import { homedir } from "os";
15570
15601
  import { join } from "path";
15571
15602
  import { createHmac, timingSafeEqual } from "crypto";
15603
+ import { lookup as dnsLookup } from "dns/promises";
15604
+ import { isIP } from "net";
15572
15605
  import { randomUUID } from "crypto";
15573
15606
  import { spawn } from "child_process";
15607
+ import { request as nodeHttpRequest } from "http";
15608
+ import { request as nodeHttpsRequest } from "https";
15574
15609
  import { randomUUID as randomUUID2 } from "crypto";
15575
15610
  function getPathValue(input, path) {
15576
15611
  return path.split(".").reduce((value, part) => {
@@ -15973,6 +16008,214 @@ function signPayload(secret, timestamp, body) {
15973
16008
  const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
15974
16009
  return `sha256=${digest}`;
15975
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
+ }
15976
16219
  function now() {
15977
16220
  return new Date().toISOString();
15978
16221
  }
@@ -16003,9 +16246,18 @@ function buildWebhookRequest(event, channel, options = {}) {
16003
16246
  }
16004
16247
  return { body, headers };
16005
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
+ }
16006
16257
  async function dispatchWebhook(event, channel, options = {}) {
16007
16258
  if (!channel.webhook)
16008
16259
  throw new Error(`Channel ${channel.id} has no webhook config`);
16260
+ const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
16009
16261
  const startedAt = now();
16010
16262
  let secret = channel.webhook.secret;
16011
16263
  if (channel.webhook.secretRef) {
@@ -16022,10 +16274,14 @@ async function dispatchWebhook(event, channel, options = {}) {
16022
16274
  }
16023
16275
  const timestamp = (options.now?.() ?? new Date).toISOString();
16024
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
+ }
16025
16281
  const controller = new AbortController;
16026
16282
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
16027
16283
  try {
16028
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
16284
+ const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
16029
16285
  method: "POST",
16030
16286
  headers,
16031
16287
  body,
@@ -16053,6 +16309,130 @@ async function dispatchWebhook(event, channel, options = {}) {
16053
16309
  clearTimeout(timeout);
16054
16310
  }
16055
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
+ }
16056
16436
  function failedAttempt(startedAt, error) {
16057
16437
  return {
16058
16438
  attempt: 1,
@@ -16262,7 +16642,9 @@ class EventsClient {
16262
16642
  this.transportOptions = {
16263
16643
  fetchImpl: options.fetchImpl,
16264
16644
  secretResolver: options.secretResolver,
16265
- now: options.now
16645
+ now: options.now,
16646
+ tls: options.tls,
16647
+ webhookTargetPolicy: options.webhookTargetPolicy
16266
16648
  };
16267
16649
  this.catalog = options.catalog ?? defaultEventTypeCatalog;
16268
16650
  this.validateCatalogTypes = options.validateCatalogTypes ?? false;
@@ -16763,9 +17145,9 @@ var {
16763
17145
  // src/cli/index.tsx
16764
17146
  init_apply();
16765
17147
  import chalk from "chalk";
16766
- import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
16767
- import { homedir as homedir10 } from "os";
16768
- 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";
16769
17151
 
16770
17152
  // src/lib/config-target-identity.ts
16771
17153
  init_apply();
@@ -16811,7 +17193,7 @@ init_redact();
16811
17193
 
16812
17194
  // src/lib/export.ts
16813
17195
  init_config_store();
16814
- 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";
16815
17197
  import { join as join13, resolve as resolve8 } from "path";
16816
17198
  import { tmpdir } from "os";
16817
17199
  async function exportConfigs(outputPath, opts = {}) {
@@ -16843,7 +17225,7 @@ async function exportConfigs(outputPath, opts = {}) {
16843
17225
  }
16844
17226
  return { path: absOutput, count: configs.length };
16845
17227
  } finally {
16846
- if (existsSync10(tmpDir)) {
17228
+ if (existsSync11(tmpDir)) {
16847
17229
  rmSync3(tmpDir, { recursive: true, force: true });
16848
17230
  }
16849
17231
  }
@@ -16851,7 +17233,7 @@ async function exportConfigs(outputPath, opts = {}) {
16851
17233
 
16852
17234
  // src/lib/import.ts
16853
17235
  init_config_store();
16854
- 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";
16855
17237
  import { join as join14, resolve as resolve9 } from "path";
16856
17238
  import { tmpdir as tmpdir2 } from "os";
16857
17239
  async function importConfigs(bundlePath, opts = {}) {
@@ -16872,14 +17254,14 @@ async function importConfigs(bundlePath, opts = {}) {
16872
17254
  throw new Error(`tar extraction failed: ${stderr}`);
16873
17255
  }
16874
17256
  const manifestPath = join14(tmpDir, "manifest.json");
16875
- if (!existsSync11(manifestPath))
17257
+ if (!existsSync12(manifestPath))
16876
17258
  throw new Error("Invalid bundle: missing manifest.json");
16877
17259
  const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
16878
17260
  for (const meta of manifest.configs) {
16879
17261
  try {
16880
17262
  const ext = meta.format === "text" ? "txt" : meta.format;
16881
17263
  const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
16882
- const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
17264
+ const content = existsSync12(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
16883
17265
  let existing = null;
16884
17266
  try {
16885
17267
  existing = await store.getConfig(meta.slug);
@@ -16913,7 +17295,7 @@ async function importConfigs(bundlePath, opts = {}) {
16913
17295
  }
16914
17296
  return result;
16915
17297
  } finally {
16916
- if (existsSync11(tmpDir)) {
17298
+ if (existsSync12(tmpDir)) {
16917
17299
  rmSync4(tmpDir, { recursive: true, force: true });
16918
17300
  }
16919
17301
  }
@@ -16930,14 +17312,14 @@ init_cursor_authority();
16930
17312
  init_session_authority();
16931
17313
  import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
16932
17314
  import {
16933
- existsSync as existsSync12,
17315
+ existsSync as existsSync13,
16934
17316
  lstatSync as lstatSync4,
16935
17317
  mkdirSync as mkdirSync6,
16936
17318
  readFileSync as readFileSync10,
16937
17319
  readdirSync as readdirSync3,
16938
17320
  statSync as statSync6
16939
17321
  } from "fs";
16940
- 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";
16941
17323
 
16942
17324
  class SessionApplyError extends Error {
16943
17325
  constructor(message) {
@@ -16958,7 +17340,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16958
17340
  }
16959
17341
  assertCursorAuthorityUnchanged(plan);
16960
17342
  const targetHome = assertSafeTargetHome(plan.targetHome);
16961
- assertClaudeAuthorityStillClear(plan, targetHome);
17343
+ assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
16962
17344
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
16963
17345
  const files = [...payloadFiles, plan.manifestFile];
16964
17346
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
@@ -17059,17 +17441,17 @@ function assertCursorAuthorityUnchanged(plan) {
17059
17441
  throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
17060
17442
  }
17061
17443
  }
17062
- function assertClaudeAuthorityStillClear(plan, targetHome) {
17444
+ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthorities) {
17063
17445
  if (plan.tool !== "claude" || plan.targetKind === "blocked")
17064
17446
  return;
17065
- const conflicts = detectClaudeAuthorityConflicts(targetHome);
17447
+ const conflicts = detectClaudeAuthorityConflicts(targetHome, ownedClaudeAuthorities);
17066
17448
  if (conflicts.length === 0)
17067
17449
  return;
17068
17450
  const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
17069
17451
  throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
17070
17452
  }
17071
17453
  function ensureSessionTargetHome(targetHome) {
17072
- if (!existsSync12(targetHome))
17454
+ if (!existsSync13(targetHome))
17073
17455
  mkdirSync6(targetHome, { recursive: true, mode: 448 });
17074
17456
  assertSafeTargetHome(targetHome);
17075
17457
  }
@@ -17092,7 +17474,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
17092
17474
  const drifted = [];
17093
17475
  for (const file of previousManifest.files) {
17094
17476
  const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
17095
- if (!existsSync12(target)) {
17477
+ if (!existsSync13(target)) {
17096
17478
  missing.push({
17097
17479
  path: target,
17098
17480
  relativePath: file.relativePath,
@@ -17244,7 +17626,7 @@ function requiredRestoreHash(file) {
17244
17626
  }
17245
17627
  function readSessionRenderSnapshot(snapshotPath) {
17246
17628
  const resolved = resolve10(snapshotPath);
17247
- if (!existsSync12(resolved))
17629
+ if (!existsSync13(resolved))
17248
17630
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
17249
17631
  const stat = lstatSync4(resolved);
17250
17632
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -17425,8 +17807,8 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
17425
17807
  if (!Number.isFinite(createdAtMs)) {
17426
17808
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
17427
17809
  }
17428
- for (const entry of readdirSync3(dirname5(snapshotPath))) {
17429
- const candidatePath = resolve10(dirname5(snapshotPath), entry);
17810
+ for (const entry of readdirSync3(dirname6(snapshotPath))) {
17811
+ const candidatePath = resolve10(dirname6(snapshotPath), entry);
17430
17812
  if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
17431
17813
  continue;
17432
17814
  const candidateStat = lstatSync4(candidatePath);
@@ -17511,7 +17893,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
17511
17893
  }
17512
17894
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
17513
17895
  const target = resolvePlannedFilePath(plan, file, targetHome);
17514
- const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
17896
+ const previousContent = existsSync13(target) ? readFileSync10(target, "utf-8") : null;
17515
17897
  const previousSha256 = previousContent === null ? null : sha2568(previousContent);
17516
17898
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
17517
17899
  const changed = previousContent !== file.content;
@@ -17610,7 +17992,7 @@ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativ
17610
17992
  }
17611
17993
  function planStaleFileResult(file, targetHome, options) {
17612
17994
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
17613
- if (!existsSync12(target))
17995
+ if (!existsSync13(target))
17614
17996
  return null;
17615
17997
  const previousContent = readFileSync10(target, "utf-8");
17616
17998
  const previousSha256 = sha2568(previousContent);
@@ -17678,7 +18060,7 @@ function resolveManifestRelativePath(relativePath, targetHome) {
17678
18060
  return target;
17679
18061
  }
17680
18062
  function readPreviousManifest(path) {
17681
- if (!existsSync12(path))
18063
+ if (!existsSync13(path))
17682
18064
  return null;
17683
18065
  try {
17684
18066
  const parsed = JSON.parse(readFileSync10(path, "utf-8"));
@@ -17720,7 +18102,7 @@ function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
17720
18102
  }
17721
18103
  function currentSessionFileHash(path, targetHome) {
17722
18104
  assertNoSymlinkSegments2(targetHome, path);
17723
- if (!existsSync12(path))
18105
+ if (!existsSync13(path))
17724
18106
  return null;
17725
18107
  const stat = lstatSync4(path);
17726
18108
  if (stat.isSymbolicLink() || !stat.isFile()) {
@@ -17735,7 +18117,7 @@ function requiredPreviousHash(result) {
17735
18117
  return result.previousSha256;
17736
18118
  }
17737
18119
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
17738
- 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) => {
17739
18121
  const content = readFileSync10(result.path, "utf-8");
17740
18122
  return {
17741
18123
  path: result.path,
@@ -17807,7 +18189,7 @@ function assertSafeTargetHome(targetHome) {
17807
18189
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17808
18190
  }
17809
18191
  assertNoSymlinkAncestors2(normalized);
17810
- if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
18192
+ if (existsSync13(normalized) && lstatSync4(normalized).isSymbolicLink()) {
17811
18193
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17812
18194
  }
17813
18195
  return normalized;
@@ -17818,7 +18200,7 @@ function assertNoSymlinkSegments2(root, target) {
17818
18200
  let current = root;
17819
18201
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17820
18202
  current = join15(current, segment);
17821
- if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
18203
+ if (existsSync13(current) && lstatSync4(current).isSymbolicLink()) {
17822
18204
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17823
18205
  }
17824
18206
  }
@@ -17830,7 +18212,7 @@ function assertNoSymlinkAncestors2(path) {
17830
18212
  const rel = relative5(parsed.root, normalized);
17831
18213
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17832
18214
  current = join15(current, segment);
17833
- if (!existsSync12(current))
18215
+ if (!existsSync13(current))
17834
18216
  return;
17835
18217
  if (lstatSync4(current).isSymbolicLink()) {
17836
18218
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
@@ -17878,6 +18260,248 @@ function formatGlobalSourceCoverageWarnings(result) {
17878
18260
  ];
17879
18261
  }
17880
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
+
17881
18505
  // src/lib/platform-profiles.ts
17882
18506
  init_config_store();
17883
18507
 
@@ -18193,18 +18817,18 @@ init_codewith_shared_todos_storage_standard();
18193
18817
 
18194
18818
  // src/lib/managed-skill-runtimes.ts
18195
18819
  import { createHash as createHash9 } from "crypto";
18196
- import { spawnSync } from "child_process";
18820
+ import { spawnSync as spawnSync2 } from "child_process";
18197
18821
  import {
18198
- existsSync as existsSync13,
18199
- lstatSync as lstatSync5,
18200
- mkdirSync as mkdirSync7,
18201
- readFileSync as readFileSync11,
18822
+ existsSync as existsSync15,
18823
+ lstatSync as lstatSync6,
18824
+ mkdirSync as mkdirSync8,
18825
+ readFileSync as readFileSync12,
18202
18826
  renameSync as renameSync2,
18203
18827
  rmSync as rmSync5,
18204
- writeFileSync as writeFileSync4
18828
+ writeFileSync as writeFileSync5
18205
18829
  } from "fs";
18206
- import { homedir as homedir8 } from "os";
18207
- 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";
18208
18832
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
18209
18833
  var INBOX_SKILL_MARKERS = [
18210
18834
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -18219,21 +18843,21 @@ function sha2569(content) {
18219
18843
  }
18220
18844
  function lstatOrNull(path) {
18221
18845
  try {
18222
- return lstatSync5(path);
18846
+ return lstatSync6(path);
18223
18847
  } catch {
18224
18848
  return null;
18225
18849
  }
18226
18850
  }
18227
18851
  function findSymlinkedAncestor(path) {
18228
- const normalized = resolve11(path);
18852
+ const normalized = resolve12(path);
18229
18853
  const parsed = parse5(normalized);
18230
18854
  let current = parsed.root;
18231
18855
  const rel = relative6(parsed.root, normalized);
18232
18856
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18233
- current = join16(current, segment);
18234
- if (!existsSync13(current))
18857
+ current = join17(current, segment);
18858
+ if (!existsSync15(current))
18235
18859
  return null;
18236
- if (lstatSync5(current).isSymbolicLink())
18860
+ if (lstatSync6(current).isSymbolicLink())
18237
18861
  return current;
18238
18862
  }
18239
18863
  return null;
@@ -18248,11 +18872,11 @@ function packagedInboxSkillPath(explicitPath) {
18248
18872
  if (explicitPath)
18249
18873
  return explicitPath;
18250
18874
  const candidates = [
18251
- join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
18252
- join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
18253
- 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")
18254
18878
  ];
18255
- const found = candidates.find((candidate) => existsSync13(candidate));
18879
+ const found = candidates.find((candidate) => existsSync15(candidate));
18256
18880
  if (!found) {
18257
18881
  throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
18258
18882
  }
@@ -18264,7 +18888,7 @@ function readCanonicalSkill(explicitPath) {
18264
18888
  if (!stat?.isFile()) {
18265
18889
  throw new Error("packaged inbox skill contract is not a regular file");
18266
18890
  }
18267
- const content = readFileSync11(assetPath, "utf8");
18891
+ const content = readFileSync12(assetPath, "utf8");
18268
18892
  if (!content.includes("conversations watch --from <agent> --all")) {
18269
18893
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
18270
18894
  }
@@ -18274,7 +18898,7 @@ function readCanonicalSkill(explicitPath) {
18274
18898
  return { content, sha256: sha2569(content) };
18275
18899
  }
18276
18900
  function runProbe(command, args) {
18277
- const result = spawnSync(command, args, {
18901
+ const result = spawnSync2(command, args, {
18278
18902
  encoding: "utf8",
18279
18903
  timeout: 5000,
18280
18904
  stdio: ["ignore", "pipe", "pipe"]
@@ -18301,8 +18925,8 @@ function compareVersions(left, right) {
18301
18925
  }
18302
18926
  return 0;
18303
18927
  }
18304
- function inspectSkillMarkers(homeDir2) {
18305
- 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) => {
18306
18930
  const stat = lstatOrNull(path);
18307
18931
  if (!stat)
18308
18932
  return null;
@@ -18311,16 +18935,16 @@ function inspectSkillMarkers(homeDir2) {
18311
18935
  }
18312
18936
  return {
18313
18937
  path,
18314
- content: readFileSync11(path, "utf8"),
18938
+ content: readFileSync12(path, "utf8"),
18315
18939
  mode: stat.mode & 511,
18316
18940
  regular: true
18317
18941
  };
18318
18942
  }).filter((snapshot) => snapshot !== null);
18319
18943
  }
18320
18944
  function inspectInbox(options) {
18321
- const homeDir2 = options.homeDir ?? homedir8();
18945
+ const homeDir3 = options.homeDir ?? homedir10();
18322
18946
  const runtimeCommand = options.conversationsCommand ?? "conversations";
18323
- const snapshots = inspectSkillMarkers(homeDir2);
18947
+ const snapshots = inspectSkillMarkers(homeDir3);
18324
18948
  const skillPresent = snapshots.length > 0;
18325
18949
  let canonicalContent = null;
18326
18950
  let canonicalSha256 = null;
@@ -18346,7 +18970,7 @@ function inspectInbox(options) {
18346
18970
  let reason = "skill not installed";
18347
18971
  if (skillPresent) {
18348
18972
  const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
18349
- 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);
18350
18974
  if (nonRegular)
18351
18975
  reason = "managed skill target is not a regular file";
18352
18976
  else if (symlinkAncestor)
@@ -18433,11 +19057,11 @@ function cleanup(path) {
18433
19057
  rmSync5(path, { force: true });
18434
19058
  }
18435
19059
  function writeAtomic(path, content, mode) {
18436
- assertNoSymlinkAncestors3(dirname6(path));
19060
+ assertNoSymlinkAncestors3(dirname8(path));
18437
19061
  const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
18438
19062
  try {
18439
- mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
18440
- writeFileSync4(tempPath, content, { mode, flag: "wx" });
19063
+ mkdirSync8(dirname8(path), { recursive: true, mode: 493 });
19064
+ writeFileSync5(tempPath, content, { mode, flag: "wx" });
18441
19065
  renameSync2(tempPath, path);
18442
19066
  } finally {
18443
19067
  cleanup(tempPath);
@@ -18445,7 +19069,7 @@ function writeAtomic(path, content, mode) {
18445
19069
  }
18446
19070
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
18447
19071
  lstat: lstatOrNull,
18448
- read: (path) => readFileSync11(path, "utf8"),
19072
+ read: (path) => readFileSync12(path, "utf8"),
18449
19073
  write: writeAtomic
18450
19074
  };
18451
19075
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -18512,7 +19136,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
18512
19136
  dry_run: dryRun
18513
19137
  };
18514
19138
  }
18515
- 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);
18516
19140
  if (symlinkedAncestor) {
18517
19141
  return {
18518
19142
  runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
@@ -18595,28 +19219,28 @@ init_project_context();
18595
19219
  init_config_store();
18596
19220
  init_apply();
18597
19221
  init_config_agents();
18598
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
19222
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
18599
19223
 
18600
19224
  // src/lib/package-version.ts
18601
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
18602
- 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";
18603
19227
  import { fileURLToPath } from "url";
18604
19228
  var cached = null;
18605
19229
  function getPackageVersion() {
18606
19230
  if (cached)
18607
19231
  return cached;
18608
19232
  try {
18609
- let dir = dirname7(fileURLToPath(import.meta.url));
19233
+ let dir = dirname9(fileURLToPath(import.meta.url));
18610
19234
  for (let i = 0;i < 8; i++) {
18611
- const pkgPath = join17(dir, "package.json");
18612
- if (existsSync14(pkgPath)) {
18613
- 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"));
18614
19238
  if (pkg.name === "@hasna/instructions" && pkg.version) {
18615
19239
  cached = pkg.version;
18616
19240
  return cached;
18617
19241
  }
18618
19242
  }
18619
- const parent = dirname7(dir);
19243
+ const parent = dirname9(dir);
18620
19244
  if (parent === dir)
18621
19245
  break;
18622
19246
  dir = parent;
@@ -18673,11 +19297,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
18673
19297
  continue;
18674
19298
  knownTargets += 1;
18675
19299
  const targetPath = expandPath(config.target_path);
18676
- if (!existsSync15(targetPath)) {
19300
+ if (!existsSync17(targetPath)) {
18677
19301
  missingTargets += 1;
18678
19302
  continue;
18679
19303
  }
18680
- const disk = readFileSync13(targetPath, "utf-8");
19304
+ const disk = readFileSync14(targetPath, "utf-8");
18681
19305
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
18682
19306
  if (redactedDisk !== config.content) {
18683
19307
  driftedTargets += 1;
@@ -18770,6 +19394,204 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
18770
19394
 
18771
19395
  // src/cli/index.tsx
18772
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
18773
19595
  import { createRequire } from "module";
18774
19596
  var pkg = createRequire(import.meta.url)("../../package.json");
18775
19597
  var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
@@ -18875,11 +19697,11 @@ function parseSessionSource(value, order) {
18875
19697
  if (!path)
18876
19698
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
18877
19699
  const absPath = resolveSessionPath(path);
18878
- if (!existsSync17(absPath))
19700
+ if (!existsSync20(absPath))
18879
19701
  throw new Error(`Instruction source file not found: ${absPath}`);
18880
19702
  const content = readSessionInstructionSourceFile(absPath);
18881
19703
  const source = sourceFromFilePath(absPath, content, order);
18882
- const resolvedId = id || source.id || basename7(absPath);
19704
+ const resolvedId = id || source.id || basename8(absPath);
18883
19705
  return {
18884
19706
  ...source,
18885
19707
  id: resolvedId,
@@ -18919,7 +19741,7 @@ function sessionSourceReplacements(values) {
18919
19741
  return replacements;
18920
19742
  }
18921
19743
  function readSessionInstructionSourceFile(path) {
18922
- const stat = lstatSync7(path);
19744
+ const stat = lstatSync8(path);
18923
19745
  if (stat.isSymbolicLink()) {
18924
19746
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
18925
19747
  }
@@ -18929,7 +19751,7 @@ function readSessionInstructionSourceFile(path) {
18929
19751
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
18930
19752
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
18931
19753
  }
18932
- return readFileSync15(path, "utf-8");
19754
+ return readFileSync17(path, "utf-8");
18933
19755
  }
18934
19756
  function parseLayeredReference(value) {
18935
19757
  const trimmed = value.trim();
@@ -18956,9 +19778,9 @@ async function collectSessionSources(opts, tool, store) {
18956
19778
  }
18957
19779
  for (const value of opts.identityExport ?? []) {
18958
19780
  const path = resolveSessionPath(value);
18959
- if (!existsSync17(path))
19781
+ if (!existsSync20(path))
18960
19782
  throw new Error(`Identity instruction export not found: ${path}`);
18961
- const parsed = JSON.parse(readFileSync15(path, "utf-8"));
19783
+ const parsed = JSON.parse(readFileSync17(path, "utf-8"));
18962
19784
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
18963
19785
  }
18964
19786
  return sources.map((source) => {
@@ -18972,11 +19794,19 @@ async function collectSessionSources(opts, tool, store) {
18972
19794
  };
18973
19795
  });
18974
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
+ }
18975
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;
18976
19804
  if (!opts.compileProfile) {
18977
19805
  if (opts.providerVariant)
18978
19806
  throw new Error("--provider-variant requires --compile-profile.");
18979
19807
  const sources = await collectSessionSources(opts, tool, store);
19808
+ if (station)
19809
+ sources.push(station);
18980
19810
  return planSessionRender({
18981
19811
  tool,
18982
19812
  profile: opts.profile,
@@ -18985,7 +19815,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
18985
19815
  sessionId: opts.sessionId,
18986
19816
  codewithNativeImports: opts.codewithNativeImports,
18987
19817
  allowEmptySources: opts.allowEmptySources,
18988
- sources
19818
+ sources,
19819
+ ownedClaudeAuthorities
18989
19820
  });
18990
19821
  }
18991
19822
  if (!opts.providerVersion?.trim())
@@ -19016,6 +19847,8 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
19016
19847
  ...opts.assetScope ? { asset_scope: opts.assetScope } : {},
19017
19848
  ...opts.assetSurface ? { asset_surface: opts.assetSurface } : {},
19018
19849
  allow_asset_installers: opts.allowAssetInstallers,
19850
+ extra_sources: station ? [station] : undefined,
19851
+ ownedClaudeAuthorities,
19019
19852
  graph_context: {
19020
19853
  ...opts.model ? { model: opts.model } : {},
19021
19854
  ...opts.path ? { path: opts.path } : {},
@@ -19079,19 +19912,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
19079
19912
  if (value === "-")
19080
19913
  return { json: readBoundedProjectContextStdin() };
19081
19914
  const path = resolveSessionPath(value);
19082
- if (!existsSync17(path)) {
19915
+ if (!existsSync20(path)) {
19083
19916
  if (allowMissing)
19084
19917
  return {};
19085
19918
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
19086
19919
  }
19087
- const stat = lstatSync7(path);
19920
+ const stat = lstatSync8(path);
19088
19921
  if (stat.isSymbolicLink() || !stat.isFile()) {
19089
19922
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
19090
19923
  }
19091
19924
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
19092
19925
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
19093
19926
  }
19094
- return { json: readFileSync15(path, "utf8"), sourcePath: path };
19927
+ return { json: readFileSync17(path, "utf8"), sourcePath: path };
19095
19928
  }
19096
19929
  function readBoundedProjectContextStdin() {
19097
19930
  const chunks = [];
@@ -19250,16 +20083,16 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
19250
20083
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
19251
20084
  });
19252
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) => {
19253
- const abs = resolve13(filePath);
19254
- if (!existsSync17(abs)) {
20086
+ const abs = resolve14(filePath);
20087
+ if (!existsSync20(abs)) {
19255
20088
  console.error(chalk.red(`File not found: ${abs}`));
19256
20089
  process.exit(1);
19257
20090
  }
19258
- const rawContent = readFileSync15(abs, "utf-8");
20091
+ const rawContent = readFileSync17(abs, "utf-8");
19259
20092
  const storedFmt = detectFormat(abs);
19260
20093
  const fmt = redactFormatForTarget(abs, storedFmt);
19261
20094
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
19262
- const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
20095
+ const targetPath = abs.startsWith(homedir12()) ? abs.replace(homedir12(), "~") : abs;
19263
20096
  const name = opts.name || filePath.split("/").pop();
19264
20097
  const store = resolveConfigStore();
19265
20098
  const allConfigs = await store.listConfigs();
@@ -19427,14 +20260,14 @@ program.command("sync").description("Sync known AI coding configs from disk into
19427
20260
  if (opts.project) {
19428
20261
  const dir = typeof opts.project === "string" ? opts.project : process.cwd();
19429
20262
  if (opts.all) {
19430
- const { readdirSync: readdirSync5 } = await import("fs");
20263
+ const { readdirSync: readdirSync6 } = await import("fs");
19431
20264
  const absDir = expandPath(dir);
19432
- const entries = readdirSync5(absDir, { withFileTypes: true });
20265
+ const entries = readdirSync6(absDir, { withFileTypes: true });
19433
20266
  let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0, projects = 0;
19434
20267
  for (const entry of entries) {
19435
20268
  if (!entry.isDirectory())
19436
20269
  continue;
19437
- const projDir = join19(absDir, entry.name);
20270
+ const projDir = join21(absDir, entry.name);
19438
20271
  const hasAgentConfig = [
19439
20272
  "CLAUDE.md",
19440
20273
  ".mcp.json",
@@ -19447,7 +20280,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
19447
20280
  ".aicopilot",
19448
20281
  ".cursor",
19449
20282
  ".agents"
19450
- ].some((marker) => existsSync17(join19(projDir, marker)));
20283
+ ].some((marker) => existsSync20(join21(projDir, marker)));
19451
20284
  if (!hasAgentConfig)
19452
20285
  continue;
19453
20286
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -19498,7 +20331,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
19498
20331
  });
19499
20332
  program.command("whoami").description("Show setup summary").action(async () => {
19500
20333
  const store = resolveConfigStore();
19501
- 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");
19502
20335
  const stats = await store.getConfigStats();
19503
20336
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
19504
20337
  console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
@@ -19882,7 +20715,7 @@ projectContextCmd.command("apply").description("Atomically write project context
19882
20715
  }
19883
20716
  });
19884
20717
  var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
19885
- 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) => {
19886
20719
  try {
19887
20720
  const tool = opts.tool;
19888
20721
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -19933,7 +20766,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
19933
20766
  process.exit(1);
19934
20767
  }
19935
20768
  });
19936
- 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) => {
19937
20770
  try {
19938
20771
  const tool = opts.tool;
19939
20772
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -19943,7 +20776,8 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
19943
20776
  const store = resolveConfigStore();
19944
20777
  const plan = await buildSessionRenderPlan(opts, tool, store, "apply");
19945
20778
  const globalCoverage = opts.checkGlobalCoverage ? await checkGlobalSourceCoverage(plan, store) : null;
19946
- 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 });
19947
20781
  if (opts.json) {
19948
20782
  printJson({
19949
20783
  ...result,
@@ -20020,6 +20854,94 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
20020
20854
  process.exit(1);
20021
20855
  }
20022
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
+ });
20023
20945
  var snapshotCmd = program.command("snapshot").description("Manage config version history");
20024
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) => {
20025
20947
  try {
@@ -20269,14 +21191,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
20269
21191
  } else if (target === "codex") {
20270
21192
  const { appendFileSync, existsSync: ex } = await import("fs");
20271
21193
  const { join: j } = await import("path");
20272
- const configPath = j(homedir10(), ".codex", "config.toml");
21194
+ const configPath = j(homedir12(), ".codex", "config.toml");
20273
21195
  const block = `
20274
21196
  [mcp_servers.configs]
20275
21197
  command = "${mcpBinary}"
20276
21198
  args = []
20277
21199
  `;
20278
21200
  if (ex(configPath)) {
20279
- const content = readFileSync15(configPath, "utf-8");
21201
+ const content = readFileSync17(configPath, "utf-8");
20280
21202
  if (content.includes("[mcp_servers.configs]")) {
20281
21203
  console.log(chalk.dim("= Already installed in Codex"));
20282
21204
  continue;
@@ -20287,7 +21209,7 @@ args = []
20287
21209
  } else if (target === "antigravity") {
20288
21210
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
20289
21211
  const { dirname: dn, join: j } = await import("path");
20290
- const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
21212
+ const configPath = j(homedir12(), ".gemini", "config", "mcp_config.json");
20291
21213
  let settings = {};
20292
21214
  if (ex(configPath)) {
20293
21215
  try {
@@ -20371,7 +21293,7 @@ DB stats:`));
20371
21293
  if (count > 0)
20372
21294
  console.log(` ${key.padEnd(18)} ${count}`);
20373
21295
  }
20374
- 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");
20375
21297
  console.log(chalk.dim(`
20376
21298
  ${isApiTransport() ? "API" : "DB"}: ${location}`));
20377
21299
  });
@@ -20432,10 +21354,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
20432
21354
  });
20433
21355
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
20434
21356
  const { mkdirSync: mk } = await import("fs");
20435
- const backupDir = join19(getRawStoreRoot(), "backups");
21357
+ const backupDir = join21(getRawStoreRoot(), "backups");
20436
21358
  mk(backupDir, { recursive: true });
20437
21359
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
20438
- const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
21360
+ const outPath = join21(backupDir, `configs-${ts}.tar.gz`);
20439
21361
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
20440
21362
  const { statSync: st } = await import("fs");
20441
21363
  const size = st(outPath).size;
@@ -20463,9 +21385,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
20463
21385
  console.log(chalk.cyan("Known files on disk:"));
20464
21386
  for (const k of KNOWN_CONFIGS) {
20465
21387
  if (k.rulesDir) {
20466
- 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`);
20467
21389
  } else {
20468
- 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`);
20469
21391
  }
20470
21392
  }
20471
21393
  const allConfigs = await store.listConfigs();
@@ -20543,6 +21465,7 @@ _configs() {
20543
21465
  'scan:Scan for secrets'
20544
21466
  'profile:Manage profiles'
20545
21467
  'session:Plan and apply session instructions'
21468
+ 'station-profile:Generate and inject the compact station profile block'
20546
21469
  'snapshot:Version history'
20547
21470
  'template:Template operations'
20548
21471
  'mcp:Install MCP server'
@@ -20558,7 +21481,7 @@ compdef _configs configs`);
20558
21481
  console.log(`# bash completion for configs
20559
21482
  _configs_completions() {
20560
21483
  local cur="\${COMP_WORDS[COMP_CWORD]}"
20561
- 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"
20562
21485
  COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
20563
21486
  }
20564
21487
  complete -F _configs_completions configs`);
@@ -20618,16 +21541,16 @@ program.command("watch").description("Watch known config files for changes and a
20618
21541
  for (const k of KNOWN_CONFIGS) {
20619
21542
  if (k.rulesDir) {
20620
21543
  const absDir = expandPath2(k.rulesDir);
20621
- if (!existsSync17(absDir))
21544
+ if (!existsSync20(absDir))
20622
21545
  continue;
20623
- const { readdirSync: readdirSync5 } = await import("fs");
20624
- for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
20625
- 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);
20626
21549
  mtimes.set(abs, st(abs).mtimeMs);
20627
21550
  }
20628
21551
  } else {
20629
21552
  const abs = expandPath2(k.path);
20630
- if (existsSync17(abs))
21553
+ if (existsSync20(abs))
20631
21554
  mtimes.set(abs, st(abs).mtimeMs);
20632
21555
  }
20633
21556
  }
@@ -20635,7 +21558,7 @@ program.command("watch").description("Watch known config files for changes and a
20635
21558
  const tick = async () => {
20636
21559
  let changed = 0;
20637
21560
  for (const [abs, oldMtime] of mtimes) {
20638
- if (!existsSync17(abs))
21561
+ if (!existsSync20(abs))
20639
21562
  continue;
20640
21563
  const newMtime = st(abs).mtimeMs;
20641
21564
  if (newMtime !== oldMtime) {
@@ -20647,10 +21570,10 @@ program.command("watch").description("Watch known config files for changes and a
20647
21570
  for (const k of KNOWN_CONFIGS) {
20648
21571
  if (k.rulesDir) {
20649
21572
  const absDir = expandPath2(k.rulesDir);
20650
- if (!existsSync17(absDir))
21573
+ if (!existsSync20(absDir))
20651
21574
  continue;
20652
21575
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
20653
- const abs = join19(absDir, f);
21576
+ const abs = join21(absDir, f);
20654
21577
  if (!mtimes.has(abs)) {
20655
21578
  mtimes.set(abs, st(abs).mtimeMs);
20656
21579
  changed++;
@@ -20658,7 +21581,7 @@ program.command("watch").description("Watch known config files for changes and a
20658
21581
  }
20659
21582
  } else {
20660
21583
  const abs = expandPath2(k.path);
20661
- if (existsSync17(abs) && !mtimes.has(abs)) {
21584
+ if (existsSync20(abs) && !mtimes.has(abs)) {
20662
21585
  mtimes.set(abs, st(abs).mtimeMs);
20663
21586
  changed++;
20664
21587
  }
@@ -20686,11 +21609,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
20686
21609
  if (!c.target_path)
20687
21610
  continue;
20688
21611
  const abs = expandPath(c.target_path);
20689
- if (!existsSync17(abs)) {
21612
+ if (!existsSync20(abs)) {
20690
21613
  missing++;
20691
21614
  continue;
20692
21615
  }
20693
- const disk = readFileSync15(abs, "utf-8");
21616
+ const disk = readFileSync17(abs, "utf-8");
20694
21617
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
20695
21618
  if (redactedDisk !== c.content)
20696
21619
  drifted++;
@@ -20757,7 +21680,7 @@ program.command("clean").description("Remove configs from DB whose target files
20757
21680
  if (!c.target_path)
20758
21681
  continue;
20759
21682
  const abs = expandPath(c.target_path);
20760
- if (!existsSync17(abs)) {
21683
+ if (!existsSync20(abs)) {
20761
21684
  if (printed < maxPrinted) {
20762
21685
  if (opts.dryRun) {
20763
21686
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -20898,6 +21821,48 @@ program.command("feedback <message>").description("Send feedback about this serv
20898
21821
  });
20899
21822
  console.log(chalk.green("\u2713") + " Feedback saved. Thank you!");
20900
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
+ });
20901
21866
  program.version(pkg.version).name("instructions");
20902
21867
  registerEventsCommands(program, { source: "configs" });
20903
21868
  program.parseAsync(process.argv).catch((e) => {