KB LabsDocs

Quickstart

Last updated April 7, 2026


A minimal KB Labs plugin built with the SDK, in under five minutes.

The goal of this page is to get a plugin running end-to-end. Nothing fancy — one CLI command that greets the user and optionally uses the LLM if available. You'll learn the package layout, the manifest shape, the handler signature, and how to link and run.

If you want to go deeper after this, read Plugins → Overview and SDK → Commands.

Prerequisites

  • A working KB Labs install (workspace with pnpm kb --help functional).
  • Node 20+.
  • pnpm.

Step 1 — Create the package

Bash
mkdir -p packages/hello-plugin/src/cli/commands
cd packages/hello-plugin
pnpm init

Edit package.json:

JSON
{
  "name": "@example/hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "type-check": "tsc --noEmit"
  },
  "dependencies": {
    "@kb-labs/sdk": "^1.5.0"
  },
  "devDependencies": {
    "tsup": "^8.0.0",
    "typescript": "^5.5.0"
  }
}

The only runtime dependency is @kb-labs/sdk. That's the whole rule — plugins see the SDK and nothing else.

Step 2 — Write the handler

src/cli/commands/hello.ts:

TypeScript
import { defineCommand, useLLM, useLogger, type CLIInput } from '@kb-labs/sdk';
 
interface HelloFlags {
  name?: string;
  ai?: boolean;
}
 
interface HelloResult {
  greeting: string;
  source: 'deterministic' | 'llm';
}
 
export default defineCommand<unknown, CLIInput<HelloFlags>, HelloResult>({
  id: 'hello:greet',
  description: 'Say hi to someone',
  handler: {
    async execute(ctx, input) {
      const logger = useLogger();
      const name = input.flags.name ?? 'world';
 
      logger.info('hello:greet invoked', { name, ai: input.flags.ai });
 
      if (input.flags.ai) {
        const llm = useLLM();
        if (llm) {
          const response = await llm.complete(
            `Write a one-sentence friendly greeting for ${name}.`,
          );
          return {
            exitCode: 0,
            result: { greeting: response.content.trim(), source: 'llm' },
          };
        }
      }
 
      return {
        exitCode: 0,
        result: { greeting: `Hello, ${name}!`, source: 'deterministic' },
      };
    },
  },
});

A few things to notice:

  • Every import comes from @kb-labs/sdk. defineCommand, useLLM, useLogger, CLIInput — all one import.
  • useLLM() can return undefined. The handler degrades gracefully: if there's no LLM, it uses a canned greeting.
  • useLogger() always returns a logger. No null check needed.
  • The return value is a CommandResult. exitCode: 0 means success; result is the structured output the CLI will render.

Step 3 — Write the manifest

src/manifest.ts:

TypeScript
import {
  defineFlags,
  combinePermissions,
  minimalPreset,
} from '@kb-labs/sdk';
 
const helloFlags = defineFlags({
  name: {
    type: 'string',
    description: 'Name to greet',
  },
  ai: {
    type: 'boolean',
    description: 'Use LLM to generate the greeting',
    default: false,
  },
});
 
export const manifest = {
  schema: 'kb.plugin/3',
  id: '@example/hello-plugin',
  version: '0.1.0',
  display: {
    name: 'Hello Plugin',
    description: 'A minimal example plugin',
    tags: ['example', 'tutorial'],
  },
  permissions: combinePermissions()
    .with(minimalPreset)
    .withPlatform({ llm: true })
    .build(),
  cli: {
    commands: [
      {
        id: 'hello:greet',
        group: 'hello',
        describe: 'Say hi to someone',
        handler: './cli/commands/hello.js#default',
        flags: helloFlags,
        examples: [
          'pnpm kb hello:greet',
          'pnpm kb hello:greet --name=Alice',
          'pnpm kb hello:greet --name=Alice --ai',
        ],
      },
    ],
  },
} as const;
 
export default manifest;

Key points:

  • schema: 'kb.plugin/3' is mandatory and a literal string.
  • id must be in @scope/name format.
  • handler path is relative to the package root and points at .js (the built output).
  • permissions combines minimalPreset (baseline Node env) with .withPlatform({ llm: true }) to grant LLM access.

Step 4 — Export everything

src/index.ts:

TypeScript
export { default as manifest } from './manifest.js';

The plugin's runtime entry point just re-exports the manifest. The handler files are loaded separately by the runtime via the paths in the manifest.

Step 5 — Set up tsconfig and build config

tsconfig.json:

JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

tsup.config.ts:

TypeScript
import { defineConfig } from 'tsup';
 
export default defineConfig({
  entry: {
    index: 'src/index.ts',
    'cli/commands/hello': 'src/cli/commands/hello.ts',
  },
  format: ['esm'],
  dts: true,
  clean: true,
});

The entry map builds each handler as a separate file, matching the path the manifest points at. Without this, tsup bundles everything into dist/index.js and the manifest's ./cli/commands/hello.js path wouldn't resolve.

Bash
pnpm install
pnpm build
# → dist/index.js, dist/cli/commands/hello.js, dist/manifest.js, plus .d.ts files
 
# Link into your KB Labs workspace:
pnpm kb marketplace link /absolute/path/to/packages/hello-plugin
pnpm kb marketplace clear-cache

The clear-cache step is mandatory — without it, the CLI uses a cached registry and won't see your new plugin.

Step 7 — Run it

Bash
pnpm kb hello:greet
# → { greeting: 'Hello, world!', source: 'deterministic' }
 
pnpm kb hello:greet --name=Alice
# → { greeting: 'Hello, Alice!', source: 'deterministic' }
 
pnpm kb hello:greet --name=Alice --ai
# → { greeting: 'Hi Alice, hope you're having a fantastic day!', source: 'llm' }
# (actual text depends on the configured LLM)

The --help output is built from the manifest:

Bash
pnpm kb hello:greet --help

What just happened

Five files:

  • package.json — declares the package and its SDK dependency.
  • tsconfig.json — standard TypeScript config with ESM and strict mode.
  • tsup.config.ts — build config that produces one file per handler.
  • src/manifest.ts — describes the plugin to the platform.
  • src/cli/commands/hello.ts — the handler itself.

The runtime loaded the manifest, saw the hello:greet command, resolved the handler path, imported it, and called handler.execute(ctx, { flags: { name, ai } }) when you typed pnpm kb hello:greet. The result was rendered by the CLI's presenter layer.

Swap useLLM() for useCache(), useStorage(), or any other hook and you have a different plugin with the same shape. The SDK is intentionally small.

Quickstart — KB Labs Docs