feat(providers): show manual enable/disable status and add nostr-sync command

- providers list now shows the source of a provider's state:
  DISABLED (manual), DISABLED (nostr review), or enabled (manual override)
- new 'routstrd providers nostr-sync <indices...>' command clears manual
  enable/disable overrides via POST /providers/nostr-sync, returning the
  provider to its kind-38425 review-based state, and reports the
  resulting enabled/disabled verdict
This commit is contained in:
redshift
2026-09-22 10:55:47 +03:00
parent 1e882fb983
commit 6a1abd9a45
2 changed files with 140 additions and 1 deletions
+52 -1
View File
@@ -1152,6 +1152,8 @@ providersCmd
index: number;
baseUrl: string;
disabled: boolean;
manuallyDisabled?: boolean;
manuallyEnabled?: boolean;
}>;
disabledCount: number;
totalCount: number;
@@ -1167,7 +1169,13 @@ providersCmd
`Providers (${output.totalCount} total, ${output.disabledCount} disabled):\n`,
);
for (const provider of output.providers) {
const status = provider.disabled ? "DISABLED" : "enabled ";
const status = provider.manuallyDisabled
? "DISABLED (manual) "
: provider.disabled
? "DISABLED (nostr review) "
: provider.manuallyEnabled
? "enabled (manual override)"
: "enabled ";
console.log(` [${provider.index}] ${status} ${provider.baseUrl}`);
}
});
@@ -1246,6 +1254,49 @@ providersCmd
}
});
providersCmd
.command("nostr-sync <indices...>")
.description(
"Remove manual enable/disable overrides and return providers to their Nostr review-based state (e.g., routstrd providers nostr-sync 0 2 5)",
)
.action(async (indices: string[]) => {
await ensureDaemonRunning();
const indexNums = indices
.map((s) => parseInt(s, 10))
.filter((n) => Number.isFinite(n));
if (indexNums.length === 0) {
console.log("No valid indices provided.");
process.exit(1);
}
const result = await callDaemon("/providers/nostr-sync", {
method: "POST",
body: { indices: indexNums },
});
if (result.error) {
console.log(result.error);
process.exit(1);
}
const output = result.output as
| {
message: string;
providers: Array<{ baseUrl: string; disabled: boolean }>;
}
| undefined;
if (output) {
console.log(output.message);
for (const provider of output.providers) {
const status = provider.disabled
? "DISABLED (nostr review)"
: "enabled";
console.log(` - ${provider.baseUrl} -> ${status}`);
}
}
});
providersCmd
.command("reviews")
.description(
+88
View File
@@ -1286,6 +1286,89 @@ export function createDaemonRequestHandler(deps: {
return;
}
if (req.method === "POST" && url.pathname === "/providers/nostr-sync") {
try {
const bodyText = await readBody(req);
const body = bodyText ? JSON.parse(bodyText) : {};
const indices = body.indices as number[] | undefined;
if (!Array.isArray(indices)) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
error: "Missing or invalid 'indices' field (expected number[]).",
}),
);
return;
}
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
// Clearing both manual lists returns the provider to the
// review-based state: the effective disabled set is the union of
// disabledProviders (kind-38425 review sync) and the manual lists,
// so with no manual entries the Nostr review verdict applies again.
const manuallyDisabledProviders: string[] = [
...(state.manuallyDisabledProviders || []),
];
const manuallyEnabledProviders: string[] = [
...(state.manuallyEnabledProviders || []),
];
const synced: string[] = [];
for (const idx of indices) {
if (
typeof idx === "number" &&
idx >= 0 &&
idx < baseUrlsList.length
) {
const baseUrl = baseUrlsList[idx]!;
const disabledPos = manuallyDisabledProviders.indexOf(baseUrl);
if (disabledPos !== -1) {
manuallyDisabledProviders.splice(disabledPos, 1);
}
const enabledPos = manuallyEnabledProviders.indexOf(baseUrl);
if (enabledPos !== -1) {
manuallyEnabledProviders.splice(enabledPos, 1);
}
synced.push(baseUrl);
}
}
deps.store.getState().setManuallyDisabledProviders(manuallyDisabledProviders);
deps.discoveryAdapter.setManuallyDisabledProviders(manuallyDisabledProviders);
deps.store.getState().setManuallyEnabledProviders(manuallyEnabledProviders);
deps.discoveryAdapter.setManuallyEnabledProviders(manuallyEnabledProviders);
// Report each synced provider's resulting state so the CLI can show
// whether Nostr reviews now have it enabled or disabled. Read from
// the discovery adapter (the source the router uses) after the
// writes above; with both manual lists cleared, its disabled set is
// exactly the review-based set.
const reviewDisabled = new Set(
deps.discoveryAdapter.getDisabledProviders() || [],
);
const resulting = synced.map((baseUrl) => ({
baseUrl,
disabled: reviewDisabled.has(baseUrl),
}));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
output: {
message: `Reverted ${synced.length} provider(s) to Nostr review-based state`,
providers: resulting,
},
}),
);
} catch (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: String(error) }));
}
return;
}
// Client management endpoints
if (req.method === "GET" && url.pathname === "/clients") {
try {
@@ -1490,6 +1573,9 @@ export function createDaemonRequestHandler(deps: {
const manuallyEnabled = new Set(
state.manuallyEnabledProviders || [],
);
const manuallyDisabled = new Set(
state.manuallyDisabledProviders || [],
);
const disabledProviders: string[] = [
...new Set([
...(state.disabledProviders || []),
@@ -1501,6 +1587,8 @@ export function createDaemonRequestHandler(deps: {
index,
baseUrl,
disabled: disabledProviders.includes(baseUrl),
manuallyDisabled: manuallyDisabled.has(baseUrl),
manuallyEnabled: manuallyEnabled.has(baseUrl),
}));
// Only count disabled providers that are actually in the current list