feat(FN-1080): add node management APIs and CLI commands

- Add dashboard /api/nodes CRUD, health-check, and metrics routes backed by CentralCore with request validation
- Extend project updates and frontend API types to support node assignment via nodeId
- Add dashboard API client helpers for node list/register/update/delete/health/metrics operations
- Implement fn node list/add/remove/show/health commands with validation, confirmation, and table/JSON output
- Add route and CLI/bin tests plus a minor @gsxdsm/fusion changeset for node management support
This commit is contained in:
gsxdsm
2026-04-07 22:28:58 -07:00
parent a0a7a9c1db
commit b5980ebbb3
8 changed files with 1277 additions and 5 deletions

View File

@@ -47,6 +47,7 @@ const { runGitStatus, runGitFetch, runGitPull, runGitPush } = await import("./co
const { runBackupCreate, runBackupList, runBackupRestore, runBackupCleanup } = await import("./commands/backup.js");
const { runMissionCreate, runMissionList, runMissionShow, runMissionDelete, runMissionActivateSlice } = await import("./commands/mission.js");
const { runProjectList, runProjectAdd, runProjectRemove, runProjectShow, runProjectInfo, runProjectSetDefault, runProjectDetect } = await import("./commands/project.js");
const { runNodeList, runNodeAdd, runNodeRemove, runNodeShow, runNodeHealth } = await import("./commands/node.js");
const { runInit } = await import("./commands/init.js");
const { runAgentStop, runAgentStart } = await import("./commands/agent.js");
const { runAgentImport } = await import("./commands/agent-import.js");
@@ -100,6 +101,13 @@ Usage:
fn project set-default | default <name>
Set default project
fn project detect Detect project from current directory
fn node list | ls [--json] List all nodes
fn node add <name> [--url <url>] [--api-key <key>] [--max-concurrent <n>]
Register a new node
fn node remove | rm <name> [--force]
Unregister a node
fn node show | info [name] Show node details
fn node health <name> Check node health
fn settings Show current Fusion configuration
fn settings set <key> <value> Update a configuration setting
fn settings export [opts] Export settings to a JSON file
@@ -170,6 +178,30 @@ function extractGlobalProjectFlag(argv: string[]): { cleanedArgs: string[]; proj
return { cleanedArgs, projectName };
}
function getFlagValue(args: string[], flag: string): string | undefined {
const index = args.indexOf(flag);
if (index === -1 || index + 1 >= args.length) {
return undefined;
}
const value = args[index + 1];
if (!value || value.startsWith("-")) {
return undefined;
}
return value;
}
function getFlagValueNumber(args: string[], flag: string): number | undefined {
const value = getFlagValue(args, flag);
if (value === undefined) {
return undefined;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
/**
* Check if migration is needed and run it automatically.
* This handles the transition from single-project to multi-project mode.
@@ -319,6 +351,46 @@ async function main() {
break;
}
case "node": {
const subcommand = args[1];
switch (subcommand) {
case "list":
case "ls": {
await runNodeList({ json: args.includes("--json") });
break;
}
case "add": {
const name = args[2];
await runNodeAdd(name, {
url: getFlagValue(args, "--url"),
apiKey: getFlagValue(args, "--api-key"),
maxConcurrent: getFlagValueNumber(args, "--max-concurrent"),
});
break;
}
case "remove":
case "rm": {
const name = args[2];
await runNodeRemove(name, { force: args.includes("--force") });
break;
}
case "show":
case "info": {
await runNodeShow(args[2]);
break;
}
case "health": {
await runNodeHealth(args[2]);
break;
}
default:
console.error(`Unknown subcommand: node ${subcommand || ""}`);
console.log("Try: fn node list | add | remove | show | health");
process.exit(1);
}
break;
}
case "task": {
const subcommand = args[1];
switch (subcommand) {