# @openbkn/bkn-sdk
🌐 **Website:** [openbkn.ai](https://openbkn.ai)
Unified TypeScript SDK + CLI for the **BKN** (Business Knowledge Network) platform.
One toolkit, two surfaces: the importable SDK and the `openbkn` CLI — both backed
by the same domain logic. A single unified CLI for the BKN platform, with the
operator surface nested under `openbkn admin`. Backend-only; no web UI.
> Status: every command/subcommand is implemented and self-consistent, with a
> full-depth grouped `--help` for every node. Reads + writes across all domains
> are validated against a live platform; a few paths are environment-gated on the
> test cluster (EACP `change-password`/`audit` upstream, physical-catalog
> `create-from-*`, populated trace index) — see
> `docs/exec-plans/tech-debt-tracker.md`.
## Install
```bash
npm install -g @openbkn/bkn-sdk # CLI: `openbkn`
# or as a library
npm install @openbkn/bkn-sdk
```
Requires Node ≥ 22.19.0.
## CLI
```bash
# Log in — attach a token, or OAuth (headless password / browser PKCE)
openbkn auth login https://your-platform --token "$TOKEN"
openbkn auth login https://your-platform -u <user> -p <password> # headless OAuth
# Knowledge networks
openbkn bkn list
openbkn bkn get <kn-id> --stats
openbkn bkn search <kn-id> "customer churn"
openbkn bkn object-type list <kn-id>
# Data platform
openbkn resource list --type table
openbkn vega catalog list
openbkn vega index-capabilities --json # available fulltext analyzers and check time
openbkn model llm list
# Operator (nested)
openbkn admin org list
openbkn admin role list
# Raw passthrough to any endpoint
openbkn call /api/bkn-backend/v1/knowledge-networks
# Return an MCP tool result with its validated BKN Trace receipt
openbkn --json context tool-call <kn-id> <tool-name> --args '{"k":"v"}' --receipt
# Global flags: --base-url, --token, --user, --json/--compact, -k/--insecure,
# --conversation-id/--interaction-id (BKN Trace correlation; also BKN_CONVERSATION_ID/BKN_INTERACTION_ID)
# --new-conversation (ignore the remembered conversation for this command)
openbkn --help # grouped command map
```
On a deploy with the managed lifecycle, commands continue the conversation the previous one
opened, so work spanning several commands lands in one thread. `openbkn context conversation` shows which
is in force and where it came from; `--forget` drops it. An explicit `--token` /
`BKN_TOKEN` opts out (identity there is the token, not the stored user) — use
`BKN_CONVERSATION_ID` to thread such a script's commands together.
`--interaction-id` / `BKN_INTERACTION_ID` can be supplied on its own for
existing scripts. The SDK follows the automatic lifecycle handshake, but does
not forward that orphan ID as a business header once it has opened an
authoritative context; it never invents a matching Conversation ID locally. Use
the returned receipt to establish the authoritative business context.
`context tool-call --receipt` requires `--json` or `--compact` and emits
`{ value, bkn_receipt }` (`--compact` is the one-line form). It is opt-in; the default command output and SDK
`context.toolCall()` remain business-value-only. The receipt is validated for this
call, not a bearer credential; confirm access under the current identity with
`openbkn trace receipts get <receipt-id>` when authorization evidence is required.
Check `bkn_receipt.receipt_status`, not whether `value` is `null`: a `pending`
receipt has no consumable value and must be read back by `receipt_id` rather
than retried. On a 0.1.5 platform a `completed` receipt carries only
`receipt_status`, `evidence_durability`, `observed_evidence_refs` and
`business_refs` (plus `partial_reasons` when set); `receipt_id` and the other
identity fields come with `pending` and terminal-replay receipts. To read the
complete record of a completed call, find its `receipt_id` with
`openbkn trace interactions operations <interaction-id>` and pass it to
`openbkn trace receipts get`.
Tokens are stored per platform/user under `~/.bkn/` (override: `BKN_CONFIG_DIR`).
Before configuring fulltext indexes, use `vega index-capabilities` to read the
deployment's supported analyzer IDs. This published Vega API changes no
configuration and starts no build. `openbkn bkn object-metric validate <kn-id> --body-file metric.json`
validates an unsaved Object Metric V1 definition. It prints
`{ valid, issues, output_shape }` and exits with code 1 when `valid` is false,
including when the server returns HTTP 200. `object-metric list` returns one page;
use `--offset`, `--limit`, and the response's `total_count` to find later metric IDs.
## SDK
```ts
import { createClient } from "@openbkn/bkn-sdk";
const bkn = createClient({ baseUrl: "https://your-platform", token: process.env.BKN_TOKEN });
const networks = await bkn.kn.list({ limit: 10 });
const task = await bkn.vega.build({ resource_id: "r-1", mode: "batch" }, { wait: true });
const raw = await bkn.call("/api/...", { method: "GET" });
```
`bkn.kn.objectMetricValidate(knId, definition)` returns the exported
`ObjectMetricValidationResult` (`{ valid, issues, output_shape }`). A completed
negative validation is a result, while HTTP errors and malformed responses reject
the promise. Object-type reads may omit `data_properties` when none exist;
SDK reads preserve omission, and local validation and push-integrity checks accept it.
Object Metric V1 query, recursive filter, result, and drill-through contracts are
exported as TypeScript types. Runtime dimensions use `query.group_by`; choose its
property refs from the definition's `query_capabilities.analysis_dimensions`.
Importing the package has no side effects; `createClient` resolves config explicitly.
### Model invocation
LLM `chat` and `chatStream` accept a model name or numeric registry ID. Existing
calls remain valid; an optional last argument adds generation and transport controls.
```typescript
import { createClient, type ChatOptions } from "@openbkn/bkn-sdk";
const bkn = createClient();
const options: ChatOptions = { temperature: 0, maxTokens: 128, timeoutMs: 60_000 };
const messages = [{ role: "user", content: "Say hello briefly." }];
const reply = await bkn.models.llm.chat("model-name-or-id", messages, options);
const text = await bkn.models.llm.chatStream("model-name-or-id", messages,
(delta) => process.stdout.write(delta), {
...options,
onChunk: (chunk) => { /* tool-call deltas and usage are available here */ },
});
```
Options use camelCase: `topP`, `topK`, `maxCompletionTokens`, `presencePenalty`,
`frequencyPenalty`, `responseFormat`, `tools`, `toolChoice`, `stop`, `cache`, and
`system` are also supported. Messages retain OpenAI `tool_calls` / `tool_call_id`
fields and accept structured or null content. Returned message extension fields
(e.g. `name`, `refusal`, `reasoning_content`) are preserved; roles are the backend
values `user`, `assistant`, `system`, and `tool`. Tools are described to the model,
never executed automatically. `signal` supports cancellation; inference defaults
to a 30-second timeout. Empty data keep-alives are ignored. Stream errors, malformed frames, or EOF before `[DONE]`
reject the promise; already emitted text is partial. `ModelStreamError.code`
preserves a provider error code when present. `onChunk` preserves structured
chunks while the return value remains concatenated text.
CLI: `openbkn model llm chat <name-or-id> -m "hello" --options
'{"temperature":0,"maxTokens":128,"timeoutMs":60000}'` (add `--stream` for text
streaming), or use `--options-file <path>`.
Model management list/get and small-model default reads mask `adapter_code` and
credential-shaped fields as `<redacted>` before returning to SDK or CLI callers.
These metadata results are not reusable edit payloads; provide configuration
credentials explicitly when changing a model. Raw `call` remains a passthrough.
### Account authentication for services
For a Node.js service that should establish its own account session instead of
reusing a prior CLI login, use the asynchronous authenticated factory. The SDK
keeps the OAuth session in memory and refreshes it automatically. It never
writes account credentials or tokens to `~/.bkn/`; use `onTokenRefresh` only
when the application intentionally persists refreshed credentials in its own
secret store.
```ts
import { createAuthenticatedClient } from "@openbkn/bkn-sdk";
const bkn = await createAuthenticatedClient({
baseUrl: process.env.BKN_BASE_URL!,
auth: {
username: process.env.BKN_USERNAME!,
password: process.env.BKN_PASSWORD!,
},
});
const networks = await bkn.kn.list({ limit: 10 });
```
Before the first business request to a platform, the SDK reads its health endpoint and checks
that the base SemVer version matches its own, ignoring prerelease and build suffixes. For example,
SDK `0.2.0` matches platform `0.2.0-rc.1` and `0.2.0-rc.2`. A mismatch stops the business request
with an error naming both versions. Set
`BKN_SKIP_VERSION_CHECK=1` to send the request anyway — for a deployment that reports a placeholder
or a different base version. It is the environment variable that serves a
library caller; the CLI has `--skip-version-check` as well.
Dynamic-data responses (including Vega and ontology queries) may contain native
`bigint` values for integers outside JavaScript's safe range. Serialize such a result with the exported
`stringifyBigIntJSON()` helper instead of native `JSON.stringify()`.
### Sign users in to your app
An app that acts as its own users — a CLI, a desktop tool, a notebook, a web
service — signs them in with the `oauth` namespace and hands the tokens to
`createClient`, which then refreshes them the way a stored `openbkn auth login`
session is refreshed. Nothing is read from or written to `~/.bkn/`.
```ts
import { createClient, oauth } from "@openbkn/bkn-sdk";
const baseUrl = process.env.BKN_BASE_URL!;
// Once per app: register a public client (loopback callbacks on any port).
const { clientId } = await oauth.registerClient(baseUrl, { clientName: "My app" });
// Per user: listen on 127.0.0.1:8765, open the browser, take the callback.
const tokens = await oauth.login(baseUrl, { clientId });
await save(tokens); // keep the refresh token — see below
const bkn = createClient({
baseUrl,
tokens,
clientId, // the refresh grant must name the client the tokens were issued to
onTokenRefresh: save, // called with the full new set after every refresh
});
const networks = await bkn.kn.list({ limit: 10 });
```
`login` takes `{ clientId, redirectUri?, scope?, openBrowser?, onUrl?, timeoutMs?, insecure? }`.
It listens on the redirect's host and port (default `oauth.DEFAULT_REDIRECT_URI`,
`http://127.0.0.1:8765/callback`) before it opens the browser, passes the sign-in
URL to `onUrl` (default: print it on stderr) and waits up to five minutes. It
does not open a browser when `openBrowser` is `false` or the machine is headless.
**Web apps** receive the callback in their own handler:
```ts
// 1. Send the user to the platform; keep `req` in their session.
const req = oauth.authorizationRequest(baseUrl, { clientId, redirectUri: "https://app.example.com/cb" });
res.redirect(req.url); // req = { url, state, codeVerifier, clientId, redirectUri }
// 2. In the callback handler: checks `state`, then exchanges the code.
const tokens = await oauth.exchangeCode(baseUrl, req, { code, state });
```
**Headless machines** (no browser, or a client you cannot register) use the device
flow, which works with the built-in `openbkn-sdk` client:
```ts
const tokens = await oauth.deviceLogin(baseUrl, {
onPrompt: (p) => console.log(`Open ${p.verificationUri} and enter ${p.userCode}`),
});
const bkn = createClient({ baseUrl, tokens, onTokenRefresh: save }); // clientId defaults to "openbkn-sdk"
```
`oauth.refresh(baseUrl, tokens, { clientId })` runs the refresh grant by hand. Sign-in
failures (denied, state mismatch, timeout, `invalid_grant`, no self-registration)
throw `InputError` with the platform's reason; tokens never appear in messages.
`registerClient` needs a platform with bkn-safe self-registration (releases after
0.2.0, unless an operator disabled it); elsewhere it throws and says to use
`deviceLogin` or ask an operator for a client.
Things to know:
1. **Save the refresh token from every callback.** Each refresh spends the old
one; an app that keeps only the first must sign the user in again on its next
start. `onTokenRefresh` receives the complete new set — store all of it. If it
throws, the refresh counts as failed.
2. **Never refresh one user's token set from two processes.** The platform treats
a reused refresh token as theft and revokes the whole chain, signing the user
out everywhere. One process is safe (concurrent requests share one refresh);
several instances need a shared store with a lock, or a single place that
refreshes.
3. **A failed refresh means sign in again.** The token expired, was revoked, or the
user disconnected the app under connected clients. The request then fails with
the original 401 and a hint saying to sign the user in again.
4. **`login` serves loopback callbacks only** (CLIs, desktop apps, scripts,
notebooks). A web app writes its own handler with `authorizationRequest` and
`exchangeCode`, and an operator must allow its non-loopback callback origin.
### Quality evaluation (Professional and above)
`client.evals` is separate from Trace's `evalSetBuild` fixture utility. Management
calls use the closed `EVAL_OPERATIONS` catalogue and network-scoped permissions:
```ts
const evaluation = bkn.evals.forNetwork(knId);
const runs = await evaluation.request("searchEvalRuns", {
query: { page: 1, limit: 30, status: "completed" },
});
```
The catalogue covers assets, rule application, daily/weekly/monthly
plans, frozen results, historical comparison and agent comparison. Management
business responses are `unknown`; validate the fields your application consumes.
See [Eval workflow](docs/product-specs/eval.md) for external participants. The five
participant methods never fetch gold or invoke a local autonomous runner.
## Develop
```bash
npm install
npm run lint # biome + tsc --noEmit
npm test # vitest (unit)
npm run build # tsup → dist/ (library + `openbkn` bin)
```
## Help system
Every command, subcommand, and sub-subcommand ships a grouped `--help` that
lists its flags and arguments, so the tree is discoverable end to end.
`openbkn describe` prints the whole tree at once — sections, arguments, and
where each id comes from; `--json` ships the same thing as data.
## Agent skill
`skills/openbkn/` is an agent skill (for Claude Code / the [skills.sh](https://skills.sh)
ecosystem) that lets an AI drive the `openbkn` CLI from natural language. It
ships a `SKILL.md` (trigger intents, `allowed-tools: Bash(openbkn *)`, a
command-group map, examples, and cautions) plus per-domain cheat sheets under
`references/` (auth, appkey, bkn, model, vega, resource, context, mcp,
skill, toolbox, function, osdk, trace, admin, call) and two how-tos
(build-a-KN, troubleshooting).
A second skill, `skills/create-bkn/`, guides an AI through **authoring** a BKN
definition tree (`network.bkn` + typed `value_types/` / `object_types/` /
`relation_types/` / `action_types/` / `concept_groups/` files, per the BKN 3.0
spec by default, or BKN 2.x) — with the format spec, templates, and a worked example under `references/`. It pairs with
the `openbkn` skill, which then runs `openbkn bkn validate` / `push` on the
tree it produced.
A third skill, `skills/create-skill/`, guides an AI through **authoring a Skill
package** for the execution factory — a `SKILL.md` that declares which metrics,
functions, actions and MCP tools of one knowledge network it uses and how an
agent should call them — and through writing a sandbox function with
`bkn-osdk` and registering it as a tool. Templates and a worked example that
was verified end-to-end live under `assets/` and `references/`.
None of the skills is part of the npm package — they're registered separately:
```bash
# Install a skill (globally), then ask in natural language:
npx skills add openbkn-ai/bkn-sdk@openbkn -g -y # operate the platform
npx skills add openbkn-ai/bkn-sdk@create-bkn -g -y # author .bkn files
npx skills add openbkn-ai/bkn-sdk@create-skill -g -y # author a Skill package / function tool
# "列出所有知识网络" / "list all knowledge networks"
# "从 Vega catalog vcat-1 建一个名为 customers 的知识网络并构建索引"
# "帮我建一个描述订单域的 BKN 知识网络文件" / "author a BKN network for the orders domain"
```
The `openbkn` skill assumes the `openbkn` CLI is installed (`npm i -g
@openbkn/bkn-sdk`) and you are logged in (`openbkn auth login`). It always defers
to live `openbkn <group> <sub> --help` for exact flags.
## License
BKN SDK is part of the OpenBKN project and is licensed under the
**Apache License, Version 2.0**. See [LICENSE](LICENSE) and [NOTICE](NOTICE).