agent-first-data 0.7.4 → 0.8.1

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
@@ -63,7 +63,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
63
63
 
64
64
  ## API Reference
65
65
 
66
- Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
66
+ Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
67
67
 
68
68
  ### Protocol Builders (returns JsonValue)
69
69
 
@@ -82,6 +82,16 @@ buildJsonError(message: string, hint?: string, trace?: JsonValue): JsonValue
82
82
  buildJson(code: string, fields: JsonValue, trace?: JsonValue): JsonValue
83
83
  ```
84
84
 
85
+ ### Redaction Helpers (returns JsonValue)
86
+
87
+ Use these before raw HTTP/MCP/SSE serializers that do not call `outputJson`.
88
+
89
+ ```typescript
90
+ redactedValue(value: unknown): JsonValue
91
+ redactedValueWith(value: unknown, redactionPolicy: RedactionPolicy): JsonValue
92
+ redactedValueWithOptions(value: unknown, redactionOptions: RedactionOptions): JsonValue
93
+ ```
94
+
85
95
  **Use case:** structured protocol payloads (frameworks automatically serialize)
86
96
 
87
97
  **Example:**
@@ -119,24 +129,35 @@ const notFound = buildJson(
119
129
  );
120
130
  ```
121
131
 
122
- ### CLI/Log Output (returns string)
132
+ ### Output Formatters (returns string)
123
133
 
124
- Format values for CLI output and logs. `outputJson` uses full `_secret` redaction by default. `outputJsonWith` supports explicit scoped policies. YAML and Plain always redact `_secret` and apply human-readable formatting.
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.
125
135
 
126
136
  ```typescript
127
137
  outputJson(value: JsonValue): string // Single-line JSON, original keys, for programs/logs
128
138
  outputJsonWith(value: JsonValue, redactionPolicy: RedactionPolicy): string
139
+ outputJsonWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string
129
140
  outputYaml(value: JsonValue): string // Multi-line YAML, keys stripped, values formatted
141
+ outputYamlWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string
130
142
  outputPlain(value: JsonValue): string // Single-line logfmt, keys stripped, values formatted
143
+ outputPlainWithOptions(value: JsonValue, redactionOptions: RedactionOptions): string
131
144
  ```
132
145
 
133
146
  ```typescript
134
147
  enum RedactionPolicy {
135
148
  RedactionTraceOnly = "RedactionTraceOnly",
136
149
  RedactionNone = "RedactionNone",
150
+ RedactionStrict = "RedactionStrict",
151
+ }
152
+
153
+ type RedactionOptions = {
154
+ policy?: RedactionPolicy;
155
+ secretNames?: readonly string[]; // legacy names like "api_key" or "authorization"
137
156
  }
138
157
  ```
139
158
 
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.
160
+
140
161
  **Example:**
141
162
  ```typescript
142
163
  import { outputJson, outputYaml, outputPlain } from "agent-first-data";
@@ -169,6 +190,7 @@ console.log(outputPlain(data));
169
190
 
170
191
  ```typescript
171
192
  internalRedactSecrets(value: JsonValue): void // Manually redact secrets in-place
193
+ internalRedactSecretsWithOptions(value: JsonValue, redactionOptions: RedactionOptions): void
172
194
  ```
173
195
 
174
196
  Most users don't need this. Output functions automatically protect secrets.
@@ -485,9 +507,11 @@ All formats automatically redact `_secret` fields.
485
507
  - **Currency**: `_msats`, `_sats`, `_btc`, `_usd_cents`, `_eur_cents`, `_jpy`, `_{code}_cents`
486
508
  - **Other**: `_percent`, `_secret` (auto-redacted in all formats)
487
509
 
510
+ TypeScript uses JavaScript `number` for JSON values, so `_epoch_ns` values above `Number.MAX_SAFE_INTEGER` lose precision. Prefer `_epoch_ms` for JSON interchange, or convert nanosecond values before formatting.
511
+
488
512
  ## Repository
489
513
 
490
- This package is part of the [agent-first-data](https://github.com/cmnspore/agent-first-data) repository, which also contains:
514
+ This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
491
515
 
492
516
  - **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
493
517
  - **`skills/`** — AI coding agent skill for working with AFDATA conventions
@@ -495,7 +519,7 @@ This package is part of the [agent-first-data](https://github.com/cmnspore/agent
495
519
  To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
496
520
 
497
521
  ```bash
498
- git clone https://github.com/cmnspore/agent-first-data
522
+ git clone https://github.com/agentfirstkit/agent-first-data
499
523
  cd agent-first-data/typescript
500
524
  npm test
501
525
  ```
package/dist/format.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * AFDATA output formatting and protocol templates.
3
3
  *
4
- * 9 public APIs and 1 type: 3 protocol builders + 4 output formatters + 1 redaction + 1 utility + RedactionPolicy.
4
+ * 16 public APIs and 2 types: protocol builders, redacted value helpers,
5
+ * output formatters, redaction, parseSize, RedactionPolicy, and RedactionOptions.
5
6
  */
6
7
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
7
8
  [key: string]: JsonValue;
@@ -14,18 +15,42 @@ export declare function buildJsonError(message: string, hint?: string, trace?: J
14
15
  export declare function buildJson(code: string, fields: JsonValue, trace?: JsonValue): JsonValue;
15
16
  export declare enum RedactionPolicy {
16
17
  RedactionTraceOnly = "RedactionTraceOnly",
17
- RedactionNone = "RedactionNone"
18
+ RedactionNone = "RedactionNone",
19
+ RedactionStrict = "RedactionStrict"
18
20
  }
21
+ export type RedactionOptions = {
22
+ /** Optional scoped policy. Omitted means default full redaction. */
23
+ policy?: RedactionPolicy;
24
+ /**
25
+ * Field names to treat as secrets in addition to _secret suffixes.
26
+ * Matching is exact field-name equality at any nesting level.
27
+ */
28
+ secretNames?: readonly string[];
29
+ };
19
30
  /** Format as single-line JSON. Secrets redacted, original keys, raw values. */
20
31
  export declare function outputJson(value: JsonValue): string;
21
32
  /** Format as single-line JSON with explicit redaction policy. */
22
33
  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;
23
36
  /** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
24
37
  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;
25
40
  /** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
26
41
  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;
27
44
  /** Redact _secret fields in-place. */
28
45
  export declare function internalRedactSecrets(value: JsonValue): void;
46
+ /** Redact secret fields in-place using explicit redaction options. */
47
+ export declare function internalRedactSecretsWithOptions(value: JsonValue, redactionOptions: RedactionOptions): void;
48
+ /** Return a JSON-safe copy with default _secret redaction applied. */
49
+ export declare function redactedValue(value: unknown): JsonValue;
50
+ /** Return a JSON-safe copy with an explicit redaction policy applied. */
51
+ export declare function redactedValueWith(value: unknown, redactionPolicy: RedactionPolicy): JsonValue;
52
+ /** Return a JSON-safe copy with explicit redaction options applied. */
53
+ export declare function redactedValueWithOptions(value: unknown, redactionOptions: RedactionOptions): JsonValue;
29
54
  /**
30
55
  * Parse a human-readable size string into bytes.
31
56
  * Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
package/dist/format.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * AFDATA output formatting and protocol templates.
3
3
  *
4
- * 9 public APIs and 1 type: 3 protocol builders + 4 output formatters + 1 redaction + 1 utility + RedactionPolicy.
4
+ * 16 public APIs and 2 types: protocol builders, redacted value helpers,
5
+ * output formatters, redaction, parseSize, RedactionPolicy, and RedactionOptions.
5
6
  */
6
7
  // ═══════════════════════════════════════════
7
8
  // Public API: Protocol Builders
@@ -37,34 +38,52 @@ export var RedactionPolicy;
37
38
  (function (RedactionPolicy) {
38
39
  RedactionPolicy["RedactionTraceOnly"] = "RedactionTraceOnly";
39
40
  RedactionPolicy["RedactionNone"] = "RedactionNone";
41
+ RedactionPolicy["RedactionStrict"] = "RedactionStrict";
40
42
  })(RedactionPolicy || (RedactionPolicy = {}));
41
43
  /** Format as single-line JSON. Secrets redacted, original keys, raw values. */
42
44
  export function outputJson(value) {
43
- const v = sanitizeForJson(value);
44
- redactSecrets(v);
45
- return JSON.stringify(v);
45
+ return JSON.stringify(redactedValue(value));
46
46
  }
47
47
  /** Format as single-line JSON with explicit redaction policy. */
48
48
  export function outputJsonWith(value, redactionPolicy) {
49
- const v = sanitizeForJson(value);
50
- applyRedactionPolicy(v, redactionPolicy);
51
- return JSON.stringify(v);
49
+ return JSON.stringify(redactedValueWith(value, redactionPolicy));
50
+ }
51
+ /** Format as single-line JSON with explicit redaction options. */
52
+ export function outputJsonWithOptions(value, redactionOptions) {
53
+ return JSON.stringify(redactedValueWithOptions(value, redactionOptions));
52
54
  }
53
55
  /** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
54
56
  export function outputYaml(value) {
55
- value = sanitizeForJson(value);
57
+ value = redactedValue(value);
58
+ const lines = ["---"];
59
+ renderYamlProcessed(value, 0, lines);
60
+ return lines.join("\n");
61
+ }
62
+ /** Format as multi-line YAML with explicit redaction options. */
63
+ export function outputYamlWithOptions(value, redactionOptions) {
64
+ value = redactedValueWithOptions(value, redactionOptions);
56
65
  const lines = ["---"];
57
66
  renderYamlProcessed(value, 0, lines);
58
67
  return lines.join("\n");
59
68
  }
60
69
  /** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
61
70
  export function outputPlain(value) {
62
- value = sanitizeForJson(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(" ");
78
+ }
79
+ /** Format as single-line logfmt with explicit redaction options. */
80
+ export function outputPlainWithOptions(value, redactionOptions) {
81
+ value = redactedValueWithOptions(value, redactionOptions);
63
82
  const pairs = [];
64
83
  collectPlainPairs(value, "", pairs);
65
84
  pairs.sort(([a], [b]) => jcsCompare(a, b));
66
85
  return pairs
67
- .map(([k, v]) => (v.includes(" ") ? `${k}="${v}"` : `${k}=${v}`))
86
+ .map(([k, v]) => `${k}=${quoteLogfmtValue(v)}`)
68
87
  .join(" ");
69
88
  }
70
89
  // ═══════════════════════════════════════════
@@ -74,6 +93,28 @@ export function outputPlain(value) {
74
93
  export function internalRedactSecrets(value) {
75
94
  redactSecrets(value);
76
95
  }
96
+ /** Redact secret fields in-place using explicit redaction options. */
97
+ export function internalRedactSecretsWithOptions(value, redactionOptions) {
98
+ applyRedactionOptions(value, redactionOptions);
99
+ }
100
+ /** Return a JSON-safe copy with default _secret redaction applied. */
101
+ export function redactedValue(value) {
102
+ const v = sanitizeForJson(value);
103
+ redactSecrets(v);
104
+ return v;
105
+ }
106
+ /** Return a JSON-safe copy with an explicit redaction policy applied. */
107
+ export function redactedValueWith(value, redactionPolicy) {
108
+ const v = sanitizeForJson(value);
109
+ applyRedactionPolicy(v, redactionPolicy);
110
+ return v;
111
+ }
112
+ /** Return a JSON-safe copy with explicit redaction options applied. */
113
+ export function redactedValueWithOptions(value, redactionOptions) {
114
+ const v = sanitizeForJson(value);
115
+ applyRedactionOptions(v, redactionOptions);
116
+ return v;
117
+ }
77
118
  /**
78
119
  * Parse a human-readable size string into bytes.
79
120
  * Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
@@ -113,41 +154,77 @@ export function parseSize(s) {
113
154
  // ═══════════════════════════════════════════
114
155
  // Secret Redaction
115
156
  // ═══════════════════════════════════════════
116
- function redactSecrets(value) {
157
+ const EMPTY_SECRET_NAMES = new Set();
158
+ function secretNameSet(redactionOptions) {
159
+ return new Set(redactionOptions.secretNames ?? []);
160
+ }
161
+ function keyHasSecretSuffix(key) {
162
+ return key.endsWith("_secret") || key.endsWith("_SECRET");
163
+ }
164
+ function isSecretKey(key, secretNames) {
165
+ return keyHasSecretSuffix(key) || secretNames.has(key);
166
+ }
167
+ function redactSecrets(value, secretNames = EMPTY_SECRET_NAMES) {
117
168
  if (isObject(value)) {
118
169
  for (const k of Object.keys(value)) {
119
- if (k.endsWith("_secret") || k.endsWith("_SECRET")) {
170
+ if (isSecretKey(k, secretNames)) {
120
171
  const v = value[k];
121
172
  if (isObject(v) || Array.isArray(v)) {
122
- redactSecrets(v);
173
+ redactSecrets(v, secretNames);
123
174
  }
124
175
  else {
125
176
  value[k] = "***";
126
177
  }
127
178
  }
128
179
  else {
129
- redactSecrets(value[k]);
180
+ redactSecrets(value[k], secretNames);
181
+ }
182
+ }
183
+ }
184
+ else if (Array.isArray(value)) {
185
+ for (const item of value) {
186
+ redactSecrets(item, secretNames);
187
+ }
188
+ }
189
+ }
190
+ function redactSecretsStrict(value, secretNames = EMPTY_SECRET_NAMES) {
191
+ if (isObject(value)) {
192
+ for (const k of Object.keys(value)) {
193
+ if (isSecretKey(k, secretNames)) {
194
+ value[k] = "***";
195
+ }
196
+ else {
197
+ redactSecretsStrict(value[k], secretNames);
130
198
  }
131
199
  }
132
200
  }
133
201
  else if (Array.isArray(value)) {
134
202
  for (const item of value) {
135
- redactSecrets(item);
203
+ redactSecretsStrict(item, secretNames);
136
204
  }
137
205
  }
138
206
  }
139
207
  function applyRedactionPolicy(value, redactionPolicy) {
208
+ applyRedactionPolicyWithNames(value, redactionPolicy, EMPTY_SECRET_NAMES);
209
+ }
210
+ function applyRedactionOptions(value, redactionOptions) {
211
+ applyRedactionPolicyWithNames(value, redactionOptions.policy, secretNameSet(redactionOptions));
212
+ }
213
+ function applyRedactionPolicyWithNames(value, redactionPolicy, secretNames) {
140
214
  switch (redactionPolicy) {
141
215
  case RedactionPolicy.RedactionTraceOnly:
142
216
  if (isObject(value) && value.trace !== undefined) {
143
- redactSecrets(value.trace);
217
+ redactSecrets(value.trace, secretNames);
144
218
  }
145
219
  break;
146
220
  case RedactionPolicy.RedactionNone:
147
221
  break;
222
+ case RedactionPolicy.RedactionStrict:
223
+ redactSecretsStrict(value, secretNames);
224
+ break;
148
225
  default:
149
- // Safety fallback for unknown values.
150
- redactSecrets(value);
226
+ // Empty/unknown policy falls back to default full redaction.
227
+ redactSecrets(value, secretNames);
151
228
  break;
152
229
  }
153
230
  }
@@ -503,6 +580,19 @@ function plainScalar(value) {
503
580
  return value.toString();
504
581
  return String(value);
505
582
  }
583
+ function quoteLogfmtValue(value) {
584
+ if (value === "")
585
+ return "";
586
+ if (!/[\s="\\]/.test(value))
587
+ return value;
588
+ const escaped = value
589
+ .replace(/\\/g, "\\\\")
590
+ .replace(/"/g, '\\"')
591
+ .replace(/\n/g, "\\n")
592
+ .replace(/\r/g, "\\r")
593
+ .replace(/\t/g, "\\t");
594
+ return `"${escaped}"`;
595
+ }
506
596
  // ═══════════════════════════════════════════
507
597
  // Utilities
508
598
  // ═══════════════════════════════════════════
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, outputJson, outputJsonWith, outputYaml, outputPlain, internalRedactSecrets, parseSize, } from "./format.js";
4
+ export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, type RedactionOptions, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, parseSize, } from "./format.js";
5
5
  export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
6
6
  export { type OutputFormat, cliParseOutput, cliParseLogFilters, cliOutput, 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, outputYaml, outputPlain, internalRedactSecrets, parseSize, } from "./format.js";
4
+ export { buildJsonOk, buildJsonError, buildJson, RedactionPolicy, outputJson, outputJsonWith, outputJsonWithOptions, outputYaml, outputYamlWithOptions, outputPlain, outputPlainWithOptions, internalRedactSecrets, internalRedactSecretsWithOptions, redactedValue, redactedValueWith, redactedValueWithOptions, parseSize, } from "./format.js";
5
5
  export { log, span, initJson, initPlain, initYaml } from "./afdata_logging.js";
6
6
  export { cliParseOutput, cliParseLogFilters, cliOutput, buildCliError } from "./cli.js";
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "agent-first-data",
3
- "version": "0.7.4",
4
- "description": "Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents",
3
+ "version": "0.8.1",
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": {
7
7
  "type": "git",
8
- "url": "https://github.com/cmnspore/agent-first-data"
8
+ "url": "https://github.com/agentfirstkit/agent-first-data"
9
9
  },
10
10
  "type": "module",
11
11
  "main": "dist/index.js",