@cortexkit/aft 0.64.0 → 0.65.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cortexkit/aft
2
2
 
3
- Unified CLI for [Agent File Tools (AFT)](https://github.com/cortexkit/aft) — setup, doctor, and diagnostics across supported agent harnesses.
3
+ Unified CLI for [Agent Foundational Toolkit (AFT)](https://github.com/cortexkit/aft) — setup, doctor, and diagnostics across supported agent harnesses.
4
4
 
5
5
  ## Usage
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @cortexkit/aft — unified CLI for Agent File Tools.
3
+ * @cortexkit/aft — unified CLI for Agent Foundational Toolkit.
4
4
  *
5
5
  * Entry point parses argv and dispatches to commands. Harness selection
6
6
  * (OpenCode, Pi) is auto-detected from installed config paths; explicit
package/dist/index.js CHANGED
@@ -263,10 +263,87 @@ function relativePathEscapesRoot(rel, platform = process.platform) {
263
263
  }
264
264
  var init_path_display = () => {};
265
265
 
266
+ // ../aft-bridge/dist/wait-clock.js
267
+ class WaitClock {
268
+ wallNow;
269
+ awakeNow;
270
+ wall;
271
+ awake;
272
+ skew = 0;
273
+ elapsed;
274
+ suspendCount = 0;
275
+ lastSuspendEnd = Number.NEGATIVE_INFINITY;
276
+ constructor(wallNow = Date.now, awakeNow = () => performance.now()) {
277
+ this.wallNow = wallNow;
278
+ this.awakeNow = awakeNow;
279
+ this.wall = wallNow();
280
+ this.awake = awakeNow();
281
+ this.elapsed = this.awake;
282
+ }
283
+ now() {
284
+ const wall = this.wallNow();
285
+ const awake = this.awakeNow();
286
+ const active = Math.max(0, awake - this.awake);
287
+ this.skew = Math.max(0, this.skew + wall - this.wall - active);
288
+ this.elapsed += active;
289
+ if (this.skew >= SUSPEND_SKEW_MS) {
290
+ this.elapsed += this.skew;
291
+ this.lastSuspendEnd = this.elapsed;
292
+ this.skew = 0;
293
+ this.suspendCount++;
294
+ }
295
+ this.wall = wall;
296
+ this.awake = awake;
297
+ return this.elapsed;
298
+ }
299
+ }
300
+
301
+ class RequestDeadline {
302
+ clock;
303
+ allowResume;
304
+ deadline;
305
+ suspendCount;
306
+ resumed = false;
307
+ constructor(clock, timeoutMs, allowResume = true) {
308
+ this.clock = clock;
309
+ this.allowResume = allowResume;
310
+ this.deadline = clock.now() + timeoutMs;
311
+ this.suspendCount = clock.suspendCount;
312
+ }
313
+ remaining() {
314
+ const now = this.clock.now();
315
+ if (now >= this.deadline && this.allowResume && !this.resumed && this.clock.suspendCount > this.suspendCount && this.clock.lastSuspendEnd >= this.deadline) {
316
+ this.resumed = true;
317
+ this.deadline = now + RESUME_ALLOWANCE_MS;
318
+ }
319
+ return Math.max(0, this.deadline - now);
320
+ }
321
+ }
322
+ var SUSPEND_SKEW_MS = 1000, RESUME_ALLOWANCE_MS = 1e4, DEADLINE_POLL_MS = 250;
323
+
266
324
  // ../aft-bridge/dist/bash-hints.js
325
+ function abortableSleep(ms, signal) {
326
+ if (signal?.aborted)
327
+ return Promise.resolve();
328
+ return new Promise((resolve) => {
329
+ const done = () => {
330
+ clearTimeout(timer);
331
+ signal?.removeEventListener("abort", done);
332
+ resolve();
333
+ };
334
+ const timer = setTimeout(done, ms);
335
+ signal?.addEventListener("abort", done, { once: true });
336
+ });
337
+ }
338
+ var watchClock;
267
339
  var init_bash_hints = __esm(() => {
268
340
  init_bash_format();
269
341
  init_path_display();
342
+ watchClock = {
343
+ source: new WaitClock,
344
+ now: () => watchClock.source.now(),
345
+ sleep: abortableSleep
346
+ };
270
347
  });
271
348
 
272
349
  // ../aft-bridge/dist/test-child-environment.js
@@ -882,6 +959,7 @@ var init_bridge = __esm(() => {
882
959
  binaryFingerprintAsyncReader = binaryContentHash;
883
960
  SYSTEM_CLOCK = {
884
961
  now: () => Date.now(),
962
+ awakeNow: () => performance.now(),
885
963
  setTimeout: (callback, ms) => setTimeout(callback, ms),
886
964
  clearTimeout: (timer) => clearTimeout(timer)
887
965
  };
@@ -961,6 +1039,7 @@ var init_bridge = __esm(() => {
961
1039
  hangWindowMinMs;
962
1040
  hangProbeTimeoutMs;
963
1041
  clock;
1042
+ waitClock;
964
1043
  lateShellReplyGraceMs;
965
1044
  maxRestarts;
966
1045
  configured = false;
@@ -997,6 +1076,7 @@ var init_bridge = __esm(() => {
997
1076
  this.hangWindowMinMs = options?.hangWindowMinMs ?? DEFAULT_MIN_HANG_WINDOW_MS;
998
1077
  this.hangProbeTimeoutMs = options?.hangProbeTimeoutMs ?? DEFAULT_HANG_PROBE_TIMEOUT_MS;
999
1078
  this.clock = options?.clock ?? SYSTEM_CLOCK;
1079
+ this.waitClock = new WaitClock(() => this.clock.now(), () => this.clock.awakeNow?.() ?? this.clock.now());
1000
1080
  this.lateShellReplyGraceMs = options?.lateShellReplyGraceMs ?? DEFAULT_LATE_SHELL_REPLY_GRACE_MS;
1001
1081
  this.maxRestarts = options?.maxRestarts ?? 3;
1002
1082
  this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
@@ -1363,11 +1443,17 @@ var init_bridge = __esm(() => {
1363
1443
  const keepBridgeOnTimeout = passive || command === "bash_drain_completions" || options?.keepBridgeOnTimeout === true;
1364
1444
  const child = this.process;
1365
1445
  const response = await new Promise((resolve, reject) => {
1366
- const timer = this.clock.setTimeout(() => {
1446
+ const deadline = new RequestDeadline(this.waitClock, effectiveTimeoutMs);
1447
+ const expire = () => {
1367
1448
  const entry = this.pending.get(id);
1368
1449
  if (!entry)
1369
1450
  return;
1370
- if (keepBridgeOnTimeout && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
1451
+ const remaining = deadline.remaining();
1452
+ if (remaining > 0) {
1453
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
1454
+ return;
1455
+ }
1456
+ if (keepBridgeOnTimeout && !deadline.resumed && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
1371
1457
  return;
1372
1458
  }
1373
1459
  this.pending.delete(id);
@@ -1380,11 +1466,12 @@ var init_bridge = __esm(() => {
1380
1466
  this.warnVia(timeoutMsg);
1381
1467
  }
1382
1468
  if (keepBridgeOnTimeout) {
1383
- entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
1469
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`, deadline.resumed && SHELL_TASK_COMMANDS.has(command) ? { kind: "unknown" } : undefined));
1384
1470
  return;
1385
1471
  }
1386
1472
  entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry. A timed-out mutation may still complete; inspect before repeating it.`));
1387
- }, effectiveTimeoutMs);
1473
+ };
1474
+ const timer = this.clock.setTimeout(expire, Math.min(effectiveTimeoutMs, DEADLINE_POLL_MS));
1388
1475
  const entry = {
1389
1476
  resolve,
1390
1477
  reject,
@@ -1940,14 +2027,21 @@ var init_bridge = __esm(() => {
1940
2027
  };
1941
2028
  entry.resolve = (response) => settle(shellOutcomeFromLateReply(response));
1942
2029
  this.clock.clearTimeout(entry.timer);
1943
- entry.timer = this.clock.setTimeout(() => {
2030
+ const deadline = new RequestDeadline(this.waitClock, graceMs, false);
2031
+ const expire = () => {
1944
2032
  if (this.pending.get(id) !== entry)
1945
2033
  return;
2034
+ const remaining = deadline.remaining();
2035
+ if (remaining > 0) {
2036
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
2037
+ return;
2038
+ }
1946
2039
  this.pending.delete(id);
1947
2040
  entry.onSettled?.();
1948
2041
  this.warnVia(`Request "${entry.command}" (id=${id}) got no reply within ${graceMs}ms after its deadline`);
1949
2042
  settle({ kind: "unknown" });
1950
- }, graceMs);
2043
+ };
2044
+ entry.timer = this.clock.setTimeout(expire, Math.min(graceMs, DEADLINE_POLL_MS));
1951
2045
  return true;
1952
2046
  }
1953
2047
  accountForBashTaskResponse(command, response) {
@@ -2840,10 +2934,11 @@ var init_durable_log = __esm(() => {
2840
2934
  init_storage_paths();
2841
2935
  DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
2842
2936
  });
2843
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/envelope.js
2844
- var MAX_FRAME_BODY_LEN, FrameType, FRAME_TYPE_MAX, Priority, AdmissionClass;
2937
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/envelope.js
2938
+ var MAX_FRAME_BODY_LEN, MAX_DELIVERED_FRAME_LEN, FrameType, FRAME_TYPE_MAX, Priority, AdmissionClass;
2845
2939
  var init_envelope = __esm(() => {
2846
2940
  MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
2941
+ MAX_DELIVERED_FRAME_LEN = 4 * 1024 * 1024;
2847
2942
  (function(FrameType) {
2848
2943
  FrameType[FrameType["Request"] = 0] = "Request";
2849
2944
  FrameType[FrameType["Response"] = 1] = "Response";
@@ -2871,7 +2966,7 @@ var init_envelope = __esm(() => {
2871
2966
  })(AdmissionClass || (AdmissionClass = {}));
2872
2967
  });
2873
2968
 
2874
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
2969
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/socket.js
2875
2970
  var WRITE_BORROWED, WRITE_TRACKED_BORROWED;
2876
2971
  var init_socket = __esm(() => {
2877
2972
  init_envelope();
@@ -2879,17 +2974,17 @@ var init_socket = __esm(() => {
2879
2974
  WRITE_TRACKED_BORROWED = Symbol("subc.socket.writeTrackedBorrowed");
2880
2975
  });
2881
2976
 
2882
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
2977
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/auth.js
2883
2978
  var init_auth = __esm(() => {
2884
2979
  init_socket();
2885
2980
  });
2886
2981
 
2887
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/connection-file.js
2982
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/connection-file.js
2888
2983
  var init_connection_file = __esm(() => {
2889
2984
  init_envelope();
2890
2985
  });
2891
2986
 
2892
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
2987
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
2893
2988
  var PROCESS_CELL_KEY, U64_MAX, I32_MAX, RedactedLaunchNonce;
2894
2989
  var init_launch_nonce = __esm(() => {
2895
2990
  PROCESS_CELL_KEY = Symbol.for("@cortexkit/subc-client/launch-nonce/v1");
@@ -2914,15 +3009,14 @@ var init_launch_nonce = __esm(() => {
2914
3009
  }
2915
3010
  };
2916
3011
  });
2917
-
2918
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/route-handle.js
3012
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/route-handle.js
2919
3013
  var connectionToken, reverseRequests;
2920
3014
  var init_route_handle = __esm(() => {
2921
3015
  connectionToken = new WeakMap;
2922
3016
  reverseRequests = new WeakMap;
2923
3017
  });
2924
3018
 
2925
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
3019
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/client.js
2926
3020
  import { debuglog } from "node:util";
2927
3021
  var debug, EMPTY_BODY, errorFrameFlags, SCOPE_ROUTE_CLOSE_REASONS, ROUTE_OPEN_ADMISSION_PRESSURE_REASONS;
2928
3022
  var init_client = __esm(() => {
@@ -2944,8 +3038,7 @@ var init_client = __esm(() => {
2944
3038
  ]);
2945
3039
  ROUTE_OPEN_ADMISSION_PRESSURE_REASONS = new Set(["open_admission_full", "target_binds_full"]);
2946
3040
  });
2947
-
2948
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/provider.js
3041
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/provider.js
2949
3042
  var init_provider = __esm(() => {
2950
3043
  init_auth();
2951
3044
  init_client();
@@ -2956,7 +3049,7 @@ var init_provider = __esm(() => {
2956
3049
  init_socket();
2957
3050
  });
2958
3051
 
2959
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/index.js
3052
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/index.js
2960
3053
  var init_dist = __esm(() => {
2961
3054
  init_client();
2962
3055
  init_route_handle();
@@ -3562,8 +3655,9 @@ function translateDataLayerPaths(map, prefix, out) {
3562
3655
  }
3563
3656
  }
3564
3657
  for (const path of REMOVED_DATA_LAYER_PATHS) {
3565
- if (hasPath(map, path))
3566
- out.removedKeys.push(`${prefix}${path}`);
3658
+ const [container, leaf] = path.split(".");
3659
+ if (container && leaf && takeNested(map, container, leaf).found)
3660
+ recordRetired(out, prefix, path);
3567
3661
  }
3568
3662
  }
3569
3663
  function dropRemovedDataLayerKeys(map) {
@@ -3815,7 +3909,6 @@ function translateConfigDocument(map, tier) {
3815
3909
  warnings: [],
3816
3910
  legacyInput: false,
3817
3911
  retiredKeys: [],
3818
- removedKeys: [],
3819
3912
  retiredEnabledFalse: false
3820
3913
  };
3821
3914
  translateBlock(map, tier === "user", "base", out);
@@ -3826,7 +3919,6 @@ function translateConfigDocument(map, tier) {
3826
3919
  }
3827
3920
  }
3828
3921
  out.retiredKeys = sortedUnique(out.retiredKeys);
3829
- out.removedKeys = sortedUnique(out.removedKeys);
3830
3922
  return out;
3831
3923
  }
3832
3924
  function canonicalJson(value) {
@@ -18498,8 +18590,22 @@ function safeRealpath(p) {
18498
18590
  function looksLikeSecret(value) {
18499
18591
  return value.length >= 16 && /^[A-Za-z0-9_\-.~+/=]+$/.test(value) && /[0-9]/.test(value) && /[A-Z]/.test(value) && /[a-z]/.test(value);
18500
18592
  }
18593
+ function looksLikeAssignmentSecret(value) {
18594
+ return !["/", "~/", "./", "../"].some((prefix) => value.startsWith(prefix)) && !value.includes("://") && looksLikeSecret(value);
18595
+ }
18501
18596
  function isSensitiveKeyName(keyName) {
18502
- return SENSITIVE_KEY_WORD.test(keyName) || SEGMENTED_KEY_WORD.test(keyName) || CAMEL_CASE_KEY_WORD.test(keyName);
18597
+ return keyName !== "PWD" && (SENSITIVE_KEY_WORD.test(keyName) || SEGMENTED_KEY_WORD.test(keyName) || CAMEL_CASE_KEY_WORD.test(keyName) || SHORT_PASSWORD_NAME.test(keyName));
18598
+ }
18599
+ function shouldRedactAssignment(prefix, keyName, value, offset, content, commandContext) {
18600
+ if (keyName.toLowerCase() === "key" && /[?&]/.test(content[offset - 1] ?? "")) {
18601
+ return false;
18602
+ }
18603
+ return isSensitiveKeyName(keyName) || commandContext && prefix.trimEnd().endsWith("=") && looksLikeAssignmentSecret(value);
18604
+ }
18605
+ function redactAssignments(content, commandContext) {
18606
+ let sanitized = content.replace(unquotedSensitiveKeyValuePattern, (match, prefix, keyName, valueQuote, value, offset, input) => shouldRedactAssignment(prefix, keyName, value, offset, input, commandContext) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18607
+ sanitized = sanitized.replace(bareSensitiveKeyValuePattern, (match, prefix, keyName, value, offset, input) => shouldRedactAssignment(prefix, keyName, value, offset, input, commandContext) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18608
+ return sanitized;
18503
18609
  }
18504
18610
  function redactSecrets(content) {
18505
18611
  let sanitized = content;
@@ -18512,64 +18618,179 @@ function redactSecrets(content) {
18512
18618
  sanitized = sanitized.replace(CREDENTIAL_QUERY_PATTERN, `$1${SECRET_PLACEHOLDER}`);
18513
18619
  sanitized = sanitized.replace(KEY_QUERY_PATTERN, (match, prefix, value) => looksLikeSecret(value) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18514
18620
  sanitized = sanitized.replace(quotedSensitiveKeyValuePattern, (match, prefix, _keyQuote, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18515
- sanitized = sanitized.replace(unquotedSensitiveKeyValuePattern, (match, prefix, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18516
- sanitized = sanitized.replace(bareSensitiveKeyValuePattern, (match, prefix, keyName) => isSensitiveKeyName(keyName) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18621
+ sanitized = redactAssignments(sanitized, false);
18517
18622
  sanitized = sanitized.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^@\s/?#]+@/gi, `$1${URL_CREDENTIAL_PLACEHOLDER}@`);
18518
18623
  sanitized = sanitized.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "<EMAIL>");
18624
+ sanitized = sanitized.replace(/^([^\r\n]*\bbash spawned task=[^\r\n]*? cmd=)([^\r\n]*)/gm, (_match, prefix, command) => `${prefix}${redactAssignments(command, true)}`);
18519
18625
  return sanitized;
18520
18626
  }
18521
- function sanitizeContent(content) {
18522
- const username = userInfo().username;
18523
- const home = homedir17();
18627
+ function sanitizeContent(content, context = {}) {
18628
+ const username = context.username ?? userInfo().username;
18629
+ const home = context.home ?? homedir17();
18524
18630
  let sanitized = redactSecrets(content);
18525
18631
  const cwd = process.cwd();
18526
- for (const candidate of new Set([cwd, safeRealpath(cwd)])) {
18527
- if (candidate && candidate !== "/" && candidate !== home) {
18528
- sanitized = sanitized.replace(new RegExp(escapeRegex(candidate), "g"), "<PROJECT>");
18632
+ const roots = new Set([
18633
+ cwd,
18634
+ safeRealpath(cwd),
18635
+ ...(context.projectRoots ?? []).flatMap((root) => root.startsWith("~/") ? [root, home + root.slice(1)] : [root]),
18636
+ ...reportProjectRoots(decodedView(content).text)
18637
+ ]);
18638
+ const candidates = [...roots].filter((root) => Boolean(root));
18639
+ for (const root of candidates) {
18640
+ const real = safeRealpath(root);
18641
+ if (real)
18642
+ roots.add(real);
18643
+ }
18644
+ for (const root of [...roots].filter((root) => Boolean(root)).sort((a, b) => b.length - a.length)) {
18645
+ if (/^(?:\/|[A-Za-z]:[/\\]|~\/)/.test(root) && root !== home && !/^(?:[/\\]|[A-Za-z]:[/\\])$/.test(root)) {
18646
+ sanitized = replacePath(sanitized, root, "<PROJECT>");
18647
+ }
18648
+ }
18649
+ if (home && home !== "/")
18650
+ sanitized = replacePath(sanitized, home, "~");
18651
+ sanitized = replaceDecoded(sanitized, /(?:(?<![\p{L}\p{N}_/.-])|(?<=file:\/\/)|(?<=file:\/\/\/))(?:\/(?:home|Users)\/|[A-Za-z]:\/Users\/)([^/\s"'<>`,;:?#&()[\]{}=]+)/gu, (match) => ({ offset: match[0].length - match[1].length, value: "<USER>" }));
18652
+ return redactUsername(sanitized, username);
18653
+ }
18654
+ function decodedView(content) {
18655
+ let units = Array.from({ length: content.length }, (_, i) => ({
18656
+ char: content[i],
18657
+ start: i,
18658
+ end: i + 1
18659
+ }));
18660
+ const decoded = [];
18661
+ for (let i = 0;i < units.length; ) {
18662
+ const byte = content.slice(i, i + 3);
18663
+ const first = /^%[\da-f]{2}$/i.test(byte) ? Number.parseInt(byte.slice(1), 16) : -1;
18664
+ const width = first < 128 ? 1 : first >= 194 && first <= 223 ? 2 : first <= 239 ? 3 : 4;
18665
+ const spelling = content.slice(i, i + width * 3);
18666
+ if (first >= 0 && /^(?:%[\da-f]{2})+$/i.test(spelling)) {
18667
+ try {
18668
+ const value = decodeURIComponent(spelling);
18669
+ for (const char of value.split(""))
18670
+ decoded.push({ char, start: i, end: i + spelling.length });
18671
+ i += spelling.length;
18672
+ continue;
18673
+ } catch {}
18529
18674
  }
18675
+ decoded.push(units[i++]);
18530
18676
  }
18531
- if (home) {
18532
- sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
18677
+ units = [];
18678
+ for (let i = 0;i < decoded.length; i++) {
18679
+ const unit = decoded[i];
18680
+ const next = decoded[i + 1];
18681
+ if (unit.char === "\\" && next && (next.char === "/" || next.char === "\\")) {
18682
+ units.push({ char: "/", start: unit.start, end: next.end });
18683
+ i++;
18684
+ } else
18685
+ units.push({ ...unit, char: unit.char === "\\" ? "/" : unit.char });
18533
18686
  }
18534
- sanitized = sanitized.replace(/\/Users\/[^/\s"']+/g, "/Users/<USER>");
18535
- sanitized = sanitized.replace(/\/home\/[^/\s"']+/g, "/home/<USER>");
18536
- sanitized = sanitized.replace(/([A-Za-z]:\\\\Users\\\\)[^\\\\"'\s]+/g, "$1<USER>");
18537
- sanitized = sanitized.replace(/([A-Za-z]:\\Users\\)[^\\"'\s]+/g, "$1<USER>");
18538
- sanitized = sanitized.replace(/([A-Za-z]:\/Users\/)[^/\s"']+/g, "$1<USER>");
18539
- if (username) {
18540
- sanitized = redactUsername(sanitized, username);
18687
+ return { text: units.map((unit) => unit.char).join(""), units };
18688
+ }
18689
+ function replaceDecoded(content, pattern, replacement) {
18690
+ const view = decodedView(content);
18691
+ let result = "";
18692
+ let cursor = 0;
18693
+ for (const match of view.text.matchAll(pattern)) {
18694
+ const change = replacement(match);
18695
+ if (!change)
18696
+ continue;
18697
+ const { offset, value } = change;
18698
+ const start = view.units[match.index + offset].start;
18699
+ const end = view.units[match.index + match[0].length - 1].end;
18700
+ result += content.slice(cursor, start) + value;
18701
+ cursor = end;
18541
18702
  }
18542
- return sanitized;
18703
+ return result + content.slice(cursor);
18543
18704
  }
18544
- function sanitizeValue(value) {
18705
+ function replacePath(content, path, value) {
18706
+ const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "");
18707
+ if (!normalized)
18708
+ return content;
18709
+ const pattern = new RegExp(`(?:(?<![\\p{L}\\p{N}_/.-])|(?<=file://)|(?<=file:///))${escapeRegex(normalized)}(?=$|[/\\s"'<>\`,;:)\\]?#&])`, /^[A-Za-z]:/.test(path) ? "giu" : "gu");
18710
+ return replaceDecoded(content, pattern, () => ({ offset: 0, value }));
18711
+ }
18712
+ function reportProjectRoots(content) {
18713
+ return [
18714
+ ...content.matchAll(/(?:project root set: |watcher started: )([^\r\n]+)/g),
18715
+ ...content.matchAll(/(?:\broot=|"(?:projectRoot|project_root|canonical_root)"\s*:\s*)("[^"]+"|'[^']+'|[^\s"'<>`,;]+)/g)
18716
+ ].map((match) => match[1].trim().replace(/^["']|["']$/g, "")).filter((root) => /^(?:\/|[A-Za-z]:\/)/.test(root));
18717
+ }
18718
+ function sanitizeValue(value, context = {}) {
18719
+ const scoped = {
18720
+ ...context,
18721
+ projectRoots: [...context.projectRoots ?? [], ...projectRootsFromValue(value)]
18722
+ };
18723
+ return sanitizeNestedValue(value, scoped);
18724
+ }
18725
+ function sanitizeNestedValue(value, context) {
18545
18726
  if (typeof value === "string") {
18546
- return sanitizeContent(value);
18727
+ return sanitizeContent(value, context);
18547
18728
  }
18548
18729
  if (Array.isArray(value)) {
18549
- return value.map((entry) => sanitizeValue(entry));
18730
+ return value.map((entry) => sanitizeNestedValue(entry, context));
18550
18731
  }
18551
18732
  if (value && typeof value === "object") {
18552
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeValue(entry)]));
18733
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeNestedValue(entry, context)]));
18553
18734
  }
18554
18735
  return value;
18555
18736
  }
18737
+ function projectRootsFromValue(value) {
18738
+ if (!value || typeof value !== "object")
18739
+ return [];
18740
+ if (Array.isArray(value))
18741
+ return value.flatMap(projectRootsFromValue);
18742
+ const object = value;
18743
+ const roots = [object.projectRoot, object.project_root, object.canonical_root].filter((root) => typeof root === "string");
18744
+ const index = object.index;
18745
+ if (index && Array.isArray(index.roots)) {
18746
+ for (const root of index.roots)
18747
+ if (root && typeof root.path === "string")
18748
+ roots.push(root.path);
18749
+ }
18750
+ return [...roots, ...Object.values(object).flatMap(projectRootsFromValue)];
18751
+ }
18556
18752
  function redactUsername(content, username) {
18557
18753
  if (!username)
18558
18754
  return content;
18559
- return content.replace(new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegex(username)}(?![\\p{L}\\p{N}_])`, "gu"), "<USER>");
18755
+ if (!COMMON_ACCOUNT_NAMES.has(username.toLowerCase())) {
18756
+ return replaceDecoded(content, new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegex(username)}(?![\\p{L}\\p{N}_])`, "gu"), () => ({ offset: 0, value: "<USER>" }));
18757
+ }
18758
+ return replaceDecoded(content, new RegExp(`(?<![\\p{L}\\p{N}_/.-])~${escapeRegex(username)}(?![\\p{L}\\p{N}_])|(?<![\\p{L}\\p{N}_/.-])${escapeRegex(username)}(?=@[\\p{L}\\p{N}])`, "gu"), (match) => ({ offset: match[0].startsWith("~") ? 1 : 0, value: "<USER>" }));
18560
18759
  }
18561
- var SECRET_PLACEHOLDER = "<REDACTED_SECRET>", URL_CREDENTIAL_PLACEHOLDER = "***", KEY_NAME = "[A-Za-z_][A-Za-z0-9_.-]*", SENSITIVE_KEY_WORD, SEGMENTED_KEY_WORD, CAMEL_CASE_KEY_WORD, JWT_PATTERN, AWS_ACCESS_KEY_ID_PATTERN, CREDENTIAL_QUERY_PATTERN, KEY_QUERY_PATTERN, quotedSensitiveKeyValuePattern, unquotedSensitiveKeyValuePattern, bareSensitiveKeyValuePattern;
18760
+ var SECRET_PLACEHOLDER = "<REDACTED_SECRET>", URL_CREDENTIAL_PLACEHOLDER = "***", KEY_NAME = "[A-Za-z_][A-Za-z0-9_.-]*", SENSITIVE_KEY_WORD, SEGMENTED_KEY_WORD, CAMEL_CASE_KEY_WORD, SHORT_PASSWORD_NAME, JWT_PATTERN, AWS_ACCESS_KEY_ID_PATTERN, CREDENTIAL_QUERY_PATTERN, KEY_QUERY_PATTERN, quotedSensitiveKeyValuePattern, unquotedSensitiveKeyValuePattern, bareSensitiveKeyValuePattern, COMMON_ACCOUNT_NAMES;
18562
18761
  var init_sanitize = __esm(() => {
18563
18762
  SENSITIVE_KEY_WORD = /(?:token|password|secret|api[_-]?key|passwd|pwd|credential)/i;
18564
18763
  SEGMENTED_KEY_WORD = /(?:^|[_.-])key(?:$|[_.-])/i;
18565
18764
  CAMEL_CASE_KEY_WORD = /[a-z0-9]Key(?:$|[A-Z_.-])/;
18765
+ SHORT_PASSWORD_NAME = /(?:^|_)(?:pw|pass)$/i;
18566
18766
  JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
18567
18767
  AWS_ACCESS_KEY_ID_PATTERN = /\b(?:AKIA|ASIA|AGPA|AIDA|AROA)[A-Z0-9]{16}\b/g;
18568
18768
  CREDENTIAL_QUERY_PATTERN = /([?&](?:[a-z0-9_-]*token|api[_-]?key|apikey|[a-z0-9_-]*secret|password|passwd|pwd)=)[^&\s#"'<>]+/gi;
18569
18769
  KEY_QUERY_PATTERN = /([?&]key=)([^&\s#"'<>]+)/gi;
18570
18770
  quotedSensitiveKeyValuePattern = new RegExp(String.raw`((['"])(${KEY_NAME})\2[^\S\r\n]*:[^\S\r\n]*)(['"])([^'"\r\n]+)\4`, "gi");
18571
18771
  unquotedSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)(['"])([^'"\r\n]+)\3`, "gi");
18572
- bareSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)([^\s,;&'"]+)`, "gi");
18772
+ bareSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)([^\s,;&|<>()'"]+)`, "gi");
18773
+ COMMON_ACCOUNT_NAMES = new Set([
18774
+ "root",
18775
+ "admin",
18776
+ "administrator",
18777
+ "user",
18778
+ "dev",
18779
+ "developer",
18780
+ "test",
18781
+ "guest",
18782
+ "ubuntu",
18783
+ "debian",
18784
+ "pi",
18785
+ "runner",
18786
+ "vagrant",
18787
+ "ec2-user",
18788
+ "centos",
18789
+ "git",
18790
+ "docker",
18791
+ "build",
18792
+ "ci"
18793
+ ]);
18573
18794
  });
18574
18795
 
18575
18796
  // src/lib/bridge-tool-failures.ts
@@ -18871,7 +19092,7 @@ function previewConfigMigration(targets) {
18871
19092
  continue;
18872
19093
  const translated = structuredClone(raw);
18873
19094
  const translation = translateConfigDocument(translated, target.tier);
18874
- if (!translation.legacyInput && translation.removedKeys.length === 0)
19095
+ if (!translation.legacyInput)
18875
19096
  continue;
18876
19097
  dropRemovedDataLayerKeys(translated);
18877
19098
  const changes = diffConfig(raw, translated);
@@ -21838,6 +22059,13 @@ async function runIssueFlow(argv) {
21838
22059
  const selectedSession = await promptForIssueSession(adapters[0]);
21839
22060
  const selectedBareSessionId = selectedSession?.id.replace(/^ses_/, "") ?? null;
21840
22061
  const report = await collectDiagnostics(adapters);
22062
+ const sanitizeContext = {
22063
+ projectRoots: [
22064
+ ...adapters.flatMap((adapter) => projectRootsFromValue(readJsoncFile(adapter.detectConfigPaths().aftConfig).value)),
22065
+ ...adapters.flatMap((adapter) => projectRootsFromValue(listRecentSessions(adapter))),
22066
+ ...projectRootsFromValue(report)
22067
+ ]
22068
+ };
21841
22069
  const logResults = adapters.flatMap((adapter) => {
21842
22070
  const path = adapter.getLogFile();
21843
22071
  const { logText, errors, notice } = collectIssueLog(path, selectedSession);
@@ -21867,7 +22095,7 @@ ${engine.logText || "<no log output>"}
21867
22095
  });
21868
22096
  const logSections = logResults.map((result) => result.section).join(`
21869
22097
  `);
21870
- const recentErrorLines = extractRecentErrors(logResults.map((result) => sanitizeContent(result.errors)).join(`
22098
+ const recentErrorLines = extractRecentErrors(logResults.map((result) => sanitizeContent(result.errors, sanitizeContext)).join(`
21871
22099
  `), 20);
21872
22100
  const recentErrorsSection = recentErrorLines.length === 0 ? "_No error-shaped log lines found in recent history._" : ["```", recentErrorLines.join(`
21873
22101
  `), "```"].join(`
@@ -21897,7 +22125,7 @@ ${engine.logText || "<no log output>"}
21897
22125
  "_Usernames and home paths have been stripped from this report._"
21898
22126
  ].join(`
21899
22127
  `);
21900
- const body = capBodyToGithubLimit(sanitizeContent(rawBody));
22128
+ const body = capBodyToGithubLimit(sanitizeContent(rawBody, sanitizeContext));
21901
22129
  const reviewFile = writeIssueReviewFile(body);
21902
22130
  if (!reviewFile) {
21903
22131
  outro("Done — could not write the issue report.");
@@ -21922,7 +22150,7 @@ https://github.com/cortexkit/aft/issues/new and paste the contents of ${outPath}
21922
22150
  outro("Done.");
21923
22151
  return 1;
21924
22152
  }
21925
- const finalBody = capBodyToGithubLimit(sanitizeContent(reviewedBody));
22153
+ const finalBody = capBodyToGithubLimit(sanitizeContent(reviewedBody, sanitizeContext));
21926
22154
  const finalTitle = deriveIssueTitleFromBody(finalBody);
21927
22155
  if (isGhInstalled()) {
21928
22156
  log2.info("Opening GitHub issue via `gh`…");
@@ -6,16 +6,22 @@ export declare function escapeRegex(value: string): string;
6
6
  */
7
7
  export declare function redactSecrets(content: string): string;
8
8
  /**
9
- * Strip personally identifiable path segments and usernames from arbitrary
10
- * text. Used on log contents, diagnostic JSON blocks, and the final issue body
11
- * so reports never leak usernames or home-directory paths.
9
+ * Redact known project/home paths and identifying usernames in logs,
10
+ * diagnostic JSON blocks and the final issue body.
12
11
  */
13
- export declare function sanitizeContent(content: string): string;
12
+ export declare function sanitizeContent(content: string, context?: SanitizeContext): string;
13
+ export interface SanitizeContext {
14
+ projectRoots?: readonly string[];
15
+ home?: string;
16
+ username?: string;
17
+ }
14
18
  /**
15
19
  * Recursively sanitize any value by deep-walking objects/arrays. Strings are
16
20
  * passed through `sanitizeContent`; other primitives are preserved.
17
21
  */
18
- export declare function sanitizeValue(value: unknown): unknown;
19
- /** Redact a username only as a whole word or path segment, never inside identifiers. */
22
+ export declare function sanitizeValue(value: unknown, context?: SanitizeContext): unknown;
23
+ /** Collect only declared root fields, never arbitrary file/config/cache paths. */
24
+ export declare function projectRootsFromValue(value: unknown): string[];
25
+ /** Mask distinctive usernames as whole words; common names only in identity positions. */
20
26
  export declare function redactUsername(content: string, username: string): string;
21
27
  //# sourceMappingURL=sanitize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAiED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CrD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkCvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAarD;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMxE"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAiID;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4CrD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAwCtF;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAkFD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAMpF;AAiBD,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAa9D;AA0BD,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiBxE"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cortexkit/aft",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "type": "module",
5
- "description": "Unified CLI for Agent File Tools (AFT) — setup, one-shot index snapshots, doctor, and diagnostics across supported agent harnesses (OpenCode, Pi)",
5
+ "description": "Unified CLI for Agent Foundational Toolkit (AFT) — setup, one-shot index snapshots, doctor, and diagnostics across supported agent harnesses (OpenCode, Pi)",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@clack/core": "^1.4.2",
27
27
  "@clack/prompts": "^1.6.0",
28
- "@cortexkit/aft-bridge": "0.64.0",
28
+ "@cortexkit/aft-bridge": "0.65.0",
29
29
  "comment-json": "^4.6.2"
30
30
  },
31
31
  "devDependencies": {