agent-first-data 0.29.2 → 0.31.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 +4 -1
- package/assets/cli-spec-v1.schema.json +40 -1
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +6 -5
- package/dist/format.d.ts +16 -0
- package/dist/format.js +190 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,12 +17,15 @@ console.log(render(event.toJSON(), "json"));
|
|
|
17
17
|
console.log(render(event.toJSON(), "plain"));
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Useful names use TypeScript casing: `render`, `outputOptionsForPolicy`, `redactedValue`, `redactUrlSecrets`, `redactArgv`, `normalizeUtcOffset`, `isValidRfc3339Date`, `isValidRfc3339Time`, `isValidRfc3339`, `isValidBcp47`, `decodeProtocolEvent`, `cliParseOutput`, `cliParseLogFilters`, `buildCliError`, and `buildCliVersion`.
|
|
20
|
+
Useful names use TypeScript casing: `render`, `outputOptionsForPolicy`, `redactedValue`, `redactUrlSecrets`, `redactUrlsInText`, `redactArgv`, `normalizeUtcOffset`, `isValidRfc3339Date`, `isValidRfc3339Time`, `isValidRfc3339`, `isValidBcp47`, `decodeProtocolEvent`, `cliParseOutput`, `cliParseLogFilters`, `buildCliError`, and `buildCliVersion`.
|
|
21
21
|
|
|
22
22
|
## Behavior Notes
|
|
23
23
|
|
|
24
24
|
- Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
|
|
25
25
|
- `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed and internal whitespace redacts the whole field.
|
|
26
|
+
- `OutputOptions.redaction.urlNames` applies URL treatment to exact legacy field names
|
|
27
|
+
and recurses through collections. `redactUrlsInText` is explicit and scans
|
|
28
|
+
only complete scheme URLs, never arbitrary prose secrets.
|
|
26
29
|
- YAML keeps original keys and values (structure-preserving, like JSON), sorting keys by UTF-16 code unit order and quoting/escaping unsafe keys and string scalars. Plain strips formatting suffixes, formats values, sorts the same way, and renders nested objects/arrays as canonical JSON.
|
|
27
30
|
- Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
28
31
|
- `buildCliError(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
|
|
@@ -32,9 +32,38 @@
|
|
|
32
32
|
"about": {
|
|
33
33
|
"$ref": "#/$defs/nonEmpty"
|
|
34
34
|
},
|
|
35
|
+
"shared_arguments": {
|
|
36
|
+
"description": "Arguments every command accepts, declared once. Spliced into each command and into every combination's optional set when the registry is built, so a resolved spec behaves as if each command declared them.",
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"$ref": "#/$defs/argument"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
35
42
|
"lifecycle_output": {
|
|
36
43
|
"$ref": "#/$defs/output"
|
|
37
44
|
},
|
|
45
|
+
"exit_codes": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"description": "Exit codes this CLI returns beyond AFDATA's 0/1/2, rendered into the reference's exit-code table. 0/1/2 are AFDATA's own and may not be redeclared.",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"required": [
|
|
52
|
+
"code",
|
|
53
|
+
"meaning"
|
|
54
|
+
],
|
|
55
|
+
"properties": {
|
|
56
|
+
"code": {
|
|
57
|
+
"type": "integer",
|
|
58
|
+
"minimum": 3,
|
|
59
|
+
"maximum": 255
|
|
60
|
+
},
|
|
61
|
+
"meaning": {
|
|
62
|
+
"$ref": "#/$defs/nonEmpty"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
38
67
|
"commands": {
|
|
39
68
|
"type": "array",
|
|
40
69
|
"minItems": 1,
|
|
@@ -142,7 +171,8 @@
|
|
|
142
171
|
"i64",
|
|
143
172
|
"finite_f64",
|
|
144
173
|
"enum",
|
|
145
|
-
"json"
|
|
174
|
+
"json",
|
|
175
|
+
"uuid"
|
|
146
176
|
]
|
|
147
177
|
},
|
|
148
178
|
"value_name": {
|
|
@@ -156,6 +186,15 @@
|
|
|
156
186
|
"$ref": "#/$defs/nonEmpty"
|
|
157
187
|
}
|
|
158
188
|
},
|
|
189
|
+
"range": {
|
|
190
|
+
"description": "Inclusive [minimum, maximum] bounds for an I64 argument, checked when the value is parsed so an out-of-range value is a usage error.",
|
|
191
|
+
"type": "array",
|
|
192
|
+
"items": {
|
|
193
|
+
"type": "integer"
|
|
194
|
+
},
|
|
195
|
+
"minItems": 2,
|
|
196
|
+
"maxItems": 2
|
|
197
|
+
},
|
|
159
198
|
"default": true,
|
|
160
199
|
"repeatable": {
|
|
161
200
|
"const": true
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { JsonValue, type OutputOptions, Event } from "./format.js";
|
|
|
6
6
|
export type OutputFormat = "json" | "yaml" | "plain";
|
|
7
7
|
/**
|
|
8
8
|
* Parse the --output flag value into an OutputFormat.
|
|
9
|
-
* Throws on unknown values; catch and pass
|
|
9
|
+
* Throws a value-safe message on unknown values; catch and pass it to
|
|
10
|
+
* buildCliError.
|
|
10
11
|
*
|
|
11
12
|
* @example
|
|
12
13
|
* cliParseOutput("json") // → "json"
|
|
@@ -78,8 +79,8 @@ export type CliEventWriter = (line: string) => void;
|
|
|
78
79
|
export type OutputTo = "split" | "stdout" | "stderr";
|
|
79
80
|
/**
|
|
80
81
|
* Parse an `--output-to` value into an OutputTo: "split" (default), "stdout", or
|
|
81
|
-
* "stderr". Throws on unknown values; catch and pass
|
|
82
|
-
* buildCliError.
|
|
82
|
+
* "stderr". Throws a value-safe message on unknown values; catch and pass it
|
|
83
|
+
* to buildCliError.
|
|
83
84
|
*
|
|
84
85
|
* @example
|
|
85
86
|
* parseOutputTo("split") // → "split"
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { Event, jsonError, jsonResult, formatJsonValue, formatYamlValue, formatPlainValue, validateProtocolEvent, } from "./format.js";
|
|
5
5
|
/**
|
|
6
6
|
* Parse the --output flag value into an OutputFormat.
|
|
7
|
-
* Throws on unknown values; catch and pass
|
|
7
|
+
* Throws a value-safe message on unknown values; catch and pass it to
|
|
8
|
+
* buildCliError.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* cliParseOutput("json") // → "json"
|
|
@@ -14,7 +15,7 @@ export function cliParseOutput(s) {
|
|
|
14
15
|
if (s === "json" || s === "yaml" || s === "plain") {
|
|
15
16
|
return s;
|
|
16
17
|
}
|
|
17
|
-
throw new Error(
|
|
18
|
+
throw new Error("invalid --output format: expected json, yaml, or plain");
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* Normalized log filter result with enabled() matching and readonly filters property.
|
|
@@ -96,8 +97,8 @@ export function render(value, format, options = {}) {
|
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
98
99
|
* Parse an `--output-to` value into an OutputTo: "split" (default), "stdout", or
|
|
99
|
-
* "stderr". Throws on unknown values; catch and pass
|
|
100
|
-
* buildCliError.
|
|
100
|
+
* "stderr". Throws a value-safe message on unknown values; catch and pass it
|
|
101
|
+
* to buildCliError.
|
|
101
102
|
*
|
|
102
103
|
* @example
|
|
103
104
|
* parseOutputTo("split") // → "split"
|
|
@@ -107,7 +108,7 @@ export function parseOutputTo(value) {
|
|
|
107
108
|
if (value === "split" || value === "stdout" || value === "stderr") {
|
|
108
109
|
return value;
|
|
109
110
|
}
|
|
110
|
-
throw new Error(
|
|
111
|
+
throw new Error("invalid --output-to: expected split, stdout, or stderr");
|
|
111
112
|
}
|
|
112
113
|
/** Event writer bound to the process stdout stream. */
|
|
113
114
|
const processStdoutWriter = (line) => {
|
package/dist/format.d.ts
CHANGED
|
@@ -153,6 +153,11 @@ export type OutputOptions = {
|
|
|
153
153
|
* Matching is exact field-name equality at any nesting level.
|
|
154
154
|
*/
|
|
155
155
|
secretNames?: readonly string[];
|
|
156
|
+
/**
|
|
157
|
+
* Exact legacy field names whose string leaves receive URL-aware
|
|
158
|
+
* redaction in addition to _url/_URL fields.
|
|
159
|
+
*/
|
|
160
|
+
urlNames?: readonly string[];
|
|
156
161
|
};
|
|
157
162
|
/** Rendering style for plain (logfmt) output only. Omitted means readable.
|
|
158
163
|
* JSON and YAML are structure-preserving and ignore this setting. */
|
|
@@ -185,6 +190,7 @@ export declare function formatPlainValue(value: JsonValue | Event, options?: Out
|
|
|
185
190
|
export declare function redactedValue(value: unknown, options?: {
|
|
186
191
|
policy?: RedactionPolicy;
|
|
187
192
|
secretNames?: readonly string[];
|
|
193
|
+
urlNames?: readonly string[];
|
|
188
194
|
}): JsonValue;
|
|
189
195
|
/**
|
|
190
196
|
* Redact secret components of a single URL string.
|
|
@@ -206,6 +212,16 @@ export declare function redactUrlSecrets(url: string, options?: {
|
|
|
206
212
|
secretNames?: readonly string[];
|
|
207
213
|
policy?: RedactionPolicy;
|
|
208
214
|
}): string;
|
|
215
|
+
/**
|
|
216
|
+
* Redact complete scheme-prefixed URL spans embedded in prose.
|
|
217
|
+
*
|
|
218
|
+
* This helper is explicit and URL-only: surrounding text is never scanned for
|
|
219
|
+
* secret-looking names or values.
|
|
220
|
+
*/
|
|
221
|
+
export declare function redactUrlsInText(text: string, options?: {
|
|
222
|
+
secretNames?: readonly string[];
|
|
223
|
+
policy?: RedactionPolicy;
|
|
224
|
+
}): string;
|
|
209
225
|
/**
|
|
210
226
|
* Redact secret values out of a command line.
|
|
211
227
|
*
|
package/dist/format.js
CHANGED
|
@@ -517,6 +517,17 @@ export function redactUrlSecrets(url, options) {
|
|
|
517
517
|
const redacted = redactUrlInStr(url, secretNameSet(options ?? {}));
|
|
518
518
|
return redacted ?? url;
|
|
519
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Redact complete scheme-prefixed URL spans embedded in prose.
|
|
522
|
+
*
|
|
523
|
+
* This helper is explicit and URL-only: surrounding text is never scanned for
|
|
524
|
+
* secret-looking names or values.
|
|
525
|
+
*/
|
|
526
|
+
export function redactUrlsInText(text, options) {
|
|
527
|
+
if (!redactsUnscopedInput(options?.policy))
|
|
528
|
+
return text;
|
|
529
|
+
return redactUrlsInTextWithContext(text, contextFromOptions(options ?? {}));
|
|
530
|
+
}
|
|
520
531
|
/**
|
|
521
532
|
* Redact secret values out of a command line.
|
|
522
533
|
*
|
|
@@ -749,12 +760,16 @@ function isRedacted(value) {
|
|
|
749
760
|
}
|
|
750
761
|
const DEFAULT_CONTEXT = {
|
|
751
762
|
secretNames: new Set(),
|
|
763
|
+
urlNames: new Set(),
|
|
752
764
|
};
|
|
753
765
|
function secretNameSet(redactionOptions) {
|
|
754
766
|
return new Set(redactionOptions.secretNames ?? []);
|
|
755
767
|
}
|
|
756
768
|
function contextFromOptions(redactionOptions) {
|
|
757
|
-
return {
|
|
769
|
+
return {
|
|
770
|
+
secretNames: secretNameSet(redactionOptions),
|
|
771
|
+
urlNames: new Set(redactionOptions.urlNames ?? []),
|
|
772
|
+
};
|
|
758
773
|
}
|
|
759
774
|
function keyHasSecretSuffix(key) {
|
|
760
775
|
return key.endsWith("_secret") || key.endsWith("_SECRET");
|
|
@@ -765,6 +780,9 @@ function keyHasUrlSuffix(key) {
|
|
|
765
780
|
function isSecretKey(key, secretNames) {
|
|
766
781
|
return keyHasSecretSuffix(key) || secretNames.has(key);
|
|
767
782
|
}
|
|
783
|
+
function isUrlKey(key, context) {
|
|
784
|
+
return keyHasUrlSuffix(key) || context.urlNames.has(key);
|
|
785
|
+
}
|
|
768
786
|
const MAX_DEPTH = 256;
|
|
769
787
|
const MAX_DEPTH_MARKER = "<afdata:max-depth>";
|
|
770
788
|
function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
@@ -774,16 +792,17 @@ function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
|
774
792
|
for (const k of Object.keys(value)) {
|
|
775
793
|
const v = value[k];
|
|
776
794
|
if (isSecretKey(k, context.secretNames)) {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
else {
|
|
784
|
-
value[k] = depth + 1 >= MAX_DEPTH ? MAX_DEPTH_MARKER : (redactSecrets(v, context, depth + 1), v);
|
|
795
|
+
// A null secret is an absent secret. Masking it would manufacture the
|
|
796
|
+
// appearance of a configured credential — readers cannot tell
|
|
797
|
+
// "***"-because-set from "***"-because-null. Redaction hides a value
|
|
798
|
+
// that exists; it does not invent one.
|
|
799
|
+
if (v !== null) {
|
|
800
|
+
value[k] = REDACTED_MARKER;
|
|
785
801
|
}
|
|
786
802
|
}
|
|
803
|
+
else if (isUrlKey(k, context)) {
|
|
804
|
+
value[k] = redactUrlValue(v, context, depth + 1);
|
|
805
|
+
}
|
|
787
806
|
else {
|
|
788
807
|
value[k] = depth + 1 >= MAX_DEPTH ? MAX_DEPTH_MARKER : (redactSecrets(v, context, depth + 1), v);
|
|
789
808
|
}
|
|
@@ -798,6 +817,32 @@ function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
|
798
817
|
}
|
|
799
818
|
}
|
|
800
819
|
}
|
|
820
|
+
function redactUrlValue(value, context, depth) {
|
|
821
|
+
if (depth >= MAX_DEPTH)
|
|
822
|
+
return MAX_DEPTH_MARKER;
|
|
823
|
+
if (typeof value === "string") {
|
|
824
|
+
return redactUrlFieldValue(value, context.secretNames);
|
|
825
|
+
}
|
|
826
|
+
if (Array.isArray(value)) {
|
|
827
|
+
for (let index = 0; index < value.length; index++) {
|
|
828
|
+
value[index] = redactUrlValue(value[index], context, depth + 1);
|
|
829
|
+
}
|
|
830
|
+
return value;
|
|
831
|
+
}
|
|
832
|
+
if (isObject(value)) {
|
|
833
|
+
for (const key of Object.keys(value)) {
|
|
834
|
+
const item = value[key];
|
|
835
|
+
if (isSecretKey(key, context.secretNames)) {
|
|
836
|
+
if (item !== null)
|
|
837
|
+
value[key] = REDACTED_MARKER;
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
value[key] = redactUrlValue(item, context, depth + 1);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return value;
|
|
845
|
+
}
|
|
801
846
|
function applyRedactionOptions(value, redactionOptions) {
|
|
802
847
|
applyRedactionPolicyWithContext(value, redactionOptions.policy, contextFromOptions(redactionOptions));
|
|
803
848
|
}
|
|
@@ -866,6 +911,120 @@ function redactUrlInStr(s, secretNames) {
|
|
|
866
911
|
const newFragment = fragment === null ? "" : `#${redactQuery(fragment, secretNames)}`;
|
|
867
912
|
return `${scheme}://${newAuthority}${newBeforeFragment}${newFragment}`;
|
|
868
913
|
}
|
|
914
|
+
function redactUrlsInTextWithContext(text, context) {
|
|
915
|
+
const output = [];
|
|
916
|
+
let copiedThrough = 0;
|
|
917
|
+
let searchFrom = 0;
|
|
918
|
+
while (true) {
|
|
919
|
+
const span = nextSchemeUrlSpan(text, searchFrom);
|
|
920
|
+
if (span === null)
|
|
921
|
+
break;
|
|
922
|
+
const [start, end] = span;
|
|
923
|
+
output.push(text.slice(copiedThrough, start));
|
|
924
|
+
const url = text.slice(start, end);
|
|
925
|
+
output.push(redactUrlInStr(url, context.secretNames) ?? url);
|
|
926
|
+
copiedThrough = end;
|
|
927
|
+
searchFrom = end;
|
|
928
|
+
}
|
|
929
|
+
if (output.length === 0)
|
|
930
|
+
return text;
|
|
931
|
+
output.push(text.slice(copiedThrough));
|
|
932
|
+
return output.join("");
|
|
933
|
+
}
|
|
934
|
+
function nextSchemeUrlSpan(text, startAt) {
|
|
935
|
+
let start = startAt;
|
|
936
|
+
while (start < text.length) {
|
|
937
|
+
// Only consider the head of a maximal scheme-byte run: a scheme cannot begin
|
|
938
|
+
// mid-run, so a position whose predecessor is a scheme byte was already
|
|
939
|
+
// covered by the run that contains it.
|
|
940
|
+
const atRunHead = start === 0 || !isSchemeCodeUnit(text.charCodeAt(start - 1));
|
|
941
|
+
if (atRunHead) {
|
|
942
|
+
let schemeEnd = start;
|
|
943
|
+
while (schemeEnd < text.length && isSchemeCodeUnit(text.charCodeAt(schemeEnd))) {
|
|
944
|
+
schemeEnd += 1;
|
|
945
|
+
}
|
|
946
|
+
// A scheme must start with a letter, but the run need not: prose like
|
|
947
|
+
// "2https://h/?token_secret=x" glues a digit onto the URL. Retry from the
|
|
948
|
+
// first letter *inside* the run instead of abandoning the span — giving up
|
|
949
|
+
// there fails open and leaks the whole query.
|
|
950
|
+
let schemeStart = -1;
|
|
951
|
+
for (let i = start; i < schemeEnd; i++) {
|
|
952
|
+
if (isAsciiAlpha(text.charCodeAt(i))) {
|
|
953
|
+
schemeStart = i;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (schemeStart >= 0 && text.startsWith("://", schemeEnd)) {
|
|
958
|
+
let end = schemeEnd + 3;
|
|
959
|
+
while (end < text.length
|
|
960
|
+
&& !isSpanTerminatorWhitespace(text.charCodeAt(end))
|
|
961
|
+
&& !isTextUrlDelimiter(text[end])) {
|
|
962
|
+
end += 1;
|
|
963
|
+
}
|
|
964
|
+
while (end > schemeEnd + 3
|
|
965
|
+
&& isTrailingTextUrlPunctuation(text[end - 1])) {
|
|
966
|
+
end -= 1;
|
|
967
|
+
}
|
|
968
|
+
if (end > schemeEnd + 3)
|
|
969
|
+
return [schemeStart, end];
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
start += 1;
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
function isSchemeCodeUnit(code) {
|
|
977
|
+
return isAsciiAlphanumeric(code) || code === 0x2b || code === 0x2d || code === 0x2e;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* True for a code unit that ends a URL span in prose: the Unicode `White_Space`
|
|
981
|
+
* property, written out here instead of delegated to `/\s/u`.
|
|
982
|
+
*
|
|
983
|
+
* The four AFDATA implementations must cut the span at the same byte, and each
|
|
984
|
+
* language's native predicate covers a different set — `\s` counts U+FEFF but
|
|
985
|
+
* not U+0085, Python's `str.isspace()` counts U+001C–U+001F, Rust and Go count
|
|
986
|
+
* neither. Divergence is a defect in both directions: ending a span early leaves
|
|
987
|
+
* the rest of the query readable, and ending it late pulls the following prose
|
|
988
|
+
* into the URL, where redacting the parameter it lands in deletes it. So the set
|
|
989
|
+
* is enumerated, and it is generous — a redaction helper exists to keep a log
|
|
990
|
+
* line readable, so anything a human reads as a space must end the URL.
|
|
991
|
+
*
|
|
992
|
+
* Two exclusions are deliberate, not oversights:
|
|
993
|
+
* - **U+FEFF** (zero-width no-break space) is not `White_Space`. `\s` counts it,
|
|
994
|
+
* which is what used to cut a URL carrying it short and leave the secret after
|
|
995
|
+
* it in the clear. It must not creep back in.
|
|
996
|
+
* - **U+001C–U+001F** (the file/group/record/unit separators) are not
|
|
997
|
+
* `White_Space` either; only Python's `str.isspace()` counted them.
|
|
998
|
+
*
|
|
999
|
+
* This is a different question from `isSingleUrl`, which asks whether a whole
|
|
1000
|
+
* string is one URL and stays ASCII-only. A span this scanner produces contains
|
|
1001
|
+
* no whitespace at all, so it satisfies that stricter gate either way.
|
|
1002
|
+
*/
|
|
1003
|
+
function isSpanTerminatorWhitespace(code) {
|
|
1004
|
+
return (
|
|
1005
|
+
// TAB, LF, VT, FF, CR, SPACE
|
|
1006
|
+
(code >= 0x09 && code <= 0x0d)
|
|
1007
|
+
|| code === 0x20
|
|
1008
|
+
// NEL, NBSP, OGHAM SPACE MARK
|
|
1009
|
+
|| code === 0x85
|
|
1010
|
+
|| code === 0xa0
|
|
1011
|
+
|| code === 0x1680
|
|
1012
|
+
// EN QUAD .. HAIR SPACE
|
|
1013
|
+
|| (code >= 0x2000 && code <= 0x200a)
|
|
1014
|
+
// LINE SEPARATOR, PARAGRAPH SEPARATOR, NARROW NO-BREAK SPACE,
|
|
1015
|
+
// MEDIUM MATHEMATICAL SPACE, IDEOGRAPHIC SPACE
|
|
1016
|
+
|| code === 0x2028
|
|
1017
|
+
|| code === 0x2029
|
|
1018
|
+
|| code === 0x202f
|
|
1019
|
+
|| code === 0x205f
|
|
1020
|
+
|| code === 0x3000);
|
|
1021
|
+
}
|
|
1022
|
+
function isTextUrlDelimiter(character) {
|
|
1023
|
+
return "\"'<>`,。;!?)》】」』".includes(character);
|
|
1024
|
+
}
|
|
1025
|
+
function isTrailingTextUrlPunctuation(character) {
|
|
1026
|
+
return ".,;!)]},。;!)》】".includes(character);
|
|
1027
|
+
}
|
|
869
1028
|
function redactUrlFieldValue(s, secretNames) {
|
|
870
1029
|
const redacted = redactUrlInStr(s, secretNames);
|
|
871
1030
|
if (redacted !== null)
|
|
@@ -1012,8 +1171,9 @@ function decimalIntText(value) {
|
|
|
1012
1171
|
}
|
|
1013
1172
|
return null;
|
|
1014
1173
|
}
|
|
1174
|
+
const MIN_I64 = -9223372036854775808n;
|
|
1015
1175
|
const MAX_I64 = 9223372036854775807n;
|
|
1016
|
-
function
|
|
1176
|
+
function signedI64(value) {
|
|
1017
1177
|
let text = null;
|
|
1018
1178
|
if (typeof value === "bigint") {
|
|
1019
1179
|
text = value.toString();
|
|
@@ -1030,14 +1190,16 @@ function nonNegativeI64(value) {
|
|
|
1030
1190
|
return null;
|
|
1031
1191
|
try {
|
|
1032
1192
|
const integer = BigInt(text);
|
|
1033
|
-
return integer >=
|
|
1193
|
+
return integer >= MIN_I64 && integer <= MAX_I64 ? integer : null;
|
|
1034
1194
|
}
|
|
1035
1195
|
catch {
|
|
1036
1196
|
return null;
|
|
1037
1197
|
}
|
|
1038
1198
|
}
|
|
1039
1199
|
function formatCents(value, symbol) {
|
|
1040
|
-
|
|
1200
|
+
const sign = value < 0n ? "-" : "";
|
|
1201
|
+
const magnitude = value < 0n ? -value : value;
|
|
1202
|
+
return `${sign}${symbol}${magnitude / 100n}.${String(magnitude % 100n).padStart(2, "0")}`;
|
|
1041
1203
|
}
|
|
1042
1204
|
function epochNsToMs(value) {
|
|
1043
1205
|
const text = decimalIntText(value);
|
|
@@ -1108,14 +1270,14 @@ function tryProcessField(key, value) {
|
|
|
1108
1270
|
// Group 2: compound currency suffixes
|
|
1109
1271
|
stripped = stripSuffixCI(key, "_usd_cents");
|
|
1110
1272
|
if (stripped !== null) {
|
|
1111
|
-
const integer =
|
|
1273
|
+
const integer = signedI64(value);
|
|
1112
1274
|
if (integer !== null)
|
|
1113
1275
|
return [stripped, formatCents(integer, "$")];
|
|
1114
1276
|
return null;
|
|
1115
1277
|
}
|
|
1116
1278
|
stripped = stripSuffixCI(key, "_eur_cents");
|
|
1117
1279
|
if (stripped !== null) {
|
|
1118
|
-
const integer =
|
|
1280
|
+
const integer = signedI64(value);
|
|
1119
1281
|
if (integer !== null)
|
|
1120
1282
|
return [stripped, formatCents(integer, "\u20ac")];
|
|
1121
1283
|
return null;
|
|
@@ -1123,18 +1285,22 @@ function tryProcessField(key, value) {
|
|
|
1123
1285
|
const gc = tryStripGenericCents(key);
|
|
1124
1286
|
if (gc !== null) {
|
|
1125
1287
|
const [gcStripped, code] = gc;
|
|
1126
|
-
const integer =
|
|
1288
|
+
const integer = signedI64(value);
|
|
1127
1289
|
if (integer !== null) {
|
|
1128
|
-
|
|
1290
|
+
const sign = integer < 0n ? "-" : "";
|
|
1291
|
+
const magnitude = integer < 0n ? -integer : integer;
|
|
1292
|
+
return [gcStripped, `${sign}${magnitude / 100n}.${String(magnitude % 100n).padStart(2, "0")} ${code.toUpperCase()}`];
|
|
1129
1293
|
}
|
|
1130
1294
|
return null;
|
|
1131
1295
|
}
|
|
1132
1296
|
const gm = tryStripGenericMicro(key);
|
|
1133
1297
|
if (gm !== null) {
|
|
1134
1298
|
const [gmStripped, code] = gm;
|
|
1135
|
-
const integer =
|
|
1299
|
+
const integer = signedI64(value);
|
|
1136
1300
|
if (integer !== null) {
|
|
1137
|
-
|
|
1301
|
+
const sign = integer < 0n ? "-" : "";
|
|
1302
|
+
const magnitude = integer < 0n ? -integer : integer;
|
|
1303
|
+
return [gmStripped, `${sign}${magnitude / 1000000n}.${String(magnitude % 1000000n).padStart(6, "0")} ${code.toUpperCase()}`];
|
|
1138
1304
|
}
|
|
1139
1305
|
return null;
|
|
1140
1306
|
}
|
|
@@ -1193,9 +1359,12 @@ function tryProcessField(key, value) {
|
|
|
1193
1359
|
// Group 5: short suffixes (last to avoid false positives)
|
|
1194
1360
|
stripped = stripSuffixCI(key, "_jpy");
|
|
1195
1361
|
if (stripped !== null) {
|
|
1196
|
-
const integer =
|
|
1197
|
-
if (integer !== null)
|
|
1198
|
-
|
|
1362
|
+
const integer = signedI64(value);
|
|
1363
|
+
if (integer !== null) {
|
|
1364
|
+
const sign = integer < 0n ? "-" : "";
|
|
1365
|
+
const magnitude = integer < 0n ? -integer : integer;
|
|
1366
|
+
return [stripped, `${sign}\u00a5${formatWithCommas(magnitude)}`];
|
|
1367
|
+
}
|
|
1199
1368
|
return null;
|
|
1200
1369
|
}
|
|
1201
1370
|
stripped = stripSuffixCI(key, "_ns");
|
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, PlainStyle, type OutputOptions, outputOptionsForPolicy, redactedValue, redactArgv, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
|
|
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, redactUrlsInText, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
|
|
5
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
|
@@ -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, PlainStyle, outputOptionsForPolicy, redactedValue, redactArgv, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
|
|
4
|
+
export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy, PlainStyle, outputOptionsForPolicy, redactedValue, redactArgv, redactUrlSecrets, redactUrlsInText, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
|
|
5
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.
|
|
3
|
+
"version": "0.31.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 Markdown structure and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|