routstrd 0.3.0 → 0.3.2
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/bun.lock +103 -17
- package/dist/daemon/index.js +18402 -8458
- package/dist/index.js +147 -88
- package/package.json +2 -2
- package/say_hi.sh +32 -0
- package/src/cli.ts +53 -23
- package/src/daemon/models.ts +55 -0
- package/src/daemon/types.ts +6 -0
- package/src/integrations/claudecode.ts +9 -10
- package/src/integrations/hermes.ts +10 -11
- package/src/integrations/openclaw.ts +5 -6
- package/src/integrations/opencode.ts +5 -6
- package/src/integrations/pi.ts +13 -9
- package/src/integrations/registry.ts +1 -0
- package/src/tui/usage/data.ts +6 -2
- package/src/tui/usage/render.ts +20 -5
- package/src/utils/config.ts +6 -0
- package/src/utils/daemon-client.ts +32 -5
- package/new-task.md +0 -60
package/dist/index.js
CHANGED
|
@@ -9530,15 +9530,19 @@ async function loadConfig() {
|
|
|
9530
9530
|
function getDaemonBaseUrl(config) {
|
|
9531
9531
|
return config.daemonUrl?.replace(/\/$/, "") || `http://localhost:${config.port}`;
|
|
9532
9532
|
}
|
|
9533
|
-
|
|
9533
|
+
function getAuthBaseUrl(config) {
|
|
9534
|
+
if (config.authUrl) {
|
|
9535
|
+
return config.authUrl.replace(/\/$/, "");
|
|
9536
|
+
}
|
|
9537
|
+
return getDaemonBaseUrl(config);
|
|
9538
|
+
}
|
|
9539
|
+
async function _callUrl(baseUrl, path, options, config) {
|
|
9534
9540
|
const { method = "GET", body } = options;
|
|
9535
|
-
const config = await loadConfig();
|
|
9536
|
-
const baseUrl = getDaemonBaseUrl(config);
|
|
9537
9541
|
const url = `${baseUrl}${path}`;
|
|
9538
9542
|
const bodyString = body ? JSON.stringify(body) : undefined;
|
|
9539
9543
|
const bodyBytes = bodyString ? new TextEncoder().encode(bodyString) : undefined;
|
|
9540
9544
|
let authorization;
|
|
9541
|
-
if (config.daemonUrl && config.nsec) {
|
|
9545
|
+
if ((config.daemonUrl || config.authUrl) && config.nsec) {
|
|
9542
9546
|
const secretKey = parseSecretKey(config.nsec);
|
|
9543
9547
|
authorization = await createNIP98Authorization(secretKey, url, method, bodyBytes);
|
|
9544
9548
|
}
|
|
@@ -9556,6 +9560,16 @@ async function callDaemon(path, options = {}) {
|
|
|
9556
9560
|
}
|
|
9557
9561
|
return response.json();
|
|
9558
9562
|
}
|
|
9563
|
+
async function callDaemon(path, options = {}) {
|
|
9564
|
+
const config = await loadConfig();
|
|
9565
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
9566
|
+
return _callUrl(baseUrl, path, options, config);
|
|
9567
|
+
}
|
|
9568
|
+
async function callAuth(path, options = {}) {
|
|
9569
|
+
const config = await loadConfig();
|
|
9570
|
+
const baseUrl = getAuthBaseUrl(config);
|
|
9571
|
+
return _callUrl(baseUrl, path, options, config);
|
|
9572
|
+
}
|
|
9559
9573
|
async function isDaemonRunning() {
|
|
9560
9574
|
try {
|
|
9561
9575
|
const config = await loadConfig();
|
|
@@ -13929,10 +13943,14 @@ function getTodayStart() {
|
|
|
13929
13943
|
return new Date(now2.getFullYear(), now2.getMonth(), now2.getDate()).getTime();
|
|
13930
13944
|
}
|
|
13931
13945
|
function formatDate(timestamp) {
|
|
13932
|
-
|
|
13946
|
+
const d = new Date(timestamp);
|
|
13947
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
13948
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
13933
13949
|
}
|
|
13934
13950
|
function formatTime(timestamp) {
|
|
13935
|
-
|
|
13951
|
+
const d = new Date(timestamp);
|
|
13952
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
13953
|
+
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
13936
13954
|
}
|
|
13937
13955
|
function formatNumber(n) {
|
|
13938
13956
|
if (n >= 1e6)
|
|
@@ -14723,19 +14741,34 @@ function renderRecent(stats, width) {
|
|
|
14723
14741
|
if (recentEntries.length === 0)
|
|
14724
14742
|
return renderBox(["No recent entries"], width, "Recent Requests");
|
|
14725
14743
|
const clientCol = 14;
|
|
14744
|
+
const tokensCol = 18;
|
|
14745
|
+
const costCol = 18;
|
|
14746
|
+
const providerCol = Math.max(16, width - 4 - 10 - 18 - tokensCol - costCol - clientCol - 5);
|
|
14747
|
+
const msatsToSats = (msats) => typeof msats === "number" ? msats / 1000 : 0;
|
|
14726
14748
|
const lines = [];
|
|
14727
|
-
lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"
|
|
14749
|
+
lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"I/CR/CW/O".padEnd(tokensCol)} ${"I/O/T in sats".padEnd(costCol)} ${"BASE:PROVIDER".padEnd(providerCol)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
|
|
14728
14750
|
lines.push(COLORS.dim + "\u2500".repeat(width - 4) + COLORS.reset);
|
|
14729
14751
|
for (const entry of recentEntries) {
|
|
14730
14752
|
const time = formatTime(entry.timestamp).slice(0, 8);
|
|
14731
14753
|
const model = entry.modelId.slice(0, 18).padEnd(18);
|
|
14732
|
-
const tokens =
|
|
14733
|
-
|
|
14734
|
-
|
|
14754
|
+
const tokens = [
|
|
14755
|
+
entry.promptTokens,
|
|
14756
|
+
entry.cacheReadInputTokens || 0,
|
|
14757
|
+
entry.cacheCreationInputTokens || 0,
|
|
14758
|
+
entry.completionTokens
|
|
14759
|
+
].map(formatNumber).join("/");
|
|
14760
|
+
const totalSats = typeof entry.totalMsats === "number" ? entry.totalMsats / 1000 : entry.satsCost;
|
|
14761
|
+
const cost = [
|
|
14762
|
+
formatCost(msatsToSats(entry.inputMsats)),
|
|
14763
|
+
formatCost(msatsToSats(entry.outputMsats)),
|
|
14764
|
+
formatCost(totalSats)
|
|
14765
|
+
].join("/");
|
|
14766
|
+
const baseUrl = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "");
|
|
14767
|
+
const provider = `${baseUrl}:${entry.provider || "unknown"}`.slice(0, providerCol).padEnd(providerCol);
|
|
14735
14768
|
const clientName = (entry.client || "unknown").slice(0, clientCol - 1);
|
|
14736
14769
|
const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
|
|
14737
14770
|
const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
|
|
14738
|
-
lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(
|
|
14771
|
+
lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(tokensCol)} ${COLORS.green}${cost.padEnd(costCol)}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
|
|
14739
14772
|
}
|
|
14740
14773
|
return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
|
|
14741
14774
|
}
|
|
@@ -15066,7 +15099,6 @@ init_logger();
|
|
|
15066
15099
|
import { join as join3 } from "path";
|
|
15067
15100
|
|
|
15068
15101
|
// src/integrations/opencode.ts
|
|
15069
|
-
init_logger();
|
|
15070
15102
|
init_daemon_client();
|
|
15071
15103
|
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
15072
15104
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -15074,9 +15106,9 @@ import { dirname as dirname2 } from "path";
|
|
|
15074
15106
|
var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
|
15075
15107
|
async function installOpencodeIntegration(config, apiKey, integrationConfig) {
|
|
15076
15108
|
const { name, configPath } = integrationConfig;
|
|
15077
|
-
|
|
15109
|
+
console.log(`
|
|
15078
15110
|
Installing routstr models in opencode.json...`);
|
|
15079
|
-
|
|
15111
|
+
console.log(`Using API key for ${name}`);
|
|
15080
15112
|
const baseUrl = getDaemonBaseUrl(config);
|
|
15081
15113
|
let opencodeConfig;
|
|
15082
15114
|
try {
|
|
@@ -15097,7 +15129,7 @@ Installing routstr models in opencode.json...`);
|
|
|
15097
15129
|
const data = await callDaemon("/models");
|
|
15098
15130
|
const models = data.output?.models || [];
|
|
15099
15131
|
if (models.length === 0) {
|
|
15100
|
-
|
|
15132
|
+
console.log("No models found from routstr daemon.");
|
|
15101
15133
|
return;
|
|
15102
15134
|
}
|
|
15103
15135
|
const modelsObj = {};
|
|
@@ -15116,23 +15148,22 @@ Installing routstr models in opencode.json...`);
|
|
|
15116
15148
|
};
|
|
15117
15149
|
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
|
15118
15150
|
await writeFile2(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
15119
|
-
|
|
15151
|
+
console.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
|
15120
15152
|
} catch (error) {
|
|
15121
|
-
|
|
15153
|
+
console.error("Failed to install models in opencode.json:", error);
|
|
15122
15154
|
}
|
|
15123
15155
|
}
|
|
15124
15156
|
|
|
15125
15157
|
// src/integrations/pi.ts
|
|
15126
|
-
init_logger();
|
|
15127
15158
|
init_daemon_client();
|
|
15128
15159
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
15129
15160
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
15130
15161
|
import { dirname as dirname3 } from "path";
|
|
15131
15162
|
async function installPiIntegration(config, apiKey, integrationConfig) {
|
|
15132
15163
|
const { name, configPath } = integrationConfig;
|
|
15133
|
-
|
|
15164
|
+
console.log(`
|
|
15134
15165
|
Installing routstr models in pi models.json...`);
|
|
15135
|
-
|
|
15166
|
+
console.log(`Using API key for ${name}`);
|
|
15136
15167
|
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
|
|
15137
15168
|
let piConfig = {};
|
|
15138
15169
|
try {
|
|
@@ -15151,12 +15182,16 @@ Installing routstr models in pi models.json...`);
|
|
|
15151
15182
|
const data = await callDaemon("/models");
|
|
15152
15183
|
const models = data.output?.models || [];
|
|
15153
15184
|
if (models.length === 0) {
|
|
15154
|
-
|
|
15185
|
+
console.log("No models found from routstr daemon.");
|
|
15155
15186
|
return;
|
|
15156
15187
|
}
|
|
15157
|
-
const providerModels = models.map((model) =>
|
|
15158
|
-
id: model.id
|
|
15159
|
-
|
|
15188
|
+
const providerModels = models.map((model) => {
|
|
15189
|
+
const entry = { id: model.id };
|
|
15190
|
+
if (model.context_length !== undefined && model.context_length > 0) {
|
|
15191
|
+
entry.contextWindow = model.context_length;
|
|
15192
|
+
}
|
|
15193
|
+
return entry;
|
|
15194
|
+
});
|
|
15160
15195
|
piConfig.providers["routstr"] = {
|
|
15161
15196
|
baseUrl,
|
|
15162
15197
|
api: "openai-completions",
|
|
@@ -15164,14 +15199,13 @@ Installing routstr models in pi models.json...`);
|
|
|
15164
15199
|
models: providerModels
|
|
15165
15200
|
};
|
|
15166
15201
|
await writeFile3(configPath, JSON.stringify(piConfig, null, 2));
|
|
15167
|
-
|
|
15202
|
+
console.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
|
|
15168
15203
|
} catch (error) {
|
|
15169
|
-
|
|
15204
|
+
console.error("Failed to install models in pi models.json:", error);
|
|
15170
15205
|
}
|
|
15171
15206
|
}
|
|
15172
15207
|
|
|
15173
15208
|
// src/integrations/openclaw.ts
|
|
15174
|
-
init_logger();
|
|
15175
15209
|
init_daemon_client();
|
|
15176
15210
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
15177
15211
|
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -15181,9 +15215,9 @@ var OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
|
|
|
15181
15215
|
var OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
|
|
15182
15216
|
async function installOpenClawIntegration(config, apiKey, integrationConfig) {
|
|
15183
15217
|
const { name, configPath } = integrationConfig;
|
|
15184
|
-
|
|
15218
|
+
console.log(`
|
|
15185
15219
|
Installing routstr models in openclaw.json...`);
|
|
15186
|
-
|
|
15220
|
+
console.log(`Using API key for ${name}`);
|
|
15187
15221
|
const baseUrl = getDaemonBaseUrl(config);
|
|
15188
15222
|
let openclawConfig = {};
|
|
15189
15223
|
try {
|
|
@@ -15211,7 +15245,7 @@ Installing routstr models in openclaw.json...`);
|
|
|
15211
15245
|
const data = await callDaemon("/models");
|
|
15212
15246
|
const models = data.output?.models || [];
|
|
15213
15247
|
if (models.length === 0) {
|
|
15214
|
-
|
|
15248
|
+
console.log("No models found from routstr daemon.");
|
|
15215
15249
|
return;
|
|
15216
15250
|
}
|
|
15217
15251
|
const providerModels = models.map((model) => ({
|
|
@@ -15240,23 +15274,22 @@ Installing routstr models in openclaw.json...`);
|
|
|
15240
15274
|
};
|
|
15241
15275
|
}
|
|
15242
15276
|
await writeFile4(configPath, JSON.stringify(openclawConfig, null, 2));
|
|
15243
|
-
|
|
15277
|
+
console.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
|
|
15244
15278
|
} catch (error) {
|
|
15245
|
-
|
|
15279
|
+
console.error("Failed to install models in openclaw.json:", error);
|
|
15246
15280
|
}
|
|
15247
15281
|
}
|
|
15248
15282
|
|
|
15249
15283
|
// src/integrations/claudecode.ts
|
|
15250
|
-
init_logger();
|
|
15251
15284
|
init_daemon_client();
|
|
15252
15285
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
15253
15286
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
15254
15287
|
import { dirname as dirname5 } from "path";
|
|
15255
15288
|
async function installClaudeCodeIntegration(config, apiKey, integrationConfig) {
|
|
15256
15289
|
const { name, configPath } = integrationConfig;
|
|
15257
|
-
|
|
15290
|
+
console.log(`
|
|
15258
15291
|
Installing routstr configuration in ${configPath}...`);
|
|
15259
|
-
|
|
15292
|
+
console.log(`Using API key for ${name}`);
|
|
15260
15293
|
const baseUrl = getDaemonBaseUrl(config);
|
|
15261
15294
|
let settings = {};
|
|
15262
15295
|
try {
|
|
@@ -15265,7 +15298,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15265
15298
|
settings = JSON.parse(content);
|
|
15266
15299
|
}
|
|
15267
15300
|
} catch (error) {
|
|
15268
|
-
|
|
15301
|
+
console.error(`Error reading ${configPath}, creating new one.`);
|
|
15269
15302
|
}
|
|
15270
15303
|
if (!settings.env) {
|
|
15271
15304
|
settings.env = {};
|
|
@@ -15282,39 +15315,38 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15282
15315
|
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
|
|
15283
15316
|
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
|
|
15284
15317
|
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
|
|
15285
|
-
|
|
15318
|
+
console.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
|
|
15286
15319
|
} else if (models.length > 0) {
|
|
15287
15320
|
const model = models[0];
|
|
15288
|
-
|
|
15321
|
+
console.log(`Only ${models.length} models available, falling back to defaults.`);
|
|
15289
15322
|
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model.id;
|
|
15290
15323
|
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model.id;
|
|
15291
15324
|
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model.id;
|
|
15292
15325
|
} else {
|
|
15293
|
-
|
|
15326
|
+
console.log("No models available from routstr daemon.");
|
|
15294
15327
|
}
|
|
15295
15328
|
} catch (error) {
|
|
15296
|
-
|
|
15329
|
+
console.error("Failed to fetch models for Claude Code integration:", error);
|
|
15297
15330
|
}
|
|
15298
15331
|
try {
|
|
15299
15332
|
mkdirSync4(dirname5(configPath), { recursive: true });
|
|
15300
15333
|
await writeFile5(configPath, JSON.stringify(settings, null, 2));
|
|
15301
|
-
|
|
15334
|
+
console.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
15302
15335
|
} catch (error) {
|
|
15303
|
-
|
|
15336
|
+
console.error(`Failed to write to ${configPath}:`, error);
|
|
15304
15337
|
}
|
|
15305
15338
|
}
|
|
15306
15339
|
|
|
15307
15340
|
// src/integrations/hermes.ts
|
|
15308
|
-
init_logger();
|
|
15309
15341
|
init_daemon_client();
|
|
15310
15342
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
15311
15343
|
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
15312
15344
|
import { dirname as dirname6 } from "path";
|
|
15313
15345
|
async function installHermesIntegration(config, apiKey, integrationConfig) {
|
|
15314
15346
|
const { name, configPath } = integrationConfig;
|
|
15315
|
-
|
|
15347
|
+
console.log(`
|
|
15316
15348
|
Installing routstr configuration in ${configPath}...`);
|
|
15317
|
-
|
|
15349
|
+
console.log(`Using API key for ${name}`);
|
|
15318
15350
|
const baseUrl = getDaemonBaseUrl(config);
|
|
15319
15351
|
const baseUrlV1 = `${baseUrl}/v1`;
|
|
15320
15352
|
let defaultModel = "minimax-m2.7";
|
|
@@ -15323,16 +15355,16 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15323
15355
|
const models = data.output?.models || [];
|
|
15324
15356
|
if (models.length >= 3) {
|
|
15325
15357
|
defaultModel = models[2].id;
|
|
15326
|
-
|
|
15358
|
+
console.log(`Set default model to 3rd available model: ${defaultModel}`);
|
|
15327
15359
|
} else if (models.length > 0) {
|
|
15328
15360
|
defaultModel = models[0].id;
|
|
15329
|
-
|
|
15361
|
+
console.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
|
|
15330
15362
|
} else {
|
|
15331
|
-
|
|
15363
|
+
console.log("No models available from routstr daemon, using fallback default.");
|
|
15332
15364
|
}
|
|
15333
15365
|
} catch (error) {
|
|
15334
|
-
|
|
15335
|
-
|
|
15366
|
+
console.error("Failed to fetch models for Hermes integration:", error);
|
|
15367
|
+
console.log("Using fallback default model.");
|
|
15336
15368
|
}
|
|
15337
15369
|
let content = "";
|
|
15338
15370
|
try {
|
|
@@ -15340,7 +15372,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15340
15372
|
content = await readFile6(configPath, "utf-8");
|
|
15341
15373
|
}
|
|
15342
15374
|
} catch (error) {
|
|
15343
|
-
|
|
15375
|
+
console.error(`Error reading ${configPath}, creating new one.`);
|
|
15344
15376
|
}
|
|
15345
15377
|
content = content.replace(/^model:\n(?: .*\n)*/gm, "");
|
|
15346
15378
|
content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
|
|
@@ -15370,9 +15402,9 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15370
15402
|
try {
|
|
15371
15403
|
mkdirSync5(dirname6(configPath), { recursive: true });
|
|
15372
15404
|
await writeFile6(configPath, newContent);
|
|
15373
|
-
|
|
15405
|
+
console.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
15374
15406
|
} catch (error) {
|
|
15375
|
-
|
|
15407
|
+
console.error(`Failed to write to ${configPath}:`, error);
|
|
15376
15408
|
}
|
|
15377
15409
|
}
|
|
15378
15410
|
|
|
@@ -15618,14 +15650,14 @@ function parseChoice(input) {
|
|
|
15618
15650
|
return 1;
|
|
15619
15651
|
}
|
|
15620
15652
|
async function setupIntegration(config) {
|
|
15621
|
-
|
|
15653
|
+
console.log(`
|
|
15622
15654
|
Choose an integration to set up:`);
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15655
|
+
console.log("1. OpenCode (default)");
|
|
15656
|
+
console.log("2. OpenClaw");
|
|
15657
|
+
console.log("3. Pi");
|
|
15658
|
+
console.log("4. Claude Code");
|
|
15659
|
+
console.log("5. Hermes");
|
|
15660
|
+
console.log("6. Skip for now");
|
|
15629
15661
|
const answer = await ask("Select integration [1]: ");
|
|
15630
15662
|
const choice = parseChoice(answer);
|
|
15631
15663
|
const integrationByChoice = {
|
|
@@ -15637,15 +15669,15 @@ Choose an integration to set up:`);
|
|
|
15637
15669
|
};
|
|
15638
15670
|
const key = integrationByChoice[choice];
|
|
15639
15671
|
if (!key) {
|
|
15640
|
-
|
|
15672
|
+
console.log("Skipping integration setup.");
|
|
15641
15673
|
return;
|
|
15642
15674
|
}
|
|
15643
15675
|
const integrationConfig = CLIENT_CONFIGS[key];
|
|
15644
15676
|
const { client, created } = await addDaemonClient(integrationConfig.name);
|
|
15645
15677
|
if (created) {
|
|
15646
|
-
|
|
15678
|
+
console.log(`Created new API key for ${integrationConfig.name}`);
|
|
15647
15679
|
} else {
|
|
15648
|
-
|
|
15680
|
+
console.log(`Using existing API key for ${integrationConfig.name}`);
|
|
15649
15681
|
}
|
|
15650
15682
|
if (key === "opencode") {
|
|
15651
15683
|
await installOpencodeIntegration(config, client.apiKey, integrationConfig);
|
|
@@ -15770,7 +15802,7 @@ async function isCocodInstalled(cocodPath) {
|
|
|
15770
15802
|
// package.json
|
|
15771
15803
|
var package_default = {
|
|
15772
15804
|
name: "routstrd",
|
|
15773
|
-
version: "0.3.
|
|
15805
|
+
version: "0.3.2",
|
|
15774
15806
|
module: "src/index.ts",
|
|
15775
15807
|
type: "module",
|
|
15776
15808
|
private: false,
|
|
@@ -15794,7 +15826,7 @@ var package_default = {
|
|
|
15794
15826
|
},
|
|
15795
15827
|
dependencies: {
|
|
15796
15828
|
"@cashu/cashu-ts": "^4.3.0",
|
|
15797
|
-
"@routstr/sdk": "^0.3.
|
|
15829
|
+
"@routstr/sdk": "^0.3.10",
|
|
15798
15830
|
"applesauce-core": "^5.1.0",
|
|
15799
15831
|
"applesauce-relay": "^5.1.0",
|
|
15800
15832
|
"applesauce-wallet-connect": "^6.0.0",
|
|
@@ -15829,17 +15861,17 @@ Invoice:
|
|
|
15829
15861
|
${invoice}`);
|
|
15830
15862
|
}
|
|
15831
15863
|
async function installCocodOrExit() {
|
|
15832
|
-
|
|
15864
|
+
console.log("cocod not found. Installing globally with bun...");
|
|
15833
15865
|
const installProc = Bun.spawn(["bun", "install", "--global", "@routstr/cocod"], {
|
|
15834
15866
|
stdout: "inherit",
|
|
15835
15867
|
stderr: "inherit"
|
|
15836
15868
|
});
|
|
15837
15869
|
const installCode = await installProc.exited;
|
|
15838
15870
|
if (installCode !== 0 || !await isCocodInstalled()) {
|
|
15839
|
-
|
|
15871
|
+
console.error("Failed to install cocod. Please run 'bun install --global @routstr/cocod' manually.");
|
|
15840
15872
|
throw new Error("cocod installation failed");
|
|
15841
15873
|
}
|
|
15842
|
-
|
|
15874
|
+
console.log("cocod installed successfully.");
|
|
15843
15875
|
}
|
|
15844
15876
|
async function requireLocalDaemon() {
|
|
15845
15877
|
const config = await loadConfig();
|
|
@@ -15849,10 +15881,10 @@ async function requireLocalDaemon() {
|
|
|
15849
15881
|
}
|
|
15850
15882
|
}
|
|
15851
15883
|
async function initDaemon() {
|
|
15852
|
-
|
|
15884
|
+
console.log("Initializing routstrd...");
|
|
15853
15885
|
if (!existsSync9(CONFIG_DIR)) {
|
|
15854
15886
|
mkdirSync6(CONFIG_DIR, { recursive: true });
|
|
15855
|
-
|
|
15887
|
+
console.log(`Created config directory: ${CONFIG_DIR}`);
|
|
15856
15888
|
}
|
|
15857
15889
|
if (!existsSync9(CONFIG_FILE)) {
|
|
15858
15890
|
const config2 = {
|
|
@@ -15860,9 +15892,21 @@ async function initDaemon() {
|
|
|
15860
15892
|
cocodPath: null
|
|
15861
15893
|
};
|
|
15862
15894
|
await Bun.write(CONFIG_FILE, JSON.stringify(config2, null, 2));
|
|
15863
|
-
|
|
15895
|
+
console.log(`Created config file: ${CONFIG_FILE}`);
|
|
15864
15896
|
}
|
|
15865
15897
|
const config = await loadConfig();
|
|
15898
|
+
if (!config.nsec) {
|
|
15899
|
+
const secretKey = generateSecretKey();
|
|
15900
|
+
const nsec = nip19_exports.nsecEncode(secretKey);
|
|
15901
|
+
const npub = npubFromSecretKey(secretKey);
|
|
15902
|
+
config.nsec = nsec;
|
|
15903
|
+
await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
15904
|
+
console.log(`
|
|
15905
|
+
A new Nostr identity has been generated for authentication.`);
|
|
15906
|
+
console.log(`Your npub: ${npub}`);
|
|
15907
|
+
console.log(`You can view it in the config file at: ${CONFIG_FILE}
|
|
15908
|
+
`);
|
|
15909
|
+
}
|
|
15866
15910
|
if (!await isCocodInstalled(config.cocodPath)) {
|
|
15867
15911
|
if (config.cocodPath) {
|
|
15868
15912
|
logger.error(`Configured cocod executable was not found: ${config.cocodPath}`);
|
|
@@ -15903,23 +15947,23 @@ Initializing cocod...`);
|
|
|
15903
15947
|
${initStderr}`.toLowerCase();
|
|
15904
15948
|
const alreadyInitialized = combinedOutput.includes("already initialized");
|
|
15905
15949
|
if (initCode !== 0 && !alreadyInitialized) {
|
|
15906
|
-
|
|
15950
|
+
console.error("Failed to initialize cocod. Please run 'cocod init' manually.");
|
|
15907
15951
|
return;
|
|
15908
15952
|
}
|
|
15909
15953
|
if (alreadyInitialized) {
|
|
15910
|
-
|
|
15954
|
+
console.log("cocod is already initialized.");
|
|
15911
15955
|
} else {
|
|
15912
|
-
|
|
15956
|
+
console.log("cocod initialized successfully.");
|
|
15913
15957
|
}
|
|
15914
15958
|
await startDaemon({ port: String(config.port || 8008) });
|
|
15915
15959
|
await setupIntegration(config);
|
|
15916
|
-
|
|
15960
|
+
console.log(`
|
|
15917
15961
|
Initialization complete!`);
|
|
15918
|
-
|
|
15962
|
+
console.log(`
|
|
15919
15963
|
use 'routstrd receive <cashu-token>' or 'routstrd receive 2100' to top up your local wallet using Lightning!`);
|
|
15920
|
-
|
|
15964
|
+
console.log(`
|
|
15921
15965
|
full wallet commands still work too, e.g. 'routstrd wallet receive cashu <token>' and 'routstrd wallet receive bolt11 2100'.`);
|
|
15922
|
-
|
|
15966
|
+
console.log(`
|
|
15923
15967
|
To ensure routstrd persists across system restarts, run: 'routstrd service install'`);
|
|
15924
15968
|
}
|
|
15925
15969
|
program.name("routstrd").description("Routstr daemon - Manage routstr processes").version(package_default.version, "--version", "output the version number");
|
|
@@ -15992,18 +16036,29 @@ Results:`);
|
|
|
15992
16036
|
process.exit(1);
|
|
15993
16037
|
}
|
|
15994
16038
|
});
|
|
15995
|
-
program.command("remote <url>").description("Configure a remote daemon URL").action(async (url) => {
|
|
16039
|
+
program.command("remote <url>").description("Configure a remote daemon URL").option("--auth-url <authUrl>", "URL of the auth proxy for management commands (npubs, clients, usage)").action(async (url, options) => {
|
|
15996
16040
|
try {
|
|
15997
16041
|
new URL(url);
|
|
15998
16042
|
} catch {
|
|
15999
16043
|
console.error(`Invalid URL: ${url}`);
|
|
16000
16044
|
process.exit(1);
|
|
16001
16045
|
}
|
|
16046
|
+
if (options.authUrl) {
|
|
16047
|
+
try {
|
|
16048
|
+
new URL(options.authUrl);
|
|
16049
|
+
} catch {
|
|
16050
|
+
console.error(`Invalid auth URL: ${options.authUrl}`);
|
|
16051
|
+
process.exit(1);
|
|
16052
|
+
}
|
|
16053
|
+
}
|
|
16002
16054
|
if (!existsSync9(CONFIG_DIR)) {
|
|
16003
16055
|
mkdirSync6(CONFIG_DIR, { recursive: true });
|
|
16004
16056
|
}
|
|
16005
16057
|
const config = await loadConfig();
|
|
16006
16058
|
const updates = { daemonUrl: url };
|
|
16059
|
+
if (options.authUrl) {
|
|
16060
|
+
updates.authUrl = options.authUrl;
|
|
16061
|
+
}
|
|
16007
16062
|
let generatedNpub;
|
|
16008
16063
|
if (!config.nsec) {
|
|
16009
16064
|
const secretKey = generateSecretKey();
|
|
@@ -16018,6 +16073,9 @@ program.command("remote <url>").description("Configure a remote daemon URL").act
|
|
|
16018
16073
|
};
|
|
16019
16074
|
await Bun.write(CONFIG_FILE, JSON.stringify(updatedConfig, null, 2));
|
|
16020
16075
|
console.log(`Remote daemon URL set to: ${url}`);
|
|
16076
|
+
if (options.authUrl) {
|
|
16077
|
+
console.log(`Auth proxy URL set to: ${options.authUrl}`);
|
|
16078
|
+
}
|
|
16021
16079
|
if (generatedNpub) {
|
|
16022
16080
|
console.log(`
|
|
16023
16081
|
A new Nostr identity has been generated for remote authentication.`);
|
|
@@ -16195,7 +16253,7 @@ program.command("usage").description("Show recent usage logs and total sats cost
|
|
|
16195
16253
|
await ensureDaemonRunning();
|
|
16196
16254
|
const requested = Number.parseInt(options.limit, 10);
|
|
16197
16255
|
const limit = Number.isFinite(requested) && requested > 0 ? Math.min(requested, 1000) : 10;
|
|
16198
|
-
const result = await
|
|
16256
|
+
const result = await callAuth(`/usage?limit=${limit}`);
|
|
16199
16257
|
if (result.error) {
|
|
16200
16258
|
console.log(result.error);
|
|
16201
16259
|
process.exit(1);
|
|
@@ -16222,9 +16280,10 @@ program.command("usage").description("Show recent usage logs and total sats cost
|
|
|
16222
16280
|
});
|
|
16223
16281
|
});
|
|
16224
16282
|
var providersCmd = program.command("providers").description("List and manage providers");
|
|
16225
|
-
providersCmd.command("list").description("List all providers with their enabled/disabled status").action(async () => {
|
|
16283
|
+
providersCmd.command("list").description("List all providers with their enabled/disabled status").option("--refresh", "Force re-fetch all Nostr events and refresh models from all enabled providers", false).action(async (options) => {
|
|
16226
16284
|
await ensureDaemonRunning();
|
|
16227
|
-
const
|
|
16285
|
+
const query = options.refresh ? "/providers?refresh=true" : "/providers";
|
|
16286
|
+
const result = await callDaemon(query);
|
|
16228
16287
|
if (result.error) {
|
|
16229
16288
|
console.log(result.error);
|
|
16230
16289
|
process.exit(1);
|
|
@@ -16302,7 +16361,7 @@ npubsCmd.command("list").description("List configured npubs with their roles").a
|
|
|
16302
16361
|
await ensureDaemonRunning();
|
|
16303
16362
|
const config = await loadConfig();
|
|
16304
16363
|
const userNpub = getUserNpub(config);
|
|
16305
|
-
const result = await
|
|
16364
|
+
const result = await callAuth("/npubs");
|
|
16306
16365
|
if (result.error) {
|
|
16307
16366
|
console.log(result.error);
|
|
16308
16367
|
process.exit(1);
|
|
@@ -16335,7 +16394,7 @@ npubsCmd.command("register").description("Register yourself as the first admin (
|
|
|
16335
16394
|
console.error("No Nostr identity configured. Run 'routstrd remote <url>' to set one up.");
|
|
16336
16395
|
process.exit(1);
|
|
16337
16396
|
}
|
|
16338
|
-
const result = await
|
|
16397
|
+
const result = await callAuth("/npubs");
|
|
16339
16398
|
if (result.error) {
|
|
16340
16399
|
console.log(result.error);
|
|
16341
16400
|
process.exit(1);
|
|
@@ -16352,7 +16411,7 @@ npubsCmd.command("register").description("Register yourself as the first admin (
|
|
|
16352
16411
|
console.error("Failed to normalize user npub.");
|
|
16353
16412
|
process.exit(1);
|
|
16354
16413
|
}
|
|
16355
|
-
const addResult = await
|
|
16414
|
+
const addResult = await callAuth("/npubs", {
|
|
16356
16415
|
method: "POST",
|
|
16357
16416
|
body: { npub: npubFromPubkey(normalized) }
|
|
16358
16417
|
});
|
|
@@ -16379,7 +16438,7 @@ npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...).
|
|
|
16379
16438
|
process.exit(1);
|
|
16380
16439
|
}
|
|
16381
16440
|
const body = { npub: npubFromPubkey(normalized), role: options.role };
|
|
16382
|
-
const result = await
|
|
16441
|
+
const result = await callAuth("/npubs", {
|
|
16383
16442
|
method: "POST",
|
|
16384
16443
|
body
|
|
16385
16444
|
});
|
|
@@ -16403,7 +16462,7 @@ npubsCmd.command("update <npub>").description("Update the role of an existing np
|
|
|
16403
16462
|
console.error("Invalid role. Expected 'admin' or 'user'.");
|
|
16404
16463
|
process.exit(1);
|
|
16405
16464
|
}
|
|
16406
|
-
const result = await
|
|
16465
|
+
const result = await callAuth("/npubs", {
|
|
16407
16466
|
method: "PATCH",
|
|
16408
16467
|
body: { npub: npubFromPubkey(normalized), role: options.role }
|
|
16409
16468
|
});
|
|
@@ -16425,7 +16484,7 @@ npubsCmd.command("delete <npub>").description("Delete an npub (hex pubkey or npu
|
|
|
16425
16484
|
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
|
16426
16485
|
process.exit(1);
|
|
16427
16486
|
}
|
|
16428
|
-
const result = await
|
|
16487
|
+
const result = await callAuth(`/npubs/${encodeURIComponent(npubFromPubkey(normalized))}`, {
|
|
16429
16488
|
method: "DELETE"
|
|
16430
16489
|
});
|
|
16431
16490
|
if (result.error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@cashu/cashu-ts": "^4.3.0",
|
|
27
|
-
"@routstr/sdk": "^0.3.
|
|
27
|
+
"@routstr/sdk": "^0.3.10",
|
|
28
28
|
"applesauce-core": "^5.1.0",
|
|
29
29
|
"applesauce-relay": "^5.1.0",
|
|
30
30
|
"applesauce-wallet-connect": "^6.0.0",
|
package/say_hi.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
LANGUAGES=(
|
|
5
|
+
"Mandarin Chinese"
|
|
6
|
+
"Spanish"
|
|
7
|
+
"English"
|
|
8
|
+
"Hindi"
|
|
9
|
+
"Arabic"
|
|
10
|
+
"Portuguese"
|
|
11
|
+
"Bengali"
|
|
12
|
+
"Russian"
|
|
13
|
+
"Japanese"
|
|
14
|
+
"German"
|
|
15
|
+
"French"
|
|
16
|
+
"Italian"
|
|
17
|
+
"Korean"
|
|
18
|
+
"Turkish"
|
|
19
|
+
"Vietnamese"
|
|
20
|
+
"Tamil"
|
|
21
|
+
"Polish"
|
|
22
|
+
"Dutch"
|
|
23
|
+
"Swahili"
|
|
24
|
+
"Thai"
|
|
25
|
+
"Greek"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for lang in "${LANGUAGES[@]}"; do
|
|
29
|
+
echo "=== $lang ==="
|
|
30
|
+
pi -p -c "in $lang"
|
|
31
|
+
echo
|
|
32
|
+
done
|