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