agent-first-data 0.8.1 → 0.10.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 +23 -5
- package/dist/cli.d.ts +6 -1
- package/dist/cli.js +12 -1
- package/dist/format.d.ts +41 -9
- package/dist/format.js +339 -46
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,13 @@ redactedValueWith(value: unknown, redactionPolicy: RedactionPolicy): JsonValue
|
|
|
92
92
|
redactedValueWithOptions(value: unknown, redactionOptions: RedactionOptions): JsonValue
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
`_url` fields are scrubbed in place during redaction (userinfo password, plus query parameters whose name follows the `_secret`/`secretNames` rule). For a URL inside a free-form string, redact it directly before interpolating:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
redactUrlSecrets(url: string): string
|
|
99
|
+
redactUrlSecretsWithOptions(url: string, redactionOptions: RedactionOptions): string
|
|
100
|
+
```
|
|
101
|
+
|
|
95
102
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
96
103
|
|
|
97
104
|
**Example:**
|
|
@@ -131,16 +138,16 @@ const notFound = buildJson(
|
|
|
131
138
|
|
|
132
139
|
### Output Formatters (returns string)
|
|
133
140
|
|
|
134
|
-
Format values for CLI output and logs. `outputJson` uses full `_secret` redaction by default. `outputJsonWith` supports explicit scoped policies. Use
|
|
141
|
+
Format values for CLI output and logs. `outputJson` uses full `_secret` redaction by default. `outputJsonWith` supports explicit scoped policies. Use `OutputOptions` to pass legacy secret names such as `api_key` or request schema-preserving YAML/plain rendering with `OutputStyle.Raw`.
|
|
135
142
|
|
|
136
143
|
```typescript
|
|
137
144
|
outputJson(value: JsonValue): string // Single-line JSON, original keys, for programs/logs
|
|
138
145
|
outputJsonWith(value: JsonValue, redactionPolicy: RedactionPolicy): string
|
|
139
|
-
outputJsonWithOptions(value: JsonValue,
|
|
146
|
+
outputJsonWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
140
147
|
outputYaml(value: JsonValue): string // Multi-line YAML, keys stripped, values formatted
|
|
141
|
-
outputYamlWithOptions(value: JsonValue,
|
|
148
|
+
outputYamlWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
142
149
|
outputPlain(value: JsonValue): string // Single-line logfmt, keys stripped, values formatted
|
|
143
|
-
outputPlainWithOptions(value: JsonValue,
|
|
150
|
+
outputPlainWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
144
151
|
```
|
|
145
152
|
|
|
146
153
|
```typescript
|
|
@@ -154,9 +161,19 @@ type RedactionOptions = {
|
|
|
154
161
|
policy?: RedactionPolicy;
|
|
155
162
|
secretNames?: readonly string[]; // legacy names like "api_key" or "authorization"
|
|
156
163
|
}
|
|
164
|
+
|
|
165
|
+
enum OutputStyle {
|
|
166
|
+
Readable = "Readable",
|
|
167
|
+
Raw = "Raw",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type OutputOptions = {
|
|
171
|
+
redaction?: RedactionOptions;
|
|
172
|
+
style?: OutputStyle;
|
|
173
|
+
}
|
|
157
174
|
```
|
|
158
175
|
|
|
159
|
-
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching.
|
|
176
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. `OutputOptions` combines `RedactionOptions` with `OutputStyle.Readable` (default suffix stripping and value formatting) or `OutputStyle.Raw` (no suffix stripping or value formatting).
|
|
160
177
|
|
|
161
178
|
**Example:**
|
|
162
179
|
```typescript
|
|
@@ -222,6 +239,7 @@ type OutputFormat = "json" | "yaml" | "plain"
|
|
|
222
239
|
cliParseOutput(s: string): OutputFormat // Parse --output flag; throws on unknown
|
|
223
240
|
cliParseLogFilters(entries: string[]): string[] // Normalize --log: trim, lowercase, dedup, remove empty
|
|
224
241
|
cliOutput(value: JsonValue, format: OutputFormat): string // Dispatch to outputJson/Yaml/Plain
|
|
242
|
+
cliOutputWithOptions(value: JsonValue, format: OutputFormat, outputOptions: OutputOptions): string
|
|
225
243
|
buildCliError(message: string, hint?: string): JsonValue // {code:"error", error_code:"invalid_request", hint?, retryable:false, trace:{duration_ms:0}}
|
|
226
244
|
```
|
|
227
245
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA CLI helpers — output format parsing, log filter normalization, error building.
|
|
3
3
|
*/
|
|
4
|
-
import { JsonValue } from "./format.js";
|
|
4
|
+
import { JsonValue, type OutputOptions } from "./format.js";
|
|
5
5
|
/** Output format for CLI and pipe/MCP modes. */
|
|
6
6
|
export type OutputFormat = "json" | "yaml" | "plain";
|
|
7
7
|
/**
|
|
@@ -29,6 +29,11 @@ export declare function cliParseLogFilters(entries: string[]): string[];
|
|
|
29
29
|
* cliOutput({ code: "ok" }, "plain") // → "code=ok"
|
|
30
30
|
*/
|
|
31
31
|
export declare function cliOutput(value: JsonValue, format: OutputFormat): string;
|
|
32
|
+
/**
|
|
33
|
+
* Dispatch output formatting with explicit redaction and style.
|
|
34
|
+
* JSON ignores OutputStyle and preserves original keys and values after redaction.
|
|
35
|
+
*/
|
|
36
|
+
export declare function cliOutputWithOptions(value: JsonValue, format: OutputFormat, outputOptions: OutputOptions): string;
|
|
32
37
|
/**
|
|
33
38
|
* Build a standard CLI parse error value.
|
|
34
39
|
* Use when flag parsing fails or a flag value is invalid.
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA CLI helpers — output format parsing, log filter normalization, error building.
|
|
3
3
|
*/
|
|
4
|
-
import { outputJson, outputYaml, outputPlain } from "./format.js";
|
|
4
|
+
import { outputJson, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, } from "./format.js";
|
|
5
5
|
/**
|
|
6
6
|
* Parse the --output flag value into an OutputFormat.
|
|
7
7
|
* Throws on unknown values; catch and pass message to buildCliError.
|
|
@@ -47,6 +47,17 @@ export function cliOutput(value, format) {
|
|
|
47
47
|
return outputPlain(value);
|
|
48
48
|
return outputJson(value);
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Dispatch output formatting with explicit redaction and style.
|
|
52
|
+
* JSON ignores OutputStyle and preserves original keys and values after redaction.
|
|
53
|
+
*/
|
|
54
|
+
export function cliOutputWithOptions(value, format, outputOptions) {
|
|
55
|
+
if (format === "yaml")
|
|
56
|
+
return outputYamlWithOptions(value, outputOptions);
|
|
57
|
+
if (format === "plain")
|
|
58
|
+
return outputPlainWithOptions(value, outputOptions);
|
|
59
|
+
return outputJsonWithOptions(value, outputOptions);
|
|
60
|
+
}
|
|
50
61
|
/**
|
|
51
62
|
* Build a standard CLI parse error value.
|
|
52
63
|
* Use when flag parsing fails or a flag value is invalid.
|
package/dist/format.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA output formatting and protocol templates.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 18 public APIs and 4 types: protocol builders, value redactors (copy and
|
|
5
|
+
* in-place; cover _secret and _url fields), output formatters, URL-string
|
|
6
|
+
* redactors (redactUrlSecrets / WithOptions), parseSize, RedactionPolicy,
|
|
7
|
+
* RedactionOptions, OutputStyle, and OutputOptions.
|
|
6
8
|
*/
|
|
7
9
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
8
10
|
[key: string]: JsonValue;
|
|
@@ -23,24 +25,36 @@ export type RedactionOptions = {
|
|
|
23
25
|
policy?: RedactionPolicy;
|
|
24
26
|
/**
|
|
25
27
|
* Field names to treat as secrets in addition to _secret suffixes.
|
|
26
|
-
* Matching is exact field-name equality at any nesting level.
|
|
28
|
+
* Matching is exact field-name equality at any nesting level. The same
|
|
29
|
+
* list also matches URL query-parameter names inside _url fields (see
|
|
30
|
+
* redactUrlSecrets).
|
|
27
31
|
*/
|
|
28
32
|
secretNames?: readonly string[];
|
|
29
33
|
};
|
|
34
|
+
export declare enum OutputStyle {
|
|
35
|
+
Readable = "Readable",
|
|
36
|
+
Raw = "Raw"
|
|
37
|
+
}
|
|
38
|
+
export type OutputOptions = {
|
|
39
|
+
/** Redaction options applied before rendering. */
|
|
40
|
+
redaction?: RedactionOptions;
|
|
41
|
+
/** Rendering style for YAML/plain output. Omitted means readable. */
|
|
42
|
+
style?: OutputStyle;
|
|
43
|
+
};
|
|
30
44
|
/** Format as single-line JSON. Secrets redacted, original keys, raw values. */
|
|
31
45
|
export declare function outputJson(value: JsonValue): string;
|
|
32
46
|
/** Format as single-line JSON with explicit redaction policy. */
|
|
33
47
|
export declare function outputJsonWith(value: JsonValue, redactionPolicy: RedactionPolicy): string;
|
|
34
|
-
/** Format as single-line JSON with explicit
|
|
35
|
-
export declare function outputJsonWithOptions(value: JsonValue,
|
|
48
|
+
/** Format as single-line JSON with explicit output options. */
|
|
49
|
+
export declare function outputJsonWithOptions(value: JsonValue, outputOptions: OutputOptions): string;
|
|
36
50
|
/** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
|
|
37
51
|
export declare function outputYaml(value: JsonValue): string;
|
|
38
|
-
/** Format as multi-line YAML with explicit
|
|
39
|
-
export declare function outputYamlWithOptions(value: JsonValue,
|
|
52
|
+
/** Format as multi-line YAML with explicit output options. */
|
|
53
|
+
export declare function outputYamlWithOptions(value: JsonValue, outputOptions: OutputOptions): string;
|
|
40
54
|
/** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
|
|
41
55
|
export declare function outputPlain(value: JsonValue): string;
|
|
42
|
-
/** Format as single-line logfmt with explicit
|
|
43
|
-
export declare function outputPlainWithOptions(value: JsonValue,
|
|
56
|
+
/** Format as single-line logfmt with explicit output options. */
|
|
57
|
+
export declare function outputPlainWithOptions(value: JsonValue, outputOptions: OutputOptions): string;
|
|
44
58
|
/** Redact _secret fields in-place. */
|
|
45
59
|
export declare function internalRedactSecrets(value: JsonValue): void;
|
|
46
60
|
/** Redact secret fields in-place using explicit redaction options. */
|
|
@@ -51,6 +65,24 @@ export declare function redactedValue(value: unknown): JsonValue;
|
|
|
51
65
|
export declare function redactedValueWith(value: unknown, redactionPolicy: RedactionPolicy): JsonValue;
|
|
52
66
|
/** Return a JSON-safe copy with explicit redaction options applied. */
|
|
53
67
|
export declare function redactedValueWithOptions(value: unknown, redactionOptions: RedactionOptions): JsonValue;
|
|
68
|
+
/**
|
|
69
|
+
* Redact secret components of a single URL string, using default options.
|
|
70
|
+
*
|
|
71
|
+
* Returns url with its userinfo password and any _secret-suffixed query
|
|
72
|
+
* parameter values replaced by "***". See redactUrlSecretsWithOptions.
|
|
73
|
+
*/
|
|
74
|
+
export declare function redactUrlSecrets(url: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Redact secret components of a single URL string.
|
|
77
|
+
*
|
|
78
|
+
* A query parameter is redacted iff its (form-decoded) name ends in
|
|
79
|
+
* _secret/_SECRET or matches an exact entry in secretNames. The userinfo
|
|
80
|
+
* password (scheme://user:pass@host) is always redacted as a structural rule.
|
|
81
|
+
* Only the secret spans are replaced with "***"; every other byte is preserved.
|
|
82
|
+
* A string that is not a single, whitespace-free, scheme-prefixed URL
|
|
83
|
+
* (including a URL embedded in surrounding prose) is returned unchanged.
|
|
84
|
+
*/
|
|
85
|
+
export declare function redactUrlSecretsWithOptions(url: string, redactionOptions: RedactionOptions): string;
|
|
54
86
|
/**
|
|
55
87
|
* Parse a human-readable size string into bytes.
|
|
56
88
|
* Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
|
package/dist/format.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA output formatting and protocol templates.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 18 public APIs and 4 types: protocol builders, value redactors (copy and
|
|
5
|
+
* in-place; cover _secret and _url fields), output formatters, URL-string
|
|
6
|
+
* redactors (redactUrlSecrets / WithOptions), parseSize, RedactionPolicy,
|
|
7
|
+
* RedactionOptions, OutputStyle, and OutputOptions.
|
|
6
8
|
*/
|
|
7
9
|
// ═══════════════════════════════════════════
|
|
8
10
|
// Public API: Protocol Builders
|
|
@@ -40,6 +42,11 @@ export var RedactionPolicy;
|
|
|
40
42
|
RedactionPolicy["RedactionNone"] = "RedactionNone";
|
|
41
43
|
RedactionPolicy["RedactionStrict"] = "RedactionStrict";
|
|
42
44
|
})(RedactionPolicy || (RedactionPolicy = {}));
|
|
45
|
+
export var OutputStyle;
|
|
46
|
+
(function (OutputStyle) {
|
|
47
|
+
OutputStyle["Readable"] = "Readable";
|
|
48
|
+
OutputStyle["Raw"] = "Raw";
|
|
49
|
+
})(OutputStyle || (OutputStyle = {}));
|
|
43
50
|
/** Format as single-line JSON. Secrets redacted, original keys, raw values. */
|
|
44
51
|
export function outputJson(value) {
|
|
45
52
|
return JSON.stringify(redactedValue(value));
|
|
@@ -48,39 +55,40 @@ export function outputJson(value) {
|
|
|
48
55
|
export function outputJsonWith(value, redactionPolicy) {
|
|
49
56
|
return JSON.stringify(redactedValueWith(value, redactionPolicy));
|
|
50
57
|
}
|
|
51
|
-
/** Format as single-line JSON with explicit
|
|
52
|
-
export function outputJsonWithOptions(value,
|
|
53
|
-
return JSON.stringify(redactedValueWithOptions(value,
|
|
58
|
+
/** Format as single-line JSON with explicit output options. */
|
|
59
|
+
export function outputJsonWithOptions(value, outputOptions) {
|
|
60
|
+
return JSON.stringify(redactedValueWithOptions(value, outputOptions.redaction ?? {}));
|
|
54
61
|
}
|
|
55
62
|
/** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
|
|
56
63
|
export function outputYaml(value) {
|
|
57
|
-
|
|
58
|
-
const lines = ["---"];
|
|
59
|
-
renderYamlProcessed(value, 0, lines);
|
|
60
|
-
return lines.join("\n");
|
|
64
|
+
return outputYamlWithOptions(value, {});
|
|
61
65
|
}
|
|
62
|
-
/** Format as multi-line YAML with explicit
|
|
63
|
-
export function outputYamlWithOptions(value,
|
|
64
|
-
value = redactedValueWithOptions(value,
|
|
66
|
+
/** Format as multi-line YAML with explicit output options. */
|
|
67
|
+
export function outputYamlWithOptions(value, outputOptions) {
|
|
68
|
+
value = redactedValueWithOptions(value, outputOptions.redaction ?? {});
|
|
65
69
|
const lines = ["---"];
|
|
66
|
-
|
|
70
|
+
if (outputOptions.style === OutputStyle.Raw) {
|
|
71
|
+
renderYamlRaw(value, 0, lines);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
renderYamlProcessed(value, 0, lines);
|
|
75
|
+
}
|
|
67
76
|
return lines.join("\n");
|
|
68
77
|
}
|
|
69
78
|
/** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
|
|
70
79
|
export function outputPlain(value) {
|
|
71
|
-
|
|
72
|
-
const pairs = [];
|
|
73
|
-
collectPlainPairs(value, "", pairs);
|
|
74
|
-
pairs.sort(([a], [b]) => jcsCompare(a, b));
|
|
75
|
-
return pairs
|
|
76
|
-
.map(([k, v]) => `${k}=${quoteLogfmtValue(v)}`)
|
|
77
|
-
.join(" ");
|
|
80
|
+
return outputPlainWithOptions(value, {});
|
|
78
81
|
}
|
|
79
|
-
/** Format as single-line logfmt with explicit
|
|
80
|
-
export function outputPlainWithOptions(value,
|
|
81
|
-
value = redactedValueWithOptions(value,
|
|
82
|
+
/** Format as single-line logfmt with explicit output options. */
|
|
83
|
+
export function outputPlainWithOptions(value, outputOptions) {
|
|
84
|
+
value = redactedValueWithOptions(value, outputOptions.redaction ?? {});
|
|
82
85
|
const pairs = [];
|
|
83
|
-
|
|
86
|
+
if (outputOptions.style === OutputStyle.Raw) {
|
|
87
|
+
collectPlainPairsRaw(value, "", pairs);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
collectPlainPairs(value, "", pairs);
|
|
91
|
+
}
|
|
84
92
|
pairs.sort(([a], [b]) => jcsCompare(a, b));
|
|
85
93
|
return pairs
|
|
86
94
|
.map(([k, v]) => `${k}=${quoteLogfmtValue(v)}`)
|
|
@@ -115,6 +123,29 @@ export function redactedValueWithOptions(value, redactionOptions) {
|
|
|
115
123
|
applyRedactionOptions(v, redactionOptions);
|
|
116
124
|
return v;
|
|
117
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Redact secret components of a single URL string, using default options.
|
|
128
|
+
*
|
|
129
|
+
* Returns url with its userinfo password and any _secret-suffixed query
|
|
130
|
+
* parameter values replaced by "***". See redactUrlSecretsWithOptions.
|
|
131
|
+
*/
|
|
132
|
+
export function redactUrlSecrets(url) {
|
|
133
|
+
return redactUrlSecretsWithOptions(url, {});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Redact secret components of a single URL string.
|
|
137
|
+
*
|
|
138
|
+
* A query parameter is redacted iff its (form-decoded) name ends in
|
|
139
|
+
* _secret/_SECRET or matches an exact entry in secretNames. The userinfo
|
|
140
|
+
* password (scheme://user:pass@host) is always redacted as a structural rule.
|
|
141
|
+
* Only the secret spans are replaced with "***"; every other byte is preserved.
|
|
142
|
+
* A string that is not a single, whitespace-free, scheme-prefixed URL
|
|
143
|
+
* (including a URL embedded in surrounding prose) is returned unchanged.
|
|
144
|
+
*/
|
|
145
|
+
export function redactUrlSecretsWithOptions(url, redactionOptions) {
|
|
146
|
+
const redacted = redactUrlInStr(url, secretNameSet(redactionOptions));
|
|
147
|
+
return redacted ?? url;
|
|
148
|
+
}
|
|
118
149
|
/**
|
|
119
150
|
* Parse a human-readable size string into bytes.
|
|
120
151
|
* Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
|
|
@@ -151,82 +182,240 @@ export function parseSize(s) {
|
|
|
151
182
|
return null;
|
|
152
183
|
return result;
|
|
153
184
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const EMPTY_SECRET_NAMES = new Set();
|
|
185
|
+
const DEFAULT_CONTEXT = {
|
|
186
|
+
secretNames: new Set(),
|
|
187
|
+
};
|
|
158
188
|
function secretNameSet(redactionOptions) {
|
|
159
189
|
return new Set(redactionOptions.secretNames ?? []);
|
|
160
190
|
}
|
|
191
|
+
function contextFromOptions(redactionOptions) {
|
|
192
|
+
return { secretNames: secretNameSet(redactionOptions) };
|
|
193
|
+
}
|
|
161
194
|
function keyHasSecretSuffix(key) {
|
|
162
195
|
return key.endsWith("_secret") || key.endsWith("_SECRET");
|
|
163
196
|
}
|
|
197
|
+
function keyHasUrlSuffix(key) {
|
|
198
|
+
return key.endsWith("_url") || key.endsWith("_URL");
|
|
199
|
+
}
|
|
164
200
|
function isSecretKey(key, secretNames) {
|
|
165
201
|
return keyHasSecretSuffix(key) || secretNames.has(key);
|
|
166
202
|
}
|
|
167
|
-
function redactSecrets(value,
|
|
203
|
+
function redactSecrets(value, context = DEFAULT_CONTEXT) {
|
|
168
204
|
if (isObject(value)) {
|
|
169
205
|
for (const k of Object.keys(value)) {
|
|
170
|
-
|
|
171
|
-
|
|
206
|
+
const v = value[k];
|
|
207
|
+
if (isSecretKey(k, context.secretNames)) {
|
|
172
208
|
if (isObject(v) || Array.isArray(v)) {
|
|
173
|
-
redactSecrets(v,
|
|
209
|
+
redactSecrets(v, context);
|
|
174
210
|
}
|
|
175
211
|
else {
|
|
176
212
|
value[k] = "***";
|
|
177
213
|
}
|
|
178
214
|
}
|
|
215
|
+
else if (keyHasUrlSuffix(k) && typeof v === "string") {
|
|
216
|
+
const redacted = redactUrlInStr(v, context.secretNames);
|
|
217
|
+
if (redacted !== null)
|
|
218
|
+
value[k] = redacted;
|
|
219
|
+
}
|
|
179
220
|
else {
|
|
180
|
-
redactSecrets(
|
|
221
|
+
redactSecrets(v, context);
|
|
181
222
|
}
|
|
182
223
|
}
|
|
183
224
|
}
|
|
184
225
|
else if (Array.isArray(value)) {
|
|
185
|
-
for (
|
|
186
|
-
redactSecrets(
|
|
226
|
+
for (let i = 0; i < value.length; i++) {
|
|
227
|
+
redactSecrets(value[i], context);
|
|
187
228
|
}
|
|
188
229
|
}
|
|
189
230
|
}
|
|
190
|
-
function redactSecretsStrict(value,
|
|
231
|
+
function redactSecretsStrict(value, context = DEFAULT_CONTEXT) {
|
|
191
232
|
if (isObject(value)) {
|
|
192
233
|
for (const k of Object.keys(value)) {
|
|
193
|
-
|
|
234
|
+
const v = value[k];
|
|
235
|
+
if (isSecretKey(k, context.secretNames)) {
|
|
194
236
|
value[k] = "***";
|
|
195
237
|
}
|
|
238
|
+
else if (keyHasUrlSuffix(k) && typeof v === "string") {
|
|
239
|
+
const redacted = redactUrlInStr(v, context.secretNames);
|
|
240
|
+
if (redacted !== null)
|
|
241
|
+
value[k] = redacted;
|
|
242
|
+
}
|
|
196
243
|
else {
|
|
197
|
-
redactSecretsStrict(
|
|
244
|
+
redactSecretsStrict(v, context);
|
|
198
245
|
}
|
|
199
246
|
}
|
|
200
247
|
}
|
|
201
248
|
else if (Array.isArray(value)) {
|
|
202
|
-
for (
|
|
203
|
-
redactSecretsStrict(
|
|
249
|
+
for (let i = 0; i < value.length; i++) {
|
|
250
|
+
redactSecretsStrict(value[i], context);
|
|
204
251
|
}
|
|
205
252
|
}
|
|
206
253
|
}
|
|
207
254
|
function applyRedactionPolicy(value, redactionPolicy) {
|
|
208
|
-
|
|
255
|
+
applyRedactionPolicyWithContext(value, redactionPolicy, DEFAULT_CONTEXT);
|
|
209
256
|
}
|
|
210
257
|
function applyRedactionOptions(value, redactionOptions) {
|
|
211
|
-
|
|
258
|
+
applyRedactionPolicyWithContext(value, redactionOptions.policy, contextFromOptions(redactionOptions));
|
|
212
259
|
}
|
|
213
|
-
function
|
|
260
|
+
function applyRedactionPolicyWithContext(value, redactionPolicy, context) {
|
|
214
261
|
switch (redactionPolicy) {
|
|
215
262
|
case RedactionPolicy.RedactionTraceOnly:
|
|
216
263
|
if (isObject(value) && value.trace !== undefined) {
|
|
217
|
-
redactSecrets(value.trace,
|
|
264
|
+
redactSecrets(value.trace, context);
|
|
218
265
|
}
|
|
219
266
|
break;
|
|
220
267
|
case RedactionPolicy.RedactionNone:
|
|
221
268
|
break;
|
|
222
269
|
case RedactionPolicy.RedactionStrict:
|
|
223
|
-
redactSecretsStrict(value,
|
|
270
|
+
redactSecretsStrict(value, context);
|
|
224
271
|
break;
|
|
225
272
|
default:
|
|
226
273
|
// Empty/unknown policy falls back to default full redaction.
|
|
227
|
-
redactSecrets(value,
|
|
274
|
+
redactSecrets(value, context);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// ═══════════════════════════════════════════
|
|
279
|
+
// URL Secret Redaction
|
|
280
|
+
// ═══════════════════════════════════════════
|
|
281
|
+
/**
|
|
282
|
+
* Redact secret components of a single URL string, returning the redacted URL
|
|
283
|
+
* when s is a processable URL, or null when it is not (so callers can keep the
|
|
284
|
+
* original). Only secret spans change; all other bytes are preserved.
|
|
285
|
+
*/
|
|
286
|
+
function redactUrlInStr(s, secretNames) {
|
|
287
|
+
// Fast path + precondition: a single, whitespace-free, scheme-prefixed URL.
|
|
288
|
+
if (!s.includes("://") || !isSingleUrl(s) || !urlParses(s)) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const schemeSep = s.indexOf("://");
|
|
292
|
+
const scheme = s.slice(0, schemeSep);
|
|
293
|
+
const rest = s.slice(schemeSep + 3);
|
|
294
|
+
// Authority runs from after "://" to the first '/', '?', or '#'.
|
|
295
|
+
let authEnd = rest.length;
|
|
296
|
+
for (let i = 0; i < rest.length; i++) {
|
|
297
|
+
const c = rest[i];
|
|
298
|
+
if (c === "/" || c === "?" || c === "#") {
|
|
299
|
+
authEnd = i;
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const authority = rest.slice(0, authEnd);
|
|
304
|
+
const remainder = rest.slice(authEnd);
|
|
305
|
+
const newAuthority = redactUserinfoPassword(authority);
|
|
306
|
+
// Query runs from the first '?' to the first '#' (or end).
|
|
307
|
+
let newRemainder;
|
|
308
|
+
const q = remainder.indexOf("?");
|
|
309
|
+
if (q >= 0) {
|
|
310
|
+
const path = remainder.slice(0, q);
|
|
311
|
+
const qOnwards = remainder.slice(q + 1);
|
|
312
|
+
const hash = qOnwards.indexOf("#");
|
|
313
|
+
let query;
|
|
314
|
+
let fragment;
|
|
315
|
+
if (hash >= 0) {
|
|
316
|
+
query = qOnwards.slice(0, hash);
|
|
317
|
+
fragment = qOnwards.slice(hash);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
query = qOnwards;
|
|
321
|
+
fragment = "";
|
|
322
|
+
}
|
|
323
|
+
newRemainder = `${path}?${redactQuery(query, secretNames)}${fragment}`;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
newRemainder = remainder;
|
|
327
|
+
}
|
|
328
|
+
return `${scheme}://${newAuthority}${newRemainder}`;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Replace the userinfo password (user:pass@) with "***", preserving the
|
|
332
|
+
* username. Authority without '@', or userinfo without ':', is unchanged.
|
|
333
|
+
*/
|
|
334
|
+
function redactUserinfoPassword(authority) {
|
|
335
|
+
const at = authority.indexOf("@");
|
|
336
|
+
if (at < 0)
|
|
337
|
+
return authority;
|
|
338
|
+
const userinfo = authority.slice(0, at);
|
|
339
|
+
const colon = userinfo.indexOf(":");
|
|
340
|
+
if (colon < 0)
|
|
341
|
+
return authority;
|
|
342
|
+
return `${authority.slice(0, colon)}:***${authority.slice(at)}`;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Redact the values of secret-named query parameters, preserving raw bytes of
|
|
346
|
+
* every other segment (keys, benign values, encoding, ordering, separators).
|
|
347
|
+
*/
|
|
348
|
+
function redactQuery(query, secretNames) {
|
|
349
|
+
return query
|
|
350
|
+
.split("&")
|
|
351
|
+
.map((segment) => {
|
|
352
|
+
const eq = segment.indexOf("=");
|
|
353
|
+
if (eq < 0)
|
|
354
|
+
return segment;
|
|
355
|
+
const rawKey = segment.slice(0, eq);
|
|
356
|
+
const name = formDecode(rawKey);
|
|
357
|
+
if (isSecretKey(name, secretNames)) {
|
|
358
|
+
return `${rawKey}=***`;
|
|
359
|
+
}
|
|
360
|
+
return segment;
|
|
361
|
+
})
|
|
362
|
+
.join("&");
|
|
363
|
+
}
|
|
364
|
+
/** Form-decode a query-parameter name: '+' → space, then percent-decode. */
|
|
365
|
+
function formDecode(rawKey) {
|
|
366
|
+
const withSpaces = rawKey.replace(/\+/g, " ");
|
|
367
|
+
try {
|
|
368
|
+
return decodeURIComponent(withSpaces);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return withSpaces;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** True when s parses as a URL (mirrors Rust's url::Url::parse check). */
|
|
375
|
+
function urlParses(s) {
|
|
376
|
+
try {
|
|
377
|
+
new URL(s);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* True when s begins with a URL scheme (ALPHA *(ALPHA / DIGIT / "+" / "-" /
|
|
386
|
+
* ".") "://") and contains no ASCII whitespace — i.e. a single bare URL, not a
|
|
387
|
+
* URL embedded in prose.
|
|
388
|
+
*/
|
|
389
|
+
function isSingleUrl(s) {
|
|
390
|
+
for (let i = 0; i < s.length; i++) {
|
|
391
|
+
const c = s.charCodeAt(i);
|
|
392
|
+
// ASCII whitespace: tab, LF, VT, FF, CR, space.
|
|
393
|
+
if (c === 0x09 || c === 0x0a || c === 0x0b || c === 0x0c || c === 0x0d || c === 0x20) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (s.length === 0)
|
|
398
|
+
return false;
|
|
399
|
+
const first = s.charCodeAt(0);
|
|
400
|
+
if (!isAsciiAlpha(first))
|
|
401
|
+
return false;
|
|
402
|
+
let i = 1;
|
|
403
|
+
while (i < s.length) {
|
|
404
|
+
const c = s.charCodeAt(i);
|
|
405
|
+
if (isAsciiAlphanumeric(c) || c === 0x2b /* + */ || c === 0x2d /* - */ || c === 0x2e /* . */) {
|
|
406
|
+
i += 1;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
228
409
|
break;
|
|
410
|
+
}
|
|
229
411
|
}
|
|
412
|
+
return s.slice(i).startsWith("://");
|
|
413
|
+
}
|
|
414
|
+
function isAsciiAlpha(c) {
|
|
415
|
+
return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a);
|
|
416
|
+
}
|
|
417
|
+
function isAsciiAlphanumeric(c) {
|
|
418
|
+
return isAsciiAlpha(c) || (c >= 0x30 && c <= 0x39);
|
|
230
419
|
}
|
|
231
420
|
// ═══════════════════════════════════════════
|
|
232
421
|
// Suffix Processing
|
|
@@ -530,6 +719,69 @@ function renderYamlProcessed(value, indent, lines) {
|
|
|
530
719
|
}
|
|
531
720
|
}
|
|
532
721
|
}
|
|
722
|
+
function renderYamlRaw(value, indent, lines) {
|
|
723
|
+
const prefix = " ".repeat(indent);
|
|
724
|
+
if (isObject(value)) {
|
|
725
|
+
for (const key of sortedObjectKeys(value)) {
|
|
726
|
+
renderYamlFieldRaw(prefix, key, value[key], indent, lines);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else if (Array.isArray(value)) {
|
|
730
|
+
renderYamlArrayRaw(value, indent, lines);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
lines.push(`${prefix}${yamlScalar(value)}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function renderYamlFieldRaw(prefix, key, value, indent, lines) {
|
|
737
|
+
if (isObject(value)) {
|
|
738
|
+
if (Object.keys(value).length > 0) {
|
|
739
|
+
lines.push(`${prefix}${key}:`);
|
|
740
|
+
renderYamlRaw(value, indent + 1, lines);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
lines.push(`${prefix}${key}: {}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
else if (Array.isArray(value)) {
|
|
747
|
+
if (value.length > 0) {
|
|
748
|
+
lines.push(`${prefix}${key}:`);
|
|
749
|
+
renderYamlArrayRaw(value, indent + 1, lines);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
lines.push(`${prefix}${key}: []`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
lines.push(`${prefix}${key}: ${yamlScalar(value)}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function renderYamlArrayRaw(arr, indent, lines) {
|
|
760
|
+
const prefix = " ".repeat(indent);
|
|
761
|
+
for (const item of arr) {
|
|
762
|
+
if (isObject(item)) {
|
|
763
|
+
if (Object.keys(item).length > 0) {
|
|
764
|
+
lines.push(`${prefix}-`);
|
|
765
|
+
renderYamlRaw(item, indent + 1, lines);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
lines.push(`${prefix}- {}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
else if (Array.isArray(item)) {
|
|
772
|
+
if (item.length > 0) {
|
|
773
|
+
lines.push(`${prefix}-`);
|
|
774
|
+
renderYamlArrayRaw(item, indent + 1, lines);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
lines.push(`${prefix}- []`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
lines.push(`${prefix}- ${yamlScalar(item)}`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
533
785
|
function escapeYamlStr(s) {
|
|
534
786
|
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
535
787
|
}
|
|
@@ -569,6 +821,26 @@ function collectPlainPairs(value, prefix, pairs) {
|
|
|
569
821
|
}
|
|
570
822
|
}
|
|
571
823
|
}
|
|
824
|
+
function collectPlainPairsRaw(value, prefix, pairs) {
|
|
825
|
+
if (!isObject(value))
|
|
826
|
+
return;
|
|
827
|
+
for (const key of sortedObjectKeys(value)) {
|
|
828
|
+
const v = value[key];
|
|
829
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
830
|
+
if (isObject(v)) {
|
|
831
|
+
collectPlainPairsRaw(v, fullKey, pairs);
|
|
832
|
+
}
|
|
833
|
+
else if (Array.isArray(v)) {
|
|
834
|
+
pairs.push([fullKey, v.map((i) => plainScalarRaw(i)).join(",")]);
|
|
835
|
+
}
|
|
836
|
+
else if (v === null) {
|
|
837
|
+
pairs.push([fullKey, ""]);
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
pairs.push([fullKey, plainScalar(v)]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
572
844
|
function plainScalar(value) {
|
|
573
845
|
if (typeof value === "string")
|
|
574
846
|
return value;
|
|
@@ -580,6 +852,12 @@ function plainScalar(value) {
|
|
|
580
852
|
return value.toString();
|
|
581
853
|
return String(value);
|
|
582
854
|
}
|
|
855
|
+
function plainScalarRaw(value) {
|
|
856
|
+
if (isObject(value) || Array.isArray(value)) {
|
|
857
|
+
return JSON.stringify(sortJsonValue(value));
|
|
858
|
+
}
|
|
859
|
+
return plainScalar(value);
|
|
860
|
+
}
|
|
583
861
|
function quoteLogfmtValue(value) {
|
|
584
862
|
if (value === "")
|
|
585
863
|
return "";
|
|
@@ -645,6 +923,21 @@ function sanitizeForJson(value, stack = new WeakSet()) {
|
|
|
645
923
|
function isObject(value) {
|
|
646
924
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
647
925
|
}
|
|
926
|
+
function sortedObjectKeys(obj) {
|
|
927
|
+
return Object.keys(obj).sort(jcsCompare);
|
|
928
|
+
}
|
|
929
|
+
function sortJsonValue(value) {
|
|
930
|
+
if (Array.isArray(value))
|
|
931
|
+
return value.map(sortJsonValue);
|
|
932
|
+
if (isObject(value)) {
|
|
933
|
+
const out = {};
|
|
934
|
+
for (const key of sortedObjectKeys(value)) {
|
|
935
|
+
out[key] = sortJsonValue(value[key]);
|
|
936
|
+
}
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
return value;
|
|
940
|
+
}
|
|
648
941
|
function jcsCompare(a, b) {
|
|
649
942
|
const ua = Array.from(a).flatMap((c) => {
|
|
650
943
|
const cp = c.codePointAt(0);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
4
|
-
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, type RedactionOptions, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, parseSize, } from "./format.js";
|
|
4
|
+
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, type RedactionOptions, OutputStyle, type OutputOptions, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, redactUrlSecrets, redactUrlSecretsWithOptions, parseSize, } from "./format.js";
|
|
5
5
|
export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
|
|
6
|
-
export { type OutputFormat, cliParseOutput, cliParseLogFilters, cliOutput, buildCliError } from "./cli.js";
|
|
6
|
+
export { type OutputFormat, cliParseOutput, cliParseLogFilters, cliOutput, cliOutputWithOptions, buildCliError, } from "./cli.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
4
|
-
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, parseSize, } from "./format.js";
|
|
4
|
+
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, OutputStyle, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, redactUrlSecrets, redactUrlSecretsWithOptions, parseSize, } from "./format.js";
|
|
5
5
|
export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
|
|
6
|
-
export { cliParseOutput, cliParseLogFilters, cliOutput, buildCliError } from "./cli.js";
|
|
6
|
+
export { cliParseOutput, cliParseLogFilters, cliOutput, cliOutputWithOptions, buildCliError, } from "./cli.js";
|