routstrd 0.2.13 → 0.2.15

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.13",
3
+ "version": "0.2.15",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "private": false,
package/src/cli.ts CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  isDaemonRunning,
8
8
  loadConfig,
9
9
  getDaemonBaseUrl,
10
- getNpubSuffix,
11
10
  getUserNpub,
12
11
  } from "./utils/daemon-client";
13
12
  import {
@@ -833,14 +832,19 @@ clientsCmd
833
832
  },
834
833
  );
835
834
 
836
- // Npubs - manage admin npubs
835
+ // Npubs - manage npubs (admin and user roles)
837
836
  const npubsCmd = program
838
837
  .command("npubs")
839
- .description("Manage admin npubs on the daemon");
838
+ .description("Manage npubs on the daemon (admin or user roles)");
839
+
840
+ type NpubEntry = {
841
+ npub: string;
842
+ role: string;
843
+ };
840
844
 
841
845
  npubsCmd
842
846
  .command("list")
843
- .description("List configured admin npubs")
847
+ .description("List configured npubs with their roles")
844
848
  .action(async () => {
845
849
  await ensureDaemonRunning();
846
850
  const config = await loadConfig();
@@ -851,20 +855,20 @@ npubsCmd
851
855
  process.exit(1);
852
856
  }
853
857
  // Handle both wrapped { output: { npubs } } and direct { npubs } response formats
854
- const data = (result.output as { npubs?: string[] } | undefined)?.npubs
858
+ const data = (result.output as { npubs?: NpubEntry[] } | undefined)?.npubs
855
859
  ? result.output
856
860
  : result;
857
- const npubs = (data as { npubs?: string[] } | undefined)?.npubs ?? [];
861
+ const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? [];
858
862
  if (npubs.length === 0) {
859
863
  console.log("No admin npubs configured. Run 'routstrd npubs register' to register yourself as the first admin.");
860
864
  return;
861
865
  }
862
- console.log(`Admin npubs (${npubs.length}):`);
866
+ console.log(`Npubs (${npubs.length}):`);
863
867
  let found = false;
864
- for (const npub of npubs) {
865
- const marker = npub === userNpub ? " → you" : "";
866
- if (npub === userNpub) found = true;
867
- console.log(`- ${npub}${marker}`);
868
+ for (const entry of npubs) {
869
+ const marker = entry.npub === userNpub ? " → you" : "";
870
+ if (entry.npub === userNpub) found = true;
871
+ console.log(`- ${entry.npub} [${entry.role}]${marker}`);
868
872
  }
869
873
  if (userNpub && !found) {
870
874
  console.log("");
@@ -893,10 +897,10 @@ npubsCmd
893
897
  console.log(result.error);
894
898
  process.exit(1);
895
899
  }
896
- const data = (result.output as { npubs?: string[] } | undefined)?.npubs
900
+ const data = (result.output as { npubs?: NpubEntry[] } | undefined)?.npubs
897
901
  ? result.output
898
902
  : result;
899
- const npubs = (data as { npubs?: string[] } | undefined)?.npubs ?? [];
903
+ const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? [];
900
904
  if (npubs.length > 0) {
901
905
  console.log(`Admin npubs already configured (${npubs.length}). Ask your admin to add your npub. \n Your npub: ${userNpub}`);
902
906
  return;
@@ -926,7 +930,7 @@ npubsCmd
926
930
 
927
931
  npubsCmd
928
932
  .command("add <npub>")
929
- .description("Add an admin npub (hex pubkey or npub1...)")
933
+ .description("Add a npub (hex pubkey or npub1...). First becomes admin, subsequent become user.")
930
934
  .action(async (npubArg: string) => {
931
935
  await ensureDaemonRunning();
932
936
  const normalized = normalizeNostrPubkey(npubArg);
@@ -947,7 +951,7 @@ npubsCmd
947
951
  | undefined;
948
952
  if (output?.npub) {
949
953
  console.log(
950
- `${output.added ? "Added" : "Already configured"} admin npub: ${output.npub}`,
954
+ `${output.added ? "Added" : "Already configured"} npub: ${output.npub}`,
951
955
  );
952
956
  }
953
957
  });
@@ -1,5 +1,5 @@
1
1
  import type { UsageTrackingEntry } from "../../daemon/types.ts";
2
- import { callDaemon, getNpubSuffix, isDaemonRunning, loadConfig } from "../../utils/daemon-client.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 {
@@ -82,21 +82,10 @@ export async function fetchUsage(limit = 10000): Promise<UsageStats | null> {
82
82
  const result = await callDaemon(`/usage?limit=${limit}`);
83
83
  if (result.error) return null;
84
84
 
85
- // The daemon returns { output: [...] } where output is the entries array directly.
86
- // In remote daemon mode, client IDs are suffixed with the last 7 chars of our npub.
87
- // Fetch the full daemon result, but only display/aggregate entries for our clients.
85
+ // The auth proxy filters usage to the authenticated npub's clients and
86
+ // returns client IDs without the owner suffix.
88
87
  const entries = result.output as UsageTrackingEntry[] | undefined;
89
- const entriesArray = Array.isArray(entries) ? entries : [];
90
- const suffix = getNpubSuffix(await loadConfig());
91
- const suffixStr = suffix ? `-${suffix}` : null;
92
- const visibleEntries = suffixStr
93
- ? entriesArray
94
- .filter((entry) => entry.client?.endsWith(suffixStr))
95
- .map((entry) => ({
96
- ...entry,
97
- client: entry.client?.slice(0, -suffixStr.length),
98
- }))
99
- : entriesArray;
88
+ const visibleEntries = Array.isArray(entries) ? entries : [];
100
89
 
101
90
  // Calculate totals from visible entries
102
91
  const totals = visibleEntries.reduce(
@@ -4,49 +4,16 @@ import {
4
4
  getDaemonBaseUrl,
5
5
  ensureDaemonRunning,
6
6
  } from "./daemon-client";
7
- import {
8
- parseSecretKey,
9
- npubFromSecretKey,
10
- } from "./nip98";
11
- import { type RoutstrdConfig } from "./config";
12
7
  import { logger } from "./logger";
13
8
  import { CLIENT_INTEGRATIONS, CLIENT_CONFIGS } from "../integrations/registry";
14
9
 
15
- export function getNpubSuffix(config: RoutstrdConfig): string | null {
16
- if (!config.daemonUrl || !config.nsec) return null;
17
- try {
18
- const secretKey = parseSecretKey(config.nsec);
19
- const npub = npubFromSecretKey(secretKey);
20
- return npub.slice(-7);
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- /**
27
- * Add suffix to a client ID.
28
- */
29
- export function addSuffixToId(id: string, suffix: string): string {
30
- return `${id}-${suffix}`;
31
- }
32
-
33
- /**
34
- * Remove suffix from a client ID if present.
35
- */
36
- export function removeSuffixFromId(id: string, suffix: string): string {
37
- const suffixStr = `-${suffix}`;
38
- if (id.endsWith(suffixStr)) {
39
- return id.slice(0, -suffixStr.length);
40
- }
41
- return id;
42
- }
43
-
44
10
  export interface ClientEntry {
45
11
  clientId: string;
46
12
  name: string;
47
13
  apiKey: string;
48
14
  createdAt: number;
49
15
  lastUsed?: number | null;
16
+ ownerNpub?: string;
50
17
  }
51
18
 
52
19
  export interface DaemonClient {
@@ -55,6 +22,7 @@ export interface DaemonClient {
55
22
  apiKey: string;
56
23
  createdAt: number;
57
24
  lastUsed?: number | null;
25
+ ownerNpub?: string;
58
26
  }
59
27
 
60
28
  /**
@@ -71,12 +39,14 @@ export function getClientsFromStore(store: { getState(): any }): ClientEntry[] {
71
39
  apiKey: string;
72
40
  createdAt: number;
73
41
  lastUsed?: number | null;
42
+ ownerNpub?: string;
74
43
  }) => ({
75
44
  clientId: c.clientId,
76
45
  name: c.name,
77
46
  apiKey: c.apiKey,
78
47
  createdAt: c.createdAt,
79
48
  lastUsed: c.lastUsed,
49
+ ownerNpub: c.ownerNpub,
80
50
  }),
81
51
  );
82
52
  }
@@ -86,7 +56,6 @@ export function getClientsFromStore(store: { getState(): any }): ClientEntry[] {
86
56
  * Use this when running remotely (CLI in remote mode).
87
57
  */
88
58
  export async function getClientsList(): Promise<ClientEntry[]> {
89
- const config = await loadConfig();
90
59
  const result = await callDaemon("/clients");
91
60
  const clients = (
92
61
  result.output as
@@ -97,6 +66,7 @@ export async function getClientsList(): Promise<ClientEntry[]> {
97
66
  apiKey: string;
98
67
  createdAt: number;
99
68
  lastUsed?: number | null;
69
+ ownerNpub?: string;
100
70
  }>;
101
71
  }
102
72
  | undefined
@@ -106,17 +76,14 @@ export async function getClientsList(): Promise<ClientEntry[]> {
106
76
  return [];
107
77
  }
108
78
 
109
- const suffix = config.daemonUrl ? getNpubSuffix(config) : null;
110
-
111
- return clients
112
- .filter((c) => !suffix || c.id.endsWith(`-${suffix}`))
113
- .map((c) => ({
114
- clientId: suffix ? removeSuffixFromId(c.id, suffix) : c.id,
115
- name: c.name,
116
- apiKey: c.apiKey,
117
- createdAt: c.createdAt,
118
- lastUsed: c.lastUsed,
119
- }));
79
+ return clients.map((c) => ({
80
+ clientId: c.id,
81
+ name: c.name,
82
+ apiKey: c.apiKey,
83
+ createdAt: c.createdAt,
84
+ lastUsed: c.lastUsed,
85
+ ownerNpub: c.ownerNpub,
86
+ }));
120
87
  }
121
88
 
122
89
  export async function addDaemonClient(
@@ -135,18 +102,14 @@ export async function addDaemonClient(
135
102
  apiKey: existing.apiKey,
136
103
  createdAt: existing.createdAt,
137
104
  lastUsed: existing.lastUsed,
105
+ ownerNpub: existing.ownerNpub,
138
106
  };
139
107
  return { client, created: false };
140
108
  }
141
109
 
142
- const config = await loadConfig();
143
- const suffix = getNpubSuffix(config);
144
- const clientId = suffix ? addSuffixToId(derivedId, suffix) : derivedId;
145
-
146
-
147
110
  const result = await callDaemon("/clients/add", {
148
111
  method: "POST",
149
- body: { name, id: clientId },
112
+ body: { name, id: derivedId },
150
113
  });
151
114
 
152
115
 
@@ -196,13 +159,9 @@ export async function listClientsAction(): Promise<void> {
196
159
  export async function deleteClientAction(id: string): Promise<void> {
197
160
  await ensureDaemonRunning();
198
161
 
199
- const config = await loadConfig();
200
- const suffix = getNpubSuffix(config);
201
- const resolvedId = suffix ? addSuffixToId(id, suffix) : id;
202
-
203
162
  const result = await callDaemon("/clients/delete", {
204
163
  method: "POST",
205
- body: { id: resolvedId },
164
+ body: { id },
206
165
  });
207
166
 
208
167
  if (result.error) {
@@ -233,7 +192,6 @@ export interface AddClientOptions {
233
192
  export async function addClientAction(options: AddClientOptions): Promise<void> {
234
193
  await ensureDaemonRunning();
235
194
  const config = await loadConfig();
236
- const suffix = getNpubSuffix(config);
237
195
 
238
196
  const integrationKeys: string[] = [];
239
197
  if (options.opencode) integrationKeys.push("opencode");
@@ -259,7 +217,7 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
259
217
  await integrationFn(config, client.apiKey, integrationConfig);
260
218
 
261
219
  console.log(`\n ${integrationConfig.name}:`);
262
- console.log(` Client ID: ${suffix ? removeSuffixFromId(client.id, suffix) : client.id}`);
220
+ console.log(` Client ID: ${client.id}`);
263
221
  console.log(` API Key: ${client.apiKey}`);
264
222
  } catch (error) {
265
223
  logger.error(
@@ -286,7 +244,7 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
286
244
 
287
245
  if (!created) {
288
246
  console.log(`Client '${options.name}' already exists.`);
289
- console.log(`\n ID: ${suffix ? removeSuffixFromId(client.id, suffix) : client.id}`);
247
+ console.log(`\n ID: ${client.id}`);
290
248
  console.log(` Name: ${client.name}`);
291
249
  console.log(` API Key: ${client.apiKey}`);
292
250
  return;
@@ -295,7 +253,7 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
295
253
  if (message) {
296
254
  console.log(message);
297
255
  }
298
- console.log(`\n ID: ${suffix ? removeSuffixFromId(client.id, suffix) : client.id}`);
256
+ console.log(`\n ID: ${client.id}`);
299
257
  console.log(` Name: ${client.name}`);
300
258
  console.log(` API Key: ${client.apiKey}`);
301
259
  console.log(`\n Access Routstr at: ${getDaemonBaseUrl(config)}/v1`);