@hasna/instructions 0.4.11 → 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;
@@ -12289,30 +12292,86 @@ async function syncToDisk(opts = {}) {
12289
12292
  }
12290
12293
  return result;
12291
12294
  }
12292
- 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) {
12293
12317
  const path = expandPath(targetPath);
12294
12318
  if (!existsSync9(path))
12295
12319
  return `(file not found on disk: ${path})`;
12296
12320
  const diskContent = readFileSync6(path, "utf-8");
12297
12321
  if (diskContent === expectedContent)
12298
12322
  return "(no diff \u2014 identical)";
12323
+ const format = redactFormatForTarget(targetPath, storedFormat);
12299
12324
  const stored = expectedContent.split(`
12300
12325
  `);
12301
12326
  const disk = diskContent.split(`
12302
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);
12303
12335
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
12304
12336
  const maxLen = Math.max(stored.length, disk.length);
12305
12337
  for (let i = 0;i < maxLen; i++) {
12338
+ const lineNo = i + 1;
12306
12339
  const s = stored[i];
12307
12340
  const dk = disk[i];
12308
12341
  if (s === dk) {
12309
12342
  if (s !== undefined)
12310
12343
  lines.push(` ${s}`);
12311
- } else {
12344
+ continue;
12345
+ }
12346
+ if (showSecrets || dk === undefined) {
12312
12347
  if (s !== undefined)
12313
12348
  lines.push(`-${s}`);
12314
12349
  if (dk !== undefined)
12315
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`);
12316
12375
  }
12317
12376
  }
12318
12377
  return lines.join(`
@@ -12327,15 +12386,16 @@ async function diffConfig(config, opts = {}) {
12327
12386
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
12328
12387
  return "(generated output \u2014 managed by fan-out)";
12329
12388
  }
12389
+ const showSecrets = opts.showSecrets ?? false;
12330
12390
  if (config.target_path) {
12331
- const diff = buildDiff(config.content, config.target_path);
12391
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
12332
12392
  if (!diff.includes("no diff"))
12333
12393
  diffs.push(diff);
12334
12394
  }
12335
12395
  if (config.outputs.length > 0) {
12336
12396
  for (const output of config.outputs) {
12337
12397
  const expected = applyTransform(config, output, { configs: contextConfigs });
12338
- const diff = buildDiff(expected, output.target_path);
12398
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
12339
12399
  if (!diff.includes("no diff"))
12340
12400
  diffs.push(diff);
12341
12401
  }
@@ -12406,7 +12466,7 @@ function detectFormat(filePath) {
12406
12466
  return "ini";
12407
12467
  return "text";
12408
12468
  }
12409
- 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;
12410
12470
  var init_sync = __esm(() => {
12411
12471
  init_config_store();
12412
12472
  init_apply();
@@ -12470,6 +12530,8 @@ var init_sync = __esm(() => {
12470
12530
  { file: ".agents/mcp_config.json", category: "mcp", agent: "antigravity", format: "json" },
12471
12531
  { file: ".qwen/settings.json", category: "agent", agent: "qwen", format: "json" }
12472
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*$/;
12473
12535
  });
12474
12536
 
12475
12537
  // src/lib/package-manager-guard.ts
@@ -15430,12 +15492,18 @@ program.command("apply <id>").description("Apply a config to its target_path and
15430
15492
  process.exit(1);
15431
15493
  }
15432
15494
  });
15433
- 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) => {
15434
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);
15435
15503
  const store = resolveConfigStore();
15436
15504
  if (id) {
15437
15505
  const config = await store.getConfig(id);
15438
- console.log(await diffConfig(config, { store }));
15506
+ console.log(await diffConfig(config, { store, showSecrets }));
15439
15507
  return;
15440
15508
  }
15441
15509
  const configs = await store.listConfigs({ kind: "file" });
@@ -15443,7 +15511,7 @@ program.command("diff [id]").description("Show diff between stored config and di
15443
15511
  for (const c of configs) {
15444
15512
  if (!c.target_path)
15445
15513
  continue;
15446
- const diff = await diffConfig(c, { store });
15514
+ const diff = await diffConfig(c, { store, showSecrets });
15447
15515
  if (diff.includes("no diff") || diff.includes("not found"))
15448
15516
  continue;
15449
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";
@@ -11680,30 +11683,88 @@ async function syncToDisk(opts = {}) {
11680
11683
  }
11681
11684
  return result;
11682
11685
  }
11683
- 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) {
11684
11710
  const path = expandPath(targetPath);
11685
11711
  if (!existsSync11(path))
11686
11712
  return `(file not found on disk: ${path})`;
11687
11713
  const diskContent = readFileSync9(path, "utf-8");
11688
11714
  if (diskContent === expectedContent)
11689
11715
  return "(no diff \u2014 identical)";
11716
+ const format = redactFormatForTarget(targetPath, storedFormat);
11690
11717
  const stored = expectedContent.split(`
11691
11718
  `);
11692
11719
  const disk = diskContent.split(`
11693
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);
11694
11728
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
11695
11729
  const maxLen = Math.max(stored.length, disk.length);
11696
11730
  for (let i = 0;i < maxLen; i++) {
11731
+ const lineNo = i + 1;
11697
11732
  const s = stored[i];
11698
11733
  const dk = disk[i];
11699
11734
  if (s === dk) {
11700
11735
  if (s !== undefined)
11701
11736
  lines.push(` ${s}`);
11702
- } else {
11737
+ continue;
11738
+ }
11739
+ if (showSecrets || dk === undefined) {
11703
11740
  if (s !== undefined)
11704
11741
  lines.push(`-${s}`);
11705
11742
  if (dk !== undefined)
11706
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`);
11707
11768
  }
11708
11769
  }
11709
11770
  return lines.join(`
@@ -11718,15 +11779,16 @@ async function diffConfig(config, opts = {}) {
11718
11779
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
11719
11780
  return "(generated output \u2014 managed by fan-out)";
11720
11781
  }
11782
+ const showSecrets = opts.showSecrets ?? false;
11721
11783
  if (config.target_path) {
11722
- const diff = buildDiff(config.content, config.target_path);
11784
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
11723
11785
  if (!diff.includes("no diff"))
11724
11786
  diffs.push(diff);
11725
11787
  }
11726
11788
  if (config.outputs.length > 0) {
11727
11789
  for (const output of config.outputs) {
11728
11790
  const expected = applyTransform(config, output, { configs: contextConfigs });
11729
- const diff = buildDiff(expected, output.target_path);
11791
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
11730
11792
  if (!diff.includes("no diff"))
11731
11793
  diffs.push(diff);
11732
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;
@@ -6515,30 +6519,86 @@ async function syncToDisk(opts = {}) {
6515
6519
  }
6516
6520
  return result;
6517
6521
  }
6518
- 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) {
6519
6544
  const path = expandPath(targetPath);
6520
6545
  if (!existsSync5(path))
6521
6546
  return `(file not found on disk: ${path})`;
6522
6547
  const diskContent = readFileSync3(path, "utf-8");
6523
6548
  if (diskContent === expectedContent)
6524
6549
  return "(no diff \u2014 identical)";
6550
+ const format = redactFormatForTarget(targetPath, storedFormat);
6525
6551
  const stored = expectedContent.split(`
6526
6552
  `);
6527
6553
  const disk = diskContent.split(`
6528
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);
6529
6562
  const lines = [`--- stored (DB)`, `+++ disk (${path})`];
6530
6563
  const maxLen = Math.max(stored.length, disk.length);
6531
6564
  for (let i = 0;i < maxLen; i++) {
6565
+ const lineNo = i + 1;
6532
6566
  const s = stored[i];
6533
6567
  const dk = disk[i];
6534
6568
  if (s === dk) {
6535
6569
  if (s !== undefined)
6536
6570
  lines.push(` ${s}`);
6537
- } else {
6571
+ continue;
6572
+ }
6573
+ if (showSecrets || dk === undefined) {
6538
6574
  if (s !== undefined)
6539
6575
  lines.push(`-${s}`);
6540
6576
  if (dk !== undefined)
6541
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`);
6542
6602
  }
6543
6603
  }
6544
6604
  return lines.join(`
@@ -6553,15 +6613,16 @@ async function diffConfig(config, opts = {}) {
6553
6613
  if (isGeneratedOutputTarget2(config, outputOwnerIdsByTarget(contextConfigs))) {
6554
6614
  return "(generated output \u2014 managed by fan-out)";
6555
6615
  }
6616
+ const showSecrets = opts.showSecrets ?? false;
6556
6617
  if (config.target_path) {
6557
- const diff = buildDiff(config.content, config.target_path);
6618
+ const diff = buildDiff(config.content, config.target_path, config.format, showSecrets);
6558
6619
  if (!diff.includes("no diff"))
6559
6620
  diffs.push(diff);
6560
6621
  }
6561
6622
  if (config.outputs.length > 0) {
6562
6623
  for (const output of config.outputs) {
6563
6624
  const expected = applyTransform(config, output, { configs: contextConfigs });
6564
- const diff = buildDiff(expected, output.target_path);
6625
+ const diff = buildDiff(expected, output.target_path, detectFormat(output.target_path), showSecrets);
6565
6626
  if (!diff.includes("no diff"))
6566
6627
  diffs.push(diff);
6567
6628
  }
@@ -6632,7 +6693,7 @@ function detectFormat(filePath) {
6632
6693
  return "ini";
6633
6694
  return "text";
6634
6695
  }
6635
- 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;
6636
6697
  var init_sync = __esm(() => {
6637
6698
  init_config_store();
6638
6699
  init_apply();
@@ -6696,6 +6757,8 @@ var init_sync = __esm(() => {
6696
6757
  { file: ".agents/mcp_config.json", category: "mcp", agent: "antigravity", format: "json" },
6697
6758
  { file: ".qwen/settings.json", category: "agent", agent: "qwen", format: "json" }
6698
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*$/;
6699
6762
  });
6700
6763
 
6701
6764
  // src/lib/sync-dir.ts
@@ -6792,7 +6855,7 @@ var init_sync_dir = __esm(() => {
6792
6855
  var require_package = __commonJS((exports, module) => {
6793
6856
  module.exports = {
6794
6857
  name: "@hasna/instructions",
6795
- version: "0.4.11",
6858
+ version: "0.4.12",
6796
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.",
6797
6860
  type: "module",
6798
6861
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/instructions",
3
- "version": "0.4.11",
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",