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 | /** |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 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 | /** | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 11 | * |
| 12 | * - **Skills**: what agents know how to do, in the open SKILL.md format | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 22 | * |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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. | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 27 | */ |
| 28 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 29 | import type { FetchPlan, MarketplaceSource, McpServerConfig, PluginLayout, SubagentFile } from "./claude-plugin"; |
| 30 | import type { ConnectorCategory } from "./connectors"; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 31 | import type { ListingKind } from "./marketplace"; |
| 32 | ||
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 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 | ||
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 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; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 55 | /** Where the site shows it, with `:workspace` to fill in. */ |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 56 | sitePath: string; |
| 57 | }; | |
| 58 | ||
| 59 | export const CUSTOMIZE: Record<CustomizeSection, CustomizeSectionInfo> = { | |
| 60 | skills: { | |
| 61 | id: "skills", | |
| 62 | title: "Skills", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 65 | }, |
| 66 | connectors: { | |
| 67 | id: "connectors", | |
| 68 | title: "Connectors", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 71 | }, |
| 72 | plugins: { | |
| 73 | id: "plugins", | |
| 74 | title: "Plugins", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 75 | description: "Skills, agents and connectors published together, from any marketplace in the open plugin format.", |
| 76 | sitePath: "/:workspace/-/customize/plugins", | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 77 | }, |
| 78 | }; | |
| 79 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 80 | /** 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) | 81 | export function sectionOfListing(kind: ListingKind): CustomizeSection { |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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}` : ""}`; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 91 | } |
| 92 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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 | }; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 346 | } |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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; |
This file's history is long; its oldest lines are credited to the oldest commit read.