agent-first-data 0.1.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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * AFD output formatting: JSON, YAML, plain text with suffix-driven transforms.
3
+ */
4
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
5
+ [key: string]: JsonValue;
6
+ };
7
+ export declare enum OutputFormat {
8
+ Json = "json",
9
+ Yaml = "yaml",
10
+ Plain = "plain"
11
+ }
12
+ export declare function formatValue(fmt: OutputFormat, value: JsonValue): string;
13
+ export declare function formatPretty(fmt: OutputFormat, value: JsonValue): string;
14
+ export declare function toYaml(value: JsonValue): string;
15
+ export declare function toPlain(value: JsonValue): string;
16
+ export declare function redactSecrets(value: JsonValue): JsonValue;
17
+ export declare function ok(result: JsonValue): JsonValue;
18
+ export declare function okTrace(result: JsonValue, trace: JsonValue): JsonValue;
19
+ export declare function error(message: string): JsonValue;
20
+ export declare function errorTrace(message: string, trace: JsonValue): JsonValue;
21
+ export declare function startup(config: JsonValue, args: JsonValue, env: JsonValue): JsonValue;
22
+ export declare function status(code: string, fields?: {
23
+ [key: string]: JsonValue;
24
+ }): JsonValue;
25
+ /**
26
+ * Parse a human-readable size string into bytes.
27
+ * Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
28
+ * Case-insensitive. Trims whitespace. Returns null for invalid input.
29
+ */
30
+ export declare function parseSize(s: string): number | null;
31
+ export {};
package/dist/format.js ADDED
@@ -0,0 +1,340 @@
1
+ /**
2
+ * AFD output formatting: JSON, YAML, plain text with suffix-driven transforms.
3
+ */
4
+ // ═══════════════════════════════════════════
5
+ // OutputFormat
6
+ // ═══════════════════════════════════════════
7
+ export var OutputFormat;
8
+ (function (OutputFormat) {
9
+ OutputFormat["Json"] = "json";
10
+ OutputFormat["Yaml"] = "yaml";
11
+ OutputFormat["Plain"] = "plain";
12
+ })(OutputFormat || (OutputFormat = {}));
13
+ export function formatValue(fmt, value) {
14
+ switch (fmt) {
15
+ case OutputFormat.Json:
16
+ return JSON.stringify(value);
17
+ case OutputFormat.Yaml:
18
+ return toYaml(value);
19
+ case OutputFormat.Plain:
20
+ return toPlain(value);
21
+ }
22
+ }
23
+ export function formatPretty(fmt, value) {
24
+ switch (fmt) {
25
+ case OutputFormat.Json:
26
+ return JSON.stringify(value, null, 2);
27
+ case OutputFormat.Yaml:
28
+ return toYaml(value);
29
+ case OutputFormat.Plain:
30
+ return toPlain(value);
31
+ }
32
+ }
33
+ // ═══════════════════════════════════════════
34
+ // YAML
35
+ // ═══════════════════════════════════════════
36
+ export function toYaml(value) {
37
+ const lines = ["---"];
38
+ renderYaml(value, 0, lines);
39
+ return lines.join("\n");
40
+ }
41
+ function renderYaml(value, indent, lines) {
42
+ const prefix = " ".repeat(indent);
43
+ if (isObject(value)) {
44
+ for (const [k, v] of Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
45
+ if (isObject(v)) {
46
+ if (Object.keys(v).length > 0) {
47
+ lines.push(`${prefix}${k}:`);
48
+ renderYaml(v, indent + 1, lines);
49
+ }
50
+ else {
51
+ lines.push(`${prefix}${k}: {}`);
52
+ }
53
+ }
54
+ else if (Array.isArray(v)) {
55
+ if (v.length === 0) {
56
+ lines.push(`${prefix}${k}: []`);
57
+ }
58
+ else {
59
+ lines.push(`${prefix}${k}:`);
60
+ for (const item of v) {
61
+ if (isObject(item)) {
62
+ lines.push(`${prefix} -`);
63
+ renderYaml(item, indent + 2, lines);
64
+ }
65
+ else {
66
+ lines.push(`${prefix} - ${yamlScalar(item)}`);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ else {
72
+ lines.push(`${prefix}${k}: ${yamlScalar(v)}`);
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ lines.push(`${prefix}${yamlScalar(value)}`);
78
+ }
79
+ }
80
+ function yamlScalar(value) {
81
+ if (typeof value === "string") {
82
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
83
+ }
84
+ if (value === null)
85
+ return "null";
86
+ if (typeof value === "boolean")
87
+ return value.toString();
88
+ if (typeof value === "number")
89
+ return value.toString();
90
+ return `"${String(value).replace(/"/g, '\\"')}"`;
91
+ }
92
+ // ═══════════════════════════════════════════
93
+ // Plain
94
+ // ═══════════════════════════════════════════
95
+ export function toPlain(value) {
96
+ const lines = [];
97
+ renderPlain(value, 0, lines);
98
+ return lines.join("\n");
99
+ }
100
+ function renderPlain(value, indent, lines) {
101
+ const prefix = " ".repeat(indent);
102
+ if (isObject(value)) {
103
+ for (const [k, v] of Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
104
+ if (isObject(v)) {
105
+ lines.push(`${prefix}${k}:`);
106
+ renderPlain(v, indent + 1, lines);
107
+ }
108
+ else if (Array.isArray(v)) {
109
+ if (v.length === 0) {
110
+ lines.push(`${prefix}${k}: []`);
111
+ }
112
+ else if (v.every((i) => !isObject(i) && !Array.isArray(i))) {
113
+ lines.push(`${prefix}${k}:`);
114
+ for (const item of v) {
115
+ lines.push(`${prefix} - ${plainScalar(item)}`);
116
+ }
117
+ }
118
+ else {
119
+ lines.push(`${prefix}${k}:`);
120
+ for (const item of v) {
121
+ if (isObject(item)) {
122
+ lines.push(`${prefix} -`);
123
+ renderPlain(item, indent + 2, lines);
124
+ }
125
+ else {
126
+ lines.push(`${prefix} - ${plainScalar(item)}`);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ else {
132
+ lines.push(`${prefix}${k}: ${formatPlainField(k, v)}`);
133
+ }
134
+ }
135
+ }
136
+ else {
137
+ lines.push(`${prefix}${plainScalar(value)}`);
138
+ }
139
+ }
140
+ function formatPlainField(key, value) {
141
+ const lower = key.toLowerCase();
142
+ // Secret
143
+ if (lower.endsWith("_secret"))
144
+ return "***";
145
+ // Timestamps
146
+ if (lower.endsWith("_epoch_ms")) {
147
+ if (typeof value === "number" && Number.isInteger(value))
148
+ return formatRfc3339Ms(value);
149
+ }
150
+ if (lower.endsWith("_epoch_s")) {
151
+ if (typeof value === "number" && Number.isInteger(value))
152
+ return formatRfc3339Ms(value * 1000);
153
+ }
154
+ if (lower.endsWith("_epoch_ns")) {
155
+ if (typeof value === "number" && Number.isInteger(value))
156
+ return formatRfc3339Ms(Math.floor(value / 1_000_000));
157
+ }
158
+ if (lower.endsWith("_rfc3339"))
159
+ return plainScalar(value);
160
+ // Size
161
+ if (lower.endsWith("_bytes")) {
162
+ if (typeof value === "number" && Number.isInteger(value))
163
+ return formatBytesHuman(value);
164
+ }
165
+ // Percentage
166
+ if (lower.endsWith("_percent") && typeof value === "number")
167
+ return `${plainScalar(value)}%`;
168
+ // Currency — Bitcoin
169
+ if (lower.endsWith("_msats") && typeof value === "number")
170
+ return `${plainScalar(value)}msats`;
171
+ if (lower.endsWith("_sats") && typeof value === "number")
172
+ return `${plainScalar(value)}sats`;
173
+ if (lower.endsWith("_btc") && typeof value === "number")
174
+ return `${plainScalar(value)} BTC`;
175
+ // Currency — Fiat
176
+ if (lower.endsWith("_usd_cents") && typeof value === "number" && value >= 0 && Number.isInteger(value)) {
177
+ return `$${Math.floor(value / 100)}.${String(value % 100).padStart(2, "0")}`;
178
+ }
179
+ if (lower.endsWith("_eur_cents") && typeof value === "number" && value >= 0 && Number.isInteger(value)) {
180
+ return `\u20ac${Math.floor(value / 100)}.${String(value % 100).padStart(2, "0")}`;
181
+ }
182
+ if (lower.endsWith("_jpy") && typeof value === "number" && value >= 0 && Number.isInteger(value)) {
183
+ return `\u00a5${formatWithCommas(value)}`;
184
+ }
185
+ if (lower.endsWith("_cents")) {
186
+ const code = extractCurrencyCode(lower);
187
+ if (code && typeof value === "number" && value >= 0 && Number.isInteger(value)) {
188
+ return `${Math.floor(value / 100)}.${String(value % 100).padStart(2, "0")} ${code.toUpperCase()}`;
189
+ }
190
+ }
191
+ // Duration — long
192
+ if (lower.endsWith("_minutes") && typeof value === "number")
193
+ return `${plainScalar(value)} minutes`;
194
+ if (lower.endsWith("_hours") && typeof value === "number")
195
+ return `${plainScalar(value)} hours`;
196
+ if (lower.endsWith("_days") && typeof value === "number")
197
+ return `${plainScalar(value)} days`;
198
+ // Duration — ms
199
+ if (lower.endsWith("_ms") && !lower.endsWith("_epoch_ms") && typeof value === "number") {
200
+ if (value >= 1000)
201
+ return `${(value / 1000).toFixed(2)}s`;
202
+ return `${plainScalar(value)}ms`;
203
+ }
204
+ // Duration — ns, us, s
205
+ if (lower.endsWith("_ns") && !lower.endsWith("_epoch_ns") && typeof value === "number")
206
+ return `${plainScalar(value)}ns`;
207
+ if (lower.endsWith("_us") && typeof value === "number")
208
+ return `${plainScalar(value)}\u03bcs`;
209
+ if (lower.endsWith("_s") && !lower.endsWith("_epoch_s") && typeof value === "number")
210
+ return `${plainScalar(value)}s`;
211
+ return plainScalar(value);
212
+ }
213
+ function plainScalar(value) {
214
+ if (typeof value === "string")
215
+ return value;
216
+ if (value === null)
217
+ return "null";
218
+ if (typeof value === "boolean")
219
+ return value.toString();
220
+ if (typeof value === "number")
221
+ return value.toString();
222
+ return String(value);
223
+ }
224
+ // ═══════════════════════════════════════════
225
+ // Secret redaction
226
+ // ═══════════════════════════════════════════
227
+ export function redactSecrets(value) {
228
+ if (isObject(value)) {
229
+ for (const k of Object.keys(value)) {
230
+ if (k.toLowerCase().endsWith("_secret") && typeof value[k] === "string") {
231
+ value[k] = "***";
232
+ }
233
+ redactSecrets(value[k]);
234
+ }
235
+ }
236
+ else if (Array.isArray(value)) {
237
+ for (const item of value) {
238
+ redactSecrets(item);
239
+ }
240
+ }
241
+ return value;
242
+ }
243
+ // ═══════════════════════════════════════════
244
+ // AFD Protocol templates
245
+ // ═══════════════════════════════════════════
246
+ export function ok(result) {
247
+ return { code: "ok", result };
248
+ }
249
+ export function okTrace(result, trace) {
250
+ return { code: "ok", result, trace };
251
+ }
252
+ export function error(message) {
253
+ return { code: "error", error: message };
254
+ }
255
+ export function errorTrace(message, trace) {
256
+ return { code: "error", error: message, trace };
257
+ }
258
+ export function startup(config, args, env) {
259
+ return { code: "startup", config, args, env };
260
+ }
261
+ export function status(code, fields) {
262
+ return { ...fields, code };
263
+ }
264
+ // ═══════════════════════════════════════════
265
+ // Helpers
266
+ // ═══════════════════════════════════════════
267
+ function formatRfc3339Ms(ms) {
268
+ try {
269
+ const d = new Date(ms);
270
+ return d.toISOString().replace(/(\.\d{3})\d*Z$/, "$1Z");
271
+ }
272
+ catch {
273
+ return String(ms);
274
+ }
275
+ }
276
+ function formatBytesHuman(bytes) {
277
+ const KB = 1024;
278
+ const MB = KB * 1024;
279
+ const GB = MB * 1024;
280
+ const TB = GB * 1024;
281
+ const sign = bytes < 0 ? "-" : "";
282
+ const b = Math.abs(bytes);
283
+ if (b >= TB)
284
+ return `${sign}${(b / TB).toFixed(1)}TB`;
285
+ if (b >= GB)
286
+ return `${sign}${(b / GB).toFixed(1)}GB`;
287
+ if (b >= MB)
288
+ return `${sign}${(b / MB).toFixed(1)}MB`;
289
+ if (b >= KB)
290
+ return `${sign}${(b / KB).toFixed(1)}KB`;
291
+ return `${bytes}B`;
292
+ }
293
+ function formatWithCommas(n) {
294
+ return n.toLocaleString("en-US");
295
+ }
296
+ /**
297
+ * Parse a human-readable size string into bytes.
298
+ * Accepts bare numbers or numbers followed by a unit letter (B/K/M/G/T).
299
+ * Case-insensitive. Trims whitespace. Returns null for invalid input.
300
+ */
301
+ export function parseSize(s) {
302
+ s = s.trim();
303
+ if (!s)
304
+ return null;
305
+ const multipliers = {
306
+ b: 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4,
307
+ };
308
+ const last = s[s.length - 1].toLowerCase();
309
+ let numStr;
310
+ let mult;
311
+ if (last in multipliers) {
312
+ numStr = s.slice(0, -1);
313
+ mult = multipliers[last];
314
+ }
315
+ else if ((last >= "0" && last <= "9") || last === ".") {
316
+ numStr = s;
317
+ mult = 1;
318
+ }
319
+ else {
320
+ return null;
321
+ }
322
+ if (!numStr)
323
+ return null;
324
+ const n = Number(numStr);
325
+ if (isNaN(n) || n < 0 || !isFinite(n))
326
+ return null;
327
+ return Math.trunc(n * mult);
328
+ }
329
+ function extractCurrencyCode(key) {
330
+ if (!key.endsWith("_cents"))
331
+ return null;
332
+ const withoutCents = key.slice(0, -6);
333
+ const idx = withoutCents.lastIndexOf("_");
334
+ if (idx < 0)
335
+ return null;
336
+ return withoutCents.slice(idx + 1);
337
+ }
338
+ function isObject(value) {
339
+ return typeof value === "object" && value !== null && !Array.isArray(value);
340
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Agent-First Data (AFD) — suffix-driven output formatting and protocol templates.
3
+ */
4
+ export { OutputFormat, formatValue, formatPretty, toYaml, toPlain, redactSecrets, parseSize } from "./format.js";
5
+ export { ok, okTrace, error, errorTrace, startup, status } from "./format.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Agent-First Data (AFD) — suffix-driven output formatting and protocol templates.
3
+ */
4
+ export { OutputFormat, formatValue, formatPretty, toYaml, toPlain, redactSecrets, parseSize } from "./format.js";
5
+ export { ok, okTrace, error, errorTrace, startup, status } from "./format.js";
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "agent-first-data",
3
+ "version": "0.1.0",
4
+ "description": "Agent-First Data (AFD) — suffix-driven output formatting and protocol templates for AI agents",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "node --test src/*.test.ts --experimental-strip-types"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.7"
22
+ }
23
+ }