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
+114 -1
View File
@@ -1,6 +1,7 @@
/**
* @file Tests for server/lib/ai-tools.js — catalog validation and override,
* PATH detection over fake executables, and the enable/default state rules.
* PATH detection over fake executables, the enable/default state rules, and
* the A2 install/update fields with their three trust tiers.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
@@ -195,6 +196,8 @@ describe("enable / default", () => {
resume: false,
bypass: false,
routerCapable: false,
install: null,
update: null,
});
assert.equal(v.tools.find((t) => t.id === "codex").routerCapable, true);
});
@@ -213,3 +216,113 @@ describe("built-in catalog", () => {
aiTools.__setCatalogDirs([builtin, custom]);
});
});
describe("install/update fields and trust tiers", () => {
const tiers = path.join(root, "tiers");
const tb = path.join(tiers, "builtin");
const tr = path.join(tiers, "remote");
const tc = path.join(tiers, "custom");
const base = (id, extra = {}) => ({
id,
label: id,
detect: [id],
prompt: { mode: "argv" },
homepageUrl: "https://docs.example.com/cli",
...extra,
});
before(() => {
for (const d of [tb, tr, tc]) fs.mkdirSync(d, { recursive: true });
write(tb, "claude.json", {
id: "claude",
label: "Claude",
detect: ["claude"],
launcher: "claude",
update: { kind: "self", args: ["update"] },
});
write(
tb,
"npmok.json",
base("npmok", {
install: { kind: "npm", package: "@scope/np-m.ok" },
update: { kind: "npm" },
})
);
write(
tb,
"scriptok.json",
base("scriptok", {
install: { kind: "script", url: "https://example.com/install" },
update: { kind: "script" },
})
);
write(tb, "badpkg.json", base("badpkg", { install: { kind: "npm", package: "Bad Pkg;rm" } }));
write(
tb,
"httpurl.json",
base("httpurl", { install: { kind: "script", url: "http://example.com/i" } })
);
write(
tb,
"offhost.json",
base("offhost", { install: { kind: "script", url: "https://evil.io/i" } })
);
write(tb, "npmnoinst.json", base("npmnoinst", { update: { kind: "npm" } }));
write(tb, "selfbad.json", base("selfbad", { update: { kind: "self", args: "update" } }));
write(tb, "shared.json", base("shared", { label: "Shared builtin" }));
write(tr, "shared.json", base("shared", { label: "Shared remote" }));
write(tr, "codex.json", {
id: "codex",
label: "Codex R",
detect: ["codex"],
launcher: "codex",
});
write(
tr,
"remoteonly.json",
base("remoteonly", { install: { kind: "npm", package: "remote-only" } })
);
write(tc, "shared.json", base("shared", { label: "Shared custom" }));
write(tc, "custinst.json", base("custinst", { install: { kind: "npm", package: "x" } }));
write(tc, "custupd.json", base("custupd", { update: { kind: "self", args: ["up"] } }));
aiTools.__setCatalogDirs([tb, tr, tc]);
});
after(() => aiTools.__setCatalogDirs([builtin, custom]));
it("accepts well-formed install/update and rejects the rest", () => {
assert.deepEqual(aiTools.get("npmok").install, { kind: "npm", package: "@scope/np-m.ok" });
assert.deepEqual(aiTools.get("npmok").update, { kind: "npm" });
assert.deepEqual(aiTools.get("scriptok").install, {
kind: "script",
url: "https://example.com/install",
});
assert.deepEqual(aiTools.get("claude").update, { kind: "self", args: ["update"] });
for (const id of ["badpkg", "httpurl", "offhost", "npmnoinst", "selfbad"]) {
assert.equal(aiTools.has(id), false, id);
}
});
it("orders custom over remote over builtin and lets a remote file keep launcher", () => {
assert.equal(aiTools.get("shared").label, "Shared custom");
assert.equal(aiTools.get("codex").source, "remote");
assert.equal(aiTools.get("codex").launcher, "codex");
assert.equal(aiTools.get("remoteonly").install.package, "remote-only");
});
it("refuses install/update in a custom file", () => {
assert.equal(aiTools.has("custinst"), false);
assert.equal(aiTools.has("custupd"), false);
});
it("view() shows install/update so the confirm dialog can name the command", () => {
const rows = aiTools.view().tools;
assert.deepEqual(rows.find((t) => t.id === "npmok").install, {
kind: "npm",
package: "@scope/np-m.ok",
});
assert.deepEqual(rows.find((t) => t.id === "claude").update, {
kind: "self",
args: ["update"],
});
});
});