agentfmt
v0.1.3
Published
Token-efficient CLI output formatting for LLM agents
Maintainers
Readme
agentfmt
Token-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.
Install
npm install agentfmtQuick Start
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: 12pxAPI
Status Indicators
ok('Done') // ✓ Done
fail('Error') // ✗ Error
warn('Careful') // ⚠ Careful
info('Note') // ℹ NotePrimitives
// 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…"Lists
Numbered lists with optional details:
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×20Options:
list(items, { numbered: false }) // • bullet points
list(items, { start: 1 }) // [1], [2], [3]...Trees
Hierarchical data with inline details:
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:
tree(root, { maxDepth: 2 }) // Limit depth
tree(root, { showIndex: false }) // Hide [N] indicesProps Block
Key-value block with indentation:
props({
name: 'Card',
width: 200,
fill: '#FFF',
empty: null // filtered out
})Output:
name: Card
width: 200
fill: #FFFHistogram
Bar chart for frequency data:
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:
histogram(items, { maxBarLength: 20, scale: 5 })Summary
Compact count summary:
summary({ colors: 45, nodes: 120, errors: 0 })
// "45 colors, 120 nodes" (zeros filtered)Node Formatter
Convenience formatter for node-like structures:
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 8pxLint Reports
Structured issue reporting:
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 gridSummary:
lintSummary({ errors: 2, warnings: 5 })
// ──────────────────────────────────────────────────
// 2 errors 5 warningsUse Cases
CLI Tools for LLM Agents
When building CLI tools that LLMs will interact with:
// Bad: verbose, wastes tokens
console.log(`Successfully created a new frame with the name "Header" and ID "1:23". The frame has dimensions of 200 pixels wide by 100 pixels tall.`)
// Good: compact, scannable
console.log(ok('Created frame'))
console.log(node({ type: 'frame', name: 'Header', id: '1:23', width: 200, height: 100 }))Design Tool Integration
// 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 }))Lint/Audit Results
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 }))Colors
Re-exported from picocolors for convenience:
import { dim, bold, green, red, yellow, cyan } from 'agentfmt'Design Principles
- Compact — minimize tokens while preserving information
- Scannable — consistent patterns LLMs can parse reliably
- Hierarchical — indentation shows structure
- Filterable — null/undefined/empty values auto-filtered
- Flexible — works for lists, trees, reports, stats
License
MIT
