npm package: @gdrl/kronos-lib
Description: Lightweight TypeScript SDK for the Kronos Knowledge Graph API
Latest version: 0.1.9

# kronos-lib

Lightweight TypeScript SDK for the [Kronos Knowledge Graph](https://github.com/your-org/kronos) API. Use it from Next.js, Node.js, or any JavaScript environment with `fetch` (Node 18+, browsers).

## Installation

```bash
npm install kronos-lib
# or
pnpm add kronos-lib
# or
yarn add kronos-lib
```

## Quick Start

```typescript
import { KronosClient } from '@gdrl/kronos-lib';

const client = new KronosClient({
  workerUrl: 'https://your-worker.workers.dev',
  mastraUrl: 'https://your-mastra-server.com',
  apiKey: 'your-app-api-key',
  appId: 'your-app-id',
  tenantUserId: 'user-123',
});

// Ingest a document
const { ingest_id } = await client.ingest({
  source_type: 'email',
  content: { type: 'text', text: 'Document content...' },
  metadata: { sender: 'user@example.com' },
});

// Check ingest status
const status = await client.getIngestStatus(ingest_id);

// Create an MCP server
const mcp = await client.createMCPServer({
  name: 'My MCP Server',
  collection_ids: ['col-1', 'col-2'],
  description: 'Optional description',
});

// Connect to the MCP server (short-lived access token, ~1 day)
const connection = await client.connectMCPServer({
  serverId: mcp.mcp_server_id,
});
// connection.url is the full MCP endpoint; connection.access_token is the Bearer token.

// Query the knowledge graph
const results = await client.query({
  query: 'What happened in Tokyo?',
  collection_ids: ['col-1', 'col-2'],
});
// results.episodes, results.entities, results.attributes, results.direct_answer

// Create a collection
const collection = await client.createCollection({
  spec: {
    name: 'Work Emails',
    include_query: 'work OR project',
    scope: 'episode',
    match_mode: 'broad',
  },
  created_by: 'user-123',
});
// collection.collection_id, collection.backfill_job_id

// Refresh collections for new episodes
await client.refreshCollections({ episode_ids: ['ep-1', 'ep-2'] });
```

## Configuration

| Option        | Description                                                                 |
|---------------|-----------------------------------------------------------------------------|
| `workerUrl`   | Base URL of the Cloudflare Worker (ingest, MCP server create/rotate).      |
| `mastraUrl`   | Base URL of the Mastra server (query, collections).                         |
| `apiKey`      | App API key; used as `Authorization: Bearer` for Worker endpoints.         |
| `appId`       | App ID; injected into Worker and Mastra request bodies.                     |
| `tenantUserId`| Tenant/user ID; injected into request bodies.                               |

### Local development

Set `KRONOS_WORKER_URL` and `KRONOS_MASTRA_URL` in your app's `.env` for local dev (e.g. `http://localhost:8787`, `http://localhost:4001`). Pass these when creating the client via your app's shared config helper, or the client will read them from `process.env`. Omit in production to use production defaults. For the frontend (KronosProvider), use `NEXT_PUBLIC_KRONOS_WORKER_URL` (mercury-nextjs already defaults to localhost).

## API Overview

### Ingest (Worker)

- **`ingest(params)`** – Submit a document. Requires `source_type` and `content: { type: 'text', text }`. Optional: `metadata`, `addToMemory`, `idempotencyKey`. `addToMemory` defaults to `true`; set it to `false` to run ingest-trigger workflows without chunking or adding the content to memory. If `idempotencyKey` is omitted, one is generated. Returns `{ ingest_id }`.
- **`getIngestStatus(ingestId)`** – Get status and progress of an ingest.

### MCP Servers (Worker)

- **`createMCPServer(params)`** – Create an MCP server. Requires `name`, `collection_ids`. Optional: `description`. Returns MCP server metadata including `mcp_server_id`.
- **`listMCPServers(params)`** – List MCP servers for a user. Returns metadata only (no token).
- **`getMCPServer(params)`** – Get MCP server metadata by `serverId` (no token).
- **`connectMCPServer(params)`** – Issue a short-lived MCP access token for a server. Returns `{ mcp_server_id, url, access_token, expires_at }`.
- **`rotateMCPServerToken(serverId)`** – Legacy token rotation endpoint (generally not needed with short-lived connect tokens).

### Query (Mastra)

- **`query(params)`** – Query the graph. Requires `query`, `collection_ids`. Returns `{ episodes, entities, attributes, direct_answer? }`.

### Collections (Mastra)

- **`createCollection(params)`** – Create a collection. Requires `spec` (e.g. `name`, `include_query`, `scope`, `match_mode`), `created_by`. Optional: `description`. Returns `{ collection_id, backfill_job_id }`.
- **`refreshCollections(params)`** – Refresh collection membership for `episode_ids`. Optional: `mode` (default `'new_episodes_only'`). Returns `{ accepted: true }`.

## Idempotency

For `ingest`, the Worker requires an `Idempotency-Key` header. You can pass `idempotencyKey` in the options; if omitted, the SDK generates one. For true idempotency (e.g. retries), pass a stable key (e.g. hash of `source_type` + `content.text` or your own id).

## Error Handling

The SDK throws typed errors for HTTP failures:

- `KronosBadRequestError` (400)
- `KronosUnauthorizedError` (401)
- `KronosForbiddenError` (403)
- `KronosNotFoundError` (404)
- `KronosServerError` (5xx)
- `KronosError` (other)

All extend `KronosError` and include `status`, `code`, and `body` when available.

```typescript
import { KronosClient, KronosNotFoundError } from '@gdrl/kronos-lib';

try {
  await client.getIngestStatus('missing-id');
} catch (e) {
  if (e instanceof KronosNotFoundError) {
    console.log('Ingest not found:', e.body);
  }
  throw e;
}
```

## React integration (optional)

If you're building a React or Next.js app, you can use the optional React hook
exported from the `kronos-lib/react` subpath.

### `useKronos` (triggers)

For now, `useKronos` focuses on trigger operations (more domains can be added over time):

```tsx
import { useKronos, type KronosTriggerOps } from '@gdrl/kronos-lib/react';

const ops: KronosTriggerOps = {
  async listTriggers() {
    // Example: call your own app route that proxies to Kronos
    const response = await fetch('/api/mercury/triggers');
    const body = await response.json();
    return {
      triggers: body.triggers ?? [],
      count: body.count ?? (body.triggers ?? []).length,
    };
  },
  async createNlTrigger({ nl, actionDescription }) {
    const response = await fetch('/api/mercury/triggers', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type: 'nl_webhook', nl, actionDescription }),
    });
    const body = await response.json();
    return { triggerId: body.trigger_id ?? null };
  },
};

function Automations() {
  const {
    triggers,
    triggersLoading,
    triggersError,
    refreshTriggers,
    createNlTrigger,
    createLoading,
    createError,
  } = useKronos({ ops, tenantUserId: 'current-user' });

  // ...render UI...
}
```

This pattern lets you keep API keys and webhook secrets on the server (behind your own API routes),
while still getting a clean React hook surface for reading and creating triggers.

## Types

All request/response types are exported:

- `IngestRequest`, `IngestResponse`, `IngestStatusResponse`
- `CreateMCPServerRequest`, `CreateMCPServerResponse`, `MCPServerDetails`, `ListMCPServersResponse`, `GetMCPServerResponse`, `ConnectMCPServerRequest`, `ConnectMCPServerResponse`
- `RotateMCPServerTokenRequest`, `RotateMCPServerTokenResponse`
- `QueryRequest`, `QueryResponse`, `EpisodeResult`, `EntityResult`, `AttributeResult`
- `CollectionSpec`, `CreateCollectionRequest`, `CreateCollectionResponse`
- `RefreshCollectionsRequest`, `RefreshCollectionsResponse`
- `KronosClientConfig`

## Requirements

- Node.js 18+ or a modern browser (uses global `fetch`)
- TypeScript 4.7+ recommended for types

## License

MIT