From 5cf0eb7c3c89e7db1ce115d79df46bc9474a0e95 Mon Sep 17 00:00:00 2001 From: Dayna Blackwell Date: Tue, 14 Jul 2026 10:12:06 -0700 Subject: [PATCH 1/2] feat: add gcf output format Adds `gcf` as an output format alongside toon/json/yaml/md/jsonl. One branch in Formatter.format() mirroring the existing TOON path; opt-in via `--format gcf`, TOON stays the default and is untouched. GCF (Graph Compact Format) path-flattens nested fields, so it ties TOON on flat records and is smaller on nested payloads (where TOON re-declares the schema per row and can exceed raw JSON), and round-trips losslessly. @blackwell-systems/gcf is MIT with zero runtime dependencies. --- .changeset/gcf-output-format.md | 5 +++++ package.json | 1 + pnpm-lock.yaml | 9 +++++++++ src/Cli.test-d.ts | 4 ++-- src/Cli.ts | 2 +- src/Formatter.test.ts | 36 +++++++++++++++++++++++++++++++++ src/Formatter.ts | 7 ++++++- src/Help.ts | 2 +- 8 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 .changeset/gcf-output-format.md diff --git a/.changeset/gcf-output-format.md b/.changeset/gcf-output-format.md new file mode 100644 index 00000000..829679ad --- /dev/null +++ b/.changeset/gcf-output-format.md @@ -0,0 +1,5 @@ +--- +'incur': patch +--- + +Added `gcf` as an output format (`--format gcf`) alongside `toon`, `json`, `yaml`, `md`, and `jsonl`. GCF (Graph Compact Format) is a lossless, zero-runtime-dependency encoding for structured data. diff --git a/package.json b/package.json index c514caaf..bc4791dc 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "SKILL.md" ], "dependencies": { + "@blackwell-systems/gcf": "^2.4.0", "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/server": "^2.0.0-alpha.2", "@scalar/openapi-types": "^0.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f890dafd..f26d1427 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: dependencies: + '@blackwell-systems/gcf': + specifier: ^2.4.0 + version: 2.4.0 '@cfworker/json-schema': specifier: ^4.1.1 version: 4.1.1 @@ -112,6 +115,10 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@blackwell-systems/gcf@2.4.0': + resolution: {integrity: sha512-lLEZCzNMYVk630iaVVPPhcQtmCjhPRETRlwk/JAptGE0SEL6ZMHfA9HZPornuMvvpGeyrimRjj9Yb2nYQNS1og==} + hasBin: true + '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} @@ -1504,6 +1511,8 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@blackwell-systems/gcf@2.4.0': {} + '@cfworker/json-schema@4.1.1': {} '@changesets/apply-release-plan@7.0.14': diff --git a/src/Cli.test-d.ts b/src/Cli.test-d.ts index 1de63ef8..b493b30a 100644 --- a/src/Cli.test-d.ts +++ b/src/Cli.test-d.ts @@ -271,7 +271,7 @@ test('middleware() without generic gives empty context', () => { middleware((c, _next) => { expectTypeOf(c.var).toEqualTypeOf<{}>() expectTypeOf(c.env).toEqualTypeOf<{}>() - expectTypeOf(c.format).toEqualTypeOf<'toon' | 'json' | 'yaml' | 'md' | 'jsonl'>() + expectTypeOf(c.format).toEqualTypeOf<'toon' | 'json' | 'yaml' | 'md' | 'jsonl' | 'gcf'>() expectTypeOf(c.formatExplicit).toEqualTypeOf() }) }) @@ -367,7 +367,7 @@ test('run() context exposes format metadata', () => { const cli = Cli.create('test') cli.command('ping', { run(c) { - expectTypeOf(c.format).toEqualTypeOf<'toon' | 'json' | 'yaml' | 'md' | 'jsonl'>() + expectTypeOf(c.format).toEqualTypeOf<'toon' | 'json' | 'yaml' | 'md' | 'jsonl' | 'gcf'>() expectTypeOf(c.formatExplicit).toEqualTypeOf() return { pong: true } }, diff --git a/src/Cli.ts b/src/Cli.ts index 9e7558ee..6230f2a5 100644 --- a/src/Cli.ts +++ b/src/Cli.ts @@ -2480,7 +2480,7 @@ declare namespace serveImpl { } /** @internal Extracts built-in flags (--full-output, --format, --json, --llms, --help, --version) from argv. */ -const validFormats = new Set(['toon', 'json', 'yaml', 'md', 'jsonl'] as const) +const validFormats = new Set(['toon', 'json', 'yaml', 'md', 'jsonl', 'gcf'] as const) function extractBuiltinFlags(argv: string[], options: extractBuiltinFlags.Options = {}) { let fullOutput = false diff --git a/src/Formatter.test.ts b/src/Formatter.test.ts index e1f01091..8ebe6115 100644 --- a/src/Formatter.test.ts +++ b/src/Formatter.test.ts @@ -1,3 +1,4 @@ +import { decodeGeneric } from '@blackwell-systems/gcf' import { decode } from '@toon-format/toon' import { Formatter } from 'incur' @@ -53,6 +54,41 @@ describe('format', () => { expect(result).toMatchInlineSnapshot(`"message: hello"`) }) + test('formats as GCF (explicit)', () => { + const result = Formatter.format({ message: 'hello' }, 'gcf') + expect(result).toBe('GCF profile=generic\nmessage=hello\n') + }) + + test('formats an array of records as a GCF table', () => { + const result = Formatter.format( + { + ok: true, + data: { + users: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + ], + }, + meta: { command: 'users' }, + }, + 'gcf', + ) + expect(result).toBe( + 'GCF profile=generic\nok=true\n## data\n ## users [2]{id,name}\n 1|a\n 2|b\n## meta\n command=users\n', + ) + }) + + test('round-trips through GCF decode', () => { + const envelope = { + ok: true, + data: { items: [1, 2, 3] }, + meta: { command: 'list', duration: '5ms' }, + } + + const result = decodeGeneric(Formatter.format(envelope, 'gcf')) + expect(result).toMatchObject(envelope) + }) + test('formats as JSON', () => { const result = Formatter.format({ message: 'hello' }, 'json') expect(result).toMatchInlineSnapshot(` diff --git a/src/Formatter.ts b/src/Formatter.ts index b0abe45c..39f4aebf 100644 --- a/src/Formatter.ts +++ b/src/Formatter.ts @@ -1,10 +1,11 @@ +import { encodeGeneric } from '@blackwell-systems/gcf' import { encode } from '@toon-format/toon' import * as Json from './internal/json.js' import * as Yaml from './internal/yaml.js' /** Supported output formats. */ -export type Format = 'toon' | 'json' | 'yaml' | 'md' | 'jsonl' +export type Format = 'toon' | 'json' | 'yaml' | 'md' | 'jsonl' | 'gcf' /** Serializes a value to the specified format. Defaults to TOON. */ export function format(value: unknown, fmt: Format = 'toon'): string { @@ -26,6 +27,10 @@ export function format(value: unknown, fmt: Format = 'toon'): string { if (Array.isArray(value)) return value.map((v) => Json.stringify(v)).join('\n') return Json.stringify(value) } + if (fmt === 'gcf') { + if (isScalar(value)) return String(value) + return encodeGeneric(value) + } // toon (default) if (isScalar(value)) return String(value) return encode(value as Record) diff --git a/src/Help.ts b/src/Help.ts index 3c6e0d3f..080b6365 100644 --- a/src/Help.ts +++ b/src/Help.ts @@ -407,7 +407,7 @@ function globalOptionsLines( flag: '--filter-output ', desc: 'Filter output by key paths (e.g. foo,bar.baz,a[0,3])', }, - { flag: '--format ', desc: 'Output format' }, + { flag: '--format ', desc: 'Output format' }, { flag: '--help', desc: 'Show help' }, { flag: '--llms, --llms-full', desc: 'Print LLM-readable manifest' }, ...(root ? [{ flag: '--mcp', desc: 'Start as MCP stdio server' }] : []), From 635e7108517d816a6361d1aa487472ca82528e6f Mon Sep 17 00:00:00 2001 From: Dayna Blackwell Date: Tue, 14 Jul 2026 11:06:22 -0700 Subject: [PATCH 2/2] chore: pin @blackwell-systems/gcf to exact 2.4.0 --- package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bc4791dc..5166b383 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "SKILL.md" ], "dependencies": { - "@blackwell-systems/gcf": "^2.4.0", + "@blackwell-systems/gcf": "2.4.0", "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/server": "^2.0.0-alpha.2", "@scalar/openapi-types": "^0.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f26d1427..3bf9a3bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: .: dependencies: '@blackwell-systems/gcf': - specifier: ^2.4.0 + specifier: 2.4.0 version: 2.4.0 '@cfworker/json-schema': specifier: ^4.1.1