routstrd 0.2.22 → 0.3.1

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.
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
2
2
  import { readFile, writeFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import type { RoutstrdConfig } from "../utils/config";
5
- import { logger } from "../utils/logger";
6
5
  import type { IntegrationConfig, RoutstrModel } from "./registry";
7
6
  import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
8
7
 
@@ -13,8 +12,8 @@ export async function installClaudeCodeIntegration(
13
12
  ): Promise<void> {
14
13
  const { name, configPath } = integrationConfig;
15
14
 
16
- logger.log(`\nInstalling routstr configuration in ${configPath}...`);
17
- logger.log(`Using API key for ${name}`);
15
+ console.log(`\nInstalling routstr configuration in ${configPath}...`);
16
+ console.log(`Using API key for ${name}`);
18
17
 
19
18
  const baseUrl = getDaemonBaseUrl(config);
20
19
 
@@ -28,7 +27,7 @@ export async function installClaudeCodeIntegration(
28
27
  settings = JSON.parse(content);
29
28
  }
30
29
  } catch (error) {
31
- logger.error(`Error reading ${configPath}, creating new one.`);
30
+ console.error(`Error reading ${configPath}, creating new one.`);
32
31
  }
33
32
 
34
33
  if (!settings.env) {
@@ -49,25 +48,25 @@ export async function installClaudeCodeIntegration(
49
48
  settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
50
49
  settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
51
50
  settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
52
- logger.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
51
+ console.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
53
52
  } else if (models.length > 0) {
54
53
  const model = models[0]!;
55
- logger.log(`Only ${models.length} models available, falling back to defaults.`);
54
+ console.log(`Only ${models.length} models available, falling back to defaults.`);
56
55
  settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model.id;
57
56
  settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model.id;
58
57
  settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model.id;
59
58
  } else {
60
- logger.log("No models available from routstr daemon.");
59
+ console.log("No models available from routstr daemon.");
61
60
  }
62
61
  } catch (error) {
63
- logger.error("Failed to fetch models for Claude Code integration:", error);
62
+ console.error("Failed to fetch models for Claude Code integration:", error);
64
63
  }
65
64
 
66
65
  try {
67
66
  mkdirSync(dirname(configPath), { recursive: true });
68
67
  await writeFile(configPath, JSON.stringify(settings, null, 2));
69
- logger.log(`Successfully updated ${configPath} with routstr settings.`);
68
+ console.log(`Successfully updated ${configPath} with routstr settings.`);
70
69
  } catch (error) {
71
- logger.error(`Failed to write to ${configPath}:`, error);
70
+ console.error(`Failed to write to ${configPath}:`, error);
72
71
  }
73
72
  }
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
2
2
  import { readFile, writeFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import type { RoutstrdConfig } from "../utils/config";
5
- import { logger } from "../utils/logger";
6
5
  import type { IntegrationConfig, RoutstrModel } from "./registry";
7
6
  import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
8
7
 
@@ -13,8 +12,8 @@ export async function installHermesIntegration(
13
12
  ): Promise<void> {
14
13
  const { name, configPath } = integrationConfig;
15
14
 
16
- logger.log(`\nInstalling routstr configuration in ${configPath}...`);
17
- logger.log(`Using API key for ${name}`);
15
+ console.log(`\nInstalling routstr configuration in ${configPath}...`);
16
+ console.log(`Using API key for ${name}`);
18
17
 
19
18
  const baseUrl = getDaemonBaseUrl(config);
20
19
  const baseUrlV1 = `${baseUrl}/v1`;
@@ -27,16 +26,16 @@ export async function installHermesIntegration(
27
26
 
28
27
  if (models.length >= 3) {
29
28
  defaultModel = models[2]!.id;
30
- logger.log(`Set default model to 3rd available model: ${defaultModel}`);
29
+ console.log(`Set default model to 3rd available model: ${defaultModel}`);
31
30
  } else if (models.length > 0) {
32
31
  defaultModel = models[0]!.id;
33
- logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
32
+ console.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
34
33
  } else {
35
- logger.log("No models available from routstr daemon, using fallback default.");
34
+ console.log("No models available from routstr daemon, using fallback default.");
36
35
  }
37
36
  } catch (error) {
38
- logger.error("Failed to fetch models for Hermes integration:", error);
39
- logger.log("Using fallback default model.");
37
+ console.error("Failed to fetch models for Hermes integration:", error);
38
+ console.log("Using fallback default model.");
40
39
  }
41
40
 
42
41
  let content = "";
@@ -45,7 +44,7 @@ export async function installHermesIntegration(
45
44
  content = await readFile(configPath, "utf-8");
46
45
  }
47
46
  } catch (error) {
48
- logger.error(`Error reading ${configPath}, creating new one.`);
47
+ console.error(`Error reading ${configPath}, creating new one.`);
49
48
  }
50
49
 
51
50
  // Remove existing model block
@@ -80,8 +79,8 @@ export async function installHermesIntegration(
80
79
  try {
81
80
  mkdirSync(dirname(configPath), { recursive: true });
82
81
  await writeFile(configPath, newContent);
83
- logger.log(`Successfully updated ${configPath} with routstr settings.`);
82
+ console.log(`Successfully updated ${configPath} with routstr settings.`);
84
83
  } catch (error) {
85
- logger.error(`Failed to write to ${configPath}:`, error);
84
+ console.error(`Failed to write to ${configPath}:`, error);
86
85
  }
87
86
  }
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
2
2
  import { readFile, writeFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import type { RoutstrdConfig } from "../utils/config";
5
- import { logger } from "../utils/logger";
6
5
  import type { IntegrationConfig, RoutstrModel } from "./registry";
7
6
  import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
8
7
 
@@ -56,8 +55,8 @@ export async function installOpenClawIntegration(
56
55
  ): Promise<void> {
57
56
  const { name, configPath } = integrationConfig;
58
57
 
59
- logger.log("\nInstalling routstr models in openclaw.json...");
60
- logger.log(`Using API key for ${name}`);
58
+ console.log("\nInstalling routstr models in openclaw.json...");
59
+ console.log(`Using API key for ${name}`);
61
60
 
62
61
  const baseUrl = getDaemonBaseUrl(config);
63
62
 
@@ -92,7 +91,7 @@ export async function installOpenClawIntegration(
92
91
  const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
93
92
 
94
93
  if (models.length === 0) {
95
- logger.log("No models found from routstr daemon.");
94
+ console.log("No models found from routstr daemon.");
96
95
  return;
97
96
  }
98
97
 
@@ -134,8 +133,8 @@ export async function installOpenClawIntegration(
134
133
  // openclawConfig.agents.defaults.models = aliasMap;
135
134
 
136
135
  await writeFile(configPath, JSON.stringify(openclawConfig, null, 2));
137
- logger.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
136
+ console.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
138
137
  } catch (error) {
139
- logger.error("Failed to install models in openclaw.json:", error);
138
+ console.error("Failed to install models in openclaw.json:", error);
140
139
  }
141
140
  }
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
2
2
  import { readFile, writeFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import type { RoutstrdConfig } from "../utils/config";
5
- import { logger } from "../utils/logger";
6
5
  import type { IntegrationConfig, RoutstrModel } from "./registry";
7
6
  import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
8
7
 
@@ -15,8 +14,8 @@ export async function installOpencodeIntegration(
15
14
  ): Promise<void> {
16
15
  const { name, configPath } = integrationConfig;
17
16
 
18
- logger.log("\nInstalling routstr models in opencode.json...");
19
- logger.log(`Using API key for ${name}`);
17
+ console.log("\nInstalling routstr models in opencode.json...");
18
+ console.log(`Using API key for ${name}`);
20
19
 
21
20
  const baseUrl = getDaemonBaseUrl(config);
22
21
 
@@ -56,7 +55,7 @@ export async function installOpencodeIntegration(
56
55
  const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
57
56
 
58
57
  if (models.length === 0) {
59
- logger.log("No models found from routstr daemon.");
58
+ console.log("No models found from routstr daemon.");
60
59
  return;
61
60
  }
62
61
 
@@ -78,8 +77,8 @@ export async function installOpencodeIntegration(
78
77
  opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
79
78
 
80
79
  await writeFile(configPath, JSON.stringify(opencodeConfig, null, 2));
81
- logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
80
+ console.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
82
81
  } catch (error) {
83
- logger.error("Failed to install models in opencode.json:", error);
82
+ console.error("Failed to install models in opencode.json:", error);
84
83
  }
85
84
  }
@@ -2,12 +2,12 @@ import { existsSync, mkdirSync } from "fs";
2
2
  import { readFile, writeFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import type { RoutstrdConfig } from "../utils/config";
5
- import { logger } from "../utils/logger";
6
5
  import type { IntegrationConfig, RoutstrModel } from "./registry";
7
6
  import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
8
7
 
9
8
  type PiModelEntry = {
10
9
  id: string;
10
+ contextWindow?: number;
11
11
  };
12
12
 
13
13
  type PiProviderConfig = {
@@ -28,8 +28,8 @@ export async function installPiIntegration(
28
28
  ): Promise<void> {
29
29
  const { name, configPath } = integrationConfig;
30
30
 
31
- logger.log("\nInstalling routstr models in pi models.json...");
32
- logger.log(`Using API key for ${name}`);
31
+ console.log("\nInstalling routstr models in pi models.json...");
32
+ console.log(`Using API key for ${name}`);
33
33
 
34
34
  const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
35
35
 
@@ -56,13 +56,17 @@ export async function installPiIntegration(
56
56
  const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
57
57
 
58
58
  if (models.length === 0) {
59
- logger.log("No models found from routstr daemon.");
59
+ console.log("No models found from routstr daemon.");
60
60
  return;
61
61
  }
62
62
 
63
- const providerModels: PiModelEntry[] = models.map((model) => ({
64
- id: model.id,
65
- }));
63
+ const providerModels: PiModelEntry[] = models.map((model) => {
64
+ const entry: PiModelEntry = { id: model.id };
65
+ if (model.context_length !== undefined && model.context_length > 0) {
66
+ entry.contextWindow = model.context_length;
67
+ }
68
+ return entry;
69
+ });
66
70
 
67
71
  piConfig.providers["routstr"] = {
68
72
  baseUrl,
@@ -72,8 +76,8 @@ export async function installPiIntegration(
72
76
  };
73
77
 
74
78
  await writeFile(configPath, JSON.stringify(piConfig, null, 2));
75
- logger.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
79
+ console.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
76
80
  } catch (error) {
77
- logger.error("Failed to install models in pi models.json:", error);
81
+ console.error("Failed to install models in pi models.json:", error);
78
82
  }
79
83
  }
@@ -15,6 +15,7 @@ export interface IntegrationConfig {
15
15
  export type RoutstrModel = {
16
16
  id: string;
17
17
  name?: string;
18
+ context_length?: number;
18
19
  };
19
20
 
20
21
  export type IntegrationFn = (
@@ -1,4 +1,5 @@
1
1
  import {
2
+ callAuth,
2
3
  callDaemon,
3
4
  loadConfig,
4
5
  getDaemonBaseUrl,
@@ -56,7 +57,7 @@ export function getClientsFromStore(store: { getState(): any }): ClientEntry[] {
56
57
  * Use this when running remotely (CLI in remote mode).
57
58
  */
58
59
  export async function getClientsList(): Promise<ClientEntry[]> {
59
- const result = await callDaemon("/clients");
60
+ const result = await callAuth("/clients");
60
61
  const clients = (
61
62
  result.output as
62
63
  | {
@@ -107,7 +108,7 @@ export async function addDaemonClient(
107
108
  return { client, created: false };
108
109
  }
109
110
 
110
- const result = await callDaemon("/clients/add", {
111
+ const result = await callAuth("/clients/add", {
111
112
  method: "POST",
112
113
  body: { name, id: derivedId },
113
114
  });
@@ -159,7 +160,7 @@ export async function listClientsAction(): Promise<void> {
159
160
  export async function deleteClientAction(id: string): Promise<void> {
160
161
  await ensureDaemonRunning();
161
162
 
162
- const result = await callDaemon("/clients/delete", {
163
+ const result = await callAuth("/clients/delete", {
163
164
  method: "POST",
164
165
  body: { id },
165
166
  });
@@ -7,15 +7,42 @@ export const DB_PATH = `${CONFIG_DIR}/routstr.db`;
7
7
  export const CONFIG_FILE = `${CONFIG_DIR}/config.json`;
8
8
  export const LOGS_DIR = `${CONFIG_DIR}/logs`;
9
9
 
10
+ /** NWC auto-refill configuration */
11
+ export interface NwcAutoRefillConfig {
12
+ /** Whether auto-refill is enabled */
13
+ enabled: boolean;
14
+ /** Refill when Cashu balance drops below this many sats */
15
+ threshold: number;
16
+ /** Refill this many sats at a time */
17
+ amount: number;
18
+ /** Minimum time between refills in milliseconds */
19
+ cooldownMs: number;
20
+ }
21
+
22
+ /** NWC configuration section */
23
+ export interface NwcConfig {
24
+ /** NWC mode: "funding_source" = NWC funds the cocod Cashu wallet */
25
+ mode: "funding_source" | "standalone";
26
+ /** NWC connection string (nostr+walletconnect://...) */
27
+ connectionString?: string;
28
+ /** Auto-refill settings */
29
+ autoRefill?: NwcAutoRefillConfig;
30
+ }
31
+
10
32
  export interface RoutstrdConfig {
11
33
  port: number;
12
34
  provider: string | null;
13
35
  cocodPath: string | null;
14
36
  mode?: "xcashu" | "apikeys";
15
37
  daemonUrl?: string;
38
+ /** URL of the auth proxy (routstrd-auth) for management endpoints (npubs, clients, usage).
39
+ * Defaults to daemonUrl or localhost:{port} if not set. */
40
+ authUrl?: string;
16
41
  nsec?: string;
17
42
  /** Nostr hex pubkey for routstr review/model events (kind 38425/38423). */
18
43
  routstrPubkey?: string;
44
+ /** NWC integration configuration */
45
+ nwc?: NwcConfig;
19
46
  }
20
47
 
21
48
  export const DEFAULT_CONFIG: RoutstrdConfig = {
@@ -35,13 +35,20 @@ export function getDaemonBaseUrl(config: RoutstrdConfig): string {
35
35
  );
36
36
  }
37
37
 
38
- export async function callDaemon(
38
+ export function getAuthBaseUrl(config: RoutstrdConfig): string {
39
+ if (config.authUrl) {
40
+ return config.authUrl.replace(/\/$/, "");
41
+ }
42
+ return getDaemonBaseUrl(config);
43
+ }
44
+
45
+ async function _callUrl(
46
+ baseUrl: string,
39
47
  path: string,
40
- options: { method?: "GET" | "POST" | "PATCH" | "DELETE"; body?: object } = {},
48
+ options: { method?: "GET" | "POST" | "PATCH" | "DELETE"; body?: object },
49
+ config: RoutstrdConfig,
41
50
  ): Promise<CommandResponse> {
42
51
  const { method = "GET", body } = options;
43
- const config = await loadConfig();
44
- const baseUrl = getDaemonBaseUrl(config);
45
52
  const url = `${baseUrl}${path}`;
46
53
 
47
54
  const bodyString = body ? JSON.stringify(body) : undefined;
@@ -50,7 +57,7 @@ export async function callDaemon(
50
57
  : undefined;
51
58
 
52
59
  let authorization: string | undefined;
53
- if (config.daemonUrl && config.nsec) {
60
+ if ((config.daemonUrl || config.authUrl) && config.nsec) {
54
61
  const secretKey = parseSecretKey(config.nsec);
55
62
  authorization = await createNIP98Authorization(
56
63
  secretKey,
@@ -77,6 +84,26 @@ export async function callDaemon(
77
84
  return response.json() as Promise<CommandResponse>;
78
85
  }
79
86
 
87
+ export async function callDaemon(
88
+ path: string,
89
+ options: { method?: "GET" | "POST" | "PATCH" | "DELETE"; body?: object } = {},
90
+ ): Promise<CommandResponse> {
91
+ const config = await loadConfig();
92
+ const baseUrl = getDaemonBaseUrl(config);
93
+ return _callUrl(baseUrl, path, options, config);
94
+ }
95
+
96
+ /** Like callDaemon but sends requests to the auth proxy URL instead.
97
+ * Falls back to the daemon URL if no authUrl is configured. */
98
+ export async function callAuth(
99
+ path: string,
100
+ options: { method?: "GET" | "POST" | "PATCH" | "DELETE"; body?: object } = {},
101
+ ): Promise<CommandResponse> {
102
+ const config = await loadConfig();
103
+ const baseUrl = getAuthBaseUrl(config);
104
+ return _callUrl(baseUrl, path, options, config);
105
+ }
106
+
80
107
  export async function isDaemonRunning(): Promise<boolean> {
81
108
  try {
82
109
  const config = await loadConfig();