> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ticket-rs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# npm SDK

> Use ticket-rs from Node.js and Bun with native TypeScript bindings

The `ticket-rs` npm package provides native TypeScript bindings to ticket-rs, giving you full access to issue management and graph analytics from Node.js and Bun.

Every visible `tk` subcommand has a typed TypeScript wrapper, generated from the same `clap::Command` tree the binary parses against — so the TypeScript surface stays in lockstep with the CLI by construction. Calls go through an in-process napi FFI; no subprocess is spawned.

***

## Installation

<Tabs>
  <Tab title="bun (Recommended)">
    ```bash theme={null}
    bun add ticket-rs
    ```

    <Note>
      This project standardises on [bun](https://bun.sh) for TypeScript work; if you maintain a Node.js project, npm and yarn install the same artefact.
    </Note>
  </Tab>

  <Tab title="npm">
    ```bash theme={null}
    npm install ticket-rs
    ```
  </Tab>

  <Tab title="Globally (CLI only)">
    ```bash theme={null}
    npm install -g ticket-rs
    ```

    Installs the `tk` CLI binary globally. The TypeScript bindings still require a per-project install.
  </Tab>
</Tabs>

The postinstall step downloads (or builds) the prebuilt native module for your platform. No Rust toolchain required for the supported triples below; if your platform isn't listed, install Rust and the package will compile from source.

***

## Quick Start

```typescript theme={null}
import * as tk from "ticket-rs";

// Every visible `tk` subcommand is a top-level function.
// Pass an options object the same way you'd pass flags on the CLI.

// Initialise a tickets directory
tk.init({ dir: "/path/to/your/project" });

// Get issues ready to work on (no open blockers)
const ready = tk.ready({
  dir: "/path/to/your/project",
  format: "json",
}) as Array<{ id: string; title: string }>;

for (const issue of ready) {
  console.log(`${issue.id}: ${issue.title}`);
}

// Get repository statistics
const stats = tk.stats({
  dir: "/path/to/your/project",
  format: "json",
}) as { open: number; closed: number; ready: number };

console.log(`Open: ${stats.open}  Closed: ${stats.closed}  Ready: ${stats.ready}`);
```

***

## API Reference

`ticket-rs` exposes three categories of API:

1. **76 typed wrappers** — one per visible `tk` subcommand. Type signatures come from `clap`.
2. **`napiDispatch(argv)`** — raw escape hatch for any command, including hidden ones.
3. **`commandSpec()`** — introspect the CLI surface as a nested JSON spec.

### Typed wrappers

Each wrapper takes the command's flags as properties of a single options object. The munging rules:

* kebab-case becomes camelCase: `--dry-run` → `dryRun`
* nested commands are namespaced: `tk dep add` → `depAdd`
* the JS reserved word `delete` gets a trailing underscore: `tk delete` → `delete_`
* global flags `dir`, `format`, `json`, and `strict` are accepted on every wrapper

Return value depends on `format`:

* `format: "json"` → parsed JSON (`unknown`; narrow at the call site)
* omitted or any other value → the raw stdout string

```typescript theme={null}
import * as tk from "ticket-rs";

// Core issue management
tk.create({ title: "Fix the bug", priority: 1, format: "json" });
const issue = tk.show({ id: "tk-abc123", format: "json" });
tk.update({ id: "tk-abc123", status: "in_progress" });
tk.close({ id: "tk-abc123", reason: "fixed" });
const issues = tk.list({ format: "json" });

// Dependencies (nested commands become camelCase)
tk.depAdd({ blocked: "tk-1", blocker: "tk-2" });
tk.depTree({ id: "tk-1" });

// AI/analytics
const triage = tk.triage({ format: "json" });
const priority = tk.priority({ format: "json" });
const plan = tk.plan({ format: "json" });
const nextPick = tk.next({ format: "json" });

// Reserved word `delete` becomes `delete_`
tk.delete_({ id: "tk-abc123" });

// kebab-case flags become camelCase
tk.depAdd({ blocked: "tk-1", blocker: "tk-2", dryRun: true });
```

The full surface includes every visible `tk` command — `linearSync`, `githubSync`, `claudeSync`, `worktree`, `stacks`, `insights`, `search`, `similar`, `validate`, `lint`, and more. Discover them at runtime:

```typescript theme={null}
import { TYPED_API_FUNCTIONS } from "ticket-rs";
console.log([...TYPED_API_FUNCTIONS].sort());
```

### `napiDispatch(argv)`

Raw escape hatch for any command, including hidden ones. Same in-process napi path; you build the argv array yourself.

```typescript theme={null}
import { napiDispatch } from "ticket-rs";

// Equivalent to `tk list --format json -C /path/to/repo`
const out = napiDispatch(["list", "--format", "json", "-C", "/path/to/repo"]);

// Returns the raw stdout string. Parse it yourself.
const issues = JSON.parse(out);
```

### `commandSpec()`

Introspect the CLI surface as a nested JSON spec — useful for building meta-tools, generating other typed bindings, or discovering commands and their flags programmatically.

```typescript theme={null}
import { commandSpec } from "ticket-rs";

const spec = JSON.parse(commandSpec());
// spec = { name: "tk", subcommands: [...], global_args: [...] }

// List every visible top-level command
for (const cmd of spec.subcommands) {
  if (!cmd.hidden) {
    console.log(cmd.name, "—", cmd.about ?? "");
  }
}
```

***

## Error handling

All errors surface as plain `Error` instances — **not `TypeError`**. The split between argv-parse failures and runtime failures is carried on `err.code`:

| `err.code`         | Cause                                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `"InvalidArg"`     | argv didn't parse (unknown subcommand, bad flag, missing required positional). Fix the call.                              |
| `"GenericFailure"` | argv parsed fine but the command itself failed (issue not found, parse error in a ticket file, etc.). The system refused. |

<Warning>
  napi-derive wraps every `Err(_)` from Rust in a plain `JsError` (which presents as `Error` in JS), not a `TypeError`. Don't write `catch (err) { if (err instanceof TypeError) ... }` — that branch will never fire. Use `err.code` to discriminate.
</Warning>

```typescript theme={null}
import * as tk from "ticket-rs";

try {
  tk.show({ id: "tk-doesnotexist" });
} catch (err) {
  // All errors are plain Error instances. Discriminate via .code:
  const code = (err as { code?: string }).code;
  if (code === "GenericFailure") {
    // The command ran but failed — issue doesn't exist
    console.error(`command failed: ${(err as Error).message}`);
  } else if (code === "InvalidArg") {
    // You mis-typed the call — bad flag, missing arg, etc.
    console.error(`bad call: ${(err as Error).message}`);
  } else {
    throw err;
  }
}
```

***

## Return shapes

When you pass `format: "json"`, wrappers return parsed JSON. The shape mirrors what `tk <command> --format json` emits on the CLI. The TypeScript type is `unknown` — narrow with a type guard or `zod` in your code. (The clap surface doesn't carry per-command output schemas; pinning them in TS would be a separate source of truth that could drift.)

```typescript theme={null}
// list({ format: "json" }) → array of issues
[
  { id: "tk-abc", title: "Fix bug", status: "open", priority: 1 },
]

// triage({ format: "json" }) → dict with nested structure
{
  quick_ref: { total: 42, open: 18, ready: 5 },
  recommendations: [{ id: "tk-abc", score: 87.5, reason: "..." }],
  quick_wins: [],
  blockers_to_clear: [],
}

// stats({ format: "json" }) → flat counts
{ total: 42, open: 18, in_progress: 3, blocked: 2, closed: 19, ready: 5 }
```

For the exact shape of each command's JSON, run `tk <command> --format json` once and inspect.

***

## Migration from the hand-written client

The previous `createClient` / `Client` API has been removed — `src/client.ts`, `src/models.ts`, and `src/batch.ts` are gone. Replace `client.method(...)` calls with the corresponding typed wrapper:

| Before                        | After                              |
| ----------------------------- | ---------------------------------- |
| `createClient().listIssues()` | `tk.list({ format: "json" })`      |
| `client.readyIssues()`        | `tk.ready({ format: "json" })`     |
| `client.computePriorities()`  | `tk.priority({ format: "json" })`  |
| `client.computeTriage()`      | `tk.triage({ format: "json" })`    |
| `client.generatePlan()`       | `tk.plan({ format: "json" })`      |
| `client.getVersion()`         | `tk.version({})`                   |
| Anything else                 | `napiDispatch(["<cmd>", ...args])` |

The migration is mechanical — every old client method maps either to a typed wrapper named after its `tk` subcommand, or to a raw `napiDispatch` call.

***

## Examples

### Find high-priority ready issues

```typescript theme={null}
import * as tk from "ticket-rs";

const ready = tk.ready({ format: "json" }) as Array<{
  id: string;
  title: string;
  priority: number;
}>;

const highPriority = ready.filter((i) => i.priority <= 1);
for (const issue of highPriority) {
  console.log(`[P${issue.priority}] ${issue.id}: ${issue.title}`);
}
```

### Batch create issues

```typescript theme={null}
import * as tk from "ticket-rs";

const tasks: Array<[string, string, number]> = [
  ["Set up CI/CD", "task", 1],
  ["Write documentation", "task", 2],
  ["Add unit tests", "task", 2],
];

for (const [title, issueType, priority] of tasks) {
  const out = tk.create({
    title,
    issueType,
    priority,
    format: "json",
  }) as { id: string };
  console.log(`Created: ${out.id}`);
}
```

### Export issues to JSON

```typescript theme={null}
import { writeFileSync } from "node:fs";
import * as tk from "ticket-rs";

const issues = tk.list({ format: "json" }) as Array<{
  id: string;
  title: string;
  status: string;
  priority: number;
}>;

const data = issues.map((i) => ({
  id: i.id,
  title: i.title,
  status: i.status,
  priority: i.priority,
}));

writeFileSync("issues.json", JSON.stringify(data, null, 2));
```

### Run a hidden command via the escape hatch

```typescript theme={null}
import { napiDispatch } from "ticket-rs";

// Hidden commands aren't surfaced as typed wrappers, but `napiDispatch`
// reaches them by argv. Useful for the MCP server and internal tools.
const out = napiDispatch(["migrate-beads", "--dry-run"]);
console.log(out);
```

***

## Platform Support

Pre-built native modules are shipped for:

| Platform | Architectures                          | Node.js Versions |
| -------- | -------------------------------------- | ---------------- |
| Linux    | x86\_64, aarch64                       | 18+              |
| macOS    | x86\_64 (Intel), arm64 (Apple Silicon) | 18+              |
| Windows  | x86\_64                                | 18+              |

<Note>
  If no pre-built module exists for your platform, the postinstall step compiles from source (requires Rust toolchain).
</Note>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Error: Cannot find module '../native/ticket_rs_bindings.node'">
    The native bindings weren't installed. If you cloned the repo, run:

    ```bash theme={null}
    cd npm && bun run build
    ```

    If you installed from npm and the postinstall step failed, reinstall with verbose logging:

    ```bash theme={null}
    bun install --verbose ticket-rs
    ```
  </Accordion>

  <Accordion title="ImportError: cannot import name 'createClient' / 'TicketRsClient' / ...">
    The hand-written `createClient` / `Client` API was removed when the typed\_api surface shipped. The `src/client.ts`, `src/models.ts`, and `src/batch.ts` files are gone.

    ```typescript theme={null}
    // Before (removed)
    import { createClient } from "ticket-rs";
    const client = createClient();
    client.listIssues();

    // After
    import * as tk from "ticket-rs";
    tk.list({ format: "json" });
    ```

    See the [Migration table](#migration-from-the-hand-written-client) above, or use `napiDispatch(["list", "-f", "json"])` as the raw escape hatch.
  </Accordion>

  <Accordion title="catch (err) { if (err instanceof TypeError) ... } never fires">
    By design. napi-derive wraps every Rust `Err(_)` in a plain `JsError` that presents as `Error` (not `TypeError`) in JS. Discriminate via `err.code`:

    ```typescript theme={null}
    catch (err) {
      const code = (err as { code?: string }).code;
      if (code === "InvalidArg") {
        // argv parse failure
      } else if (code === "GenericFailure") {
        // runtime failure (e.g. issue not found)
      }
    }
    ```

    See [Error handling](#error-handling) for the full table.
  </Accordion>

  <Accordion title="Build fails during npm install">
    The package contains a native Node addon. If no pre-built native module exists for your platform, you need:

    1. Rust toolchain: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
    2. Node.js 18+ with headers

    Then re-run install:

    ```bash theme={null}
    bun install ticket-rs
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="MCP Server" icon="plug" href="/integrations/mcp">
    Use ticket-mcp for AI agent integration.
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/cli-reference/commands">
    Full command documentation.
  </Card>
</CardGroup>
