agent-first-data 0.30.0 → 0.32.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 +87 -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 +183 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
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,6 +32,13 @@
|
|
|
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
|
},
|
|
@@ -164,7 +171,8 @@
|
|
|
164
171
|
"i64",
|
|
165
172
|
"finite_f64",
|
|
166
173
|
"enum",
|
|
167
|
-
"json"
|
|
174
|
+
"json",
|
|
175
|
+
"uuid"
|
|
168
176
|
]
|
|
169
177
|
},
|
|
170
178
|
"value_name": {
|
|
@@ -178,6 +186,15 @@
|
|
|
178
186
|
"$ref": "#/$defs/nonEmpty"
|
|
179
187
|
}
|
|
180
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
|
+
},
|
|
181
198
|
"default": true,
|
|
182
199
|
"repeatable": {
|
|
183
200
|
"const": true
|
|
@@ -186,6 +203,10 @@
|
|
|
186
203
|
"type": "boolean",
|
|
187
204
|
"description": "This argument's value must never be echoed back in help, errors, or logs."
|
|
188
205
|
},
|
|
206
|
+
"sources": {
|
|
207
|
+
"description": "Sources this string argument accepts beside a literal value. The core validates that a value names only a listed scheme and renders env/file/file+FORMAT/stream syntax into help; reading the source is the host's, at the moment it chooses.",
|
|
208
|
+
"$ref": "#/$defs/sourceSet"
|
|
209
|
+
},
|
|
189
210
|
"about": {
|
|
190
211
|
"$ref": "#/$defs/nonEmpty"
|
|
191
212
|
}
|
|
@@ -351,6 +372,71 @@
|
|
|
351
372
|
}
|
|
352
373
|
}
|
|
353
374
|
]
|
|
375
|
+
},
|
|
376
|
+
"sourceSet": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"required": [
|
|
379
|
+
"schemes"
|
|
380
|
+
],
|
|
381
|
+
"additionalProperties": false,
|
|
382
|
+
"anyOf": [
|
|
383
|
+
{
|
|
384
|
+
"properties": {
|
|
385
|
+
"schemes": {
|
|
386
|
+
"minItems": 1
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"required": [
|
|
392
|
+
"host_schemes"
|
|
393
|
+
],
|
|
394
|
+
"properties": {
|
|
395
|
+
"host_schemes": {
|
|
396
|
+
"minItems": 1
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
],
|
|
401
|
+
"properties": {
|
|
402
|
+
"schemes": {
|
|
403
|
+
"type": "array",
|
|
404
|
+
"uniqueItems": true,
|
|
405
|
+
"items": {
|
|
406
|
+
"enum": [
|
|
407
|
+
"env",
|
|
408
|
+
"file",
|
|
409
|
+
"stdin",
|
|
410
|
+
"fd",
|
|
411
|
+
"prompt"
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
"host_schemes": {
|
|
416
|
+
"description": "Schemes the host parses and reads itself, declared so help and validation know they exist. Names must not collide with each other or the built-in/reserved grammar; CliSpec::build validates that each syntax begins with its own name and a colon.",
|
|
417
|
+
"type": "array",
|
|
418
|
+
"minItems": 1,
|
|
419
|
+
"uniqueItems": true,
|
|
420
|
+
"items": {
|
|
421
|
+
"type": "object",
|
|
422
|
+
"required": [
|
|
423
|
+
"name",
|
|
424
|
+
"syntax"
|
|
425
|
+
],
|
|
426
|
+
"additionalProperties": false,
|
|
427
|
+
"properties": {
|
|
428
|
+
"name": {
|
|
429
|
+
"type": "string",
|
|
430
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
431
|
+
},
|
|
432
|
+
"syntax": {
|
|
433
|
+
"$ref": "#/$defs/nonEmpty",
|
|
434
|
+
"description": "Displayed source syntax beginning with the exact host scheme name, a colon, and a non-empty value placeholder."
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
354
440
|
}
|
|
355
441
|
}
|
|
356
442
|
}
|
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) {
|
|
@@ -782,13 +800,8 @@ function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
|
782
800
|
value[k] = REDACTED_MARKER;
|
|
783
801
|
}
|
|
784
802
|
}
|
|
785
|
-
else if (
|
|
786
|
-
|
|
787
|
-
value[k] = redactUrlFieldValue(v, context.secretNames);
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
value[k] = depth + 1 >= MAX_DEPTH ? MAX_DEPTH_MARKER : (redactSecrets(v, context, depth + 1), v);
|
|
791
|
-
}
|
|
803
|
+
else if (isUrlKey(k, context)) {
|
|
804
|
+
value[k] = redactUrlValue(v, context, depth + 1);
|
|
792
805
|
}
|
|
793
806
|
else {
|
|
794
807
|
value[k] = depth + 1 >= MAX_DEPTH ? MAX_DEPTH_MARKER : (redactSecrets(v, context, depth + 1), v);
|
|
@@ -804,6 +817,32 @@ function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
|
804
817
|
}
|
|
805
818
|
}
|
|
806
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
|
+
}
|
|
807
846
|
function applyRedactionOptions(value, redactionOptions) {
|
|
808
847
|
applyRedactionPolicyWithContext(value, redactionOptions.policy, contextFromOptions(redactionOptions));
|
|
809
848
|
}
|
|
@@ -872,6 +911,120 @@ function redactUrlInStr(s, secretNames) {
|
|
|
872
911
|
const newFragment = fragment === null ? "" : `#${redactQuery(fragment, secretNames)}`;
|
|
873
912
|
return `${scheme}://${newAuthority}${newBeforeFragment}${newFragment}`;
|
|
874
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
|
+
}
|
|
875
1028
|
function redactUrlFieldValue(s, secretNames) {
|
|
876
1029
|
const redacted = redactUrlInStr(s, secretNames);
|
|
877
1030
|
if (redacted !== null)
|
|
@@ -1018,8 +1171,9 @@ function decimalIntText(value) {
|
|
|
1018
1171
|
}
|
|
1019
1172
|
return null;
|
|
1020
1173
|
}
|
|
1174
|
+
const MIN_I64 = -9223372036854775808n;
|
|
1021
1175
|
const MAX_I64 = 9223372036854775807n;
|
|
1022
|
-
function
|
|
1176
|
+
function signedI64(value) {
|
|
1023
1177
|
let text = null;
|
|
1024
1178
|
if (typeof value === "bigint") {
|
|
1025
1179
|
text = value.toString();
|
|
@@ -1036,14 +1190,16 @@ function nonNegativeI64(value) {
|
|
|
1036
1190
|
return null;
|
|
1037
1191
|
try {
|
|
1038
1192
|
const integer = BigInt(text);
|
|
1039
|
-
return integer >=
|
|
1193
|
+
return integer >= MIN_I64 && integer <= MAX_I64 ? integer : null;
|
|
1040
1194
|
}
|
|
1041
1195
|
catch {
|
|
1042
1196
|
return null;
|
|
1043
1197
|
}
|
|
1044
1198
|
}
|
|
1045
1199
|
function formatCents(value, symbol) {
|
|
1046
|
-
|
|
1200
|
+
const sign = value < 0n ? "-" : "";
|
|
1201
|
+
const magnitude = value < 0n ? -value : value;
|
|
1202
|
+
return `${sign}${symbol}${magnitude / 100n}.${String(magnitude % 100n).padStart(2, "0")}`;
|
|
1047
1203
|
}
|
|
1048
1204
|
function epochNsToMs(value) {
|
|
1049
1205
|
const text = decimalIntText(value);
|
|
@@ -1114,14 +1270,14 @@ function tryProcessField(key, value) {
|
|
|
1114
1270
|
// Group 2: compound currency suffixes
|
|
1115
1271
|
stripped = stripSuffixCI(key, "_usd_cents");
|
|
1116
1272
|
if (stripped !== null) {
|
|
1117
|
-
const integer =
|
|
1273
|
+
const integer = signedI64(value);
|
|
1118
1274
|
if (integer !== null)
|
|
1119
1275
|
return [stripped, formatCents(integer, "$")];
|
|
1120
1276
|
return null;
|
|
1121
1277
|
}
|
|
1122
1278
|
stripped = stripSuffixCI(key, "_eur_cents");
|
|
1123
1279
|
if (stripped !== null) {
|
|
1124
|
-
const integer =
|
|
1280
|
+
const integer = signedI64(value);
|
|
1125
1281
|
if (integer !== null)
|
|
1126
1282
|
return [stripped, formatCents(integer, "\u20ac")];
|
|
1127
1283
|
return null;
|
|
@@ -1129,18 +1285,22 @@ function tryProcessField(key, value) {
|
|
|
1129
1285
|
const gc = tryStripGenericCents(key);
|
|
1130
1286
|
if (gc !== null) {
|
|
1131
1287
|
const [gcStripped, code] = gc;
|
|
1132
|
-
const integer =
|
|
1288
|
+
const integer = signedI64(value);
|
|
1133
1289
|
if (integer !== null) {
|
|
1134
|
-
|
|
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()}`];
|
|
1135
1293
|
}
|
|
1136
1294
|
return null;
|
|
1137
1295
|
}
|
|
1138
1296
|
const gm = tryStripGenericMicro(key);
|
|
1139
1297
|
if (gm !== null) {
|
|
1140
1298
|
const [gmStripped, code] = gm;
|
|
1141
|
-
const integer =
|
|
1299
|
+
const integer = signedI64(value);
|
|
1142
1300
|
if (integer !== null) {
|
|
1143
|
-
|
|
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()}`];
|
|
1144
1304
|
}
|
|
1145
1305
|
return null;
|
|
1146
1306
|
}
|
|
@@ -1199,9 +1359,12 @@ function tryProcessField(key, value) {
|
|
|
1199
1359
|
// Group 5: short suffixes (last to avoid false positives)
|
|
1200
1360
|
stripped = stripSuffixCI(key, "_jpy");
|
|
1201
1361
|
if (stripped !== null) {
|
|
1202
|
-
const integer =
|
|
1203
|
-
if (integer !== null)
|
|
1204
|
-
|
|
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
|
+
}
|
|
1205
1368
|
return null;
|
|
1206
1369
|
}
|
|
1207
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.32.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": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^26.1.2",
|
|
31
|
-
"tsx": "^4.23.
|
|
31
|
+
"tsx": "^4.23.10",
|
|
32
32
|
"typescript": "^7.0.2"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|