agent-first-data 0.17.3 → 0.19.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 +5 -5
- package/assets/skills/agent-first-data/SKILL.md +160 -14
- package/assets/skills/agent-first-data/references/rules.md +36 -26
- package/dist/cli.d.ts +125 -14
- package/dist/cli.js +179 -31
- package/dist/format.d.ts +44 -17
- package/dist/format.js +146 -135
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/skill.d.ts +3 -3
- package/dist/skill.js +2 -2
- package/package.json +5 -2
package/dist/format.js
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
* AFDATA output formatting and protocol templates.
|
|
3
3
|
*
|
|
4
4
|
* Public APIs include protocol event builders, redactedValue (covers _secret
|
|
5
|
-
* and _url fields),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* and _url fields), redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date,
|
|
6
|
+
* isValidRfc3339Time, isValidRfc3339, isValidBcp47, RedactionPolicy,
|
|
7
|
+
* PlainStyle, and OutputOptions. The single render entry point (`render`)
|
|
8
|
+
* lives in cli.ts and calls this module's internal JSON/YAML/plain formatters.
|
|
9
|
+
*
|
|
10
|
+
* Number literal fidelity: decodeProtocolEvent parses with `lossless-json`
|
|
11
|
+
* instead of JSON.parse, so a number's exact source literal (arbitrary
|
|
12
|
+
* precision integers, high-precision floats, exponent forms, "-0") survives
|
|
13
|
+
* decode. Parsed numbers become LosslessNumber internally; every JSON/YAML
|
|
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.
|
|
8
17
|
*/
|
|
18
|
+
import { parse as losslessParse, stringify as losslessStringify, isLosslessNumber } from "lossless-json";
|
|
9
19
|
// ═══════════════════════════════════════════
|
|
10
20
|
// Event type: opaque branded wrapper
|
|
11
21
|
// ═══════════════════════════════════════════
|
|
@@ -46,25 +56,17 @@ export class EventBuildError extends Error {
|
|
|
46
56
|
export class JsonResultBuilder {
|
|
47
57
|
payload;
|
|
48
58
|
traceValue = {};
|
|
49
|
-
issues = [];
|
|
50
59
|
constructor(payload) {
|
|
51
60
|
this.payload = payload;
|
|
52
61
|
}
|
|
53
62
|
trace(value) {
|
|
54
63
|
if (value !== undefined) {
|
|
55
|
-
|
|
56
|
-
this.issues.push("trace must be a JSON object");
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
this.traceValue = value;
|
|
60
|
-
}
|
|
64
|
+
this.traceValue = value;
|
|
61
65
|
}
|
|
62
66
|
return this;
|
|
63
67
|
}
|
|
68
|
+
/** Build the event. This builder cannot fail. */
|
|
64
69
|
build() {
|
|
65
|
-
if (this.issues.length > 0) {
|
|
66
|
-
throw new EventBuildError(`Failed to build result event: ${this.issues.join("; ")}`, this.issues);
|
|
67
|
-
}
|
|
68
70
|
const m = { kind: "result", result: this.payload, trace: this.traceValue };
|
|
69
71
|
return new Event(m);
|
|
70
72
|
}
|
|
@@ -178,25 +180,17 @@ export class JsonErrorBuilder {
|
|
|
178
180
|
export class JsonProgressBuilder {
|
|
179
181
|
payload;
|
|
180
182
|
traceValue = {};
|
|
181
|
-
issues = [];
|
|
182
183
|
constructor(payload) {
|
|
183
184
|
this.payload = payload;
|
|
184
185
|
}
|
|
185
186
|
trace(value) {
|
|
186
187
|
if (value !== undefined) {
|
|
187
|
-
|
|
188
|
-
this.issues.push("trace must be a JSON object");
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
this.traceValue = value;
|
|
192
|
-
}
|
|
188
|
+
this.traceValue = value;
|
|
193
189
|
}
|
|
194
190
|
return this;
|
|
195
191
|
}
|
|
192
|
+
/** Build the event. This builder cannot fail. */
|
|
196
193
|
build() {
|
|
197
|
-
if (this.issues.length > 0) {
|
|
198
|
-
throw new EventBuildError(`Failed to build progress event: ${this.issues.join("; ")}`, this.issues);
|
|
199
|
-
}
|
|
200
194
|
const m = { kind: "progress", progress: this.payload, trace: this.traceValue };
|
|
201
195
|
return new Event(m);
|
|
202
196
|
}
|
|
@@ -204,25 +198,17 @@ export class JsonProgressBuilder {
|
|
|
204
198
|
export class JsonLogBuilder {
|
|
205
199
|
payload;
|
|
206
200
|
traceValue = {};
|
|
207
|
-
issues = [];
|
|
208
201
|
constructor(payload) {
|
|
209
202
|
this.payload = payload;
|
|
210
203
|
}
|
|
211
204
|
trace(value) {
|
|
212
205
|
if (value !== undefined) {
|
|
213
|
-
|
|
214
|
-
this.issues.push("trace must be a JSON object");
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
this.traceValue = value;
|
|
218
|
-
}
|
|
206
|
+
this.traceValue = value;
|
|
219
207
|
}
|
|
220
208
|
return this;
|
|
221
209
|
}
|
|
210
|
+
/** Build the event. This builder cannot fail. */
|
|
222
211
|
build() {
|
|
223
|
-
if (this.issues.length > 0) {
|
|
224
|
-
throw new EventBuildError(`Failed to build log event: ${this.issues.join("; ")}`, this.issues);
|
|
225
|
-
}
|
|
226
212
|
const m = { kind: "log", log: this.payload, trace: this.traceValue };
|
|
227
213
|
return new Event(m);
|
|
228
214
|
}
|
|
@@ -343,7 +329,11 @@ const ERROR_RESERVED_FIELDS = new Set(["code", "message", "hint", "retryable"]);
|
|
|
343
329
|
export function decodeProtocolEvent(text) {
|
|
344
330
|
let parsed;
|
|
345
331
|
try {
|
|
346
|
-
|
|
332
|
+
// lossless-json instead of JSON.parse: preserves each number's exact
|
|
333
|
+
// source literal (see module-level fidelity note) instead of collapsing
|
|
334
|
+
// it through float64. isSyntaxError-compatible: throws the same
|
|
335
|
+
// SyntaxError shape JSON.parse would.
|
|
336
|
+
parsed = losslessParse(text);
|
|
347
337
|
}
|
|
348
338
|
catch (e) {
|
|
349
339
|
throw new EventDecodeError(`invalid JSON: ${e.message}`);
|
|
@@ -388,46 +378,71 @@ export function decodeProtocolEvent(text) {
|
|
|
388
378
|
}
|
|
389
379
|
}
|
|
390
380
|
// ═══════════════════════════════════════════
|
|
391
|
-
//
|
|
381
|
+
// Internal: Output Formatters (called by render() in cli.ts)
|
|
392
382
|
// ═══════════════════════════════════════════
|
|
383
|
+
/** Which fields a redaction pass scrubs. The default is `All`. */
|
|
393
384
|
export var RedactionPolicy;
|
|
394
385
|
(function (RedactionPolicy) {
|
|
395
|
-
RedactionPolicy["
|
|
396
|
-
RedactionPolicy["
|
|
386
|
+
RedactionPolicy["All"] = "All";
|
|
387
|
+
RedactionPolicy["TraceOnly"] = "TraceOnly";
|
|
388
|
+
RedactionPolicy["Off"] = "Off";
|
|
397
389
|
})(RedactionPolicy || (RedactionPolicy = {}));
|
|
398
|
-
export var
|
|
399
|
-
(function (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
})(
|
|
390
|
+
export var PlainStyle;
|
|
391
|
+
(function (PlainStyle) {
|
|
392
|
+
PlainStyle["Readable"] = "Readable";
|
|
393
|
+
PlainStyle["Raw"] = "Raw";
|
|
394
|
+
})(PlainStyle || (PlainStyle = {}));
|
|
403
395
|
/** Convenience: build OutputOptions scoped to a RedactionPolicy. */
|
|
404
396
|
export function outputOptionsForPolicy(policy) {
|
|
405
397
|
return { redaction: { policy } };
|
|
406
398
|
}
|
|
407
|
-
/**
|
|
408
|
-
|
|
399
|
+
/**
|
|
400
|
+
* Format as single-line JSON. Secrets redacted, original keys, raw values.
|
|
401
|
+
* Internal: called by `render` in cli.ts, which is the single public render
|
|
402
|
+
* entry point. Not re-exported from index.ts.
|
|
403
|
+
*/
|
|
404
|
+
export function formatJsonValue(value, options = {}) {
|
|
409
405
|
const unwrapped = value instanceof Event ? value.toJSON() : value;
|
|
410
|
-
return
|
|
406
|
+
return canonicalJsonString(redactedValue(unwrapped, options.redaction ?? {}));
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Compact JSON serialization, byte-identical to `JSON.stringify(value)` for
|
|
410
|
+
* every value that contains no LosslessNumber, and additionally correct for
|
|
411
|
+
* values that do: `JSON.stringify` cannot serialize a LosslessNumber (it has
|
|
412
|
+
* no `toJSON` and isn't a boxed Number, so it would fall back to serializing
|
|
413
|
+
* its internal `{value, isLosslessNumber}` fields as if they were user data).
|
|
414
|
+
* lossless-json's `stringify` recognizes LosslessNumber and re-emits its
|
|
415
|
+
* exact literal text unquoted, and matches native JSON.stringify's escaping
|
|
416
|
+
* and formatting for everything else.
|
|
417
|
+
*/
|
|
418
|
+
function canonicalJsonString(value) {
|
|
419
|
+
return losslessStringify(value) ?? "null";
|
|
411
420
|
}
|
|
412
|
-
/**
|
|
413
|
-
|
|
421
|
+
/**
|
|
422
|
+
* Format as multi-line YAML. Structure-preserving: like JSON, original keys,
|
|
423
|
+
* scalar types, and numeric semantics are kept after redaction; secrets are
|
|
424
|
+
* still redacted. `options.style` is ignored — YAML output does not vary by
|
|
425
|
+
* PlainStyle.
|
|
426
|
+
* Internal: called by `render` in cli.ts, which is the single public render
|
|
427
|
+
* entry point. Not re-exported from index.ts.
|
|
428
|
+
*/
|
|
429
|
+
export function formatYamlValue(value, options = {}) {
|
|
414
430
|
const unwrapped = value instanceof Event ? value.toJSON() : value;
|
|
415
431
|
const redacted = redactedValue(unwrapped, options.redaction ?? {});
|
|
416
432
|
const lines = ["---"];
|
|
417
|
-
|
|
418
|
-
renderYamlRaw(redacted, 0, lines);
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
renderYamlProcessed(redacted, 0, lines);
|
|
422
|
-
}
|
|
433
|
+
renderYamlRaw(redacted, 0, lines);
|
|
423
434
|
return lines.join("\n");
|
|
424
435
|
}
|
|
425
|
-
/**
|
|
426
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Format as single-line logfmt. Keys stripped, values formatted, secrets redacted.
|
|
438
|
+
* Internal: called by `render` in cli.ts, which is the single public render
|
|
439
|
+
* entry point. Not re-exported from index.ts.
|
|
440
|
+
*/
|
|
441
|
+
export function formatPlainValue(value, options = {}) {
|
|
427
442
|
const unwrapped = value instanceof Event ? value.toJSON() : value;
|
|
428
443
|
const redacted = redactedValue(unwrapped, options.redaction ?? {});
|
|
429
444
|
const pairs = [];
|
|
430
|
-
if (options.style ===
|
|
445
|
+
if (options.style === PlainStyle.Raw) {
|
|
431
446
|
collectPlainPairsRaw(redacted, "", pairs);
|
|
432
447
|
}
|
|
433
448
|
else {
|
|
@@ -668,15 +683,15 @@ function applyRedactionOptions(value, redactionOptions) {
|
|
|
668
683
|
}
|
|
669
684
|
function applyRedactionPolicyWithContext(value, redactionPolicy, context) {
|
|
670
685
|
switch (redactionPolicy) {
|
|
671
|
-
case RedactionPolicy.
|
|
686
|
+
case RedactionPolicy.TraceOnly:
|
|
672
687
|
if (isObject(value) && value.trace !== undefined) {
|
|
673
688
|
redactSecrets(value.trace, context);
|
|
674
689
|
}
|
|
675
690
|
break;
|
|
676
|
-
case RedactionPolicy.
|
|
691
|
+
case RedactionPolicy.Off:
|
|
677
692
|
break;
|
|
678
693
|
default:
|
|
679
|
-
//
|
|
694
|
+
// undefined or RedactionPolicy.All = full redaction (default).
|
|
680
695
|
redactSecrets(value, context);
|
|
681
696
|
break;
|
|
682
697
|
}
|
|
@@ -876,6 +891,10 @@ function decimalIntText(value) {
|
|
|
876
891
|
return value;
|
|
877
892
|
if (isInt(value))
|
|
878
893
|
return String(value);
|
|
894
|
+
if (isLosslessNumber(value)) {
|
|
895
|
+
const text = value.toString();
|
|
896
|
+
return /^-?\d+$/.test(text) ? text : null;
|
|
897
|
+
}
|
|
879
898
|
return null;
|
|
880
899
|
}
|
|
881
900
|
function epochNsToMs(value) {
|
|
@@ -902,11 +921,24 @@ function isNum(value) {
|
|
|
902
921
|
}
|
|
903
922
|
function tryProcessField(key, value) {
|
|
904
923
|
let stripped;
|
|
924
|
+
// Plain-format suffix processing below does real arithmetic (division,
|
|
925
|
+
// modulo, Math.floor/abs) that only works on a native JS number. A decoded
|
|
926
|
+
// LosslessNumber is normalized to one here via Number() \u2014 which never
|
|
927
|
+
// throws, unlike LosslessNumber's own valueOf() (documented to throw for
|
|
928
|
+
// values it can't safely represent as number/bigint) \u2014 so an ordinary
|
|
929
|
+
// decoded value (e.g. duration_ms: 42) keeps formatting exactly as before,
|
|
930
|
+
// and an exotic decoded value degrades gracefully (loses precision here,
|
|
931
|
+
// same as it always would for a >2^53 native number) rather than crashing.
|
|
932
|
+
// This is Plain's documented lossy/human path; JSON/YAML stay exact via
|
|
933
|
+
// yamlScalar/plainScalar/canonicalJsonString, and decimalIntText below
|
|
934
|
+
// (msats/sats/epoch_ns) keeps exactness by reading `value` directly instead
|
|
935
|
+
// of `numeric`.
|
|
936
|
+
const numeric = isLosslessNumber(value) ? Number(value.toString()) : value;
|
|
905
937
|
// Group 1: compound timestamp suffixes
|
|
906
938
|
stripped = stripSuffixCI(key, "_epoch_ms");
|
|
907
939
|
if (stripped !== null) {
|
|
908
|
-
if (isInt(
|
|
909
|
-
const formatted = formatRfc3339Ms(
|
|
940
|
+
if (isInt(numeric)) {
|
|
941
|
+
const formatted = formatRfc3339Ms(numeric);
|
|
910
942
|
if (formatted !== null)
|
|
911
943
|
return [stripped, formatted];
|
|
912
944
|
}
|
|
@@ -914,8 +946,8 @@ function tryProcessField(key, value) {
|
|
|
914
946
|
}
|
|
915
947
|
stripped = stripSuffixCI(key, "_epoch_s");
|
|
916
948
|
if (stripped !== null) {
|
|
917
|
-
if (isInt(
|
|
918
|
-
const formatted = formatRfc3339Ms(
|
|
949
|
+
if (isInt(numeric)) {
|
|
950
|
+
const formatted = formatRfc3339Ms(numeric * 1000);
|
|
919
951
|
if (formatted !== null)
|
|
920
952
|
return [stripped, formatted];
|
|
921
953
|
}
|
|
@@ -934,28 +966,28 @@ function tryProcessField(key, value) {
|
|
|
934
966
|
// Group 2: compound currency suffixes
|
|
935
967
|
stripped = stripSuffixCI(key, "_usd_cents");
|
|
936
968
|
if (stripped !== null) {
|
|
937
|
-
if (isInt(
|
|
938
|
-
return [stripped, `$${Math.floor(
|
|
969
|
+
if (isInt(numeric) && numeric >= 0)
|
|
970
|
+
return [stripped, `$${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")}`];
|
|
939
971
|
return null;
|
|
940
972
|
}
|
|
941
973
|
stripped = stripSuffixCI(key, "_eur_cents");
|
|
942
974
|
if (stripped !== null) {
|
|
943
|
-
if (isInt(
|
|
944
|
-
return [stripped, `\u20ac${Math.floor(
|
|
975
|
+
if (isInt(numeric) && numeric >= 0)
|
|
976
|
+
return [stripped, `\u20ac${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")}`];
|
|
945
977
|
return null;
|
|
946
978
|
}
|
|
947
979
|
const gc = tryStripGenericCents(key);
|
|
948
980
|
if (gc !== null) {
|
|
949
981
|
const [gcStripped, code] = gc;
|
|
950
|
-
if (isInt(
|
|
951
|
-
return [gcStripped, `${Math.floor(
|
|
982
|
+
if (isInt(numeric) && numeric >= 0)
|
|
983
|
+
return [gcStripped, `${Math.floor(numeric / 100)}.${String(numeric % 100).padStart(2, "0")} ${code.toUpperCase()}`];
|
|
952
984
|
return null;
|
|
953
985
|
}
|
|
954
986
|
const gm = tryStripGenericMicro(key);
|
|
955
987
|
if (gm !== null) {
|
|
956
988
|
const [gmStripped, code] = gm;
|
|
957
|
-
if (isInt(
|
|
958
|
-
return [gmStripped, `${Math.floor(
|
|
989
|
+
if (isInt(numeric) && numeric >= 0)
|
|
990
|
+
return [gmStripped, `${Math.floor(numeric / 1_000_000)}.${String(numeric % 1_000_000).padStart(6, "0")} ${code.toUpperCase()}`];
|
|
959
991
|
return null;
|
|
960
992
|
}
|
|
961
993
|
// Group 3: multi-char suffixes
|
|
@@ -967,20 +999,20 @@ function tryProcessField(key, value) {
|
|
|
967
999
|
}
|
|
968
1000
|
stripped = stripSuffixCI(key, "_minutes");
|
|
969
1001
|
if (stripped !== null) {
|
|
970
|
-
if (isNum(
|
|
971
|
-
return [stripped, `${plainScalar(
|
|
1002
|
+
if (isNum(numeric))
|
|
1003
|
+
return [stripped, `${plainScalar(numeric)} minutes`];
|
|
972
1004
|
return null;
|
|
973
1005
|
}
|
|
974
1006
|
stripped = stripSuffixCI(key, "_hours");
|
|
975
1007
|
if (stripped !== null) {
|
|
976
|
-
if (isNum(
|
|
977
|
-
return [stripped, `${plainScalar(
|
|
1008
|
+
if (isNum(numeric))
|
|
1009
|
+
return [stripped, `${plainScalar(numeric)} hours`];
|
|
978
1010
|
return null;
|
|
979
1011
|
}
|
|
980
1012
|
stripped = stripSuffixCI(key, "_days");
|
|
981
1013
|
if (stripped !== null) {
|
|
982
|
-
if (isNum(
|
|
983
|
-
return [stripped, `${plainScalar(
|
|
1014
|
+
if (isNum(numeric))
|
|
1015
|
+
return [stripped, `${plainScalar(numeric)} days`];
|
|
984
1016
|
return null;
|
|
985
1017
|
}
|
|
986
1018
|
// Group 4: single-unit suffixes
|
|
@@ -1000,46 +1032,46 @@ function tryProcessField(key, value) {
|
|
|
1000
1032
|
}
|
|
1001
1033
|
stripped = stripSuffixCI(key, "_bytes");
|
|
1002
1034
|
if (stripped !== null) {
|
|
1003
|
-
if (isInt(
|
|
1004
|
-
return [stripped, formatBytesHuman(
|
|
1035
|
+
if (isInt(numeric) && numeric >= 0)
|
|
1036
|
+
return [stripped, formatBytesHuman(numeric)];
|
|
1005
1037
|
return null;
|
|
1006
1038
|
}
|
|
1007
1039
|
stripped = stripSuffixCI(key, "_percent");
|
|
1008
1040
|
if (stripped !== null) {
|
|
1009
|
-
if (isNum(
|
|
1010
|
-
return [stripped, `${plainScalar(
|
|
1041
|
+
if (isNum(numeric))
|
|
1042
|
+
return [stripped, `${plainScalar(numeric)}%`];
|
|
1011
1043
|
return null;
|
|
1012
1044
|
}
|
|
1013
1045
|
// Group 5: short suffixes (last to avoid false positives)
|
|
1014
1046
|
stripped = stripSuffixCI(key, "_jpy");
|
|
1015
1047
|
if (stripped !== null) {
|
|
1016
|
-
if (isInt(
|
|
1017
|
-
return [stripped, `\u00a5${formatWithCommas(
|
|
1048
|
+
if (isInt(numeric) && numeric >= 0)
|
|
1049
|
+
return [stripped, `\u00a5${formatWithCommas(numeric)}`];
|
|
1018
1050
|
return null;
|
|
1019
1051
|
}
|
|
1020
1052
|
stripped = stripSuffixCI(key, "_ns");
|
|
1021
1053
|
if (stripped !== null) {
|
|
1022
|
-
if (isNum(
|
|
1023
|
-
return [stripped, `${plainScalar(
|
|
1054
|
+
if (isNum(numeric))
|
|
1055
|
+
return [stripped, `${plainScalar(numeric)}ns`];
|
|
1024
1056
|
return null;
|
|
1025
1057
|
}
|
|
1026
1058
|
stripped = stripSuffixCI(key, "_us");
|
|
1027
1059
|
if (stripped !== null) {
|
|
1028
|
-
if (isNum(
|
|
1029
|
-
return [stripped, `${plainScalar(
|
|
1060
|
+
if (isNum(numeric))
|
|
1061
|
+
return [stripped, `${plainScalar(numeric)}\u03bcs`];
|
|
1030
1062
|
return null;
|
|
1031
1063
|
}
|
|
1032
1064
|
stripped = stripSuffixCI(key, "_ms");
|
|
1033
1065
|
if (stripped !== null) {
|
|
1034
|
-
const fv = formatMsValue(
|
|
1066
|
+
const fv = formatMsValue(numeric);
|
|
1035
1067
|
if (fv !== null)
|
|
1036
1068
|
return [stripped, fv];
|
|
1037
1069
|
return null;
|
|
1038
1070
|
}
|
|
1039
1071
|
stripped = stripSuffixCI(key, "_s");
|
|
1040
1072
|
if (stripped !== null) {
|
|
1041
|
-
if (isNum(
|
|
1042
|
-
return [stripped, `${plainScalar(
|
|
1073
|
+
if (isNum(numeric))
|
|
1074
|
+
return [stripped, `${plainScalar(numeric)}s`];
|
|
1043
1075
|
return null;
|
|
1044
1076
|
}
|
|
1045
1077
|
return null;
|
|
@@ -1184,47 +1216,6 @@ function extractCurrencyCodeFromStem(stem) {
|
|
|
1184
1216
|
// ═══════════════════════════════════════════
|
|
1185
1217
|
// YAML Rendering
|
|
1186
1218
|
// ═══════════════════════════════════════════
|
|
1187
|
-
function renderYamlProcessed(value, indent, lines) {
|
|
1188
|
-
const prefix = " ".repeat(indent);
|
|
1189
|
-
if (!isObject(value)) {
|
|
1190
|
-
lines.push(`${prefix}${yamlScalar(value)}`);
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
for (const pf of processObjectFields(value)) {
|
|
1194
|
-
if (pf.formatted !== null) {
|
|
1195
|
-
lines.push(`${prefix}${yamlKey(pf.key)}: "${escapeYamlStr(pf.formatted)}"`);
|
|
1196
|
-
}
|
|
1197
|
-
else if (isObject(pf.value)) {
|
|
1198
|
-
if (Object.keys(pf.value).length > 0) {
|
|
1199
|
-
lines.push(`${prefix}${yamlKey(pf.key)}:`);
|
|
1200
|
-
renderYamlProcessed(pf.value, indent + 1, lines);
|
|
1201
|
-
}
|
|
1202
|
-
else {
|
|
1203
|
-
lines.push(`${prefix}${yamlKey(pf.key)}: {}`);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
else if (Array.isArray(pf.value)) {
|
|
1207
|
-
if (pf.value.length === 0) {
|
|
1208
|
-
lines.push(`${prefix}${yamlKey(pf.key)}: []`);
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
lines.push(`${prefix}${yamlKey(pf.key)}:`);
|
|
1212
|
-
for (const item of pf.value) {
|
|
1213
|
-
if (isObject(item)) {
|
|
1214
|
-
lines.push(`${prefix} -`);
|
|
1215
|
-
renderYamlProcessed(item, indent + 2, lines);
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
lines.push(`${prefix} - ${yamlScalar(item)}`);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
else {
|
|
1224
|
-
lines.push(`${prefix}${yamlKey(pf.key)}: ${yamlScalar(pf.value)}`);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
1219
|
function renderYamlRaw(value, indent, lines) {
|
|
1229
1220
|
const prefix = " ".repeat(indent);
|
|
1230
1221
|
if (isObject(value)) {
|
|
@@ -1310,7 +1301,11 @@ function yamlScalar(value) {
|
|
|
1310
1301
|
return value.toString();
|
|
1311
1302
|
if (typeof value === "number")
|
|
1312
1303
|
return value.toString();
|
|
1313
|
-
|
|
1304
|
+
// Decoded number: emit the exact source literal unquoted, matching JSON's
|
|
1305
|
+
// structure-preserving fidelity contract for YAML too.
|
|
1306
|
+
if (isLosslessNumber(value))
|
|
1307
|
+
return value.toString();
|
|
1308
|
+
return `"${escapeYamlStr(canonicalJsonString(sortJsonValue(value)))}"`;
|
|
1314
1309
|
}
|
|
1315
1310
|
// ═══════════════════════════════════════════
|
|
1316
1311
|
// Plain Rendering (logfmt)
|
|
@@ -1366,11 +1361,16 @@ function plainScalar(value) {
|
|
|
1366
1361
|
return value.toString();
|
|
1367
1362
|
if (typeof value === "number")
|
|
1368
1363
|
return value.toString();
|
|
1369
|
-
|
|
1364
|
+
// Decoded number: emit the exact source literal (Plain is documented lossy
|
|
1365
|
+
// for arithmetic-derived formatting, but a bare pass-through scalar still
|
|
1366
|
+
// gets its exact digits, not a mangled float64 round-trip).
|
|
1367
|
+
if (isLosslessNumber(value))
|
|
1368
|
+
return value.toString();
|
|
1369
|
+
return canonicalJsonString(sortJsonValue(value));
|
|
1370
1370
|
}
|
|
1371
1371
|
function plainScalarRaw(value) {
|
|
1372
1372
|
if (isObject(value) || Array.isArray(value)) {
|
|
1373
|
-
return
|
|
1373
|
+
return canonicalJsonString(sortJsonValue(value));
|
|
1374
1374
|
}
|
|
1375
1375
|
return plainScalar(value);
|
|
1376
1376
|
}
|
|
@@ -1417,6 +1417,11 @@ function sanitizeForJson(value, stack = new WeakSet(), depth = 0) {
|
|
|
1417
1417
|
return "<unsupported:function>";
|
|
1418
1418
|
if (t === "symbol")
|
|
1419
1419
|
return "<unsupported:symbol>";
|
|
1420
|
+
// A LosslessNumber (from decodeProtocolEvent) is typeof "object" but must
|
|
1421
|
+
// stay opaque: the generic object branch below would otherwise flatten it
|
|
1422
|
+
// into its internal {value, isLosslessNumber} fields via Object.entries.
|
|
1423
|
+
if (isLosslessNumber(value))
|
|
1424
|
+
return value;
|
|
1420
1425
|
if (value instanceof Error)
|
|
1421
1426
|
return value.message;
|
|
1422
1427
|
if (value instanceof Date)
|
|
@@ -1444,7 +1449,13 @@ function sanitizeForJson(value, stack = new WeakSet(), depth = 0) {
|
|
|
1444
1449
|
return "<unsupported:unknown>";
|
|
1445
1450
|
}
|
|
1446
1451
|
function isObject(value) {
|
|
1447
|
-
|
|
1452
|
+
// isLosslessNumber excludes decoded numbers: a LosslessNumber is
|
|
1453
|
+
// typeof "object" but is an opaque scalar leaf, never a plain JSON object —
|
|
1454
|
+
// every object-walking function below (redaction, YAML/plain rendering,
|
|
1455
|
+
// sortJsonValue, validation) funnels through this check, so excluding it
|
|
1456
|
+
// here is the single choke point that keeps LosslessNumber from being
|
|
1457
|
+
// destructured into its internal {value, isLosslessNumber} fields.
|
|
1458
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !isLosslessNumber(value);
|
|
1448
1459
|
}
|
|
1449
1460
|
function sortedObjectKeys(obj) {
|
|
1450
1461
|
return Object.keys(obj).sort(jcsCompare);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
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,
|
|
5
|
-
export { type OutputFormat, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters,
|
|
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, 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";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
4
|
-
export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy,
|
|
5
|
-
export { CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters,
|
|
4
|
+
export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy, PlainStyle, outputOptionsForPolicy, redactedValue, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
|
|
5
|
+
export { CliEmitter, LogFilters, cliParseOutput, parseOutputTo, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
|
package/dist/skill.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* supported coding agents (Codex, Claude Code, opencode, Hermes).
|
|
7
7
|
*
|
|
8
8
|
* The function performs the filesystem work and returns the protocol value (rendered by
|
|
9
|
-
* the caller with `
|
|
9
|
+
* the caller with `render`) or throws a {@link SkillError}. It never writes to
|
|
10
10
|
* stdout/stderr itself.
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
@@ -67,7 +67,7 @@ export interface SkillUninstallStatus {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* The result of a skill action. Read fields off the discriminated union (narrow on
|
|
70
|
-
* `code`), or pass it to `
|
|
70
|
+
* `code`), or pass it to `render` to serialize — every field is already JSON-shaped.
|
|
71
71
|
*/
|
|
72
72
|
export type SkillReport = {
|
|
73
73
|
code: "skill_status";
|
|
@@ -90,7 +90,7 @@ export type SkillReport = {
|
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
92
92
|
* Install, uninstall, or report status of `spec`'s skill across the selected agent
|
|
93
|
-
* target(s). Returns the protocol value (caller renders with `
|
|
93
|
+
* target(s). Returns the protocol value (caller renders with `render`) or throws a
|
|
94
94
|
* {@link SkillError}. Does not touch stdout/stderr.
|
|
95
95
|
*/
|
|
96
96
|
export declare function runSkillAdmin(spec: SkillSpec, action: SkillAction, options: SkillOptions): SkillReport;
|
package/dist/skill.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* supported coding agents (Codex, Claude Code, opencode, Hermes).
|
|
7
7
|
*
|
|
8
8
|
* The function performs the filesystem work and returns the protocol value (rendered by
|
|
9
|
-
* the caller with `
|
|
9
|
+
* the caller with `render`) or throws a {@link SkillError}. It never writes to
|
|
10
10
|
* stdout/stderr itself.
|
|
11
11
|
*/
|
|
12
12
|
import * as fs from "node:fs";
|
|
@@ -26,7 +26,7 @@ export class SkillError extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Install, uninstall, or report status of `spec`'s skill across the selected agent
|
|
29
|
-
* target(s). Returns the protocol value (caller renders with `
|
|
29
|
+
* target(s). Returns the protocol value (caller renders with `render`) or throws a
|
|
30
30
|
* {@link SkillError}. Does not touch stdout/stderr.
|
|
31
31
|
*/
|
|
32
32
|
export function runSkillAdmin(spec, action, options) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-first-data",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A naming convention that lets AI agents understand your data without being told what it means.",
|
|
3
|
+
"version": "0.19.0",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
@@ -38,5 +38,8 @@
|
|
|
38
38
|
"@types/node": "^26.1.1",
|
|
39
39
|
"tsx": "^4.23.1",
|
|
40
40
|
"typescript": "^7.0.2"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"lossless-json": "^4.3.0"
|
|
41
44
|
}
|
|
42
45
|
}
|