npm package: @skytalesh/sdk
Description: Skytale SDK — encrypted channels for AI agents
Latest version: 0.5.2

# @skytalesh/sdk

[![npm version](https://img.shields.io/npm/v/@skytalesh/sdk.svg)](https://www.npmjs.com/package/@skytalesh/sdk)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

TypeScript/Node.js SDK for Skytale — encrypted shared context for AI agents.

Create MLS-encrypted communication channels with multi-protocol support (SLIM, A2A, ACP, MCP, ANP, LMOS, NLIP). Native Rust core via NAPI for performance.

## Install

```bash
npm install @skytalesh/sdk
```

Prebuilt binaries are available for macOS (x64, ARM64), Linux (x64, ARM64), and Windows (x64). Requires Node.js 18+.

## Setup

Get an API key with the Skytale CLI:

```bash
skytale signup you@example.com
```

Or set the key directly:

```bash
export SKYTALE_API_KEY="sk_live_..."
```

## Quick Start

```typescript
import { SkytaleChannelManager } from '@skytalesh/sdk';

const alice = new SkytaleChannelManager({
  identity: 'alice-agent',
  apiKey: process.env.SKYTALE_API_KEY,
});

await alice.create('acme/support/routing');
const token = await alice.invite('acme/support/routing');

const bob = new SkytaleChannelManager({
  identity: 'bob-agent',
  apiKey: process.env.SKYTALE_API_KEY,
});

await bob.joinWithToken('acme/support/routing', token);
alice.send('acme/support/routing', 'Hello from Alice!');
const messages = await bob.receive('acme/support/routing');
console.log(messages); // ['Hello from Alice!']

alice.close();
bob.close();
```

## API Reference

### `SkytaleChannelManager`

High-level API for most use cases. Handles background message buffering and environment-based configuration.

```typescript
import { SkytaleChannelManager } from "@skytalesh/sdk";

const mgr = new SkytaleChannelManager({
  identity: "my-agent",      // string or Buffer
  endpoint: "https://...",   // defaults to SKYTALE_RELAY env
  apiKey: "sk_live_...",     // defaults to SKYTALE_API_KEY env
  apiUrl: "https://...",     // defaults to SKYTALE_API_URL env
  mock: false,               // enable mock mode for testing
});
```

#### Constructor Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `identity` | `string \| Buffer` | Yes | Agent identity (strings are UTF-8 encoded) |
| `endpoint` | `string` | No | Relay URL (default: `SKYTALE_RELAY` env or `https://relay.skytale.sh:5000`) |
| `dataDir` | `string` | No | MLS state directory (default: auto-generated) |
| `apiKey` | `string` | No | API key (default: `SKYTALE_API_KEY` env) |
| `apiUrl` | `string` | No | API server URL (default: `SKYTALE_API_URL` env or `https://api.skytale.sh`) |
| `mock` | `boolean` | No | Enable mock mode for testing without a relay (default: `false`) |

#### `create(channelName: string): Promise<void>`

Create a channel and start a background listener.

```typescript
await mgr.create("myorg/team/general");
```

#### `invite(channelName: string, maxUses?: number, ttl?: number): Promise<string>`

Generate an invite token for a channel. Returns an `skt_inv_...` token.

- `maxUses` — maximum number of times the token can be used (default: 1)
- `ttl` — token lifetime in seconds (default: 3600)

```typescript
const token = await mgr.invite("myorg/team/general");
```

#### `joinWithToken(channelName: string, token: string, timeout?: number): Promise<void>`

Join a channel using an invite token. MLS key exchange is handled automatically.

- `timeout` — max milliseconds to wait (default: 60000)

```typescript
await mgr.joinWithToken("myorg/team/general", token);
```

#### `send(channelName: string, message: string | Buffer): void`

Send a message. Strings are UTF-8 encoded automatically.

```typescript
mgr.send("myorg/team/general", "Hello!");
```

#### `receive(channelName: string, timeout?: number): Promise<string[]>`

Drain all buffered messages. Waits up to `timeout` ms (default: 5000) if buffer is empty.

```typescript
const msgs = await mgr.receive("myorg/team/general");
```

#### `receiveLatest(channelName: string, timeout?: number): Promise<string | null>`

Return only the most recent message, discarding older ones.

```typescript
const latest = await mgr.receiveLatest("myorg/team/general");
```

#### `sendEnvelope(channelName: string, envelope: Envelope): void`

Send a structured envelope on a channel.

#### `receiveEnvelopes(channelName: string, timeout?: number): Promise<Envelope[]>`

Receive structured envelopes. Raw messages are auto-wrapped as `Protocol.RAW`.

#### `listChannels(): string[]`

Return names of all active channels.

#### `close(): void`

Stop all background listeners and release resources.

#### `on('message', (channelName: string, message: Buffer) => void)`

Event emitter for real-time message handling. Fires for every incoming message on any channel.

```typescript
mgr.on("message", (channel, msg) => {
  console.log(`[${channel}] ${msg.toString()}`);
});
```

## MCP Server

Expose Skytale encrypted channels as MCP tools for Claude Desktop, Cursor, or any MCP client.

```bash
npx @skytalesh/mcp-server
```

### Claude Desktop Configuration

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "skytale": {
      "command": "npx",
      "args": ["@skytalesh/mcp-server"],
      "env": {
        "SKYTALE_API_KEY": "sk_live_..."
      }
    }
  }
}
```

### Cursor Configuration

Add to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "skytale": {
      "command": "npx",
      "args": ["@skytalesh/mcp-server"]
    }
  }
}
```

### MCP Tools

| Tool | Description |
|------|-------------|
| `skytale_create` | Create an encrypted channel |
| `skytale_invite` | Generate an invite token for a channel |
| `skytale_join` | Join a channel with an invite token |
| `skytale_send` | Send a message on a channel |
| `skytale_receive` | Receive messages from a channel |
| `skytale_channels` | List active channels |

## MCP Encrypted Transport

Use Skytale as the transport layer for MCP protocol messages, replacing plaintext HTTP/stdio with MLS-encrypted channels.

```typescript
import { SkytaleChannelManager } from "@skytalesh/sdk";
import { SkytaleTransport } from "@skytalesh/sdk/mcp-transport";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

const mgr = new SkytaleChannelManager({ identity: "agent-1", mock: true });
await mgr.create("org/ns/mcp-rpc");

const transport = new SkytaleTransport(mgr, "org/ns/mcp-rpc");
const client = new Client({ name: "my-client", version: "1.0.0" });
await client.connect(transport);
```

## Mock Mode

Test agent logic without a running relay:

```typescript
const mgr = new SkytaleChannelManager({ identity: "test-agent", mock: true });
await mgr.create("org/ns/test");
mgr.send("org/ns/test", "hello");
const msgs = await mgr.receive("org/ns/test"); // ["hello"]
mgr.close();
```

Mock mode is useful for unit tests, CI pipelines, and local development. Also enabled by `SKYTALE_MOCK=1` env var.

## Error Handling

All methods throw typed exceptions extending `SkytaleError`:

```typescript
import { SkytaleChannelManager, ChannelError, AuthError, SkytaleError } from "@skytalesh/sdk";

try {
  const mgr = new SkytaleChannelManager({ identity: "agent" });
  await mgr.create("bad-name");
} catch (e) {
  if (e instanceof AuthError) {
    console.log(`Auth failed (HTTP ${e.httpStatus}): ${e.message}`);
    console.log(`Fix: ${e.docUrl}`);
  } else if (e instanceof ChannelError) {
    console.log(`Channel error [${e.code}]: ${e.message}`);
  } else if (e instanceof SkytaleError) {
    console.log(`SDK error: ${e.message}`);
  }
}
```

### Error Types

| Exception | When |
|-----------|------|
| `AuthError` | API key invalid, expired, or missing |
| `TransportError` | Relay unreachable, connection error, timeout |
| `ChannelError` | Invalid channel name, channel not found |
| `MlsError` | Bad key package, invalid Welcome, decryption failure |
| `QuotaExceededError` | Free tier message limit reached |

Every exception includes `code`, `httpStatus`, and `docUrl` attributes.

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `SKYTALE_RELAY` | Relay server URL | `https://relay.skytale.sh:5000` |
| `SKYTALE_API_KEY` | API key for authentication | -- |
| `SKYTALE_API_URL` | API server URL | `https://api.skytale.sh` |
| `SKYTALE_DATA_DIR` | MLS state directory | Auto-generated |
| `SKYTALE_IDENTITY` | Default agent identity | -- |
| `SKYTALE_MOCK` | Enable mock mode (`1`, `true`, `yes`) | `false` |

## Envelope & Protocol

Protocol-tagged messages for multi-protocol channels:

```typescript
import { Protocol, type Envelope, serializeEnvelope, deserializeEnvelope } from "@skytalesh/sdk";

const env: Envelope = {
  protocol: Protocol.A2A,
  contentType: "application/json",
  payload: Buffer.from('{"parts":[]}'),
};

const data = serializeEnvelope(env);
const env2 = deserializeEnvelope(data);
```

### Supported Protocols

| Value | Description |
|-------|-------------|
| `Protocol.RAW` | Plain bytes (default, backward compatible) |
| `Protocol.A2A` | Google Agent-to-Agent protocol |
| `Protocol.MCP` | Model Context Protocol |
| `Protocol.SLIM` | SLIM (Secure Lightweight Inter-agent Messaging) |
| `Protocol.ACP` | IBM Agent Communication Protocol |
| `Protocol.ANP` | Agent Network Protocol (DID-based) |
| `Protocol.LMOS` | Eclipse LMOS (Web of Things) |
| `Protocol.NLIP` | Natural Language Interaction Protocol |

## Architecture

```
Your Agent --> SkytaleChannelManager --> gRPC --> Relay --> gRPC --> SkytaleChannelManager --> Their Agent
                       |                                                    |
                       +-- MLS encrypt                       MLS decrypt --+
```

All messages are encrypted end-to-end with MLS (RFC 9420). The relay cannot read message contents. Messages are padded to fixed sizes and mixed with cover traffic — observers can't infer what's being said, when, or even if real communication is happening.

## License

Apache 2.0