Plugin Developer Guide

Authentication

How plugins receive credentials and connect to external services.

How credentials reach your plugin

Plugins run inside a sandbox. They cannot read process.env or access the host machine's environment directly. The only way your plugin receives credentials is through the env object that Kazibee passes to your main(env) function.

This means you need to tell Kazibee what variables your plugin needs. You do this by creating a permissions.json file and pointing to it from package.json. At install time, Kazibee reads this file and asks the user to grant each variable. At runtime, only granted variables are injected into your env.

The end-to-end flow

1.

You declare required env vars in permissions.json

2.

User installs or links your plugin — Kazibee prompts them to grant each variable from a source (LOCAL, ENV, GLOBAL, or SYSTEM)

3.

User provides the actual values (via kazibee env, a login command, or a setup script)

4.

At runtime, Kazibee resolves only granted variables from their granted sources and passes them as the env object to main(env)

5.

Your plugin uses env to create authenticated API clients

Pattern 1: API Key

The simplest pattern. The external service gives you a static API key (e.g. from a settings page). The user sets this key once, and your plugin reads it from env every time it runs.

Step 1 — Declare the variable

Create permissions.json in your plugin root. Each key in the "env" object is a variable name your plugin code will see. The value tells Kazibee which source key to look up (see the Permissions page for advanced source options).

my-plugin/permissions.json

{

"env": {

"CLOCKIFY_API_KEY": "CLOCKIFY_API_KEY"

}

}

Step 2 — Point to it from package.json

my-plugin/package.json

{

"name": "clockify",

"type": "module",

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

"kazibee": {

"permissions": "./permissions.json"

}

}

Step 3 — Define a typed Env interface

Create an auth.ts file that defines an Env interface matching your permissions.json keys. This gives you type safety so you catch missing or misspelled keys at compile time. It also provides a single place to validate credentials and create the authenticated client.

my-plugin/src/auth.ts

export interface Env {

CLOCKIFY_API_KEY: string;

}

 

export interface AuthConfig {

apiKey: string;

}

 

export function getAuthConfig(env: Env): AuthConfig {

if (!env.CLOCKIFY_API_KEY)

throw new Error('Missing CLOCKIFY_API_KEY');

return { apiKey: env.CLOCKIFY_API_KEY };

}

Step 4 — Use it in index.ts

Your main(env) receives the sandboxed env, creates an authenticated config, and passes it to your client code.

my-plugin/src/index.ts

import { getAuthConfig, type Env } from './auth';

import { createClient } from './client';

 

export default function main(env: Env) {

const config = getAuthConfig(env);

return createClient(config);

}

User experience

After installing, the user sets the key with kazibee env. From that point on, every execution of the plugin has access to the key.

kazibee-terminal

dev@local:~/workspace $ kazibee link clockify ./clockify

# Kazibee prompts: "Grant CLOCKIFY_API_KEY? [LOCAL/ENV/GLOBAL/SYSTEM]"

dev@local:~/workspace $ kazibee env clockify --set CLOCKIFY_API_KEY=abc123

dev@local:~/workspace $ echo 'return await tools["clockify"].getUser()' | kazibee exec

Pattern 2: OAuth2

For services that use OAuth2 (like Google, Microsoft, etc.), you need a browser-based consent flow. The user runs a login command that opens the browser, handles the redirect, exchanges the authorization code for tokens, and returns the credentials. Because the login command returns a Record<string, string>, Kazibee automatically persists the returned values as env vars for the plugin.

Step 1 — Declare the variables

OAuth2 typically needs three variables: the client credentials (CLIENT_ID, CLIENT_SECRET) and the refresh token obtained during the login flow.

my-plugin/permissions.json

{

"env": {

"CLIENT_ID": "CLIENT_ID",

"CLIENT_SECRET": "CLIENT_SECRET",

"REFRESH_TOKEN": "REFRESH_TOKEN"

}

}

Step 2 — Write the auth helper

The auth.ts file defines the Env interface and creates an authenticated OAuth2 client using the credentials from env.

my-plugin/src/auth.ts

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

 

export interface Env {

CLIENT_ID: string;

CLIENT_SECRET: string;

REFRESH_TOKEN: string;

}

 

export function createAuthClient(env: Env) {

const oauth2 = new auth.OAuth2(

env.CLIENT_ID,

env.CLIENT_SECRET

);

oauth2.setCredentials({

refresh_token: env.REFRESH_TOKEN

});

return oauth2;

}

Step 3 — Write the login command

The login command runs outside the sandbox (it's a CLI command, not a tool method). It starts a local HTTP server, opens the browser to the OAuth consent screen, waits for the redirect callback with the auth code, exchanges it for tokens, and returns the credentials as Record<string, string>. Kazibee sees that return type and automatically stores the values as env vars for the plugin.

my-plugin/src/command.ts

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

import { createServer } from 'node:http';

 

const SCOPES = ['https://...gmail.modify'];

const REDIRECT_PORT = 3848;

const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`;

 

export async function login(

env: Record<string, string>,

...args: string[]

) {

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

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

 

// 1. Build consent URL

const oauth2 = new auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);

const authUrl = oauth2.generateAuthUrl({

access_type: 'offline',

scope: SCOPES,

prompt: 'consent'

});

 

// 2. Start local server, open browser

const code = await waitForAuthCode(authUrl);

 

// 3. Exchange code for tokens

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

 

// 4. Return credentials — Kazibee auto-stores these

return {

CLIENT_ID,

CLIENT_SECRET,

REFRESH_TOKEN: tokens.refresh_token

};

}

Step 4 — Wire it together in index.ts

my-plugin/src/index.ts

import { createAuthClient, type Env } from './auth';

import { createGmailClient } from './gmail-client';

 

export default function main(env: Env) {

const auth = createAuthClient(env);

return createGmailClient(auth);

}

User experience

The user sets client credentials first (often via a setup script or kazibee env), then runs the login command. The browser opens, the user grants consent, and the refresh token is saved automatically. The plugin is ready to use.

kazibee-terminal

dev@local:~/workspace $ kazibee env gmail --set CLIENT_ID=xxx CLIENT_SECRET=yyy

dev@local:~/workspace $ kazibee gmail login

# Browser opens → user authorizes → tokens saved

dev@local:~/workspace $ echo 'return await tools["gmail"].listMessages("in:inbox", 5)' | kazibee exec

Key concepts

Sandbox isolation

Plugins never see process.env. They only receive the specific variables declared in permissions.json and granted by the user. Unganted variables are not injected.

Auto-persistence

When a CLI command (like login) returns Record<string, string>, Kazibee stores each key-value pair as an env var for that plugin. No manual kazibee env --set needed.

Commands run outside the sandbox

Functions in command.ts (like login) run in the host process with full access. This is why they can open browsers, start HTTP servers, and interact with the terminal. The sandbox only applies to the tool API methods defined by main().

The Env interface

Define an Env interface in auth.ts that matches your permissions.json keys. Export it from index.ts so Kazibee can use it for type generation.

Kazibee

Bounded AI execution. Free and open source.