Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 244 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
</h1>

<p align="center">
Official Node.js SDK for <a href="https://onecli.sh">OneCLI</a>. Route AI agent traffic through the OneCLI proxy — agents never see real credentials.
Official Node.js SDK for <a href="https://onecli.sh">OneCLI</a>. Route AI agent traffic through the OneCLI gateway — agents never see real credentials.
</p>

<p align="center">
<a href="https://onecli.sh/docs">Documentation</a> &nbsp;|&nbsp;
<a href="https://onecli.sh/docs/sdks/node">Documentation</a> &nbsp;|&nbsp;
<a href="https://onecli.sh">Website</a> &nbsp;|&nbsp;
<a href="https://github.com/onecli/node-sdk">GitHub</a>
</p>
Expand All @@ -29,7 +29,11 @@
## Installation

```bash
npm install @onecli-sh/sdk
# or
pnpm add @onecli-sh/sdk
# or
yarn add @onecli-sh/sdk
```

## Requirements
Expand All @@ -43,41 +47,71 @@ pnpm add @onecli-sh/sdk
```typescript
import { OneCLI } from "@onecli-sh/sdk";

// Reads ONECLI_API_KEY and ONECLI_URL from environment
const onecli = new OneCLI();
// Cloud (api.onecli.sh) — no url needed, it's the default
const onecli = new OneCLI({
apiKey: "oc_your_api_key",
});

const args = ["run", "-i", "--rm", "--name", "my-agent"];

// Fetches container config and pushes -e / -v flags onto args
const active = await onecli.applyContainerConfig(args);
// args now contains proxy env vars and CA certificate mounts

// args now contains HTTPS_PROXY, CA certs, and volume mounts
console.log(active); // true if OneCLI was reachable
```

### Explicit configuration
> **Self-hosted?** Pass `url: "http://localhost:10254"` (or wherever your instance runs) to the constructor, or set the `ONECLI_URL` environment variable.

### Environment variables

Instead of passing options explicitly, set environment variables:

```bash
export ONECLI_API_KEY=oc_your_api_key

# Self-hosted only — cloud users can skip this (defaults to https://api.onecli.sh)
# export ONECLI_URL=http://localhost:10254
```

```typescript
import { OneCLI } from "@onecli-sh/sdk";

// Automatically reads from ONECLI_API_KEY (and ONECLI_URL if set)
const onecli = new OneCLI();
const active = await onecli.applyContainerConfig(args);
```

| Variable | Description |
| -------------------- | ------------------------------------------------------------------------ |
| `ONECLI_API_KEY` | API key (`oc_...` for project keys, `oc_org_...` for org keys) |
| `ONECLI_URL` | Base URL of the OneCLI instance. Defaults to `https://api.onecli.sh` |
| `ONECLI_GATEWAY_URL` | Gateway URL for manual approval polling (auto-resolved if not set) |
| `ONECLI_PROJECT_ID` | Default project ID for org-level API keys |

### Organization API keys

Organization-level API keys (`oc_org_...`) grant access across all projects in an org. Pass a `projectId` to specify which project to target.

```typescript
import { OneCLI } from "@onecli-sh/sdk";

// Set a default project for all operations
const onecli = new OneCLI({
apiKey: "oc_...", // optional: falls back to ONECLI_API_KEY env var
url: "http://localhost:3000", // optional: falls back to ONECLI_URL env var, then https://api.onecli.sh
apiKey: "oc_org_your_org_key",
projectId: "proj-123",
});

// Get raw container configuration
const config = await onecli.getContainerConfig();
console.log(config.env); // { HTTPS_PROXY: "...", HTTP_PROXY: "...", ... }
console.log(config.caCertificate); // PEM content
await onecli.createAgent({ name: "Bot", identifier: "bot" });

// Or apply directly to Docker run args
const args = ["run", "-i", "--rm", "my-image"];
const active = await onecli.applyContainerConfig(args);
// Override the project for a specific operation
await onecli.createAgent(
{ name: "Bot", identifier: "bot" },
{ projectId: "proj-456" },
);
```

### Environment variables

| Variable | Description |
| ---------------- | -------------------------------------------------------- |
| `ONECLI_API_KEY` | User API key (`oc_...`). Used when `apiKey` is not passed to constructor. |
| `ONECLI_URL` | Base URL of OneCLI instance. Defaults to `https://api.onecli.sh`. |
---

## API Reference

Expand All @@ -89,53 +123,204 @@ Main SDK client.
new OneCLI(options?: OneCLIOptions)
```

| Option | Type | Required | Default | Description |
| --------- | -------- | -------- | ----------------------------------- | ------------------------------- |
| `apiKey` | `string` | No | `ONECLI_API_KEY` env var | User API key (`oc_...`) |
| `url` | `string` | No | `ONECLI_URL` or `https://api.onecli.sh` | Base URL of the OneCLI instance |
| `timeout` | `number` | No | `5000` | Request timeout in milliseconds |
| Option | Type | Default | Description |
| ------------ | -------- | -------------------------------- | ---------------------------------------------------------------------- |
| `apiKey` | `string` | `ONECLI_API_KEY` env var | API key (`oc_...` for project keys, `oc_org_...` for org keys) |
| `url` | `string` | `ONECLI_URL` or `https://api.onecli.sh` | Base URL of the OneCLI instance |
| `timeout` | `number` | `5000` | Request timeout in milliseconds |
| `gatewayUrl` | `string` | `ONECLI_GATEWAY_URL` env var | Gateway URL for manual approval polling (auto-resolved if not set) |
| `projectId` | `string` | `ONECLI_PROJECT_ID` env var | Default project ID for org-level API keys (can be overridden per-operation) |

---

### Container configuration

#### `onecli.getContainerConfig()`
#### `onecli.getContainerConfig(options?)`

Fetch the raw container configuration from OneCLI.

```typescript
const config = await onecli.getContainerConfig();
// Returns: { env, caCertificate, caCertificateContainerPath }
console.log(config.env); // { HTTPS_PROXY: "...", HTTP_PROXY: "...", ... }
console.log(config.caCertificate); // PEM-formatted CA certificate
console.log(config.caCertificateContainerPath); // /tmp/onecli-proxy-ca.pem

// Fetch config for a specific agent
const agentConfig = await onecli.getContainerConfig({ agent: "my-agent" });

// With org-level API key, specify the target project
const config = await onecli.getContainerConfig({ projectId: "proj-123" });
```

| Option | Type | Description |
| ----------- | -------- | ------------------------------------------------------------------------ |
| `agent` | `string` | Agent identifier to fetch config for (uses default agent if omitted) |
| `projectId` | `string` | Project ID override for org-level API keys |

**Returns** `{ env, caCertificate, caCertificateContainerPath }`

**Throws** `OneCLIRequestError` on non-200 response.

#### `onecli.applyContainerConfig(args, options?)`

Fetch config and push Docker flags onto the `args` array. Returns `true` on success, `false` on failure (graceful degradation).
Fetch config and push Docker flags onto the `args` array. Returns `true` on success, `false` if OneCLI was unreachable (graceful degradation).

```typescript
const args = ["run", "-i", "--rm", "my-image"];
const active = await onecli.applyContainerConfig(args, {
combineCaBundle: true, // Merge system + OneCLI CAs (default: true)
addHostMapping: true, // Add --add-host on Linux (default: true)
combineCaBundle: true,
addHostMapping: true,
});
```

| Option | Type | Default | Description |
| ---------------- | --------- | ------- | ---------------------------------------------- |
| `combineCaBundle`| `boolean` | `true` | Build combined CA bundle for system-wide trust |
| `addHostMapping` | `boolean` | `true` | Add `host.docker.internal` mapping on Linux |
| Option | Type | Default | Description |
| ----------------- | --------- | ------- | ---------------------------------------------- |
| `combineCaBundle` | `boolean` | `true` | Build combined CA bundle for system-wide trust |
| `addHostMapping` | `boolean` | `true` | Add `host.docker.internal` mapping on Linux |
| `agent` | `string` | | Agent identifier to fetch config for |
| `projectId` | `string` | | Project ID override for org-level API keys |

**What it does:**
1. Fetches `/v1/container-config` with `Authorization: Bearer {apiKey}`
2. Pushes `-e KEY=VALUE` for each server-controlled environment variable
3. Writes CA certificate to a temp file and mounts it into the container
4. Builds a combined CA bundle (system CAs + OneCLI CA) so curl, Python, Go, etc. also trust OneCLI
1. Fetches container config from OneCLI with Bearer auth
2. Pushes `-e KEY=VALUE` for each environment variable
3. Writes the CA certificate to a temp file and mounts it with `-v`
4. Builds a combined CA bundle (system CAs + OneCLI CA) so all tools trust OneCLI
5. Adds `--add-host host.docker.internal:host-gateway` on Linux

If OneCLI is unreachable, returns `false` without mutating the args array.

---

### Error Classes
### Agent management

#### `onecli.createAgent(input, options?)`

Create a new agent.

```typescript
const agent = await onecli.createAgent({
name: "My Agent",
identifier: "my-agent",
});

console.log(agent.id); // Agent ID
console.log(agent.identifier); // "my-agent"
console.log(agent.createdAt); // ISO 8601 timestamp
```

| Input | Type | Description |
| ------------ | -------- | ---------------------------------------------------------------------------- |
| `name` | `string` | Display name for the agent |
| `identifier` | `string` | Unique identifier (lowercase letters, numbers, hyphens, starts with a letter) |

**Returns** `{ id, name, identifier, createdAt }`

#### `onecli.ensureAgent(input, options?)`

Ensure an agent exists. Creates it if missing, returns normally if it already exists.

```typescript
const result = await onecli.ensureAgent({
name: "My Agent",
identifier: "my-agent",
});

console.log(result.created); // true if newly created, false if already existed
```

**Returns** `{ name, identifier, created }`

---

### Project provisioning

> **Cloud-only feature.** Calling `provisionProject()` against an OSS instance throws `OneCLIError`.

#### `onecli.provisionProject(input?, options?)`

Pre-create a user account with a project and API key. The API key works immediately. Requires admin or owner role.

```typescript
const result = await onecli.provisionProject({
role: "member",
skipOnboarding: true,
});

console.log(result.apiKey); // oc_... (usable immediately)
console.log(result.claimUrl); // https://app.onecli.sh/claim?token=...
console.log(result.projectId);
```

| Input | Type | Default | Description |
| ---------------- | --------------------- | ---------- | --------------------------------------------- |
| `role` | `"admin" \| "member"` | `"member"` | Role the provisioned user will have in the org |
| `skipOnboarding` | `boolean` | `true` | Whether the user skips the onboarding wizard |

**Returns**

| Field | Type | Description |
| ----------- | -------- | -------------------------------------------------------- |
| `id` | `string` | Provision record ID |
| `userId` | `string` | Placeholder user ID (becomes the real user after claim) |
| `projectId` | `string` | Pre-created project ID |
| `apiKey` | `string` | API key for the provisioned project (usable immediately) |
| `claimUrl` | `string` | URL the user visits to claim the account |
| `expiresAt` | `string` | Expiration timestamp (ISO 8601) |

**Throws** `OneCLIError` if called against an OSS instance. **Throws** `OneCLIRequestError` with status 403 if the API key doesn't belong to an admin/owner.

---

### Manual approval

#### `onecli.configureManualApproval(callback, options?)`

Register a callback that's invoked whenever an agent request needs human approval. Starts background long-polling to the gateway. Returns a handle to stop polling.

```typescript
const handle = onecli.configureManualApproval(async (request) => {
console.log(`${request.method} ${request.url}`);
console.log(`Agent: ${request.agent.name}`);

if (request.bodyPreview) {
console.log(`Body: ${request.bodyPreview}`);
}

// Return 'approve' to forward the request, 'deny' to block it
return "approve";
});

// Stop polling on shutdown
process.on("SIGTERM", () => handle.stop());
```

The callback is called once per pending approval. Multiple approvals are handled concurrently, and each callback runs independently. If the callback throws or the decision fails to submit, the same request is retried on the next poll cycle.

**Callback parameter: `ApprovalRequest`**

| Field | Type | Description |
| ---------------- | ----------------------------------------------------------- | ------------------------------------------------- |
| `id` | `string` | Unique approval ID |
| `method` | `string` | HTTP method (`GET`, `POST`, `DELETE`, etc.) |
| `url` | `string` | Full request URL |
| `host` | `string` | Hostname |
| `path` | `string` | Request path |
| `headers` | `Record<string, string>` | Sanitized request headers (no credentials) |
| `bodyPreview` | `string \| null` | First 4KB of the request body, or `null` |
| `agent` | `{ id: string; name: string; externalId: string \| null }` | The agent that made the request |
| `createdAt` | `string` | When the request arrived (ISO 8601) |
| `expiresAt` | `string` | When the approval expires (ISO 8601) |
| `timeoutSeconds` | `number` | Seconds until auto-deny (300) |

**Returns** `ManualApprovalHandle` with a `stop()` method to disconnect.

---

### Error classes

#### `OneCLIError`

General SDK error (e.g. missing `apiKey`).
General SDK error (e.g., missing API key).

```typescript
import { OneCLIError } from "@onecli-sh/sdk";
Expand All @@ -162,23 +347,38 @@ try {

### Types

All types are exported for use in your own code:

```typescript
import type {
OneCLIOptions,
RequestOptions,
ContainerConfig,
GetContainerConfigOptions,
ApplyContainerConfigOptions,
CreateAgentInput,
CreateAgentResponse,
EnsureAgentResponse,
ApprovalRequest,
ManualApprovalCallback,
ManualApprovalHandle,
ProvisionProjectInput,
ProvisionProjectResponse,
} from "@onecli-sh/sdk";
```

## How It Works

OneCLI acts as a MITM proxy for containerized agents. When a container makes HTTPS requests to intercepted domains (e.g. `api.anthropic.com`), OneCLI:
OneCLI runs on the host machine and acts as a gateway for containerized agents. When a container makes HTTPS requests to intercepted domains (e.g. `api.anthropic.com`), OneCLI:

1. Terminates TLS using a local CA certificate
2. Inspects the request and injects real credentials
2. Inspects the request and injects real credentials (replacing placeholder tokens)
3. Forwards the request to the upstream service
4. Returns the response to the container

**Containers never see real API keys.** They only have placeholder tokens that OneCLI swaps out transparently.

**Containers never see real API keys.** The SDK configures containers with the right environment variables (`HTTPS_PROXY`, `HTTP_PROXY`, `NODE_EXTRA_CA_CERTS`) and CA certificate mounts so this works automatically.
The SDK configures containers with the right environment variables (`HTTPS_PROXY`, `HTTP_PROXY`) and CA certificate mounts so this works automatically.

## Development

Expand Down
Loading