CLI (cac)

A cac CLI around a DevframeDefinition with dev, build, and mcp commands. cac is an optional peer:

A cac CLI around a DevframeDefinition with dev, build, and mcp commands. cac is an optional peer:

npm install devframe cac

The lower-level factories need no cac.

import { defineDevframe } from 'devframe'
import { createCac } from 'devframe/adapters/cac'

const myDevframe = defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  clientAssets: './client/dist',
  setup(ctx) { /* register docks, RPC, etc. */ },
})

await createCac(myDevframe).parse()
my-tool                         # dev server at http://localhost:9999/
my-tool --port 8080
my-tool build --out-dir dist-static
my-tool build --out-dir dist-static --base /my-tool/
my-tool mcp                     # stdio MCP server

The SPA serves at / standalone, /__my-tool/ hosted (Mount paths).

Options

createCac(def, options?):

OptionDefaultDescription
defaultPort9999 (or def.cli?.port)Dev port if --port unset.
configureCli(cli: CAC) => void — add commands/flags.
onReady(info: { origin, port, app }) => void | Promise<void> — once listening.

Returns a CacHandle:

interface CacHandle {
  cli: CAC // raw cac instance — mutate before calling parse()
  parse: (argv?: string[]) => Promise<void>
}

Definition-level cli fields

defineDevframe({
  id: 'my-tool',
  clientAssets: './client/dist', // built SPA served as the UI
  cli: {
    command: 'mytool', // binary name; default: the id
    port: 7777, // preferred port
    portRange: [7777, 9000], // passed through to get-port-please
    random: false, // passed through to get-port-please
    host: '127.0.0.1', // default bind host; --host overrides
    open: true, // auto-open the browser on dev start; embeds the current OTP so the tab lands authenticated
    configure(cli) { // contribute capability flags/commands
      cli.option('--config <file>', 'Custom config file')
        .option('--no-files', 'Skip file matching')
    },
  },
  setup(ctx, { flags }) {
    // `flags` is the parsed cac flag bag — includes both devframe's
    // built-ins (`--port`, `--host`, `--open`) and anything declared in
    // `cli.configure` or `configureCli`.
  },
})

configure runs before createCac's configureCli.

Headless logging

Print a banner via onReady:

await createCac(myDevframe, {
  onReady({ origin }) {
    console.log(`ESLint Config Inspector ready at ${origin}`)
  },
}).parse()

Use your own CLI framework

Peer factories for a commander/yargs program:

Building blockEntryPurpose
createDevServer()devframe/adapters/devh3 + WebSocket RPC + SPA mount
createBuild()devframe/adapters/buildStatic deploy
createMcpServer()devframe/adapters/mcpstdio MCP server
parseCliFlags(schema, raw)devframe/adapters/cacValidate flags (CliFlagsSchema)

See the Standalone CLI guide.