CLI

Write your types and clients, print the routes your config serves, and compare an API against a git ref.

@kizunajs/cli is the kizuna command. It reads your config and answers three questions: what should be generated, what does this API serve, and what did this change cost a caller.

pnpm add -D @kizunajs/cli@beta
CommandWhat it does
kizuna generateWrites kizuna.types.ts, every client the config declares, and the snapshot
kizuna routesPrints every route the config serves
kizuna diffCompares the config against the same config at a git ref

--config <path> points any of them at a config other than kizuna.config.ts.

kizuna generate

See Configuration for what it writes and where.

--check writes nothing and exits 1 when anything on disk has fallen behind the config:

Terminal
kizuna generate --check

It covers kizuna.types.ts, every client under clients, and .kizuna/snapshot.yaml, so one command answers whether everything generated matches today's config.

.github/workflows/kizuna-checks.yml
name: Kizuna Checks

on: pull_request

jobs:
    kizuna-checks:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v6

            - uses: pnpm/action-setup@v6
            - uses: actions/setup-node@v6
              with:
                  node-version: 24
                  cache: pnpm

            - run: pnpm install --frozen-lockfile
            - run: pnpm kizuna generate --check

Configuration has what each file is and where it lands.

In a monorepo, build the other packages your config imports before generating. The config reads their built output, and an out-of-date build loads without complaint.

kizuna routes

Every route, with what it asks of a caller, the group it is filed under, and what it publishes:

Terminal
kizuna routes
GET      /users                users.listUsers          auth: public  group: users
POST     /users                users.createUser         auth: user  group: users
DELETE   /users/:id            users.deleteUser         auth: member, requires workspace:delete  group: users  deprecated, use `archiveUser` instead  sunset 2027-01-01
GET      /forecast/:city       weather.getForecast      auth: public  group: weather  tool: weather_get_forecast

The path is the one the server matches. A route's auth reads as public, an identity name, or the identity with the roles and permissions it requires. A route publishing a tool shows the name a model calls it by, resolved the same way the MCP endpoint resolves it.

--json prints the same map as an array, for something other than a person to read:

Terminal
kizuna routes --json
[
    {
        "key": "weather.getForecast",
        "method": "GET",
        "path": "/forecast/:city",
        "auth": "public",
        "groups": ["weather"],
        "tool": "weather_get_forecast"
    }
]

Group problems

Once your config declares groups, kizuna routes also checks that every route is in one, and exits 1 when they are not, so a pipeline can fail on it:

2 group problems:
  getHealth is in no group. Declare it with k.routes.<group>(...).
  The group billing holds no routes. Remove it, or file a route in it.

It reports two things:

  • A route in no group, which would sit at the top of every client beside your groups, and under a "default" heading in the documentation.
  • A group with no route in it or below it.

A route's key always comes from its group, so the two cannot disagree. Hidden routes and plugin routes are left out, because neither reaches the OpenAPI document. With --json, the problems go to stderr and the map stays parseable.

kizuna diff

What a change costs the people already calling your API, against a git ref:

Terminal
kizuna diff --against main
BREAKING POST /users body.organisationId is now required

CHANGED  users.patchUser renamed to users.updateUser
         the generated client method changes name, the HTTP surface does not

CHANGED  GET /users/:id can now answer 410
ADDED    POST /users/:id/archive added

It exits 1 when a change breaks callers, so a pipeline fails on one. --against defaults to main, and --json prints the changes as an array. See Breaking changes for what counts as breaking and how to gate a pull request on it.

kizuna generate writes .kizuna/snapshot.yaml beside your config, and diff compares two of those. The base version is read straight out of git, so nothing is checked out, installed or run for the other side, and a comparison against a commit from a year ago costs the same as one against yesterday.

The head version is whatever is on disk, so run kizuna generate --check first. A snapshot nobody regenerated compares cleanly against a base it no longer describes.

Two snapshots can also be compared directly, with no git and no config:

kizuna diff --from base.yaml --to .kizuna/snapshot.yaml

Scripts

package.json
{
    "scripts": {
        "kizuna:generate": "kizuna generate",
        "kizuna:check": "kizuna generate --check"
    }
}

Reference