agent-first-data 0.17.3 → 0.18.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
@@ -5,7 +5,7 @@ npm install agent-first-data
5
5
  ```
6
6
 
7
7
  ```typescript
8
- import { jsonResult, outputJson, outputPlain } from "agent-first-data";
8
+ import { jsonResult, render } from "agent-first-data";
9
9
 
10
10
  const event = jsonResult({
11
11
  api_key_secret: "sk-123",
@@ -13,11 +13,11 @@ const event = jsonResult({
13
13
  db_url: "postgres://user:p@ss@db/app?token_secret=abc",
14
14
  }).build();
15
15
 
16
- console.log(outputJson(event.toJSON()));
17
- console.log(outputPlain(event.toJSON()));
16
+ console.log(render(event.toJSON(), "json"));
17
+ console.log(render(event.toJSON(), "plain"));
18
18
  ```
19
19
 
20
- Useful names use TypeScript casing: `outputJson`, `outputYaml`, `outputPlain`, `outputOptionsForPolicy`, `redactedValue`, `redactUrlSecrets`, `normalizeUtcOffset`, `isValidRfc3339Date`, `isValidRfc3339Time`, `isValidRfc3339`, `isValidBcp47`, `decodeProtocolEvent`, `cliParseOutput`, `cliParseLogFilters`, `cliOutput`, `buildCliError`, `buildCliVersion`, and `cliHandleVersionOrContinue`.
20
+ Useful names use TypeScript casing: `render`, `outputOptionsForPolicy`, `redactedValue`, `redactUrlSecrets`, `normalizeUtcOffset`, `isValidRfc3339Date`, `isValidRfc3339Time`, `isValidRfc3339`, `isValidBcp47`, `decodeProtocolEvent`, `cliParseOutput`, `cliParseLogFilters`, `buildCliError`, `buildCliVersion`, and `cliHandleVersionOrContinue`.
21
21
 
22
22
  Skill admin and stream redirection are not re-exported from the package root; import them from their own subpaths:
23
23
 
@@ -30,7 +30,7 @@ import { installStreamRedirectFromRawArgs } from "agent-first-data/stream-redire
30
30
 
31
31
  - Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
32
32
  - `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed and internal whitespace redacts the whole field.
33
- - YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
33
+ - YAML keeps original keys and values (structure-preserving, like JSON), sorting keys by UTF-16 code unit order and quoting/escaping unsafe keys and string scalars. Plain strips formatting suffixes, formats values, sorts the same way, and renders nested objects/arrays as canonical JSON.
34
34
  - Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
35
35
  - `buildCliError(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
36
36
  - Use `cliHandleVersionOrContinue()` before argument parsing so bare `--version` stays conventional and `--version --output json|yaml|plain` stays structured.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agent-first-data
3
- description: Apply and review the formal Agent-First Data specification using the Rust/Go/Python/TypeScript libraries or afdata CLI for naming, redaction, formatting, protocol envelopes, logging, linting, and validation. Use proactively when writing or reviewing structured data, configs, logs, transport payloads, database/wire fields, CLI output, or compatibility-sensitive public and persistent field names.
3
+ description: Apply and review the formal Agent-First Data specification using the Rust/Go/Python/TypeScript libraries or afdata CLI for naming, redaction, formatting, protocol envelopes, logging, linting, validation, and reading or safely editing structured JSON/TOML/YAML/dotenv/INI documents by dot-path. Use proactively when writing or reviewing structured data, configs, logs, transport payloads, database/wire fields, CLI output, compatibility-sensitive public and persistent field names, or when reading or editing a config/document file from the shell.
4
4
  ---
5
5
 
6
6
  <!-- Canonical source: skills/agent-first-data/SKILL.md.
@@ -47,22 +47,21 @@ go get github.com/agentfirstkit/agent-first-data/go
47
47
  ```
48
48
 
49
49
  ```rust
50
- use agent_first_data::{json_result, output_json};
50
+ use agent_first_data::{json_result, render, OutputFormat, OutputOptions};
51
51
  use serde_json::json;
52
52
 
53
53
  let event = json_result(json!({
54
54
  "latency_ms": 1280,
55
55
  "api_key_secret": "sk-live-example",
56
- })).build()?;
57
- let rendered = output_json(event.as_value());
56
+ })).build();
57
+ let rendered = render(event.as_value(), OutputFormat::Json, &OutputOptions::default());
58
58
  assert!(rendered.contains("\"api_key_secret\":\"***\""));
59
- # Ok::<(), agent_first_data::BuildError>(())
60
59
  ```
61
60
 
62
61
  For a finite CLI execution implemented in Rust, create one `CliEmitter`, select
63
62
  `OutputFormat` through `cli_parse_output`, enable strict protocol, and emit one
64
63
  terminal result or error. Handle version before clap with
65
- `cli_handle_version_or_continue` and `VersionConfig::conventional_default()`.
64
+ `cli_handle_version_or_continue(raw_args, name, version)`.
66
65
  For non-CLI HTTP/MCP/SSE serialization, call `redacted_value()` or
67
66
  `redact_url_secrets()` explicitly at the boundary.
68
67
 
@@ -94,11 +93,11 @@ working in that runtime.
94
93
 
95
94
  ## Redaction behavior contract
96
95
 
97
- Fields named `_secret` or `_url` and values passing through AFDATA output formatters (`output_json`, `output_yaml`, `output_plain`) are automatically redacted: secrets become `***`, URL userinfo passwords and suffix-named query parameters are scrubbed. When serializing outside output formatters (HTTP, MCP, SSE), call `redacted_value()` or `redact_url_secrets()` at the serialization boundary. PII redaction, domain-specific privacy policies (header allowlists, API scope sensitivity), and per-field secret naming are each spore's responsibility; the library provides field-name-based mechanics only.
96
+ Fields named `_secret` or `_url` and values passing through AFDATA output rendering (`render`, in any format) are automatically redacted: secrets become `***`, URL userinfo passwords and suffix-named query parameters are scrubbed. When serializing outside output formatters (HTTP, MCP, SSE), call `redacted_value()` or `redact_url_secrets()` at the serialization boundary. PII redaction, domain-specific privacy policies (header allowlists, API scope sensitivity), and per-field secret naming are each spore's responsibility; the library provides field-name-based mechanics only.
98
97
 
99
98
  ## Logging behavior contract
100
99
 
101
- One-time CLI output uses `json_log()` or `CliEmitter` for a single event; serialization via `cli_output()` applies output formatting. Long-running services or processes depending on structured logging (tonic, sqlx, hyper, etc. via tracing) initialize with `afdata_tracing::try_init()` (Rust only) to wire process-wide logging; other languages emit log events explicitly via builders or integrate their own structured logging.
100
+ One-time CLI output uses `json_log()` or `CliEmitter` for a single event; serialization via `render()` applies output formatting. Long-running services or processes depending on structured logging (tonic, sqlx, hyper, etc. via tracing) initialize with `afdata_tracing::try_init()` (Rust only) to wire process-wide logging; other languages emit log events explicitly via builders or integrate their own structured logging.
102
101
 
103
102
  ## CLI workflow
104
103
 
@@ -114,16 +113,62 @@ Use it for deterministic checks:
114
113
  ```bash
115
114
  afdata lint payload.json
116
115
  afdata validate events.jsonl
117
- afdata format payload.json --output yaml
116
+ afdata render payload.json --output yaml
118
117
  afdata skill validate skills/example-skill
119
118
  ```
120
119
 
121
120
  - Run `afdata lint` for ordinary JSON/JSONL, JSON Schema, or MCP input/output schemas.
122
121
  - Run `afdata validate` for AFDATA protocol events or finite event streams.
123
- - Omit the input path to read stdin. `--output` accepts `json`, `yaml`, or
124
- `plain`.
122
+ - Run `afdata render` to apply AFDATA redaction/formatting to arbitrary JSON or JSONL. Omit the input path to read stdin. `--output` accepts `json`, `yaml`, or
123
+ `plain`; JSON and YAML keep original keys/values (structure-preserving), Plain is the lossy human renderer.
125
124
  - Treat findings as contract issues unless the owning tool intentionally documents a non-AFDATA field.
126
125
 
126
+ ## Document workflow
127
+
128
+ Use `show`/`get`/`value` to read a JSON, TOML, YAML, dotenv, or INI document
129
+ and `set`/`add`/`remove`/`unset` to edit one in place, instead of `sed`,
130
+ regular expressions, or a generic reserializer — those can reorder keys, drop
131
+ comments, or change quoting/anchors in the rest of the file.
132
+
133
+ - `show` returns the whole document as one AFDATA record; use it for batch
134
+ inspection, and pass every known sensitive legacy field name with
135
+ `--secret-name FIELD` (an exact field name, not a dot-path).
136
+ - `get <KEY>` returns one dot-path value as an AFDATA record. A secret-named
137
+ leaf (`_secret`/`_SECRET`, or an exact `--secret-name` match) is still
138
+ redacted to `"***"` even though the caller targeted it directly — use
139
+ `value --reveal-secret` when the real value is genuinely needed.
140
+ - `value <KEY>` writes only the raw scalar to stdout, with no AFDATA envelope
141
+ and no forced trailing newline, for direct use in shell substitution:
142
+ `value=$(afdata value server.host config.toml)` (KEY then FILE). Only
143
+ scalars are supported; an array or object errors with `path <KEY> is not a
144
+ scalar` (exit 1) — use `get`/`show` for a subtree. A secret-named leaf errors unless
145
+ `--reveal-secret` is passed — there is no default bypass for a targeted read.
146
+ - Read commands (`show`/`get`/`value`) take a positional FILE or fall back to
147
+ piped stdin, defaulting to JSON and rejecting an interactive TTY rather than
148
+ blocking. Mutation commands (`set`/`add`/`remove`/`unset`) always require
149
+ `--input-file PATH` and never read stdin.
150
+ - Escape a literal dot as `\.` and a literal backslash as `\\` in a dot-path;
151
+ every command shares this grammar, so an unrecognized escape or empty
152
+ segment is always an error, never a guess.
153
+ - `add`/`remove` operate on a keyed list — an array of objects addressed by a
154
+ slug — and always require an explicit `--slug-field FIELD`; the CLI never
155
+ infers an identity field. Keyed editing is implemented for JSON and YAML
156
+ only; TOML, dotenv, and INI return a structured "not implemented" error, so
157
+ edit their list entries by writing the whole value another way.
158
+ - For `set`, choose exactly one secret source instead of a positional value
159
+ when the value must not land in shell history or `ps`: `--value-secret
160
+ VALUE` (convenient, but visible to argv/process listings),
161
+ `--value-secret-stdin` (pipe), `--value-secret-prompt` (human, terminal echo
162
+ off), or `--value-secret-fd FD` (Unix automation that needs stdin for
163
+ something else).
164
+ - Mutations preserve the rest of the source document — comments, key order,
165
+ unrelated formatting — and refuse to write through a symlink or (on Unix) a
166
+ multi-link hardlink target. Treat a failed mutation as leaving the original
167
+ file untouched rather than assuming a partial write, and reread before
168
+ retrying. A format backend that isn't compiled in, or input outside the
169
+ documented dotenv/INI dialect, is a structured error to report, not a cue to
170
+ hand-edit the file.
171
+
127
172
  ## Review checklist
128
173
 
129
174
  1. Numeric fields with units carry the right suffix and safe integer/string representation.
@@ -107,7 +107,7 @@ Sub-cent precision — `_{code}_micro`, integer millionths (10⁻⁶) of the maj
107
107
  | `_secret` | redact the entire value/subtree to `***` | `api_key_secret: "sk-or-v1-abc..."` |
108
108
  | `_url` | scrub secrets *inside* the URL value, keep the rest | `callback_url: "https://h/cb?code_secret=..."` |
109
109
 
110
- All CLI output formats (JSON, YAML, Plain) automatically redact `_secret` fields. Matching recognizes `_secret` and `_SECRET` only — no mixed case. The entire `_secret` value/subtree becomes `***`, including objects and arrays. For legacy fields that cannot be renamed, configure `OutputOptions.redaction` with `secret_names`/`secretNames` such as `["api_key", "authorization"]`; names match exact field names at any nesting level; no trim, case folding, hyphen/underscore normalization, globs, regex, or substring matching. AFDATA does not define named redaction profiles; use the default, `secret_names`, `RedactionTraceOnly`, or `RedactionNone` deliberately at the serialization boundary. Callers that need schema-preserving YAML/plain rendering can pass `OutputOptions` with the `Raw` output style.
110
+ All CLI output formats (JSON, YAML, Plain) automatically redact `_secret` fields. Matching recognizes `_secret` and `_SECRET` only — no mixed case. The entire `_secret` value/subtree becomes `***`, including objects and arrays. For legacy fields that cannot be renamed, configure `OutputOptions.redaction` with `secret_names`/`secretNames` such as `["api_key", "authorization"]`; names match exact field names at any nesting level; no trim, case folding, hyphen/underscore normalization, globs, regex, or substring matching. AFDATA does not define named redaction profiles; use the default policy `All`, `secret_names`, `TraceOnly`, or `Off` deliberately at the serialization boundary. YAML is always schema-preserving, like JSON. Callers that need schema-preserving Plain rendering can pass `OutputOptions` with the `Raw` output style.
111
111
 
112
112
  `***` means only one thing: AFDATA redacted a sensitive value. Do not use it for serialization failure, truncation, unsupported types, or “maybe secret” guesses.
113
113
 
@@ -158,15 +158,17 @@ ORM struct fields preserve the suffix: `duration_ms: i64`, not `duration: i64`.
158
158
 
159
159
  ## Part 2: Output Processing
160
160
 
161
- Three output formats via `output_json()`, `output_yaml()`, `output_plain()`. Default YAML and Plain apply key stripping + value formatting. Optional `options` (Python/TS keyword arg; Rust/Go `_with_options` variant).
161
+ Three output formats via one entry point `render(value, format, options)` (`format` = json/yaml/plain). JSON and YAML are both **structure-preserving**: original keys, scalar types, and numeric semantics survive after redaction. Plain is the one **lossy/human** renderer — it strips formatting suffixes and reformats values for scanning. `options` is an optional keyword arg in Python/TS; Rust/Go pass `OutputOptions` explicitly.
162
162
 
163
163
  ### Formats
164
164
 
165
165
  - **JSON** — single-line, original keys, raw values, no sorting (machine-readable), secrets redacted
166
- - **YAML** — multi-line, formatting suffixes stripped, values formatted, secrets redacted by default
166
+ - **YAML** — multi-line, original keys, raw values (same semantics as JSON), keys sorted, secrets redacted by default
167
167
  - **Plain** — single-line logfmt, formatting suffixes stripped, values formatted, secrets redacted by default
168
168
 
169
- ### Key stripping (YAML and Plain)
169
+ Rust gates YAML output behind the `yaml` Cargo feature (on by default, along with the rest of the default feature set). With `default-features = false` and `yaml` not re-enabled, `OutputFormat::Yaml` does not exist and `--output yaml` is rejected with a feature-requirement error instead of silently falling back to another format.
170
+
171
+ ### Key stripping (Plain only)
170
172
 
171
173
  Remove recognized formatting suffix from key. Longest match first, exact lowercase or uppercase only:
172
174
 
@@ -176,9 +178,9 @@ Remove recognized formatting suffix from key. Longest match first, exact lowerca
176
178
  4. `_msats`, `_sats`, `_bytes`, `_percent`, `_secret`
177
179
  5. `_jpy`, `_ns`, `_us`, `_ms`, `_s`
178
180
 
179
- `_bcp47`, `_utc_offset`, `_rfc3339_date`, and `_rfc3339_time` are NOT stripped (pass through). If two keys collide after stripping, both revert to original key AND raw value (no formatting). Redaction runs before collision handling, so fallback never restores a secret.
181
+ `_bcp47`, `_utc_offset`, `_rfc3339_date`, and `_rfc3339_time` are NOT stripped (pass through). If two keys collide after stripping, both revert to original key AND raw value (no formatting). Redaction runs before collision handling, so fallback never restores a secret. JSON and YAML never strip suffixes — every key renders exactly as written.
180
182
 
181
- ### Value formatting (YAML and Plain)
183
+ ### Value formatting (Plain only)
182
184
 
183
185
  - `_ms` with absolute value < 1000 → `{n}ms`; absolute value ≥ 1000 → seconds (`1280` → `1.28s`, `-1500` → `-1.5s`)
184
186
  - `_s`, `_ns`, `_us` → append unit (`3600s`, `450000ns`, `830μs`)
@@ -192,7 +194,7 @@ Remove recognized formatting suffix from key. Longest match first, exact lowerca
192
194
  - `_secret` → `***` (the redaction phase already replaced the subtree)
193
195
  - `_bcp47`, `_utc_offset`, `_rfc3339_date`, `_rfc3339_time` → pass through unchanged
194
196
 
195
- **Type constraints**: `_bytes`/`_epoch_*` require integer. `_usd_cents`/`_eur_cents`/`_jpy`/`_{code}_cents`/`_{code}_micro` require non-negative integer. Duration/Bitcoin/`_percent` accept any number. Wrong type → raw value + original key.
197
+ **Type constraints**: `_bytes`/`_epoch_*` require integer. `_usd_cents`/`_eur_cents`/`_jpy`/`_{code}_cents`/`_{code}_micro` require non-negative integer. Duration/Bitcoin/`_percent` accept any number. Wrong type → raw value + original key. JSON and YAML never format values — a value renders exactly as its JSON type (numbers stay numbers, strings stay quoted strings, no unit/date/currency formatting).
196
198
 
197
199
  ### Plain logfmt details
198
200
 
@@ -204,7 +206,11 @@ Remove recognized formatting suffix from key. Longest match first, exact lowerca
204
206
 
205
207
  ### Key ordering
206
208
 
207
- YAML and Plain sort keys (after stripping) by UTF-16 code unit order (JCS, RFC 8785). For ASCII keys this equals byte-order sorting.
209
+ YAML sorts keys by UTF-16 code unit order (JCS, RFC 8785) without stripping suffixes. Plain sorts keys the same way, but after stripping. For ASCII keys this equals byte-order sorting.
210
+
211
+ ### `PlainStyle` (Plain only)
212
+
213
+ `PlainStyle` (`Readable`/`Raw`) only affects Plain: `Readable` (default) strips suffixes and formats values; `Raw` keeps original keys/values (still redacted). YAML ignores `PlainStyle` entirely — it is always structure-preserving, matching JSON.
208
214
 
209
215
  ---
210
216
 
@@ -269,17 +275,17 @@ Use the local language README for installation and the full overview/spec for AP
269
275
 
270
276
  Required cross-language behavior to rely on:
271
277
 
272
- - Output helpers redact before formatting: `output_json()`, `output_yaml()`, `output_plain()` (with optional `options` in Python/TS).
273
- - Use `redacted_value()` for raw HTTP/MCP/SSE serialization paths that bypass `output_json()`.
278
+ - The single output entry point `render(value, format, options)` redacts before formatting (with optional `options` in Python/TS).
279
+ - Use `redacted_value()` for raw HTTP/MCP/SSE serialization paths that bypass `render()`.
274
280
  - Use `redact_url_secrets()` for URLs embedded in log messages or prose before interpolating them into output.
275
- - Use `cli_parse_output()`, `cli_parse_log_filters()`, `cli_output()`, `build_cli_error()`, and the version helper for CLI tools instead of custom parsing/error envelopes.
281
+ - Use `cli_parse_output()`, `cli_parse_log_filters()`, `render()`, `build_cli_error()`, and the version helper for CLI tools instead of custom parsing/error envelopes.
276
282
  - `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
277
- - Rust CLIs should call `cli_handle_version_or_continue()` before clap parsing so `--version --output json|yaml|plain` emits a structured `kind:"result"` event with `result.version` instead of clap's plain text. Use `VersionConfig::conventional_default()` so bare `--version` stays human text while explicit `--output` remains structured.
283
+ - Rust CLIs should call `cli_handle_version_or_continue(raw_args, name, version)` before clap parsing so `--version --output json|yaml|plain` emits a structured `kind:"result"` event with `result.code == "version"` and `result.version` instead of clap's plain text. Bare `--version` always stays human `<name> <version>` text; explicit `--output` is always structured — there is no configuration.
278
284
  - Use the protocol reader `decode_protocol_event(text)` to parse and strict-validate a single JSON text line, receiving a typed decoded event.
279
285
 
280
286
  ## AFDATA Logging
281
287
 
282
- Long-running services and processes depending on structured logging (tonic, sqlx, hyper, etc. via tracing) use a logging init function to capture the full process. One-time CLI output uses `json_log()` + `cli_output()` or `CliEmitter`.
288
+ Long-running services and processes depending on structured logging (tonic, sqlx, hyper, etc. via tracing) use a logging init function to capture the full process. One-time CLI output uses `json_log()` + `render()` or `CliEmitter`.
283
289
 
284
290
  ### Init (Rust only; pick one format per process)
285
291
 
@@ -312,7 +318,7 @@ Log redaction is **by field name** (the same `_secret`/`_url` rule as all output
312
318
  CLI tools that use AFDATA should support output and logging flags:
313
319
 
314
320
  ```
315
- --output json|yaml|plain # default is tool-defined (interactive → yaml, scripting/logging → json)
321
+ --output json|yaml|plain # default is tool-defined (structured/scripting → json or yaml, human scanning → plain)
316
322
  --log startup,request,progress,retry,redirect
317
323
  --verbose # shorthand for all log categories
318
324
  ```
package/dist/cli.d.ts CHANGED
@@ -21,18 +21,22 @@ export declare class LogFilters {
21
21
  constructor(entries: readonly string[]);
22
22
  /**
23
23
  * Check if an event matches the filter set.
24
- * Returns false for empty filters, true if "all" or "*" is present,
25
- * otherwise checks if event.toLowerCase() starts with any filter.
24
+ *
25
+ * An empty filter set returns false (filtering is opt-in). The single
26
+ * wildcard word "all" returns true ("*" is not special — one wildcard
27
+ * spelling, not two). Otherwise returns true iff event.toLowerCase() starts
28
+ * with any filter (prefix match); a mistyped filter simply matches nothing
29
+ * and silently emits no output.
26
30
  *
27
31
  * @example
28
32
  * const lf = new LogFilters(["query", "error"]);
29
33
  * lf.enabled("QueryStarted") // → true (starts with "query")
30
34
  * lf.enabled("debug") // → false
31
35
  *
32
- * const all = new LogFilters([]);
33
- * all.enabled("anything") // → false (empty)
36
+ * const none = new LogFilters([]);
37
+ * none.enabled("anything") // → false (empty)
34
38
  *
35
- * const wild = new LogFilters(["*"]);
39
+ * const wild = new LogFilters(["all"]);
36
40
  * wild.enabled("anything") // → true
37
41
  */
38
42
  enabled(event: string): boolean;
@@ -48,13 +52,15 @@ export declare class LogFilters {
48
52
  */
49
53
  export declare function cliParseLogFilters(entries: string[]): LogFilters;
50
54
  /**
51
- * Dispatch output formatting by OutputFormat, with optional explicit redaction and style.
52
- * JSON ignores OutputStyle and preserves original keys and values after redaction.
55
+ * Single public render entry point: dispatch a value or Event to
56
+ * JSON/YAML/plain text by OutputFormat, with optional explicit redaction and
57
+ * style. `options.style` (PlainStyle) affects plain (logfmt) output only;
58
+ * JSON and YAML are structure-preserving and ignore it.
53
59
  *
54
60
  * @example
55
- * cliOutput(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
61
+ * render(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
56
62
  */
57
- export declare function cliOutput(value: JsonValue | Event, format: OutputFormat, options?: OutputOptions): string;
63
+ export declare function render(value: JsonValue | Event, format: OutputFormat, options?: OutputOptions): string;
58
64
  export type CliEventWriter = (line: string) => void;
59
65
  /** Stateful emitter for finite structured CLI executions. */
60
66
  export declare class CliEmitter {
@@ -79,16 +85,30 @@ export declare function cliRenderVersion(name: string, version: string, format?:
79
85
  /**
80
86
  * Render version output if --version/-V is present; otherwise return undefined.
81
87
  * Throws for malformed version requests, for example `--version --output xml`.
88
+ *
89
+ * One blessed behavior, not configurable: a bare version request renders
90
+ * conventional "<name> <version>" text; `--json` or `--output <fmt>` /
91
+ * `--output=<fmt>` alongside it selects the structured AFDATA payload
92
+ * instead.
93
+ *
94
+ * Only a top-level version request is recognized: scanning stops at the first
95
+ * positional argument (the subcommand) or `--`, so `tool sub --version <value>`
96
+ * leaves `--version` for the subcommand's parser rather than printing the
97
+ * tool version.
82
98
  */
83
- export declare function cliHandleVersionOrContinue(rawArgs: string[], name: string, version: string, defaultOutput?: OutputFormat | null, outputFlag?: string, allowOutputFormat?: boolean): string | undefined;
99
+ export declare function cliHandleVersionOrContinue(rawArgs: string[], name: string, version: string): string | undefined;
84
100
  /**
85
- * Build a standard CLI parse error value.
101
+ * Build a standard CLI parse error value. This function cannot fail.
86
102
  * Use when flag parsing fails or a flag value is invalid.
87
- * Print with outputJson and exit with code 2.
103
+ * Print with render and exit with code 2.
104
+ *
105
+ * Always returns a strict-valid `kind:"error"` event with code `"cli_error"`.
106
+ * An empty `message` is replaced with a generic placeholder so the returned
107
+ * event stays strict-valid without throwing.
88
108
  *
89
109
  * @example
90
110
  * const err = buildCliError("--output: invalid value 'xml'");
91
- * console.log(outputJson(err));
111
+ * console.log(render(err, "json"));
92
112
  * process.exit(2);
93
113
  */
94
114
  export declare function buildCliError(message: string, hint?: string): Event;
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 { Event, jsonError, jsonResult, outputJson, outputYaml, outputPlain, validateProtocolEvent, } from "./format.js";
4
+ import { Event, jsonError, jsonResult, formatJsonValue, formatYamlValue, formatPlainValue, validateProtocolEvent, } from "./format.js";
5
5
  /**
6
6
  * Parse the --output flag value into an OutputFormat.
7
7
  * Throws on unknown values; catch and pass message to buildCliError.
@@ -33,24 +33,28 @@ export class LogFilters {
33
33
  }
34
34
  /**
35
35
  * Check if an event matches the filter set.
36
- * Returns false for empty filters, true if "all" or "*" is present,
37
- * otherwise checks if event.toLowerCase() starts with any filter.
36
+ *
37
+ * An empty filter set returns false (filtering is opt-in). The single
38
+ * wildcard word "all" returns true ("*" is not special — one wildcard
39
+ * spelling, not two). Otherwise returns true iff event.toLowerCase() starts
40
+ * with any filter (prefix match); a mistyped filter simply matches nothing
41
+ * and silently emits no output.
38
42
  *
39
43
  * @example
40
44
  * const lf = new LogFilters(["query", "error"]);
41
45
  * lf.enabled("QueryStarted") // → true (starts with "query")
42
46
  * lf.enabled("debug") // → false
43
47
  *
44
- * const all = new LogFilters([]);
45
- * all.enabled("anything") // → false (empty)
48
+ * const none = new LogFilters([]);
49
+ * none.enabled("anything") // → false (empty)
46
50
  *
47
- * const wild = new LogFilters(["*"]);
51
+ * const wild = new LogFilters(["all"]);
48
52
  * wild.enabled("anything") // → true
49
53
  */
50
54
  enabled(event) {
51
55
  if (this.filters.length === 0)
52
56
  return false;
53
- if (this.filters.includes("all") || this.filters.includes("*"))
57
+ if (this.filters.includes("all"))
54
58
  return true;
55
59
  const eventLower = event.toLowerCase();
56
60
  for (const filter of this.filters) {
@@ -74,19 +78,21 @@ export function cliParseLogFilters(entries) {
74
78
  return new LogFilters(entries);
75
79
  }
76
80
  /**
77
- * Dispatch output formatting by OutputFormat, with optional explicit redaction and style.
78
- * JSON ignores OutputStyle and preserves original keys and values after redaction.
81
+ * Single public render entry point: dispatch a value or Event to
82
+ * JSON/YAML/plain text by OutputFormat, with optional explicit redaction and
83
+ * style. `options.style` (PlainStyle) affects plain (logfmt) output only;
84
+ * JSON and YAML are structure-preserving and ignore it.
79
85
  *
80
86
  * @example
81
- * cliOutput(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
87
+ * render(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
82
88
  */
83
- export function cliOutput(value, format, options = {}) {
89
+ export function render(value, format, options = {}) {
84
90
  const jsonValue = value instanceof Event ? value.toJSON() : value;
85
91
  if (format === "yaml")
86
- return outputYaml(jsonValue, options);
92
+ return formatYamlValue(jsonValue, options);
87
93
  if (format === "plain")
88
- return outputPlain(jsonValue, options);
89
- return outputJson(jsonValue, options);
94
+ return formatPlainValue(jsonValue, options);
95
+ return formatJsonValue(jsonValue, options);
90
96
  }
91
97
  /** Stateful emitter for finite structured CLI executions. */
92
98
  export class CliEmitter {
@@ -121,7 +127,7 @@ export class CliEmitter {
121
127
  else {
122
128
  throw new Error(`unsupported event kind ${JSON.stringify(kind)}`);
123
129
  }
124
- this.writer(`${cliOutput(jsonValue, this.format, this.outputOptions)}\n`);
130
+ this.writer(`${render(jsonValue, this.format, this.outputOptions)}\n`);
125
131
  if (kind === "result" || kind === "error")
126
132
  this.terminalEmitted = true;
127
133
  }
@@ -141,14 +147,14 @@ export class CliEmitter {
141
147
  else {
142
148
  throw new Error(`unsupported event kind ${JSON.stringify(kind)}`);
143
149
  }
144
- this.writer(`${cliOutput(value, this.format, this.outputOptions)}\n`);
150
+ this.writer(`${render(value, this.format, this.outputOptions)}\n`);
145
151
  if (kind === "result" || kind === "error")
146
152
  this.terminalEmitted = true;
147
153
  }
148
154
  }
149
155
  /** Build a standard CLI version value. */
150
156
  export function buildCliVersion(version) {
151
- return jsonResult({ version }).build();
157
+ return jsonResult({ code: "version", version }).build();
152
158
  }
153
159
  /**
154
160
  * Render CLI version output.
@@ -156,14 +162,24 @@ export function buildCliVersion(version) {
156
162
  * conventional "<name> <version>" text.
157
163
  */
158
164
  export function cliRenderVersion(name, version, format = null) {
159
- const rendered = format === null ? `${name} ${version}` : cliOutput(buildCliVersion(version), format);
165
+ const rendered = format === null ? `${name} ${version}` : render(buildCliVersion(version), format);
160
166
  return `${rendered.replace(/\n+$/u, "")}\n`;
161
167
  }
162
168
  /**
163
169
  * Render version output if --version/-V is present; otherwise return undefined.
164
170
  * Throws for malformed version requests, for example `--version --output xml`.
171
+ *
172
+ * One blessed behavior, not configurable: a bare version request renders
173
+ * conventional "<name> <version>" text; `--json` or `--output <fmt>` /
174
+ * `--output=<fmt>` alongside it selects the structured AFDATA payload
175
+ * instead.
176
+ *
177
+ * Only a top-level version request is recognized: scanning stops at the first
178
+ * positional argument (the subcommand) or `--`, so `tool sub --version <value>`
179
+ * leaves `--version` for the subcommand's parser rather than printing the
180
+ * tool version.
165
181
  */
166
- export function cliHandleVersionOrContinue(rawArgs, name, version, defaultOutput = null, outputFlag = "--output", allowOutputFormat = true) {
182
+ export function cliHandleVersionOrContinue(rawArgs, name, version) {
167
183
  let versionRequested = false;
168
184
  let outputFormat;
169
185
  let outputError;
@@ -171,12 +187,17 @@ export function cliHandleVersionOrContinue(rawArgs, name, version, defaultOutput
171
187
  const arg = rawArgs[i];
172
188
  if (arg === "--")
173
189
  break;
190
+ // The first positional argument marks the subcommand boundary. Past it,
191
+ // --version and -V belong to the subcommand's own parser, matching
192
+ // git/cargo/clap: this pre-parser only owns a top-level version request.
193
+ if (!arg.startsWith("-"))
194
+ break;
174
195
  if (arg === "--version" || arg === "-V") {
175
196
  versionRequested = true;
176
197
  i += 1;
177
198
  continue;
178
199
  }
179
- if (allowOutputFormat && arg === "--json") {
200
+ if (arg === "--json") {
180
201
  if (outputFormat !== undefined && outputFormat !== "json") {
181
202
  outputError = new Error("conflicting output formats: --json conflicts with previous output format");
182
203
  }
@@ -186,24 +207,24 @@ export function cliHandleVersionOrContinue(rawArgs, name, version, defaultOutput
186
207
  i += 1;
187
208
  continue;
188
209
  }
189
- if (allowOutputFormat && (arg === outputFlag || arg.startsWith(`${outputFlag}=`))) {
210
+ if (arg === "--output" || arg.startsWith("--output=")) {
190
211
  let value;
191
212
  let step = 1;
192
- if (arg.startsWith(`${outputFlag}=`)) {
193
- value = arg.slice(outputFlag.length + 1);
213
+ if (arg.startsWith("--output=")) {
214
+ value = arg.slice("--output=".length);
194
215
  }
195
216
  else if (rawArgs[i + 1] !== undefined && !rawArgs[i + 1].startsWith("-")) {
196
217
  value = rawArgs[i + 1];
197
218
  step = 2;
198
219
  }
199
220
  if (value === undefined) {
200
- outputError = new Error(`missing value for ${outputFlag}: expected json, yaml, or plain`);
221
+ outputError = new Error("missing value for --output: expected json, yaml, or plain");
201
222
  }
202
223
  else {
203
224
  try {
204
225
  const parsedOutput = cliParseOutput(value);
205
226
  if (outputFormat !== undefined && outputFormat !== parsedOutput) {
206
- outputError = new Error(`conflicting output formats: ${outputFlag} ${value} conflicts with previous output format`);
227
+ outputError = new Error(`conflicting output formats: --output ${value} conflicts with previous output format`);
207
228
  }
208
229
  else {
209
230
  outputFormat = parsedOutput;
@@ -222,18 +243,23 @@ export function cliHandleVersionOrContinue(rawArgs, name, version, defaultOutput
222
243
  return undefined;
223
244
  if (outputError !== undefined)
224
245
  throw outputError;
225
- return cliRenderVersion(name, version, allowOutputFormat && outputFormat !== undefined ? outputFormat : defaultOutput);
246
+ return cliRenderVersion(name, version, outputFormat ?? null);
226
247
  }
227
248
  /**
228
- * Build a standard CLI parse error value.
249
+ * Build a standard CLI parse error value. This function cannot fail.
229
250
  * Use when flag parsing fails or a flag value is invalid.
230
- * Print with outputJson and exit with code 2.
251
+ * Print with render and exit with code 2.
252
+ *
253
+ * Always returns a strict-valid `kind:"error"` event with code `"cli_error"`.
254
+ * An empty `message` is replaced with a generic placeholder so the returned
255
+ * event stays strict-valid without throwing.
231
256
  *
232
257
  * @example
233
258
  * const err = buildCliError("--output: invalid value 'xml'");
234
- * console.log(outputJson(err));
259
+ * console.log(render(err, "json"));
235
260
  * process.exit(2);
236
261
  */
237
262
  export function buildCliError(message, hint) {
238
- return jsonError("cli_error", message).hintIfSome(hint).build();
263
+ const msg = message && message !== "" ? message : "unspecified error";
264
+ return jsonError("cli_error", msg).hintIfSome(hint).build();
239
265
  }
package/dist/format.d.ts CHANGED
@@ -2,9 +2,10 @@
2
2
  * AFDATA output formatting and protocol templates.
3
3
  *
4
4
  * Public APIs include protocol event builders, redactedValue (covers _secret
5
- * and _url fields), output formatters, redactUrlSecrets,
6
- * normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47,
7
- * RedactionPolicy, OutputStyle, and OutputOptions.
5
+ * and _url fields), redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date,
6
+ * isValidRfc3339Time, isValidRfc3339, isValidBcp47, RedactionPolicy,
7
+ * PlainStyle, and OutputOptions. The single render entry point (`render`)
8
+ * lives in cli.ts and calls this module's internal JSON/YAML/plain formatters.
8
9
  */
9
10
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
10
11
  [key: string]: JsonValue;
@@ -28,9 +29,9 @@ export declare class EventBuildError extends Error {
28
29
  export declare class JsonResultBuilder {
29
30
  private readonly payload;
30
31
  private traceValue;
31
- private issues;
32
32
  constructor(payload: JsonValue);
33
33
  trace(value: Record<string, JsonValue> | undefined): this;
34
+ /** Build the event. This builder cannot fail. */
34
35
  build(): Event;
35
36
  }
36
37
  export declare class JsonErrorBuilder {
@@ -56,17 +57,17 @@ export declare class JsonErrorBuilder {
56
57
  export declare class JsonProgressBuilder {
57
58
  private readonly payload;
58
59
  private traceValue;
59
- private issues;
60
60
  constructor(payload: JsonValue);
61
61
  trace(value: Record<string, JsonValue> | undefined): this;
62
+ /** Build the event. This builder cannot fail. */
62
63
  build(): Event;
63
64
  }
64
65
  export declare class JsonLogBuilder {
65
66
  private readonly payload;
66
67
  private traceValue;
67
- private issues;
68
68
  constructor(payload: JsonValue);
69
69
  trace(value: Record<string, JsonValue> | undefined): this;
70
+ /** Build the event. This builder cannot fail. */
70
71
  build(): Event;
71
72
  }
72
73
  /** Fluent builder for result events. */
@@ -116,11 +117,13 @@ export declare class EventDecodeError extends Error {
116
117
  * return a typed decoded event.
117
118
  */
118
119
  export declare function decodeProtocolEvent(text: string): DecodedEvent;
120
+ /** Which fields a redaction pass scrubs. The default is `All`. */
119
121
  export declare enum RedactionPolicy {
120
- RedactionTraceOnly = "RedactionTraceOnly",
121
- RedactionNone = "RedactionNone"
122
+ All = "All",
123
+ TraceOnly = "TraceOnly",
124
+ Off = "Off"
122
125
  }
123
- export declare enum OutputStyle {
126
+ export declare enum PlainStyle {
124
127
  Readable = "Readable",
125
128
  Raw = "Raw"
126
129
  }
@@ -135,17 +138,33 @@ export type OutputOptions = {
135
138
  */
136
139
  secretNames?: readonly string[];
137
140
  };
138
- /** Rendering style for YAML/plain output. Omitted means readable. */
139
- style?: OutputStyle;
141
+ /** Rendering style for plain (logfmt) output only. Omitted means readable.
142
+ * JSON and YAML are structure-preserving and ignore this setting. */
143
+ style?: PlainStyle;
140
144
  };
141
145
  /** Convenience: build OutputOptions scoped to a RedactionPolicy. */
142
146
  export declare function outputOptionsForPolicy(policy: RedactionPolicy): OutputOptions;
143
- /** Format as single-line JSON. Secrets redacted, original keys, raw values. */
144
- export declare function outputJson(value: JsonValue | Event, options?: OutputOptions): string;
145
- /** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
146
- export declare function outputYaml(value: JsonValue | Event, options?: OutputOptions): string;
147
- /** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
148
- export declare function outputPlain(value: JsonValue | Event, options?: OutputOptions): string;
147
+ /**
148
+ * Format as single-line JSON. Secrets redacted, original keys, raw values.
149
+ * Internal: called by `render` in cli.ts, which is the single public render
150
+ * entry point. Not re-exported from index.ts.
151
+ */
152
+ export declare function formatJsonValue(value: JsonValue | Event, options?: OutputOptions): string;
153
+ /**
154
+ * Format as multi-line YAML. Structure-preserving: like JSON, original keys,
155
+ * scalar types, and numeric semantics are kept after redaction; secrets are
156
+ * still redacted. `options.style` is ignored — YAML output does not vary by
157
+ * PlainStyle.
158
+ * Internal: called by `render` in cli.ts, which is the single public render
159
+ * entry point. Not re-exported from index.ts.
160
+ */
161
+ export declare function formatYamlValue(value: JsonValue | Event, options?: OutputOptions): string;
162
+ /**
163
+ * Format as single-line logfmt. Keys stripped, values formatted, secrets redacted.
164
+ * Internal: called by `render` in cli.ts, which is the single public render
165
+ * entry point. Not re-exported from index.ts.
166
+ */
167
+ export declare function formatPlainValue(value: JsonValue | Event, options?: OutputOptions): string;
149
168
  /** Return a JSON-safe copy with redaction options applied (default: full _secret redaction). */
150
169
  export declare function redactedValue(value: unknown, options?: {
151
170
  policy?: RedactionPolicy;
package/dist/format.js CHANGED
@@ -2,9 +2,10 @@
2
2
  * AFDATA output formatting and protocol templates.
3
3
  *
4
4
  * Public APIs include protocol event builders, redactedValue (covers _secret
5
- * and _url fields), output formatters, redactUrlSecrets,
6
- * normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47,
7
- * RedactionPolicy, OutputStyle, and OutputOptions.
5
+ * and _url fields), redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date,
6
+ * isValidRfc3339Time, isValidRfc3339, isValidBcp47, RedactionPolicy,
7
+ * PlainStyle, and OutputOptions. The single render entry point (`render`)
8
+ * lives in cli.ts and calls this module's internal JSON/YAML/plain formatters.
8
9
  */
9
10
  // ═══════════════════════════════════════════
10
11
  // Event type: opaque branded wrapper
@@ -46,25 +47,17 @@ export class EventBuildError extends Error {
46
47
  export class JsonResultBuilder {
47
48
  payload;
48
49
  traceValue = {};
49
- issues = [];
50
50
  constructor(payload) {
51
51
  this.payload = payload;
52
52
  }
53
53
  trace(value) {
54
54
  if (value !== undefined) {
55
- if (!isObject(value)) {
56
- this.issues.push("trace must be a JSON object");
57
- }
58
- else {
59
- this.traceValue = value;
60
- }
55
+ this.traceValue = value;
61
56
  }
62
57
  return this;
63
58
  }
59
+ /** Build the event. This builder cannot fail. */
64
60
  build() {
65
- if (this.issues.length > 0) {
66
- throw new EventBuildError(`Failed to build result event: ${this.issues.join("; ")}`, this.issues);
67
- }
68
61
  const m = { kind: "result", result: this.payload, trace: this.traceValue };
69
62
  return new Event(m);
70
63
  }
@@ -178,25 +171,17 @@ export class JsonErrorBuilder {
178
171
  export class JsonProgressBuilder {
179
172
  payload;
180
173
  traceValue = {};
181
- issues = [];
182
174
  constructor(payload) {
183
175
  this.payload = payload;
184
176
  }
185
177
  trace(value) {
186
178
  if (value !== undefined) {
187
- if (!isObject(value)) {
188
- this.issues.push("trace must be a JSON object");
189
- }
190
- else {
191
- this.traceValue = value;
192
- }
179
+ this.traceValue = value;
193
180
  }
194
181
  return this;
195
182
  }
183
+ /** Build the event. This builder cannot fail. */
196
184
  build() {
197
- if (this.issues.length > 0) {
198
- throw new EventBuildError(`Failed to build progress event: ${this.issues.join("; ")}`, this.issues);
199
- }
200
185
  const m = { kind: "progress", progress: this.payload, trace: this.traceValue };
201
186
  return new Event(m);
202
187
  }
@@ -204,25 +189,17 @@ export class JsonProgressBuilder {
204
189
  export class JsonLogBuilder {
205
190
  payload;
206
191
  traceValue = {};
207
- issues = [];
208
192
  constructor(payload) {
209
193
  this.payload = payload;
210
194
  }
211
195
  trace(value) {
212
196
  if (value !== undefined) {
213
- if (!isObject(value)) {
214
- this.issues.push("trace must be a JSON object");
215
- }
216
- else {
217
- this.traceValue = value;
218
- }
197
+ this.traceValue = value;
219
198
  }
220
199
  return this;
221
200
  }
201
+ /** Build the event. This builder cannot fail. */
222
202
  build() {
223
- if (this.issues.length > 0) {
224
- throw new EventBuildError(`Failed to build log event: ${this.issues.join("; ")}`, this.issues);
225
- }
226
203
  const m = { kind: "log", log: this.payload, trace: this.traceValue };
227
204
  return new Event(m);
228
205
  }
@@ -388,46 +365,58 @@ export function decodeProtocolEvent(text) {
388
365
  }
389
366
  }
390
367
  // ═══════════════════════════════════════════
391
- // Public API: Output Formatters
368
+ // Internal: Output Formatters (called by render() in cli.ts)
392
369
  // ═══════════════════════════════════════════
370
+ /** Which fields a redaction pass scrubs. The default is `All`. */
393
371
  export var RedactionPolicy;
394
372
  (function (RedactionPolicy) {
395
- RedactionPolicy["RedactionTraceOnly"] = "RedactionTraceOnly";
396
- RedactionPolicy["RedactionNone"] = "RedactionNone";
373
+ RedactionPolicy["All"] = "All";
374
+ RedactionPolicy["TraceOnly"] = "TraceOnly";
375
+ RedactionPolicy["Off"] = "Off";
397
376
  })(RedactionPolicy || (RedactionPolicy = {}));
398
- export var OutputStyle;
399
- (function (OutputStyle) {
400
- OutputStyle["Readable"] = "Readable";
401
- OutputStyle["Raw"] = "Raw";
402
- })(OutputStyle || (OutputStyle = {}));
377
+ export var PlainStyle;
378
+ (function (PlainStyle) {
379
+ PlainStyle["Readable"] = "Readable";
380
+ PlainStyle["Raw"] = "Raw";
381
+ })(PlainStyle || (PlainStyle = {}));
403
382
  /** Convenience: build OutputOptions scoped to a RedactionPolicy. */
404
383
  export function outputOptionsForPolicy(policy) {
405
384
  return { redaction: { policy } };
406
385
  }
407
- /** Format as single-line JSON. Secrets redacted, original keys, raw values. */
408
- export function outputJson(value, options = {}) {
386
+ /**
387
+ * Format as single-line JSON. Secrets redacted, original keys, raw values.
388
+ * Internal: called by `render` in cli.ts, which is the single public render
389
+ * entry point. Not re-exported from index.ts.
390
+ */
391
+ export function formatJsonValue(value, options = {}) {
409
392
  const unwrapped = value instanceof Event ? value.toJSON() : value;
410
393
  return JSON.stringify(redactedValue(unwrapped, options.redaction ?? {}));
411
394
  }
412
- /** Format as multi-line YAML. Keys stripped, values formatted, secrets redacted. */
413
- export function outputYaml(value, options = {}) {
395
+ /**
396
+ * Format as multi-line YAML. Structure-preserving: like JSON, original keys,
397
+ * scalar types, and numeric semantics are kept after redaction; secrets are
398
+ * still redacted. `options.style` is ignored — YAML output does not vary by
399
+ * PlainStyle.
400
+ * Internal: called by `render` in cli.ts, which is the single public render
401
+ * entry point. Not re-exported from index.ts.
402
+ */
403
+ export function formatYamlValue(value, options = {}) {
414
404
  const unwrapped = value instanceof Event ? value.toJSON() : value;
415
405
  const redacted = redactedValue(unwrapped, options.redaction ?? {});
416
406
  const lines = ["---"];
417
- if (options.style === OutputStyle.Raw) {
418
- renderYamlRaw(redacted, 0, lines);
419
- }
420
- else {
421
- renderYamlProcessed(redacted, 0, lines);
422
- }
407
+ renderYamlRaw(redacted, 0, lines);
423
408
  return lines.join("\n");
424
409
  }
425
- /** Format as single-line logfmt. Keys stripped, values formatted, secrets redacted. */
426
- export function outputPlain(value, options = {}) {
410
+ /**
411
+ * Format as single-line logfmt. Keys stripped, values formatted, secrets redacted.
412
+ * Internal: called by `render` in cli.ts, which is the single public render
413
+ * entry point. Not re-exported from index.ts.
414
+ */
415
+ export function formatPlainValue(value, options = {}) {
427
416
  const unwrapped = value instanceof Event ? value.toJSON() : value;
428
417
  const redacted = redactedValue(unwrapped, options.redaction ?? {});
429
418
  const pairs = [];
430
- if (options.style === OutputStyle.Raw) {
419
+ if (options.style === PlainStyle.Raw) {
431
420
  collectPlainPairsRaw(redacted, "", pairs);
432
421
  }
433
422
  else {
@@ -668,15 +657,15 @@ function applyRedactionOptions(value, redactionOptions) {
668
657
  }
669
658
  function applyRedactionPolicyWithContext(value, redactionPolicy, context) {
670
659
  switch (redactionPolicy) {
671
- case RedactionPolicy.RedactionTraceOnly:
660
+ case RedactionPolicy.TraceOnly:
672
661
  if (isObject(value) && value.trace !== undefined) {
673
662
  redactSecrets(value.trace, context);
674
663
  }
675
664
  break;
676
- case RedactionPolicy.RedactionNone:
665
+ case RedactionPolicy.Off:
677
666
  break;
678
667
  default:
679
- // Empty/unknown policy falls back to default full redaction.
668
+ // undefined or RedactionPolicy.All = full redaction (default).
680
669
  redactSecrets(value, context);
681
670
  break;
682
671
  }
@@ -1184,47 +1173,6 @@ function extractCurrencyCodeFromStem(stem) {
1184
1173
  // ═══════════════════════════════════════════
1185
1174
  // YAML Rendering
1186
1175
  // ═══════════════════════════════════════════
1187
- function renderYamlProcessed(value, indent, lines) {
1188
- const prefix = " ".repeat(indent);
1189
- if (!isObject(value)) {
1190
- lines.push(`${prefix}${yamlScalar(value)}`);
1191
- return;
1192
- }
1193
- for (const pf of processObjectFields(value)) {
1194
- if (pf.formatted !== null) {
1195
- lines.push(`${prefix}${yamlKey(pf.key)}: "${escapeYamlStr(pf.formatted)}"`);
1196
- }
1197
- else if (isObject(pf.value)) {
1198
- if (Object.keys(pf.value).length > 0) {
1199
- lines.push(`${prefix}${yamlKey(pf.key)}:`);
1200
- renderYamlProcessed(pf.value, indent + 1, lines);
1201
- }
1202
- else {
1203
- lines.push(`${prefix}${yamlKey(pf.key)}: {}`);
1204
- }
1205
- }
1206
- else if (Array.isArray(pf.value)) {
1207
- if (pf.value.length === 0) {
1208
- lines.push(`${prefix}${yamlKey(pf.key)}: []`);
1209
- }
1210
- else {
1211
- lines.push(`${prefix}${yamlKey(pf.key)}:`);
1212
- for (const item of pf.value) {
1213
- if (isObject(item)) {
1214
- lines.push(`${prefix} -`);
1215
- renderYamlProcessed(item, indent + 2, lines);
1216
- }
1217
- else {
1218
- lines.push(`${prefix} - ${yamlScalar(item)}`);
1219
- }
1220
- }
1221
- }
1222
- }
1223
- else {
1224
- lines.push(`${prefix}${yamlKey(pf.key)}: ${yamlScalar(pf.value)}`);
1225
- }
1226
- }
1227
- }
1228
1176
  function renderYamlRaw(value, indent, lines) {
1229
1177
  const prefix = " ".repeat(indent);
1230
1178
  if (isObject(value)) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
3
3
  */
4
- export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, type LogLevel, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, type DecodedEvent, type DecodedResult, type DecodedError, type DecodedProgress, type DecodedLog, EventDecodeError, type JsonValue, RedactionPolicy, OutputStyle, type OutputOptions, outputOptionsForPolicy, outputJson, outputYaml, outputPlain, redactedValue, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
- export { type OutputFormat, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters, cliOutput, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
4
+ export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, type LogLevel, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, type DecodedEvent, type DecodedResult, type DecodedError, type DecodedProgress, type DecodedLog, EventDecodeError, type JsonValue, RedactionPolicy, PlainStyle, type OutputOptions, outputOptionsForPolicy, redactedValue, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
+ export { type OutputFormat, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates.
3
3
  */
4
- export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy, OutputStyle, outputOptionsForPolicy, outputJson, outputYaml, outputPlain, redactedValue, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
- export { CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters, cliOutput, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
4
+ export { jsonResult, jsonError, jsonProgress, jsonLog, Event, EventBuildError, JsonResultBuilder, JsonErrorBuilder, JsonProgressBuilder, JsonLogBuilder, validateProtocolEvent, validateProtocolStream, decodeProtocolEvent, EventDecodeError, RedactionPolicy, PlainStyle, outputOptionsForPolicy, redactedValue, redactUrlSecrets, normalizeUtcOffset, isValidRfc3339Date, isValidRfc3339Time, isValidRfc3339, isValidBcp47, } from "./format.js";
5
+ export { CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters, render, buildCliError, buildCliVersion, cliRenderVersion, cliHandleVersionOrContinue, } from "./cli.js";
package/dist/skill.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * supported coding agents (Codex, Claude Code, opencode, Hermes).
7
7
  *
8
8
  * The function performs the filesystem work and returns the protocol value (rendered by
9
- * the caller with `cliOutput`) or throws a {@link SkillError}. It never writes to
9
+ * the caller with `render`) or throws a {@link SkillError}. It never writes to
10
10
  * stdout/stderr itself.
11
11
  */
12
12
  /**
@@ -67,7 +67,7 @@ export interface SkillUninstallStatus {
67
67
  }
68
68
  /**
69
69
  * The result of a skill action. Read fields off the discriminated union (narrow on
70
- * `code`), or pass it to `cliOutput` to serialize — every field is already JSON-shaped.
70
+ * `code`), or pass it to `render` to serialize — every field is already JSON-shaped.
71
71
  */
72
72
  export type SkillReport = {
73
73
  code: "skill_status";
@@ -90,7 +90,7 @@ export type SkillReport = {
90
90
  };
91
91
  /**
92
92
  * Install, uninstall, or report status of `spec`'s skill across the selected agent
93
- * target(s). Returns the protocol value (caller renders with `cliOutput`) or throws a
93
+ * target(s). Returns the protocol value (caller renders with `render`) or throws a
94
94
  * {@link SkillError}. Does not touch stdout/stderr.
95
95
  */
96
96
  export declare function runSkillAdmin(spec: SkillSpec, action: SkillAction, options: SkillOptions): SkillReport;
package/dist/skill.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * supported coding agents (Codex, Claude Code, opencode, Hermes).
7
7
  *
8
8
  * The function performs the filesystem work and returns the protocol value (rendered by
9
- * the caller with `cliOutput`) or throws a {@link SkillError}. It never writes to
9
+ * the caller with `render`) or throws a {@link SkillError}. It never writes to
10
10
  * stdout/stderr itself.
11
11
  */
12
12
  import * as fs from "node:fs";
@@ -26,7 +26,7 @@ export class SkillError extends Error {
26
26
  }
27
27
  /**
28
28
  * Install, uninstall, or report status of `spec`'s skill across the selected agent
29
- * target(s). Returns the protocol value (caller renders with `cliOutput`) or throws a
29
+ * target(s). Returns the protocol value (caller renders with `render`) or throws a
30
30
  * {@link SkillError}. Does not touch stdout/stderr.
31
31
  */
32
32
  export function runSkillAdmin(spec, action, options) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-first-data",
3
- "version": "0.17.3",
4
- "description": "A naming convention that lets AI agents understand your data without being told what it means.",
3
+ "version": "0.18.0",
4
+ "description": "A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",