(options?: {
readonly codexHome?: string;
/** Explicit `codex` CLI path (tests); default resolves PATH + fallbacks. */
readonly codexCli?: string;
})
| 387 | * as scanned and spawned directly. |
| 388 | */ |
| 389 | export const scanCodexPlugins = (options?: { |
| 390 | readonly codexHome?: string; |
| 391 | /** Explicit `codex` CLI path (tests); default resolves PATH + fallbacks. */ |
| 392 | readonly codexCli?: string; |
| 393 | }): readonly CodexPluginEntry[] => { |
| 394 | const codexHome = |
| 395 | options?.codexHome ?? process.env["CODEX_HOME"] ?? path.join(os.homedir(), ".codex"); |
| 396 | |
| 397 | const codexCli = resolveCodexCli(options?.codexCli); |
| 398 | const computerUseApp = isExecutableFile(clientBinaryPath(codexHome)); |
| 399 | const browserClient = browserClientPath(codexHome); |
| 400 | const chromePlugin = isReadableFile(browserClient); |
| 401 | |
| 402 | /** Each curated card states what it needs; the `codex` CLI is required by |
| 403 | * all of them because every one is reached through `codex app-server`. */ |
| 404 | const requirementMet: Record<CuratedCodexPlugin["requires"], boolean> = { |
| 405 | "computer-use-app": computerUseApp, |
| 406 | "chrome-plugin": chromePlugin, |
| 407 | codex: true, |
| 408 | }; |
| 409 | |
| 410 | const curated: readonly CodexPluginEntry[] = CURATED_CODEX_PLUGINS.map((entry) => { |
| 411 | const display = curatedDisplayMetadata(codexHome, entry.pluginName); |
| 412 | const available = codexCli !== undefined && requirementMet[entry.requires]; |
| 413 | return { |
| 414 | id: entry.id, |
| 415 | name: entry.name, |
| 416 | summary: entry.summary, |
| 417 | available, |
| 418 | slug: entry.slug, |
| 419 | source: "curated" as const, |
| 420 | command: codexCli ?? "codex", |
| 421 | args: ["app-server"], |
| 422 | env: { CODEX_HOME: codexHome }, |
| 423 | appServer: { |
| 424 | presetId: entry.id, |
| 425 | server: entry.server, |
| 426 | ...(entry.surface === undefined ? {} : { surface: entry.surface }), |
| 427 | ...(entry.surface === "browser" ? { modulePath: browserClient } : {}), |
| 428 | }, |
| 429 | ...(available |
| 430 | ? {} |
| 431 | : { setupHint: setupHint(entry.requires, entry.name), setupUrl: setupUrl(entry.requires) }), |
| 432 | fallbackIcon: entry.publicIcon ?? "https://integrations.sh/logo/openai.com", |
| 433 | ...display, |
| 434 | }; |
| 435 | }); |
| 436 | |
| 437 | const cacheDir = path.join(codexHome, "plugins", "cache"); |
| 438 | const scanned = listDirs(cacheDir).flatMap((sourceName) => { |
| 439 | const sourceDir = path.join(cacheDir, sourceName); |
| 440 | return listDirs(sourceDir) |
| 441 | .filter((pluginName) => !CURATED_PLUGIN_NAMES.has(pluginName)) |
| 442 | .flatMap((pluginName) => |
| 443 | scanCachedPlugin(codexHome, path.join(sourceDir, pluginName), pluginName), |
| 444 | ); |
| 445 | }); |
| 446 |
no test coverage detected