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 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. | 1 | /** |
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 2 | * The Marketplace (routes/workspace/marketplace/): what adds functionality |
| 3 | * to a workspace, read against what it has and what its people asked for. | |
| 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. | 4 | * |
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 5 | * Two kinds of listing: extensions, which add pages, data, cards and agent |
| 6 | * roles (g1t's own are listed before their first release), and | |
| 7 | * integrations, which connect what a team already uses so agents can work | |
| 8 | * with it. Owners add them; anyone else asks, and the agents service keeps | |
| 9 | * the request (`install_requests`). Agents aren't listed: they start from | |
| 10 | * templates in Agents mode (lib/agent-templates.ts). | |
| 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. | 11 | * |
| 12 | * No Workers or React imports, so it can be tested under Node. | |
| 13 | */ | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 14 | import type { ExtensionInstall, ExtensionManifest, InstallRequest, InstallRequestStatus, ListingKind, ListingTier } from "@g1t/contracts"; |
| 15 | import { CONNECTOR_CATEGORIES, type Connector, type ConnectorCapability, type ConnectorScope, type ConnectorView, connectorPath, connectorView } from "@g1t/contracts/connectors"; | |
| 16 | import { CONNECTOR_PUBLISHER, LISTING_TIERS, listingRef, parseListing } from "@g1t/contracts/marketplace"; | |
| 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. | 17 | |
| 18 | import type { ConnectedState } from "./connectors"; | |
| 19 | ||
| 20 | /** The Marketplace's pages, under `/<workspace>/-/marketplace`. */ | |
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 21 | export function marketplacePath(slug: string, page: "" | "extensions" | "integrations" | "requests" = ""): string { |
| 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. | 22 | return `/${slug}/-/marketplace${page ? `/${page}` : ""}`; |
| 23 | } | |
| 24 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 25 | /** |
| 26 | * Who stands behind a listing: in a word, a sentence, and what a tier's | |
| 27 | * section says while nothing in it is listed (`none`, by kind). | |
| 28 | */ | |
| 29 | export const TIERS: Record<ListingTier, { label: string; about: string; none: Record<ListingKind, string> }> = { | |
| 30 | official: { | |
| 31 | label: "Official", | |
| 32 | about: "Built and supported by g1t.", | |
| 33 | none: { extension: "No official extensions match.", integration: "No official integrations match." }, | |
| 34 | }, | |
| 35 | verified: { | |
| 36 | label: "Verified", | |
| 37 | about: "From a reviewed publisher. Its code and the scopes it asks for are checked before it is listed.", | |
| 38 | none: { extension: "No verified publishers yet.", integration: "No verified publishers yet." }, | |
| 39 | }, | |
| 40 | community: { | |
| 41 | label: "Community", | |
| 42 | about: "From anyone. Its pages run sandboxed on the user-content domain, and its server code stays off until that sandbox is hardened.", | |
| 43 | none: { extension: "No community extensions yet.", integration: "No community integrations yet." }, | |
| 44 | }, | |
| 45 | internal: { | |
| 46 | label: "Internal", | |
| 47 | about: "Built by your own people and agents, and promoted for this workspace only.", | |
| 48 | none: { extension: "Nothing built in this workspace yet.", integration: "Nothing built in this workspace yet." }, | |
| 49 | }, | |
| 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. | 50 | }; |
| 51 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 52 | /** |
| 53 | * Whether a listing can be added here, now: it can (`available`), the | |
| 54 | * workspace has it (`added`: connected, or installed), it is planned and | |
| 55 | * can't be added by anyone yet (`soon`), or this workspace or this g1t | |
| 56 | * lacks something it needs (`unavailable`, with why). | |
| 57 | */ | |
| 58 | export type Availability = "available" | "added" | "soon" | "unavailable"; | |
| 59 | ||
| 60 | export const AVAILABILITIES: readonly Availability[] = ["available", "added", "soon", "unavailable"]; | |
| 61 | ||
| 62 | /** Each availability in words, and the sentence its hint shows. */ | |
| 63 | export const AVAILABILITY: Record<Availability, { label: string; about: string }> = { | |
| 64 | available: { label: "Available", about: "Can be added now. Owners add it for everyone; anyone else can ask an owner." }, | |
| 65 | added: { label: "Added", about: "This workspace has it." }, | |
| 66 | soon: { label: "Soon", about: "Planned, not built yet. Nobody can add it until it is released." }, | |
| 67 | unavailable: { label: "Not available here", about: "This workspace or this g1t lacks something it needs." }, | |
| 68 | }; | |
| 69 | ||
| 70 | /** What a workspace that has a listing calls it: an integration is connected, an extension installed. */ | |
| 71 | export function addedWord(kind: ListingKind): string { | |
| 72 | return kind === "integration" ? "Connected" : "Installed"; | |
| 73 | } | |
| 74 | ||
| 75 | /** A filter's value: one tier or availability, or every one. */ | |
| 76 | export type ListingFilters = { tier: ListingTier | "all"; availability: Availability | "all" }; | |
| 77 | ||
| 78 | /** The filters a Marketplace page's address asks for (`?tier=verified&availability=soon`); anything unknown is All. */ | |
| 79 | export function readFilters(params: URLSearchParams): ListingFilters { | |
| 80 | const tier = params.get("tier"); | |
| 81 | const availability = params.get("availability"); | |
| 82 | return { | |
| 83 | tier: LISTING_TIERS.includes(tier as ListingTier) ? (tier as ListingTier) : "all", | |
| 84 | availability: AVAILABILITIES.includes(availability as Availability) ? (availability as Availability) : "all", | |
| 85 | }; | |
| 86 | } | |
| 87 | ||
| 88 | /** Whether a listing passes the filters. */ | |
| 89 | export function passes(listing: { tier: ListingTier; availability: Availability }, filters: ListingFilters): boolean { | |
| 90 | return (filters.tier === "all" || listing.tier === filters.tier) && (filters.availability === "all" || listing.availability === filters.availability); | |
| 91 | } | |
| 92 | ||
| 93 | /** The tiers a page shows sections for under `filters`: every one, or the one asked for. */ | |
| 94 | export function tiersShown(filters: ListingFilters): readonly ListingTier[] { | |
| 95 | return filters.tier === "all" ? LISTING_TIERS : [filters.tier]; | |
| 96 | } | |
| 97 | ||
| 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. | 98 | /** Whether `request` is `username`'s and still waiting. */ |
| 99 | function openFor(request: InstallRequest, ref: string, username: string): boolean { | |
| 100 | return request.status === "open" && request.listing === ref && request.requested_by.toLowerCase() === username.toLowerCase(); | |
| 101 | } | |
| 102 | ||
| 103 | /** Open requests for `ref`. */ | |
| 104 | function openCount(requests: InstallRequest[], ref: string): number { | |
| 105 | return requests.filter((request) => request.status === "open" && request.listing === ref).length; | |
| 106 | } | |
| 107 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 108 | /** An integration in the Marketplace, with who stands behind it and whether the workspace has it. */ |
| 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. | 109 | export type IntegrationListing = { |
| 110 | ref: string; | |
| 111 | view: ConnectorView; | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 112 | /** Who connects it: an owner, once for the workspace, or each person for themselves. */ |
| 113 | scope: ConnectorScope; | |
| 114 | /** Every connector is g1t's own (CONNECTOR_PUBLISHER). */ | |
| 115 | tier: ListingTier; | |
| 116 | publisher: string; | |
| 117 | availability: Availability; | |
| 118 | /** Why it can't be connected here, when it can't. */ | |
| 119 | why: string | null; | |
| 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. | 120 | /** How it is doing, when it is connected. */ |
| 121 | connected: ConnectedState | null; | |
| 122 | /** Where an owner connects it, or manages it once connected. */ | |
| 123 | href: string | null; | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 124 | /** Its page in the Marketplace. */ |
| 125 | path: string; | |
| 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. | 126 | requested: boolean; |
| 127 | waiting: number; | |
| 128 | }; | |
| 129 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 130 | /** One connector as a listing in the workspace `slug`. */ |
| 131 | function integrationListing( | |
| 132 | view: ConnectorView, | |
| 133 | scope: ConnectorScope, | |
| 134 | slug: string, | |
| 135 | connected: Record<string, ConnectedState> = {}, | |
| 136 | unavailable: Record<string, string> = {}, | |
| 137 | requests: InstallRequest[] = [], | |
| 138 | username = "", | |
| 139 | ): IntegrationListing { | |
| 140 | const ref = listingRef("integration", view.id); | |
| 141 | const state = scope === "workspace" ? (connected[view.id] ?? null) : null; | |
| 142 | const why = view.status === "available" && !state ? (unavailable[view.id] ?? null) : null; | |
| 143 | const availability: Availability = state ? "added" : view.status !== "available" ? "soon" : why ? "unavailable" : "available"; | |
| 144 | const setup = view.href && !why ? connectorPath(view.href, slug) : null; | |
| 145 | return { | |
| 146 | ref, | |
| 147 | view, | |
| 148 | scope, | |
| 149 | tier: CONNECTOR_PUBLISHER.tier, | |
| 150 | publisher: CONNECTOR_PUBLISHER.name, | |
| 151 | availability, | |
| 152 | why, | |
| 153 | connected: state, | |
| 154 | href: state?.manage ?? setup, | |
| 155 | path: integrationPath(slug, view.id), | |
| 156 | requested: requests.some((request) => openFor(request, ref, username)), | |
| 157 | waiting: openCount(requests, ref), | |
| 158 | }; | |
| 159 | } | |
| 160 | ||
| 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. | 161 | /** |
| 162 | * The integrations a workspace can connect today, connected ones first, | |
| 163 | * each in catalog order. `connected` comes from the integrations service | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 164 | * and the GitHub App (lib/connected.server.ts), as does `unavailable`: |
| 165 | * connectors this g1t can't connect, with why. | |
| 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. | 166 | */ |
| 167 | export function integrationListings( | |
| 168 | views: ConnectorView[], | |
| 169 | connected: Record<string, ConnectedState>, | |
| 170 | requests: InstallRequest[], | |
| 171 | username: string, | |
| 172 | slug: string, | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 173 | unavailable: Record<string, string> = {}, |
| 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. | 174 | ): IntegrationListing[] { |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 175 | const listings = views.filter((view) => view.status === "available").map((view) => integrationListing(view, "workspace", slug, connected, unavailable, requests, username)); |
| 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. | 176 | return [...listings.filter((l) => l.connected), ...listings.filter((l) => !l.connected)]; |
| 177 | } | |
| 178 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 179 | /** What each person connects for themselves, available today, as listings. */ |
| 180 | export function personalListings(views: ConnectorView[], slug: string, unavailable: Record<string, string> = {}): IntegrationListing[] { | |
| 181 | return views.filter((view) => view.status === "available" && view.href != null).map((view) => integrationListing(view, "personal", slug, {}, unavailable)); | |
| 182 | } | |
| 183 | ||
| 184 | /** The coming integrations (comingIntegrations), as listings: Soon, with nothing to press. */ | |
| 185 | export function comingListings(views: ConnectorView[], personal: ConnectorView[], slug: string): IntegrationListing[] { | |
| 186 | const workspace = new Set(views.map((view) => view.id)); | |
| 187 | return comingIntegrations(views, personal).map((view) => integrationListing(view, workspace.has(view.id) ? "workspace" : "personal", slug)); | |
| 188 | } | |
| 189 | ||
| 190 | /** One integration's page in the Marketplace. */ | |
| 191 | export function integrationPath(slug: string, id: string): string { | |
| 192 | return `/${slug}/-/marketplace/integrations/${id}`; | |
| 193 | } | |
| 194 | ||
| 195 | /** One way an integration is connected, and what it does there. */ | |
| 196 | export type IntegrationUse = { | |
| 197 | scope: ConnectorScope; | |
| 198 | /** Who connects it this way, in words. */ | |
| 199 | who: string; | |
| 200 | description: string; | |
| 201 | capabilities: ConnectorCapability[]; | |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * What an integration does, today and soon: each way it is connected (for | |
| 206 | * the workspace, for each person) under whether that way is available yet, | |
| 207 | * from the catalog. Linear, say, works for a workspace today, and for each | |
| 208 | * person's own inbox soon. | |
| 209 | */ | |
| 210 | export function integrationUses(connector: Connector): { today: IntegrationUse[]; soon: IntegrationUse[] } { | |
| 211 | const today: IntegrationUse[] = []; | |
| 212 | const soon: IntegrationUse[] = []; | |
| 213 | for (const scope of connector.scopes) { | |
| 214 | const view = connectorView(connector, scope); | |
| 215 | if (!view) continue; | |
| 216 | const use = { | |
| 217 | scope, | |
| 218 | who: scope === "workspace" ? "For the whole workspace, connected once by an owner" : "For each person, with their own account", | |
| 219 | description: view.description, | |
| 220 | capabilities: view.capabilities, | |
| 221 | }; | |
| 222 | (view.status === "available" ? today : soon).push(use); | |
| 223 | } | |
| 224 | return { today, soon }; | |
| 225 | } | |
| 226 | ||
| 227 | /** A connector category's title: `issues` is `Issues & projects`. */ | |
| 228 | export function categoryTitle(category: Connector["category"]): string { | |
| 229 | return CONNECTOR_CATEGORIES.find((c) => c.id === category)?.title ?? category; | |
| 230 | } | |
| 231 | ||
| 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. | 232 | /** |
| 233 | * The integrations the catalog lists as coming: a workspace's, then those | |
| 234 | * each person connects for themselves (a calendar, a mailbox), once each. | |
| 235 | */ | |
| 236 | export function comingIntegrations(views: ConnectorView[], personal: ConnectorView[] = []): ConnectorView[] { | |
| 237 | const workspace = views.filter((view) => view.status === "soon"); | |
| 238 | const seen = new Set(workspace.map((view) => view.id)); | |
| 239 | const own = personal.filter((view) => view.status === "soon" && !seen.has(view.id) && !views.some((v) => v.id === view.id)); | |
| 240 | return [...workspace, ...own]; | |
| 241 | } | |
| 242 | ||
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 243 | /** An extension in the Marketplace, with who stands behind it and whether the workspace has it. */ |
| 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. | 244 | export type ExtensionListing = { |
| 245 | ref: string; | |
| 246 | manifest: ExtensionManifest; | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 247 | /** Its publisher's tier. */ |
| 248 | tier: ListingTier; | |
| 249 | availability: Availability; | |
| 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. | 250 | install: ExtensionInstall | null; |
| 251 | requested: boolean; | |
| 252 | waiting: number; | |
| 253 | }; | |
| 254 | ||
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 255 | /** Where extensions are grouped: connected systems apart from the rest. */ |
| 256 | export const CONNECTED_SYSTEMS = "Connected systems"; | |
| 257 | ||
| 258 | /** Whether an extension bridges a system the team already runs. */ | |
| 259 | export function isConnectedSystem(manifest: Pick<ExtensionManifest, "category">): boolean { | |
| 260 | return manifest.category === CONNECTED_SYSTEMS; | |
| 261 | } | |
| 262 | ||
| 263 | /** | |
| 264 | * A starting set of extensions for one kind of team, installed together | |
| 265 | * once each is published. Ids are first-party extensions'. | |
| 266 | */ | |
| 267 | export type StarterKit = { id: string; name: string; about: string; extensions: string[] }; | |
| 268 | ||
| 269 | export const STARTER_KITS: StarterKit[] = [ | |
| 270 | { id: "customers", name: "Customer team", about: "Mail on your domain, support conversations and a pipeline.", extensions: ["mail", "support", "crm"] }, | |
| 271 | { id: "engineering", name: "Engineering extras", about: "On-call rotations, and the helpdesk your customers already write to.", extensions: ["on-call", "helpdesk-bridge"] }, | |
| 272 | { id: "operations", name: "People and operations", about: "Hiring, and the orders and invoices in your ERP.", extensions: ["recruiting", "erp-bridge"] }, | |
| 273 | ]; | |
| 274 | ||
| 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. | 275 | /** Every extension listed, published ones first, each with its install. */ |
| 276 | export function extensionListings(manifests: ExtensionManifest[], installs: ExtensionInstall[], requests: InstallRequest[], username: string): ExtensionListing[] { | |
| 277 | const listings = manifests.map((manifest) => { | |
| 278 | const ref = listingRef("extension", manifest.id); | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 279 | const install = installs.find((install) => install.listing === ref) ?? null; |
| 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. | 280 | return { |
| 281 | ref, | |
| 282 | manifest, | |
| Every Marketplace listing says who builds it and whether you can add it: a tier (Official, Verified, Community, Internal) with what it means, and an availability (Available, Connected, Soon, or Not available here with why) that never looks like a button that doesn't work; Extensions and Integrations are grouped and filtered by both, each integration has a page with what it lets agents do today and what's Soon, and the marketplace guide says how. | 283 | tier: manifest.publisher.tier, |
| 284 | availability: (install ? "added" : manifest.status === "available" ? "available" : "soon") as Availability, | |
| 285 | install, | |
| 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. | 286 | requested: requests.some((request) => openFor(request, ref, username)), |
| 287 | waiting: openCount(requests, ref), | |
| 288 | }; | |
| 289 | }); | |
| 290 | return [...listings.filter((l) => l.manifest.status === "available"), ...listings.filter((l) => l.manifest.status !== "available")]; | |
| 291 | } | |
| 292 | ||
| 293 | /** One extension's page in the Marketplace. */ | |
| 294 | export function extensionPath(slug: string, id: string): string { | |
| 295 | return `/${slug}/-/marketplace/extensions/${id}`; | |
| 296 | } | |
| 297 | ||
| 298 | /** Where an extension runs, in words. */ | |
| 299 | export function runtimeWords(manifest: Pick<ExtensionManifest, "runtime" | "publisher">): string { | |
| 300 | return manifest.runtime === "hosted" ? `Runs on g1t` : `Runs on ${manifest.publisher.name}'s servers`; | |
| 301 | } | |
| 302 | ||
| 303 | /** Whether an integration listing matches what someone typed: its name, category words, capabilities or keywords. */ | |
| 304 | export function integrationMatches(listing: IntegrationListing, query: string): boolean { | |
| 305 | const wanted = query.trim().toLowerCase(); | |
| 306 | if (!wanted) return true; | |
| 307 | const { view } = listing; | |
| 308 | return [view.name, view.description, view.category, ...view.capabilities, ...view.keywords].some((word) => word.toLowerCase().includes(wanted)); | |
| 309 | } | |
| 310 | ||
| 311 | /** The requests still waiting on an owner. */ | |
| 312 | export function openRequests(requests: InstallRequest[]): InstallRequest[] { | |
| 313 | return requests.filter((request) => request.status === "open"); | |
| 314 | } | |
| 315 | ||
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 316 | /** Where an owner goes to add what a request asks for: the extension's page, or the integration's setup page. */ |
| 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. | 317 | export function addPath(request: Pick<InstallRequest, "listing">, slug: string, views: ConnectorView[]): string | null { |
| 318 | const parsed = parseListing(request.listing); | |
| 319 | if (!parsed) return null; | |
| 320 | if (parsed.kind === "extension") return extensionPath(slug, parsed.id); | |
| 321 | const view = views.find((v) => v.id === parsed.id); | |
| 322 | return view?.href ? connectorPath(view.href, slug) : null; | |
| 323 | } | |
| 324 | ||
| 325 | /** What the Marketplace's forms ask for, read from one. */ | |
| 326 | export type MarketplaceForm = | |
| 327 | | { intent: "request"; listing: string; note: string | null } | |
| 328 | | { intent: "resolve"; id: string; status: Exclude<InstallRequestStatus, "open"> } | |
| 329 | | { intent: "install"; extension: string } | |
| 330 | | { intent: "switch"; listing: string; enabled: boolean } | |
| 331 | | { intent: "uninstall"; listing: string }; | |
| 332 | ||
| 333 | /** A Marketplace form's request; null when it asks for nothing that makes sense. */ | |
| 334 | export function marketplaceForm(form: FormData): MarketplaceForm | null { | |
| 335 | const intent = form.get("intent"); | |
| 336 | if (intent === "request") { | |
| 337 | const listing = String(form.get("listing") ?? ""); | |
| 338 | if (!parseListing(listing)) return null; | |
| 339 | const note = String(form.get("note") ?? "").trim(); | |
| 340 | return { intent, listing, note: note || null }; | |
| 341 | } | |
| 342 | if (intent === "resolve") { | |
| 343 | const id = String(form.get("id") ?? ""); | |
| 344 | const status = form.get("status"); | |
| 345 | if (!id || (status !== "done" && status !== "declined")) return null; | |
| 346 | return { intent, id, status }; | |
| 347 | } | |
| 348 | const listing = String(form.get("listing") ?? ""); | |
| 349 | const extension = parseListing(listing)?.kind === "extension" ? listing : null; | |
| 350 | if (intent === "install") { | |
| 351 | const id = String(form.get("extension") ?? ""); | |
| 352 | return /^[a-z][a-z0-9-]{1,31}$/.test(id) ? { intent, extension: id } : null; | |
| 353 | } | |
| 354 | if (intent === "switch" && extension) { | |
| 355 | const enabled = form.get("enabled"); | |
| 356 | return enabled === "on" || enabled === "off" ? { intent, listing: extension, enabled: enabled === "on" } : null; | |
| 357 | } | |
| 358 | if (intent === "uninstall" && extension) return { intent, listing: extension }; | |
| 359 | return null; | |
| 360 | } |