routstrd 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +16 -217
- package/dist/daemon/index.js +471 -396
- package/dist/index.js +10238 -31672
- package/package.json +2 -1
- package/src/cli.ts +291 -208
- package/src/integrations/claudecode.ts +19 -40
- package/src/integrations/openclaw.ts +8 -34
- package/src/integrations/opencode.ts +8 -34
- package/src/integrations/pi.ts +7 -34
- package/src/integrations/registry.ts +4 -12
- package/src/tui/usage/data.ts +19 -7
- package/src/utils/clients.ts +304 -0
- package/src/utils/config.ts +2 -0
- package/src/utils/daemon-client.ts +84 -13
- package/src/utils/nip98.ts +102 -0
- package/src/daemon/http/index.ts +0 -1130
- package/src/daemon/index.ts +0 -242
- package/src/daemon/wallet/index.ts +0 -122
- package/src/index.ts +0 -4
- package/src/integrations/index.ts +0 -76
- package/src/tui/usage/index.ts +0 -1
|
@@ -3,44 +3,20 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import type { RoutstrdConfig } from "../utils/config";
|
|
5
5
|
import { logger } from "../utils/logger";
|
|
6
|
-
import type { SdkStore } from "@routstr/sdk";
|
|
7
6
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
8
|
-
import {
|
|
7
|
+
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
9
8
|
|
|
10
9
|
export async function installClaudeCodeIntegration(
|
|
11
10
|
config: RoutstrdConfig,
|
|
12
|
-
|
|
11
|
+
apiKey: string,
|
|
13
12
|
integrationConfig: IntegrationConfig,
|
|
14
13
|
): Promise<void> {
|
|
15
|
-
const {
|
|
14
|
+
const { name, configPath } = integrationConfig;
|
|
16
15
|
|
|
17
16
|
logger.log(`\nInstalling routstr configuration in ${configPath}...`);
|
|
17
|
+
logger.log(`Using API key for ${name}`);
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
// Get or create clientId entry
|
|
22
|
-
const state = store.getState();
|
|
23
|
-
const existingClient = (state.clientIds || []).find(
|
|
24
|
-
(c: { clientId: string }) => c.clientId === clientId,
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
let apiKey: string;
|
|
28
|
-
if (existingClient) {
|
|
29
|
-
apiKey = existingClient.apiKey;
|
|
30
|
-
logger.log(`Using existing API key for ${name}`);
|
|
31
|
-
} else {
|
|
32
|
-
apiKey = generateApiKey();
|
|
33
|
-
store.getState().setClientIds((prev) => [
|
|
34
|
-
...(prev || []),
|
|
35
|
-
{
|
|
36
|
-
clientId,
|
|
37
|
-
name,
|
|
38
|
-
apiKey,
|
|
39
|
-
createdAt: Date.now(),
|
|
40
|
-
},
|
|
41
|
-
]);
|
|
42
|
-
logger.log(`Created new API key for ${name}`);
|
|
43
|
-
}
|
|
19
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
44
20
|
|
|
45
21
|
let settings: {
|
|
46
22
|
env?: Record<string, string>;
|
|
@@ -60,23 +36,26 @@ export async function installClaudeCodeIntegration(
|
|
|
60
36
|
}
|
|
61
37
|
|
|
62
38
|
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
63
|
-
settings.env["ANTHROPIC_BASE_URL"] =
|
|
39
|
+
settings.env["ANTHROPIC_BASE_URL"] = baseUrl;
|
|
64
40
|
|
|
65
41
|
try {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const models = data.output?.models || [];
|
|
42
|
+
const data = await callDaemon("/models");
|
|
43
|
+
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
|
69
44
|
|
|
70
45
|
if (models.length >= 3) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
const opus = models[0]!;
|
|
47
|
+
const sonnet = models[1]!;
|
|
48
|
+
const haiku = models[2]!;
|
|
49
|
+
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
|
|
50
|
+
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
|
|
51
|
+
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
|
|
52
|
+
logger.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
|
|
75
53
|
} else if (models.length > 0) {
|
|
54
|
+
const model = models[0]!;
|
|
76
55
|
logger.log(`Only ${models.length} models available, falling back to defaults.`);
|
|
77
|
-
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] =
|
|
78
|
-
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] =
|
|
79
|
-
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] =
|
|
56
|
+
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model.id;
|
|
57
|
+
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model.id;
|
|
58
|
+
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model.id;
|
|
80
59
|
} else {
|
|
81
60
|
logger.log("No models available from routstr daemon.");
|
|
82
61
|
}
|
|
@@ -3,9 +3,8 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import type { RoutstrdConfig } from "../utils/config";
|
|
5
5
|
import { logger } from "../utils/logger";
|
|
6
|
-
import type { SdkStore } from "@routstr/sdk";
|
|
7
6
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
8
|
-
import {
|
|
7
|
+
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
9
8
|
|
|
10
9
|
const OPENCLAW_PROVIDER_ID = "routstr";
|
|
11
10
|
const OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
|
@@ -52,39 +51,15 @@ function toAlias(modelId: string): string {
|
|
|
52
51
|
|
|
53
52
|
export async function installOpenClawIntegration(
|
|
54
53
|
config: RoutstrdConfig,
|
|
55
|
-
|
|
54
|
+
apiKey: string,
|
|
56
55
|
integrationConfig: IntegrationConfig,
|
|
57
56
|
): Promise<void> {
|
|
58
|
-
const {
|
|
57
|
+
const { name, configPath } = integrationConfig;
|
|
59
58
|
|
|
60
59
|
logger.log("\nInstalling routstr models in openclaw.json...");
|
|
60
|
+
logger.log(`Using API key for ${name}`);
|
|
61
61
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
// Get or create clientId entry for OpenClaw
|
|
65
|
-
const state = store.getState();
|
|
66
|
-
const existingClient = (state.clientIds || []).find(
|
|
67
|
-
(c: { clientId: string }) => c.clientId === clientId,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
let apiKey: string;
|
|
71
|
-
if (existingClient) {
|
|
72
|
-
apiKey = existingClient.apiKey;
|
|
73
|
-
logger.log(`Using existing API key for ${name}`);
|
|
74
|
-
} else {
|
|
75
|
-
apiKey = generateApiKey();
|
|
76
|
-
// Add new clientId entry using proper store action
|
|
77
|
-
store.getState().setClientIds((prev) => [
|
|
78
|
-
...(prev || []),
|
|
79
|
-
{
|
|
80
|
-
clientId,
|
|
81
|
-
name,
|
|
82
|
-
apiKey,
|
|
83
|
-
createdAt: Date.now(),
|
|
84
|
-
},
|
|
85
|
-
]);
|
|
86
|
-
logger.log(`Created new API key for ${name}`);
|
|
87
|
-
}
|
|
62
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
88
63
|
|
|
89
64
|
let openclawConfig: OpenClawConfig = {};
|
|
90
65
|
|
|
@@ -113,9 +88,8 @@ export async function installOpenClawIntegration(
|
|
|
113
88
|
try {
|
|
114
89
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
115
90
|
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const models = data.output?.models || [];
|
|
91
|
+
const data = await callDaemon("/models");
|
|
92
|
+
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
|
119
93
|
|
|
120
94
|
if (models.length === 0) {
|
|
121
95
|
logger.log("No models found from routstr daemon.");
|
|
@@ -129,7 +103,7 @@ export async function installOpenClawIntegration(
|
|
|
129
103
|
}));
|
|
130
104
|
|
|
131
105
|
openclawConfig.models.providers[OPENCLAW_PROVIDER_ID] = {
|
|
132
|
-
baseUrl:
|
|
106
|
+
baseUrl: `${baseUrl}/v1`,
|
|
133
107
|
apiKey,
|
|
134
108
|
api: "openai-completions",
|
|
135
109
|
models: providerModels,
|
|
@@ -3,47 +3,22 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import type { RoutstrdConfig } from "../utils/config";
|
|
5
5
|
import { logger } from "../utils/logger";
|
|
6
|
-
import type { SdkStore } from "@routstr/sdk";
|
|
7
6
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
8
|
-
import {
|
|
7
|
+
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
9
8
|
|
|
10
9
|
const OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
|
11
10
|
|
|
12
11
|
export async function installOpencodeIntegration(
|
|
13
12
|
config: RoutstrdConfig,
|
|
14
|
-
|
|
13
|
+
apiKey: string,
|
|
15
14
|
integrationConfig: IntegrationConfig,
|
|
16
15
|
): Promise<void> {
|
|
17
|
-
const {
|
|
16
|
+
const { name, configPath } = integrationConfig;
|
|
18
17
|
|
|
19
18
|
logger.log("\nInstalling routstr models in opencode.json...");
|
|
19
|
+
logger.log(`Using API key for ${name}`);
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
// Get or create clientId entry for OpenCode
|
|
24
|
-
const state = store.getState();
|
|
25
|
-
const existingClient = (state.clientIds || []).find(
|
|
26
|
-
(c: { clientId: string }) => c.clientId === clientId,
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
let apiKey: string;
|
|
30
|
-
if (existingClient) {
|
|
31
|
-
apiKey = existingClient.apiKey;
|
|
32
|
-
logger.log(`Using existing API key for ${name}`);
|
|
33
|
-
} else {
|
|
34
|
-
apiKey = generateApiKey();
|
|
35
|
-
// Add new clientId entry using proper store action
|
|
36
|
-
store.getState().setClientIds((prev) => [
|
|
37
|
-
...(prev || []),
|
|
38
|
-
{
|
|
39
|
-
clientId,
|
|
40
|
-
name,
|
|
41
|
-
apiKey,
|
|
42
|
-
createdAt: Date.now(),
|
|
43
|
-
},
|
|
44
|
-
]);
|
|
45
|
-
logger.log(`Created new API key for ${name}`);
|
|
46
|
-
}
|
|
21
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
47
22
|
|
|
48
23
|
let opencodeConfig: {
|
|
49
24
|
provider?: Record<string, {
|
|
@@ -77,9 +52,8 @@ export async function installOpencodeIntegration(
|
|
|
77
52
|
try {
|
|
78
53
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
79
54
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const models = data.output?.models || [];
|
|
55
|
+
const data = await callDaemon("/models");
|
|
56
|
+
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
|
83
57
|
|
|
84
58
|
if (models.length === 0) {
|
|
85
59
|
logger.log("No models found from routstr daemon.");
|
|
@@ -95,7 +69,7 @@ export async function installOpencodeIntegration(
|
|
|
95
69
|
npm: "@ai-sdk/openai-compatible",
|
|
96
70
|
name: "routstr",
|
|
97
71
|
options: {
|
|
98
|
-
baseURL:
|
|
72
|
+
baseURL: `${baseUrl}/`,
|
|
99
73
|
apiKey,
|
|
100
74
|
includeUsage: true,
|
|
101
75
|
},
|
package/src/integrations/pi.ts
CHANGED
|
@@ -3,9 +3,8 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import type { RoutstrdConfig } from "../utils/config";
|
|
5
5
|
import { logger } from "../utils/logger";
|
|
6
|
-
import type { SdkStore } from "@routstr/sdk";
|
|
7
6
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
8
|
-
import {
|
|
7
|
+
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
9
8
|
|
|
10
9
|
type PiModelEntry = {
|
|
11
10
|
id: string;
|
|
@@ -24,40 +23,15 @@ type PiConfig = {
|
|
|
24
23
|
|
|
25
24
|
export async function installPiIntegration(
|
|
26
25
|
config: RoutstrdConfig,
|
|
27
|
-
|
|
26
|
+
apiKey: string,
|
|
28
27
|
integrationConfig: IntegrationConfig,
|
|
29
28
|
): Promise<void> {
|
|
30
|
-
const {
|
|
29
|
+
const { name, configPath } = integrationConfig;
|
|
31
30
|
|
|
32
31
|
logger.log("\nInstalling routstr models in pi models.json...");
|
|
32
|
+
logger.log(`Using API key for ${name}`);
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
const baseUrl = `http://localhost:${port}/v1`;
|
|
36
|
-
|
|
37
|
-
// Get or create clientId entry for Pi Agent
|
|
38
|
-
const state = store.getState();
|
|
39
|
-
const existingClient = (state.clientIds || []).find(
|
|
40
|
-
(c: { clientId: string }) => c.clientId === clientId,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
let apiKey: string;
|
|
44
|
-
if (existingClient) {
|
|
45
|
-
apiKey = existingClient.apiKey;
|
|
46
|
-
logger.log(`Using existing API key for ${name}`);
|
|
47
|
-
} else {
|
|
48
|
-
apiKey = generateApiKey();
|
|
49
|
-
// Add new clientId entry using proper store action
|
|
50
|
-
store.getState().setClientIds((prev) => [
|
|
51
|
-
...(prev || []),
|
|
52
|
-
{
|
|
53
|
-
clientId,
|
|
54
|
-
name,
|
|
55
|
-
apiKey,
|
|
56
|
-
createdAt: Date.now(),
|
|
57
|
-
},
|
|
58
|
-
]);
|
|
59
|
-
logger.log(`Created new API key for ${name}`);
|
|
60
|
-
}
|
|
34
|
+
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
|
|
61
35
|
|
|
62
36
|
let piConfig: PiConfig = {};
|
|
63
37
|
|
|
@@ -78,9 +52,8 @@ export async function installPiIntegration(
|
|
|
78
52
|
// Ensure directory exists
|
|
79
53
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
80
54
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const models = data.output?.models || [];
|
|
55
|
+
const data = await callDaemon("/models");
|
|
56
|
+
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
|
84
57
|
|
|
85
58
|
if (models.length === 0) {
|
|
86
59
|
logger.log("No models found from routstr daemon.");
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { randomBytes } from "crypto";
|
|
2
1
|
import { join } from "path";
|
|
3
2
|
import type { RoutstrdConfig } from "../utils/config";
|
|
4
|
-
import type { SdkStore } from "@routstr/sdk";
|
|
5
3
|
import { installOpencodeIntegration } from "./opencode";
|
|
6
4
|
import { installPiIntegration } from "./pi";
|
|
7
5
|
import { installOpenClawIntegration } from "./openclaw";
|
|
@@ -18,14 +16,9 @@ export type RoutstrModel = {
|
|
|
18
16
|
name?: string;
|
|
19
17
|
};
|
|
20
18
|
|
|
21
|
-
export function generateApiKey(): string {
|
|
22
|
-
const bytes = randomBytes(24);
|
|
23
|
-
return `sk-${bytes.toString("hex")}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
export type IntegrationFn = (
|
|
27
20
|
config: RoutstrdConfig,
|
|
28
|
-
|
|
21
|
+
apiKey: string,
|
|
29
22
|
integrationConfig: IntegrationConfig,
|
|
30
23
|
) => Promise<void>;
|
|
31
24
|
|
|
@@ -60,16 +53,15 @@ export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
|
|
60
53
|
};
|
|
61
54
|
|
|
62
55
|
export async function runIntegrationsForClients(
|
|
63
|
-
clientIds: Array<{ clientId: string }>,
|
|
56
|
+
clientIds: Array<{ clientId: string; apiKey?: string }>,
|
|
64
57
|
config: RoutstrdConfig,
|
|
65
|
-
store: SdkStore,
|
|
66
58
|
): Promise<void> {
|
|
67
59
|
for (const client of clientIds) {
|
|
68
60
|
const integrationFn = CLIENT_INTEGRATIONS[client.clientId];
|
|
69
61
|
const integrationConfig = CLIENT_CONFIGS[client.clientId];
|
|
70
|
-
if (integrationFn && integrationConfig) {
|
|
62
|
+
if (integrationFn && integrationConfig && client.apiKey) {
|
|
71
63
|
try {
|
|
72
|
-
await integrationFn(config,
|
|
64
|
+
await integrationFn(config, client.apiKey, integrationConfig);
|
|
73
65
|
} catch (error) {
|
|
74
66
|
console.error(`Integration failed for ${client.clientId}:`, error);
|
|
75
67
|
}
|
package/src/tui/usage/data.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UsageTrackingEntry } from "../../daemon/types.ts";
|
|
2
|
-
import { callDaemon, isDaemonRunning } from "../../utils/daemon-client.ts";
|
|
2
|
+
import { callDaemon, getNpubSuffix, isDaemonRunning, loadConfig } from "../../utils/daemon-client.ts";
|
|
3
3
|
import type { ClientStats, DayStats, ModelStats, ProviderStats, UsageStats } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
export interface BalanceKey {
|
|
@@ -82,12 +82,24 @@ export async function fetchUsage(limit = 10000): Promise<UsageStats | null> {
|
|
|
82
82
|
const result = await callDaemon(`/usage?limit=${limit}`);
|
|
83
83
|
if (result.error) return null;
|
|
84
84
|
|
|
85
|
-
// The daemon returns { output: [...] } where output is the entries array directly
|
|
85
|
+
// The daemon returns { output: [...] } where output is the entries array directly.
|
|
86
|
+
// In remote daemon mode, client IDs are suffixed with the last 7 chars of our npub.
|
|
87
|
+
// Fetch the full daemon result, but only display/aggregate entries for our clients.
|
|
86
88
|
const entries = result.output as UsageTrackingEntry[] | undefined;
|
|
87
89
|
const entriesArray = Array.isArray(entries) ? entries : [];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
90
|
+
const suffix = getNpubSuffix(await loadConfig());
|
|
91
|
+
const suffixStr = suffix ? `-${suffix}` : null;
|
|
92
|
+
const visibleEntries = suffixStr
|
|
93
|
+
? entriesArray
|
|
94
|
+
.filter((entry) => entry.client?.endsWith(suffixStr))
|
|
95
|
+
.map((entry) => ({
|
|
96
|
+
...entry,
|
|
97
|
+
client: entry.client?.slice(0, -suffixStr.length),
|
|
98
|
+
}))
|
|
99
|
+
: entriesArray;
|
|
100
|
+
|
|
101
|
+
// Calculate totals from visible entries
|
|
102
|
+
const totals = visibleEntries.reduce(
|
|
91
103
|
(acc, entry) => ({
|
|
92
104
|
promptTokens: acc.promptTokens + entry.promptTokens,
|
|
93
105
|
completionTokens: acc.completionTokens + entry.completionTokens,
|
|
@@ -98,8 +110,8 @@ export async function fetchUsage(limit = 10000): Promise<UsageStats | null> {
|
|
|
98
110
|
);
|
|
99
111
|
|
|
100
112
|
return {
|
|
101
|
-
entries:
|
|
102
|
-
totalEntries:
|
|
113
|
+
entries: visibleEntries,
|
|
114
|
+
totalEntries: visibleEntries.length,
|
|
103
115
|
totalSatsCost: totals.satsCost,
|
|
104
116
|
recentSatsCost: totals.satsCost, // For now, recent = total since we don't have time window
|
|
105
117
|
limit,
|