Skip to content

CLI (cac)

The cac adapter wraps a DevframeDefinition in a cac-powered command-line interface. From one entry it spins up an h3 dev server with WebSocket RPC, builds static snapshots, builds SPA bundles, or starts an MCP server.

cac is an optional peer dependency, pulled in only through this adapter — install it alongside devframe to opt into createCac:

sh
npm install devframe cac

Tools that assemble their own command-line shell from the lower-level factories never import this adapter, so they run without cac.

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

const devframe = defineDevframe({
  id: 'my-devframe',
  name: 'My Devframe',
  cli: { distDir: './client/dist' },
  setup(ctx) { /* register docks, RPC, etc. */ },
})

await createCac(devframe).parse()

The devframe/adapters/cli entry (createCli) remains as a deprecated alias for this module — new code should import createCac from devframe/adapters/cac.

Running the resulting binary:

sh
my-devframe                     # dev server at http://localhost:9999/
my-devframe --port 8080
my-devframe build --out-dir dist-static
my-devframe build --out-dir dist-static --base /devframe/
my-devframe mcp                 # stdio MCP server (experimental)

Standalone CLI serves the SPA at / by default. The /__devframe/ prefix is for hosted adapters where devframe mounts alongside an existing app — see Mount paths.

Options

createCac(def, options?) accepts:

OptionDefaultDescription
defaultPort9999 (or def.cli?.port)Port used by the dev command when --port isn't provided.
configureCli(cli: CAC) => void — final hook to add commands/flags at the assembly stage, after the definition's cli.configure runs.
onReady(info: { origin, port, app }) => void | Promise<void> — called once the dev server is listening. Use this to print your own startup banner.

createCac returns a CacHandle:

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

The cli property lets the caller add ad-hoc commands and flags right before parse() when a configureCli callback is inconvenient.

Definition-level cli fields

ts
defineDevframe({
  id: 'my-devframe',
  cli: {
    command: 'my-devframe', // binary name; default: the id
    distDir: './client/dist', // required for dev/build/spa
    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 host; --host overrides
    open: true, // auto-open the browser on dev start
    auth: false, // skip the trust handshake (single-user localhost)
    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`.
  },
})

distDir is the only required field; everything else has sensible defaults. The configure hook runs before the configureCli option passed to createCac, so the final tool author always has the last word on flags.

Headless logging

Devframe leaves startup output to the application. Wire onReady to print your own banner:

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

Structured diagnostics (via nostics) continue to surface through their normal reporters.

Use your own CLI framework

To integrate devframe into an existing commander / yargs program — or to expose a different command structure than createCac's dev / build / mcp triplet — drop down to the peer factories. Same DevframeDefinition, different shell:

Building blockEntryPurpose
createDevServer(def, opts?)devframe/adapters/devh3 + WebSocket RPC + SPA mount
createBuild(def, opts?)devframe/adapters/buildStatic deploy
createMcpServer(def, opts?)devframe/adapters/mcpstdio MCP server
parseCliFlags(schema, raw)devframe/adapters/cacValidate a flag bag against a CliFlagsSchema

See the Standalone CLI guide for a worked commander example.

Released under the MIT License.