feat(ai-tools): install/update AI CLIs, remote catalog sync, cached favicons (#40 A2)

This commit is contained in:
2026-09-29 08:25:37 +07:00
parent fcfebe44f1
commit 7ec5a7cd85
40 changed files with 3145 additions and 92 deletions
+51 -1
View File
@@ -1950,6 +1950,28 @@ export const api = {
method: "PUT",
body: JSON.stringify(body),
}),
/** POST /api/ai-tools/:id/install - start the catalog's install job. */
install: (id: string) =>
request<{ job: AiToolJob }>(`/ai-tools/${encodeURIComponent(id)}/install`, {
method: "POST",
}),
/** POST /api/ai-tools/:id/update - start the catalog's update job. */
updateTool: (id: string) =>
request<{ job: AiToolJob }>(`/ai-tools/${encodeURIComponent(id)}/update`, {
method: "POST",
}),
/** POST /api/ai-tools/update-all - one job updating every tool with an update. */
updateAll: () => request<{ job: AiToolJob }>("/ai-tools/update-all", { method: "POST" }),
/** GET /api/ai-tools/job - the current or last job, or null. */
job: () => request<AiToolJob | null>("/ai-tools/job"),
/** GET /api/ai-tools/versions - npm version badges, cached a day server-side. */
versions: () => request<Record<string, AiToolVersion>>("/ai-tools/versions"),
/** The server-cached icon; an <img> cannot send the bearer header, so the token rides the query. */
iconUrl: (id: string) => {
const token = dashboardToken();
const q = token ? `?token=${encodeURIComponent(token)}` : "";
return `${BASE}/ai-tools/${encodeURIComponent(id)}/icon${q}`;
},
},
aiRouters: {
/** GET /api/ai-routers - every profile, the built-in `native` one first. */
@@ -2985,7 +3007,7 @@ export interface AiTool {
installed: boolean;
path: string | null;
enabled: boolean;
source: "builtin" | "custom";
source: "builtin" | "remote" | "custom";
/** `claude` / `codex` keep their own run form; null for every other tool. */
launcher: "claude" | "codex" | null;
prompt: { mode: "argv" | "flag" | "after-start" } | null;
@@ -2995,12 +3017,40 @@ export interface AiTool {
bypass: boolean;
/** Can use an AI Router profile (Claude, Codex); others are native only. */
routerCapable: boolean;
/** What Install runs; null when the catalog names no install. */
install: { kind: "npm"; package: string } | { kind: "script"; url: string } | null;
/** What Update runs; `self` is the tool's own updater with these args. */
update: { kind: "npm" } | { kind: "script" } | { kind: "self"; args: string[] } | null;
}
export interface AiToolsView {
tools: AiTool[];
/** The effective default tool id, or null when nothing is installed and enabled. */
default: string | null;
/** Where the catalog came from: this build, or the newer release synced daily. */
catalog?: { source: "builtin" | "remote"; tag: string | null; fetchedAt: string | null };
}
/** An install/update job (GET /api/ai-tools/job). */
export interface AiToolJob {
id: number;
/** null for update-all. */
tool: string | null;
action: "install" | "update" | "update-all";
status: "running" | "ok" | "failed";
startedAt: string;
endedAt: string | null;
exitCode: number | null;
log: string;
}
/** One row of GET /api/ai-tools/versions. */
export interface AiToolVersion {
/** Its binary lives under npm's global prefix, so an npm update replaces it. */
npmManaged: boolean;
installed: string | null;
latest: string | null;
updateAvailable: boolean;
}
export interface AiRouter {