routstrd 0.1.8 → 0.2.0

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/dist/index.js CHANGED
@@ -28789,13 +28789,13 @@ async function startDaemon(options = {}) {
28789
28789
  // src/cli.ts
28790
28790
  init_cli_shared();
28791
28791
  init_config();
28792
- import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
28792
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
28793
28793
  import { execSync } from "child_process";
28794
28794
 
28795
28795
  // src/integrations/opencode.ts
28796
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
28797
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
28798
- import { dirname as dirname3 } from "path";
28796
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
28797
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
28798
+ import { dirname as dirname4 } from "path";
28799
28799
 
28800
28800
  // src/integrations/registry.ts
28801
28801
  import { randomBytes } from "crypto";
@@ -28960,6 +28960,60 @@ Installing routstr models in openclaw.json...`);
28960
28960
  }
28961
28961
  }
28962
28962
 
28963
+ // src/integrations/claudecode.ts
28964
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
28965
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
28966
+ import { dirname as dirname3 } from "path";
28967
+ async function installClaudeCodeIntegration(config, store, integrationConfig) {
28968
+ const { clientId, name, configPath } = integrationConfig;
28969
+ logger.log(`
28970
+ Installing routstr configuration in ${configPath}...`);
28971
+ const port = config.port || 8008;
28972
+ const state = store.getState();
28973
+ const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
28974
+ let apiKey;
28975
+ if (existingClient) {
28976
+ apiKey = existingClient.apiKey;
28977
+ logger.log(`Using existing API key for ${name}`);
28978
+ } else {
28979
+ apiKey = generateApiKey();
28980
+ store.getState().setClientIds((prev) => [
28981
+ ...prev || [],
28982
+ {
28983
+ clientId,
28984
+ name,
28985
+ apiKey,
28986
+ createdAt: Date.now()
28987
+ }
28988
+ ]);
28989
+ logger.log(`Created new API key for ${name}`);
28990
+ }
28991
+ let settings = {};
28992
+ try {
28993
+ if (existsSync6(configPath)) {
28994
+ const content = await readFile3(configPath, "utf-8");
28995
+ settings = JSON.parse(content);
28996
+ }
28997
+ } catch (error) {
28998
+ logger.error(`Error reading ${configPath}, creating new one.`);
28999
+ }
29000
+ if (!settings.env) {
29001
+ settings.env = {};
29002
+ }
29003
+ settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
29004
+ settings.env["ANTHROPIC_BASE_URL"] = `http://localhost:${port}`;
29005
+ settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "gpt-5.4";
29006
+ settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "claude-opus-4.7";
29007
+ settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "minimax-m2.7";
29008
+ try {
29009
+ mkdirSync3(dirname3(configPath), { recursive: true });
29010
+ await writeFile3(configPath, JSON.stringify(settings, null, 2));
29011
+ logger.log(`Successfully updated ${configPath} with routstr settings.`);
29012
+ } catch (error) {
29013
+ logger.error(`Failed to write to ${configPath}:`, error);
29014
+ }
29015
+ }
29016
+
28963
29017
  // src/integrations/registry.ts
28964
29018
  function generateApiKey() {
28965
29019
  const bytes = randomBytes(24);
@@ -28980,6 +29034,11 @@ var CLIENT_CONFIGS = {
28980
29034
  clientId: "openclaw",
28981
29035
  name: "OpenClaw",
28982
29036
  configPath: join2(process.env.HOME || "", ".openclaw/openclaw.json")
29037
+ },
29038
+ "claude-code": {
29039
+ clientId: "claude-code",
29040
+ name: "Claude Code",
29041
+ configPath: join2(process.env.HOME || "", ".claude/settings.json")
28983
29042
  }
28984
29043
  };
28985
29044
 
@@ -29011,8 +29070,8 @@ Installing routstr models in opencode.json...`);
29011
29070
  }
29012
29071
  let opencodeConfig;
29013
29072
  try {
29014
- if (existsSync6(configPath)) {
29015
- const content = await readFile3(configPath, "utf-8");
29073
+ if (existsSync7(configPath)) {
29074
+ const content = await readFile4(configPath, "utf-8");
29016
29075
  opencodeConfig = JSON.parse(content);
29017
29076
  } else {
29018
29077
  opencodeConfig = { provider: {} };
@@ -29024,7 +29083,7 @@ Installing routstr models in opencode.json...`);
29024
29083
  opencodeConfig.provider = {};
29025
29084
  }
29026
29085
  try {
29027
- mkdirSync3(dirname3(configPath), { recursive: true });
29086
+ mkdirSync4(dirname4(configPath), { recursive: true });
29028
29087
  const response = await fetch(`http://localhost:${port}/models`);
29029
29088
  const data = await response.json();
29030
29089
  const models = data.output?.models || [];
@@ -29047,7 +29106,7 @@ Installing routstr models in opencode.json...`);
29047
29106
  models: modelsObj
29048
29107
  };
29049
29108
  opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
29050
- await writeFile3(configPath, JSON.stringify(opencodeConfig, null, 2));
29109
+ await writeFile4(configPath, JSON.stringify(opencodeConfig, null, 2));
29051
29110
  logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
29052
29111
  } catch (error) {
29053
29112
  logger.error("Failed to install models in opencode.json:", error);
@@ -29074,7 +29133,7 @@ function parseChoice(input) {
29074
29133
  return 1;
29075
29134
  }
29076
29135
  const parsed = Number.parseInt(input, 10);
29077
- if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 4) {
29136
+ if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 5) {
29078
29137
  return parsed;
29079
29138
  }
29080
29139
  return 1;
@@ -29085,7 +29144,8 @@ Choose an integration to set up:`);
29085
29144
  logger.log("1. OpenCode (default)");
29086
29145
  logger.log("2. OpenClaw");
29087
29146
  logger.log("3. Pi");
29088
- logger.log("4. Skip for now");
29147
+ logger.log("4. Claude Code");
29148
+ logger.log("5. Skip for now");
29089
29149
  const answer = await ask("Select integration [1]: ");
29090
29150
  const choice = parseChoice(answer);
29091
29151
  if (choice === 1) {
@@ -29100,6 +29160,10 @@ Choose an integration to set up:`);
29100
29160
  await installPiIntegration(config, store, CLIENT_CONFIGS["pi-agent"]);
29101
29161
  return;
29102
29162
  }
29163
+ if (choice === 4) {
29164
+ await installClaudeCodeIntegration(config, store, CLIENT_CONFIGS["claude-code"]);
29165
+ return;
29166
+ }
29103
29167
  logger.log("Skipping integration setup.");
29104
29168
  }
29105
29169
 
@@ -36306,8 +36370,8 @@ var AuditLogger = class _AuditLogger {
36306
36370
  if (typeof window === "undefined") {
36307
36371
  try {
36308
36372
  const fs2 = await import("fs");
36309
- const path = await import("path");
36310
- const logPath = path.join(process.cwd(), "audit.log");
36373
+ const path2 = await import("path");
36374
+ const logPath = path2.join(process.cwd(), "audit.log");
36311
36375
  fs2.appendFileSync(logPath, logLine);
36312
36376
  } catch (error) {
36313
36377
  console.error("[AuditLogger] Failed to write to file:", error);
@@ -36816,7 +36880,7 @@ var $toString = function toString(text, opts, cb) {
36816
36880
  };
36817
36881
 
36818
36882
  // src/daemon/wallet/cocod-client.ts
36819
- import { existsSync as existsSync7 } from "fs";
36883
+ import { existsSync as existsSync8 } from "fs";
36820
36884
  var DEFAULT_CONFIG_DIR = `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
36821
36885
  var DEFAULT_SOCKET_PATH = process.env.COCOD_SOCKET || `${DEFAULT_CONFIG_DIR}/cocod.sock`;
36822
36886
  function resolveCocodExecutable(cocodPath) {
@@ -36826,7 +36890,7 @@ function resolveCocodExecutable(cocodPath) {
36826
36890
  async function isCocodInstalled(cocodPath) {
36827
36891
  const executable = resolveCocodExecutable(cocodPath);
36828
36892
  if (executable.includes("/")) {
36829
- return existsSync7(executable);
36893
+ return existsSync8(executable);
36830
36894
  }
36831
36895
  try {
36832
36896
  const proc = Bun.spawn({
@@ -36875,11 +36939,11 @@ async function initDaemon() {
36875
36939
  }
36876
36940
  logger.log("cocod installed successfully.");
36877
36941
  }
36878
- if (!existsSync8(CONFIG_DIR)) {
36879
- mkdirSync4(CONFIG_DIR, { recursive: true });
36942
+ if (!existsSync9(CONFIG_DIR)) {
36943
+ mkdirSync5(CONFIG_DIR, { recursive: true });
36880
36944
  logger.log(`Created config directory: ${CONFIG_DIR}`);
36881
36945
  }
36882
- if (!existsSync8(CONFIG_FILE)) {
36946
+ if (!existsSync9(CONFIG_FILE)) {
36883
36947
  const config2 = {
36884
36948
  ...DEFAULT_CONFIG,
36885
36949
  cocodPath: null
@@ -37367,7 +37431,7 @@ serviceCmd.command("install").description("Install and start routstrd using PM2
37367
37431
  const path = __require("path");
37368
37432
  daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
37369
37433
  }
37370
- if (!existsSync8(daemonPath)) {
37434
+ if (!existsSync9(daemonPath)) {
37371
37435
  console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
37372
37436
  process.exit(1);
37373
37437
  }
@@ -37501,14 +37565,14 @@ program.command("logs").description("View daemon logs").option("-f, --follow", "
37501
37565
  const yesterday = new Date;
37502
37566
  yesterday.setDate(yesterday.getDate() - 1);
37503
37567
  const yesterdayFile = getLogFileForDate2(yesterday);
37504
- if (!existsSync8(todayFile) && !existsSync8(yesterdayFile)) {
37568
+ if (!existsSync9(todayFile) && !existsSync9(yesterdayFile)) {
37505
37569
  console.log("No log files found. Daemon may not have started yet.");
37506
37570
  console.log(`Logs directory: ${LOGS_DIR2}`);
37507
37571
  process.exit(1);
37508
37572
  }
37509
37573
  const lines = parseInt(options.lines, 10);
37510
37574
  const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
37511
- return existsSync8(file) && files.indexOf(file) === index;
37575
+ return existsSync9(file) && files.indexOf(file) === index;
37512
37576
  });
37513
37577
  if (options.follow) {
37514
37578
  const proc2 = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -13,6 +13,7 @@
13
13
  "lint": "tsc --noEmit",
14
14
  "test": "bun test",
15
15
  "build": "mkdir -p dist/daemon && bun build src/index.ts --target=bun --outfile=dist/index.js && bun build src/daemon/index.ts --target=bun --outfile=dist/daemon/index.js",
16
+ "build:dev": "sed -i '' 's/\"@routstr\\/sdk\": \"\\^0.2.11\"/\"@routstr\\/sdk\": \"file:..\\/routstr-chat\\/sdk\"/' package.json && bun install && bun run build && sed -i '' 's/\"@routstr\\/sdk\": \"file:..\\/routstr-chat\\/sdk\"/\"@routstr\\/sdk\": \"^0.2.11\"/' package.json && bun install",
16
17
  "prepublishOnly": "bun run build"
17
18
  },
18
19
  "devDependencies": {
@@ -24,7 +25,7 @@
24
25
  },
25
26
  "dependencies": {
26
27
  "@cashu/cashu-ts": "^3.1.1",
27
- "@routstr/sdk": "^0.2.11",
28
+ "@routstr/sdk": "^0.2.12",
28
29
  "applesauce-core": "^5.1.0",
29
30
  "applesauce-relay": "^5.1.0",
30
31
  "commander": "^14.0.2",
@@ -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({
@@ -129,8 +129,8 @@ async function main(): Promise<void> {
129
129
  }
130
130
  };
131
131
 
132
- // Recurring job to refund pending tokens every 10 minutes
133
- const REFUND_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
132
+ // Recurring job to refund pending tokens every 42 minutes
133
+ const REFUND_INTERVAL_MS = 42 * 60 * 1000; // 42 minutes
134
134
  let refundInterval: ReturnType<typeof setInterval> | null = null;
135
135
 
136
136
  const startRefundJob = async () => {
@@ -209,7 +209,7 @@ async function main(): Promise<void> {
209
209
  void ensureProvidersBootstrapped()
210
210
  .then(() => {
211
211
  startModelRefreshJob();
212
- // startRefundJob(); DISABLING refund job for now.
212
+ startRefundJob();
213
213
  // Run an immediate refresh to populate models right away
214
214
  logger.log("Running initial model refresh...");
215
215
  return getRoutstr21Models(true);
@@ -229,7 +229,7 @@ async function main(): Promise<void> {
229
229
  logger.error("Initial model refresh failed:", error);
230
230
  // Still start the jobs even if initial refresh fails
231
231
  startModelRefreshJob();
232
- // startRefundJob(); DISABLING refund job for now.
232
+ startRefundJob();
233
233
  });
234
234
  });
235
235
  }
@@ -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(