Skip to content

pleaseai/cli-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@pleaseai/cli-toolkit

npm version CI codecov Socket Badge code style License: MIT

Shared CLI utilities for LLM-focused command-line tools.

Features

  • Output Formatting: JSON and TOON (Token-Oriented Object Notation) output with field filtering
  • Internationalization: Language detection and message management (Korean/English)
  • Progress Indicators: Emoji-based status messages for long-running operations
  • Input Validation: Type-safe validation for common input patterns
  • Structured Errors: Machine-readable error codes, suggestions, and exit-code mapping for agent-friendly CLIs

Installation

bun add @pleaseai/cli-toolkit

Usage

Output Module

Format command output as JSON or TOON with optional field filtering:

import { outputData, parseFields } from '@pleaseai/cli-toolkit/output'

const data = [
  { number: 123, title: 'Feature request', state: 'OPEN' },
  { number: 124, title: 'Bug fix', state: 'CLOSED' },
]

// JSON output with all fields
outputData(data, 'json')

// TOON output for LLM consumption (58.9% token savings vs JSON)
outputData(data, 'toon')

// JSON output with field filtering
const fields = parseFields('number,title')
outputData(data, 'json', fields)
// Output: [{"number": 123, "title": "Feature request"}, ...]

i18n Module

Manage multilingual messages for your CLI:

import { detectSystemLanguage, I18nManager } from '@pleaseai/cli-toolkit/i18n'

const i18n = new I18nManager()

// Register messages
i18n.register('myapp.issues', 'en', {
  creating: 'Creating issue...',
  created: (number: number) => `Issue #${number} created!`,
})

i18n.register('myapp.issues', 'ko', {
  creating: '이슈 생성 중...',
  created: (number: number) => `이슈 #${number}가 생성되었습니다!`,
})

// Auto-detect language and get messages
const lang = detectSystemLanguage() // 'ko' or 'en' based on LANG env var
const msg = i18n.get('myapp.issues', lang)

console.log(msg.creating) // 'Creating issue...' or '이슈 생성 중...'
console.log(msg.created(123)) // 'Issue #123 created!' or '이슈 #123가 생성되었습니다!'

Progress Module

Display user-friendly progress indicators:

import { createProgressIndicator } from '@pleaseai/cli-toolkit/progress'

const progress = createProgressIndicator()

progress.start('Fetching data...') // 📡 Fetching data...
progress.update('Processing records...') // ⏳ Processing records...
progress.success('Operation completed!') // ✅ Operation completed!
progress.error('Operation failed') // ❌ Operation failed
progress.info('Additional info') // ℹ️  Additional info

Validation Module

Validate user inputs with clear error messages:

import {
  validateNonEmptyString,
  validatePattern,
  validatePositiveInteger,
} from '@pleaseai/cli-toolkit/validation'

// Validate positive integers
const id = validatePositiveInteger('123', 'Issue ID') // 123
// validatePositiveInteger('0', 'Issue ID') → throws "Issue ID must be a positive integer"

// Validate non-empty strings
const body = validateNonEmptyString('Hello world', 'Comment body') // 'Hello world' (trimmed)
// validateNonEmptyString('', 'Comment body') → throws "Comment body cannot be empty"

// Validate patterns
const username = validatePattern('user123', /^[a-z0-9]+$/, 'Username', 'alphanumeric only')
// validatePattern('user-123', /^[a-z0-9]+$/, 'Username') → throws "Username must be alphanumeric only"

Errors Module

Throw structured errors with machine-readable codes and render them consistently for agents to parse. Built on @vercel/error, so every error can answer what happened (message), why (reason), what could help (hint), how to fix it (fix), and where to learn more (link):

import {
  CliError,
  exitCodeForError,
  toErrorOutput,
  VALIDATION_ERROR,
} from '@pleaseai/cli-toolkit/errors'
import { outputData } from '@pleaseai/cli-toolkit/output'

try {
  if (!issue) {
    throw new CliError('Issue #999 not found', {
      code: 'NOT_FOUND',
      fix: 'Run `gh-please issue list` to see open issues',
    })
  }
}
catch (error) {
  // Render a stable { error, code, reason?, hint?, fix?, link? } payload (JSON or TOON)
  outputData(toErrorOutput(error), 'toon')
  // Map to a process exit code (2 for validation errors, otherwise 1)
  process.exitCode = exitCodeForError(error)
}

For tools with many error sites, createErrors (re-exported from @vercel/error) builds a factory that injects a shared scope and derives docs links from error codes:

import { CliError, createErrors } from '@pleaseai/cli-toolkit/errors'

const errors = createErrors({
  scope: 'gh-please',
  ErrorClass: CliError,
  docsBaseUrl: 'https://example.com/docs/errors',
})

errors.raise('Issue #999 not found', { code: 'NOT_FOUND' })
// throws a CliError with link https://example.com/docs/errors/NOT_FOUND

The built-in validators already throw CliError tagged with VALIDATION_ERROR (exit code 2), so wrapping a command in the try/catch above gives you consistent structured errors and exit codes for free.

Detecting a CliError: use the isCliError(error) type guard instead of a bare error instanceof CliError. Each subpath (/errors, /output, /validation) bundles its own copy of CliError, so a direct instanceof check can silently return false for an error thrown by a different subpath (or by a second copy of the package in the dependency tree). isCliError, toErrorOutput, and exitCodeForError already handle this for you.

import { isCliError } from '@pleaseai/cli-toolkit/errors'

if (isCliError(error)) {
  // error.code / error.reason / error.hint / error.fix are safe to read here
}

The collapseHomeDirectory helper (in the output module) keeps paths portable in structured output:

import { collapseHomeDirectory } from '@pleaseai/cli-toolkit/output'

collapseHomeDirectory('/Users/alice/project/bin', '/Users/alice') // '~/project/bin'
// The home directory defaults to os.homedir() when omitted.

API Reference

Output Module

  • outputData(data, format, fields?) - Output data in JSON or TOON format
  • outputJson(data) - Output as JSON
  • outputToon(data) - Output as TOON (58.9% token savings)
  • parseFields(fieldString) - Parse comma-separated field list
  • filterFields(data, fields) - Filter object/array to specified fields
  • isStructuredOutput(options) - Check if structured output is requested
  • validateFormat(format) - Validate output format
  • collapseHomeDirectory(path, homeDir?) - Replace a leading home-dir prefix with ~

i18n Module

  • detectSystemLanguage() - Detect language from environment variables
  • I18nManager - Message registry and manager
    • register(domain, lang, messages) - Register message dictionary
    • get(domain, lang) - Get messages for domain and language
    • has(domain) - Check if domain is registered
    • hasLanguage(domain, lang) - Check if language is registered
  • getCommonMessages(lang) - Get built-in common messages

Progress Module

  • createProgressIndicator() - Create console-based progress indicator
  • formatSuccess(message) - Format success message with ✅
  • formatError(message) - Format error message with ❌
  • formatInfo(message) - Format info message with ℹ️
  • formatWarning(message) - Format warning message with ⚠️
  • formatProgress(message) - Format progress message with ⏳

Validation Module

Numeric:

  • validatePositiveInteger(value, fieldName?) - Validate positive integer string
  • validateNumericId(value, fieldName?) - Validate positive integer (string or number)
  • validateRange(value, min, max, fieldName?) - Validate number within range

Text:

  • validateNonEmptyString(value, fieldName?) - Validate non-empty string
  • validateMaxLength(value, maxLength, fieldName?) - Validate string length
  • validateString(value, maxLength, fieldName?) - Validate non-empty + max length
  • validatePattern(value, pattern, fieldName?, description?) - Validate regex pattern

Validators throw CliError with code VALIDATION_ERROR (a subclass of Error, so existing try/catch keeps working).

Errors Module

  • CliError - Structured error extending VercelError; carries code (always set) plus reason, hint, fix, link, metadata, cause, ...
  • isCliError(error) - Type guard for CliError, robust across subpath/version boundaries (prefer over instanceof)
  • exitCodeForError(error) - Map an error to a process exit code (2 for validation, else 1)
  • errorOutput(message, code, details?) - Build a { error, code, reason?, hint?, fix?, link? } payload
  • toErrorOutput(error) - Convert any thrown value into a structured payload
  • VALIDATION_ERROR / UNKNOWN_ERROR - Built-in error code constants
  • Re-exported from @vercel/error: VercelError, createErrors, isVercelError, isError, isErrorLike, hasCode, getMessage, getRootCause

TypeScript Support

All modules are fully typed with TypeScript. Import types directly:

import type { Language, OutputFormat, ProgressIndicator } from '@pleaseai/cli-toolkit'

Testing

bun test
bun test --coverage

License

MIT

Author

PleaseAI

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors