Skip to content
309 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { InstallRequest, PluginComponents, PluginListing, SkillListing, WorkspaceConnector } from "@g1t/contracts";
5import { connectorsFor } from "@g1t/contracts/connectors";
6
7import {
8 addPath,
9 availabilityWord,
10 categoriesOf,
11 claudeCodeOnly,
12 componentChips,
13 connectorCallbackPath,
14 connectorNewPath,
15 connectorPagePath,
16 connectorsDirectory,
17 customizeForm,
18 fetchWords,
19 filterPlugins,
20 filterSkills,
21 flashOf,
22 fromWords,
23 groupByMarketplace,
24 kindWord,
25 matches,
26 needsReload,
27 openRequests,
28 personalDirectory,
29 pluginAgentDraft,
30 pluginListingRef,
31 pluginPath,
32 reachOptions,
33 reachWords,
34 readPluginFilters,
35 readQuery,
36 readView,
37 requestedBy,
38 skillListingRef,
39 splitTarget,
40 tabOf,
41 titleOf,
42 waitingFor,
43 withParams,
44} from "./customize.ts";
45import { modeOf, workspaceRedirect } from "./workspace-nav.ts";
46
47const request = (listing: string, by: string, status: InstallRequest["status"] = "open"): InstallRequest => ({
48 id: `ins_${listing}_${by}`,
49 listing,
50 kind: listing.startsWith("plugin:") ? "plugin" : listing.startsWith("skill:") ? "skill" : listing.startsWith("connector:") ? "connector" : "integration",
51 name: listing,
52 note: null,
53 requested_by: by,
54 requested_at: "2026-10-10T12:00:00Z",
55 status,
56 resolved_by: null,
57 resolved_at: null,
58});
59
60const plugin = (name: string, marketplace: string, extra: Partial<PluginListing> = {}): PluginListing => ({
61 ref: `${name}@${marketplace}`,
62 name,
63 marketplace,
64 display_name: null,
65 description: `${name} does things`,
66 version: "1.0.0",
67 author: "Anthropic",
68 homepage: null,
69 category: null,
70 tags: [],
71 keywords: [],
72 fetch: { kind: "github", repo: "anthropics/skills", ref: null, sha: null, path: name },
73 availability: { kind: "available" },
74 skills: [],
75 ...extra,
76});
77
78const skill = (name: string, plugin: string, marketplace: string, extra: Partial<SkillListing> = {}): SkillListing => ({
79 ref: `${plugin}:${name}`,
80 name,
81 plugin,
82 marketplace,
83 description: `${name} skill`,
84 category: null,
85 author: null,
86 added: false,
87 availability: { kind: "available" },
88 ...extra,
89});
90
91const connector = (id: string, from: WorkspaceConnector["from"]): WorkspaceConnector => ({
92 id,
93 name: id.replace("con_", ""),
94 title: id,
95 url: "https://mcp.example.com/mcp",
96 category: null,
97 about: null,
98 auth: "oauth",
99 state: "connected",
100 problem: null,
101 from,
102 tools: [],
103 checked_at: null,
104 header_name: null,
105 signed_in_as: null,
106 signed_in_at: "2026-10-10T12:00:00Z",
107 grants: [],
108 added_by: "ana",
109 added_at: "2026-10-10T12:00:00Z",
110});
111
112test("the view and search live in the address, and Yours has a clean one", () => {
113 assert.equal(readView(new URLSearchParams("")), "yours");
114 assert.equal(readView(new URLSearchParams("view=discover")), "discover");
115 assert.equal(readView(new URLSearchParams("view=nonsense")), "yours");
116 assert.equal(readQuery(new URLSearchParams("q=+pdf+")), "pdf");
117 assert.equal(withParams("/acme/-/customize/skills", new URLSearchParams("q=pdf"), { view: "discover" }), "/acme/-/customize/skills?q=pdf&view=discover");
118 assert.equal(withParams("/acme/-/customize/skills", new URLSearchParams("view=discover&q=pdf"), { view: "yours", q: null }), "/acme/-/customize/skills");
119 assert.equal(tabOf("/acme/-/customize/plugins"), "plugins");
120 assert.equal(tabOf("/acme/-/customize/plugins.data"), "plugins");
121 assert.equal(tabOf("/acme/-/customize/plugins/pdf%40anthropic-agent-skills"), null);
122 assert.equal(tabOf("/acme/-/customize/requests"), null);
123});
124
125test("pages are under -/customize, with a plugin's ref encoded", () => {
126 assert.equal(pluginPath("acme", "pdf@anthropic-agent-skills"), "/acme/-/customize/plugins/pdf%40anthropic-agent-skills");
127 assert.equal(connectorPagePath("acme", "con_abc"), "/acme/-/customize/connectors/con_abc");
128 assert.equal(connectorNewPath("acme"), "/acme/-/customize/connectors/new");
129 assert.equal(connectorNewPath("acme", "notion"), "/acme/-/customize/connectors/new?directory=notion");
130 assert.equal(connectorCallbackPath("acme"), "/acme/-/customize/connectors/callback");
131});
132
133test("Customize is part of Apps, and the Marketplace's addresses lead to it", () => {
134 assert.equal(modeOf("/acme/-/customize", "acme"), "apps");
135 assert.equal(modeOf("/acme/-/customize/plugins/pdf%40g1t", "acme"), "apps");
136 assert.equal(workspaceRedirect("/acme/-/marketplace"), "/acme/-/customize");
137 assert.equal(workspaceRedirect("/acme/-/marketplace/extensions"), "/acme/-/customize/plugins");
138 assert.equal(workspaceRedirect("/acme/-/marketplace/extensions/mail"), "/acme/-/customize/plugins/mail%40g1t");
139 assert.equal(workspaceRedirect("/acme/-/marketplace/integrations"), "/acme/-/customize/connectors");
140 assert.equal(workspaceRedirect("/acme/-/marketplace/integrations/linear"), "/acme/-/customize/connectors?directory=linear");
141 assert.equal(workspaceRedirect("/acme/-/marketplace/requests", "?x=1"), "/acme/-/customize/requests?x=1");
142 assert.equal(workspaceRedirect("/acme/-/agents/skills"), "/acme/-/customize/skills?view=yours");
143 assert.equal(workspaceRedirect("/acme/-/agents/skills.data", "?_routes=x&q=pdf"), "/acme/-/customize/skills?q=pdf&view=yours");
144 assert.equal(workspaceRedirect("/acme/-/agents/skills/pdf"), null, "a skill's own page stays where it is");
145 assert.equal(workspaceRedirect("/acme/-/customize/skills"), null);
146});
147
148test("requests name plugins, skills and connectors, and an owner adds each where it is added", () => {
149 const views = connectorsFor("workspace");
150 assert.equal(pluginListingRef(plugin("pdf", "anthropic-agent-skills")), "plugin:pdf@anthropic-agent-skills");
151 assert.equal(skillListingRef(skill("pdf", "document-skills", "anthropic-agent-skills")), "skill:document-skills@anthropic-agent-skills/pdf");
152 assert.equal(addPath(request("plugin:pdf@anthropic-agent-skills", "ana"), "acme", views), "/acme/-/customize/plugins/pdf%40anthropic-agent-skills");
153 assert.equal(addPath(request("skill:document-skills@anthropic-agent-skills/pdf", "ana"), "acme", views), "/acme/-/customize/plugins/document-skills%40anthropic-agent-skills");
154 assert.equal(addPath(request("connector:notion", "ana"), "acme", views), "/acme/-/customize/connectors/new?directory=notion");
155 assert.equal(addPath(request("integration:jira", "ana"), "acme", views), "/acme/-/integrations/trackers?add=jira#add");
156 assert.equal(addPath({ listing: "extension:mail" }, "acme", views), null, "extensions are g1t's own, announced");
157 assert.equal(addPath({ listing: "nonsense" }, "acme", views), null);
158 assert.equal(kindWord("skill"), "Skill");
159 assert.equal(kindWord("integration"), "Integration");
160});
161
162test("who asked for what, and what is still waiting", () => {
163 const requests = [request("plugin:pdf@m", "ana"), request("plugin:pdf@m", "bo"), request("plugin:xlsx@m", "ana", "done")];
164 assert.equal(openRequests(requests).length, 2);
165 assert.ok(requestedBy(requests, "plugin:pdf@m", "Ana"));
166 assert.ok(!requestedBy(requests, "plugin:xlsx@m", "ana"), "answered is not waiting");
167 assert.equal(waitingFor(requests, "plugin:pdf@m"), 2);
168 assert.equal(waitingFor(requests, "plugin:none@m"), 0);
169});
170
171test("the requests forms are read strictly", () => {
172 const form = (fields: Record<string, string>) => {
173 const data = new FormData();
174 for (const [k, v] of Object.entries(fields)) data.set(k, v);
175 return data;
176 };
177 assert.deepEqual(customizeForm(form({ intent: "request", listing: "plugin:pdf@anthropic-agent-skills", note: " for invoices " })), { intent: "request", listing: "plugin:pdf@anthropic-agent-skills", note: "for invoices" });
178 assert.deepEqual(customizeForm(form({ intent: "request", listing: "skill:a@m/b" })), { intent: "request", listing: "skill:a@m/b", note: null });
179 assert.equal(customizeForm(form({ intent: "request", listing: "agent:qa" })), null);
180 assert.deepEqual(customizeForm(form({ intent: "resolve", id: "ins_1", status: "declined" })), { intent: "resolve", id: "ins_1", status: "declined" });
181 assert.equal(customizeForm(form({ intent: "resolve", id: "ins_1", status: "open" })), null);
182 assert.equal(customizeForm(form({ intent: "install" })), null);
183});
184
185test("plugins are narrowed by what was typed, a category and a marketplace, and ordered by marketplace or name", () => {
186 const listings = [
187 plugin("xlsx", "anthropic-agent-skills", { category: "Documents" }),
188 plugin("pdf", "anthropic-agent-skills", { category: "Documents", keywords: ["invoices"] }),
189 plugin("linear", "claude-plugins-official", { category: "Productivity", author: "Linear" }),
190 plugin("support", "g1t", { availability: { kind: "soon" } }),
191 ];
192 assert.deepEqual(categoriesOf(listings), ["Documents", "Productivity"]);
193 assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams(""))).map((l) => l.ref), ["pdf@anthropic-agent-skills", "xlsx@anthropic-agent-skills", "linear@claude-plugins-official", "support@g1t"]);
194 assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("sort=name"))).map((l) => l.name), ["linear", "pdf", "support", "xlsx"]);
195 assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("category=Documents&q=invoice"))).map((l) => l.name), ["pdf"]);
196 assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("marketplace=g1t"))).map((l) => l.name), ["support"]);
197 assert.deepEqual(filterPlugins(listings, readPluginFilters(new URLSearchParams("q=Linear"))).map((l) => l.name), ["linear"], "the author counts");
198 assert.ok(matches(["Agents can read", "PostHog"], "post hog"));
199 assert.ok(!matches(["PostHog"], "sentry"));
200 assert.deepEqual(groupByMarketplace(filterPlugins(listings, readPluginFilters(new URLSearchParams("")))).map((g) => [g.marketplace, g.items.length]), [["anthropic-agent-skills", 2], ["claude-plugins-official", 1], ["g1t", 1]]);
201});
202
203test("skills in Discover are searched and filtered the same way", () => {
204 const listings = [skill("pdf", "document-skills", "anthropic-agent-skills", { category: "Documents" }), skill("brand", "design", "anthropic-agent-skills", { category: "Design" }), skill("abc", "zzz", "other")];
205 assert.deepEqual(filterSkills(listings, { q: "", category: null }).map((l) => l.name), ["abc", "brand", "pdf"]);
206 assert.deepEqual(filterSkills(listings, { q: "document", category: null }).map((l) => l.name), ["pdf"]);
207 assert.deepEqual(filterSkills(listings, { q: "", category: "Design" }).map((l) => l.name), ["brand"]);
208});
209
210test("a listing's availability and what a plugin brings read in a few words", () => {
211 assert.deepEqual(availabilityWord({ kind: "available" }), { label: "Available", tone: "neutral" });
212 assert.deepEqual(availabilityWord({ kind: "installed", enabled: true }), { label: "Installed", tone: "success" });
213 assert.deepEqual(availabilityWord({ kind: "installed", enabled: false }), { label: "Switched off", tone: "warn" });
214 assert.equal(availabilityWord({ kind: "unavailable", why: "npm" }).label, "Not available here");
215 assert.equal(availabilityWord({ kind: "soon" }).label, "Soon");
216 const components: PluginComponents = {
217 skills: [{ name: "pdf", description: null, path: "skills/pdf" }],
218 agents: [{ name: "reviewer", description: null, path: "agents/reviewer.md" }],
219 commands: [{ name: "release", path: "commands/release.md" }],
220 mcp_servers: [
221 { name: "linear", type: "http", url: "https://mcp.linear.app/mcp", remote: true },
222 { name: "fs", type: "stdio", url: null, remote: false },
223 ],
224 hooks: true,
225 lsp: false,
226 bin: true,
227 output_styles: false,
228 };
229 assert.deepEqual(componentChips(components), ["2 skills", "1 agent", "1 connector"]);
230 assert.deepEqual(claudeCodeOnly(components), ["hooks", "bin", "1 local MCP server"]);
231 assert.deepEqual(componentChips({ ...components, skills: [], commands: [], agents: [], mcp_servers: [] }), []);
232 assert.equal(fromWords({ kind: "plugin", ref: "linear@claude-plugins-official", server: "linear" }), "Brought by linear@claude-plugins-official");
233 assert.equal(fromWords({ kind: "agent", handle: "margo" }), "Moved from @margo");
234 assert.equal(fetchWords({ kind: "github", repo: "anthropics/skills", ref: null, sha: null, path: "skills/pdf" }), "github.com/anthropics/skills/skills/pdf");
235 assert.equal(fetchWords({ kind: "g1t", repo: "acme/plugins", ref: "main", path: "" }), "acme/plugins on this g1t at main");
236 assert.equal(fetchWords({ kind: "unsupported", why: "It is published on npm." }), "It is published on npm.");
237});
238
239test("the connectors directory puts MCP servers first, says which are connected, and keeps g1t's integrations apart", () => {
240 const views = connectorsFor("workspace");
241 const notion = connector("con_notion", { kind: "directory", id: "notion" });
242 const directory = connectorsDirectory(views, [notion, connector("con_x", { kind: "address" })], { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }, { github: "No GitHub App." }, "acme");
243 assert.ok(directory.mcp.every((entry) => entry.view.mcp && entry.view.status === "available"));
244 assert.equal(directory.mcp.at(-1)?.view.id, "notion", "the connected one comes last");
245 assert.equal(directory.mcp.find((entry) => entry.view.id === "notion")?.connector?.id, "con_notion");
246 assert.ok(directory.mcp.some((entry) => entry.view.mcp?.caveat), "a caveat the directory knows is kept");
247 assert.equal(directory.integrations[0]?.view.id, "sentry");
248 assert.equal(directory.integrations[0]?.href, "/acme/-/integrations/alerts");
249 const github = directory.integrations.find((entry) => entry.view.id === "github")!;
250 assert.equal(github.why, "No GitHub App.");
251 assert.equal(github.href, null);
252 assert.equal(directory.integrations.find((entry) => entry.view.id === "jira")?.href, "/acme/-/integrations/trackers?add=jira#add");
253 assert.ok(directory.soon.some((view) => view.id === "slack"));
254 assert.ok(!directory.integrations.some((entry) => entry.view.mcp));
255 const searched = connectorsDirectory(views, [], {}, {}, "acme", "notion");
256 assert.deepEqual(searched.mcp.map((entry) => entry.view.id), ["notion"]);
257 assert.equal(searched.soon.length, 0);
258 const personal = personalDirectory(connectorsFor("personal"));
259 assert.ok(personal.some((view) => view.id === "mcp"));
260 assert.ok(personal.every((view) => view.href));
261});
262
263test("where something reaches: the options, what is already given, and the line that says so", () => {
264 const reach = [
265 { id: "g1", scope: "team" as const, target: "qa", label: "QA" },
266 { id: "g2", scope: "agent" as const, target: "Margo", label: "@margo" },
267 ];
268 const options = reachOptions(reach, { can_manage: true, teams: [{ slug: "qa", name: "QA" }], agents: [{ handle: "margo", display_name: "Margo" }, { handle: "otto", display_name: "Otto" }] });
269 assert.deepEqual(options.map((o) => [o.value, o.checked]), [["workspace:", false], ["team:qa", true], ["agent:margo", true], ["agent:otto", false]]);
270 assert.equal(options[1]!.reach?.id, "g1");
271 const maintainer = reachOptions([], { can_manage: false, teams: [{ slug: "qa", name: "QA" }], agents: [{ handle: "margo", display_name: "Margo" }] });
272 assert.deepEqual(maintainer.map((o) => o.value), ["team:qa"]);
273 assert.deepEqual(splitTarget("workspace:"), { scope: "workspace", target: null });
274 assert.deepEqual(splitTarget("team:qa"), { scope: "team", target: "qa" });
275 assert.deepEqual(splitTarget("nonsense:x"), { scope: "workspace", target: "x" });
276 assert.equal(reachWords([]), "no agent yet");
277 assert.equal(reachWords([{ id: "g0", scope: "workspace", target: null, label: "Every agent" }, ...reach]), "Every agent");
278 assert.equal(reachWords(reach), "QA and @margo");
279 assert.equal(reachWords([...reach, { id: "g3", scope: "agent", target: "otto", label: "@otto" }]), "QA, @margo and 1 more");
280});
281
282test("a plugin's subagent starts the new-agent form", () => {
283 const draft = pluginAgentDraft({ ref: "pr-review-toolkit:code-reviewer", plugin: "pr-review-toolkit", marketplace: "claude-plugins-official", name: "code-reviewer", description: "Reviews pull requests for bugs.", prompt: "You review code.", model: "opus", skills: [], tools: [] });
284 assert.equal(draft.handle, "code-reviewer");
285 assert.equal(draft.display_name, "Code Reviewer");
286 assert.deepEqual(draft.responsibilities, ["Reviews pull requests for bugs."]);
287 assert.equal(draft.instructions, "You review code.");
288 assert.equal(draft.routing.floor, "frontier");
289 assert.equal(pluginAgentDraft({ ref: "a:b", plugin: "a", marketplace: "m", name: "b", description: "", prompt: "", model: "haiku", skills: [], tools: [] }).routing.ceiling, "small");
290 assert.equal(pluginAgentDraft({ ref: "a:b", plugin: "a", marketplace: "m", name: "b", description: "", prompt: "", model: null, skills: [], tools: [] }).routing.floor, null);
291 assert.equal(titleOf("my_fine-agent"), "My Fine Agent");
292});
293
294test("typing a search or picking a filter reloads nothing; another page, view or a posted form does", () => {
295 const at = (path: string) => new URL(`https://g1t.sh${path}`);
296 assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins?q=pdf"), undefined), false);
297 assert.equal(needsReload(at("/acme/-/customize/plugins?q=p"), at("/acme/-/customize/plugins?q=pd&category=Documents"), undefined), false);
298 assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins?view=discover"), undefined), true);
299 assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/skills"), undefined), true);
300 assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins"), "POST"), true);
301 assert.equal(needsReload(at("/acme/-/customize/plugins"), at("/acme/-/customize/plugins"), "GET"), false);
302});
303
304test("a page's flash comes from its address", () => {
305 assert.deepEqual(flashOf(new URLSearchParams("problem=The+server+refused")), { tone: "danger", text: "The server refused" });
306 assert.equal(flashOf(new URLSearchParams("signed_in=1"))?.tone, "success");
307 assert.equal(flashOf(new URLSearchParams("added=1"))?.tone, "success");
308 assert.equal(flashOf(new URLSearchParams("")), null);
309});

This file's history is long; its oldest lines are credited to the oldest commit read.