agent-first-data 0.12.1 → 0.13.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 +25 -557
- package/dist/afdata_logging.d.ts +7 -3
- package/dist/afdata_logging.js +16 -14
- package/dist/cli.js +0 -3
- package/dist/format.d.ts +12 -7
- package/dist/format.js +181 -97
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/skill.js +155 -43
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -71,10 +71,7 @@ export function cliOutputWithOptions(value, format, outputOptions) {
|
|
|
71
71
|
export function buildCliError(message, hint) {
|
|
72
72
|
const m = {
|
|
73
73
|
code: "error",
|
|
74
|
-
error_code: "invalid_request",
|
|
75
74
|
error: message,
|
|
76
|
-
retryable: false,
|
|
77
|
-
trace: { duration_ms: 0 },
|
|
78
75
|
};
|
|
79
76
|
if (hint !== undefined)
|
|
80
77
|
m.hint = hint;
|
package/dist/format.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA output formatting and protocol templates.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 19 public APIs and 4 types: protocol builders, value redactors (copy and
|
|
5
5
|
* in-place; cover _secret and _url fields), output formatters, URL-string
|
|
6
|
-
* redactors (redactUrlSecrets / WithOptions), parseSize,
|
|
7
|
-
* RedactionOptions, OutputStyle, and OutputOptions.
|
|
6
|
+
* redactors (redactUrlSecrets / WithOptions), parseSize, normalizeUtcOffset,
|
|
7
|
+
* RedactionPolicy, RedactionOptions, OutputStyle, and OutputOptions.
|
|
8
8
|
*/
|
|
9
9
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
10
10
|
[key: string]: JsonValue;
|
|
@@ -17,8 +17,7 @@ export declare function buildJsonError(message: string, hint?: string, trace?: J
|
|
|
17
17
|
export declare function buildJson(code: string, fields: JsonValue, trace?: JsonValue): JsonValue;
|
|
18
18
|
export declare enum RedactionPolicy {
|
|
19
19
|
RedactionTraceOnly = "RedactionTraceOnly",
|
|
20
|
-
RedactionNone = "RedactionNone"
|
|
21
|
-
RedactionStrict = "RedactionStrict"
|
|
20
|
+
RedactionNone = "RedactionNone"
|
|
22
21
|
}
|
|
23
22
|
export type RedactionOptions = {
|
|
24
23
|
/** Optional scoped policy. Omitted means default full redaction. */
|
|
@@ -56,9 +55,9 @@ export declare function outputPlain(value: JsonValue): string;
|
|
|
56
55
|
/** Format as single-line logfmt with explicit output options. */
|
|
57
56
|
export declare function outputPlainWithOptions(value: JsonValue, outputOptions: OutputOptions): string;
|
|
58
57
|
/** Redact _secret fields in-place. */
|
|
59
|
-
export declare function
|
|
58
|
+
export declare function redactSecretsInPlace(value: JsonValue): void;
|
|
60
59
|
/** Redact secret fields in-place using explicit redaction options. */
|
|
61
|
-
export declare function
|
|
60
|
+
export declare function redactSecretsInPlaceWithOptions(value: JsonValue, redactionOptions: RedactionOptions): void;
|
|
62
61
|
/** Return a JSON-safe copy with default _secret redaction applied. */
|
|
63
62
|
export declare function redactedValue(value: unknown): JsonValue;
|
|
64
63
|
/** Return a JSON-safe copy with an explicit redaction policy applied. */
|
|
@@ -89,3 +88,9 @@ export declare function redactUrlSecretsWithOptions(url: string, redactionOption
|
|
|
89
88
|
* Case-insensitive. Trims whitespace. Returns null for invalid input.
|
|
90
89
|
*/
|
|
91
90
|
export declare function parseSize(s: string): number | null;
|
|
91
|
+
/**
|
|
92
|
+
* Normalize a fixed UTC offset string to "UTC" or ±HH:MM.
|
|
93
|
+
* This helper handles fixed offsets only; IANA timezone names and DST rules
|
|
94
|
+
* are intentionally out of scope.
|
|
95
|
+
*/
|
|
96
|
+
export declare function normalizeUtcOffset(value: string): string | null;
|
package/dist/format.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AFDATA output formatting and protocol templates.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 19 public APIs and 4 types: protocol builders, value redactors (copy and
|
|
5
5
|
* in-place; cover _secret and _url fields), output formatters, URL-string
|
|
6
|
-
* redactors (redactUrlSecrets / WithOptions), parseSize,
|
|
7
|
-
* RedactionOptions, OutputStyle, and OutputOptions.
|
|
6
|
+
* redactors (redactUrlSecrets / WithOptions), parseSize, normalizeUtcOffset,
|
|
7
|
+
* RedactionPolicy, RedactionOptions, OutputStyle, and OutputOptions.
|
|
8
8
|
*/
|
|
9
9
|
// ═══════════════════════════════════════════
|
|
10
10
|
// Public API: Protocol Builders
|
|
@@ -40,7 +40,6 @@ export var RedactionPolicy;
|
|
|
40
40
|
(function (RedactionPolicy) {
|
|
41
41
|
RedactionPolicy["RedactionTraceOnly"] = "RedactionTraceOnly";
|
|
42
42
|
RedactionPolicy["RedactionNone"] = "RedactionNone";
|
|
43
|
-
RedactionPolicy["RedactionStrict"] = "RedactionStrict";
|
|
44
43
|
})(RedactionPolicy || (RedactionPolicy = {}));
|
|
45
44
|
export var OutputStyle;
|
|
46
45
|
(function (OutputStyle) {
|
|
@@ -91,18 +90,18 @@ export function outputPlainWithOptions(value, outputOptions) {
|
|
|
91
90
|
}
|
|
92
91
|
pairs.sort(([a], [b]) => jcsCompare(a, b));
|
|
93
92
|
return pairs
|
|
94
|
-
.map(([k, v]) => `${k}=${quoteLogfmtValue(v)}`)
|
|
93
|
+
.map(([k, v]) => `${quoteLogfmtKey(k)}=${quoteLogfmtValue(v)}`)
|
|
95
94
|
.join(" ");
|
|
96
95
|
}
|
|
97
96
|
// ═══════════════════════════════════════════
|
|
98
97
|
// Public API: Redaction & Utility
|
|
99
98
|
// ═══════════════════════════════════════════
|
|
100
99
|
/** Redact _secret fields in-place. */
|
|
101
|
-
export function
|
|
100
|
+
export function redactSecretsInPlace(value) {
|
|
102
101
|
redactSecrets(value);
|
|
103
102
|
}
|
|
104
103
|
/** Redact secret fields in-place using explicit redaction options. */
|
|
105
|
-
export function
|
|
104
|
+
export function redactSecretsInPlaceWithOptions(value, redactionOptions) {
|
|
106
105
|
applyRedactionOptions(value, redactionOptions);
|
|
107
106
|
}
|
|
108
107
|
/** Return a JSON-safe copy with default _secret redaction applied. */
|
|
@@ -172,7 +171,7 @@ export function parseSize(s) {
|
|
|
172
171
|
else {
|
|
173
172
|
return null;
|
|
174
173
|
}
|
|
175
|
-
if (!numStr)
|
|
174
|
+
if (!numStr || !/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(numStr))
|
|
176
175
|
return null;
|
|
177
176
|
const n = Number(numStr);
|
|
178
177
|
if (isNaN(n) || n < 0 || !isFinite(n))
|
|
@@ -182,6 +181,49 @@ export function parseSize(s) {
|
|
|
182
181
|
return null;
|
|
183
182
|
return result;
|
|
184
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Normalize a fixed UTC offset string to "UTC" or ±HH:MM.
|
|
186
|
+
* This helper handles fixed offsets only; IANA timezone names and DST rules
|
|
187
|
+
* are intentionally out of scope.
|
|
188
|
+
*/
|
|
189
|
+
export function normalizeUtcOffset(value) {
|
|
190
|
+
const s = value.trim();
|
|
191
|
+
if (s.toLowerCase() === "utc" || s.toLowerCase() === "z")
|
|
192
|
+
return "UTC";
|
|
193
|
+
if (!s || (s[0] !== "+" && s[0] !== "-"))
|
|
194
|
+
return null;
|
|
195
|
+
const parsed = parseUtcOffsetBody(s.slice(1));
|
|
196
|
+
if (parsed === null)
|
|
197
|
+
return null;
|
|
198
|
+
const [hours, minutes] = parsed;
|
|
199
|
+
if (hours > 23 || minutes > 59)
|
|
200
|
+
return null;
|
|
201
|
+
if (hours === 0 && minutes === 0)
|
|
202
|
+
return "UTC";
|
|
203
|
+
return `${s[0]}${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
|
204
|
+
}
|
|
205
|
+
function parseUtcOffsetBody(body) {
|
|
206
|
+
if (!body)
|
|
207
|
+
return null;
|
|
208
|
+
if (body.includes(":")) {
|
|
209
|
+
const parts = body.split(":");
|
|
210
|
+
if (parts.length !== 2)
|
|
211
|
+
return null;
|
|
212
|
+
const [hours, minutes] = parts;
|
|
213
|
+
if (!hours || hours.length > 2 || minutes.length !== 2)
|
|
214
|
+
return null;
|
|
215
|
+
if (!/^[0-9]+$/.test(hours) || !/^[0-9]+$/.test(minutes))
|
|
216
|
+
return null;
|
|
217
|
+
return [Number(hours), Number(minutes)];
|
|
218
|
+
}
|
|
219
|
+
if (!/^[0-9]+$/.test(body))
|
|
220
|
+
return null;
|
|
221
|
+
if (body.length === 1 || body.length === 2)
|
|
222
|
+
return [Number(body), 0];
|
|
223
|
+
if (body.length === 4)
|
|
224
|
+
return [Number(body.slice(0, 2)), Number(body.slice(2))];
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
185
227
|
const DEFAULT_CONTEXT = {
|
|
186
228
|
secretNames: new Set(),
|
|
187
229
|
};
|
|
@@ -200,54 +242,35 @@ function keyHasUrlSuffix(key) {
|
|
|
200
242
|
function isSecretKey(key, secretNames) {
|
|
201
243
|
return keyHasSecretSuffix(key) || secretNames.has(key);
|
|
202
244
|
}
|
|
203
|
-
|
|
245
|
+
const MAX_DEPTH = 256;
|
|
246
|
+
function redactSecrets(value, context = DEFAULT_CONTEXT, depth = 0) {
|
|
247
|
+
if (depth >= MAX_DEPTH)
|
|
248
|
+
return;
|
|
204
249
|
if (isObject(value)) {
|
|
205
250
|
for (const k of Object.keys(value)) {
|
|
206
251
|
const v = value[k];
|
|
207
252
|
if (isSecretKey(k, context.secretNames)) {
|
|
208
|
-
|
|
209
|
-
|
|
253
|
+
value[k] = "***";
|
|
254
|
+
}
|
|
255
|
+
else if (keyHasUrlSuffix(k)) {
|
|
256
|
+
if (typeof v === "string") {
|
|
257
|
+
value[k] = redactUrlFieldValue(v, context.secretNames);
|
|
210
258
|
}
|
|
211
259
|
else {
|
|
212
|
-
value[k] = "***";
|
|
260
|
+
value[k] = depth + 1 >= MAX_DEPTH ? "***" : (redactSecrets(v, context, depth + 1), v);
|
|
213
261
|
}
|
|
214
262
|
}
|
|
215
|
-
else if (keyHasUrlSuffix(k) && typeof v === "string") {
|
|
216
|
-
const redacted = redactUrlInStr(v, context.secretNames);
|
|
217
|
-
if (redacted !== null)
|
|
218
|
-
value[k] = redacted;
|
|
219
|
-
}
|
|
220
263
|
else {
|
|
221
|
-
redactSecrets(v, context);
|
|
264
|
+
value[k] = depth + 1 >= MAX_DEPTH ? "***" : (redactSecrets(v, context, depth + 1), v);
|
|
222
265
|
}
|
|
223
266
|
}
|
|
224
267
|
}
|
|
225
268
|
else if (Array.isArray(value)) {
|
|
226
269
|
for (let i = 0; i < value.length; i++) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
function redactSecretsStrict(value, context = DEFAULT_CONTEXT) {
|
|
232
|
-
if (isObject(value)) {
|
|
233
|
-
for (const k of Object.keys(value)) {
|
|
234
|
-
const v = value[k];
|
|
235
|
-
if (isSecretKey(k, context.secretNames)) {
|
|
236
|
-
value[k] = "***";
|
|
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
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
redactSecretsStrict(v, context);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else if (Array.isArray(value)) {
|
|
249
|
-
for (let i = 0; i < value.length; i++) {
|
|
250
|
-
redactSecretsStrict(value[i], context);
|
|
270
|
+
if (depth + 1 >= MAX_DEPTH)
|
|
271
|
+
value[i] = "***";
|
|
272
|
+
else
|
|
273
|
+
redactSecrets(value[i], context, depth + 1);
|
|
251
274
|
}
|
|
252
275
|
}
|
|
253
276
|
}
|
|
@@ -266,9 +289,6 @@ function applyRedactionPolicyWithContext(value, redactionPolicy, context) {
|
|
|
266
289
|
break;
|
|
267
290
|
case RedactionPolicy.RedactionNone:
|
|
268
291
|
break;
|
|
269
|
-
case RedactionPolicy.RedactionStrict:
|
|
270
|
-
redactSecretsStrict(value, context);
|
|
271
|
-
break;
|
|
272
292
|
default:
|
|
273
293
|
// Empty/unknown policy falls back to default full redaction.
|
|
274
294
|
redactSecrets(value, context);
|
|
@@ -284,8 +304,13 @@ function applyRedactionPolicyWithContext(value, redactionPolicy, context) {
|
|
|
284
304
|
* original). Only secret spans change; all other bytes are preserved.
|
|
285
305
|
*/
|
|
286
306
|
function redactUrlInStr(s, secretNames) {
|
|
287
|
-
//
|
|
288
|
-
|
|
307
|
+
// Precondition (spec): a single, whitespace-free, scheme-prefixed URL.
|
|
308
|
+
// The gate is scheme + no-whitespace only — NOT "parses via `new URL`". Span
|
|
309
|
+
// location below is purely byte-wise (we never re-serialize the URL), so a
|
|
310
|
+
// `new URL` gate would only diverge across languages — it rejects inputs
|
|
311
|
+
// (ports > 65535, empty host) that other languages' URL libraries accept, and
|
|
312
|
+
// rejecting here would silently leak secrets in the values it rejects.
|
|
313
|
+
if (!s.includes("://") || !isSingleUrl(s)) {
|
|
289
314
|
return null;
|
|
290
315
|
}
|
|
291
316
|
const schemeSep = s.indexOf("://");
|
|
@@ -327,12 +352,31 @@ function redactUrlInStr(s, secretNames) {
|
|
|
327
352
|
}
|
|
328
353
|
return `${scheme}://${newAuthority}${newRemainder}`;
|
|
329
354
|
}
|
|
355
|
+
function redactUrlFieldValue(s, secretNames) {
|
|
356
|
+
const redacted = redactUrlInStr(s, secretNames);
|
|
357
|
+
if (redacted !== null)
|
|
358
|
+
return redacted;
|
|
359
|
+
const trimmed = s.trim();
|
|
360
|
+
if (trimmed !== s) {
|
|
361
|
+
const trimmedRedacted = redactUrlInStr(trimmed, secretNames);
|
|
362
|
+
if (trimmedRedacted !== null)
|
|
363
|
+
return trimmedRedacted;
|
|
364
|
+
}
|
|
365
|
+
// Fail closed: a _url value we could not parse as a clean scheme-prefixed
|
|
366
|
+
// URL, yet which carries a credential sigil ('@' userinfo) or internal
|
|
367
|
+
// whitespace, is redacted wholesale rather than passed through. A schemeless
|
|
368
|
+
// connection string like user:pass@host/db has no scheme anchor for the
|
|
369
|
+
// surgical span logic above, so blanket redaction is the safe default.
|
|
370
|
+
if (/\s/.test(s) || s.includes("@"))
|
|
371
|
+
return "***";
|
|
372
|
+
return s;
|
|
373
|
+
}
|
|
330
374
|
/**
|
|
331
375
|
* Replace the userinfo password (user:pass@) with "***", preserving the
|
|
332
376
|
* username. Authority without '@', or userinfo without ':', is unchanged.
|
|
333
377
|
*/
|
|
334
378
|
function redactUserinfoPassword(authority) {
|
|
335
|
-
const at = authority.
|
|
379
|
+
const at = authority.lastIndexOf("@");
|
|
336
380
|
if (at < 0)
|
|
337
381
|
return authority;
|
|
338
382
|
const userinfo = authority.slice(0, at);
|
|
@@ -371,16 +415,6 @@ function formDecode(rawKey) {
|
|
|
371
415
|
return withSpaces;
|
|
372
416
|
}
|
|
373
417
|
}
|
|
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
418
|
/**
|
|
385
419
|
* True when s begins with a URL scheme (ALPHA *(ALPHA / DIGIT / "+" / "-" /
|
|
386
420
|
* ".") "://") and contains no ASCII whitespace — i.e. a single bare URL, not a
|
|
@@ -449,20 +483,29 @@ function tryProcessField(key, value) {
|
|
|
449
483
|
// Group 1: compound timestamp suffixes
|
|
450
484
|
stripped = stripSuffixCI(key, "_epoch_ms");
|
|
451
485
|
if (stripped !== null) {
|
|
452
|
-
if (isInt(value))
|
|
453
|
-
|
|
486
|
+
if (isInt(value)) {
|
|
487
|
+
const formatted = formatRfc3339Ms(value);
|
|
488
|
+
if (formatted !== null)
|
|
489
|
+
return [stripped, formatted];
|
|
490
|
+
}
|
|
454
491
|
return null;
|
|
455
492
|
}
|
|
456
493
|
stripped = stripSuffixCI(key, "_epoch_s");
|
|
457
494
|
if (stripped !== null) {
|
|
458
|
-
if (isInt(value))
|
|
459
|
-
|
|
495
|
+
if (isInt(value)) {
|
|
496
|
+
const formatted = formatRfc3339Ms(value * 1000);
|
|
497
|
+
if (formatted !== null)
|
|
498
|
+
return [stripped, formatted];
|
|
499
|
+
}
|
|
460
500
|
return null;
|
|
461
501
|
}
|
|
462
502
|
stripped = stripSuffixCI(key, "_epoch_ns");
|
|
463
503
|
if (stripped !== null) {
|
|
464
|
-
if (isInt(value))
|
|
465
|
-
|
|
504
|
+
if (isInt(value)) {
|
|
505
|
+
const formatted = formatRfc3339Ms(Math.floor(value / 1_000_000));
|
|
506
|
+
if (formatted !== null)
|
|
507
|
+
return [stripped, formatted];
|
|
508
|
+
}
|
|
466
509
|
return null;
|
|
467
510
|
}
|
|
468
511
|
// Group 2: compound currency suffixes
|
|
@@ -535,9 +578,6 @@ function tryProcessField(key, value) {
|
|
|
535
578
|
return [stripped, `${plainScalar(value)}%`];
|
|
536
579
|
return null;
|
|
537
580
|
}
|
|
538
|
-
stripped = stripSuffixCI(key, "_secret");
|
|
539
|
-
if (stripped !== null)
|
|
540
|
-
return [stripped, "***"];
|
|
541
581
|
// Group 5: short suffixes (last to avoid false positives)
|
|
542
582
|
stripped = stripSuffixCI(key, "_btc");
|
|
543
583
|
if (stripped !== null) {
|
|
@@ -581,6 +621,11 @@ function tryProcessField(key, value) {
|
|
|
581
621
|
function processObjectFields(obj) {
|
|
582
622
|
const entries = [];
|
|
583
623
|
for (const [k, v] of Object.entries(obj)) {
|
|
624
|
+
const secretStripped = stripSuffixCI(k, "_secret");
|
|
625
|
+
if (secretStripped !== null) {
|
|
626
|
+
entries.push({ stripped: secretStripped, original: k, value: v, formatted: null });
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
584
629
|
const result = tryProcessField(k, v);
|
|
585
630
|
if (result !== null) {
|
|
586
631
|
entries.push({ stripped: result[0], original: k, value: v, formatted: result[1] });
|
|
@@ -622,14 +667,36 @@ function formatMsValue(value) {
|
|
|
622
667
|
return formatMsAsSeconds(value);
|
|
623
668
|
return `${plainScalar(value)}ms`;
|
|
624
669
|
}
|
|
670
|
+
const MIN_RFC3339_MS = -62135596800000;
|
|
671
|
+
const MAX_RFC3339_MS = 253402300799999;
|
|
625
672
|
function formatRfc3339Ms(ms) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
673
|
+
if (ms < MIN_RFC3339_MS || ms > MAX_RFC3339_MS)
|
|
674
|
+
return null;
|
|
675
|
+
const d = new Date(ms);
|
|
676
|
+
if (Number.isNaN(d.getTime()))
|
|
677
|
+
return null;
|
|
678
|
+
return d.toISOString().replace(/(\.\d{3})\d*Z$/, "$1Z");
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Round to `digits` decimals using round-half-to-even (banker's rounding) and
|
|
682
|
+
* format with a fixed decimal count. `Number.toFixed` rounds half away from
|
|
683
|
+
* zero, which diverges from the printf `%.1f` used by Rust/Go/Python on exact
|
|
684
|
+
* ties (e.g. 1280 bytes = 1.25 KB → "1.2KB", not "1.3KB").
|
|
685
|
+
*/
|
|
686
|
+
function toFixedHalfEven(x, digits) {
|
|
687
|
+
const factor = 10 ** digits;
|
|
688
|
+
const scaled = x * factor;
|
|
689
|
+
const floor = Math.floor(scaled);
|
|
690
|
+
const diff = scaled - floor;
|
|
691
|
+
const eps = 1e-9;
|
|
692
|
+
let rounded;
|
|
693
|
+
if (diff > 0.5 + eps)
|
|
694
|
+
rounded = floor + 1;
|
|
695
|
+
else if (diff < 0.5 - eps)
|
|
696
|
+
rounded = floor;
|
|
697
|
+
else
|
|
698
|
+
rounded = floor % 2 === 0 ? floor : floor + 1;
|
|
699
|
+
return (rounded / factor).toFixed(digits);
|
|
633
700
|
}
|
|
634
701
|
function formatBytesHuman(bytes) {
|
|
635
702
|
const KB = 1024;
|
|
@@ -639,13 +706,13 @@ function formatBytesHuman(bytes) {
|
|
|
639
706
|
const sign = bytes < 0 ? "-" : "";
|
|
640
707
|
const b = Math.abs(bytes);
|
|
641
708
|
if (b >= TB)
|
|
642
|
-
return `${sign}${(b / TB
|
|
709
|
+
return `${sign}${toFixedHalfEven(b / TB, 1)}TB`;
|
|
643
710
|
if (b >= GB)
|
|
644
|
-
return `${sign}${(b / GB
|
|
711
|
+
return `${sign}${toFixedHalfEven(b / GB, 1)}GB`;
|
|
645
712
|
if (b >= MB)
|
|
646
|
-
return `${sign}${(b / MB
|
|
713
|
+
return `${sign}${toFixedHalfEven(b / MB, 1)}MB`;
|
|
647
714
|
if (b >= KB)
|
|
648
|
-
return `${sign}${(b / KB
|
|
715
|
+
return `${sign}${toFixedHalfEven(b / KB, 1)}KB`;
|
|
649
716
|
return `${bytes}B`;
|
|
650
717
|
}
|
|
651
718
|
function formatWithCommas(n) {
|
|
@@ -671,7 +738,7 @@ function extractCurrencyCode(key) {
|
|
|
671
738
|
if (idx < 0)
|
|
672
739
|
return null;
|
|
673
740
|
const code = withoutCents.slice(idx + 1);
|
|
674
|
-
if (
|
|
741
|
+
if (!/^[A-Za-z]{3,4}$/.test(code))
|
|
675
742
|
return null;
|
|
676
743
|
return code;
|
|
677
744
|
}
|
|
@@ -686,23 +753,23 @@ function renderYamlProcessed(value, indent, lines) {
|
|
|
686
753
|
}
|
|
687
754
|
for (const pf of processObjectFields(value)) {
|
|
688
755
|
if (pf.formatted !== null) {
|
|
689
|
-
lines.push(`${prefix}${pf.key}: "${escapeYamlStr(pf.formatted)}"`);
|
|
756
|
+
lines.push(`${prefix}${yamlKey(pf.key)}: "${escapeYamlStr(pf.formatted)}"`);
|
|
690
757
|
}
|
|
691
758
|
else if (isObject(pf.value)) {
|
|
692
759
|
if (Object.keys(pf.value).length > 0) {
|
|
693
|
-
lines.push(`${prefix}${pf.key}:`);
|
|
760
|
+
lines.push(`${prefix}${yamlKey(pf.key)}:`);
|
|
694
761
|
renderYamlProcessed(pf.value, indent + 1, lines);
|
|
695
762
|
}
|
|
696
763
|
else {
|
|
697
|
-
lines.push(`${prefix}${pf.key}: {}`);
|
|
764
|
+
lines.push(`${prefix}${yamlKey(pf.key)}: {}`);
|
|
698
765
|
}
|
|
699
766
|
}
|
|
700
767
|
else if (Array.isArray(pf.value)) {
|
|
701
768
|
if (pf.value.length === 0) {
|
|
702
|
-
lines.push(`${prefix}${pf.key}: []`);
|
|
769
|
+
lines.push(`${prefix}${yamlKey(pf.key)}: []`);
|
|
703
770
|
}
|
|
704
771
|
else {
|
|
705
|
-
lines.push(`${prefix}${pf.key}:`);
|
|
772
|
+
lines.push(`${prefix}${yamlKey(pf.key)}:`);
|
|
706
773
|
for (const item of pf.value) {
|
|
707
774
|
if (isObject(item)) {
|
|
708
775
|
lines.push(`${prefix} -`);
|
|
@@ -715,7 +782,7 @@ function renderYamlProcessed(value, indent, lines) {
|
|
|
715
782
|
}
|
|
716
783
|
}
|
|
717
784
|
else {
|
|
718
|
-
lines.push(`${prefix}${pf.key}: ${yamlScalar(pf.value)}`);
|
|
785
|
+
lines.push(`${prefix}${yamlKey(pf.key)}: ${yamlScalar(pf.value)}`);
|
|
719
786
|
}
|
|
720
787
|
}
|
|
721
788
|
}
|
|
@@ -736,24 +803,24 @@ function renderYamlRaw(value, indent, lines) {
|
|
|
736
803
|
function renderYamlFieldRaw(prefix, key, value, indent, lines) {
|
|
737
804
|
if (isObject(value)) {
|
|
738
805
|
if (Object.keys(value).length > 0) {
|
|
739
|
-
lines.push(`${prefix}${key}:`);
|
|
806
|
+
lines.push(`${prefix}${yamlKey(key)}:`);
|
|
740
807
|
renderYamlRaw(value, indent + 1, lines);
|
|
741
808
|
}
|
|
742
809
|
else {
|
|
743
|
-
lines.push(`${prefix}${key}: {}`);
|
|
810
|
+
lines.push(`${prefix}${yamlKey(key)}: {}`);
|
|
744
811
|
}
|
|
745
812
|
}
|
|
746
813
|
else if (Array.isArray(value)) {
|
|
747
814
|
if (value.length > 0) {
|
|
748
|
-
lines.push(`${prefix}${key}:`);
|
|
815
|
+
lines.push(`${prefix}${yamlKey(key)}:`);
|
|
749
816
|
renderYamlArrayRaw(value, indent + 1, lines);
|
|
750
817
|
}
|
|
751
818
|
else {
|
|
752
|
-
lines.push(`${prefix}${key}: []`);
|
|
819
|
+
lines.push(`${prefix}${yamlKey(key)}: []`);
|
|
753
820
|
}
|
|
754
821
|
}
|
|
755
822
|
else {
|
|
756
|
-
lines.push(`${prefix}${key}: ${yamlScalar(value)}`);
|
|
823
|
+
lines.push(`${prefix}${yamlKey(key)}: ${yamlScalar(value)}`);
|
|
757
824
|
}
|
|
758
825
|
}
|
|
759
826
|
function renderYamlArrayRaw(arr, indent, lines) {
|
|
@@ -783,7 +850,17 @@ function renderYamlArrayRaw(arr, indent, lines) {
|
|
|
783
850
|
}
|
|
784
851
|
}
|
|
785
852
|
function escapeYamlStr(s) {
|
|
786
|
-
return s
|
|
853
|
+
return s
|
|
854
|
+
.replace(/\\/g, "\\\\")
|
|
855
|
+
.replace(/"/g, '\\"')
|
|
856
|
+
.replace(/\n/g, "\\n")
|
|
857
|
+
.replace(/\r/g, "\\r")
|
|
858
|
+
.replace(/\t/g, "\\t")
|
|
859
|
+
.replace(/\f/g, "\\f")
|
|
860
|
+
.replace(/\v/g, "\\v");
|
|
861
|
+
}
|
|
862
|
+
function yamlKey(key) {
|
|
863
|
+
return /^[A-Za-z0-9_.-]+$/.test(key) ? key : `"${escapeYamlStr(key)}"`;
|
|
787
864
|
}
|
|
788
865
|
function yamlScalar(value) {
|
|
789
866
|
if (typeof value === "string")
|
|
@@ -794,7 +871,7 @@ function yamlScalar(value) {
|
|
|
794
871
|
return value.toString();
|
|
795
872
|
if (typeof value === "number")
|
|
796
873
|
return value.toString();
|
|
797
|
-
return `"${
|
|
874
|
+
return `"${escapeYamlStr(JSON.stringify(sortJsonValue(value)))}"`;
|
|
798
875
|
}
|
|
799
876
|
// ═══════════════════════════════════════════
|
|
800
877
|
// Plain Rendering (logfmt)
|
|
@@ -850,7 +927,7 @@ function plainScalar(value) {
|
|
|
850
927
|
return value.toString();
|
|
851
928
|
if (typeof value === "number")
|
|
852
929
|
return value.toString();
|
|
853
|
-
return
|
|
930
|
+
return JSON.stringify(sortJsonValue(value));
|
|
854
931
|
}
|
|
855
932
|
function plainScalarRaw(value) {
|
|
856
933
|
if (isObject(value) || Array.isArray(value)) {
|
|
@@ -868,13 +945,20 @@ function quoteLogfmtValue(value) {
|
|
|
868
945
|
.replace(/"/g, '\\"')
|
|
869
946
|
.replace(/\n/g, "\\n")
|
|
870
947
|
.replace(/\r/g, "\\r")
|
|
871
|
-
.replace(/\t/g, "\\t")
|
|
948
|
+
.replace(/\t/g, "\\t")
|
|
949
|
+
.replace(/\f/g, "\\f")
|
|
950
|
+
.replace(/\v/g, "\\v");
|
|
872
951
|
return `"${escaped}"`;
|
|
873
952
|
}
|
|
953
|
+
function quoteLogfmtKey(key) {
|
|
954
|
+
return /^[A-Za-z0-9_.-]+$/.test(key) ? key : quoteLogfmtValue(key);
|
|
955
|
+
}
|
|
874
956
|
// ═══════════════════════════════════════════
|
|
875
957
|
// Utilities
|
|
876
958
|
// ═══════════════════════════════════════════
|
|
877
|
-
function sanitizeForJson(value, stack = new WeakSet()) {
|
|
959
|
+
function sanitizeForJson(value, stack = new WeakSet(), depth = 0) {
|
|
960
|
+
if (depth >= MAX_DEPTH)
|
|
961
|
+
return "***";
|
|
878
962
|
if (value === null)
|
|
879
963
|
return null;
|
|
880
964
|
const t = typeof value;
|
|
@@ -902,7 +986,7 @@ function sanitizeForJson(value, stack = new WeakSet()) {
|
|
|
902
986
|
if (stack.has(value))
|
|
903
987
|
return "<unsupported:circular>";
|
|
904
988
|
stack.add(value);
|
|
905
|
-
const out = value.map((item) => sanitizeForJson(item, stack));
|
|
989
|
+
const out = value.map((item) => sanitizeForJson(item, stack, depth + 1));
|
|
906
990
|
stack.delete(value);
|
|
907
991
|
return out;
|
|
908
992
|
}
|
|
@@ -913,7 +997,7 @@ function sanitizeForJson(value, stack = new WeakSet()) {
|
|
|
913
997
|
stack.add(obj);
|
|
914
998
|
const out = {};
|
|
915
999
|
for (const [k, v] of Object.entries(obj)) {
|
|
916
|
-
out[k] = sanitizeForJson(v, stack);
|
|
1000
|
+
out[k] = sanitizeForJson(v, stack, depth + 1);
|
|
917
1001
|
}
|
|
918
1002
|
stack.delete(obj);
|
|
919
1003
|
return out;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
4
|
-
export { buildJsonOk, buildJsonError, buildJson, type JsonValue, RedactionPolicy, type RedactionOptions, OutputStyle, type OutputOptions, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions,
|
|
4
|
+
export { buildJsonOk, buildJsonError, buildJson, type JsonValue, RedactionPolicy, type RedactionOptions, OutputStyle, type OutputOptions, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, redactSecretsInPlace, redactSecretsInPlaceWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, redactUrlSecrets, redactUrlSecretsWithOptions, parseSize, normalizeUtcOffset, } from "./format.js";
|
|
5
5
|
export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
|
|
6
6
|
export { type OutputFormat, cliParseOutput, cliParseLogFilters, cliOutput, cliOutputWithOptions, buildCliError, } from "./cli.js";
|
|
7
7
|
export { type SkillSpec, type SkillAction, type SkillScope, type SkillAgentSelection, type SkillAgent, type SkillOptions, type SkillTargetStatus, type SkillUninstallStatus, type SkillReport, SkillError, runSkillAdmin, } from "./skill.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
|
|
3
3
|
*/
|
|
4
|
-
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, OutputStyle, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions,
|
|
4
|
+
export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, OutputStyle, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, redactSecretsInPlace, redactSecretsInPlaceWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, redactUrlSecrets, redactUrlSecretsWithOptions, parseSize, normalizeUtcOffset, } from "./format.js";
|
|
5
5
|
export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
|
|
6
6
|
export { cliParseOutput, cliParseLogFilters, cliOutput, cliOutputWithOptions, buildCliError, } from "./cli.js";
|
|
7
7
|
export { SkillError, runSkillAdmin, } from "./skill.js";
|