mirror of
https://github.com/Routstr/routstrd.git
synced 2026-07-30 15:46:14 +00:00
routstrd skill. decreased refresh interval to 21 mins.
centralized config in registry.
This commit is contained in:
@@ -0,0 +1,260 @@
|
|||||||
|
# routstrd CLI Reference
|
||||||
|
|
||||||
|
Routstr daemon — a Bun-based CLI tool that runs a background HTTP server for the Routstr protocol. It integrates with `cocod` for Cashu wallet management and routes LLM requests to available providers.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```sh
|
||||||
|
routstrd onboard # Initialize (creates config, sets up cocod)
|
||||||
|
routstrd start # Start the daemon
|
||||||
|
routstrd stop # Stop the daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
After onboarding, the daemon listens at `http://localhost:8008` and exposes an OpenAI-compatible API.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `routstrd onboard`
|
||||||
|
|
||||||
|
Initialize routstrd for the first time:
|
||||||
|
- Creates `~/.routstrd/` config directory
|
||||||
|
- Creates `~/.routstrd/config.json` with defaults (port 8008, apikeys mode)
|
||||||
|
- Installs `cocod` globally via bun if not present
|
||||||
|
- Runs `cocod init` to set up the wallet
|
||||||
|
- Starts the daemon and configures integrations
|
||||||
|
|
||||||
|
### `routstrd start`
|
||||||
|
|
||||||
|
Start the background daemon process.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--port <port>` | Port to listen on (default: 8008) |
|
||||||
|
| `-p, --provider <provider>` | Default provider to use |
|
||||||
|
|
||||||
|
### `routstrd stop`
|
||||||
|
|
||||||
|
Stop the background daemon.
|
||||||
|
|
||||||
|
### `routstrd restart`
|
||||||
|
|
||||||
|
Restart the daemon (stops if running, then starts).
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--port <port>` | Port to listen on |
|
||||||
|
| `-p, --provider <provider>` | Default provider to use |
|
||||||
|
|
||||||
|
### `routstrd status`
|
||||||
|
|
||||||
|
Check daemon and wallet status. Returns JSON with current state.
|
||||||
|
|
||||||
|
### `routstrd ping`
|
||||||
|
|
||||||
|
Test connection to the daemon.
|
||||||
|
|
||||||
|
### `routstrd balance`
|
||||||
|
|
||||||
|
Get wallet and API key balances. Shows per-mint wallet balances, per-key API balances, and a grand total (all in sats).
|
||||||
|
|
||||||
|
### `routstrd models`
|
||||||
|
|
||||||
|
List available routstr21 models (discovered via Nostr).
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-r, --refresh` | Force refresh models from Nostr |
|
||||||
|
|
||||||
|
### `routstrd usage`
|
||||||
|
|
||||||
|
Show recent usage logs and total sats cost.
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-n, --limit <number>` | 10 | Number of recent entries (max 1000) |
|
||||||
|
|
||||||
|
Shows timestamp, model, provider, sats cost, token counts, and request ID for each entry.
|
||||||
|
|
||||||
|
### `routstrd providers`
|
||||||
|
|
||||||
|
List and manage providers (subcommand required).
|
||||||
|
|
||||||
|
#### `routstrd providers list`
|
||||||
|
|
||||||
|
List all providers with their enabled/disabled status. Shows index, status, and base URL.
|
||||||
|
|
||||||
|
```
|
||||||
|
Providers (12 total, 2 disabled):
|
||||||
|
|
||||||
|
[0] enabled https://provider1.example.com
|
||||||
|
[1] enabled https://provider2.example.com
|
||||||
|
[2] DISABLED https://provider3.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `routstrd providers disable <indices...>`
|
||||||
|
|
||||||
|
Disable providers by their index numbers.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
routstrd providers disable 0 2 5
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `routstrd providers enable <indices...>`
|
||||||
|
|
||||||
|
Enable providers by their index numbers.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
routstrd providers enable 0 2 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### `routstrd clients`
|
||||||
|
|
||||||
|
List and manage API clients (subcommand required).
|
||||||
|
|
||||||
|
#### `routstrd clients list`
|
||||||
|
|
||||||
|
List all registered clients with their ID, name, API key, and creation date.
|
||||||
|
|
||||||
|
#### `routstrd clients add`
|
||||||
|
|
||||||
|
Add a new client.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-n, --name <name>` | **Required.** Client name |
|
||||||
|
|
||||||
|
Returns the client ID and API key for use with the OpenAI-compatible API.
|
||||||
|
|
||||||
|
### `routstrd mode`
|
||||||
|
|
||||||
|
Interactive prompt to set the client mode:
|
||||||
|
1. **lazyrefund/apikeys** (default) — Pseudonymous accounts kept with Routstr nodes, refunded after 5 mins if unused.
|
||||||
|
2. **xcashu** (coming soon) — Balances never kept with nodes, all refunded in response.
|
||||||
|
|
||||||
|
Changing mode restarts the daemon automatically.
|
||||||
|
|
||||||
|
### `routstrd monitor`
|
||||||
|
|
||||||
|
Open an interactive TUI (htop-like) for usage monitoring.
|
||||||
|
|
||||||
|
### `routstrd logs`
|
||||||
|
|
||||||
|
View daemon logs.
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-f, --follow` | false | Follow log output (like `tail -f`) |
|
||||||
|
| `-n, --lines <number>` | 50 | Number of lines to show |
|
||||||
|
|
||||||
|
Log files are stored at `~/.routstrd/logs/YYYY-MM-DD.log`.
|
||||||
|
|
||||||
|
## Wallet Commands
|
||||||
|
|
||||||
|
### `routstrd wallet status`
|
||||||
|
|
||||||
|
Check wallet status.
|
||||||
|
|
||||||
|
### `routstrd wallet unlock <passphrase>`
|
||||||
|
|
||||||
|
Unlock the wallet with a passphrase.
|
||||||
|
|
||||||
|
### `routstrd wallet balance`
|
||||||
|
|
||||||
|
Get wallet balance.
|
||||||
|
|
||||||
|
### `routstrd wallet receive cashu <token>`
|
||||||
|
|
||||||
|
Receive funds via a Cashu token.
|
||||||
|
|
||||||
|
### `routstrd wallet receive bolt11 <amount>`
|
||||||
|
|
||||||
|
Create a Lightning invoice to receive funds. Displays a QR code.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--mint-url <url>` | Mint URL to use |
|
||||||
|
|
||||||
|
### `routstrd wallet send cashu <amount>`
|
||||||
|
|
||||||
|
Create a Cashu token to send.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--mint-url <url>` | Mint URL to use |
|
||||||
|
|
||||||
|
### `routstrd wallet send bolt11 <invoice>`
|
||||||
|
|
||||||
|
Pay a Lightning invoice.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--mint-url <url>` | Mint URL to use |
|
||||||
|
|
||||||
|
### `routstrd wallet mints list`
|
||||||
|
|
||||||
|
List configured wallet mints.
|
||||||
|
|
||||||
|
### `routstrd wallet mints add <url>`
|
||||||
|
|
||||||
|
Add a new mint by URL.
|
||||||
|
|
||||||
|
### `routstrd wallet mints info <url>`
|
||||||
|
|
||||||
|
Get info about a specific mint.
|
||||||
|
|
||||||
|
## Daemon API
|
||||||
|
|
||||||
|
The daemon exposes an OpenAI-compatible HTTP API at `http://localhost:8008`:
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Health check endpoint.
|
||||||
|
|
||||||
|
### `GET /v1/models`
|
||||||
|
|
||||||
|
List available models (OpenAI-compatible).
|
||||||
|
|
||||||
|
### `POST /v1/chat/completions`
|
||||||
|
|
||||||
|
Route a chat completion request.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "model-id",
|
||||||
|
"messages": [{ "role": "user", "content": "Hello" }],
|
||||||
|
"stream": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Config file: `~/.routstrd/config.json`
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `port` | number | 8008 | Daemon HTTP port |
|
||||||
|
| `provider` | string\|null | null | Default provider URL |
|
||||||
|
| `cocodPath` | string\|null | null | Custom path to cocod executable |
|
||||||
|
| `mode` | string | `"apikeys"` | Client mode (`apikeys` or `xcashu`) |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `ROUTSTRD_DIR` | `~/.routstrd` | Config directory |
|
||||||
|
| `ROUTSTRD_SOCKET` | `~/.routstrd/routstrd.sock` | IPC socket path |
|
||||||
|
| `ROUTSTRD_PID` | `~/.routstrd/routstrd.pid` | PID file path |
|
||||||
|
|
||||||
|
## Pi Integration
|
||||||
|
|
||||||
|
When `routstrd onboard` runs, it automatically configures a `routstr` provider in `pi`'s `models.json` with an OpenAI-compatible base URL and API key. This allows pi (the AI coding agent) to use Routstr providers seamlessly.
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
| Path | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `~/.routstrd/config.json` | Configuration |
|
||||||
|
| `~/.routstrd/routstr.db` | SQLite database |
|
||||||
|
| `~/.routstrd/routstrd.sock` | IPC socket |
|
||||||
|
| `~/.routstrd/routstrd.pid` | PID file |
|
||||||
|
| `~/.routstrd/logs/YYYY-MM-DD.log` | Daily log files |
|
||||||
+3
-7
@@ -90,7 +90,7 @@ async function main(): Promise<void> {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
const REFRESH_INTERVAL_MS = 3.5 * 60 * 60 * 1000; // 3.5 hours
|
const REFRESH_INTERVAL_MS = 21 * 60 * 1000; // 21 mins
|
||||||
|
|
||||||
// Recurring job to refresh routstr21 models
|
// Recurring job to refresh routstr21 models
|
||||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
@@ -110,9 +110,7 @@ async function main(): Promise<void> {
|
|||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const clientIds = state.clientIds || [];
|
const clientIds = state.clientIds || [];
|
||||||
if (clientIds.length > 0) {
|
if (clientIds.length > 0) {
|
||||||
logger.log(
|
logger.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
||||||
`Refreshing ${clientIds.length} client integration(s)...`,
|
|
||||||
);
|
|
||||||
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
||||||
logger.log("Client integrations refreshed.");
|
logger.log("Client integrations refreshed.");
|
||||||
}
|
}
|
||||||
@@ -221,9 +219,7 @@ async function main(): Promise<void> {
|
|||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const clientIds = state.clientIds || [];
|
const clientIds = state.clientIds || [];
|
||||||
if (clientIds.length > 0) {
|
if (clientIds.length > 0) {
|
||||||
logger.log(
|
logger.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
||||||
`Refreshing ${clientIds.length} client integration(s)...`,
|
|
||||||
);
|
|
||||||
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
await runIntegrationsForClients(clientIds, updatedConfig, store);
|
||||||
logger.log("Client integrations refreshed.");
|
logger.log("Client integrations refreshed.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { installOpencodeIntegration } from "./opencode";
|
|||||||
import { installOpenClawIntegration } from "./openclaw";
|
import { installOpenClawIntegration } from "./openclaw";
|
||||||
import { installPiIntegration } from "./pi";
|
import { installPiIntegration } from "./pi";
|
||||||
import type { SdkStore } from "@routstr/sdk";
|
import type { SdkStore } from "@routstr/sdk";
|
||||||
export { CLIENT_INTEGRATIONS, runIntegrationsForClients } from "./registry";
|
import { CLIENT_CONFIGS } from "./registry";
|
||||||
|
export { CLIENT_INTEGRATIONS, CLIENT_CONFIGS, runIntegrationsForClients } from "./registry";
|
||||||
|
|
||||||
function ask(question: string): Promise<string> {
|
function ask(question: string): Promise<string> {
|
||||||
process.stdout.write(question);
|
process.stdout.write(question);
|
||||||
@@ -50,17 +51,17 @@ export async function setupIntegration(
|
|||||||
const choice = parseChoice(answer);
|
const choice = parseChoice(answer);
|
||||||
|
|
||||||
if (choice === 1) {
|
if (choice === 1) {
|
||||||
await installOpencodeIntegration(config, store);
|
await installOpencodeIntegration(config, store, CLIENT_CONFIGS.opencode!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice === 2) {
|
if (choice === 2) {
|
||||||
await installOpenClawIntegration(config, store);
|
await installOpenClawIntegration(config, store, CLIENT_CONFIGS.openclaw!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice === 3) {
|
if (choice === 3) {
|
||||||
await installPiIntegration(config, store);
|
await installPiIntegration(config, store, CLIENT_CONFIGS["pi-agent"]!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
import { randomBytes } from "crypto";
|
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import { readFile, writeFile } from "fs/promises";
|
import { readFile, writeFile } from "fs/promises";
|
||||||
import { dirname, join } from "path";
|
import { dirname } from "path";
|
||||||
import type { RoutstrdConfig } from "../utils/config";
|
import type { RoutstrdConfig } from "../utils/config";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import type { SdkStore } from "@routstr/sdk";
|
import type { SdkStore } from "@routstr/sdk";
|
||||||
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
||||||
|
import { generateApiKey } from "./registry";
|
||||||
|
|
||||||
const OPENCLAW_CONFIG_PATH = join(process.env.HOME || "", ".openclaw/openclaw.json");
|
|
||||||
const OPENCLAW_PROVIDER_ID = "routstr";
|
const OPENCLAW_PROVIDER_ID = "routstr";
|
||||||
const OPENCLAW_API_BASE = "http://localhost:8008/v1";
|
|
||||||
const OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
const OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
||||||
const OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
|
const OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
|
||||||
const OPENCLAW_CLIENT_ID = "openclaw";
|
|
||||||
const OPENCLAW_NAME = "OpenClaw";
|
|
||||||
|
|
||||||
type RoutstrModel = {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OpenClawModelEntry = {
|
type OpenClawModelEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -58,15 +50,13 @@ function toAlias(modelId: string): string {
|
|||||||
return modelId;
|
return modelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateApiKey(): string {
|
|
||||||
const bytes = randomBytes(24);
|
|
||||||
return `sk-${bytes.toString("hex")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function installOpenClawIntegration(
|
export async function installOpenClawIntegration(
|
||||||
config: RoutstrdConfig,
|
config: RoutstrdConfig,
|
||||||
store: SdkStore,
|
store: SdkStore,
|
||||||
|
integrationConfig: IntegrationConfig,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { clientId, name, configPath } = integrationConfig;
|
||||||
|
|
||||||
logger.log("\nInstalling routstr models in openclaw.json...");
|
logger.log("\nInstalling routstr models in openclaw.json...");
|
||||||
|
|
||||||
const port = config.port || 8008;
|
const port = config.port || 8008;
|
||||||
@@ -74,33 +64,33 @@ export async function installOpenClawIntegration(
|
|||||||
// Get or create clientId entry for OpenClaw
|
// Get or create clientId entry for OpenClaw
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const existingClient = (state.clientIds || []).find(
|
const existingClient = (state.clientIds || []).find(
|
||||||
(c: { clientId: string }) => c.clientId === OPENCLAW_CLIENT_ID,
|
(c: { clientId: string }) => c.clientId === clientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
let apiKey: string;
|
let apiKey: string;
|
||||||
if (existingClient) {
|
if (existingClient) {
|
||||||
apiKey = existingClient.apiKey;
|
apiKey = existingClient.apiKey;
|
||||||
logger.log(`Using existing API key for ${OPENCLAW_NAME}`);
|
logger.log(`Using existing API key for ${name}`);
|
||||||
} else {
|
} else {
|
||||||
apiKey = generateApiKey();
|
apiKey = generateApiKey();
|
||||||
// Add new clientId entry using proper store action
|
// Add new clientId entry using proper store action
|
||||||
store.getState().setClientIds((prev) => [
|
store.getState().setClientIds((prev) => [
|
||||||
...(prev || []),
|
...(prev || []),
|
||||||
{
|
{
|
||||||
clientId: OPENCLAW_CLIENT_ID,
|
clientId,
|
||||||
name: OPENCLAW_NAME,
|
name,
|
||||||
apiKey,
|
apiKey,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
logger.log(`Created new API key for ${OPENCLAW_NAME}`);
|
logger.log(`Created new API key for ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let openclawConfig: OpenClawConfig = {};
|
let openclawConfig: OpenClawConfig = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (existsSync(OPENCLAW_CONFIG_PATH)) {
|
if (existsSync(configPath)) {
|
||||||
const content = await readFile(OPENCLAW_CONFIG_PATH, "utf-8");
|
const content = await readFile(configPath, "utf-8");
|
||||||
openclawConfig = JSON.parse(content) as OpenClawConfig;
|
openclawConfig = JSON.parse(content) as OpenClawConfig;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -121,7 +111,7 @@ export async function installOpenClawIntegration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mkdirSync(dirname(OPENCLAW_CONFIG_PATH), { recursive: true });
|
mkdirSync(dirname(configPath), { recursive: true });
|
||||||
|
|
||||||
const response = await fetch(`http://localhost:${port}/models`);
|
const response = await fetch(`http://localhost:${port}/models`);
|
||||||
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
||||||
@@ -139,7 +129,7 @@ export async function installOpenClawIntegration(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
|
openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
|
||||||
baseUrl: OPENCLAW_API_BASE,
|
baseUrl: `http://localhost:${port}/v1`,
|
||||||
apiKey,
|
apiKey,
|
||||||
api: "openai-completions",
|
api: "openai-completions",
|
||||||
models: providerModels,
|
models: providerModels,
|
||||||
@@ -169,7 +159,7 @@ export async function installOpenClawIntegration(
|
|||||||
// }
|
// }
|
||||||
// openclawConfig.agents.defaults.models = aliasMap;
|
// openclawConfig.agents.defaults.models = aliasMap;
|
||||||
|
|
||||||
await writeFile(OPENCLAW_CONFIG_PATH, JSON.stringify(openclawConfig, null, 2));
|
await writeFile(configPath, JSON.stringify(openclawConfig, null, 2));
|
||||||
logger.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
|
logger.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to install models in openclaw.json:", error);
|
logger.error("Failed to install models in openclaw.json:", error);
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
import { randomBytes } from "crypto";
|
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import { readFile, writeFile } from "fs/promises";
|
import { readFile, writeFile } from "fs/promises";
|
||||||
import { dirname, join } from "path";
|
import { dirname } from "path";
|
||||||
import type { RoutstrdConfig } from "../utils/config";
|
import type { RoutstrdConfig } from "../utils/config";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import type { SdkStore } from "@routstr/sdk";
|
import type { SdkStore } from "@routstr/sdk";
|
||||||
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
||||||
|
import { generateApiKey } from "./registry";
|
||||||
|
|
||||||
const OPENCODE_CONFIG_PATH = join(process.env.HOME || "", ".config/opencode/opencode.json");
|
|
||||||
const OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
const OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
||||||
const OPENCODE_CLIENT_ID = "opencode";
|
|
||||||
const OPENCODE_NAME = "OpenCode";
|
|
||||||
|
|
||||||
type RoutstrModel = {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function generateApiKey(): string {
|
|
||||||
const bytes = randomBytes(24);
|
|
||||||
return `sk-${bytes.toString("hex")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function installOpencodeIntegration(
|
export async function installOpencodeIntegration(
|
||||||
config: RoutstrdConfig,
|
config: RoutstrdConfig,
|
||||||
store: SdkStore,
|
store: SdkStore,
|
||||||
|
integrationConfig: IntegrationConfig,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { clientId, name, configPath } = integrationConfig;
|
||||||
|
|
||||||
logger.log("\nInstalling routstr models in opencode.json...");
|
logger.log("\nInstalling routstr models in opencode.json...");
|
||||||
|
|
||||||
const port = config.port || 8008;
|
const port = config.port || 8008;
|
||||||
@@ -32,26 +23,26 @@ export async function installOpencodeIntegration(
|
|||||||
// Get or create clientId entry for OpenCode
|
// Get or create clientId entry for OpenCode
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const existingClient = (state.clientIds || []).find(
|
const existingClient = (state.clientIds || []).find(
|
||||||
(c: { clientId: string }) => c.clientId === OPENCODE_CLIENT_ID,
|
(c: { clientId: string }) => c.clientId === clientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
let apiKey: string;
|
let apiKey: string;
|
||||||
if (existingClient) {
|
if (existingClient) {
|
||||||
apiKey = existingClient.apiKey;
|
apiKey = existingClient.apiKey;
|
||||||
logger.log(`Using existing API key for ${OPENCODE_NAME}`);
|
logger.log(`Using existing API key for ${name}`);
|
||||||
} else {
|
} else {
|
||||||
apiKey = generateApiKey();
|
apiKey = generateApiKey();
|
||||||
// Add new clientId entry using proper store action
|
// Add new clientId entry using proper store action
|
||||||
store.getState().setClientIds((prev) => [
|
store.getState().setClientIds((prev) => [
|
||||||
...(prev || []),
|
...(prev || []),
|
||||||
{
|
{
|
||||||
clientId: OPENCODE_CLIENT_ID,
|
clientId,
|
||||||
name: OPENCODE_NAME,
|
name,
|
||||||
apiKey,
|
apiKey,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
logger.log(`Created new API key for ${OPENCODE_NAME}`);
|
logger.log(`Created new API key for ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opencodeConfig: {
|
let opencodeConfig: {
|
||||||
@@ -69,8 +60,8 @@ export async function installOpencodeIntegration(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (existsSync(OPENCODE_CONFIG_PATH)) {
|
if (existsSync(configPath)) {
|
||||||
const content = await readFile(OPENCODE_CONFIG_PATH, "utf-8");
|
const content = await readFile(configPath, "utf-8");
|
||||||
opencodeConfig = JSON.parse(content);
|
opencodeConfig = JSON.parse(content);
|
||||||
} else {
|
} else {
|
||||||
opencodeConfig = { provider: {} };
|
opencodeConfig = { provider: {} };
|
||||||
@@ -84,7 +75,7 @@ export async function installOpencodeIntegration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mkdirSync(dirname(OPENCODE_CONFIG_PATH), { recursive: true });
|
mkdirSync(dirname(configPath), { recursive: true });
|
||||||
|
|
||||||
const response = await fetch(`http://localhost:${port}/models`);
|
const response = await fetch(`http://localhost:${port}/models`);
|
||||||
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
||||||
@@ -112,7 +103,7 @@ export async function installOpencodeIntegration(
|
|||||||
};
|
};
|
||||||
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
||||||
|
|
||||||
await writeFile(OPENCODE_CONFIG_PATH, JSON.stringify(opencodeConfig, null, 2));
|
await writeFile(configPath, JSON.stringify(opencodeConfig, null, 2));
|
||||||
logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to install models in opencode.json:", error);
|
logger.error("Failed to install models in opencode.json:", error);
|
||||||
|
|||||||
+15
-25
@@ -1,19 +1,11 @@
|
|||||||
import { randomBytes } from "crypto";
|
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import { readFile, writeFile } from "fs/promises";
|
import { readFile, writeFile } from "fs/promises";
|
||||||
import { dirname, join } from "path";
|
import { dirname } from "path";
|
||||||
import type { RoutstrdConfig } from "../utils/config";
|
import type { RoutstrdConfig } from "../utils/config";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import type { SdkStore } from "@routstr/sdk";
|
import type { SdkStore } from "@routstr/sdk";
|
||||||
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
||||||
const PI_CONFIG_PATH = join(process.env.HOME || "", ".pi/agent/models.json");
|
import { generateApiKey } from "./registry";
|
||||||
const PI_CLIENT_ID = "pi-agent";
|
|
||||||
const PI_NAME = "Pi Agent";
|
|
||||||
|
|
||||||
type RoutstrModel = {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PiModelEntry = {
|
type PiModelEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -30,15 +22,13 @@ type PiConfig = {
|
|||||||
providers?: Record<string, PiProviderConfig>;
|
providers?: Record<string, PiProviderConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateApiKey(): string {
|
|
||||||
const bytes = randomBytes(24);
|
|
||||||
return `sk-${bytes.toString("hex")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function installPiIntegration(
|
export async function installPiIntegration(
|
||||||
config: RoutstrdConfig,
|
config: RoutstrdConfig,
|
||||||
store: SdkStore,
|
store: SdkStore,
|
||||||
|
integrationConfig: IntegrationConfig,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { clientId, name, configPath } = integrationConfig;
|
||||||
|
|
||||||
logger.log("\nInstalling routstr models in pi models.json...");
|
logger.log("\nInstalling routstr models in pi models.json...");
|
||||||
|
|
||||||
const port = config.port || 8008;
|
const port = config.port || 8008;
|
||||||
@@ -47,33 +37,33 @@ export async function installPiIntegration(
|
|||||||
// Get or create clientId entry for Pi Agent
|
// Get or create clientId entry for Pi Agent
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const existingClient = (state.clientIds || []).find(
|
const existingClient = (state.clientIds || []).find(
|
||||||
(c: { clientId: string }) => c.clientId === PI_CLIENT_ID,
|
(c: { clientId: string }) => c.clientId === clientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
let apiKey: string;
|
let apiKey: string;
|
||||||
if (existingClient) {
|
if (existingClient) {
|
||||||
apiKey = existingClient.apiKey;
|
apiKey = existingClient.apiKey;
|
||||||
logger.log(`Using existing API key for ${PI_NAME}`);
|
logger.log(`Using existing API key for ${name}`);
|
||||||
} else {
|
} else {
|
||||||
apiKey = generateApiKey();
|
apiKey = generateApiKey();
|
||||||
// Add new clientId entry using proper store action
|
// Add new clientId entry using proper store action
|
||||||
store.getState().setClientIds((prev) => [
|
store.getState().setClientIds((prev) => [
|
||||||
...(prev || []),
|
...(prev || []),
|
||||||
{
|
{
|
||||||
clientId: PI_CLIENT_ID,
|
clientId,
|
||||||
name: PI_NAME,
|
name,
|
||||||
apiKey,
|
apiKey,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
logger.log(`Created new API key for ${PI_NAME}`);
|
logger.log(`Created new API key for ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let piConfig: PiConfig = {};
|
let piConfig: PiConfig = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (existsSync(PI_CONFIG_PATH)) {
|
if (existsSync(configPath)) {
|
||||||
const content = await readFile(PI_CONFIG_PATH, "utf-8");
|
const content = await readFile(configPath, "utf-8");
|
||||||
piConfig = JSON.parse(content) as PiConfig;
|
piConfig = JSON.parse(content) as PiConfig;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -86,7 +76,7 @@ export async function installPiIntegration(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
mkdirSync(dirname(PI_CONFIG_PATH), { recursive: true });
|
mkdirSync(dirname(configPath), { recursive: true });
|
||||||
|
|
||||||
const response = await fetch(`http://localhost:${port}/models`);
|
const response = await fetch(`http://localhost:${port}/models`);
|
||||||
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
const data = await response.json() as { output?: { models: RoutstrModel[] } };
|
||||||
@@ -108,7 +98,7 @@ export async function installPiIntegration(
|
|||||||
models: providerModels,
|
models: providerModels,
|
||||||
};
|
};
|
||||||
|
|
||||||
await writeFile(PI_CONFIG_PATH, JSON.stringify(piConfig, null, 2));
|
await writeFile(configPath, JSON.stringify(piConfig, null, 2));
|
||||||
logger.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
|
logger.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to install models in pi models.json:", error);
|
logger.error("Failed to install models in pi models.json:", error);
|
||||||
|
|||||||
@@ -1,14 +1,51 @@
|
|||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import { join } from "path";
|
||||||
import type { RoutstrdConfig } from "../utils/config";
|
import type { RoutstrdConfig } from "../utils/config";
|
||||||
import type { SdkStore } from "@routstr/sdk";
|
import type { SdkStore } from "@routstr/sdk";
|
||||||
import { installOpencodeIntegration } from "./opencode";
|
import { installOpencodeIntegration } from "./opencode";
|
||||||
import { installPiIntegration } from "./pi";
|
import { installPiIntegration } from "./pi";
|
||||||
import { installOpenClawIntegration } from "./openclaw";
|
import { installOpenClawIntegration } from "./openclaw";
|
||||||
|
|
||||||
|
export interface IntegrationConfig {
|
||||||
|
clientId: string;
|
||||||
|
name: string;
|
||||||
|
configPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoutstrModel = {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateApiKey(): string {
|
||||||
|
const bytes = randomBytes(24);
|
||||||
|
return `sk-${bytes.toString("hex")}`;
|
||||||
|
}
|
||||||
|
|
||||||
export type IntegrationFn = (
|
export type IntegrationFn = (
|
||||||
config: RoutstrdConfig,
|
config: RoutstrdConfig,
|
||||||
store: SdkStore,
|
store: SdkStore,
|
||||||
|
integrationConfig: IntegrationConfig,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
export const CLIENT_CONFIGS: Record<string, IntegrationConfig> = {
|
||||||
|
opencode: {
|
||||||
|
clientId: "opencode",
|
||||||
|
name: "OpenCode",
|
||||||
|
configPath: join(process.env.HOME || "", ".config/opencode/opencode.json"),
|
||||||
|
},
|
||||||
|
"pi-agent": {
|
||||||
|
clientId: "pi-agent",
|
||||||
|
name: "Pi Agent",
|
||||||
|
configPath: join(process.env.HOME || "", ".pi/agent/models.json"),
|
||||||
|
},
|
||||||
|
openclaw: {
|
||||||
|
clientId: "openclaw",
|
||||||
|
name: "OpenClaw",
|
||||||
|
configPath: join(process.env.HOME || "", ".openclaw/openclaw.json"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
||||||
opencode: installOpencodeIntegration,
|
opencode: installOpencodeIntegration,
|
||||||
"pi-agent": installPiIntegration,
|
"pi-agent": installPiIntegration,
|
||||||
@@ -22,9 +59,10 @@ export async function runIntegrationsForClients(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
for (const client of clientIds) {
|
for (const client of clientIds) {
|
||||||
const integrationFn = CLIENT_INTEGRATIONS[client.clientId];
|
const integrationFn = CLIENT_INTEGRATIONS[client.clientId];
|
||||||
if (integrationFn) {
|
const integrationConfig = CLIENT_CONFIGS[client.clientId];
|
||||||
|
if (integrationFn && integrationConfig) {
|
||||||
try {
|
try {
|
||||||
await integrationFn(config, store);
|
await integrationFn(config, store, integrationConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Integration failed for ${client.clientId}:`, error);
|
console.error(`Integration failed for ${client.clientId}:`, error);
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
AUTH="Bearer sk-a2d0981bd84ebf214f4bfb861a273873bfe3e10e6af97533"
|
||||||
|
BASE_URL="http://localhost:8008/v1/chat/completions"
|
||||||
|
|
||||||
|
echo "=== Testing GPT-4 ==="
|
||||||
|
curl -s "$BASE_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: $AUTH" \
|
||||||
|
-d '{
|
||||||
|
"model": "gpt-4",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "Hello, how are you?"}
|
||||||
|
],
|
||||||
|
"max_tokens": 128
|
||||||
|
}' | python3 -m json.tool
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing GLM-4.7 ==="
|
||||||
|
curl -s "$BASE_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: $AUTH" \
|
||||||
|
-d '{
|
||||||
|
"model": "glm-4.7",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "Hello, how are you?"}
|
||||||
|
],
|
||||||
|
"max_tokens": 128
|
||||||
|
}' | python3 -m json.tool
|
||||||
Reference in New Issue
Block a user