agent-first-data 0.26.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/format.js CHANGED
@@ -12,8 +12,9 @@
12
12
  * precision integers, high-precision floats, exponent forms, "-0") survives
13
13
  * decode. Parsed numbers become LosslessNumber internally; every JSON/YAML
14
14
  * rendering path here treats it as an opaque scalar leaf (never destructured
15
- * as a plain object) and re-emits its exact literal text. This is internal —
16
- * LosslessNumber is never part of the public JsonValue type or exports.
15
+ * as a plain object) and re-emits its exact literal text. Native bigint values
16
+ * are also supported so callers can render integers beyond Number's safe
17
+ * range without first corrupting them.
17
18
  */
18
19
  import { parse as losslessParse, stringify as losslessStringify, isLosslessNumber } from "lossless-json";
19
20
  // ═══════════════════════════════════════════
@@ -96,10 +97,7 @@ export class JsonErrorBuilder {
96
97
  return this;
97
98
  }
98
99
  hint(value) {
99
- if (!value || value === "") {
100
- this.issues.push("hint must be a non-empty string");
101
- }
102
- else {
100
+ if (value && value !== "") {
103
101
  this.hintValue = value;
104
102
  }
105
103
  return this;
@@ -174,6 +172,13 @@ export class JsonErrorBuilder {
174
172
  if (this.hintValue !== undefined)
175
173
  error.hint = this.hintValue;
176
174
  const m = { kind: "error", error, trace: this.traceValue };
175
+ try {
176
+ losslessStringify(m);
177
+ }
178
+ catch (error) {
179
+ const issue = `event is not JSON serializable: ${error instanceof Error ? error.message : String(error)}`;
180
+ throw new EventBuildError(`Failed to build error event: ${issue}`, [issue]);
181
+ }
177
182
  return new Event(m);
178
183
  }
179
184
  }
@@ -380,13 +385,35 @@ export function decodeProtocolEvent(text) {
380
385
  // ═══════════════════════════════════════════
381
386
  // Internal: Output Formatters (called by render() in cli.ts)
382
387
  // ═══════════════════════════════════════════
383
- /** Which fields a redaction pass scrubs. The default is `All`. */
388
+ /**
389
+ * Which fields a redaction pass scrubs. The default is `All`.
390
+ *
391
+ * The policy selects a *scope* inside a structured value. A command line
392
+ * (`redactArgv`) and a bare URL string (`redactUrlSecrets`) have no
393
+ * result/trace split to scope to, so on those two paths `TraceOnly` redacts in
394
+ * full like `All`, and only `Off` turns redaction off.
395
+ */
384
396
  export var RedactionPolicy;
385
397
  (function (RedactionPolicy) {
386
398
  RedactionPolicy["All"] = "All";
387
399
  RedactionPolicy["TraceOnly"] = "TraceOnly";
388
400
  RedactionPolicy["Off"] = "Off";
389
401
  })(RedactionPolicy || (RedactionPolicy = {}));
402
+ /**
403
+ * Whether a policy redacts an input that carries no scope of its own — a
404
+ * command line or a single URL string, as opposed to a JSON value with a
405
+ * `trace` object to narrow to.
406
+ *
407
+ * `TraceOnly` narrows *where* redaction applies within a value; it does not
408
+ * weaken redaction. A standalone argv or URL has no non-`trace` half to leave
409
+ * alone — and both are diagnostic material by construction, the very thing
410
+ * `TraceOnly` scrubs — so `TraceOnly` redacts them in full, exactly like `All`.
411
+ * Only `Off`, the caller explicitly asking for raw output, disables redaction,
412
+ * and it does so on all three paths alike.
413
+ */
414
+ function redactsUnscopedInput(policy) {
415
+ return policy !== RedactionPolicy.Off;
416
+ }
390
417
  export var PlainStyle;
391
418
  (function (PlainStyle) {
392
419
  PlainStyle["Readable"] = "Readable";
@@ -441,6 +468,9 @@ export function formatYamlValue(value, options = {}) {
441
468
  export function formatPlainValue(value, options = {}) {
442
469
  const unwrapped = value instanceof Event ? value.toJSON() : value;
443
470
  const redacted = redactedValue(unwrapped, options.redaction ?? {});
471
+ if (!isObject(redacted)) {
472
+ return plainScalar(redacted);
473
+ }
444
474
  const pairs = [];
445
475
  if (options.style === PlainStyle.Raw) {
446
476
  collectPlainPairsRaw(redacted, "", pairs);
@@ -448,6 +478,9 @@ export function formatPlainValue(value, options = {}) {
448
478
  else {
449
479
  collectPlainPairs(redacted, "", pairs);
450
480
  }
481
+ if (pairs.length === 0) {
482
+ return "{}";
483
+ }
451
484
  pairs.sort(([a], [b]) => jcsCompare(a, b));
452
485
  return pairs
453
486
  .map(([k, v]) => `${quoteLogfmtKey(k)}=${quoteLogfmtValue(v)}`)
@@ -466,13 +499,21 @@ export function redactedValue(value, options) {
466
499
  * Redact secret components of a single URL string.
467
500
  *
468
501
  * A query parameter is redacted iff its (form-decoded) name ends in
469
- * _secret/_SECRET or matches an exact entry in secretNames. The userinfo
470
- * password (scheme://user:pass@host) is always redacted as a structural rule.
471
- * Only the secret spans are replaced with "***"; every other byte is preserved.
472
- * A string that is not a single, whitespace-free, scheme-prefixed URL
473
- * (including a URL embedded in surrounding prose) is returned unchanged.
502
+ * _secret/_SECRET or matches an exact entry in secretNames. A fragment written
503
+ * in the same `k=v&k=v` shape — how an OAuth implicit-flow response carries its
504
+ * token — is redacted by that same rule; any other fragment passes through
505
+ * byte-for-byte. The userinfo password (scheme://user:pass@host) is always
506
+ * redacted as a structural rule. Only the secret spans are replaced with "***";
507
+ * every other byte is preserved. A string that is not a single,
508
+ * whitespace-free, scheme-prefixed URL (including a URL embedded in surrounding
509
+ * prose) is returned unchanged.
510
+ *
511
+ * A URL is unscoped input: `RedactionPolicy.Off` returns it unchanged, every
512
+ * other policy redacts it in full.
474
513
  */
475
514
  export function redactUrlSecrets(url, options) {
515
+ if (!redactsUnscopedInput(options?.policy))
516
+ return url;
476
517
  const redacted = redactUrlInStr(url, secretNameSet(options ?? {}));
477
518
  return redacted ?? url;
478
519
  }
@@ -496,9 +537,12 @@ export function redactUrlSecrets(url, options) {
496
537
  * Intended for CLIs that record their own invocation — startup diagnostics,
497
538
  * audit trails, crash reports — where writing argv verbatim would put a
498
539
  * credential in the log.
540
+ *
541
+ * A command line is unscoped input: `RedactionPolicy.Off` returns `args`
542
+ * unchanged, every other policy redacts in full.
499
543
  */
500
544
  export function redactArgv(args, options) {
501
- if (options?.policy === RedactionPolicy.Off)
545
+ if (!redactsUnscopedInput(options?.policy))
502
546
  return [...args];
503
547
  const secretNames = secretNameSet(options ?? {});
504
548
  const out = [];
@@ -507,7 +551,7 @@ export function redactArgv(args, options) {
507
551
  if (redactNext) {
508
552
  redactNext = false;
509
553
  if (!arg.startsWith("-")) {
510
- out.push("***");
554
+ out.push(REDACTED_MARKER);
511
555
  continue;
512
556
  }
513
557
  }
@@ -517,7 +561,7 @@ export function redactArgv(args, options) {
517
561
  if (equals >= 0) {
518
562
  const name = rest.slice(0, equals);
519
563
  if (isSecretFlagName(name, secretNames)) {
520
- out.push(`--${name}=***`);
564
+ out.push(`--${name}=${REDACTED_MARKER}`);
521
565
  continue;
522
566
  }
523
567
  }
@@ -688,6 +732,21 @@ function daysInMonth(year, month) {
688
732
  function isLeapYear(year) {
689
733
  return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
690
734
  }
735
+ // ═══════════════════════════════════════════
736
+ // Secret Redaction
737
+ // ═══════════════════════════════════════════
738
+ /**
739
+ * The scalar every redacted span, value, and subtree is replaced with. Also the
740
+ * signal plain rendering reads to tell a hidden field from a live one.
741
+ */
742
+ const REDACTED_MARKER = "***";
743
+ /**
744
+ * True when a field already carries the redaction marker, i.e. redaction ran
745
+ * over it and hid the value.
746
+ */
747
+ function isRedacted(value) {
748
+ return value === REDACTED_MARKER;
749
+ }
691
750
  const DEFAULT_CONTEXT = {
692
751
  secretNames: new Set(),
693
752
  };
@@ -715,7 +774,7 @@ function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
715
774
  for (const k of Object.keys(value)) {
716
775
  const v = value[k];
717
776
  if (isSecretKey(k, context.secretNames)) {
718
- value[k] = "***";
777
+ value[k] = REDACTED_MARKER;
719
778
  }
720
779
  else if (keyHasUrlSuffix(k)) {
721
780
  if (typeof v === "string") {
@@ -790,29 +849,22 @@ function redactUrlInStr(s, secretNames) {
790
849
  const authority = rest.slice(0, authEnd);
791
850
  const remainder = rest.slice(authEnd);
792
851
  const newAuthority = redactUserinfoPassword(authority);
793
- // Query runs from the first '?' to the first '#' (or end).
794
- let newRemainder;
795
- const q = remainder.indexOf("?");
796
- if (q >= 0) {
797
- const path = remainder.slice(0, q);
798
- const qOnwards = remainder.slice(q + 1);
799
- const hash = qOnwards.indexOf("#");
800
- let query;
801
- let fragment;
802
- if (hash >= 0) {
803
- query = qOnwards.slice(0, hash);
804
- fragment = qOnwards.slice(hash);
805
- }
806
- else {
807
- query = qOnwards;
808
- fragment = "";
809
- }
810
- newRemainder = `${path}?${redactQuery(query, secretNames)}${fragment}`;
811
- }
812
- else {
813
- newRemainder = remainder;
814
- }
815
- return `${scheme}://${newAuthority}${newRemainder}`;
852
+ // `remainder` is `path[?query][#fragment]`; '#' ends the query, so split the
853
+ // fragment off first and the query out of what is left.
854
+ const hash = remainder.indexOf("#");
855
+ const beforeFragment = hash >= 0 ? remainder.slice(0, hash) : remainder;
856
+ const fragment = hash >= 0 ? remainder.slice(hash + 1) : null;
857
+ const q = beforeFragment.indexOf("?");
858
+ const newBeforeFragment = q >= 0
859
+ ? `${beforeFragment.slice(0, q)}?${redactQuery(beforeFragment.slice(q + 1), secretNames)}`
860
+ : beforeFragment;
861
+ // A fragment gets the same treatment as the query: `k=v&k=v` after the '#' is
862
+ // exactly how an OAuth implicit-flow response hands back a token, so a
863
+ // secret-named fragment parameter must not survive where the identically
864
+ // named query parameter would not. A fragment that is not in that shape has
865
+ // no '=' in its segments and passes through byte-for-byte.
866
+ const newFragment = fragment === null ? "" : `#${redactQuery(fragment, secretNames)}`;
867
+ return `${scheme}://${newAuthority}${newBeforeFragment}${newFragment}`;
816
868
  }
817
869
  function redactUrlFieldValue(s, secretNames) {
818
870
  const redacted = redactUrlInStr(s, secretNames);
@@ -830,7 +882,7 @@ function redactUrlFieldValue(s, secretNames) {
830
882
  // connection string like user:pass@host/db has no scheme anchor for the
831
883
  // surgical span logic above, so blanket redaction is the safe default.
832
884
  if (/\s/.test(s) || s.includes("@"))
833
- return "***";
885
+ return REDACTED_MARKER;
834
886
  return s;
835
887
  }
836
888
  /**
@@ -845,7 +897,7 @@ function redactUserinfoPassword(authority) {
845
897
  const colon = userinfo.indexOf(":");
846
898
  if (colon < 0)
847
899
  return authority;
848
- return `${authority.slice(0, colon)}:***${authority.slice(at)}`;
900
+ return `${authority.slice(0, colon)}:${REDACTED_MARKER}${authority.slice(at)}`;
849
901
  }
850
902
  /**
851
903
  * Redact the values of secret-named query parameters, preserving raw bytes of
@@ -861,7 +913,7 @@ function redactQuery(query, secretNames) {
861
913
  const rawKey = segment.slice(0, eq);
862
914
  const name = formDecode(rawKey);
863
915
  if (isSecretKey(name, secretNames)) {
864
- return `${rawKey}=***`;
916
+ return `${rawKey}=${REDACTED_MARKER}`;
865
917
  }
866
918
  return segment;
867
919
  })
@@ -950,7 +1002,9 @@ function isInt(value) {
950
1002
  function decimalIntText(value) {
951
1003
  if (typeof value === "string" && /^-?\d+$/.test(value))
952
1004
  return value;
953
- if (isInt(value))
1005
+ if (typeof value === "bigint")
1006
+ return value.toString();
1007
+ if (isInt(value) && Number.isSafeInteger(value))
954
1008
  return String(value);
955
1009
  if (isLosslessNumber(value)) {
956
1010
  const text = value.toString();
@@ -958,6 +1012,33 @@ function decimalIntText(value) {
958
1012
  }
959
1013
  return null;
960
1014
  }
1015
+ const MAX_I64 = 9223372036854775807n;
1016
+ function nonNegativeI64(value) {
1017
+ let text = null;
1018
+ if (typeof value === "bigint") {
1019
+ text = value.toString();
1020
+ }
1021
+ else if (typeof value === "number" && Number.isSafeInteger(value)) {
1022
+ text = String(value);
1023
+ }
1024
+ else if (isLosslessNumber(value)) {
1025
+ const literal = value.toString();
1026
+ if (/^-?\d+$/.test(literal))
1027
+ text = literal;
1028
+ }
1029
+ if (text === null)
1030
+ return null;
1031
+ try {
1032
+ const integer = BigInt(text);
1033
+ return integer >= 0n && integer <= MAX_I64 ? integer : null;
1034
+ }
1035
+ catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+ function formatCents(value, symbol) {
1040
+ return `${symbol}${value / 100n}.${String(value % 100n).padStart(2, "0")}`;
1041
+ }
961
1042
  function epochNsToMs(value) {
962
1043
  const text = decimalIntText(value);
963
1044
  if (text === null)
@@ -1027,28 +1108,34 @@ function tryProcessField(key, value) {
1027
1108
  // Group 2: compound currency suffixes
1028
1109
  stripped = stripSuffixCI(key, "_usd_cents");
1029
1110
  if (stripped !== null) {
1030
- if (isInt(numeric) && numeric >= 0)
1031
- return [stripped, `$${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")}`];
1111
+ const integer = nonNegativeI64(value);
1112
+ if (integer !== null)
1113
+ return [stripped, formatCents(integer, "$")];
1032
1114
  return null;
1033
1115
  }
1034
1116
  stripped = stripSuffixCI(key, "_eur_cents");
1035
1117
  if (stripped !== null) {
1036
- if (isInt(numeric) && numeric >= 0)
1037
- return [stripped, `\u20ac${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")}`];
1118
+ const integer = nonNegativeI64(value);
1119
+ if (integer !== null)
1120
+ return [stripped, formatCents(integer, "\u20ac")];
1038
1121
  return null;
1039
1122
  }
1040
1123
  const gc = tryStripGenericCents(key);
1041
1124
  if (gc !== null) {
1042
1125
  const [gcStripped, code] = gc;
1043
- if (isInt(numeric) && numeric >= 0)
1044
- return [gcStripped, `${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")} ${code.toUpperCase()}`];
1126
+ const integer = nonNegativeI64(value);
1127
+ if (integer !== null) {
1128
+ return [gcStripped, `${integer / 100n}.${String(integer % 100n).padStart(2, "0")} ${code.toUpperCase()}`];
1129
+ }
1045
1130
  return null;
1046
1131
  }
1047
1132
  const gm = tryStripGenericMicro(key);
1048
1133
  if (gm !== null) {
1049
1134
  const [gmStripped, code] = gm;
1050
- if (isInt(numeric) && numeric >= 0)
1051
- return [gmStripped, `${Math.floor(numeric / 1_000_000)}.${String(numeric % 1_000_000).padStart(6, "0")} ${code.toUpperCase()}`];
1135
+ const integer = nonNegativeI64(value);
1136
+ if (integer !== null) {
1137
+ return [gmStripped, `${integer / 1000000n}.${String(integer % 1000000n).padStart(6, "0")} ${code.toUpperCase()}`];
1138
+ }
1052
1139
  return null;
1053
1140
  }
1054
1141
  // Group 3: multi-char suffixes
@@ -1106,8 +1193,9 @@ function tryProcessField(key, value) {
1106
1193
  // Group 5: short suffixes (last to avoid false positives)
1107
1194
  stripped = stripSuffixCI(key, "_jpy");
1108
1195
  if (stripped !== null) {
1109
- if (isInt(numeric) && numeric >= 0)
1110
- return [stripped, `\u00a5${formatWithCommas(numeric)}`];
1196
+ const integer = nonNegativeI64(value);
1197
+ if (integer !== null)
1198
+ return [stripped, `\u00a5${formatWithCommas(integer)}`];
1111
1199
  return null;
1112
1200
  }
1113
1201
  stripped = stripSuffixCI(key, "_ns");
@@ -1142,7 +1230,13 @@ function processObjectFields(obj) {
1142
1230
  for (const [k, v] of Object.entries(obj)) {
1143
1231
  const secretStripped = stripSuffixCI(k, "_secret");
1144
1232
  if (secretStripped !== null) {
1145
- entries.push({ stripped: secretStripped, original: k, value: v, formatted: null });
1233
+ // Dropping `_secret` is the readable half of an actual redaction: the
1234
+ // marker has done its job once the value reads `***`. When the value was
1235
+ // *not* redacted — policy `Off`, or a field outside `trace` under
1236
+ // `TraceOnly` — the suffix is the only thing telling a downstream reader
1237
+ // that `api_key_secret=sk-live-xxx` is a credential, so it stays.
1238
+ const displayKey = isRedacted(v) ? secretStripped : k;
1239
+ entries.push({ stripped: displayKey, original: k, value: v, formatted: null });
1146
1240
  continue;
1147
1241
  }
1148
1242
  const result = tryProcessField(k, v);
@@ -1344,14 +1438,29 @@ function escapeYamlStr(s) {
1344
1438
  return s
1345
1439
  .replace(/\\/g, "\\\\")
1346
1440
  .replace(/"/g, '\\"')
1347
- .replace(/\n/g, "\\n")
1348
- .replace(/\r/g, "\\r")
1349
- .replace(/\t/g, "\\t")
1350
- .replace(/\f/g, "\\f")
1351
- .replace(/\v/g, "\\v");
1441
+ .replace(/[\x00-\x1f]/g, (character) => {
1442
+ switch (character) {
1443
+ case "\0": return "\\0";
1444
+ case "\b": return "\\b";
1445
+ case "\t": return "\\t";
1446
+ case "\n": return "\\n";
1447
+ case "\v": return "\\v";
1448
+ case "\f": return "\\f";
1449
+ case "\r": return "\\r";
1450
+ default: return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
1451
+ }
1452
+ });
1352
1453
  }
1353
1454
  function yamlKey(key) {
1354
- return /^[A-Za-z0-9_.-]+$/.test(key) ? key : `"${escapeYamlStr(key)}"`;
1455
+ return /^[A-Za-z0-9_.-]+$/.test(key) && !isAmbiguousYamlKey(key)
1456
+ ? key
1457
+ : `"${escapeYamlStr(key)}"`;
1458
+ }
1459
+ function isAmbiguousYamlKey(key) {
1460
+ if (["true", "false", "null", "~", ".nan", ".inf", "+.inf", "-.inf"].includes(key.toLowerCase())) {
1461
+ return true;
1462
+ }
1463
+ return key.trim() !== "" && Number.isFinite(Number(key));
1355
1464
  }
1356
1465
  function yamlScalar(value) {
1357
1466
  if (typeof value === "string")
@@ -1362,6 +1471,8 @@ function yamlScalar(value) {
1362
1471
  return value.toString();
1363
1472
  if (typeof value === "number")
1364
1473
  return value.toString();
1474
+ if (typeof value === "bigint")
1475
+ return value.toString();
1365
1476
  // Decoded number: emit the exact source literal unquoted, matching JSON's
1366
1477
  // structure-preserving fidelity contract for YAML too.
1367
1478
  if (isLosslessNumber(value))
@@ -1380,10 +1491,13 @@ function collectPlainPairs(value, prefix, pairs) {
1380
1491
  pairs.push([fullKey, pf.formatted]);
1381
1492
  }
1382
1493
  else if (isObject(pf.value)) {
1383
- collectPlainPairs(pf.value, fullKey, pairs);
1494
+ if (Object.keys(pf.value).length === 0)
1495
+ pairs.push([fullKey, "{}"]);
1496
+ else
1497
+ collectPlainPairs(pf.value, fullKey, pairs);
1384
1498
  }
1385
1499
  else if (Array.isArray(pf.value)) {
1386
- pairs.push([fullKey, pf.value.map((i) => plainScalar(i)).join(",")]);
1500
+ pairs.push([fullKey, pf.value.length === 0 ? "[]" : pf.value.map((i) => plainScalar(i)).join(",")]);
1387
1501
  }
1388
1502
  else if (pf.value === null) {
1389
1503
  pairs.push([fullKey, ""]);
@@ -1400,10 +1514,13 @@ function collectPlainPairsRaw(value, prefix, pairs) {
1400
1514
  const v = value[key];
1401
1515
  const fullKey = prefix ? `${prefix}.${key}` : key;
1402
1516
  if (isObject(v)) {
1403
- collectPlainPairsRaw(v, fullKey, pairs);
1517
+ if (Object.keys(v).length === 0)
1518
+ pairs.push([fullKey, "{}"]);
1519
+ else
1520
+ collectPlainPairsRaw(v, fullKey, pairs);
1404
1521
  }
1405
1522
  else if (Array.isArray(v)) {
1406
- pairs.push([fullKey, v.map((i) => plainScalarRaw(i)).join(",")]);
1523
+ pairs.push([fullKey, v.length === 0 ? "[]" : v.map((i) => plainScalarRaw(i)).join(",")]);
1407
1524
  }
1408
1525
  else if (v === null) {
1409
1526
  pairs.push([fullKey, ""]);
@@ -1422,6 +1539,8 @@ function plainScalar(value) {
1422
1539
  return value.toString();
1423
1540
  if (typeof value === "number")
1424
1541
  return value.toString();
1542
+ if (typeof value === "bigint")
1543
+ return value.toString();
1425
1544
  // Decoded number: emit the exact source literal (Plain is documented lossy
1426
1545
  // for arithmetic-derived formatting, but a bare pass-through scalar still
1427
1546
  // gets its exact digits, not a mangled float64 round-trip).
@@ -1468,10 +1587,14 @@ function sanitizeForJson(value, stack = new WeakSet(), depth = 0) {
1468
1587
  return value;
1469
1588
  if (t === "number") {
1470
1589
  const n = value;
1471
- return Number.isFinite(n) ? n : "<unsupported:number>";
1590
+ if (!Number.isFinite(n))
1591
+ return "<unsupported:number>";
1592
+ if (Number.isInteger(n) && !Number.isSafeInteger(n))
1593
+ return "<unsupported:unsafe-integer>";
1594
+ return n;
1472
1595
  }
1473
1596
  if (t === "bigint")
1474
- return "<unsupported:bigint>";
1597
+ return value;
1475
1598
  if (t === "undefined")
1476
1599
  return "<unsupported:undefined>";
1477
1600
  if (t === "function")
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
3
3
  */
4
4
  export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, type LogLevel, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, type DecodedEvent, type DecodedResult, type DecodedError, type DecodedProgress, type DecodedLog, EventDecodeError, type JsonValue, RedactionPolicy, PlainStyle, type OutputOptions, outputOptionsForPolicy, redactedValue, redactArgv, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
- export { type OutputFormat, type OutputTo, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, parseOutputTo, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
5
+ export { type OutputFormat, type OutputTo, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, parseOutputTo, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, } from "./cli.js";
package/dist/index.js CHANGED
@@ -2,4 +2,4 @@
2
2
  * Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
3
3
  */
4
4
  export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy, PlainStyle, outputOptionsForPolicy, redactedValue, redactArgv, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
- export { CliEmitter, LogFilters, cliParseOutput, parseOutputTo, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
5
+ export { CliEmitter, LogFilters, cliParseOutput, parseOutputTo, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, } from "./cli.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-first-data",
3
- "version": "0.26.2",
3
+ "version": "0.28.0",
4
4
  "description": "A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "scripts": {
25
25
  "build": "tsc",
26
26
  "prepack": "npm run build",
27
- "test": "tsx --test src/*.test.ts examples/agent_cli.ts"
27
+ "test": "tsx --test src/*.test.ts"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.1.2",
@@ -32,6 +32,6 @@
32
32
  "typescript": "^7.0.2"
33
33
  },
34
34
  "dependencies": {
35
- "lossless-json": "^4.3.0"
35
+ "lossless-json": "^4.3.1"
36
36
  }
37
37
  }