agent-first-data 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -557
- package/dist/afdata_logging.d.ts +7 -3
- package/dist/afdata_logging.js +16 -14
- package/dist/cli.js +0 -3
- package/dist/format.d.ts +12 -7
- package/dist/format.js +149 -81
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/skill.js +155 -43
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,578 +1,46 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
**Agent-First Data (AFDATA)** — Suffix-driven output formatting and protocol templates for AI agents.
|
|
4
|
-
|
|
5
|
-
The field name is the schema. Agents read `latency_ms` and know milliseconds, `api_key_secret` and know to redact, no external schema needed.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
1
|
+
# Agent-First Data for TypeScript
|
|
8
2
|
|
|
9
3
|
```bash
|
|
10
4
|
npm install agent-first-data
|
|
11
5
|
```
|
|
12
6
|
|
|
13
|
-
## Quick Example
|
|
14
|
-
|
|
15
|
-
A backup tool invoked from the CLI — flags, env vars, and config all use the same suffixes:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
API_KEY_SECRET=sk-1234 cloudback --timeout-s 30 --max-file-size-bytes 10737418240 /data/backup.tar.gz
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
For CLI diagnostics, enable log categories explicitly:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
--log startup,request,progress,retry,redirect
|
|
25
|
-
--verbose # shorthand for all categories
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Without these flags, startup diagnostics should stay off by default.
|
|
29
|
-
|
|
30
|
-
The tool reads env vars, flags, and config — all with AFDATA suffixes — and can emit a startup diagnostic event:
|
|
31
|
-
|
|
32
7
|
```typescript
|
|
33
|
-
import {
|
|
8
|
+
import { outputJson, outputPlain } from "agent-first-data";
|
|
34
9
|
|
|
35
|
-
const
|
|
36
|
-
"
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
env: { API_KEY_SECRET: process.env.API_KEY_SECRET ?? null },
|
|
10
|
+
const value = {
|
|
11
|
+
code: "ok",
|
|
12
|
+
result: {
|
|
13
|
+
api_key_secret: "sk-123",
|
|
14
|
+
latency_ms: 1280,
|
|
15
|
+
db_url: "postgres://user:p@ss@db/app?token_secret=abc",
|
|
42
16
|
},
|
|
43
|
-
);
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Three output formats, same data:
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
JSON: {"code":"log","event":"startup","args":{"input_path":"/data/backup.tar.gz"},"config":{"max_file_size_bytes":10737418240,"timeout_s":30},"env":{"API_KEY_SECRET":"***"}}
|
|
50
|
-
YAML: code: "log"
|
|
51
|
-
event: "startup"
|
|
52
|
-
args:
|
|
53
|
-
input_path: "/data/backup.tar.gz"
|
|
54
|
-
config:
|
|
55
|
-
max_file_size: "10.0GB"
|
|
56
|
-
timeout: "30s"
|
|
57
|
-
env:
|
|
58
|
-
API_KEY: "***"
|
|
59
|
-
Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_file_size=10.0GB config.timeout=30s env.API_KEY=***
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
`--timeout-s` → `timeout_s` → `timeout: 30s`. `API_KEY_SECRET` → `API_KEY: "***"`. The suffix is the schema.
|
|
63
|
-
|
|
64
|
-
## API Reference
|
|
65
|
-
|
|
66
|
-
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
67
|
-
|
|
68
|
-
### Protocol Builders (returns JsonValue)
|
|
69
|
-
|
|
70
|
-
Build AFDATA protocol structures. Return JSON-serializable objects for transport payloads.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
74
|
-
|
|
75
|
-
// Success (result)
|
|
76
|
-
buildJsonOk(result: JsonValue, trace?: JsonValue): JsonValue
|
|
77
|
-
|
|
78
|
-
// Error (simple message, optional hint)
|
|
79
|
-
buildJsonError(message: string, hint?: string, trace?: JsonValue): JsonValue
|
|
80
|
-
|
|
81
|
-
// Generic (any code + fields)
|
|
82
|
-
buildJson(code: string, fields: JsonValue, trace?: JsonValue): JsonValue
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Redaction Helpers (returns JsonValue)
|
|
86
|
-
|
|
87
|
-
Use these before raw HTTP/MCP/SSE serializers that do not call `outputJson`.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
redactedValue(value: unknown): JsonValue
|
|
91
|
-
redactedValueWith(value: unknown, redactionPolicy: RedactionPolicy): JsonValue
|
|
92
|
-
redactedValueWithOptions(value: unknown, redactionOptions: RedactionOptions): JsonValue
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
`_url` fields are scrubbed in place during redaction (userinfo password, plus query parameters whose name follows the `_secret`/`secretNames` rule). For a URL inside a free-form string, redact it directly before interpolating:
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
redactUrlSecrets(url: string): string
|
|
99
|
-
redactUrlSecretsWithOptions(url: string, redactionOptions: RedactionOptions): string
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
103
|
-
|
|
104
|
-
**Example:**
|
|
105
|
-
```typescript
|
|
106
|
-
import { buildJsonOk, buildJsonError, buildJson } from "agent-first-data";
|
|
107
|
-
|
|
108
|
-
// Startup
|
|
109
|
-
const startup = buildJson(
|
|
110
|
-
"log",
|
|
111
|
-
{
|
|
112
|
-
event: "startup",
|
|
113
|
-
config: { api_key_secret: "sk-123", timeout_s: 30 },
|
|
114
|
-
args: { config_path: "config.yml" },
|
|
115
|
-
env: { RUST_LOG: "info" },
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// Success (always include trace)
|
|
120
|
-
const response = buildJsonOk(
|
|
121
|
-
{ user_id: 123 },
|
|
122
|
-
{ duration_ms: 150, source: "db" },
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Error
|
|
126
|
-
const error = buildJsonError("user not found", undefined, { duration_ms: 5 });
|
|
127
|
-
|
|
128
|
-
// Error with hint
|
|
129
|
-
const errorHint = buildJsonError("wallet not found", "list wallets with: afpay wallet list", { duration_ms: 5 });
|
|
130
|
-
|
|
131
|
-
// Specific error code
|
|
132
|
-
const notFound = buildJson(
|
|
133
|
-
"not_found",
|
|
134
|
-
{ resource: "user", id: 123 },
|
|
135
|
-
{ duration_ms: 8 },
|
|
136
|
-
);
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Output Formatters (returns string)
|
|
140
|
-
|
|
141
|
-
Format values for CLI output and logs. `outputJson` uses full `_secret` redaction by default. `outputJsonWith` supports explicit scoped policies. Use `OutputOptions` to pass legacy secret names such as `api_key` or request schema-preserving YAML/plain rendering with `OutputStyle.Raw`.
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
outputJson(value: JsonValue): string // Single-line JSON, original keys, for programs/logs
|
|
145
|
-
outputJsonWith(value: JsonValue, redactionPolicy: RedactionPolicy): string
|
|
146
|
-
outputJsonWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
147
|
-
outputYaml(value: JsonValue): string // Multi-line YAML, keys stripped, values formatted
|
|
148
|
-
outputYamlWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
149
|
-
outputPlain(value: JsonValue): string // Single-line logfmt, keys stripped, values formatted
|
|
150
|
-
outputPlainWithOptions(value: JsonValue, outputOptions: OutputOptions): string
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
enum RedactionPolicy {
|
|
155
|
-
RedactionTraceOnly = "RedactionTraceOnly",
|
|
156
|
-
RedactionNone = "RedactionNone",
|
|
157
|
-
RedactionStrict = "RedactionStrict",
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
type RedactionOptions = {
|
|
161
|
-
policy?: RedactionPolicy;
|
|
162
|
-
secretNames?: readonly string[]; // legacy names like "api_key" or "authorization"
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
enum OutputStyle {
|
|
166
|
-
Readable = "Readable",
|
|
167
|
-
Raw = "Raw",
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
type OutputOptions = {
|
|
171
|
-
redaction?: RedactionOptions;
|
|
172
|
-
style?: OutputStyle;
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. `OutputOptions` combines `RedactionOptions` with `OutputStyle.Readable` (default suffix stripping and value formatting) or `OutputStyle.Raw` (no suffix stripping or value formatting).
|
|
177
|
-
|
|
178
|
-
**Example:**
|
|
179
|
-
```typescript
|
|
180
|
-
import { outputJson, outputYaml, outputPlain } from "agent-first-data";
|
|
181
|
-
|
|
182
|
-
const data = {
|
|
183
|
-
user_id: 123,
|
|
184
|
-
api_key_secret: "sk-1234567890abcdef",
|
|
185
|
-
created_at_epoch_ms: 1738886400000,
|
|
186
|
-
file_size_bytes: 5242880,
|
|
187
17
|
};
|
|
188
18
|
|
|
189
|
-
|
|
190
|
-
console.log(
|
|
191
|
-
// {"api_key_secret":"***","created_at_epoch_ms":1738886400000,"file_size_bytes":5242880,"user_id":123}
|
|
192
|
-
|
|
193
|
-
// YAML (keys stripped, values formatted, secrets redacted)
|
|
194
|
-
console.log(outputYaml(data));
|
|
195
|
-
// ---
|
|
196
|
-
// api_key: "***"
|
|
197
|
-
// created_at: "2025-02-07T00:00:00.000Z"
|
|
198
|
-
// file_size: "5.0MB"
|
|
199
|
-
// user_id: 123
|
|
200
|
-
|
|
201
|
-
// Plain logfmt (keys stripped, values formatted, secrets redacted)
|
|
202
|
-
console.log(outputPlain(data));
|
|
203
|
-
// api_key=*** created_at=2025-02-07T00:00:00.000Z file_size=5.0MB user_id=123
|
|
19
|
+
console.log(outputJson(value));
|
|
20
|
+
console.log(outputPlain(value));
|
|
204
21
|
```
|
|
205
22
|
|
|
206
|
-
|
|
23
|
+
Useful names use TypeScript casing: `outputJson`, `outputYaml`, `outputPlain`, `outputJsonWithOptions`, `redactedValue`, `redactSecretsInPlace`, `redactUrlSecrets`, `parseSize`, `normalizeUtcOffset`, `cliParseOutput`, `cliOutput`, and `buildCliError`.
|
|
207
24
|
|
|
208
|
-
|
|
209
|
-
internalRedactSecrets(value: JsonValue): void // Manually redact secrets in-place
|
|
210
|
-
internalRedactSecretsWithOptions(value: JsonValue, redactionOptions: RedactionOptions): void
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Most users don't need this. Output functions automatically protect secrets.
|
|
214
|
-
|
|
215
|
-
### Utility Functions
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
parseSize(s: string): number | null // Parse "10M" → bytes
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Returns `null` for invalid, negative, overflow, or non-safe-integer values.
|
|
222
|
-
|
|
223
|
-
**Example:**
|
|
224
|
-
```typescript
|
|
225
|
-
import { parseSize } from "agent-first-data";
|
|
226
|
-
|
|
227
|
-
parseSize("10M"); // 10485760
|
|
228
|
-
parseSize("1.5K"); // 1536
|
|
229
|
-
parseSize("512"); // 512
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### CLI Helpers (for tools built on AFDATA)
|
|
233
|
-
|
|
234
|
-
Shared helpers that prevent flag-parsing drift between CLI tools. Use these instead of reimplementing `--output` and `--log` handling in each tool.
|
|
235
|
-
|
|
236
|
-
Help scope and format are orthogonal: `--recursive` decides one-level vs
|
|
237
|
-
recursive, `--output` decides plain/json/yaml/markdown. Human-facing CLIs use
|
|
238
|
-
one-level `--help` (and scoped `sub --help`); agent/doc flows use
|
|
239
|
-
`--help --recursive`, adding `--output json|yaml|markdown` for a recursive
|
|
240
|
-
export. A bare `--recursive` without `--help` is ignored by the help layer.
|
|
241
|
-
See `examples/agent_cli.ts` for that pattern.
|
|
25
|
+
Logging is available through `initJson`, `initPlain`, `initYaml`, `span`, and `log`.
|
|
242
26
|
|
|
243
27
|
```typescript
|
|
244
|
-
|
|
28
|
+
import { initJson } from "agent-first-data";
|
|
245
29
|
|
|
246
|
-
|
|
247
|
-
cliParseLogFilters(entries: string[]): string[] // Normalize --log: trim, lowercase, dedup, remove empty
|
|
248
|
-
cliOutput(value: JsonValue, format: OutputFormat): string // Dispatch to outputJson/Yaml/Plain
|
|
249
|
-
cliOutputWithOptions(value: JsonValue, format: OutputFormat, outputOptions: OutputOptions): string
|
|
250
|
-
buildCliError(message: string, hint?: string): JsonValue // {code:"error", error_code:"invalid_request", hint?, retryable:false, trace:{duration_ms:0}}
|
|
30
|
+
initJson({ secretNames: ["authorization"] });
|
|
251
31
|
```
|
|
252
32
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
import {
|
|
257
|
-
type OutputFormat, cliParseOutput, cliParseLogFilters,
|
|
258
|
-
cliOutput, buildCliError, outputJson,
|
|
259
|
-
} from "agent-first-data";
|
|
260
|
-
|
|
261
|
-
let fmt: OutputFormat;
|
|
262
|
-
try {
|
|
263
|
-
fmt = cliParseOutput(outputArg);
|
|
264
|
-
} catch (e) {
|
|
265
|
-
console.log(outputJson(buildCliError((e as Error).message)));
|
|
266
|
-
process.exit(2);
|
|
267
|
-
}
|
|
33
|
+
## Behavior Notes
|
|
268
34
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
See `examples/agent_cli.ts` for the complete working example (`npx tsx --test examples/agent_cli.ts`).
|
|
275
|
-
|
|
276
|
-
### Skill Admin (for spore CLIs that ship an Agent Skill)
|
|
277
|
-
|
|
278
|
-
`runSkillAdmin` installs, uninstalls, and reports status of a spore's embedded `SKILL.md` across Codex, Claude Code, and opencode. Describe the skill once with a `SkillSpec`, then call it per action. It returns a typed `SkillReport` discriminated union (narrow on `code` to read fields, or serialize with `cliOutput`) or throws `SkillError`; it never writes to stdout/stderr. The generated `SKILL.md` is byte-identical to the Rust, Go, and Python ports.
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
runSkillAdmin(spec: SkillSpec, action: SkillAction, options: SkillOptions): SkillReport
|
|
282
|
-
|
|
283
|
-
const spec: SkillSpec = { name: "agent-first-widget", source: skillMarkdown, title: "Agent-First Widget", markerSlug: "afwidget" };
|
|
284
|
-
const options: SkillOptions = { agent: "all", scope: "personal" };
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
const report = runSkillAdmin(spec, "install", options);
|
|
288
|
-
console.log(cliOutput(report as unknown as JsonValue, fmt));
|
|
289
|
-
} catch (e) {
|
|
290
|
-
if (e instanceof SkillError) {
|
|
291
|
-
console.log(cliOutput(buildCliError(e.message, e.hint), fmt));
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
throw e;
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
`status` reports `installed` / `valid` / `managed` / `current` per target; `current` is true only when the installed content matches the bundle, so re-running `install` refreshes a stale copy. See `examples/agent_cli.ts` for the `skill` subcommand wiring.
|
|
299
|
-
|
|
300
|
-
## Usage Examples
|
|
301
|
-
|
|
302
|
-
### Example 1: REST API
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
import { buildJsonOk } from "agent-first-data";
|
|
306
|
-
import express from "express";
|
|
307
|
-
|
|
308
|
-
const app = express();
|
|
309
|
-
|
|
310
|
-
app.get("/users/:id", (req, res) => {
|
|
311
|
-
const response = buildJsonOk(
|
|
312
|
-
{ user_id: Number(req.params.id), name: "alice" },
|
|
313
|
-
{ duration_ms: 150, source: "db" },
|
|
314
|
-
);
|
|
315
|
-
// API returns raw JSON — no output processing, no key stripping
|
|
316
|
-
res.json(response);
|
|
317
|
-
});
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Example 2: CLI Tool (Complete Lifecycle)
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
import { buildJsonOk, buildJson, outputYaml, outputPlain } from "agent-first-data";
|
|
324
|
-
|
|
325
|
-
// 1. Startup
|
|
326
|
-
const startup = buildJson(
|
|
327
|
-
"log",
|
|
328
|
-
{
|
|
329
|
-
event: "startup",
|
|
330
|
-
config: { api_key_secret: "sk-sensitive-key", timeout_s: 30 },
|
|
331
|
-
args: { input_path: "data.json" },
|
|
332
|
-
env: { RUST_LOG: "info" },
|
|
333
|
-
},
|
|
334
|
-
);
|
|
335
|
-
console.log(outputYaml(startup));
|
|
336
|
-
// ---
|
|
337
|
-
// code: "log"
|
|
338
|
-
// event: "startup"
|
|
339
|
-
// args:
|
|
340
|
-
// input_path: "data.json"
|
|
341
|
-
// config:
|
|
342
|
-
// api_key: "***"
|
|
343
|
-
// timeout: "30s"
|
|
344
|
-
// env:
|
|
345
|
-
// RUST_LOG: "info"
|
|
346
|
-
|
|
347
|
-
// 2. Progress
|
|
348
|
-
const progress = buildJson(
|
|
349
|
-
"progress",
|
|
350
|
-
{ current: 3, total: 10, message: "processing" },
|
|
351
|
-
{ duration_ms: 1500 },
|
|
352
|
-
);
|
|
353
|
-
console.log(outputPlain(progress));
|
|
354
|
-
// code=progress current=3 message=processing total=10 trace.duration=1.5s
|
|
355
|
-
|
|
356
|
-
// 3. Result
|
|
357
|
-
const result = buildJsonOk(
|
|
358
|
-
{
|
|
359
|
-
records_processed: 10,
|
|
360
|
-
file_size_bytes: 5242880,
|
|
361
|
-
created_at_epoch_ms: 1738886400000,
|
|
362
|
-
},
|
|
363
|
-
{ duration_ms: 3500, source: "file" },
|
|
364
|
-
);
|
|
365
|
-
console.log(outputYaml(result));
|
|
366
|
-
// ---
|
|
367
|
-
// code: "ok"
|
|
368
|
-
// result:
|
|
369
|
-
// created_at: "2025-02-07T00:00:00.000Z"
|
|
370
|
-
// file_size: "5.0MB"
|
|
371
|
-
// records_processed: 10
|
|
372
|
-
// trace:
|
|
373
|
-
// duration: "3.5s"
|
|
374
|
-
// source: "file"
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Example 3: JSONL Output
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
import { buildJsonOk, outputJson } from "agent-first-data";
|
|
381
|
-
|
|
382
|
-
const result = buildJsonOk(
|
|
383
|
-
{ status: "success" },
|
|
384
|
-
{ duration_ms: 250, api_key_secret: "sk-123" },
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
// Print JSONL to stdout (secrets redacted, one JSON object per line)
|
|
388
|
-
// Channel policy: machine-readable protocol/log events must not use stderr.
|
|
389
|
-
console.log(outputJson(result));
|
|
390
|
-
// {"code":"ok","result":{"status":"success"},"trace":{"api_key_secret":"***","duration_ms":250}}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
## Complete Suffix Example
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
import { outputYaml, outputPlain } from "agent-first-data";
|
|
397
|
-
|
|
398
|
-
const data = {
|
|
399
|
-
created_at_epoch_ms: 1738886400000,
|
|
400
|
-
request_timeout_ms: 5000,
|
|
401
|
-
cache_ttl_s: 3600,
|
|
402
|
-
file_size_bytes: 5242880,
|
|
403
|
-
payment_msats: 50000000,
|
|
404
|
-
price_usd_cents: 9999,
|
|
405
|
-
success_rate_percent: 95.5,
|
|
406
|
-
api_key_secret: "sk-1234567890abcdef",
|
|
407
|
-
user_name: "alice",
|
|
408
|
-
count: 42,
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
// YAML output (keys stripped, values formatted, secrets redacted)
|
|
412
|
-
console.log(outputYaml(data));
|
|
413
|
-
// ---
|
|
414
|
-
// api_key: "***"
|
|
415
|
-
// cache_ttl: "3600s"
|
|
416
|
-
// count: 42
|
|
417
|
-
// created_at: "2025-02-07T00:00:00.000Z"
|
|
418
|
-
// file_size: "5.0MB"
|
|
419
|
-
// payment: "50000000msats"
|
|
420
|
-
// price: "$99.99"
|
|
421
|
-
// request_timeout: "5.0s"
|
|
422
|
-
// success_rate: "95.5%"
|
|
423
|
-
// user_name: "alice"
|
|
424
|
-
|
|
425
|
-
// Plain logfmt output (same transformations, single line)
|
|
426
|
-
console.log(outputPlain(data));
|
|
427
|
-
// api_key=*** cache_ttl=3600s count=42 created_at=2025-02-07T00:00:00.000Z file_size=5.0MB payment=50000000msats price=$99.99 request_timeout=5.0s success_rate=95.5% user_name=alice
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
## AFDATA Logging
|
|
431
|
-
|
|
432
|
-
AFDATA-compliant structured logging. Every log line is formatted using the library's own `outputJson`/`outputPlain`/`outputYaml` functions. Span fields are carried via `AsyncLocalStorage` (async-safe), automatically flattened into each log line. Zero dependencies beyond Node.js built-ins.
|
|
433
|
-
|
|
434
|
-
### API
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
import { log, span, initJson, initPlain, initYaml } from "agent-first-data";
|
|
438
|
-
|
|
439
|
-
// Format selectors — set the output format for all subsequent log calls
|
|
440
|
-
initJson() // Single-line JSONL (secrets redacted, original keys) — default
|
|
441
|
-
initPlain() // Single-line logfmt (keys stripped, values formatted)
|
|
442
|
-
initYaml() // Multi-line YAML (keys stripped, values formatted)
|
|
443
|
-
|
|
444
|
-
// Logger — each method outputs a single log line to stdout
|
|
445
|
-
log.trace(msg, fields?)
|
|
446
|
-
log.debug(msg, fields?)
|
|
447
|
-
log.info(msg, fields?)
|
|
448
|
-
log.warn(msg, fields?)
|
|
449
|
-
log.error(msg, fields?)
|
|
450
|
-
|
|
451
|
-
// Span — run fn with additional fields on all log events
|
|
452
|
-
span<T>(fields, fn: () => T): T // works with sync and async functions
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### Setup
|
|
456
|
-
|
|
457
|
-
```typescript
|
|
458
|
-
import { log, initJson, initPlain, initYaml } from "agent-first-data";
|
|
459
|
-
|
|
460
|
-
// JSON output for production (one JSONL line per event, secrets redacted)
|
|
461
|
-
initJson(); // default, can be omitted
|
|
462
|
-
|
|
463
|
-
// Plain logfmt for development (keys stripped, values formatted)
|
|
464
|
-
initPlain();
|
|
465
|
-
|
|
466
|
-
// YAML for detailed inspection (multi-line, keys stripped, values formatted)
|
|
467
|
-
initYaml();
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
### Log Output
|
|
471
|
-
|
|
472
|
-
Output format depends on the init function used.
|
|
473
|
-
|
|
474
|
-
```typescript
|
|
475
|
-
log.info("Server started");
|
|
476
|
-
// JSON: {"timestamp_epoch_ms":1739000000000,"message":"Server started","code":"info"}
|
|
477
|
-
// Plain: code=info message="Server started" timestamp_epoch_ms=1739000000000
|
|
478
|
-
// YAML: ---
|
|
479
|
-
// code: "info"
|
|
480
|
-
// message: "Server started"
|
|
481
|
-
// timestamp_epoch_ms: 1739000000000
|
|
482
|
-
|
|
483
|
-
log.warn("DNS lookup failed", { error: "timeout", domain: "example.com" });
|
|
484
|
-
// JSON: {"timestamp_epoch_ms":...,"message":"DNS lookup failed","error":"timeout","domain":"example.com","code":"warn"}
|
|
485
|
-
// Plain: code=warn domain=example.com error=timeout message="DNS lookup failed" ...
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Span Support
|
|
489
|
-
|
|
490
|
-
Use `span()` to add fields to all log events within the callback. Spans nest and work with both sync and async functions.
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
import { log, span } from "agent-first-data";
|
|
494
|
-
|
|
495
|
-
await span({ request_id: "abc-123" }, async () => {
|
|
496
|
-
log.info("Processing");
|
|
497
|
-
// {"timestamp_epoch_ms":...,"message":"Processing","request_id":"abc-123","code":"info"}
|
|
498
|
-
|
|
499
|
-
await span({ step: "validate" }, async () => {
|
|
500
|
-
log.info("Validating input");
|
|
501
|
-
// {"timestamp_epoch_ms":...,"message":"Validating input","request_id":"abc-123","step":"validate","code":"info"}
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### Custom Code Override
|
|
507
|
-
|
|
508
|
-
The `code` field defaults to the log level. Override with an explicit field:
|
|
509
|
-
|
|
510
|
-
```typescript
|
|
511
|
-
log.info("Server ready", { code: "log", event: "startup" });
|
|
512
|
-
// {"timestamp_epoch_ms":...,"message":"Server ready","code":"log","event":"startup"}
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
### Output Fields
|
|
516
|
-
|
|
517
|
-
Every log line contains:
|
|
518
|
-
|
|
519
|
-
| Field | Type | Description |
|
|
520
|
-
|:------|:-----|:------------|
|
|
521
|
-
| `timestamp_epoch_ms` | number | Unix milliseconds |
|
|
522
|
-
| `message` | string | Log message |
|
|
523
|
-
| `code` | string | Level (trace/debug/info/warn/error) or explicit override |
|
|
524
|
-
| *span fields* | any | From `span()` callback |
|
|
525
|
-
| *event fields* | any | From fields argument |
|
|
526
|
-
|
|
527
|
-
### Log Output Formats
|
|
528
|
-
|
|
529
|
-
All three formats use the library's own output functions, so AFDATA suffix processing applies to log fields too:
|
|
530
|
-
|
|
531
|
-
| Format | Function | Keys | Values | Use case |
|
|
532
|
-
|:-------|:---------|:-----|:-------|:---------|
|
|
533
|
-
| **JSON** | `initJson` | original (with suffix) | raw | production, log aggregation |
|
|
534
|
-
| **Plain** | `initPlain` | stripped | formatted | development, compact scanning |
|
|
535
|
-
| **YAML** | `initYaml` | stripped | formatted | debugging, detailed inspection |
|
|
536
|
-
|
|
537
|
-
All formats automatically redact `_secret` fields in log output.
|
|
538
|
-
|
|
539
|
-
## Output Formats
|
|
540
|
-
|
|
541
|
-
Three output formats for different use cases:
|
|
542
|
-
|
|
543
|
-
| Format | Structure | Keys | Values | Use case |
|
|
544
|
-
|:-------|:----------|:-----|:-------|:---------|
|
|
545
|
-
| **JSON** | single-line | original (with suffix) | raw | programs, logs |
|
|
546
|
-
| **YAML** | multi-line | stripped | formatted | human inspection |
|
|
547
|
-
| **Plain** | single-line logfmt | stripped | formatted | compact scanning |
|
|
548
|
-
|
|
549
|
-
All formats automatically redact `_secret` fields.
|
|
550
|
-
|
|
551
|
-
## Supported Suffixes
|
|
552
|
-
|
|
553
|
-
- **Duration**: `_ms`, `_s`, `_ns`, `_us`, `_minutes`, `_hours`, `_days`
|
|
554
|
-
- **Timestamps**: `_epoch_ms`, `_epoch_s`, `_epoch_ns`, `_rfc3339`
|
|
555
|
-
- **Size**: `_bytes` (auto-scales to KB/MB/GB/TB), `_size` (config input, pass through)
|
|
556
|
-
- **Currency**: `_msats`, `_sats`, `_btc`, `_usd_cents`, `_eur_cents`, `_jpy`, `_{code}_cents`
|
|
557
|
-
- **Other**: `_percent`, `_secret` (auto-redacted in all formats)
|
|
558
|
-
|
|
559
|
-
TypeScript uses JavaScript `number` for JSON values, so `_epoch_ns` values above `Number.MAX_SAFE_INTEGER` lose precision. Prefer `_epoch_ms` for JSON interchange, or convert nanosecond values before formatting.
|
|
560
|
-
|
|
561
|
-
## Repository
|
|
562
|
-
|
|
563
|
-
This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
|
|
564
|
-
|
|
565
|
-
- **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
|
|
566
|
-
- **`skills/`** — AI coding agent skill for working with AFDATA conventions
|
|
567
|
-
|
|
568
|
-
To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
|
|
569
|
-
|
|
570
|
-
```bash
|
|
571
|
-
git clone https://github.com/agentfirstkit/agent-first-data
|
|
572
|
-
cd agent-first-data/typescript
|
|
573
|
-
npm test
|
|
574
|
-
```
|
|
35
|
+
- Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
|
|
36
|
+
- `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed and internal whitespace redacts the whole field.
|
|
37
|
+
- 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.
|
|
38
|
+
- Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
39
|
+
- `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
|
|
575
40
|
|
|
576
|
-
##
|
|
41
|
+
## Reference
|
|
577
42
|
|
|
578
|
-
|
|
43
|
+
- Full convention and API groups: [../docs/overview.md](../docs/overview.md)
|
|
44
|
+
- Formal cross-language contract: [../spec/agent-first-data.md](../spec/agent-first-data.md)
|
|
45
|
+
- Conformance fixtures: [../spec/fixtures](../spec/fixtures)
|
|
46
|
+
- Agent skill: [../skills/agent-first-data.md](../skills/agent-first-data.md)
|
package/dist/afdata_logging.d.ts
CHANGED
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
* log.info("Processing", { domain: "example.com" });
|
|
17
17
|
* });
|
|
18
18
|
*/
|
|
19
|
+
type LoggingRedactionOptions = {
|
|
20
|
+
secretNames?: readonly string[];
|
|
21
|
+
};
|
|
19
22
|
/** Set log output to single-line JSONL (secrets redacted, original keys). */
|
|
20
|
-
export declare function initJson(): void;
|
|
23
|
+
export declare function initJson(options?: LoggingRedactionOptions): void;
|
|
21
24
|
/** Set log output to single-line logfmt (keys stripped, values formatted). */
|
|
22
|
-
export declare function initPlain(): void;
|
|
25
|
+
export declare function initPlain(options?: LoggingRedactionOptions): void;
|
|
23
26
|
/** Set log output to multi-line YAML (keys stripped, values formatted). */
|
|
24
|
-
export declare function initYaml(): void;
|
|
27
|
+
export declare function initYaml(options?: LoggingRedactionOptions): void;
|
|
25
28
|
/**
|
|
26
29
|
* AFDATA logger. Each method outputs a single log line to stdout.
|
|
27
30
|
* Format is controlled by initJson/initPlain/initYaml (default: JSON).
|
|
@@ -38,3 +41,4 @@ export declare const log: {
|
|
|
38
41
|
* Spans nest: inner spans inherit and can override outer span fields.
|
|
39
42
|
*/
|
|
40
43
|
export declare function span<T>(fields: Record<string, unknown>, fn: () => T): T;
|
|
44
|
+
export {};
|
package/dist/afdata_logging.js
CHANGED
|
@@ -17,19 +17,26 @@
|
|
|
17
17
|
* });
|
|
18
18
|
*/
|
|
19
19
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
|
-
import {
|
|
20
|
+
import { outputJsonWithOptions, outputYamlWithOptions, outputPlainWithOptions, } from "./format.js";
|
|
21
21
|
const spanStore = new AsyncLocalStorage();
|
|
22
22
|
let currentFormat = "json";
|
|
23
|
+
let currentRedaction = {};
|
|
24
|
+
function setFormat(format, options) {
|
|
25
|
+
currentFormat = format;
|
|
26
|
+
currentRedaction = options?.secretNames ? { secretNames: options.secretNames } : {};
|
|
27
|
+
}
|
|
23
28
|
/** Set log output to single-line JSONL (secrets redacted, original keys). */
|
|
24
|
-
export function initJson() {
|
|
29
|
+
export function initJson(options) { setFormat("json", options); }
|
|
25
30
|
/** Set log output to single-line logfmt (keys stripped, values formatted). */
|
|
26
|
-
export function initPlain() {
|
|
31
|
+
export function initPlain(options) { setFormat("plain", options); }
|
|
27
32
|
/** Set log output to multi-line YAML (keys stripped, values formatted). */
|
|
28
|
-
export function initYaml() {
|
|
33
|
+
export function initYaml(options) { setFormat("yaml", options); }
|
|
29
34
|
function emit(level, message, fields) {
|
|
30
35
|
const entry = {
|
|
31
36
|
timestamp_epoch_ms: Date.now(),
|
|
32
37
|
message,
|
|
38
|
+
code: "log",
|
|
39
|
+
level,
|
|
33
40
|
};
|
|
34
41
|
// Span fields (from AsyncLocalStorage)
|
|
35
42
|
const spanFields = spanStore.getStore();
|
|
@@ -38,31 +45,26 @@ function emit(level, message, fields) {
|
|
|
38
45
|
entry[k] = v;
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
|
-
// Event fields
|
|
42
|
-
let hasCode = false;
|
|
48
|
+
// Event fields override span fields, except protocol code is always "log".
|
|
43
49
|
if (fields) {
|
|
44
50
|
for (const [k, v] of Object.entries(fields)) {
|
|
45
51
|
if (k === "code")
|
|
46
|
-
|
|
52
|
+
continue;
|
|
47
53
|
entry[k] = v;
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
// Default code from level
|
|
51
|
-
if (!hasCode) {
|
|
52
|
-
entry["code"] = level;
|
|
53
|
-
}
|
|
54
56
|
// Format using the library's own output functions
|
|
55
57
|
const value = entry;
|
|
56
58
|
let line;
|
|
57
59
|
switch (currentFormat) {
|
|
58
60
|
case "plain":
|
|
59
|
-
line =
|
|
61
|
+
line = outputPlainWithOptions(value, { redaction: currentRedaction });
|
|
60
62
|
break;
|
|
61
63
|
case "yaml":
|
|
62
|
-
line =
|
|
64
|
+
line = outputYamlWithOptions(value, { redaction: currentRedaction });
|
|
63
65
|
break;
|
|
64
66
|
default:
|
|
65
|
-
line =
|
|
67
|
+
line = outputJsonWithOptions(value, { redaction: currentRedaction });
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
68
70
|
process.stdout.write(line + "\n");
|