Plugin Developer Guide

CLI Commands

User-facing commands that run outside the sandbox — for tasks like login flows, setup scripts, and diagnostics.

Quick reminder: Two execution contexts

Your plugin has two types of code. The main() function in index.ts returns methods that run inside a sandbox — the AI agent calls these. CLI commands in command.ts run outside the sandbox, in the host process, with full access to the terminal, browser, network, and filesystem. This is where you put things like OAuth login flows, setup wizards, or any task that requires user interaction.

Why commands exist

Some things simply cannot happen inside a sandbox. Opening a browser for OAuth consent, starting an HTTP server to receive a callback, prompting the user for input, or printing diagnostic information — these all need access to the host machine. CLI commands give your plugin a way to do these things.

The most common use case is a login command that authenticates the user and stores credentials so the sandboxed tool methods can use them later.

How it works

1.

Create a command.ts file and export named functions

2.

Point to it in package.json with "command": "./src/command.ts"

3.

Each exported function becomes a CLI command: export function login becomes kazibee my-plugin login

4.

If the function returns Record<string, string>, Kazibee automatically stores those values as env vars for the plugin

Setting up package.json

Add a "command" field to your package.json pointing to the command module. This tells Kazibee where to find your CLI commands.

my-plugin/package.json

{

"name": "my-plugin",

"type": "module",

"main": "./src/index.ts",

"command": "./src/command.ts",

"kazibee": {

"permissions": "./permissions.json"

}

}

The command function signature

Every command function receives two things: the plugin's current env vars, and any extra arguments the user typed after the command name.

Function signature

export async function commandName(

env: Record<string, string>, // plugin's current env vars

...args: string[] // extra CLI arguments

): Promise<Record<string, string> | void>

env

The plugin's currently stored environment variables. For example, if the user already set CLIENT_ID via kazibee env, it will be in env.CLIENT_ID. This lets commands build on previously stored credentials.

...args

Any additional tokens the user typed after the command name, forwarded exactly as the shell tokenized them. Kazibee does not parse flags — your command is responsible for interpreting arguments.

Return value

If you return Record<string, string>, Kazibee automatically stores each key-value pair as an env var for the plugin. If you return void, nothing is stored. This is how login commands persist tokens without requiring a separate kazibee env --set step.

How arguments are forwarded

Kazibee splits the command line into three parts: the plugin name, the command name, and everything else. Everything else becomes args.

kazibee-terminal

dev@local:~/workspace $ kazibee gmail login my-client-id my-client-secret

# ^tool ^cmd ^args[0] ^args[1]

In this example, Kazibee calls login(env, "my-client-id", "my-client-secret"). Your function receives the extra arguments as the rest parameters. Kazibee does not parse --flags for you — tokens are forwarded exactly as-is.

Complete example: OAuth login command

This is a simplified version of the real Gmail plugin's login command. It opens a browser for OAuth consent, receives the callback, exchanges the code for tokens, and returns the credentials for auto-persistence.

my-plugin/src/command.ts

import { auth } from '@googleapis/gmail';

import { createServer } from 'node:http';

 

export interface LoginResult {

CLIENT_ID: string;

CLIENT_SECRET: string;

REFRESH_TOKEN: string;

}

 

export async function login(

env: Record<string, string>,

...args: string[]

): Promise<LoginResult> {

// CLI args override stored env values

const CLIENT_ID = args[0] || env.CLIENT_ID;

const CLIENT_SECRET = args[1] || env.CLIENT_SECRET;

 

// Build OAuth consent URL

const oauth2 = new auth.OAuth2(CLIENT_ID, CLIENT_SECRET, 'http://localhost:3848');

const authUrl = oauth2.generateAuthUrl({

access_type: 'offline',

scope: ['https://...gmail.modify'],

prompt: 'consent'

});

 

// Open browser and wait for callback

const code = await waitForAuthCode(authUrl);

const { tokens } = await oauth2.getToken(code);

 

// Return credentials — Kazibee auto-stores these

return {

CLIENT_ID,

CLIENT_SECRET,

REFRESH_TOKEN: tokens.refresh_token

};

}

Because the function returns { CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN }, Kazibee stores all three as env vars. The next time the sandboxed main(env) runs, env.REFRESH_TOKEN will have the token from the login flow.

What the user sees

After installing the plugin, the user runs the login command. The browser opens, they authorize, and the credentials are stored automatically.

kazibee-terminal

dev@local:~/workspace $ kazibee gmail login

# Browser opens → user authorizes → tokens saved

 

# Or pass client credentials directly as arguments:

dev@local:~/workspace $ kazibee gmail login my-client-id my-client-secret

Non-login example: A simple API key command

Not every command needs a complex OAuth flow. For services that use static API keys, you might not even need a login command. But you can still create commands for other tasks. Here is a command that validates an API key is working.

my-plugin/src/command.ts

export async function status(

env: Record<string, string>

): Promise<void> {

if (!env.API_KEY) {

console.log('No API key set. Run: kazibee env my-plugin --set API_KEY=...');

return;

}

 

const res = await fetch('https://api.example.com/me', {

headers: { Authorization: `Bearer ${env.API_KEY}` }

});

 

if (res.ok) {

const user = await res.json();

console.log(`Connected as ${user.name}`);

} else {

console.log(`API key invalid (HTTP ${res.status})`);

}

}

kazibee-terminal

dev@local:~/workspace $ kazibee my-plugin status

Connected as Jane Doe

This command returns void, so Kazibee does not try to store anything. It just prints output for the user. Use console.log in commands when you want the user to see something — return values are not auto-printed.

Key concepts

Commands run outside the sandbox

Functions in command.ts run in the host process with full access to the machine. They can open browsers, start HTTP servers, read files, and interact with the terminal. This is the opposite of main() tool methods, which run in the sandbox.

Auto-persistence

Return Record<string, string> and Kazibee stores each key-value pair as an env var for the plugin. Return void and nothing is stored. This is how login commands save tokens without requiring a separate kazibee env --set step.

Output is not auto-printed

Kazibee does not auto-print return values from commands. To show output to the user, use console.log() inside your command. Returning a Record<string, string> triggers env persistence only — it will not be displayed.

Missing commands show available ones

If a user runs kazibee my-plugin without a command name, Kazibee prints the list of available commands (i.e. the exported function names from your command.ts). If the user types an unknown command name, they get an error with the list of valid ones.

Kazibee

Bounded AI execution. Free and open source.