routstrd 0.1.7 → 0.1.9

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.
@@ -863,242 +863,6 @@ export function createDaemonRequestHandler(deps: {
863
863
  return;
864
864
  }
865
865
 
866
- if (req.method === "POST" && url.pathname === "/providers/disable") {
867
- try {
868
- const bodyText = await readBody(req);
869
- const body = bodyText ? JSON.parse(bodyText) : {};
870
- const indices = body.indices as number[] | undefined;
871
-
872
- if (!Array.isArray(indices)) {
873
- res.writeHead(400, { "Content-Type": "application/json" });
874
- res.end(
875
- JSON.stringify({
876
- error: "Missing or invalid 'indices' field (expected number[]).",
877
- }),
878
- );
879
- return;
880
- }
881
-
882
- const state = deps.store.getState();
883
- const baseUrlsList: string[] = state.baseUrlsList || [];
884
- const disabledProviders: string[] = [
885
- ...(state.disabledProviders || []),
886
- ];
887
-
888
- const toDisable: string[] = [];
889
- for (const idx of indices) {
890
- if (
891
- typeof idx === "number" &&
892
- idx >= 0 &&
893
- idx < baseUrlsList.length
894
- ) {
895
- const baseUrl = baseUrlsList[idx]!;
896
- if (!disabledProviders.includes(baseUrl)) {
897
- disabledProviders.push(baseUrl);
898
- toDisable.push(baseUrl);
899
- }
900
- }
901
- }
902
-
903
- deps.store.getState().setDisabledProviders(disabledProviders);
904
-
905
- res.writeHead(200, { "Content-Type": "application/json" });
906
- res.end(
907
- JSON.stringify({
908
- output: {
909
- message: `Disabled ${toDisable.length} provider(s)`,
910
- disabled: toDisable,
911
- },
912
- }),
913
- );
914
- } catch (error) {
915
- res.writeHead(500, { "Content-Type": "application/json" });
916
- res.end(JSON.stringify({ error: String(error) }));
917
- }
918
- return;
919
- }
920
-
921
- if (req.method === "POST" && url.pathname === "/providers/enable") {
922
- try {
923
- const bodyText = await readBody(req);
924
- const body = bodyText ? JSON.parse(bodyText) : {};
925
- const indices = body.indices as number[] | undefined;
926
-
927
- if (!Array.isArray(indices)) {
928
- res.writeHead(400, { "Content-Type": "application/json" });
929
- res.end(
930
- JSON.stringify({
931
- error: "Missing or invalid 'indices' field (expected number[]).",
932
- }),
933
- );
934
- return;
935
- }
936
-
937
- const state = deps.store.getState();
938
- const baseUrlsList: string[] = state.baseUrlsList || [];
939
- const disabledProviders: string[] = [
940
- ...(state.disabledProviders || []),
941
- ];
942
-
943
- const toEnable: string[] = [];
944
- for (const idx of indices) {
945
- if (
946
- typeof idx === "number" &&
947
- idx >= 0 &&
948
- idx < baseUrlsList.length
949
- ) {
950
- const baseUrl = baseUrlsList[idx]!;
951
- const pos = disabledProviders.indexOf(baseUrl);
952
- if (pos !== -1) {
953
- disabledProviders.splice(pos, 1);
954
- toEnable.push(baseUrl);
955
- }
956
- }
957
- }
958
-
959
- deps.store.getState().setDisabledProviders(disabledProviders);
960
-
961
- res.writeHead(200, { "Content-Type": "application/json" });
962
- res.end(
963
- JSON.stringify({
964
- output: {
965
- message: `Enabled ${toEnable.length} provider(s)`,
966
- enabled: toEnable,
967
- },
968
- }),
969
- );
970
- } catch (error) {
971
- res.writeHead(500, { "Content-Type": "application/json" });
972
- res.end(JSON.stringify({ error: String(error) }));
973
- }
974
- return;
975
- }
976
-
977
- // Client management endpoints
978
- if (req.method === "GET" && url.pathname === "/clients") {
979
- try {
980
- const state = deps.store.getState();
981
- const clientIds = state.clientIds || [];
982
-
983
- const clients = clientIds.map(
984
- (c: {
985
- clientId: string;
986
- name: string;
987
- apiKey: string;
988
- createdAt: number;
989
- lastUsed?: number | null;
990
- }) => ({
991
- id: c.clientId,
992
- name: c.name,
993
- apiKey: c.apiKey,
994
- createdAt: c.createdAt,
995
- lastUsed: c.lastUsed,
996
- }),
997
- );
998
-
999
- res.writeHead(200, { "Content-Type": "application/json" });
1000
- res.end(
1001
- JSON.stringify({
1002
- output: {
1003
- clients,
1004
- totalCount: clients.length,
1005
- },
1006
- }),
1007
- );
1008
- } catch (error) {
1009
- res.writeHead(500, { "Content-Type": "application/json" });
1010
- res.end(JSON.stringify({ error: String(error) }));
1011
- }
1012
- return;
1013
- }
1014
-
1015
- if (req.method === "POST" && url.pathname === "/clients/add") {
1016
- try {
1017
- const bodyText = await readBody(req);
1018
- const body = bodyText ? JSON.parse(bodyText) : {};
1019
- const name = body.name as string | undefined;
1020
-
1021
- if (!name || typeof name !== "string" || name.trim() === "") {
1022
- res.writeHead(400, { "Content-Type": "application/json" });
1023
- res.end(
1024
- JSON.stringify({
1025
- error:
1026
- "Missing required 'name' field (must be a non-empty string).",
1027
- }),
1028
- );
1029
- return;
1030
- }
1031
-
1032
- const clientId = name
1033
- .toLowerCase()
1034
- .replace(/\s+/g, "-")
1035
- .replace(/[^a-z0-9-]/g, "");
1036
-
1037
- if (!clientId) {
1038
- res.writeHead(400, { "Content-Type": "application/json" });
1039
- res.end(
1040
- JSON.stringify({
1041
- error:
1042
- "Invalid client name. Must contain alphanumeric characters.",
1043
- }),
1044
- );
1045
- return;
1046
- }
1047
-
1048
- const state = deps.store.getState();
1049
- const existingClients = state.clientIds || [];
1050
- const existingClient = existingClients.find(
1051
- (c: { clientId: string }) => c.clientId === clientId,
1052
- );
1053
-
1054
- if (existingClient) {
1055
- res.writeHead(409, { "Content-Type": "application/json" });
1056
- res.end(
1057
- JSON.stringify({
1058
- error: `Client with id '${clientId}' already exists.`,
1059
- }),
1060
- );
1061
- return;
1062
- }
1063
-
1064
- const apiKey = generateApiKey();
1065
- const newClient = {
1066
- clientId,
1067
- name: name.trim(),
1068
- apiKey,
1069
- createdAt: Date.now(),
1070
- };
1071
-
1072
- deps.store
1073
- .getState()
1074
- .setClientIds((prev: typeof existingClients) => [
1075
- ...(prev || []),
1076
- newClient,
1077
- ]);
1078
-
1079
- logger.log(`Added client '${name}' with id '${clientId}'`);
1080
-
1081
- res.writeHead(200, { "Content-Type": "application/json" });
1082
- res.end(
1083
- JSON.stringify({
1084
- output: {
1085
- message: `Client '${name}' added successfully`,
1086
- client: {
1087
- id: clientId,
1088
- name: name.trim(),
1089
- apiKey,
1090
- createdAt: newClient.createdAt,
1091
- },
1092
- },
1093
- }),
1094
- );
1095
- } catch (error) {
1096
- res.writeHead(500, { "Content-Type": "application/json" });
1097
- res.end(JSON.stringify({ error: String(error) }));
1098
- }
1099
- return;
1100
- }
1101
-
1102
866
  if (req.method === "GET" && url.pathname === "/usage") {
1103
867
  try {
1104
868
  const output = await deps.usageTrackingDriver.list({
@@ -99,7 +99,7 @@ export async function createWalletAdapter(
99
99
  } catch (error) {
100
100
  const errorMessage =
101
101
  error instanceof Error ? error.message : String(error);
102
- logger.error("Error in walletAdapter receiveToken:", error);
102
+ logger.error("Error in walletAdapter receiveToken:", errorMessage);
103
103
  return { success: false, amount: 0, unit: "sat", message: errorMessage };
104
104
  }
105
105
  },
@@ -0,0 +1,77 @@
1
+ import { existsSync, mkdirSync } from "fs";
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+ import type { RoutstrdConfig } from "../utils/config";
5
+ import { logger } from "../utils/logger";
6
+ import type { SdkStore } from "@routstr/sdk";
7
+ import type { IntegrationConfig } from "./registry";
8
+ import { generateApiKey } from "./registry";
9
+
10
+ export async function installClaudeCodeIntegration(
11
+ config: RoutstrdConfig,
12
+ store: SdkStore,
13
+ integrationConfig: IntegrationConfig,
14
+ ): Promise<void> {
15
+ const { clientId, name, configPath } = integrationConfig;
16
+
17
+ logger.log(`\nInstalling routstr configuration in ${configPath}...`);
18
+
19
+ const port = config.port || 8008;
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
+ }
44
+
45
+ let settings: {
46
+ env?: Record<string, string>;
47
+ } = {};
48
+
49
+ try {
50
+ if (existsSync(configPath)) {
51
+ const content = await readFile(configPath, "utf-8");
52
+ settings = JSON.parse(content);
53
+ }
54
+ } catch (error) {
55
+ logger.error(`Error reading ${configPath}, creating new one.`);
56
+ }
57
+
58
+ if (!settings.env) {
59
+ settings.env = {};
60
+ }
61
+
62
+ settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
63
+ settings.env["ANTHROPIC_BASE_URL"] = `http://localhost:${port}`;
64
+
65
+ // Default models as requested
66
+ settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "gpt-5.4";
67
+ settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "claude-opus-4.7";
68
+ settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "minimax-m2.7";
69
+
70
+ try {
71
+ mkdirSync(dirname(configPath), { recursive: true });
72
+ await writeFile(configPath, JSON.stringify(settings, null, 2));
73
+ logger.log(`Successfully updated ${configPath} with routstr settings.`);
74
+ } catch (error) {
75
+ logger.error(`Failed to write to ${configPath}:`, error);
76
+ }
77
+ }
@@ -3,6 +3,7 @@ import { logger } from "../utils/logger";
3
3
  import { installOpencodeIntegration } from "./opencode";
4
4
  import { installOpenClawIntegration } from "./openclaw";
5
5
  import { installPiIntegration } from "./pi";
6
+ import { installClaudeCodeIntegration } from "./claudecode";
6
7
  import type { SdkStore } from "@routstr/sdk";
7
8
  import { CLIENT_CONFIGS } from "./registry";
8
9
  export { CLIENT_INTEGRATIONS, CLIENT_CONFIGS, runIntegrationsForClients } from "./registry";
@@ -30,7 +31,7 @@ function parseChoice(input: string): number {
30
31
  }
31
32
 
32
33
  const parsed = Number.parseInt(input, 10);
33
- if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 4) {
34
+ if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 5) {
34
35
  return parsed;
35
36
  }
36
37
 
@@ -45,7 +46,8 @@ export async function setupIntegration(
45
46
  logger.log("1. OpenCode (default)");
46
47
  logger.log("2. OpenClaw");
47
48
  logger.log("3. Pi");
48
- logger.log("4. Skip for now");
49
+ logger.log("4. Claude Code");
50
+ logger.log("5. Skip for now");
49
51
 
50
52
  const answer = await ask("Select integration [1]: ");
51
53
  const choice = parseChoice(answer);
@@ -65,5 +67,10 @@ export async function setupIntegration(
65
67
  return;
66
68
  }
67
69
 
70
+ if (choice === 4) {
71
+ await installClaudeCodeIntegration(config, store, CLIENT_CONFIGS["claude-code"]!);
72
+ return;
73
+ }
74
+
68
75
  logger.log("Skipping integration setup.");
69
76
  }
@@ -5,6 +5,7 @@ import type { SdkStore } from "@routstr/sdk";
5
5
  import { installOpencodeIntegration } from "./opencode";
6
6
  import { installPiIntegration } from "./pi";
7
7
  import { installOpenClawIntegration } from "./openclaw";
8
+ import { installClaudeCodeIntegration } from "./claudecode";
8
9
 
9
10
  export interface IntegrationConfig {
10
11
  clientId: string;
@@ -44,12 +45,18 @@ export const CLIENT_CONFIGS: Record<string, IntegrationConfig> = {
44
45
  name: "OpenClaw",
45
46
  configPath: join(process.env.HOME || "", ".openclaw/openclaw.json"),
46
47
  },
48
+ "claude-code": {
49
+ clientId: "claude-code",
50
+ name: "Claude Code",
51
+ configPath: join(process.env.HOME || "", ".claude/settings.json"),
52
+ },
47
53
  };
48
54
 
49
55
  export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
50
56
  opencode: installOpencodeIntegration,
51
57
  "pi-agent": installPiIntegration,
52
58
  openclaw: installOpenClawIntegration,
59
+ "claude-code": installClaudeCodeIntegration,
53
60
  };
54
61
 
55
62
  export async function runIntegrationsForClients(