mirror of
https://github.com/Routstr/routstrd.git
synced 2026-08-10 12:13:08 +00:00
Merge pull request #19 from Routstr/fix/remote-mode-clients-list
Fix/remote mode clients list
This commit is contained in:
+19
-21
@@ -9,8 +9,10 @@ import {
|
||||
getDaemonBaseUrl,
|
||||
getNpubSuffix,
|
||||
addDaemonClient,
|
||||
ensureDaemonClient,
|
||||
getUserNpub,
|
||||
} from "./utils/daemon-client";
|
||||
import { getClientsList } from "./utils/clients";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
import {
|
||||
@@ -26,7 +28,6 @@ import {
|
||||
setupIntegration,
|
||||
CLIENT_CONFIGS,
|
||||
CLIENT_INTEGRATIONS,
|
||||
ensureIntegrationClient,
|
||||
} from "./integrations";
|
||||
import * as QRCode from "qrcode";
|
||||
import { normalizeNostrPubkey, npubFromPubkey } from "./utils/nip98";
|
||||
@@ -732,26 +733,15 @@ clientsCmd
|
||||
const config = await loadConfig();
|
||||
const suffix = getNpubSuffix(config);
|
||||
|
||||
const result = await callDaemon("/clients");
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
const entries = await getClientsList();
|
||||
|
||||
const output = result.output as
|
||||
| {
|
||||
clients: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
createdAt: number;
|
||||
lastUsed?: number | null;
|
||||
}>;
|
||||
totalCount: number;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
let clients = output?.clients || [];
|
||||
let clients = entries.map((c) => ({
|
||||
id: c.clientId,
|
||||
name: c.name,
|
||||
apiKey: c.apiKey,
|
||||
createdAt: c.createdAt,
|
||||
lastUsed: c.lastUsed,
|
||||
}));
|
||||
|
||||
if (suffix) {
|
||||
const suffixStr = `_${suffix}`;
|
||||
@@ -856,7 +846,15 @@ clientsCmd
|
||||
if (!integrationFn || !integrationConfig) continue;
|
||||
|
||||
try {
|
||||
const client = await ensureIntegrationClient(integrationConfig);
|
||||
const { client, created } = await ensureDaemonClient(
|
||||
integrationConfig.name,
|
||||
integrationConfig.clientId,
|
||||
);
|
||||
if (created) {
|
||||
logger.log(`Created new API key for ${integrationConfig.name}`);
|
||||
} else {
|
||||
logger.log(`Using existing API key for ${integrationConfig.name}`);
|
||||
}
|
||||
await integrationFn(config, client.apiKey, integrationConfig);
|
||||
|
||||
console.log(`\n ${integrationConfig.name}:`);
|
||||
|
||||
+15
-31
@@ -14,6 +14,7 @@ import {
|
||||
type CocodState,
|
||||
} from "../wallet/cocod-client";
|
||||
import { decodeCashuTokenAmount } from "../wallet";
|
||||
import { getClientsFromStore } from "../../utils/clients";
|
||||
|
||||
type ClientMode = "xcashu" | "lazyrefund" | "apikeys";
|
||||
|
||||
@@ -63,12 +64,8 @@ function getClientIdFromRequest(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const state = store.getState();
|
||||
const clientIds = state.clientIds || [];
|
||||
|
||||
const matchingClient = (
|
||||
clientIds as { clientId: string; apiKey: string }[]
|
||||
).find((c) => c.apiKey === apiKey);
|
||||
const clients = getClientsFromStore(store);
|
||||
const matchingClient = clients.find((c) => c.apiKey === apiKey);
|
||||
|
||||
return matchingClient?.clientId;
|
||||
}
|
||||
@@ -710,24 +707,13 @@ export function createDaemonRequestHandler(deps: {
|
||||
// Client management endpoints
|
||||
if (req.method === "GET" && url.pathname === "/clients") {
|
||||
try {
|
||||
const state = deps.store.getState();
|
||||
const clientIds = state.clientIds || [];
|
||||
|
||||
const clients = clientIds.map(
|
||||
(c: {
|
||||
clientId: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
createdAt: number;
|
||||
lastUsed?: number | null;
|
||||
}) => ({
|
||||
id: c.clientId,
|
||||
name: c.name,
|
||||
apiKey: c.apiKey,
|
||||
createdAt: c.createdAt,
|
||||
lastUsed: c.lastUsed,
|
||||
}),
|
||||
);
|
||||
const clients = getClientsFromStore(deps.store).map((c) => ({
|
||||
id: c.clientId,
|
||||
name: c.name,
|
||||
apiKey: c.apiKey,
|
||||
createdAt: c.createdAt,
|
||||
lastUsed: c.lastUsed,
|
||||
}));
|
||||
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(
|
||||
@@ -793,10 +779,9 @@ export function createDaemonRequestHandler(deps: {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = deps.store.getState();
|
||||
const existingClients = state.clientIds || [];
|
||||
const existingClients = getClientsFromStore(deps.store);
|
||||
const existingClient = existingClients.find(
|
||||
(c: { clientId: string }) => c.clientId === clientId,
|
||||
(c) => c.clientId === clientId,
|
||||
);
|
||||
|
||||
if (existingClient) {
|
||||
@@ -863,10 +848,9 @@ export function createDaemonRequestHandler(deps: {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = deps.store.getState();
|
||||
const existingClients = state.clientIds || [];
|
||||
const existingClients = getClientsFromStore(deps.store);
|
||||
const index = existingClients.findIndex(
|
||||
(c: { clientId: string }) => c.clientId === id,
|
||||
(c) => c.clientId === id,
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
@@ -879,7 +863,7 @@ export function createDaemonRequestHandler(deps: {
|
||||
return;
|
||||
}
|
||||
|
||||
const removedClient = existingClients[index];
|
||||
const removedClient = existingClients[index]!;
|
||||
const updatedClients = existingClients.filter(
|
||||
(_c: unknown, i: number) => i !== index,
|
||||
);
|
||||
|
||||
+3
-4
@@ -21,6 +21,7 @@ import { createCocodClient } from "./wallet/cocod-client";
|
||||
import { createModelService } from "./models";
|
||||
import { createDaemonRequestHandler } from "./http";
|
||||
import { runIntegrationsForClients } from "../integrations";
|
||||
import { getClientsFromStore } from "../utils/clients";
|
||||
import { RoutstrClient } from "@routstr/sdk";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
@@ -108,8 +109,7 @@ async function main(): Promise<void> {
|
||||
logger.log("Scheduled model refresh completed successfully.");
|
||||
|
||||
// Refresh integrations for all registered clients
|
||||
const state = store.getState();
|
||||
const clientIds = state.clientIds || [];
|
||||
const clientIds = getClientsFromStore(store);
|
||||
if (clientIds.length > 0) {
|
||||
logger.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
||||
await runIntegrationsForClients(clientIds, updatedConfig);
|
||||
@@ -217,8 +217,7 @@ async function main(): Promise<void> {
|
||||
.then(async () => {
|
||||
logger.log("Initial model refresh completed.");
|
||||
// Refresh integrations for all registered clients after initial bootstrap
|
||||
const state = store.getState();
|
||||
const clientIds = state.clientIds || [];
|
||||
const clientIds = getClientsFromStore(store);
|
||||
if (clientIds.length > 0) {
|
||||
logger.log(`Refreshing ${clientIds.length} client integration(s)...`);
|
||||
await runIntegrationsForClients(clientIds, updatedConfig);
|
||||
|
||||
+11
-36
@@ -1,8 +1,7 @@
|
||||
import type { RoutstrdConfig } from "../utils/config";
|
||||
import { logger } from "../utils/logger";
|
||||
import {
|
||||
addDaemonClient,
|
||||
callDaemon,
|
||||
ensureDaemonClient,
|
||||
type DaemonClient,
|
||||
} from "../utils/daemon-client";
|
||||
import { installOpencodeIntegration } from "./opencode";
|
||||
@@ -13,8 +12,6 @@ import type { IntegrationConfig } from "./registry";
|
||||
import { CLIENT_CONFIGS } from "./registry";
|
||||
export { CLIENT_INTEGRATIONS, CLIENT_CONFIGS, runIntegrationsForClients } from "./registry";
|
||||
|
||||
export type IntegrationClient = DaemonClient;
|
||||
|
||||
function ask(question: string): Promise<string> {
|
||||
process.stdout.write(question);
|
||||
|
||||
@@ -45,37 +42,6 @@ function parseChoice(input: string): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
export async function ensureIntegrationClient(
|
||||
integrationConfig: IntegrationConfig,
|
||||
): Promise<IntegrationClient> {
|
||||
try {
|
||||
const { client } = await addDaemonClient(integrationConfig.name);
|
||||
|
||||
logger.log(`Created new API key for ${integrationConfig.name}`);
|
||||
return client;
|
||||
} catch (error) {
|
||||
const message = (error as Error).message || "";
|
||||
if (!message.includes("already exists")) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const clientsResult = await callDaemon("/clients");
|
||||
const clients =
|
||||
(clientsResult.output as { clients?: IntegrationClient[] } | undefined)
|
||||
?.clients || [];
|
||||
const client = clients.find((c) => c.id === integrationConfig.clientId);
|
||||
|
||||
if (!client?.apiKey) {
|
||||
throw new Error(
|
||||
`Client '${integrationConfig.clientId}' already exists but could not be fetched from the daemon.`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.log(`Using existing API key for ${integrationConfig.name}`);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupIntegration(
|
||||
config: RoutstrdConfig,
|
||||
): Promise<void> {
|
||||
@@ -103,7 +69,16 @@ export async function setupIntegration(
|
||||
}
|
||||
|
||||
const integrationConfig = CLIENT_CONFIGS[key]!;
|
||||
const client = await ensureIntegrationClient(integrationConfig);
|
||||
const { client, created } = await ensureDaemonClient(
|
||||
integrationConfig.name,
|
||||
integrationConfig.clientId,
|
||||
);
|
||||
|
||||
if (created) {
|
||||
logger.log(`Created new API key for ${integrationConfig.name}`);
|
||||
} else {
|
||||
logger.log(`Using existing API key for ${integrationConfig.name}`);
|
||||
}
|
||||
|
||||
if (key === "opencode") {
|
||||
await installOpencodeIntegration(config, client.apiKey, integrationConfig);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { callDaemon } from "./daemon-client";
|
||||
|
||||
export interface ClientEntry {
|
||||
clientId: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
createdAt: number;
|
||||
lastUsed?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the clients list directly from the SDK store.
|
||||
* Use this when running inside the daemon (local mode).
|
||||
*/
|
||||
export function getClientsFromStore(store: { getState(): any }): ClientEntry[] {
|
||||
const state = store.getState();
|
||||
const clientIds = state.clientIds || [];
|
||||
return clientIds.map(
|
||||
(c: {
|
||||
clientId: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
createdAt: number;
|
||||
lastUsed?: number | null;
|
||||
}) => ({
|
||||
clientId: c.clientId,
|
||||
name: c.name,
|
||||
apiKey: c.apiKey,
|
||||
createdAt: c.createdAt,
|
||||
lastUsed: c.lastUsed,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the clients list from the daemon API.
|
||||
* Use this when running remotely (CLI in remote mode).
|
||||
*/
|
||||
export async function getClientsList(): Promise<ClientEntry[]> {
|
||||
const result = await callDaemon("/clients");
|
||||
const clients = (
|
||||
result.output as
|
||||
| {
|
||||
clients?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
createdAt: number;
|
||||
lastUsed?: number | null;
|
||||
}>;
|
||||
}
|
||||
| undefined
|
||||
)?.clients;
|
||||
|
||||
if (!clients) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return clients.map((c) => ({
|
||||
clientId: c.id,
|
||||
name: c.name,
|
||||
apiKey: c.apiKey,
|
||||
createdAt: c.createdAt,
|
||||
lastUsed: c.lastUsed,
|
||||
}));
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
npubFromSecretKey,
|
||||
type HttpMethod,
|
||||
} from "./nip98";
|
||||
import { getClientsList } from "./clients";
|
||||
|
||||
export interface CommandResponse {
|
||||
output?: unknown;
|
||||
@@ -44,6 +45,40 @@ export async function addDaemonClient(
|
||||
return { message: output.message, client: output.client };
|
||||
}
|
||||
|
||||
export async function ensureDaemonClient(
|
||||
name: string,
|
||||
clientId: string,
|
||||
): Promise<{ client: DaemonClient; created: boolean }> {
|
||||
try {
|
||||
const { client } = await addDaemonClient(name);
|
||||
return { client, created: true };
|
||||
} catch (error) {
|
||||
const message = (error as Error).message || "";
|
||||
if (!message.includes("already exists")) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const clients = await getClientsList();
|
||||
const entry = clients.find((c) => c.clientId === clientId);
|
||||
|
||||
if (!entry?.apiKey) {
|
||||
throw new Error(
|
||||
`Client '${clientId}' already exists but could not be fetched from the daemon.`,
|
||||
);
|
||||
}
|
||||
|
||||
const client: DaemonClient = {
|
||||
id: entry.clientId,
|
||||
name: entry.name,
|
||||
apiKey: entry.apiKey,
|
||||
createdAt: entry.createdAt,
|
||||
lastUsed: entry.lastUsed,
|
||||
};
|
||||
|
||||
return { client, created: false };
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadConfig(): Promise<RoutstrdConfig> {
|
||||
try {
|
||||
if (existsSync(CONFIG_FILE)) {
|
||||
|
||||
Reference in New Issue
Block a user