feat(api): add workspace list and switch API routes
New /api/workspace/list and /api/workspace/switch for workspace discovery and switching.
This commit is contained in:
parent
796a2fcb34
commit
232d6640ba
53
apps/web/app/api/workspace/list/route.ts
Normal file
53
apps/web/app/api/workspace/list/route.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { discoverWorkspaces, getActiveWorkspaceName } from "@/lib/workspace";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
type GatewayMeta = {
|
||||
mode?: string;
|
||||
port?: number;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
function readGatewayMeta(stateDir: string): GatewayMeta | null {
|
||||
for (const filename of ["openclaw.json", "config.json"]) {
|
||||
const configPath = join(stateDir, filename);
|
||||
if (!existsSync(configPath)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as {
|
||||
gateway?: { mode?: unknown; port?: unknown };
|
||||
};
|
||||
const port = typeof raw.gateway?.port === "number"
|
||||
? raw.gateway.port
|
||||
: typeof raw.gateway?.port === "string"
|
||||
? Number.parseInt(raw.gateway.port, 10)
|
||||
: undefined;
|
||||
const mode = typeof raw.gateway?.mode === "string" ? raw.gateway.mode : undefined;
|
||||
return {
|
||||
...(mode ? { mode } : {}),
|
||||
...(Number.isFinite(port) ? { port } : {}),
|
||||
...(Number.isFinite(port) ? { url: `ws://127.0.0.1:${port}` } : {}),
|
||||
};
|
||||
} catch {
|
||||
// Continue to fallback config file candidate.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const workspaces = discoverWorkspaces().map((workspace) => ({
|
||||
...workspace,
|
||||
gateway: readGatewayMeta(workspace.stateDir),
|
||||
}));
|
||||
const activeWorkspace = getActiveWorkspaceName() ?? workspaces.find((item) => item.isActive)?.name ?? null;
|
||||
|
||||
return Response.json({
|
||||
workspaces,
|
||||
activeWorkspace,
|
||||
});
|
||||
}
|
||||
56
apps/web/app/api/workspace/switch/route.ts
Normal file
56
apps/web/app/api/workspace/switch/route.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
discoverWorkspaces,
|
||||
getActiveWorkspaceName,
|
||||
resolveOpenClawStateDir,
|
||||
resolveWorkspaceRoot,
|
||||
setUIActiveWorkspace,
|
||||
} from "@/lib/workspace";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const WORKSPACE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
|
||||
function normalizeSwitchWorkspace(raw: unknown): string | null {
|
||||
if (typeof raw !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (!WORKSPACE_NAME_RE.test(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = (await req.json().catch(() => ({}))) as { workspace?: unknown };
|
||||
const requestedWorkspace = normalizeSwitchWorkspace(body.workspace);
|
||||
if (!requestedWorkspace) {
|
||||
return Response.json(
|
||||
{ error: "Invalid workspace name. Use letters, numbers, hyphens, or underscores." },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const discovered = discoverWorkspaces();
|
||||
const availableNames = new Set(discovered.map((workspace) => workspace.name));
|
||||
if (!availableNames.has(requestedWorkspace)) {
|
||||
return Response.json(
|
||||
{ error: `Workspace '${requestedWorkspace}' was not found.` },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
setUIActiveWorkspace(requestedWorkspace);
|
||||
const activeWorkspace = getActiveWorkspaceName();
|
||||
const selected = discoverWorkspaces().find((workspace) => workspace.name === activeWorkspace) ?? null;
|
||||
return Response.json({
|
||||
activeWorkspace,
|
||||
stateDir: resolveOpenClawStateDir(),
|
||||
workspaceRoot: resolveWorkspaceRoot(),
|
||||
workspace: selected,
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user