@hasna/instructions 0.4.10 → 0.4.12

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
@@ -7550,6 +7550,9 @@ function scanSecrets(content, format) {
7550
7550
  const r = redactContent(content, format);
7551
7551
  return r.redacted;
7552
7552
  }
7553
+ function isSecretVarName(name) {
7554
+ return SECRET_KEY_PATTERN.test(name);
7555
+ }
7553
7556
  var SECRET_KEY_PATTERN, VALUE_PATTERNS, MIN_SECRET_VALUE_LEN = 8;
7554
7557
  var init_redact = __esm(() => {
7555
7558
  SECRET_KEY_PATTERN = /^(.*_?API_?KEY|.*_?TOKEN|.*_?SECRET|.*_?PASSWORD|.*_?PASSWD|.*_?CREDENTIAL|.*_?AUTH(?:_TOKEN|_KEY|ORIZATION)?|.*_?PRIVATE_?KEY|.*_?ACCESS_?KEY|.*_?CLIENT_?SECRET|.*_?SIGNING_?KEY|.*_?ENCRYPTION_?KEY|.*_AUTH_TOKEN)$/i;
@@ -11048,8 +11051,10 @@ function sourcesFromIdentityExport(value, options = {}) {
11048
11051
  function requireIdentityExportShape(record) {
11049
11052
  if (record["contract"] === "hasna.identities.configs-instructions/v1")
11050
11053
  return "configs-contract";
11051
- if (record["version"] === 1 && record["package"] === "@hasna/identities")
11054
+ const packageName = record["package"];
11055
+ if (record["version"] === 1 && typeof packageName === "string" && CANONICAL_IDENTITY_EXPORT_PACKAGES.has(packageName)) {
11052
11056
  return "canonical-open-identities";
11057
+ }
11053
11058
  throw new Error("Unsupported identity instruction export contract.");
11054
11059
  }
11055
11060
  function normalizeInstructionRules(source, tool) {
@@ -11308,7 +11313,7 @@ function asStringArray(value) {
11308
11313
  return [];
11309
11314
  return value.filter((item) => typeof item === "string");
11310
11315
  }
11311
- var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME", ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK;
11316
+ var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME", ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CANONICAL_IDENTITY_EXPORT_PACKAGES;
11312
11317
  var init_session_render = __esm(() => {
11313
11318
  init_global_agent_rules_standard();
11314
11319
  init_project_context();
@@ -11444,6 +11449,10 @@ var init_session_render = __esm(() => {
11444
11449
  session: 100,
11445
11450
  local: 110
11446
11451
  };
11452
+ CANONICAL_IDENTITY_EXPORT_PACKAGES = new Set([
11453
+ "@hasna/identities",
11454
+ "@hasna/personas"
11455
+ ]);
11447
11456
  });
11448
11457
 
11449
11458
  // src/lib/session-render-ownership.ts
@@ -12283,30 +12292,86 @@ async function syncToDisk(opts = {}) {
12283
12292
  }
12284
12293
  return result;
12285
12294
  }
12286
- function buildDiff(expectedContent, targetPath) {
12295
+ function redactionPlaceholders(storedLine) {
12296
+ const found = [...storedLine.match(CERTAIN_PLACEHOLDER_RE) ?? []];
12297
+ const assigned = storedLine.match(ASSIGNED_PLACEHOLDER_RE);
12298
+ if (assigned) {
12299
+ const name = assigned[3] ?? assigned[4] ?? assigned[5];
12300
+ if (name && isSecretVarName(name))
12301
+ found.push(assigned[2]);
12302
+ }
12303
+ return found;
12304
+ }
12305
+ function redactFormatForTarget(targetPath, storedFormat) {
12306
+ const p = targetPath.toLowerCase();
12307
+ if (/(^|\/)\.(zshrc|zprofile|bashrc|bash_profile|profile|zshenv|env)$/.test(p) || p.endsWith(".env"))
12308
+ return "shell";
12309
+ if (/(^|\/)\.(npmrc|yarnrc|curlrc|netrc)$/.test(p))
12310
+ return "ini";
12311
+ return storedFormat;
12312
+ }
12313
+ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
12314
+ return redactionPlaceholders(storedLine).some((p) => !diskLine.includes(p));
12315
+ }
12316
+ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
12287
12317
  const path = expandPath(targetPath);
12288
12318
  if (!existsSync9(path))
12289
12319
  return `(file not found on disk: ${path})`;
12290
12320
  const diskContent = readFileSync6(path, "utf-8");
12291
12321
  if (diskContent === expectedContent)
12292
12322
  return "(no diff \u2014 identical)";
12323
+ const format = redactFormatForTarget(targetPath, storedFormat);
12293
12324
  const stored = expectedContent.split(`
12294
12325
  `);
12295
12326
  const disk = diskContent.split(`
12296
12327
  `);
12328
+ const { content: redactedDiskContent, redacted } = showSecrets ? { content: diskContent, redacted: [] } : redactContent(diskContent, format);
12329
+ const redactedDisk = redactedDiskContent.split(`
12330
+ `);
12331
+ const reasonByLine = new Map;
12332
+ for (const r of redacted)
12333
+ if (!reasonByLine.has(r.line))
12334
+ reasonByLine.set(r.line, r.reason);
12297
12335
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
12298
12336
  const maxLen = Math.max(stored.length, disk.length);
12299
12337
  for (let i = 0;i < maxLen; i++) {
12338
+ const lineNo = i + 1;
12300
12339
  const s = stored[i];
12301
12340
  const dk = disk[i];
12302
12341
  if (s === dk) {
12303
12342
  if (s !== undefined)
12304
12343
  lines.push(` ${s}`);
12305
- } else {
12344
+ continue;
12345
+ }
12346
+ if (showSecrets || dk === undefined) {
12306
12347
  if (s !== undefined)
12307
12348
  lines.push(`-${s}`);
12308
12349
  if (dk !== undefined)
12309
12350
  lines.push(`+${dk}`);
12351
+ continue;
12352
+ }
12353
+ const structural = s !== undefined && storedPlaceholderIsLiteralOnDisk(s, dk);
12354
+ const patterned = reasonByLine.has(lineNo);
12355
+ if (!structural && !patterned) {
12356
+ if (s !== undefined)
12357
+ lines.push(`-${s}`);
12358
+ lines.push(`+${dk}`);
12359
+ continue;
12360
+ }
12361
+ if (structural) {
12362
+ const placeholder = redactionPlaceholders(s).find((p) => !dk.includes(p)) ?? "a placeholder";
12363
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value for ${placeholder} \u2014 redacted (use --show-secrets on a TTY to reveal)`);
12364
+ continue;
12365
+ }
12366
+ const safe = redactedDisk[i] ?? "";
12367
+ const reason = reasonByLine.get(lineNo);
12368
+ if (s !== undefined && safe === s) {
12369
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value (${reason}) \u2014 redacted (use --show-secrets on a TTY to reveal)`);
12370
+ } else {
12371
+ if (s !== undefined)
12372
+ lines.push(`-${s}`);
12373
+ lines.push(`+${safe}`);
12374
+ lines.push(`~line ${lineNo}: disk value redacted (${reason}) \u2014 use --show-secrets on a TTY to reveal`);
12310
12375
  }
12311
12376
  }
12312
12377
  return lines.join(`
@@ -12321,15 +12386,16 @@ async function diffConfig(config, opts = {}) {
12321
12386
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
12322
12387
  return "(generated output \u2014 managed by fan-out)";
12323
12388
  }
12389
+ const showSecrets = opts.showSecrets ?? false;
12324
12390
  if (config.target_path) {
12325
- const diff = buildDiff(config.content, config.target_path);
12391
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
12326
12392
  if (!diff.includes("no diff"))
12327
12393
  diffs.push(diff);
12328
12394
  }
12329
12395
  if (config.outputs.length > 0) {
12330
12396
  for (const output of config.outputs) {
12331
12397
  const expected = applyTransform(config, output, { configs: contextConfigs });
12332
- const diff = buildDiff(expected, output.target_path);
12398
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
12333
12399
  if (!diff.includes("no diff"))
12334
12400
  diffs.push(diff);
12335
12401
  }
@@ -12400,7 +12466,7 @@ function detectFormat(filePath) {
12400
12466
  return "ini";
12401
12467
  return "text";
12402
12468
  }
12403
- var CLAUDE_PROMPT_OUTPUTS, KNOWN_CONFIGS, PROJECT_CONFIG_FILES;
12469
+ var CLAUDE_PROMPT_OUTPUTS, KNOWN_CONFIGS, PROJECT_CONFIG_FILES, CERTAIN_PLACEHOLDER_RE, ASSIGNED_PLACEHOLDER_RE;
12404
12470
  var init_sync = __esm(() => {
12405
12471
  init_config_store();
12406
12472
  init_apply();
@@ -12464,6 +12530,8 @@ var init_sync = __esm(() => {
12464
12530
  { file: ".agents/mcp_config.json", category: "mcp", agent: "antigravity", format: "json" },
12465
12531
  { file: ".qwen/settings.json", category: "agent", agent: "qwen", format: "json" }
12466
12532
  ];
12533
+ CERTAIN_PLACEHOLDER_RE = /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/g;
12534
+ ASSIGNED_PLACEHOLDER_RE = /[=:]\s*(["']?)(\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Z][A-Z0-9_]*)|%([A-Za-z_][A-Za-z0-9_]*)%)\1\s*,?\s*$/;
12467
12535
  });
12468
12536
 
12469
12537
  // src/lib/package-manager-guard.ts
@@ -15424,12 +15492,18 @@ program.command("apply <id>").description("Apply a config to its target_path and
15424
15492
  process.exit(1);
15425
15493
  }
15426
15494
  });
15427
- program.command("diff [id]").description("Show diff between stored config and disk (omit id for --all)").option("--all", "diff every known config against disk").action(async (id, opts) => {
15495
+ program.command("diff [id]").description("Show diff between stored config and disk (omit id for --all). Credential values on disk are redacted.").option("--all", "diff every known config against disk").option("--show-secrets", "render credential values from disk verbatim (interactive TTY only)").action(async (id, opts) => {
15428
15496
  try {
15497
+ if (opts.showSecrets && !process.stdout.isTTY) {
15498
+ console.error(chalk.red("--show-secrets refuses to run on a non-TTY: the output would be captured verbatim into a log or transcript."));
15499
+ console.error(chalk.dim("Run it in an interactive terminal, or use the redacted default."));
15500
+ process.exit(1);
15501
+ }
15502
+ const showSecrets = Boolean(opts.showSecrets);
15429
15503
  const store = resolveConfigStore();
15430
15504
  if (id) {
15431
15505
  const config = await store.getConfig(id);
15432
- console.log(await diffConfig(config, { store }));
15506
+ console.log(await diffConfig(config, { store, showSecrets }));
15433
15507
  return;
15434
15508
  }
15435
15509
  const configs = await store.listConfigs({ kind: "file" });
@@ -15437,7 +15511,7 @@ program.command("diff [id]").description("Show diff between stored config and di
15437
15511
  for (const c of configs) {
15438
15512
  if (!c.target_path)
15439
15513
  continue;
15440
- const diff = await diffConfig(c, { store });
15514
+ const diff = await diffConfig(c, { store, showSecrets });
15441
15515
  if (diff.includes("no diff") || diff.includes("not found"))
15442
15516
  continue;
15443
15517
  drifted++;
package/dist/index.js CHANGED
@@ -5515,6 +5515,9 @@ function scanSecrets(content, format) {
5515
5515
  function hasSecrets(content, format) {
5516
5516
  return scanSecrets(content, format).length > 0;
5517
5517
  }
5518
+ function isSecretVarName(name) {
5519
+ return SECRET_KEY_PATTERN.test(name);
5520
+ }
5518
5521
 
5519
5522
  // src/lib/session-render-contract.ts
5520
5523
  var CODEWITH_NATIVE_IMPORTS_ENV = "HASNA_CONFIGS_CODEWITH_NATIVE_IMPORTS";
@@ -9212,11 +9215,17 @@ function sourcesFromIdentityExport(value, options = {}) {
9212
9215
  exportShape: shape
9213
9216
  })).filter((source) => source !== null);
9214
9217
  }
9218
+ var CANONICAL_IDENTITY_EXPORT_PACKAGES = new Set([
9219
+ "@hasna/identities",
9220
+ "@hasna/personas"
9221
+ ]);
9215
9222
  function requireIdentityExportShape(record) {
9216
9223
  if (record["contract"] === "hasna.identities.configs-instructions/v1")
9217
9224
  return "configs-contract";
9218
- if (record["version"] === 1 && record["package"] === "@hasna/identities")
9225
+ const packageName = record["package"];
9226
+ if (record["version"] === 1 && typeof packageName === "string" && CANONICAL_IDENTITY_EXPORT_PACKAGES.has(packageName)) {
9219
9227
  return "canonical-open-identities";
9228
+ }
9220
9229
  throw new Error("Unsupported identity instruction export contract.");
9221
9230
  }
9222
9231
  function normalizeInstructionRules(source, tool) {
@@ -11674,30 +11683,88 @@ async function syncToDisk(opts = {}) {
11674
11683
  }
11675
11684
  return result;
11676
11685
  }
11677
- function buildDiff(expectedContent, targetPath) {
11686
+ var CERTAIN_PLACEHOLDER_RE = /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/g;
11687
+ var ASSIGNED_PLACEHOLDER_RE = /[=:]\s*(["']?)(\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Z][A-Z0-9_]*)|%([A-Za-z_][A-Za-z0-9_]*)%)\1\s*,?\s*$/;
11688
+ function redactionPlaceholders(storedLine) {
11689
+ const found = [...storedLine.match(CERTAIN_PLACEHOLDER_RE) ?? []];
11690
+ const assigned = storedLine.match(ASSIGNED_PLACEHOLDER_RE);
11691
+ if (assigned) {
11692
+ const name = assigned[3] ?? assigned[4] ?? assigned[5];
11693
+ if (name && isSecretVarName(name))
11694
+ found.push(assigned[2]);
11695
+ }
11696
+ return found;
11697
+ }
11698
+ function redactFormatForTarget(targetPath, storedFormat) {
11699
+ const p = targetPath.toLowerCase();
11700
+ if (/(^|\/)\.(zshrc|zprofile|bashrc|bash_profile|profile|zshenv|env)$/.test(p) || p.endsWith(".env"))
11701
+ return "shell";
11702
+ if (/(^|\/)\.(npmrc|yarnrc|curlrc|netrc)$/.test(p))
11703
+ return "ini";
11704
+ return storedFormat;
11705
+ }
11706
+ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
11707
+ return redactionPlaceholders(storedLine).some((p) => !diskLine.includes(p));
11708
+ }
11709
+ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
11678
11710
  const path = expandPath(targetPath);
11679
11711
  if (!existsSync11(path))
11680
11712
  return `(file not found on disk: ${path})`;
11681
11713
  const diskContent = readFileSync9(path, "utf-8");
11682
11714
  if (diskContent === expectedContent)
11683
11715
  return "(no diff \u2014 identical)";
11716
+ const format = redactFormatForTarget(targetPath, storedFormat);
11684
11717
  const stored = expectedContent.split(`
11685
11718
  `);
11686
11719
  const disk = diskContent.split(`
11687
11720
  `);
11721
+ const { content: redactedDiskContent, redacted } = showSecrets ? { content: diskContent, redacted: [] } : redactContent(diskContent, format);
11722
+ const redactedDisk = redactedDiskContent.split(`
11723
+ `);
11724
+ const reasonByLine = new Map;
11725
+ for (const r of redacted)
11726
+ if (!reasonByLine.has(r.line))
11727
+ reasonByLine.set(r.line, r.reason);
11688
11728
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
11689
11729
  const maxLen = Math.max(stored.length, disk.length);
11690
11730
  for (let i = 0;i < maxLen; i++) {
11731
+ const lineNo = i + 1;
11691
11732
  const s = stored[i];
11692
11733
  const dk = disk[i];
11693
11734
  if (s === dk) {
11694
11735
  if (s !== undefined)
11695
11736
  lines.push(` ${s}`);
11696
- } else {
11737
+ continue;
11738
+ }
11739
+ if (showSecrets || dk === undefined) {
11697
11740
  if (s !== undefined)
11698
11741
  lines.push(`-${s}`);
11699
11742
  if (dk !== undefined)
11700
11743
  lines.push(`+${dk}`);
11744
+ continue;
11745
+ }
11746
+ const structural = s !== undefined && storedPlaceholderIsLiteralOnDisk(s, dk);
11747
+ const patterned = reasonByLine.has(lineNo);
11748
+ if (!structural && !patterned) {
11749
+ if (s !== undefined)
11750
+ lines.push(`-${s}`);
11751
+ lines.push(`+${dk}`);
11752
+ continue;
11753
+ }
11754
+ if (structural) {
11755
+ const placeholder = redactionPlaceholders(s).find((p) => !dk.includes(p)) ?? "a placeholder";
11756
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value for ${placeholder} \u2014 redacted (use --show-secrets on a TTY to reveal)`);
11757
+ continue;
11758
+ }
11759
+ const safe = redactedDisk[i] ?? "";
11760
+ const reason = reasonByLine.get(lineNo);
11761
+ if (s !== undefined && safe === s) {
11762
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value (${reason}) \u2014 redacted (use --show-secrets on a TTY to reveal)`);
11763
+ } else {
11764
+ if (s !== undefined)
11765
+ lines.push(`-${s}`);
11766
+ lines.push(`+${safe}`);
11767
+ lines.push(`~line ${lineNo}: disk value redacted (${reason}) \u2014 use --show-secrets on a TTY to reveal`);
11701
11768
  }
11702
11769
  }
11703
11770
  return lines.join(`
@@ -11712,15 +11779,16 @@ async function diffConfig(config, opts = {}) {
11712
11779
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
11713
11780
  return "(generated output \u2014 managed by fan-out)";
11714
11781
  }
11782
+ const showSecrets = opts.showSecrets ?? false;
11715
11783
  if (config.target_path) {
11716
- const diff = buildDiff(config.content, config.target_path);
11784
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
11717
11785
  if (!diff.includes("no diff"))
11718
11786
  diffs.push(diff);
11719
11787
  }
11720
11788
  if (config.outputs.length > 0) {
11721
11789
  for (const output of config.outputs) {
11722
11790
  const expected = applyTransform(config, output, { configs: contextConfigs });
11723
- const diff = buildDiff(expected, output.target_path);
11791
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
11724
11792
  if (!diff.includes("no diff"))
11725
11793
  diffs.push(diff);
11726
11794
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=diff-redaction.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-redaction.test.d.ts","sourceRoot":"","sources":["../../src/lib/diff-redaction.test.ts"],"names":[],"mappings":""}
@@ -26,4 +26,13 @@ export declare function redactContent(content: string, format: RedactFormat): Re
26
26
  export declare function scanSecrets(content: string, format: RedactFormat): RedactedVar[];
27
27
  /** Returns true if content contains any detectable secrets. */
28
28
  export declare function hasSecrets(content: string, format: RedactFormat): boolean;
29
+ /**
30
+ * Is this variable NAME secret-shaped? (`NPM_TOKEN` yes, `PATH`/`HOME` no.)
31
+ *
32
+ * Exported so callers reasoning about placeholder names — notably the diff
33
+ * renderer, which must tell a redaction artefact from an ordinary shell
34
+ * variable — share this module's single definition of "secret-shaped" instead
35
+ * of duplicating the pattern and drifting from it.
36
+ */
37
+ export declare function isSecretVarName(name: string): boolean;
29
38
  //# sourceMappingURL=redact.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAgND,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5F,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,YAAY,CAQjF;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,EAAE,CAGhF;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAEzE"}
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAgND,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5F,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,YAAY,CAQjF;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,EAAE,CAGhF;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -43,6 +43,16 @@ export interface SyncToDiskOptions {
43
43
  export declare function syncToDisk(opts?: SyncToDiskOptions): Promise<SyncResult>;
44
44
  export interface DiffConfigOptions {
45
45
  store?: ConfigStore;
46
+ /**
47
+ * Render the raw disk content, INCLUDING any credential values it holds.
48
+ *
49
+ * Off by default and deliberately hard to reach: the CLI refuses it on a
50
+ * non-TTY, mirroring `secrets get --show`. Everything a tool prints is
51
+ * persisted verbatim into the session transcript, and agent output is also
52
+ * served as run evidence — so an unredacted diff writes a live credential
53
+ * into a durable, fetchable artefact.
54
+ */
55
+ showSecrets?: boolean;
46
56
  }
47
57
  export declare function diffConfig(config: Config, opts?: DiffConfigOptions): Promise<string>;
48
58
  export declare function detectCategory(filePath: string): ConfigCategory;
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/lib/sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAW/E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,qBAAqB,EAAE,YAAY,EAQ/C,CAAC;AAwDF,eAAO,MAAM,aAAa,EAAE,WAAW,EAwDtC,CAAC;AAIF,eAAO,MAAM,oBAAoB;;cAC2B,cAAc;WAAsB,WAAW;YAAwB,YAAY;GAc9I,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgE/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqGhF;AAGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmClF;AAGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAwBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2B9F;AAGD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAU/D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAgBzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAQ3D;AAGD,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/lib/sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAW/E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,qBAAqB,EAAE,YAAY,EAQ/C,CAAC;AAwDF,eAAO,MAAM,aAAa,EAAE,WAAW,EAwDtC,CAAC;AAIF,eAAO,MAAM,oBAAoB;;cAC2B,cAAc;WAAsB,WAAW;YAAwB,YAAY;GAc9I,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgE/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqGhF;AAGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmClF;AAGD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA8JD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B9F;AAGD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAU/D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAgBzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAQ3D;AAGD,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/dist/mcp/index.js CHANGED
@@ -5170,6 +5170,7 @@ var exports_redact = {};
5170
5170
  __export(exports_redact, {
5171
5171
  scanSecrets: () => scanSecrets,
5172
5172
  redactContent: () => redactContent,
5173
+ isSecretVarName: () => isSecretVarName,
5173
5174
  hasSecrets: () => hasSecrets
5174
5175
  });
5175
5176
  function redactShell(content) {
@@ -5343,6 +5344,9 @@ function scanSecrets(content, format) {
5343
5344
  function hasSecrets(content, format) {
5344
5345
  return scanSecrets(content, format).length > 0;
5345
5346
  }
5347
+ function isSecretVarName(name) {
5348
+ return SECRET_KEY_PATTERN.test(name);
5349
+ }
5346
5350
  var SECRET_KEY_PATTERN, VALUE_PATTERNS, MIN_SECRET_VALUE_LEN = 8;
5347
5351
  var init_redact = __esm(() => {
5348
5352
  SECRET_KEY_PATTERN = /^(.*_?API_?KEY|.*_?TOKEN|.*_?SECRET|.*_?PASSWORD|.*_?PASSWD|.*_?CREDENTIAL|.*_?AUTH(?:_TOKEN|_KEY|ORIZATION)?|.*_?PRIVATE_?KEY|.*_?ACCESS_?KEY|.*_?CLIENT_?SECRET|.*_?SIGNING_?KEY|.*_?ENCRYPTION_?KEY|.*_AUTH_TOKEN)$/i;
@@ -5649,7 +5653,7 @@ function applyTransform(source, output, context = {}) {
5649
5653
  var init_transforms = () => {};
5650
5654
 
5651
5655
  // src/lib/session-render.ts
5652
- var ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS;
5656
+ var ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, CANONICAL_IDENTITY_EXPORT_PACKAGES;
5653
5657
  var init_session_render = __esm(() => {
5654
5658
  init_global_agent_rules_standard();
5655
5659
  init_project_context();
@@ -5762,6 +5766,10 @@ var init_session_render = __esm(() => {
5762
5766
  SESSION_RENDER_MANIFEST_RELATIVE_PATH,
5763
5767
  SESSION_RENDER_SNAPSHOT_RELATIVE_DIR
5764
5768
  ];
5769
+ CANONICAL_IDENTITY_EXPORT_PACKAGES = new Set([
5770
+ "@hasna/identities",
5771
+ "@hasna/personas"
5772
+ ]);
5765
5773
  });
5766
5774
 
5767
5775
  // src/lib/session-render-ownership.ts
@@ -6511,30 +6519,86 @@ async function syncToDisk(opts = {}) {
6511
6519
  }
6512
6520
  return result;
6513
6521
  }
6514
- function buildDiff(expectedContent, targetPath) {
6522
+ function redactionPlaceholders(storedLine) {
6523
+ const found = [...storedLine.match(CERTAIN_PLACEHOLDER_RE) ?? []];
6524
+ const assigned = storedLine.match(ASSIGNED_PLACEHOLDER_RE);
6525
+ if (assigned) {
6526
+ const name = assigned[3] ?? assigned[4] ?? assigned[5];
6527
+ if (name && isSecretVarName(name))
6528
+ found.push(assigned[2]);
6529
+ }
6530
+ return found;
6531
+ }
6532
+ function redactFormatForTarget(targetPath, storedFormat) {
6533
+ const p = targetPath.toLowerCase();
6534
+ if (/(^|\/)\.(zshrc|zprofile|bashrc|bash_profile|profile|zshenv|env)$/.test(p) || p.endsWith(".env"))
6535
+ return "shell";
6536
+ if (/(^|\/)\.(npmrc|yarnrc|curlrc|netrc)$/.test(p))
6537
+ return "ini";
6538
+ return storedFormat;
6539
+ }
6540
+ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
6541
+ return redactionPlaceholders(storedLine).some((p) => !diskLine.includes(p));
6542
+ }
6543
+ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
6515
6544
  const path = expandPath(targetPath);
6516
6545
  if (!existsSync5(path))
6517
6546
  return `(file not found on disk: ${path})`;
6518
6547
  const diskContent = readFileSync3(path, "utf-8");
6519
6548
  if (diskContent === expectedContent)
6520
6549
  return "(no diff \u2014 identical)";
6550
+ const format = redactFormatForTarget(targetPath, storedFormat);
6521
6551
  const stored = expectedContent.split(`
6522
6552
  `);
6523
6553
  const disk = diskContent.split(`
6524
6554
  `);
6555
+ const { content: redactedDiskContent, redacted } = showSecrets ? { content: diskContent, redacted: [] } : redactContent(diskContent, format);
6556
+ const redactedDisk = redactedDiskContent.split(`
6557
+ `);
6558
+ const reasonByLine = new Map;
6559
+ for (const r of redacted)
6560
+ if (!reasonByLine.has(r.line))
6561
+ reasonByLine.set(r.line, r.reason);
6525
6562
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
6526
6563
  const maxLen = Math.max(stored.length, disk.length);
6527
6564
  for (let i = 0;i < maxLen; i++) {
6565
+ const lineNo = i + 1;
6528
6566
  const s = stored[i];
6529
6567
  const dk = disk[i];
6530
6568
  if (s === dk) {
6531
6569
  if (s !== undefined)
6532
6570
  lines.push(` ${s}`);
6533
- } else {
6571
+ continue;
6572
+ }
6573
+ if (showSecrets || dk === undefined) {
6534
6574
  if (s !== undefined)
6535
6575
  lines.push(`-${s}`);
6536
6576
  if (dk !== undefined)
6537
6577
  lines.push(`+${dk}`);
6578
+ continue;
6579
+ }
6580
+ const structural = s !== undefined && storedPlaceholderIsLiteralOnDisk(s, dk);
6581
+ const patterned = reasonByLine.has(lineNo);
6582
+ if (!structural && !patterned) {
6583
+ if (s !== undefined)
6584
+ lines.push(`-${s}`);
6585
+ lines.push(`+${dk}`);
6586
+ continue;
6587
+ }
6588
+ if (structural) {
6589
+ const placeholder = redactionPlaceholders(s).find((p) => !dk.includes(p)) ?? "a placeholder";
6590
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value for ${placeholder} \u2014 redacted (use --show-secrets on a TTY to reveal)`);
6591
+ continue;
6592
+ }
6593
+ const safe = redactedDisk[i] ?? "";
6594
+ const reason = reasonByLine.get(lineNo);
6595
+ if (s !== undefined && safe === s) {
6596
+ lines.push(`~line ${lineNo} differs: stored \`${s}\`, disk holds a literal value (${reason}) \u2014 redacted (use --show-secrets on a TTY to reveal)`);
6597
+ } else {
6598
+ if (s !== undefined)
6599
+ lines.push(`-${s}`);
6600
+ lines.push(`+${safe}`);
6601
+ lines.push(`~line ${lineNo}: disk value redacted (${reason}) \u2014 use --show-secrets on a TTY to reveal`);
6538
6602
  }
6539
6603
  }
6540
6604
  return lines.join(`
@@ -6549,15 +6613,16 @@ async function diffConfig(config, opts = {}) {
6549
6613
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
6550
6614
  return "(generated output \u2014 managed by fan-out)";
6551
6615
  }
6616
+ const showSecrets = opts.showSecrets ?? false;
6552
6617
  if (config.target_path) {
6553
- const diff = buildDiff(config.content, config.target_path);
6618
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
6554
6619
  if (!diff.includes("no diff"))
6555
6620
  diffs.push(diff);
6556
6621
  }
6557
6622
  if (config.outputs.length > 0) {
6558
6623
  for (const output of config.outputs) {
6559
6624
  const expected = applyTransform(config, output, { configs: contextConfigs });
6560
- const diff = buildDiff(expected, output.target_path);
6625
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
6561
6626
  if (!diff.includes("no diff"))
6562
6627
  diffs.push(diff);
6563
6628
  }
@@ -6628,7 +6693,7 @@ function detectFormat(filePath) {
6628
6693
  return "ini";
6629
6694
  return "text";
6630
6695
  }
6631
- var CLAUDE_PROMPT_OUTPUTS, KNOWN_CONFIGS, PROJECT_CONFIG_FILES;
6696
+ var CLAUDE_PROMPT_OUTPUTS, KNOWN_CONFIGS, PROJECT_CONFIG_FILES, CERTAIN_PLACEHOLDER_RE, ASSIGNED_PLACEHOLDER_RE;
6632
6697
  var init_sync = __esm(() => {
6633
6698
  init_config_store();
6634
6699
  init_apply();
@@ -6692,6 +6757,8 @@ var init_sync = __esm(() => {
6692
6757
  { file: ".agents/mcp_config.json", category: "mcp", agent: "antigravity", format: "json" },
6693
6758
  { file: ".qwen/settings.json", category: "agent", agent: "qwen", format: "json" }
6694
6759
  ];
6760
+ CERTAIN_PLACEHOLDER_RE = /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/g;
6761
+ ASSIGNED_PLACEHOLDER_RE = /[=:]\s*(["']?)(\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Z][A-Z0-9_]*)|%([A-Za-z_][A-Za-z0-9_]*)%)\1\s*,?\s*$/;
6695
6762
  });
6696
6763
 
6697
6764
  // src/lib/sync-dir.ts
@@ -6788,7 +6855,7 @@ var init_sync_dir = __esm(() => {
6788
6855
  var require_package = __commonJS((exports, module) => {
6789
6856
  module.exports = {
6790
6857
  name: "@hasna/instructions",
6791
- version: "0.4.10",
6858
+ version: "0.4.12",
6792
6859
  description: "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
6793
6860
  type: "module",
6794
6861
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/instructions",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",