History

Use published plugin marks and link the install from the add screen

Rhys Sullivan committed 2 months ago

Commit a87180f1 parent ff496ec6 files · +53 −13Browse files

Unified · Split

6 files
  1. packages/plugins/mcp/src/api/group.ts +2 −0
  2. packages/plugins/mcp/src/react/CodexPluginAdd.tsx +14 −9
  3. packages/plugins/mcp/src/sdk/codex-plugin-presets.ts +16 −0
  4. packages/plugins/mcp/src/sdk/codex-plugins.test.ts +6 −0
  5. packages/plugins/mcp/src/sdk/codex-plugins.ts +14 −3
  6. packages/plugins/mcp/src/sdk/presets.ts +1 −1
packages/plugins/mcp/src/api/group.ts+2−0
@@ -163,6 +163,8 @@ const CodexPluginEntrySchema = Schema.Struct({ · 162 unmodified lines
163163 }),
164164 ),
165165 setupHint: Schema.optional(Schema.String),
166+ setupUrl: Schema.optional(Schema.String),
167+ fallbackIcon: Schema.optional(Schema.String),
166168 /** The plugin's own icon from its local install, as a data URI. */
167169 icon: Schema.optional(Schema.String),
168170 /** The plugin's own display metadata from its local manifest. */
packages/plugins/mcp/src/react/CodexPluginAdd.tsx+14−9
@@ -98,7 +98,7 @@ export default function CodexPluginAdd(props: { · 97 unmodified lines
9898 one the card still identifies its provider rather than showing a
9999 gap, which matters most on the machines that have no install. */}
100100 <img
101− src={plugin.icon ?? "https://integrations.sh/logo/openai.com"}
101+ src={plugin.icon ?? plugin.fallbackIcon ?? "https://integrations.sh/logo/openai.com"}
102102 alt=""
103103 className="size-16 rounded-2xl"
104104 />
@@ -128,7 +128,7 @@ export default function CodexPluginAdd(props: { · 23 unmodified lines
128128 Status
129129 </span>
130130 <span className="font-mono text-[11px] text-muted-foreground">
131− {added ? "Added" : plugin.available ? "Ready" : "Requires Codex"}
131+ {added ? "Added" : plugin.available ? "Ready" : "Not installed on this Mac"}
132132 </span>
133133 </div>
134134 {!plugin.available && plugin.setupHint !== undefined && (
@@ -159,15 +159,20 @@ export default function CodexPluginAdd(props: { · 24 unmodified lines
159159 <Button type="button" onClick={() => props.onComplete(plugin.slug)}>
160160 View integration
161161 </Button>
162− ) : (
163− <Button
164− type="button"
165− onClick={() => void handleAdd()}
166− disabled={!plugin.available}
167− loading={adding}
168− >
162+ ) : plugin.available ? (
163+ <Button type="button" onClick={() => void handleAdd()} loading={adding}>
169164 Add integration
170165 </Button>
166+ ) : (
167+ <Button asChild>
168+ <a
169+ href={plugin.setupUrl ?? "https://openai.com/codex"}
170+ target="_blank"
171+ rel="noreferrer"
172+ >
173+ Install Codex
174+ </a>
175+ </Button>
171176 )}
172177 </FloatActions>
173178 </div>
packages/plugins/mcp/src/sdk/codex-plugin-presets.ts+16−0
@@ -18,6 +18,10 @@ export interface CuratedCodexPlugin { · 17 unmodified lines
1818 /** The MCP server name this plugin registers inside Codex — the `server`
1919 * the app-server bridge calls tools against. */
2020 readonly server: string;
21+ /** A public image for this plugin, used when the machine-local icon cannot
22+ * be read (i.e. Codex is not installed here). Only some plugins have one
23+ * published; the rest fall back to the provider's mark. */
24+ readonly publicIcon?: string;
2125 /** Present when the plugin has no MCP server of its own and its API is
2226 * projected onto another one. Computer Use and Chrome both ship as
2327 * skills/`node-repl` content: Codex never starts a server for either, and
@@ -68,6 +72,16 @@ export const setupHint = (requires: CuratedCodexPlugin["requires"], name: string · 44 unmodified lines
6872 .map((step, index) => `${index + 1}. ${step}`)
6973 .join("\n");
7074
75+/** Where a person goes to get what a card needs.
76+ *
77+ * Linked rather than only described: a card that cannot be used is a dead end
78+ * unless it hands over the next step. Chrome's own requirement is documented
79+ * on the Computer Use page, so it points there instead of the app download. */
80+export const setupUrl = (requires: CuratedCodexPlugin["requires"]): string =>
81+ requires === "chrome-plugin"
82+ ? "https://learn.chatgpt.com/docs/computer-use"
83+ : "https://openai.com/codex";
84+
7185 export const CURATED_CODEX_PLUGINS: readonly CuratedCodexPlugin[] = [
7286 // Names are exactly the plugins' own displayNames — nothing invented, no
7387 // provenance suffix. Codex provenance shows in the summaries and on the
@@ -88,6 +102,7 @@ export const CURATED_CODEX_PLUGINS: readonly CuratedCodexPlugin[] = [ · 14 unmodified lines
88102 pluginName: "computer-use",
89103 name: "Computer Use",
90104 slug: "codex_computer_use",
105+ publicIcon: "https://learn.chatgpt.com/images/codex/icons/computer-use-plugin-icon.png",
91106 requires: "computer-use-app",
92107 server: "node_repl",
93108 surface: "sky",
@@ -101,6 +116,7 @@ export const CURATED_CODEX_PLUGINS: readonly CuratedCodexPlugin[] = [ · 7 unmodified lines
101116 pluginName: "chrome",
102117 name: "Chrome",
103118 slug: "codex_chrome",
119+ publicIcon: "https://learn.chatgpt.com/images/codex/icons/chrome-production-large.png",
104120 server: "node_repl",
105121 surface: "browser",
106122 requires: "chrome-plugin",
packages/plugins/mcp/src/sdk/codex-plugins.test.ts+6−0
@@ -177,6 +177,12 @@ describe("scanCodexPlugins", () => { · 176 unmodified lines
177177 // Each card's steps are about ITS requirement: Chrome needs the browser
178178 // extension, which no other card mentions.
179179 expect(byId.get("codex-chrome")?.setupHint).toContain("browser extension");
180+ // A card that cannot be used still hands over where to go, and shows the
181+ // plugin's published mark rather than a gap.
182+ expect(byId.get("codex-messages")?.setupUrl).toBe("https://openai.com/codex");
183+ expect(byId.get("codex-chrome")?.setupUrl).toContain("learn.chatgpt.com");
184+ expect(byId.get("codex-computer-use")?.fallbackIcon).toContain("computer-use-plugin-icon");
185+ expect(byId.get("codex-messages")?.fallbackIcon).toContain("integrations.sh");
180186 expect(byId.get("codex-messages")?.setupHint).not.toContain("browser extension");
181187 // And the steps are numbered and name the plugin the person clicked.
182188 expect(byId.get("codex-messages")?.setupHint).toContain("1. Install the Codex app");
packages/plugins/mcp/src/sdk/codex-plugins.ts+14−3
@@ -20,7 +20,12 @@ import * as path from "node:path"; · 19 unmodified lines
2020
2121 import { Option, Schema } from "effect";
2222
23−import { CURATED_CODEX_PLUGINS, setupHint, type CuratedCodexPlugin } from "./codex-plugin-presets";
23+import {
24+ CURATED_CODEX_PLUGINS,
25+ setupHint,
26+ setupUrl,
27+ type CuratedCodexPlugin,
28+} from "./codex-plugin-presets";
2429
2530 export interface CodexPluginEntry {
2631 /** Stable card id, e.g. `codex-messages`. */
@@ -46,8 +51,11 @@ export interface CodexPluginEntry { · 19 unmodified lines
4651 readonly surface?: "sky" | "browser";
4752 readonly modulePath?: string;
4853 };
49− /** Shown when `available` is false. */
54+ /** Shown when `available` is false: the ordered steps, and where to go. */
5055 readonly setupHint?: string;
56+ readonly setupUrl?: string;
57+ /** Public image for this plugin, for machines with no local install. */
58+ readonly fallbackIcon?: string;
5159 /** The plugin's own icon from its local install, as a data URI. Read at
5260 * runtime from the user's disk — never shipped with executor. */
5361 readonly icon?: string;
@@ -416,7 +424,10 @@ export const scanCodexPlugins = (options?: { · 362 unmodified lines
416424 ...(entry.surface === undefined ? {} : { surface: entry.surface }),
417425 ...(entry.surface === "browser" ? { modulePath: browserClient } : {}),
418426 },
419− ...(available ? {} : { setupHint: setupHint(entry.requires, entry.name) }),
427+ ...(available
428+ ? {}
429+ : { setupHint: setupHint(entry.requires, entry.name), setupUrl: setupUrl(entry.requires) }),
430+ fallbackIcon: entry.publicIcon ?? "https://integrations.sh/logo/openai.com",
420431 ...display,
421432 };
422433 });
packages/plugins/mcp/src/sdk/presets.ts+1−1
@@ -49,7 +49,7 @@ const codexPluginPresets: readonly McpStdioPreset[] = CURATED_CODEX_PLUGINS.map( · 48 unmodified lines
4949 // without Codex has none to read. Fall back to the provider's mark from the
5050 // same logo service every other preset uses, rather than vendoring OpenAI's
5151 // artwork into this repo.
52− fallbackIcon: "https://integrations.sh/logo/openai.com",
52+ fallbackIcon: plugin.publicIcon ?? "https://integrations.sh/logo/openai.com",
5353 family: "codex",
5454 defaultSlug: plugin.slug,
5555 transport: "stdio",