Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Programmatic Gateway

When kimaki runs without a TTY (piped stdin and stdout, no terminal), it prints structured events on stdout in the SSE (Server-Sent Events) wire format. A host process can parse them reliably with any SSE parser.
data: {"type":"install_url","url":"https://kimaki.dev/discord-install?clientId=...&clientSecret=..."}

Use case

You are building a cloud platform that provisions kimaki instances for users. Each user gets their own kimaki process running on a VPS. Your platform needs to:
  1. Start kimaki for a new user
  2. Get the Discord install URL to show in your web UI
  3. Know when the user has authorized the bot
  4. Know when the bot is fully ready and listening for messages
  5. Redirect the user to your own page after OAuth (custom callback URL)

Event lifecycle

When kimaki starts in gateway mode without a TTY, it emits these events in order:
install_url ──▶ authorized ──▶ ready ▲ │ (user opens the URL and authorizes)
EventPayloadDescription
install_url{ type, url }Discord install URL to send to the user
authorized{ type, guild_id }User authorized the bot in a server
ready{ type, app_id, guild_ids }Bot is connected and first-run onboarding is done
error{ type, message, install_url? }Something went wrong. install_url is set when the user must open the URL again
When saved credentials are already installed, kimaki skips install_url and authorized and only emits ready.
Without a TTY, kimaki always uses gateway mode for a new install, even without --gateway. If the bot is in several servers, pass --guild <id>, otherwise startup fails with an error event.

SSE wire format

Each event is a single line prefixed with data: and followed by an empty line:
data: {"type":"ready","app_id":"1477605701202481173","guild_ids":["123..."]}\n\n
Kimaki writes its logs to stderr, so stdout only has these events. The data: prefix still lets a standard SSE parser ignore any other line.

Parsing events with eventsource-parser

Install the parser:
npm install eventsource-parser
Parse events from the child process stdout:
import { spawn } from 'node:child_process' import { createParser } from 'eventsource-parser' const child = spawn('kimaki', [ '--gateway', '--data-dir', '/data/user-abc', '--gateway-callback-url', 'https://your-platform.com/oauth-done', ], { env: { ...process.env, // Unique port per instance; a busy port makes startup fail KIMAKI_LOCK_PORT: '31200', }, stdio: ['ignore', 'pipe', 'pipe'], }) const parser = createParser({ onEvent(sseEvent) { const event = JSON.parse(sseEvent.data) switch (event.type) { case 'install_url': { // Send this URL to your user (email, web UI, etc.) console.log('Install URL:', event.url) break } case 'authorized': { // User authorized the bot; you now have their guild_id console.log('Guild ID:', event.guild_id) break } case 'ready': { // Bot is connected and listening for Discord messages console.log('App ID:', event.app_id) console.log('Guild IDs:', event.guild_ids) break } case 'error': { console.error('Error:', event.message) break } } }, }) // Feed raw stdout into the parser; it extracts data: lines child.stdout.on('data', (chunk) => { parser.feed(chunk.toString()) })

Get the install URL without starting the bot

--install-url prints the install URL as plain text and exits. With --gateway, it creates and saves gateway credentials first when the data directory has none.
kimaki --gateway --install-url --data-dir /data/user-abc
When you later start kimaki --gateway with the same data directory, it reuses these credentials and waits for the install to finish.
From a data directory that already has credentials, kimaki bot install-url prints the same URL.

CLI flags

FlagRequiredDescription
--gatewayyesUse the shared Kimaki gateway bot
--data-dir <path>recommendedIsolated data directory per user instance
--gateway-callback-url <url>optionalRedirect the user here after the install instead of the default kimaki page
--install-urloptionalPrint the install URL and exit
--guild <id>when the bot is in several serversServer to onboard
--machine-name <name>optionalName of the Discord category, Kimaki <name> (default: hostname)
--restart-onboardingoptionalRun the install flow again, even with saved credentials
--no-analyticsoptionalDisable anonymous usage analytics
Kimaki needs OpenCode 2 on the machine. Without a TTY, when no OpenCode is installed, kimaki installs OpenCode 2 with curl -fsSL https://opencode.ai/v2/install | bash before it emits any event. Install it in your image ahead of time to make startup faster.

Custom callback URL

Pass --gateway-callback-url to redirect the user to your own page after they authorize the bot. The URL must use https, or http on localhost or 127.0.0.1.
kimaki --gateway --gateway-callback-url https://your-platform.com/setup-done
The install URL in the install_url event includes the callback:
https://kimaki.dev/discord-install?clientId=...&clientSecret=...&kimakiCallbackUrl=https%3A%2F%2Fyour-platform.com%2Fsetup-done
After the user authorizes, Discord redirects to kimaki.dev, which then redirects to your callback URL with guild_id and client_id query parameters appended. Your platform knows which server was authorized for which instance.

Running multiple instances

Each kimaki process needs a unique KIMAKI_LOCK_PORT (default 29988). If the port is busy, the new process fails to start with port is in use. It never stops the process that already runs.
const lockPort = 31100 + userIndex const child = spawn('kimaki', ['--gateway', '--data-dir', userDataDir], { env: { ...process.env, KIMAKI_LOCK_PORT: String(lockPort) }, stdio: ['ignore', 'pipe', 'pipe'], })
Also use a unique --data-dir per user so their SQLite databases, logs, and credentials are isolated. CLI subcommands for an instance need the same KIMAKI_LOCK_PORT and --data-dir.
All instances on one machine use the same OpenCode 2 service of the machine user. To isolate users from each other, run each instance as its own OS user or in its own container.