JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 763
  • Score
    100M100P100Q99341F
  • License MIT

Token-efficient CLI output formatting for LLM agents

Package Exports

  • agentfmt

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 agentfmt

Quick 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: 12px

API

Status Indicators

ok('Done')      // ✓ Done
fail('Error')   // ✗ Error
warn('Careful') // ⚠ Careful
info('Note')    // ℹ Note

Primitives

// 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×20

Options:

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] indices

Props Block

Key-value block with indentation:

props({
  name: 'Card',
  width: 200,
  fill: '#FFF',
  empty: null  // filtered out
})

Output:

  name: Card
  width: 200
  fill: #FFF

Histogram

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 8px

Lint 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 grid

Summary:

lintSummary({ errors: 2, warnings: 5 })
// ──────────────────────────────────────────────────
// 2 errors  5 warnings

Use 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

  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

License

MIT