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.
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 1 | /** |
| 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. | 2 | * Customize (docs.g1t.sh/guides/customize/): one place for what shapes |
| 3 | * how a workspace's agents work, in three parts, each with what the | |
| 4 | * workspace has (**Yours**) and what it can add (**Discover**). It is the | |
| 5 | * Marketplace's successor on the site and the phone. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 6 | * |
| 7 | * - **Skills**: what agents know how to do, in the open SKILL.md format | |
| 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. | 8 | * (./skill-library.ts, ./skill-format.ts): g1t's foundational skills, |
| 9 | * the workspace's library, and every skill published in a marketplace. | |
| 10 | * - **Connectors**: what agents can reach: MCP servers (./claude-plugin.ts | |
| 11 | * `.mcp.json`, the directory in ./connectors.ts, and any address an | |
| 12 | * owner adds), and g1t's own integrations (GitHub, trackers, alerts, | |
| 13 | * model providers). | |
| 14 | * - **Plugins**: folders in the open plugin format (./claude-plugin.ts), | |
| 15 | * found in marketplaces, each bringing skills, agents and connectors to | |
| 16 | * g1t's agents and everything else to Claude Code on a runner. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 17 | * |
| 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. | 18 | * Everything installable keeps the names the open ecosystem uses: |
| 19 | * `name@marketplace` for a plugin, `plugin:skill` for a skill inside one. | |
| 20 | * Wire shapes are snake_case. No imports beyond types, so it runs | |
| 21 | * anywhere: services, the site, the phone. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 22 | */ |
| 23 | ||
| 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. | 24 | import type { FetchPlan, MarketplaceSource, McpServerConfig, PluginLayout, SubagentFile } from "./claude-plugin"; |
| 25 | import type { McpTool } from "./abilities"; | |
| 26 | import type { ConnectorCategory } from "./connectors"; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 27 | import type { ListingKind } from "./marketplace"; |
| 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. | 28 | import type { SkillScope } from "./skill-library"; |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 29 | |
| 30 | export type CustomizeSection = "skills" | "connectors" | "plugins"; | |
| 31 | ||
| 32 | export const CUSTOMIZE_SECTIONS: readonly CustomizeSection[] = ["skills", "connectors", "plugins"]; | |
| 33 | ||
| 34 | export type CustomizeSectionInfo = { | |
| 35 | id: CustomizeSection; | |
| 36 | title: string; | |
| 37 | /** One line, for the section's row. */ | |
| 38 | description: string; | |
| 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. | 39 | /** Where the site shows it, with `:workspace` to fill in. */ |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 40 | sitePath: string; |
| 41 | }; | |
| 42 | ||
| 43 | export const CUSTOMIZE: Record<CustomizeSection, CustomizeSectionInfo> = { | |
| 44 | skills: { | |
| 45 | id: "skills", | |
| 46 | title: "Skills", | |
| 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. | 47 | description: "What agents know how to do: the skills every agent has, your workspace's library, and public skills to add.", |
| 48 | sitePath: "/:workspace/-/customize/skills", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 49 | }, |
| 50 | connectors: { | |
| 51 | id: "connectors", | |
| 52 | title: "Connectors", | |
| 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. | 53 | description: "What agents can reach: MCP servers and the tools your team already uses, connected once and given to agents.", |
| 54 | sitePath: "/:workspace/-/customize/connectors", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 55 | }, |
| 56 | plugins: { | |
| 57 | id: "plugins", | |
| 58 | title: "Plugins", | |
| 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. | 59 | description: "Skills, agents and connectors published together, from any marketplace in the open plugin format.", |
| 60 | sitePath: "/:workspace/-/customize/plugins", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 61 | }, |
| 62 | }; | |
| 63 | ||
| 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. | 64 | /** The part of Customize that shows a listing of this kind. */ |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 65 | export function sectionOfListing(kind: ListingKind): CustomizeSection { |
| 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. | 66 | if (kind === "integration" || kind === "connector") return "connectors"; |
| 67 | if (kind === "skill") return "skills"; | |
| 68 | return "plugins"; | |
| 69 | } | |
| 70 | ||
| 71 | /** Customize's pages, under `/<workspace>/-/customize`. */ | |
| 72 | export function customizePath(section: CustomizeSection | "" | "requests" | "marketplaces", workspace: string, rest = ""): string { | |
| 73 | const base = `/${encodeURIComponent(workspace)}/-/customize`; | |
| 74 | return `${base}${section ? `/${section}` : ""}${rest ? `/${rest}` : ""}`; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 75 | } |
| 76 | ||
| 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. | 77 | // ── Marketplaces ───────────────────────────────────────────────────────── |
| 78 | ||
| 79 | /** A marketplace a workspace reads plugins from, as its page lists it. */ | |
| 80 | export type Marketplace = { | |
| 81 | /** Its name, as its manifest says and as `name@marketplace` uses. */ | |
| 82 | name: string; | |
| 83 | source: MarketplaceSource; | |
| 84 | /** Built in: every workspace has it, and it can't be removed. */ | |
| 85 | builtin: boolean; | |
| 86 | about: string | null; | |
| 87 | owner: string | null; | |
| 88 | /** How many plugins it lists, and how many of those carry skills. */ | |
| 89 | plugins: number; | |
| 90 | /** RFC 3339: when its manifest was last read, and why it couldn't be, if so. */ | |
| 91 | fetched_at: string | null; | |
| 92 | problem: string | null; | |
| 93 | added_by: string | null; | |
| 94 | added_at: string | null; | |
| 95 | can_remove: boolean; | |
| 96 | }; | |
| 97 | ||
| 98 | /** Whether a plugin from Discover can be installed here, now. */ | |
| 99 | export type PluginAvailability = | |
| 100 | /** Installable now. */ | |
| 101 | | { kind: "available" } | |
| 102 | /** The workspace has it (switched off, if `enabled` is false). */ | |
| 103 | | { kind: "installed"; enabled: boolean } | |
| 104 | /** Listed but not something g1t can fetch (npm, a command, a git host g1t doesn't read), with why. It still works in Claude Code. */ | |
| 105 | | { kind: "unavailable"; why: string } | |
| 106 | /** g1t's own, announced but not released. */ | |
| 107 | | { kind: "soon" }; | |
| 108 | ||
| 109 | /** What a plugin would bring, worked out from its layout, before or after installing. */ | |
| 110 | export type PluginComponents = { | |
| 111 | /** Skills by name (`plugin:name` to agents). */ | |
| 112 | skills: { name: string; description: string | null; path: string }[]; | |
| 113 | /** Subagents by name, as hireable starting points. */ | |
| 114 | agents: { name: string; description: string | null; path: string }[]; | |
| 115 | /** Commands: the older one-file form of a skill. */ | |
| 116 | commands: { name: string; path: string }[]; | |
| 117 | /** MCP servers it names: remote ones become connectors; stdio ones are for Claude Code only. */ | |
| 118 | mcp_servers: { name: string; type: McpServerConfig["type"]; url: string | null; remote: boolean }[]; | |
| 119 | /** What only Claude Code on a runner uses. */ | |
| 120 | hooks: boolean; | |
| 121 | lsp: boolean; | |
| 122 | bin: boolean; | |
| 123 | output_styles: boolean; | |
| 124 | }; | |
| 125 | ||
| 126 | /** One plugin as Discover lists it, from a marketplace. */ | |
| 127 | export type PluginListing = { | |
| 128 | /** `name@marketplace`. */ | |
| 129 | ref: string; | |
| 130 | name: string; | |
| 131 | marketplace: string; | |
| 132 | display_name: string | null; | |
| 133 | description: string; | |
| 134 | version: string | null; | |
| 135 | author: string | null; | |
| 136 | homepage: string | null; | |
| 137 | category: string | null; | |
| 138 | tags: string[]; | |
| 139 | keywords: string[]; | |
| 140 | /** Where g1t would read it from, when it can. */ | |
| 141 | fetch: FetchPlan; | |
| 142 | availability: PluginAvailability; | |
| 143 | /** Skills the entry names outright (a skills-only plugin), so Discover can list them without fetching. */ | |
| 144 | skills: string[]; | |
| 145 | }; | |
| 146 | ||
| 147 | /** A plugin the workspace installed. */ | |
| 148 | export type PluginInstall = { | |
| 149 | id: string; | |
| 150 | /** `name@marketplace`. */ | |
| 151 | ref: string; | |
| 152 | name: string; | |
| 153 | marketplace: string; | |
| 154 | display_name: string | null; | |
| 155 | description: string; | |
| 156 | version: string | null; | |
| 157 | /** The commit it was read at, when the source has one; the ref otherwise. */ | |
| 158 | sha: string | null; | |
| 159 | fetch: FetchPlan; | |
| 160 | components: PluginComponents; | |
| 161 | /** What couldn't be read when it was installed. */ | |
| 162 | problems: string[]; | |
| 163 | enabled: boolean; | |
| 164 | installed_by: string; | |
| 165 | installed_at: string; | |
| 166 | /** A newer version in the marketplace, when the listing moved on. */ | |
| 167 | update: { version: string | null; sha: string | null } | null; | |
| 168 | }; | |
| 169 | ||
| 170 | /** A plugin's page: the listing, the install if any, and what it holds, read from its source. */ | |
| 171 | export type PluginDetail = { | |
| 172 | listing: PluginListing; | |
| 173 | install: PluginInstall | null; | |
| 174 | components: PluginComponents | null; | |
| 175 | readme: string | null; | |
| 176 | /** The plugin's manifest, when it has one. */ | |
| 177 | manifest: { name: string; version: string | null; license: string | null; repository: string | null } | null; | |
| 178 | /** The subagents in full, for the hire flow. */ | |
| 179 | agents: SubagentFile[]; | |
| 180 | problems: string[]; | |
| 181 | /** The marketplace it is listed in, for the header. */ | |
| 182 | source: { marketplace: string; owner: string | null }; | |
| 183 | }; | |
| 184 | ||
| 185 | // ── Skills in Discover ─────────────────────────────────────────────────── | |
| 186 | ||
| 187 | /** One skill as Discover lists it: a skill inside a marketplace's plugin. */ | |
| 188 | export type SkillListing = { | |
| 189 | /** `plugin:skill`. */ | |
| 190 | ref: string; | |
| 191 | name: string; | |
| 192 | plugin: string; | |
| 193 | marketplace: string; | |
| 194 | /** The plugin's description until the skill's own is read. */ | |
| 195 | description: string; | |
| 196 | category: string | null; | |
| 197 | author: string | null; | |
| 198 | /** Whether the workspace's library already has it from this plugin. */ | |
| 199 | added: boolean; | |
| 200 | /** Whether the plugin it is in can be fetched here. */ | |
| 201 | availability: PluginAvailability; | |
| 202 | }; | |
| 203 | ||
| 204 | // ── Connectors ─────────────────────────────────────────────────────────── | |
| 205 | ||
| 206 | /** How a connector signs in to its server. */ | |
| 207 | export type ConnectorAuth = "none" | "header" | "oauth"; | |
| 208 | ||
| 209 | /** Where a connector came from. */ | |
| 210 | export type ConnectorFrom = | |
| 211 | | { kind: "directory"; id: string } | |
| 212 | | { kind: "plugin"; ref: string; server: string } | |
| 213 | | { kind: "address" } | |
| 214 | /** An MCP server kept on one agent before connectors existed. */ | |
| 215 | | { kind: "agent"; handle: string }; | |
| 216 | ||
| 217 | /** Whether a connector can be used now. */ | |
| 218 | export type ConnectorState = "connected" | "needs_sign_in" | "needs_key" | "problem"; | |
| 219 | ||
| 220 | /** A workspace's connector: an MCP server connected once, then given to agents. */ | |
| 221 | export type WorkspaceConnector = { | |
| 222 | /** `con_…`. */ | |
| 223 | id: string; | |
| 224 | /** 2 to 32 lowercase letters, digits and hyphens; tools are offered as `<name>__<tool>`. */ | |
| 225 | name: string; | |
| 226 | /** For the page: the directory's name, or the name again. */ | |
| 227 | title: string; | |
| 228 | url: string; | |
| 229 | category: ConnectorCategory | null; | |
| 230 | about: string | null; | |
| 231 | auth: ConnectorAuth; | |
| 232 | state: ConnectorState; | |
| 233 | /** Why it is a problem, when it is. */ | |
| 234 | problem: string | null; | |
| 235 | from: ConnectorFrom; | |
| 236 | tools: McpTool[]; | |
| 237 | /** RFC 3339: when its tools were last listed. */ | |
| 238 | checked_at: string | null; | |
| 239 | /** The header's name when `auth` is header (its value is never shown). */ | |
| 240 | header_name: string | null; | |
| 241 | /** For OAuth: who it is signed in as, if the server said, and when the sign-in was done. */ | |
| 242 | signed_in_as: string | null; | |
| 243 | signed_in_at: string | null; | |
| 244 | grants: ConnectorGrant[]; | |
| 245 | added_by: string; | |
| 246 | added_at: string; | |
| 247 | }; | |
| 248 | ||
| 249 | /** Where a connector reaches: every agent, a team's, or one. */ | |
| 250 | export type ConnectorGrant = { | |
| 251 | id: string; | |
| 252 | scope: SkillScope; | |
| 253 | /** The agent's handle, the team's slug, or null for the workspace. */ | |
| 254 | target: string | null; | |
| 255 | label: string; | |
| 256 | granted_by: string; | |
| 257 | granted_at: string; | |
| 258 | }; | |
| 259 | ||
| 260 | /** What an owner sends to add a connector. */ | |
| 261 | export type ConnectorInput = { | |
| 262 | /** From the directory (its id), a plugin's server (`ref` and `server`), or by address. */ | |
| 263 | from: { kind: "directory"; id: string } | { kind: "plugin"; ref: string; server: string } | { kind: "address"; name: string; url: string }; | |
| 264 | auth: ConnectorAuth; | |
| 265 | /** For `header`: the header's name and its value, kept sealed. */ | |
| 266 | header?: { name: string; value: string } | null; | |
| 267 | }; | |
| 268 | ||
| 269 | /** Starting an OAuth sign-in for a connector: where the browser goes. */ | |
| 270 | export type ConnectorSignIn = { url: string }; | |
| 271 | ||
| 272 | // ── Agents and what reaches them ───────────────────────────────────────── | |
| 273 | ||
| 274 | /** A connector as one agent's Abilities tab lists it. */ | |
| 275 | export type AgentConnector = { | |
| 276 | id: string; | |
| 277 | name: string; | |
| 278 | title: string; | |
| 279 | state: ConnectorState; | |
| 280 | /** How the agent has it. */ | |
| 281 | via: SkillScope | "agent-only"; | |
| 282 | via_label: string; | |
| 283 | tools: number; | |
| 284 | }; | |
| 285 | ||
| 286 | // ── The bundle a runner gets ───────────────────────────────────────────── | |
| 287 | ||
| 288 | /** | |
| 289 | * What a job's Claude Code receives from the workspace's Customize | |
| 290 | * (docs.g1t.sh/guides/self-hosted-runners/, "What a run gets from | |
| 291 | * Customize"): plugins to fetch at their pinned commit, the library's skills | |
| 292 | * as files, and connectors with the headers that sign them in. Read with | |
| 293 | * the job's agent token, once per job. | |
| 294 | */ | |
| 295 | export type CustomizeBundle = { | |
| 296 | plugins: { ref: string; name: string; fetch: FetchPlan; version: string | null }[]; | |
| 297 | /** Library skills, each a folder: SKILL.md and its files, as `path` and content. */ | |
| 298 | skills: { name: string; plugin: string | null; files: { path: string; content: string; encoding: "utf8" | "base64" }[] }[]; | |
| 299 | /** Connectors the job's agent has, with what to send; secrets included, so this never leaves the runner. */ | |
| 300 | connectors: { name: string; url: string; headers: Record<string, string> }[]; | |
| 301 | }; | |
| 302 | ||
| 303 | /** Something to hire from a plugin's `agents/` folder, as a template. */ | |
| 304 | export type PluginAgentTemplate = { | |
| 305 | /** `plugin:agent`. */ | |
| 306 | ref: string; | |
| 307 | plugin: string; | |
| 308 | marketplace: string; | |
| 309 | name: string; | |
| 310 | description: string; | |
| 311 | /** The system prompt, as the agent's instructions. */ | |
| 312 | prompt: string; | |
| 313 | model: string | null; | |
| 314 | skills: string[]; | |
| 315 | tools: string[]; | |
| 316 | }; | |
| 317 | ||
| 318 | /** The layout of a plugin turned into what it brings. */ | |
| 319 | export function componentsOf(layout: PluginLayout, servers: McpServerConfig[], skillDescriptions: Record<string, string | null> = {}, agentDescriptions: Record<string, string | null> = {}): PluginComponents { | |
| 320 | return { | |
| 321 | skills: layout.skills.map((s) => ({ name: s.name, description: skillDescriptions[s.path] ?? null, path: s.path })), | |
| 322 | agents: layout.agents.map((a) => ({ name: a.name, description: agentDescriptions[a.path] ?? null, path: a.path })), | |
| 323 | commands: layout.commands.map((c) => ({ name: c.name, path: c.path })), | |
| 324 | mcp_servers: servers.map((s) => ({ name: s.name, type: s.type, url: s.url, remote: s.type === "http" || s.type === "sse" })), | |
| 325 | hooks: layout.hooks.length > 0, | |
| 326 | lsp: layout.lsp, | |
| 327 | bin: layout.bin, | |
| 328 | output_styles: layout.outputStyles, | |
| 329 | }; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 330 | } |
| 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. | 331 | |
| 332 | /** The most marketplaces a workspace adds, and the most plugins it installs. */ | |
| 333 | export const MAX_MARKETPLACES = 20; | |
| 334 | export const MAX_PLUGIN_INSTALLS = 100; | |
| 335 | /** The most connectors a workspace keeps. */ | |
| 336 | export const MAX_CONNECTORS = 50; |
This file's history is long; its oldest lines are credited to the oldest commit read.