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 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 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. | 4 | import type { InstallRequest } from "@g1t/contracts"; |
| 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. | 5 | import { connectorById, connectorsFor } from "@g1t/contracts/connectors"; |
| 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. | 6 | |
| 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. | 7 | import { FIRST_PARTY_EXTENSIONS, extensionById } 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. | 8 | |
| 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. | 9 | import { |
| 10 | STARTER_KITS, | |
| 11 | TIERS, | |
| 12 | addPath, | |
| 13 | categoryTitle, | |
| 14 | comingIntegrations, | |
| 15 | comingListings, | |
| 16 | extensionListings, | |
| 17 | integrationListings, | |
| 18 | integrationMatches, | |
| 19 | integrationUses, | |
| 20 | isConnectedSystem, | |
| 21 | marketplaceForm, | |
| 22 | passes, | |
| 23 | readFilters, | |
| 24 | tiersShown, | |
| 25 | } from "./marketplace.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. | 26 | import { modeOf } from "./workspace-nav.ts"; |
| 27 | ||
| 28 | const request = (listing: string, by: string, status: InstallRequest["status"] = "open"): InstallRequest => ({ | |
| 29 | id: `ins_${listing}_${by}`, | |
| 30 | listing, | |
| 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. | 31 | kind: listing.startsWith("extension:") ? "extension" : "integration", |
| 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. | 32 | name: listing, |
| 33 | note: null, | |
| 34 | requested_by: by, | |
| 35 | requested_at: "2026-10-10T12:00:00Z", | |
| 36 | status, | |
| 37 | resolved_by: null, | |
| 38 | resolved_at: null, | |
| 39 | }); | |
| 40 | ||
| 41 | test("only integrations a workspace can connect today are listed, connected first", () => { | |
| 42 | const views = connectorsFor("workspace"); | |
| 43 | const listings = integrationListings(views, { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }, [], "ana", "acme"); | |
| 44 | assert.ok(listings.every((l) => l.view.status === "available")); | |
| 45 | assert.equal(listings[0]!.view.id, "sentry"); | |
| 46 | assert.equal(listings[0]!.href, "/acme/-/integrations/alerts", "managed where it is managed"); | |
| 47 | const linear = listings.find((l) => l.view.id === "linear")!; | |
| 48 | assert.equal(linear.href, "/acme/-/integrations/trackers?add=linear#add", "connected on its setup page"); | |
| 49 | assert.ok(!listings.some((l) => l.view.id === "slack")); | |
| 50 | assert.ok(comingIntegrations(views).some((v) => v.id === "slack")); | |
| 51 | assert.ok(integrationMatches(linear, "tracker")); | |
| 52 | assert.ok(!integrationMatches(linear, "deploys")); | |
| 53 | }); | |
| 54 | ||
| 55 | test("an owner adds what a request asks for where it is added", () => { | |
| 56 | const views = connectorsFor("workspace"); | |
| 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. | 57 | assert.equal(addPath({ listing: "agent:qa" }, "acme", views), null, "agents aren't added from the 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. | 58 | assert.equal(addPath({ listing: "integration:jira" }, "acme", views), "/acme/-/integrations/trackers?add=jira#add"); |
| 59 | assert.equal(addPath({ listing: "nonsense" }, "acme", views), null); | |
| 60 | }); | |
| 61 | ||
| 62 | test("the Marketplace's forms are read strictly", () => { | |
| 63 | const form = (fields: Record<string, string>) => { | |
| 64 | const data = new FormData(); | |
| 65 | for (const [k, v] of Object.entries(fields)) data.set(k, v); | |
| 66 | return data; | |
| 67 | }; | |
| 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. | 68 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "integration:jira", note: " flaky " })), { intent: "request", listing: "integration:jira", note: "flaky" }); |
| 69 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "extension:support" })), { intent: "request", listing: "extension:support", note: null }); | |
| 70 | assert.equal(marketplaceForm(form({ intent: "request", listing: "agent:qa" })), null, "agents start from templates in Agents"); | |
| 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. | 71 | assert.equal(marketplaceForm(form({ intent: "request", listing: "app:qa" })), null); |
| 72 | assert.deepEqual(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "declined" })), { intent: "resolve", id: "ins_1", status: "declined" }); | |
| 73 | assert.equal(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "open" })), null); | |
| 74 | assert.equal(marketplaceForm(form({ intent: "install" })), null); | |
| 75 | }); | |
| 76 | ||
| 77 | test("extensions list published ones first, each with its install", () => { | |
| 78 | const published = { ...FIRST_PARTY_EXTENSIONS[1]!, id: "standup", status: "available" as const, source: { repo: "flagon-io/standup", tag: "v1.0.0" }, version: "1.0.0" }; | |
| 79 | const install = { id: "ins_1", listing: "extension:standup", version: "1.0.0", plan: "free", installed_by: "chase", installed_at: "2026-10-10T00:00:00Z", enabled: true, disabled_by: null, disabled_at: null, budget_monthly_micros: null }; | |
| 80 | const listings = extensionListings([...FIRST_PARTY_EXTENSIONS, published], [install], [request("extension:support", "ana")], "ana"); | |
| 81 | assert.equal(listings[0]!.manifest.id, "standup"); | |
| 82 | assert.equal(listings[0]!.install?.version, "1.0.0"); | |
| 83 | assert.equal(listings.find((l) => l.manifest.id === "support")!.requested, true); | |
| 84 | assert.equal(listings.find((l) => l.manifest.id === "mail")!.install, null); | |
| 85 | }); | |
| 86 | ||
| 87 | test("coming integrations include the ones each person connects, once", () => { | |
| 88 | const workspace = connectorsFor("workspace"); | |
| 89 | const personal = connectorsFor("personal"); | |
| 90 | const coming = comingIntegrations(workspace, personal).map((v) => v.id); | |
| 91 | assert.ok(coming.includes("slack")); | |
| 92 | assert.ok(coming.includes("gmail"), "Gmail is each person's"); | |
| 93 | assert.ok(coming.includes("google-calendar")); | |
| 94 | assert.equal(new Set(coming).size, coming.length); | |
| 95 | }); | |
| 96 | ||
| 97 | test("installing, switching and removing an extension are read from forms", () => { | |
| 98 | const form = (fields: Record<string, string>) => { | |
| 99 | const data = new FormData(); | |
| 100 | for (const [k, v] of Object.entries(fields)) data.set(k, v); | |
| 101 | return data; | |
| 102 | }; | |
| 103 | assert.deepEqual(marketplaceForm(form({ intent: "install", extension: "support" })), { intent: "install", extension: "support" }); | |
| 104 | assert.deepEqual(marketplaceForm(form({ intent: "switch", listing: "extension:support", enabled: "off" })), { intent: "switch", listing: "extension:support", enabled: false }); | |
| 105 | assert.equal(marketplaceForm(form({ intent: "switch", listing: "integration:sentry", enabled: "off" })), null); | |
| 106 | assert.deepEqual(marketplaceForm(form({ intent: "uninstall", listing: "extension:support" })), { intent: "uninstall", listing: "extension:support" }); | |
| 107 | assert.equal(addPath({ listing: "extension:mail" }, "acme", []), "/acme/-/marketplace/extensions/mail"); | |
| 108 | }); | |
| 109 | ||
| 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. | 110 | test("starter kits name extensions that are listed, and connected systems are told apart", () => { |
| 111 | for (const kit of STARTER_KITS) { | |
| 112 | assert.ok(kit.extensions.length > 0, kit.id); | |
| 113 | for (const id of kit.extensions) assert.ok(extensionById(id), `${kit.id}: ${id}`); | |
| 114 | } | |
| 115 | const systems = FIRST_PARTY_EXTENSIONS.filter(isConnectedSystem).map((m) => m.id); | |
| 116 | assert.deepEqual(systems, ["helpdesk-bridge", "crm-bridge", "erp-bridge"]); | |
| 117 | assert.ok(FIRST_PARTY_EXTENSIONS.filter(isConnectedSystem).every((m) => m.bridges), "a connected system says what it bridges"); | |
| 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. | 118 | }); |
| 119 | ||
| 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. | 120 | test("every listing says who builds it and whether it can be added here", () => { |
| 121 | const views = connectorsFor("workspace"); | |
| 122 | const connected = { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }; | |
| 123 | const listings = integrationListings(views, connected, [], "ana", "acme", { github: "This g1t has no GitHub App set up." }); | |
| 124 | assert.ok(listings.every((l) => l.tier === "official" && l.publisher === "g1t"), "the connector catalog is g1t's own"); | |
| 125 | assert.equal(listings.find((l) => l.view.id === "sentry")!.availability, "added"); | |
| 126 | assert.equal(listings.find((l) => l.view.id === "jira")!.availability, "available"); | |
| 127 | const github = listings.find((l) => l.view.id === "github")!; | |
| 128 | assert.equal(github.availability, "unavailable"); | |
| 129 | assert.equal(github.why, "This g1t has no GitHub App set up."); | |
| 130 | assert.equal(github.href, null, "nothing to connect where it can't be connected"); | |
| 131 | assert.equal(listings.find((l) => l.view.id === "linear")!.path, "/acme/-/marketplace/integrations/linear"); | |
| 132 | ||
| 133 | const coming = comingListings(views, connectorsFor("personal").filter((v) => v.id === "gmail"), "acme"); | |
| 134 | assert.ok(coming.every((l) => l.availability === "soon" && l.href === null && l.tier === "official")); | |
| 135 | assert.equal(coming.find((l) => l.view.id === "gmail")!.scope, "personal"); | |
| 136 | ||
| 137 | const extensions = extensionListings(FIRST_PARTY_EXTENSIONS, [], [], "ana"); | |
| 138 | assert.ok(extensions.every((l) => l.tier === "official" && l.availability === "soon"), "g1t's own, none published yet"); | |
| 139 | }); | |
| 140 | ||
| 141 | test("an integration's page says what it does today and what is still Soon", () => { | |
| 142 | const linear = integrationUses(connectorById("linear")!); | |
| 143 | assert.deepEqual(linear.today.map((u) => u.scope), ["workspace"]); | |
| 144 | assert.deepEqual(linear.today[0]!.capabilities, ["Agents can read", "Writes back"]); | |
| 145 | assert.deepEqual(linear.soon.map((u) => u.scope), ["personal"], "each person's Linear inbox is Soon"); | |
| 146 | const slack = integrationUses(connectorById("slack")!); | |
| 147 | assert.equal(slack.today.length, 0); | |
| 148 | assert.equal(slack.soon.length, 2); | |
| 149 | assert.equal(categoryTitle("issues"), "Issues & projects"); | |
| 150 | }); | |
| 151 | ||
| 152 | test("filters by tier and availability are read from the address, and unknown ones are All", () => { | |
| 153 | assert.deepEqual(readFilters(new URLSearchParams("tier=verified&availability=soon")), { tier: "verified", availability: "soon" }); | |
| 154 | assert.deepEqual(readFilters(new URLSearchParams("tier=gold&availability=maybe")), { tier: "all", availability: "all" }); | |
| 155 | assert.deepEqual(tiersShown({ tier: "all", availability: "all" }), ["official", "verified", "community", "internal"]); | |
| 156 | assert.deepEqual(tiersShown({ tier: "community", availability: "all" }), ["community"]); | |
| 157 | assert.ok(passes({ tier: "official", availability: "soon" }, { tier: "all", availability: "soon" })); | |
| 158 | assert.ok(!passes({ tier: "official", availability: "soon" }, { tier: "verified", availability: "all" })); | |
| 159 | for (const tier of ["verified", "community", "internal"] as const) assert.ok(TIERS[tier].none.extension.length > 0, `${tier} says when it is empty`); | |
| 160 | }); | |
| 161 | ||
| 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. | 162 | test("the Marketplace is part of Apps, not a mode of its own", () => { |
| 163 | assert.equal(modeOf("/acme/-/marketplace", "acme"), "apps"); | |
| 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. | 164 | assert.equal(modeOf("/acme/-/marketplace/extensions/mail", "acme"), "apps"); |
| 165 | assert.equal(modeOf("/acme/-/agents/templates", "acme"), "agents", "agent templates are Agents'"); | |
| 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 | }); |