mirror of
https://github.com/Routstr/routstrd.git
synced 2026-07-22 12:22:20 +00:00
feat(cli): add npubs management commands
- Add normalizeNostrPubkey, npubFromPubkey, npubFromSecretKey to nip98.ts - Extend callDaemon to accept DELETE method - Add subcommands: - list: GET /npubs - add <npub>: POST /npubs (accepts hex pubkey or npub1...) - delete <npub>: DELETE /npubs/:npub (accepts hex pubkey or npub1...) - All npubs commands go through callDaemon, so they work with remote daemons and are automatically NIP-98 signed
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -30,3 +30,7 @@ temp/
|
||||
.pi/
|
||||
|
||||
.worktrees/
|
||||
.routstrd/
|
||||
|
||||
# Standalone prototype scripts
|
||||
index.ts
|
||||
|
||||
83
src/cli.ts
83
src/cli.ts
@@ -27,6 +27,10 @@ import {
|
||||
import { createSdkStore } from "@routstr/sdk";
|
||||
import { createBunSqliteDriver } from "@routstr/sdk/storage";
|
||||
import * as QRCode from "qrcode";
|
||||
import {
|
||||
normalizeNostrPubkey,
|
||||
npubFromPubkey,
|
||||
} from "./utils/nip98";
|
||||
import {
|
||||
isCocodInstalled,
|
||||
resolveCocodExecutable,
|
||||
@@ -866,6 +870,85 @@ clientsCmd
|
||||
}
|
||||
});
|
||||
|
||||
// Npubs - manage admin npubs
|
||||
const npubsCmd = program
|
||||
.command("npubs")
|
||||
.description("Manage admin npubs on the daemon");
|
||||
|
||||
npubsCmd
|
||||
.command("list")
|
||||
.description("List configured admin npubs")
|
||||
.action(async () => {
|
||||
await ensureDaemonRunning();
|
||||
const result = await callDaemon("/npubs");
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
const npubs = (result.output as { npubs?: string[] } | undefined)?.npubs ?? [];
|
||||
if (npubs.length === 0) {
|
||||
console.log("No admin npubs configured.");
|
||||
return;
|
||||
}
|
||||
console.log(`Admin npubs (${npubs.length}):`);
|
||||
for (const npub of npubs) {
|
||||
console.log(`- ${npub}`);
|
||||
}
|
||||
});
|
||||
|
||||
npubsCmd
|
||||
.command("add <npub>")
|
||||
.description("Add an admin npub (hex pubkey or npub1...)")
|
||||
.action(async (npubArg: string) => {
|
||||
await ensureDaemonRunning();
|
||||
const normalized = normalizeNostrPubkey(npubArg);
|
||||
if (!normalized) {
|
||||
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
||||
process.exit(1);
|
||||
}
|
||||
const result = await callDaemon("/npubs", {
|
||||
method: "POST",
|
||||
body: { npub: npubFromPubkey(normalized) },
|
||||
});
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
const output = result.output as
|
||||
| { npub?: string; added?: boolean; error?: string }
|
||||
| undefined;
|
||||
if (output?.npub) {
|
||||
console.log(
|
||||
`${output.added ? "Added" : "Already configured"} admin npub: ${output.npub}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
npubsCmd
|
||||
.command("delete <npub>")
|
||||
.description("Delete an admin npub (hex pubkey or npub1...)")
|
||||
.action(async (npubArg: string) => {
|
||||
await ensureDaemonRunning();
|
||||
const normalized = normalizeNostrPubkey(npubArg);
|
||||
if (!normalized) {
|
||||
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
||||
process.exit(1);
|
||||
}
|
||||
const result = await callDaemon(`/npubs/${encodeURIComponent(npubFromPubkey(normalized))}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (result.error) {
|
||||
console.log(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
const output = result.output as
|
||||
| { removed?: boolean; error?: string }
|
||||
| undefined;
|
||||
console.log(
|
||||
output?.removed ? "Removed admin npub." : "Admin npub was not configured.",
|
||||
);
|
||||
});
|
||||
|
||||
// Monitor - interactive TUI
|
||||
program
|
||||
.command("monitor")
|
||||
|
||||
@@ -34,7 +34,7 @@ export function getDaemonBaseUrl(config: RoutstrdConfig): string {
|
||||
|
||||
export async function callDaemon(
|
||||
path: string,
|
||||
options: { method?: "GET" | "POST"; body?: object } = {},
|
||||
options: { method?: "GET" | "POST" | "DELETE"; body?: object } = {},
|
||||
): Promise<CommandResponse> {
|
||||
const { method = "GET", body } = options;
|
||||
const config = await loadConfig();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { finalizeEvent, nip19, type EventTemplate } from "nostr-tools";
|
||||
import { finalizeEvent, getPublicKey, nip19, type EventTemplate } from "nostr-tools";
|
||||
|
||||
const NIP98_KIND = 27235;
|
||||
|
||||
@@ -45,6 +45,36 @@ function base64EncodeUtf8(value: string): string {
|
||||
return btoa(String.fromCharCode(...new TextEncoder().encode(value)));
|
||||
}
|
||||
|
||||
export function normalizeNostrPubkey(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return null;
|
||||
|
||||
if (/^[a-f0-9]{64}$/i.test(trimmed)) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
if (trimmed.toLowerCase().startsWith("npub1")) {
|
||||
try {
|
||||
const decoded = nip19.decode(trimmed);
|
||||
if (decoded.type === "npub" && typeof decoded.data === "string") {
|
||||
return decoded.data.toLowerCase();
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function npubFromPubkey(pubkey: string): string {
|
||||
return nip19.npubEncode(pubkey.toLowerCase());
|
||||
}
|
||||
|
||||
export function npubFromSecretKey(secretKey: Uint8Array): string {
|
||||
return npubFromPubkey(getPublicKey(secretKey));
|
||||
}
|
||||
|
||||
export async function createNIP98Authorization(
|
||||
secretKey: Uint8Array,
|
||||
url: string,
|
||||
|
||||
Reference in New Issue
Block a user