# Build

Extend nikcli with plugins, drive it from your own program over HTTP, or embed the whole agent in-process — without forking it.

## Three ways in

nikcli is a CLI, a server and a terminal UI that all sit on the same [typed HTTP API](/docs/server-api). That means there is no privileged inside: the TUI reaches the session engine through the same endpoints your code can. Which path you take depends on where your code should run.

[

Plugins

### Extend

Your code runs _inside_ nikcli. Add tools, intercept the model call, register a provider, gate permissions, extend the TUI.

Open reference →

](/docs/guides/plugins)[

Client SDK

### Connect

Your program runs outside and drives a nikcli server over HTTP. Scripts, bots, CI jobs, alternative interfaces.

Open reference →

](/docs/build/client)[

In-process

### Embed

nikcli runs inside _your_ process, with no listener and no socket. For products that ship the agent rather than talk to one.

Open reference →

](/docs/build/sdk)

Prefer a walkthrough?

[The guides](/docs/guides) take each of these paths end to end with working code — [Writing a plugin](/docs/guides/plugins), [Plugin recipes](/docs/guides/plugin-recipes), [Using the API](/docs/guides/api) and [Streaming a response](/docs/guides/streaming).

None of these need a fork

Every extension point below is a published contract. If you find yourself patching `packages/nikcli` to do something on this page, that is a gap worth reporting rather than a fork worth maintaining.

## Extend — plugins

A plugin is a module nikcli loads at startup. It receives a client bound to the running instance and returns a set of hooks. `@nikcli-ai/plugin` is the contract package; it carries only types and the `tool()` helper, so it adds nothing to your runtime.

my-plugin.ts

ts 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

```
import type { Plugin } from "@nikcli-ai/plugin"
import { tool } from "@nikcli-ai/plugin/tool"

export const MyPlugin: Plugin = async ({ client, directory, $ }) => ({
  tool: {
    deploy: tool({
      description: "Deploy the current branch to staging",
      args: { branch: tool.schema.string().describe("branch to deploy") },
      async execute(args, ctx) {
        ctx.metadata({ title: `Deploying ${args.branch}` })
        const out = await $`./script/deploy.sh ${args.branch}`.text()
        return { title: "Deployed", output: out }
      },
    }),
  },

  async "tool.execute.before"(input, output) {
    if (input.tool === "bash") output.args.timeout = 120_000
  },
})
```

The hook surface is the interesting part. Beyond `tool`, a plugin can observe every server `event`, rewrite the parameters and headers of the model call (`chat.params`, `chat.headers`, `chat.request`), decide permissions (`permission.ask`), shape the shell environment (`shell.env`, `shell.create.before`), register an `auth` method or a whole `provider`, and transform the messages and system prompt before they reach the model.

A shell hook cannot smuggle past approval

`shell.create.before` runs before the permission check, and the check is performed against the _final_ values the hook wrote. Rewriting a command does not inherit the approval granted for the original one.

TUI feature plugins are a separate runtime with its own contract (`@nikcli-ai/plugin/tui`): they add routes, sidebar panels, dialogs and keybinds to the terminal UI, and local ones hot-reload in place while it runs. See [TUI Feature Plugins](/docs/tui-plugins).

## Connect — the client

`@nikcli-ai/sdk` is a typed client generated from the same OpenAPI document as the [API reference](/docs/api), so every endpoint is a method and contract drift fails generation rather than surfacing as a runtime 404.

Terminal

bash 

```
npm install @nikcli-ai/sdk
```

$ shell · paste into your terminal

ts 

1

2

3

4

5

6

7

8

9

10

11

12

```
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient({
  baseUrl: "http://127.0.0.1:4096",
  directory: "/Users/me/project",
})

const { data: session } = await client.session.create({ title: "release notes" })
await client.session.prompt({
  sessionID: session!.id,
  parts: [{ type: "text", text: "Summarise what changed since v1.2.0" }],
})
```

Full walkthrough — auth, the result envelope, instance selection, streaming and errors — in [Client](/docs/build/client).

## Embed — in-process

The embedded host runs the server's request handler in memory. It opens no listener and does no network I/O, while keeping the same routing, middleware, handlers, codecs and errors as the network client — so code written against the client works unchanged against the embedded host.

ts 

1

2

3

4

5

6

7

```
import { NikCli } from "@nikcli-ai/sdk-next"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const nikcli = yield* NikCli.create({ directory: process.cwd() })
  return yield* Effect.promise(() => nikcli.session.list())
})
```

It also lets you register tools directly, which the HTTP path cannot do — a tool is executable code and does not travel over a wire. See [Embedded SDK](/docs/build/sdk).

## Which one do I want?

| If you want to… | Path | Package |
| --- | --- | --- |
| Give the agent a new tool | Plugin | `@nikcli-ai/plugin` |
| Change how the model is called | Plugin | `@nikcli-ai/plugin` |
| Add a panel or route to the TUI | TUI plugin | `@nikcli-ai/plugin/tui` |
| Script nikcli from CI or a bot | Client | `@nikcli-ai/sdk` |
| Build a different interface on top | Client | `@nikcli-ai/sdk` |
| Ship the agent inside your own app | Embedded | `@nikcli-ai/sdk-next` |
| Generate a client for another language | OpenAPI | [the document](/docs/api/openapi.json) |

Plugins and the client are not exclusive

A plugin is handed a `client` bound to the instance it is running in — the same `NikcliClient` the Connect path uses. Reaching for the API from inside a plugin is the normal thing to do, not a workaround.

## What people build

Plugin

### Domain tools

Wrap a deploy script, a migration runner or an internal API so the agent can call it with arguments it cannot get wrong.

Plugin

### Policy

Auto-deny a permission class, force a timeout, inject credentials into the shell environment, or strip secrets before a request leaves the process.

Plugin

### Custom providers

Register a model provider with its own auth flow and model list — including an internal gateway that is not in the public catalogue.

Client

### Automation

Open a session from a webhook, prompt it, wait on the event feed, and post the diff back to a pull request.

Client

### Alternative interfaces

The web app, the desktop app and the mobile app are all just clients of these endpoints. Yours can be too.

Embedded

### Embedded agents

A product that runs sessions on its own, with your tools registered and no server for a user to start.

## Source anchors

| Need | Where |
| --- | --- |
| Plugin types, hooks, `tool()` | `packages/plugin/src/index.ts`, `src/tool.ts` |
| TUI plugin contract | `packages/plugin/src/tui.ts`, `src/v2/` |
| Plugin loading and hook dispatch | `packages/nikcli/src/plugin/index.ts` |
| Generated HTTP client | `packages/sdk/js/src/httpapi/` |
| Client generation | `packages/nikcli/script/generate-httpapi-clients.ts` |
| Embedded host | `packages/sdk-next/src/nikcli.ts` |
| Tool registry | `packages/nikcli/src/tool/registry.ts` |

## Related

-   [Plugins & Skills Installing, configuring and writing server plugins.](/docs/plugins)
-   [TUI Feature Plugins The terminal UI plugin runtime, route by route.](/docs/tui-plugins)
-   [API Reference Every endpoint the client wraps.](/docs/api)
-   [Tools The built-in tools your own sit beside.](/docs/tools)

---

Source: https://nikcli-ai.dev/docs/build
