Token-efficient CLI output formatting for LLM agents
npm install agentfmtToken-efficient CLI output formatting for LLM agents.
When LLMs interact with CLI tools, every token counts. This library provides formatters that produce compact, scannable output optimized for agent consumption.
``bash`
npm install agentfmt
`typescript
import { ok, fail, entity, list, node } from 'agentfmt'
// Status messages
console.log(ok('Created frame')) // ✓ Created frame
console.log(fail('Not found')) // ✗ Not found
// Entity with type and ID
console.log(entity('frame', 'Header', '1:23'))
// [frame] "Header" (1:23)
// Detailed node
console.log(node({
type: 'frame',
name: 'Card',
id: '1:23',
width: 200,
height: 100
}, {
fill: '#FFFFFF',
radius: '12px'
}))
// [frame] "Card" (1:23)
// box: 200×100
// fill: #FFFFFF
// radius: 12px
`
`typescript`
ok('Done') // ✓ Done
fail('Error') // ✗ Error
warn('Careful') // ⚠ Careful
info('Note') // ℹ Note
`typescript
// Key-value pair
kv('fill', '#FFF') // fill: #FFF
kv('empty', null) // '' (empty string)
// Box dimensions
box(200, 100) // 200×100
box(200, 100, 50, 30) // 200×100 at (50, 30)
// Entity header
entity('frame', 'Header') // [frame] "Header"
entity('frame', 'Header', '1:23') // [frame] "Header" (1:23)
// Text utilities
truncate('long text here', 8) // long tex…
quoted('hello\nworld') // "hello↵world"
quoted('very long text', 10) // "very long…"
`
Numbered lists with optional details:
`typescript`
list([
{ header: 'frame "Header" (1:23)', details: { box: '200×100', fill: '#FFF' } },
{ header: 'text "Title" (1:24)', details: { box: '100×20' } }
])
Output:
`
[0] frame "Header" (1:23)
box: 200×100
fill: #FFF
[1] text "Title" (1:24)
box: 100×20
`
Options:
`typescript`
list(items, { numbered: false }) // • bullet points
list(items, { start: 1 }) // [1], [2], [3]...
Hierarchical data with inline details:
`typescript`
tree({
header: 'frame "Root" (1:1)',
details: { box: '400×300' },
children: [
{ header: 'text "Title" (1:2)', details: { font: '24px Bold' } },
{ header: 'frame "Body" (1:3)', children: [...] }
]
})
Output:
``
[0] frame "Root" (1:1)
box: 400×300
[0] text "Title" (1:2)
font: 24px Bold
[1] frame "Body" (1:3)
...
Options:
`typescript`
tree(root, { maxDepth: 2 }) // Limit depth
tree(root, { showIndex: false }) // Hide [N] indices
Key-value block with indentation:
`typescript`
props({
name: 'Card',
width: 200,
fill: '#FFF',
empty: null // filtered out
})
Output:
``
name: Card
width: 200
fill: #FFF
Bar chart for frequency data:
`typescript`
histogram([
{ label: '#FFFFFF', value: 128, tag: '$White' },
{ label: '#000000', value: 64 },
{ label: '#3B82F6', value: 32, tag: '$Primary' }
])
Output:
``
#FFFFFF █████████████ 128× ($White)
#000000 ███████ 64×
#3B82F6 ████ 32× ($Primary)
Options:
`typescript`
histogram(items, { maxBarLength: 20, scale: 5 })
Compact count summary:
`typescript`
summary({ colors: 45, nodes: 120, errors: 0 })
// "45 colors, 120 nodes" (zeros filtered)
Convenience formatter for node-like structures:
`typescript`
node({
type: 'FRAME',
name: 'Card',
id: '1:23',
width: 200,
height: 100,
x: 0,
y: 0
}, {
fill: '#FFFFFF',
radius: '12px',
shadow: '0 4px 8px'
})
Output:
``
[frame] "Card" (1:23)
box: 200×100 at (0, 0)
fill: #FFFFFF
radius: 12px
shadow: 0 4px 8px
Structured issue reporting:
`typescript`
lint([
{
path: 'Page/Frame/Button (1:23)',
messages: [
{ severity: 'error', message: 'Missing fill style', rule: 'no-mixed-styles' },
{ severity: 'warning', message: 'Off-grid position', rule: 'pixel-perfect', suggest: 'Snap to 8px grid' }
]
}
], { verbose: true })
Output:
``
✖ Page/Frame/Button (1:23)
✖ Missing fill style no-mixed-styles
⚠ Off-grid position pixel-perfect
→ Snap to 8px grid
Summary:
`typescript`
lintSummary({ errors: 2, warnings: 5 })
// ──────────────────────────────────────────────────
// 2 errors 5 warnings
When building CLI tools that LLMs will interact with:
`typescriptSuccessfully created a new frame with the name "Header" and ID "1:23". The frame has dimensions of 200 pixels wide by 100 pixels tall.
// Bad: verbose, wastes tokens
console.log()
// Good: compact, scannable
console.log(ok('Created frame'))
console.log(node({ type: 'frame', name: 'Header', id: '1:23', width: 200, height: 100 }))
`
`typescript
// List components
console.log(list(components.map(c => ({
header: entity(c.type, c.name, c.id),
details: { box: box(c.width, c.height), variants: c.variants?.length }
}))))
// Analyze colors
console.log(histogram(colors.map(c => ({
label: c.hex,
value: c.count,
tag: c.variableName ? $${c.variableName} : undefined`
}))))
console.log()
console.log(summary({ unique: colors.length, hardcoded: hardcodedCount }))
`typescript
const groups = issues.reduce((acc, issue) => {
const key = issue.nodePath
if (!acc[key]) acc[key] = { path: key, messages: [] }
acc[key].messages.push({
severity: issue.severity,
message: issue.message,
rule: issue.ruleId,
suggest: issue.fix
})
return acc
}, {})
console.log(lint(Object.values(groups), { verbose: args.verbose }))
console.log(lintSummary({ errors: errorCount, warnings: warningCount }))
`
Re-exported from picocolors for convenience:
`typescript``
import { dim, bold, green, red, yellow, cyan } from 'agentfmt'
1. Compact — minimize tokens while preserving information
2. Scannable — consistent patterns LLMs can parse reliably
3. Hierarchical — indentation shows structure
4. Filterable — null/undefined/empty values auto-filtered
5. Flexible — works for lists, trees, reports, stats
MIT