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 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 the `*WithOptions` functions to pass legacy secret names such as `api_key`. YAML and Plain always redact secrets and apply human-readable formatting.
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, redactionOptions: RedactionOptions): string
146
+ outputJsonWithOptions(value: JsonValue, outputOptions: OutputOptions): string
140
147
  outputYaml(value: JsonValue): string // Multi-line YAML, keys stripped, values formatted
141
- outputYamlWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string
148
+ outputYamlWithOptions(value: JsonValue, outputOptions: OutputOptions): string
142
149
  outputPlain(value: JsonValue): string // Single-line logfmt, keys stripped, values formatted
143
- outputPlainWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string
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. They do not change YAML/Plain suffix stripping.
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
- * 16 public APIs and 2 types: protocol builders, redacted value helpers,
5
- * output formatters, redaction, parseSize, RedactionPolicy, and RedactionOptions.
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 redaction options. */
35
- export declare function outputJsonWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string;
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 redaction options. */
39
- export declare function outputYamlWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string;
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 redaction options. */
43
- export declare function outputPlainWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string;
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
- * 16 public APIs and 2 types: protocol builders, redacted value helpers,
5
- * output formatters, redaction, parseSize, RedactionPolicy, and RedactionOptions.
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 redaction options. */
52
- export function outputJsonWithOptions(value, redactionOptions) {
53
- return JSON.stringify(redactedValueWithOptions(value, redactionOptions));
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
- value = redactedValue(value);
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 redaction options. */
63
- export function outputYamlWithOptions(value, redactionOptions) {
64
- value = redactedValueWithOptions(value, redactionOptions);
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
- renderYamlProcessed(value, 0, lines);
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
- value = redactedValue(value);
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 redaction options. */
80
- export function outputPlainWithOptions(value, redactionOptions) {
81
- value = redactedValueWithOptions(value, redactionOptions);
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
- collectPlainPairs(value, "", pairs);
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
- // Secret Redaction
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, secretNames = EMPTY_SECRET_NAMES) {
203
+ function redactSecrets(value, context = DEFAULT_CONTEXT) {
168
204
  if (isObject(value)) {
169
205
  for (const k of Object.keys(value)) {
170
- if (isSecretKey(k, secretNames)) {
171
- const v = value[k];
206
+ const v = value[k];
207
+ if (isSecretKey(k, context.secretNames)) {
172
208
  if (isObject(v) || Array.isArray(v)) {
173
- redactSecrets(v, secretNames);
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(value[k], secretNames);
221
+ redactSecrets(v, context);
181
222
  }
182
223
  }
183
224
  }
184
225
  else if (Array.isArray(value)) {
185
- for (const item of value) {
186
- redactSecrets(item, secretNames);
226
+ for (let i = 0; i < value.length; i++) {
227
+ redactSecrets(value[i], context);
187
228
  }
188
229
  }
189
230
  }
190
- function redactSecretsStrict(value, secretNames = EMPTY_SECRET_NAMES) {
231
+ function redactSecretsStrict(value, context = DEFAULT_CONTEXT) {
191
232
  if (isObject(value)) {
192
233
  for (const k of Object.keys(value)) {
193
- if (isSecretKey(k, secretNames)) {
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(value[k], secretNames);
244
+ redactSecretsStrict(v, context);
198
245
  }
199
246
  }
200
247
  }
201
248
  else if (Array.isArray(value)) {
202
- for (const item of value) {
203
- redactSecretsStrict(item, secretNames);
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
- applyRedactionPolicyWithNames(value, redactionPolicy, EMPTY_SECRET_NAMES);
255
+ applyRedactionPolicyWithContext(value, redactionPolicy, DEFAULT_CONTEXT);
209
256
  }
210
257
  function applyRedactionOptions(value, redactionOptions) {
211
- applyRedactionPolicyWithNames(value, redactionOptions.policy, secretNameSet(redactionOptions));
258
+ applyRedactionPolicyWithContext(value, redactionOptions.policy, contextFromOptions(redactionOptions));
212
259
  }
213
- function applyRedactionPolicyWithNames(value, redactionPolicy, secretNames) {
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, secretNames);
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, secretNames);
270
+ redactSecretsStrict(value, context);
224
271
  break;
225
272
  default:
226
273
  // Empty/unknown policy falls back to default full redaction.
227
- redactSecrets(value, secretNames);
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-first-data",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "A naming convention that lets AI agents understand your data without being told what it means.",
5
5
  "license": "MIT",
6
6
  "repository": {