中文

Introduction

Build TypeScript CLIs with func that balance developer experience, maintainability, runtime performance, and artifact size.

Updated

func is a TypeScript framework for building modern, extensible command-line applications. It uses classes and decorators to declare commands and provides input definition and validation, command dispatch, error boundaries, service injection, and a range of type-safety features. It is an efficient end-to-end solution for the entire workflow from local development to production builds.

This is not another command-line argument parser. func is designed to solve architectural problems in command-line applications. By providing a complete project model, ecosystem components, a consistent input and runtime model, and an integrated build workflow, it helps projects stay healthy and extensible as they grow.

func also cares about final bundle size and performance. Automatic tree shaking, precompilation, and decoupled packages help applications stay lean and efficient, combining sound architecture with compact output. Whether you are validating a prototype, starting an MVP, or building a large project, you can adopt it without unnecessary overhead.

Why?

The complexity of a CLI project usually rises quickly. Even a modest extension can make the code difficult to understand and maintain, leaving the project trapped in endless patches and redundant defensive programming. A single input may need a name, type, default, validation rules, and an error message; commands may need to share filesystem access, network requests, and common business rules; after release, the application must work across platforms while preserving existing invocation patterns.

If all these responsibilities are packed into argument parsing and command callbacks, changing even one option affects parsing, validation, execution, and error handling at the same time. As features accumulate, every modification requires understanding and verifying an ever-larger portion of the codebase.

Clear boundaries from the first command The same structure applies from the first command; as commands, rules, and dependencies accumulate, responsibilities remain clear. func argument parser + local structure
Maintainability easy to maintain hard to maintain 1 command many commands shared services cross-field rules tests & release project complexity → func argument parser + local structure
This chart compares how two approaches scale as a project grows: func keeps maintenance manageable through clear responsibility boundaries, while relying on an argument parser and local structure becomes harder to maintain as commands, rules, and dependencies accumulate.

func provides ready-made capabilities for common CLI requirements and gives their Handlers strict TypeScript contracts. Projects can therefore support larger and more complex business modules without eroding their structure. Each change can stay focused on business logic instead of reaching into framework internals; developers do not need to understand the machinery underneath.

Overall considerations

Bundle size, cold start, DX, and maintainability Each point is positioned by bundle size, cold-start time, and DX; larger points indicate a higher maintainability proxy.
0 25 50 75 100 125 30 40 50 60 70 25 50 75 100 gzip bundle size (KiB) cold start (ms) DX func 18.2 KiB · 39.7 ms func/parser 4.5 KiB · 34.7 ms Commander 12.2 KiB · 38.9 ms yargs 34.8 KiB · 71.2 ms @oclif/core 101.9 KiB · 71.4 ms cac 5.1 KiB · 35.1 ms
Gzip bundle and startup values come from benchmarks/report.json. The axes use linear scales of 0–125 KiB, 30–70 ms, and DX 0–100. DX and maintainability come from the report authoring evaluation: every criterion is graded from 0 to 4 and converted to 100 with published weights; the report includes each grade and its evidence. These are workload-specific engineering proxies, not universal rankings.

In the current benchmark workload (the sample project used by benchmarks), func has a mean cold start of 37.9 ms and a raw artifact size of 18.7 KiB. Its startup performance is on par with Commander and cac, while its artifact is substantially smaller than those produced by yargs and oclif—placing it in the top tier for both performance and size. In the same report’s proxy evaluation of developer experience and maintainability, func scores 95 and 90 respectively, the highest scores in this comparison.

Category Performance Score
Performance func registers every command in advance through reflection, keeping execution static and complexity low.
Architecture Provides starter templates and a scalable architecture with room for continued growth.
Developer experience Complete type support and editor assistance, backed by thoughtful project architecture and scaffolding.

The following examples implement the same input rules. func keeps types, defaults, and validation on the fields they describe, and the handler receives input only after it has been converted and validated.

Comparing the same command

The same input rules implement the artifact inspect command: a required reference, a platform enum, a numeric retry count, and a JSON flag. Line counts exclude imports and shared business functions.

Commander 34 lines
const artifact = program
  .command('artifact')
  .description('inspect an artifact')

artifact
  .command('inspect')
  .requiredOption('--reference <image>')
  .addOption(
    new Option('--platform <platform>')
      .choices(platforms)
      .default('linux/amd64'),
  )
  .option(
    '--retries <count>',
    'download retries',
    value => {
      const retries = Number(value)
      if (Number.isNaN(retries)) {
        throw new InvalidArgumentError(
          'retries must be a number',
        )
      }
      return retries
    },
    2,
  )
  .option('--json')
  .action(options => {
    if (!isDigestReference(options.reference)) {
      throw new Error(
        'reference must include a sha256 digest',
      )
    }

    inspectArtifact(options.reference, options)
  })
Parser callbacks, defaults, and error branches accumulate on the command chain.
func 18 lines
@Command('artifact')
class ArtifactCommand {
  @Required()
  @ValueValidate(isDigestReference)
  @Value()
  reference?: string

  @Enum(platforms)
  @Value()
  platform: string = 'linux/amd64'

  @Value()
  retries: number = 2

  @Flag()
  json = false

  @Handler('inspect')
  inspect(): void {
    inspectArtifact(this.reference!, this)
  }
}
Default parsers and field validators prepare input; inspect only calls the business function.

Lines of code are not a criterion for evaluating a framework. This comparison simply shows that func remains fast and lightweight while using clearer, more modern, and developer-friendly engineering practices, keeping projects healthy and easy for people to understand, extend, and maintain at a glance.

Agent compatibility

Beyond the qualities above, func also provides excellent Agent compatibility.

Type safety is central to func. Clear, rigorous interfaces define firm boundaries between commands, input, validation, handlers, and services. This allows an Agent to follow project rules and generate safe, reliable, stable, and architecturally sound code. When necessary, you may not need to write any code yourself—guiding the business logic can be enough to produce a high-quality terminal tool.

At the same time, func provides complete testing support and Agent compatibility. An Agent can run commands from a user’s perspective, verify exit behavior and stable output, and add automated acceptance tests for expected behavior, further safeguarding business logic and project quality through automation.

Open the Agent guide. Choose a task that fits the project’s current stage, then let an Agent create the project, improve its structure, or add CLI behavior tests.

Where to start next

Choose a documentation entry based on your current goal: