routstrd 0.2.3 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "private": false,
package/src/cli.ts CHANGED
@@ -1,12 +1,12 @@
1
+ import { program } from "commander";
1
2
  import { startDaemon } from "./start-daemon";
2
3
  import {
3
- program,
4
4
  handleDaemonCommand,
5
5
  callDaemon,
6
6
  ensureDaemonRunning,
7
7
  isDaemonRunning,
8
8
  loadConfig,
9
- } from "./cli-shared";
9
+ } from "./utils/daemon-client";
10
10
  import { existsSync, mkdirSync } from "fs";
11
11
  import { execSync } from "child_process";
12
12
  import {
@@ -211,6 +211,81 @@ program
211
211
  .description("Routstr daemon - Manage routstr processes")
212
212
  .version(cliVersion, "--version", "output the version number");
213
213
 
214
+ program
215
+ .command("refund")
216
+ .description("Refund pending tokens and API keys to a specified mint")
217
+ .option("-m, --mint-url <mintUrl>", "Mint URL to refund to (defaults to first mint in wallet)")
218
+ .option("-y, --yes", "Skip confirmation prompt", false)
219
+ .action(async (options: { mintUrl?: string; yes: boolean }) => {
220
+ const config = await loadConfig();
221
+
222
+ let mintUrl = options.mintUrl;
223
+ if (!mintUrl) {
224
+ const balanceResponse = await fetch(`http://localhost:${config.port}/balance`);
225
+ const balanceResult = (await balanceResponse.json()) as {
226
+ output?: { balances?: Record<string, number> };
227
+ error?: string;
228
+ };
229
+ if (balanceResult.error) {
230
+ console.log(balanceResult.error);
231
+ process.exit(1);
232
+ }
233
+ const balances = balanceResult.output?.balances;
234
+ if (!balances || Object.keys(balances).length === 0) {
235
+ console.log("No mint URLs found in wallet balance");
236
+ process.exit(1);
237
+ }
238
+ mintUrl = Object.keys(balances)[0];
239
+ console.log(`Using mint URL: ${mintUrl}`);
240
+ }
241
+
242
+ try {
243
+ const response = await fetch(`http://localhost:${config.port}/refund`, {
244
+ method: "POST",
245
+ headers: { "Content-Type": "application/json" },
246
+ body: JSON.stringify({ mintUrl }),
247
+ });
248
+
249
+ if (!response.ok) {
250
+ const errorData = (await response.json()) as { error?: string };
251
+ throw new Error(errorData.error || `HTTP ${response.status}`);
252
+ }
253
+
254
+ const result = (await response.json()) as {
255
+ output?: {
256
+ message: string;
257
+ pendingTokens: number;
258
+ apiKeys: number;
259
+ results: Array<{ baseUrl: string; success: boolean }>;
260
+ };
261
+ error?: string;
262
+ };
263
+
264
+ if (result.error) {
265
+ console.log(result.error);
266
+ process.exit(1);
267
+ }
268
+
269
+ if (result.output) {
270
+ console.log(result.output.message);
271
+ console.log(`\nPending tokens: ${result.output.pendingTokens}`);
272
+ console.log(`API keys: ${result.output.apiKeys}`);
273
+ console.log("\nResults:");
274
+ for (const r of result.output.results) {
275
+ console.log(` - ${r.baseUrl}: ${r.success ? "success" : "failed"}`);
276
+ }
277
+ }
278
+ } catch (error) {
279
+ const message = (error as Error).message;
280
+ if (message?.includes("fetch failed") || message?.includes("Connection refused")) {
281
+ console.error("Daemon is not running");
282
+ process.exit(1);
283
+ }
284
+ console.error(message);
285
+ process.exit(1);
286
+ }
287
+ });
288
+
214
289
  // Onboard - initialize the daemon
215
290
  program
216
291
  .command("onboard")
@@ -659,6 +734,34 @@ clientsCmd
659
734
  }
660
735
  });
661
736
 
737
+ clientsCmd
738
+ .command("delete <id>")
739
+ .description("Delete a client by its ID")
740
+ .action(async (id: string) => {
741
+ await ensureDaemonRunning();
742
+
743
+ const result = await callDaemon("/clients/delete", {
744
+ method: "POST",
745
+ body: { id },
746
+ });
747
+
748
+ if (result.error) {
749
+ console.log(result.error);
750
+ process.exit(1);
751
+ }
752
+
753
+ const output = result.output as
754
+ | {
755
+ message: string;
756
+ id: string;
757
+ }
758
+ | undefined;
759
+
760
+ if (output) {
761
+ console.log(output.message);
762
+ }
763
+ });
764
+
662
765
  clientsCmd
663
766
  .command("add")
664
767
  .description("Add a new client or set up client integrations")
@@ -714,7 +817,7 @@ clientsCmd
714
817
  }
715
818
 
716
819
  console.log(
717
- `\n Access Routstr at: http://localhost:${config.port || 8008}`,
820
+ `\n Access Routstr at: http://localhost:${config.port || 8008}/v1`,
718
821
  );
719
822
  return;
720
823
  }
@@ -754,7 +857,7 @@ clientsCmd
754
857
  console.log(` Name: ${output.client.name}`);
755
858
  console.log(` API Key: ${output.client.apiKey}`);
756
859
  console.log(
757
- `\n Access Routstr at: http://localhost:${config.port || 8008}`,
860
+ `\n Access Routstr at: http://localhost:${config.port || 8008}/v1`,
758
861
  );
759
862
  }
760
863
  });
@@ -831,6 +831,65 @@ export function createDaemonRequestHandler(deps: {
831
831
  return;
832
832
  }
833
833
 
834
+ if (req.method === "POST" && url.pathname === "/clients/delete") {
835
+ try {
836
+ const bodyText = await readBody(req);
837
+ const body = bodyText ? JSON.parse(bodyText) : {};
838
+ const id = body.id as string | undefined;
839
+
840
+ if (!id || typeof id !== "string" || id.trim() === "") {
841
+ res.writeHead(400, { "Content-Type": "application/json" });
842
+ res.end(
843
+ JSON.stringify({
844
+ error:
845
+ "Missing required 'id' field (must be a non-empty string).",
846
+ }),
847
+ );
848
+ return;
849
+ }
850
+
851
+ const state = deps.store.getState();
852
+ const existingClients = state.clientIds || [];
853
+ const index = existingClients.findIndex(
854
+ (c: { clientId: string }) => c.clientId === id,
855
+ );
856
+
857
+ if (index === -1) {
858
+ res.writeHead(404, { "Content-Type": "application/json" });
859
+ res.end(
860
+ JSON.stringify({
861
+ error: `Client with id '${id}' not found.`,
862
+ }),
863
+ );
864
+ return;
865
+ }
866
+
867
+ const removedClient = existingClients[index];
868
+ const updatedClients = existingClients.filter(
869
+ (_c: unknown, i: number) => i !== index,
870
+ );
871
+
872
+ deps.store.getState().setClientIds(updatedClients);
873
+
874
+ logger.log(
875
+ `Deleted client '${removedClient.name}' with id '${id}'`,
876
+ );
877
+
878
+ res.writeHead(200, { "Content-Type": "application/json" });
879
+ res.end(
880
+ JSON.stringify({
881
+ output: {
882
+ message: `Client '${removedClient.name}' deleted successfully`,
883
+ id,
884
+ },
885
+ }),
886
+ );
887
+ } catch (error) {
888
+ respondWithError(res, error);
889
+ }
890
+ return;
891
+ }
892
+
834
893
  if (req.method === "GET" && url.pathname === "/providers") {
835
894
  try {
836
895
  const state = deps.store.getState();
@@ -203,7 +203,7 @@ async function main(): Promise<void> {
203
203
  });
204
204
 
205
205
  server.listen(port, async () => {
206
- logger.log(`Routstr daemon listening on http://localhost:${port}`);
206
+ logger.log(`Routstr daemon listening on http://localhost:${port}/v1`);
207
207
 
208
208
  // Start the recurring model refresh job after initial bootstrap
209
209
  void ensureProvidersBootstrapped()
@@ -26,7 +26,7 @@ export async function startDaemon(
26
26
  });
27
27
  clearTimeout(timeoutId);
28
28
  if (existing.ok) {
29
- logger.log(`Routstr daemon already running on http://localhost:${port}`);
29
+ logger.log(`Routstr daemon already running on http://localhost:${port}/v1`);
30
30
  return;
31
31
  }
32
32
  } catch {
@@ -28,7 +28,7 @@ import {
28
28
  import { COLORS } from "./constants.ts";
29
29
  import { renderHeader, renderSearchBar, renderSeparator, renderTabContent, renderTabs } from "./render.ts";
30
30
  import type { TabId, UsageStats } from "./types.ts";
31
- import { isDaemonRunning } from "../../cli-shared.ts";
31
+ import { isDaemonRunning } from "../../utils/daemon-client.ts";
32
32
 
33
33
  export async function runUsageTui(): Promise<void> {
34
34
  const running = await isDaemonRunning();
@@ -1,5 +1,5 @@
1
1
  import type { UsageTrackingEntry } from "../../daemon/types.ts";
2
- import { callDaemon, isDaemonRunning } from "../../cli-shared.ts";
2
+ import { callDaemon, isDaemonRunning } from "../../utils/daemon-client.ts";
3
3
  import type { ClientStats, DayStats, ModelStats, ProviderStats, UsageStats } from "./types.ts";
4
4
 
5
5
  export interface BalanceKey {
@@ -1,11 +1,10 @@
1
- import { program } from "commander";
2
1
  import { existsSync } from "fs";
3
2
  import {
4
3
  CONFIG_FILE,
5
4
  DEFAULT_CONFIG,
6
5
  LOGS_DIR,
7
6
  type RoutstrdConfig,
8
- } from "./utils/config";
7
+ } from "./config";
9
8
 
10
9
  export interface CommandResponse {
11
10
  output?: unknown;
@@ -24,7 +23,7 @@ export async function loadConfig(): Promise<RoutstrdConfig> {
24
23
  return DEFAULT_CONFIG;
25
24
  }
26
25
 
27
- async function callDaemon(
26
+ export async function callDaemon(
28
27
  path: string,
29
28
  options: { method?: "GET" | "POST"; body?: object } = {},
30
29
  ): Promise<CommandResponse> {
@@ -61,9 +60,7 @@ export async function startDaemonProcess(): Promise<void> {
61
60
  await Bun.$`mkdir -p ${LOGS_DIR}`;
62
61
  }
63
62
 
64
- const proc = Bun.spawn([
65
- "bun", "run", `${import.meta.dir}/daemon/index.ts`
66
- ], {
63
+ const proc = Bun.spawn(["bun", "run", `${import.meta.dir}/../daemon/index.ts`], {
67
64
  stdout: "inherit",
68
65
  stderr: "inherit",
69
66
  stdin: "ignore",
@@ -128,80 +125,3 @@ export async function handleDaemonCommand(
128
125
  process.exit(1);
129
126
  }
130
127
  }
131
-
132
- export { program, callDaemon };
133
-
134
- program
135
- .command("refund")
136
- .description("Refund pending tokens and API keys to a specified mint")
137
- .option("-m, --mint-url <mintUrl>", "Mint URL to refund to (defaults to first mint in wallet)")
138
- .option("-y, --yes", "Skip confirmation prompt", false)
139
- .action(async (options: { mintUrl?: string; yes: boolean }) => {
140
- const config = await loadConfig();
141
-
142
- let mintUrl = options.mintUrl;
143
- if (!mintUrl) {
144
- const balanceResponse = await fetch(`http://localhost:${config.port}/balance`);
145
- const balanceResult = (await balanceResponse.json()) as {
146
- output?: { balances?: Record<string, number> };
147
- error?: string;
148
- };
149
- if (balanceResult.error) {
150
- console.log(balanceResult.error);
151
- process.exit(1);
152
- }
153
- const balances = balanceResult.output?.balances;
154
- if (!balances || Object.keys(balances).length === 0) {
155
- console.log("No mint URLs found in wallet balance");
156
- process.exit(1);
157
- }
158
- mintUrl = Object.keys(balances)[0];
159
- console.log(`Using mint URL: ${mintUrl}`);
160
- }
161
-
162
- try {
163
- const response = await fetch(`http://localhost:${config.port}/refund`, {
164
- method: "POST",
165
- headers: { "Content-Type": "application/json" },
166
- body: JSON.stringify({ mintUrl }),
167
- });
168
-
169
- if (!response.ok) {
170
- const errorData = (await response.json()) as { error?: string };
171
- throw new Error(errorData.error || `HTTP ${response.status}`);
172
- }
173
-
174
- const result = (await response.json()) as {
175
- output?: {
176
- message: string;
177
- pendingTokens: number;
178
- apiKeys: number;
179
- results: Array<{ baseUrl: string; success: boolean }>;
180
- };
181
- error?: string;
182
- };
183
-
184
- if (result.error) {
185
- console.log(result.error);
186
- process.exit(1);
187
- }
188
-
189
- if (result.output) {
190
- console.log(result.output.message);
191
- console.log(`\nPending tokens: ${result.output.pendingTokens}`);
192
- console.log(`API keys: ${result.output.apiKeys}`);
193
- console.log("\nResults:");
194
- for (const r of result.output.results) {
195
- console.log(` - ${r.baseUrl}: ${r.success ? "success" : "failed"}`);
196
- }
197
- }
198
- } catch (error) {
199
- const message = (error as Error).message;
200
- if (message?.includes("fetch failed") || message?.includes("Connection refused")) {
201
- console.error("Daemon is not running");
202
- process.exit(1);
203
- }
204
- console.error(message);
205
- process.exit(1);
206
- }
207
- });
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(bun build:*)",
5
- "Bash(/Users/r/projects/routstr_main/routstrd/log_balance_table.sh)",
6
- "Bash(bunx tsc *)",
7
- "Bash(python3 *)"
8
- ]
9
- }
10
- }