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"; |
| 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. | 5 | import { connectorsFor } from "@g1t/contracts/connectors"; |
| 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 | |
| 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. | 9 | import { STARTER_KITS, addPath, comingIntegrations, extensionListings, integrationListings, integrationMatches, isConnectedSystem, marketplaceForm } 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. | 10 | import { modeOf } from "./workspace-nav.ts"; |
| 11 | ||
| 12 | const request = (listing: string, by: string, status: InstallRequest["status"] = "open"): InstallRequest => ({ | |
| 13 | id: `ins_${listing}_${by}`, | |
| 14 | 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. | 15 | 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. | 16 | name: listing, |
| 17 | note: null, | |
| 18 | requested_by: by, | |
| 19 | requested_at: "2026-10-10T12:00:00Z", | |
| 20 | status, | |
| 21 | resolved_by: null, | |
| 22 | resolved_at: null, | |
| 23 | }); | |
| 24 | ||
| 25 | test("only integrations a workspace can connect today are listed, connected first", () => { | |
| 26 | const views = connectorsFor("workspace"); | |
| 27 | const listings = integrationListings(views, { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }, [], "ana", "acme"); | |
| 28 | assert.ok(listings.every((l) => l.view.status === "available")); | |
| 29 | assert.equal(listings[0]!.view.id, "sentry"); | |
| 30 | assert.equal(listings[0]!.href, "/acme/-/integrations/alerts", "managed where it is managed"); | |
| 31 | const linear = listings.find((l) => l.view.id === "linear")!; | |
| 32 | assert.equal(linear.href, "/acme/-/integrations/trackers?add=linear#add", "connected on its setup page"); | |
| 33 | assert.ok(!listings.some((l) => l.view.id === "slack")); | |
| 34 | assert.ok(comingIntegrations(views).some((v) => v.id === "slack")); | |
| 35 | assert.ok(integrationMatches(linear, "tracker")); | |
| 36 | assert.ok(!integrationMatches(linear, "deploys")); | |
| 37 | }); | |
| 38 | ||
| 39 | test("an owner adds what a request asks for where it is added", () => { | |
| 40 | 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. | 41 | 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. | 42 | assert.equal(addPath({ listing: "integration:jira" }, "acme", views), "/acme/-/integrations/trackers?add=jira#add"); |
| 43 | assert.equal(addPath({ listing: "nonsense" }, "acme", views), null); | |
| 44 | }); | |
| 45 | ||
| 46 | test("the Marketplace's forms are read strictly", () => { | |
| 47 | const form = (fields: Record<string, string>) => { | |
| 48 | const data = new FormData(); | |
| 49 | for (const [k, v] of Object.entries(fields)) data.set(k, v); | |
| 50 | return data; | |
| 51 | }; | |
| 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. | 52 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "integration:jira", note: " flaky " })), { intent: "request", listing: "integration:jira", note: "flaky" }); |
| 53 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "extension:support" })), { intent: "request", listing: "extension:support", note: null }); | |
| 54 | 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. | 55 | assert.equal(marketplaceForm(form({ intent: "request", listing: "app:qa" })), null); |
| 56 | assert.deepEqual(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "declined" })), { intent: "resolve", id: "ins_1", status: "declined" }); | |
| 57 | assert.equal(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "open" })), null); | |
| 58 | assert.equal(marketplaceForm(form({ intent: "install" })), null); | |
| 59 | }); | |
| 60 | ||
| 61 | test("extensions list published ones first, each with its install", () => { | |
| 62 | 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" }; | |
| 63 | 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 }; | |
| 64 | const listings = extensionListings([...FIRST_PARTY_EXTENSIONS, published], [install], [request("extension:support", "ana")], "ana"); | |
| 65 | assert.equal(listings[0]!.manifest.id, "standup"); | |
| 66 | assert.equal(listings[0]!.install?.version, "1.0.0"); | |
| 67 | assert.equal(listings.find((l) => l.manifest.id === "support")!.requested, true); | |
| 68 | assert.equal(listings.find((l) => l.manifest.id === "mail")!.install, null); | |
| 69 | }); | |
| 70 | ||
| 71 | test("coming integrations include the ones each person connects, once", () => { | |
| 72 | const workspace = connectorsFor("workspace"); | |
| 73 | const personal = connectorsFor("personal"); | |
| 74 | const coming = comingIntegrations(workspace, personal).map((v) => v.id); | |
| 75 | assert.ok(coming.includes("slack")); | |
| 76 | assert.ok(coming.includes("gmail"), "Gmail is each person's"); | |
| 77 | assert.ok(coming.includes("google-calendar")); | |
| 78 | assert.equal(new Set(coming).size, coming.length); | |
| 79 | }); | |
| 80 | ||
| 81 | test("installing, switching and removing an extension are read from forms", () => { | |
| 82 | const form = (fields: Record<string, string>) => { | |
| 83 | const data = new FormData(); | |
| 84 | for (const [k, v] of Object.entries(fields)) data.set(k, v); | |
| 85 | return data; | |
| 86 | }; | |
| 87 | assert.deepEqual(marketplaceForm(form({ intent: "install", extension: "support" })), { intent: "install", extension: "support" }); | |
| 88 | assert.deepEqual(marketplaceForm(form({ intent: "switch", listing: "extension:support", enabled: "off" })), { intent: "switch", listing: "extension:support", enabled: false }); | |
| 89 | assert.equal(marketplaceForm(form({ intent: "switch", listing: "integration:sentry", enabled: "off" })), null); | |
| 90 | assert.deepEqual(marketplaceForm(form({ intent: "uninstall", listing: "extension:support" })), { intent: "uninstall", listing: "extension:support" }); | |
| 91 | assert.equal(addPath({ listing: "extension:mail" }, "acme", []), "/acme/-/marketplace/extensions/mail"); | |
| 92 | }); | |
| 93 | ||
| 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. | 94 | test("starter kits name extensions that are listed, and connected systems are told apart", () => { |
| 95 | for (const kit of STARTER_KITS) { | |
| 96 | assert.ok(kit.extensions.length > 0, kit.id); | |
| 97 | for (const id of kit.extensions) assert.ok(extensionById(id), `${kit.id}: ${id}`); | |
| 98 | } | |
| 99 | const systems = FIRST_PARTY_EXTENSIONS.filter(isConnectedSystem).map((m) => m.id); | |
| 100 | assert.deepEqual(systems, ["helpdesk-bridge", "crm-bridge", "erp-bridge"]); | |
| 101 | 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. | 102 | }); |
| 103 | ||
| 104 | test("the Marketplace is part of Apps, not a mode of its own", () => { | |
| 105 | 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. | 106 | assert.equal(modeOf("/acme/-/marketplace/extensions/mail", "acme"), "apps"); |
| 107 | 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. | 108 | }); |