agent-first-data 0.17.2 → 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 +5 -5
- package/assets/skills/agent-first-data/SKILL.md +123 -8
- package/assets/skills/agent-first-data/references/rules.md +20 -14
- package/dist/cli.d.ts +33 -13
- package/dist/cli.js +56 -30
- package/dist/format.d.ts +36 -17
- package/dist/format.js +46 -98
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/skill.d.ts +3 -3
- package/dist/skill.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ npm install agent-first-data
|
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
import { jsonResult,
|
|
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(
|
|
17
|
-
console.log(
|
|
16
|
+
console.log(render(event.toJSON(), "json"));
|
|
17
|
+
console.log(render(event.toJSON(), "plain"));
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Useful names use TypeScript casing: `
|
|
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
|
|
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 Agent-First Data naming, redaction, formatting, protocol
|
|
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.
|
|
@@ -11,12 +11,66 @@ description: Apply Agent-First Data naming, redaction, formatting, protocol enve
|
|
|
11
11
|
|
|
12
12
|
Use AFDATA when naming fields or reviewing structured output. The contract is naming-based: suffixes communicate units, formatting, and redaction semantics without relying on free-text inference.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Specification workflow
|
|
15
|
+
|
|
16
|
+
Treat the formal specification as normative, not the skill summary or an
|
|
17
|
+
example payload. In a repository checkout, read these spore-root-relative
|
|
18
|
+
sources as needed:
|
|
19
|
+
|
|
20
|
+
- `spec/agent-first-data.md` — complete cross-language naming, formatting,
|
|
21
|
+
redaction, protocol, logging, and CLI contract.
|
|
22
|
+
- `spec/registry.json` — exact suffix metadata.
|
|
23
|
+
- `spec/protocol-v1.schema.json` — protocol event schema.
|
|
24
|
+
- `spec/fixtures/` — cross-language conformance cases.
|
|
25
|
+
|
|
26
|
+
When only the installed skill is available, use the bundled equivalents:
|
|
15
27
|
|
|
16
28
|
- `references/rules.md` — detailed suffix rules, output formatting, protocol templates, logging, CLI flags, and review checklist.
|
|
17
29
|
- `references/registry.json` — machine-readable suffix registry; use this as the exact source for suffix metadata.
|
|
18
30
|
- `references/protocol-v1.schema.json` — protocol event schema for `kind:"result"`, `kind:"error"`, `kind:"progress"`, and `kind:"log"` envelopes.
|
|
19
31
|
|
|
32
|
+
If an example or README conflicts with the formal spec, follow the spec and
|
|
33
|
+
report the discrepancy. Do not invent suffix meanings or reduce the protocol
|
|
34
|
+
schema.
|
|
35
|
+
|
|
36
|
+
## Library workflow
|
|
37
|
+
|
|
38
|
+
Use the library at serialization boundaries so redaction and formatting are
|
|
39
|
+
not reimplemented ad hoc. Rust applications can build a typed protocol event
|
|
40
|
+
and render it in the required output format:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cargo add agent-first-data --no-default-features
|
|
44
|
+
pip install agent-first-data
|
|
45
|
+
npm install agent-first-data
|
|
46
|
+
go get github.com/agentfirstkit/agent-first-data/go
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```rust
|
|
50
|
+
use agent_first_data::{json_result, render, OutputFormat, OutputOptions};
|
|
51
|
+
use serde_json::json;
|
|
52
|
+
|
|
53
|
+
let event = json_result(json!({
|
|
54
|
+
"latency_ms": 1280,
|
|
55
|
+
"api_key_secret": "sk-live-example",
|
|
56
|
+
})).build();
|
|
57
|
+
let rendered = render(event.as_value(), OutputFormat::Json, &OutputOptions::default());
|
|
58
|
+
assert!(rendered.contains("\"api_key_secret\":\"***\""));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For a finite CLI execution implemented in Rust, create one `CliEmitter`, select
|
|
62
|
+
`OutputFormat` through `cli_parse_output`, enable strict protocol, and emit one
|
|
63
|
+
terminal result or error. Handle version before clap with
|
|
64
|
+
`cli_handle_version_or_continue(raw_args, name, version)`.
|
|
65
|
+
For non-CLI HTTP/MCP/SSE serialization, call `redacted_value()` or
|
|
66
|
+
`redact_url_secrets()` explicitly at the boundary.
|
|
67
|
+
|
|
68
|
+
The same library contract is available for Go, Python, and TypeScript. Use the
|
|
69
|
+
runtime's native names while preserving the same builders, output formats,
|
|
70
|
+
redaction behavior, registry, schema, and conformance fixtures; consult its
|
|
71
|
+
root-level `go/README.md`, `python/README.md`, or `typescript/README.md` when
|
|
72
|
+
working in that runtime.
|
|
73
|
+
|
|
20
74
|
## Naming and redaction decisions
|
|
21
75
|
|
|
22
76
|
- Add unit suffixes to ambiguous numeric fields: `_ms`, `_s`, `_bytes`, `_percent`, `_sats`, `_msats`, currency suffixes, and timestamp suffixes.
|
|
@@ -28,32 +82,93 @@ Use AFDATA when naming fields or reviewing structured output. The contract is na
|
|
|
28
82
|
## Evaluation rules
|
|
29
83
|
|
|
30
84
|
- Trigger this skill for field naming, structured output, configs, logs, CLI output, protocol events, MCP/HTTP/SSE payloads, database columns, wire fields, or persisted JSON. Do not wait for the user to say "AFDATA" when the work changes those surfaces.
|
|
31
|
-
-
|
|
85
|
+
- In a repository checkout, treat `spec/agent-first-data.md`,
|
|
86
|
+
`spec/registry.json`, and `spec/protocol-v1.schema.json` as authoritative; in
|
|
87
|
+
an installed skill, use their bundled `references/` equivalents. Do not
|
|
88
|
+
replace them with invented suffix meanings, a reduced schema, or free-text
|
|
89
|
+
interpretation.
|
|
32
90
|
- For secrets, fix the field name or serializer configuration. Use `_secret` or configured exact secret names; do not rely on scanning arbitrary strings or object debug output.
|
|
33
91
|
- Run `afdata lint` on JSON/JSONL examples, JSON Schema, MCP schemas, or serialized samples when the CLI is available. Run `afdata validate` on finite protocol events/streams. If a check cannot run, report that explicitly.
|
|
34
92
|
- Before modifying an external project's public API, wire format, database schema, or persisted field names, stop and report the compatibility impact; request approval before applying the change.
|
|
35
93
|
|
|
36
94
|
## Redaction behavior contract
|
|
37
95
|
|
|
38
|
-
Fields named `_secret` or `_url` and values passing through AFDATA output
|
|
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.
|
|
39
97
|
|
|
40
98
|
## Logging behavior contract
|
|
41
99
|
|
|
42
|
-
One-time CLI output uses `json_log()` or `CliEmitter` for a single event; serialization via `
|
|
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.
|
|
43
101
|
|
|
44
102
|
## CLI workflow
|
|
45
103
|
|
|
46
|
-
|
|
104
|
+
Install `afdata` when a shell workflow needs validation, formatting, or skill
|
|
105
|
+
administration without embedding a library:
|
|
47
106
|
|
|
48
107
|
```bash
|
|
49
|
-
|
|
50
|
-
|
|
108
|
+
cargo install agent-first-data
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use it for deterministic checks:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
afdata lint payload.json
|
|
115
|
+
afdata validate events.jsonl
|
|
116
|
+
afdata render payload.json --output yaml
|
|
117
|
+
afdata skill validate skills/example-skill
|
|
51
118
|
```
|
|
52
119
|
|
|
53
120
|
- Run `afdata lint` for ordinary JSON/JSONL, JSON Schema, or MCP input/output schemas.
|
|
54
121
|
- Run `afdata validate` for AFDATA protocol events or finite event streams.
|
|
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.
|
|
55
124
|
- Treat findings as contract issues unless the owning tool intentionally documents a non-AFDATA field.
|
|
56
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
|
+
|
|
57
172
|
## Review checklist
|
|
58
173
|
|
|
59
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
|
|
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 `
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
-
|
|
273
|
-
- Use `redacted_value()` for raw HTTP/MCP/SSE serialization paths that bypass `
|
|
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()`, `
|
|
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.
|
|
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()` + `
|
|
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 (
|
|
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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
33
|
-
*
|
|
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
|
-
*
|
|
52
|
-
* JSON
|
|
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
|
-
*
|
|
61
|
+
* render(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
|
|
56
62
|
*/
|
|
57
|
-
export declare function
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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
|
-
*
|
|
37
|
-
*
|
|
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
|
|
45
|
-
*
|
|
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")
|
|
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
|
-
*
|
|
78
|
-
* JSON
|
|
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
|
-
*
|
|
87
|
+
* render(jsonResult({ ok: true }).build(), "plain") // → "kind=result result.ok=true"
|
|
82
88
|
*/
|
|
83
|
-
export function
|
|
89
|
+
export function render(value, format, options = {}) {
|
|
84
90
|
const jsonValue = value instanceof Event ? value.toJSON() : value;
|
|
85
91
|
if (format === "yaml")
|
|
86
|
-
return
|
|
92
|
+
return formatYamlValue(jsonValue, options);
|
|
87
93
|
if (format === "plain")
|
|
88
|
-
return
|
|
89
|
-
return
|
|
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(`${
|
|
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(`${
|
|
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}` :
|
|
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
|
|
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 (
|
|
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 (
|
|
210
|
+
if (arg === "--output" || arg.startsWith("--output=")) {
|
|
190
211
|
let value;
|
|
191
212
|
let step = 1;
|
|
192
|
-
if (arg.startsWith(
|
|
193
|
-
value = arg.slice(
|
|
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(
|
|
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:
|
|
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,
|
|
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
|
|
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(
|
|
259
|
+
* console.log(render(err, "json"));
|
|
235
260
|
* process.exit(2);
|
|
236
261
|
*/
|
|
237
262
|
export function buildCliError(message, hint) {
|
|
238
|
-
|
|
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),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
121
|
-
|
|
122
|
+
All = "All",
|
|
123
|
+
TraceOnly = "TraceOnly",
|
|
124
|
+
Off = "Off"
|
|
122
125
|
}
|
|
123
|
-
export declare enum
|
|
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
|
|
139
|
-
|
|
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
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
export declare function
|
|
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),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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["
|
|
396
|
-
RedactionPolicy["
|
|
373
|
+
RedactionPolicy["All"] = "All";
|
|
374
|
+
RedactionPolicy["TraceOnly"] = "TraceOnly";
|
|
375
|
+
RedactionPolicy["Off"] = "Off";
|
|
397
376
|
})(RedactionPolicy || (RedactionPolicy = {}));
|
|
398
|
-
export var
|
|
399
|
-
(function (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
})(
|
|
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
|
-
/**
|
|
408
|
-
|
|
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
|
-
/**
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
426
|
-
|
|
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 ===
|
|
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.
|
|
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.
|
|
665
|
+
case RedactionPolicy.Off:
|
|
677
666
|
break;
|
|
678
667
|
default:
|
|
679
|
-
//
|
|
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,
|
|
5
|
-
export { type OutputFormat, type CliEventWriter, CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters,
|
|
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,
|
|
5
|
-
export { CliEmitter, LogFilters, cliParseOutput, cliParseLogFilters,
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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.
|
|
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",
|