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/.claude/settings.local.json +3 -1
- package/bun.lock +2 -2
- package/dist/daemon/index.js +186 -275
- package/dist/index.js +84 -20
- package/package.json +3 -2
- package/src/daemon/http/index.ts +0 -236
- package/src/daemon/index.ts +4 -4
- package/src/daemon/wallet/index.ts +1 -1
- package/src/integrations/claudecode.ts +77 -0
- package/src/integrations/index.ts +9 -2
- package/src/integrations/registry.ts +7 -0
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
|
|
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
|
|
28797
|
-
import { readFile as
|
|
28798
|
-
import { dirname as
|
|
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 (
|
|
29015
|
-
const content = await
|
|
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
|
-
|
|
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
|
|
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 <=
|
|
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.
|
|
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
|
|
36310
|
-
const logPath =
|
|
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
|
|
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
|
|
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 (!
|
|
36879
|
-
|
|
36942
|
+
if (!existsSync9(CONFIG_DIR)) {
|
|
36943
|
+
mkdirSync5(CONFIG_DIR, { recursive: true });
|
|
36880
36944
|
logger.log(`Created config directory: ${CONFIG_DIR}`);
|
|
36881
36945
|
}
|
|
36882
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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.
|
|
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.
|
|
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",
|
package/src/daemon/http/index.ts
CHANGED
|
@@ -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({
|
package/src/daemon/index.ts
CHANGED
|
@@ -129,8 +129,8 @@ async function main(): Promise<void> {
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
// Recurring job to refund pending tokens every
|
|
133
|
-
const REFUND_INTERVAL_MS =
|
|
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
|
-
|
|
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
|
-
|
|
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:",
|
|
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 <=
|
|
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.
|
|
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(
|