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.
- package/.claude/settings.local.json +4 -1
- package/bun.lock +2 -2
- package/dist/daemon/index.js +298 -302
- package/dist/index.js +161 -72
- package/package.json +3 -2
- package/src/cli.ts +113 -65
- package/src/daemon/http/index.ts +0 -236
- 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/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({
|
|
@@ -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(
|