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.
| Integrations are a directory of connectors, for a workspace and for each person | 1 | import { |
| 2 | AlertTriangle, | |
| 3 | BarChart3, | |
| 4 | BookOpen, | |
| 5 | Bot, | |
| 6 | CalendarDays, | |
| 7 | ChevronRight, | |
| 8 | Cloud, | |
| 9 | Code2, | |
| 10 | FolderOpen, | |
| 11 | Headset, | |
| 12 | LayoutGrid, | |
| 13 | ListChecks, | |
| 14 | MessagesSquare, | |
| 15 | Palette, | |
| 16 | Search, | |
| 17 | ShieldCheck, | |
| 18 | Siren, | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 19 | Webhook, |
| Integrations are a directory of connectors, for a workspace and for each person | 20 | } from "lucide-react"; |
| 21 | import { type ReactNode, useState } from "react"; | |
| 22 | import { Link, useSearchParams } from "react-router"; | |
| 23 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 24 | import { type Provider, PROVIDERS } from "@g1t/contracts"; |
| Integrations are a directory of connectors, for a workspace and for each person | 25 | import { type ConnectorScope, type ConnectorView, connectorPath } from "@g1t/contracts/connectors"; |
| 26 | ||
| 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. | 27 | import { AI_PROVIDERS } from "../lib/ai-providers"; |
| Integrations are a directory of connectors, for a workspace and for each person | 28 | import { CONTACT } from "../lib/legal"; |
| 29 | import { | |
| 30 | type CategoryFilter, | |
| 31 | type ConnectedState, | |
| 32 | arrange, | |
| 33 | askHref, | |
| 34 | categoriesWith, | |
| 35 | categoryCounts, | |
| 36 | categoryFilter, | |
| 37 | categoryTitle, | |
| 38 | groupByCategory, | |
| 39 | monogram, | |
| 40 | tileColor, | |
| 41 | } from "../lib/connectors"; | |
| 42 | import { cn } from "../lib/cn"; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 43 | import { integrationsSection } from "../lib/integration-sections"; |
| Integrations are a directory of connectors, for a workspace and for each person | 44 | import { Badge } from "./ui/badge"; |
| 45 | import { Hint } from "./ui/hint"; | |
| 46 | import { Input } from "./ui"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 47 | import { Card } from "./ui/card"; |
| Integrations are a directory of connectors, for a workspace and for each person | 48 | |
| 49 | const CATEGORY_ICONS: Record<CategoryFilter, ReactNode> = { | |
| 50 | all: <LayoutGrid size={15} />, | |
| 51 | code: <Code2 size={15} />, | |
| 52 | issues: <ListChecks size={15} />, | |
| 53 | chat: <MessagesSquare size={15} />, | |
| 54 | calendar: <CalendarDays size={15} />, | |
| 55 | docs: <BookOpen size={15} />, | |
| 56 | monitoring: <Siren size={15} />, | |
| 57 | cloud: <Cloud size={15} />, | |
| 58 | ai: <Bot size={15} />, | |
| 59 | data: <BarChart3 size={15} />, | |
| 60 | crm: <Headset size={15} />, | |
| 61 | design: <Palette size={15} />, | |
| 62 | security: <ShieldCheck size={15} />, | |
| 63 | files: <FolderOpen size={15} />, | |
| 64 | }; | |
| 65 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 66 | const PROVIDER_COLORS: Partial<Record<Provider, string>> = { |
| 67 | sentry: "#a78bfa", | |
| 68 | datadog: "#a26ce6", | |
| 69 | webhook: "#5eead4", | |
| 70 | jira: "#5b8def", | |
| 71 | linear: "#8b8ff0", | |
| 72 | }; | |
| 73 | ||
| 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. | 74 | /** A provider's mark: its initials in its colour, or an icon for the generic ones. */ |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 75 | export function ProviderMark({ provider, size = 32 }: { provider: Provider; size?: number }) { |
| 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. | 76 | const entry = AI_PROVIDERS[provider]; |
| 77 | const color = entry?.color ?? PROVIDER_COLORS[provider] ?? "#a1a1aa"; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 78 | const content = |
| 79 | provider === "webhook" ? ( | |
| 80 | <Webhook size={size * 0.5} /> | |
| 81 | ) : provider.endsWith("_endpoint") ? ( | |
| 82 | <Bot size={size * 0.5} /> | |
| 83 | ) : ( | |
| 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. | 84 | entry?.mark || PROVIDERS[provider].label[0] |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 85 | ); |
| 86 | return ( | |
| 87 | <span | |
| 88 | aria-hidden="true" | |
| 89 | className="inline-flex shrink-0 items-center justify-center font-semibold tracking-tight" | |
| 90 | style={{ | |
| 91 | width: size, | |
| 92 | height: size, | |
| 93 | borderRadius: size * 0.28, | |
| 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. | 94 | fontSize: size * (String(content).length > 1 ? 0.34 : 0.42), |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 95 | background: `color-mix(in srgb, ${color} 16%, transparent)`, |
| 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. | 96 | // Toward the text colour, so the letters read on the tile in both themes. |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 97 | color: `color-mix(in srgb, ${color} 60%, var(--color-fg))`, |
| 98 | boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${color} 30%, transparent)`, | |
| 99 | }} | |
| 100 | > | |
| 101 | {content} | |
| 102 | </span> | |
| 103 | ); | |
| 104 | } | |
| 105 | ||
| 106 | /** Providers to choose from, as compact tiles that open their form. */ | |
| 107 | export function ProviderTiles({ | |
| 108 | providers, | |
| 109 | slug, | |
| 110 | adding, | |
| 111 | blurb, | |
| 112 | }: { | |
| 113 | providers: Provider[]; | |
| 114 | slug: string; | |
| 115 | adding: Provider | null; | |
| 116 | blurb: (provider: Provider) => string; | |
| 117 | }) { | |
| 118 | return ( | |
| 119 | <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3"> | |
| 120 | {providers.map((provider) => ( | |
| 121 | <Link | |
| 122 | key={provider} | |
| 123 | to={`/${slug}/-/integrations/${integrationsSection(PROVIDERS[provider].kind)}?add=${provider}#add`} | |
| 124 | preventScrollReset | |
| 125 | className={`group flex items-center gap-3 rounded-xl border px-3 py-2.5 transition-colors ${ | |
| 126 | adding === provider ? "border-accent-dim bg-surface" : "border-line hover:border-line-strong hover:bg-surface" | |
| 127 | }`} | |
| 128 | > | |
| 129 | <ProviderMark provider={provider} size={28} /> | |
| 130 | <span className="min-w-0 grow"> | |
| 131 | <span className="block truncate text-sm font-medium">{PROVIDERS[provider].label}</span> | |
| 132 | <Hint label={blurb(provider)}> | |
| 133 | <span className="block truncate text-xs text-muted">{blurb(provider)}</span> | |
| 134 | </Hint> | |
| 135 | </span> | |
| 136 | <ChevronRight size={14} className="shrink-0 text-faint transition-transform group-hover:translate-x-0.5" /> | |
| 137 | </Link> | |
| 138 | ))} | |
| 139 | </div> | |
| 140 | ); | |
| 141 | } | |
| 142 | ||
| Integrations are a directory of connectors, for a workspace and for each person | 143 | /** A connector's tile: its provider's mark, or its letters on a steady colour. No logos. */ |
| 144 | export function ConnectorMark({ view, size = 36 }: { view: Pick<ConnectorView, "id" | "name" | "provider">; size?: number }) { | |
| 145 | if (view.provider) return <ProviderMark provider={view.provider} size={size} />; | |
| 146 | const color = tileColor(view.id); | |
| 147 | const letters = monogram(view.name); | |
| 148 | return ( | |
| 149 | <span | |
| 150 | aria-hidden="true" | |
| 151 | className="inline-flex shrink-0 items-center justify-center font-semibold tracking-tight" | |
| 152 | style={{ | |
| 153 | width: size, | |
| 154 | height: size, | |
| 155 | borderRadius: size * 0.28, | |
| 156 | fontSize: size * (letters.length > 1 ? 0.34 : 0.42), | |
| 157 | background: `color-mix(in srgb, ${color} 16%, transparent)`, | |
| The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how. | 158 | // Toward the text colour, so the letters read in both themes. |
| 159 | color: `color-mix(in srgb, ${color} 60%, var(--color-fg))`, | |
| Integrations are a directory of connectors, for a workspace and for each person | 160 | boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${color} 30%, transparent)`, |
| 161 | }} | |
| 162 | > | |
| 163 | {letters} | |
| 164 | </span> | |
| 165 | ); | |
| 166 | } | |
| 167 | ||
| 168 | export type ConnectorDirectoryProps = { | |
| 169 | scope: ConnectorScope; | |
| 170 | views: ConnectorView[]; | |
| 171 | /** What is connected here, by connector id. */ | |
| 172 | connected: Record<string, ConnectedState>; | |
| 173 | /** The workspace's slug on its page; null on your own. */ | |
| 174 | workspace: string | null; | |
| 175 | /** Whether the viewer can connect things here (an owner, or anyone for themselves). */ | |
| 176 | canManage: boolean; | |
| 177 | /** Category navigation as a sidebar beside the cards (wide pages), or as chips above them. */ | |
| 178 | nav?: "sidebar" | "chips"; | |
| 179 | }; | |
| 180 | ||
| 181 | /** | |
| 182 | * The integrations directory: categories to browse, a search, and every | |
| 183 | * connector as a card, sorted into Connected, Available and Soon. | |
| 184 | */ | |
| 185 | export function ConnectorDirectory({ scope, views, connected, workspace, canManage, nav = "sidebar" }: ConnectorDirectoryProps) { | |
| 186 | const [params, setParams] = useSearchParams(); | |
| 187 | const category = categoryFilter(params.get("category")); | |
| 188 | const [query, setQuery] = useState(""); | |
| 189 | const counts = categoryCounts(views); | |
| 190 | const categories = categoriesWith(views); | |
| 191 | const found = arrange(views, { category, query, connected: new Set(Object.keys(connected)) }); | |
| 192 | const searching = query.trim().length > 0; | |
| 193 | const grouped = category === "all" && !searching; | |
| 194 | const empty = found.connected.length + found.available.length + found.soon.length === 0; | |
| 195 | ||
| 196 | const choose = (next: CategoryFilter) => { | |
| 197 | setParams( | |
| 198 | (current) => { | |
| 199 | const updated = new URLSearchParams(current); | |
| 200 | if (next === "all") updated.delete("category"); | |
| 201 | else updated.set("category", next); | |
| 202 | return updated; | |
| 203 | }, | |
| 204 | { replace: true, preventScrollReset: true }, | |
| 205 | ); | |
| 206 | setQuery(""); | |
| 207 | }; | |
| 208 | ||
| 209 | const options: { id: CategoryFilter; title: string }[] = [{ id: "all", title: "All" }, ...categories]; | |
| 210 | const card = (view: ConnectorView) => ( | |
| 211 | <ConnectorCard | |
| 212 | key={view.id} | |
| 213 | view={view} | |
| 214 | scope={scope} | |
| 215 | state={connected[view.id] ?? null} | |
| 216 | workspace={workspace} | |
| 217 | canManage={canManage} | |
| 218 | showCategory={category === "all" || searching} | |
| 219 | /> | |
| 220 | ); | |
| 221 | ||
| 222 | return ( | |
| 223 | <div className={cn(nav === "sidebar" && "md:grid md:grid-cols-[12rem_minmax(0,1fr)] md:gap-8")}> | |
| 224 | {/* Categories: a list beside the cards on a wide page, chips that scroll sideways otherwise. */} | |
| 225 | <nav aria-label="Categories" className={cn(nav === "sidebar" && "md:sticky md:top-20 md:self-start")}> | |
| 226 | <ul | |
| 227 | className={cn( | |
| 228 | "-mx-4 flex gap-1.5 overflow-x-auto px-4 pb-1 [scrollbar-width:none] sm:mx-0 sm:px-0", | |
| 229 | nav === "sidebar" && "md:mx-0 md:flex-col md:gap-px md:overflow-visible md:px-0", | |
| 230 | )} | |
| 231 | > | |
| 232 | {options.map((option) => { | |
| 233 | const current = option.id === category; | |
| 234 | return ( | |
| 235 | <li key={option.id} className="shrink-0"> | |
| 236 | <button | |
| 237 | type="button" | |
| 238 | onClick={() => choose(option.id)} | |
| 239 | aria-current={current ? "page" : undefined} | |
| 240 | className={cn( | |
| 241 | "flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm whitespace-nowrap transition-colors", | |
| 242 | nav === "sidebar" && "md:w-full md:rounded-md md:border-transparent md:px-2.5", | |
| 243 | current | |
| 244 | ? "border-accent/40 bg-accent/10 text-fg md:bg-raised" | |
| 245 | : "border-line text-muted hover:border-line-strong hover:text-fg md:hover:bg-surface", | |
| 246 | )} | |
| 247 | > | |
| 248 | <span className={cn("hidden text-faint", nav === "sidebar" && "md:inline-flex", current && "text-accent")}> | |
| 249 | {CATEGORY_ICONS[option.id]} | |
| 250 | </span> | |
| 251 | <span className="grow text-left">{option.title}</span> | |
| 252 | <span className="text-xs text-faint tabular-nums">{counts[option.id]}</span> | |
| 253 | </button> | |
| 254 | </li> | |
| 255 | ); | |
| 256 | })} | |
| 257 | </ul> | |
| 258 | </nav> | |
| 259 | ||
| 260 | <div className="mt-5 min-w-0 md:mt-0"> | |
| 261 | <div className="relative"> | |
| 262 | <Search size={15} className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-faint" /> | |
| 263 | <Input | |
| 264 | type="search" | |
| 265 | value={query} | |
| 266 | onChange={(event) => setQuery(event.target.value)} | |
| 267 | placeholder={category === "all" ? "Search integrations" : `Search all integrations, not just ${categoryTitle(category)}`} | |
| 268 | aria-label="Search integrations" | |
| 269 | className="pl-9" | |
| 270 | /> | |
| 271 | </div> | |
| 272 | ||
| 273 | {empty && ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 274 | <Card asChild tone="plain" className="mt-8 border-dashed px-4 py-8 text-center text-sm text-muted"> |
| 275 | <p> | |
| 276 | Nothing matches “{query.trim()}”.{" "} | |
| 277 | <a | |
| 278 | href={askHref({ connector: { id: "other", name: query.trim() || "something else" }, scope, workspace, address: CONTACT.support })} | |
| 279 | className="text-accent hover:underline" | |
| 280 | > | |
| 281 | Ask for it | |
| 282 | </a> | |
| 283 | , and say what you would use it for. | |
| 284 | </p> | |
| 285 | </Card> | |
| Integrations are a directory of connectors, for a workspace and for each person | 286 | )} |
| 287 | ||
| 288 | {found.connected.length > 0 && ( | |
| 289 | <Group title="Connected" count={found.connected.length}> | |
| 290 | <Cards>{found.connected.map(card)}</Cards> | |
| 291 | </Group> | |
| 292 | )} | |
| 293 | {found.available.length > 0 && ( | |
| 294 | <Group | |
| 295 | title="Available" | |
| 296 | count={found.available.length} | |
| 297 | about={ | |
| 298 | scope === "workspace" | |
| 299 | ? canManage | |
| 300 | ? "Ready to connect now." | |
| 301 | : `Ready now. An owner of ${workspace} connects them.` | |
| 302 | : "Ready to connect now." | |
| 303 | } | |
| 304 | > | |
| 305 | <Cards>{found.available.map(card)}</Cards> | |
| 306 | </Group> | |
| 307 | )} | |
| 308 | {found.soon.length > 0 && ( | |
| 309 | <Group title="Soon" count={found.soon.length} about="Being built. Ask for the ones you need, and they come sooner."> | |
| 310 | {grouped ? ( | |
| 311 | <div className="space-y-6"> | |
| 312 | {groupByCategory(found.soon).map((group) => ( | |
| 313 | <div key={group.id}> | |
| 314 | <p className="mb-2 flex items-center gap-2 text-xs font-medium tracking-wide text-faint uppercase"> | |
| 315 | {CATEGORY_ICONS[group.id]} | |
| 316 | {group.title} | |
| 317 | </p> | |
| 318 | <Cards>{group.views.map((view) => <ConnectorCard key={view.id} view={view} scope={scope} state={null} workspace={workspace} canManage={canManage} showCategory={false} />)}</Cards> | |
| 319 | </div> | |
| 320 | ))} | |
| 321 | </div> | |
| 322 | ) : ( | |
| 323 | <Cards>{found.soon.map(card)}</Cards> | |
| 324 | )} | |
| 325 | </Group> | |
| 326 | )} | |
| 327 | </div> | |
| 328 | </div> | |
| 329 | ); | |
| 330 | } | |
| 331 | ||
| 332 | function Group({ title, count, about, children }: { title: string; count: number; about?: string; children: ReactNode }) { | |
| 333 | return ( | |
| 334 | <section className="mt-8"> | |
| 335 | <h2 className="flex items-baseline gap-2 text-sm font-medium"> | |
| 336 | {title} | |
| 337 | <span className="text-xs font-normal text-faint tabular-nums">{count}</span> | |
| 338 | </h2> | |
| 339 | {about && <p className="mt-0.5 text-xs text-muted">{about}</p>} | |
| 340 | <div className="mt-3">{children}</div> | |
| 341 | </section> | |
| 342 | ); | |
| 343 | } | |
| 344 | ||
| 345 | function Cards({ children }: { children: ReactNode }) { | |
| 346 | return <ul className="grid gap-3 sm:grid-cols-2">{children}</ul>; | |
| 347 | } | |
| 348 | ||
| 349 | function ConnectorCard({ | |
| 350 | view, | |
| 351 | scope, | |
| 352 | state, | |
| 353 | workspace, | |
| 354 | canManage, | |
| 355 | showCategory, | |
| 356 | }: { | |
| 357 | view: ConnectorView; | |
| 358 | scope: ConnectorScope; | |
| 359 | state: ConnectedState | null; | |
| 360 | workspace: string | null; | |
| 361 | canManage: boolean; | |
| 362 | showCategory: boolean; | |
| 363 | }) { | |
| 364 | const soon = view.status === "soon"; | |
| 365 | const setup = view.href ? connectorPath(view.href, workspace ?? "") : null; | |
| 366 | const to = state?.manage ?? setup; | |
| 367 | const also = | |
| 368 | view.alsoIn === "personal" | |
| 369 | ? "Each person can also connect their own, under their settings." | |
| 370 | : view.alsoIn === "workspace" | |
| 371 | ? "A workspace owner can also connect it for everyone." | |
| 372 | : null; | |
| 373 | return ( | |
| 374 | <li | |
| 375 | className={cn( | |
| 376 | "relative flex flex-col rounded-xl border p-4 transition-colors", | |
| 377 | soon ? "border-line/70 bg-surface/40" : "border-line bg-surface", | |
| 378 | !soon && to && "hover:border-line-strong", | |
| 379 | )} | |
| 380 | > | |
| 381 | <div className="flex items-start gap-3"> | |
| 382 | <span className={cn(soon && "opacity-70 grayscale-[35%]")}> | |
| 383 | <ConnectorMark view={view} /> | |
| 384 | </span> | |
| 385 | <div className="min-w-0 grow"> | |
| 386 | <div className="flex items-center gap-2"> | |
| 387 | {!soon && to ? ( | |
| 388 | // The whole card opens it: the link stretches over the card. | |
| 389 | <Link to={to} className="truncate text-sm font-medium after:absolute after:inset-0 after:rounded-xl focus-visible:outline-none"> | |
| 390 | {view.name} | |
| 391 | </Link> | |
| 392 | ) : ( | |
| 393 | <p className={cn("truncate text-sm font-medium", soon && "text-fg/80")}>{view.name}</p> | |
| 394 | )} | |
| 395 | {state ? ( | |
| 396 | state.problem ? ( | |
| 397 | <Hint label={state.problem}> | |
| 398 | <Badge tone="warn" className="relative z-10" tabIndex={0}> | |
| 399 | <AlertTriangle size={11} /> Needs attention | |
| 400 | </Badge> | |
| 401 | </Hint> | |
| 402 | ) : ( | |
| 403 | <Badge tone="success">Connected</Badge> | |
| 404 | ) | |
| 405 | ) : soon ? ( | |
| 406 | <Badge>Soon</Badge> | |
| 407 | ) : null} | |
| 408 | </div> | |
| 409 | {showCategory && <p className="text-xs text-faint">{categoryTitle(view.category)}</p>} | |
| 410 | </div> | |
| 411 | </div> | |
| 412 | <p className={cn("mt-2.5 text-sm", soon ? "text-muted/90" : "text-muted")}>{view.description}</p> | |
| 413 | {state?.detail && <p className="mt-1.5 truncate text-xs text-faint">{state.detail}</p>} | |
| 414 | {view.capabilities.length > 0 && ( | |
| 415 | <ul className="mt-3 flex flex-wrap gap-1.5"> | |
| 416 | {view.capabilities.map((capability) => ( | |
| 417 | <li key={capability} className="rounded-md bg-raised px-1.5 py-0.5 text-[0.6875rem] text-muted"> | |
| 418 | {capability} | |
| 419 | </li> | |
| 420 | ))} | |
| 421 | </ul> | |
| 422 | )} | |
| 423 | <div className="mt-auto flex items-end justify-between gap-3 pt-3"> | |
| 424 | <p className="text-[0.6875rem] text-faint">{also}</p> | |
| 425 | {soon ? ( | |
| 426 | <a | |
| 427 | href={askHref({ connector: view, scope, workspace, address: CONTACT.support })} | |
| 428 | className="relative z-10 shrink-0 rounded-md border border-line px-2.5 py-1 text-xs font-medium text-fg/80 hover:border-line-strong hover:bg-raised hover:text-fg" | |
| 429 | > | |
| 430 | Ask for this | |
| 431 | </a> | |
| 432 | ) : to ? ( | |
| 433 | <span className="flex shrink-0 items-center gap-0.5 text-xs font-medium text-accent"> | |
| 434 | {state ? (canManage ? "Manage" : "View") : canManage ? "Set up" : "View"} | |
| 435 | <ChevronRight size={13} /> | |
| 436 | </span> | |
| 437 | ) : null} | |
| 438 | </div> | |
| 439 | </li> | |
| 440 | ); | |
| 441 | } | |
| 442 | ||
| 443 | /** A short line saying who a page's connections are for, and where the other kind lives. */ | |
| 444 | export function ScopeNote({ scope, workspace }: { scope: ConnectorScope; workspace: string | null }) { | |
| 445 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 446 | <Card asChild tone="plain" className="mb-6 bg-surface/60 px-4 py-3 text-sm text-muted"> |
| 447 | <p> | |
| 448 | {scope === "workspace" ? ( | |
| 449 | <> | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 450 | Connected here, a tool works <span className="text-fg">for everyone in {workspace}</span>. |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 451 | Your own accounts, such as your calendar, are under{" "} |
| 452 | <Link to="/settings/integrations" className="text-accent hover:underline"> | |
| 453 | your integrations | |
| 454 | </Link> | |
| 455 | . | |
| 456 | </> | |
| 457 | ) : ( | |
| 458 | <> | |
| 459 | Connected here, a tool works <span className="text-fg">just for you, in every workspace</span> you belong to. Tools | |
| 460 | the whole team shares are connected by a workspace owner, under the workspace's Integrations. | |
| 461 | </> | |
| 462 | )} | |
| 463 | </p> | |
| 464 | </Card> | |
| Integrations are a directory of connectors, for a workspace and for each person | 465 | ); |
| 466 | } |