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
You declare required env vars in permissions.json
User installs or links your plugin — Kazibee prompts them to grant each variable from a source (LOCAL, ENV, GLOBAL, or SYSTEM)
User provides the actual values (via kazibee env, a login command, or a setup script)
At runtime, Kazibee resolves only granted variables from their granted sources and passes them as the env object to main(env)
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).
{
"env": {
"CLOCKIFY_API_KEY": "CLOCKIFY_API_KEY"
}
}
Step 2 — Point to it from 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.
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.
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.
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.
{
"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.
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.
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
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.
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.