feat(launcher): add tiles for opencode, Pi, and GitHub Copilot CLI#268
Conversation
Issue plmbr#260: the Coding Agent launcher category only had Claude Code. First-phase scope per the issue body: detect the CLI on PATH, show a tile when present, click opens a Jupyter terminal in the file-browser's current directory and types the CLI command. No session picker. Backend: generalized resolve_claude_cli_path into a generic _resolve_cli_path(name, env_var) backed by a single dict cache. Added resolve_opencode_cli_path, resolve_pi_cli_path, resolve_copilot_cli_path as thin wrappers, each with an NBI_<AGENT>_CLI_PATH env override that matches the existing Claude pattern. Capabilities response gains opencode_cli_available, pi_cli_available, github_copilot_cli_available flags. Frontend: added matching isOpenCodeCliAvailable / isPiCliAvailable / isGitHubCopilotCliAvailable getters on NBIAPI.config. New registerAgentCliLauncher helper takes (label, caption, icon, cliCommand, isAvailable) and wires the command + tile + configChanged refresh in one place. The Claude tile keeps its bespoke session-picker UX and isn't routed through the helper (helper API would have to balloon to cover the one outlier; not worth it). Renamed launchClaudeInTerminal to launchCliInTerminal since three new callers needed it. Icons: opencode and Pi use JL's terminalIcon as a first-phase placeholder (matches "this opens a terminal"). GitHub Copilot CLI reuses the existing githubCopilotIcon. Brand-specific icons can be a follow-up. No new tests: the new util helpers are direct wrappers around the existing memoized shutil.which path, which the rest of the codebase already exercises; tile registration is JL framework boilerplate unreachable from jest without a real lab application context. Closes plmbr#260
| caption: 'Start a Pi session in a Jupyter terminal', | ||
| icon: terminalIcon, | ||
| cliCommand: 'pi', | ||
| isAvailable: () => NBIAPI.config.isPiCliAvailable |
There was a problem hiding this comment.
isAvailable doesn't seem to have effect. I have only Claude CLI available but all tiles are visible & enabled.
There was a problem hiding this comment.
API response looks correct.
"claude_cli_available": true,
"opencode_cli_available": false,
"pi_cli_available": false,
"github_copilot_cli_available": false,
Mehmet pointed out that the tiles added in this PR ignored the
per-tile availability check: with only the Claude CLI installed, the
opencode / Pi / GitHub Copilot CLI tiles still showed up in the
launcher (and the same was true of the older Claude tile, but Mehmet
couldn't see it because his Claude CLI was present).
Root cause: JL's `Launcher` widget iterates every item in its model
and renders one card per entry. The backing command's `isVisible`
only gates the *command palette*, not the launcher tile. So calling
`launcher.add(...)` unconditionally and trusting `isVisible: () =>
config.isAvailable()` to hide the card was a no-op.
Fix: introduce `syncLauncherEntry(commandId, options, isAvailable)`
that adds the launcher item when `isAvailable()` returns true and
disposes the entry's `IDisposable` handle when it flips back. The
sync runs on initial activate and again on every `NBIAPI.configChanged`,
so a capabilities round-trip (or a future CLI-install hot reload) makes
the right tiles appear and disappear without a page refresh. Applied
to the Claude tile too, not just the new ones, so the gating behavior
is consistent.
Also added the OpenAI Codex CLI per Mehmet's other comment on this
PR: a `codex_cli_available` capability flag (mirroring the existing
four), `isCodexCliAvailable` getter, `NBI_CODEX_CLI_PATH` env override,
and a launcher tile using `terminalIcon` as the first-phase placeholder
(brand icon is a follow-up alongside opencode and Pi).
Verified with Playwright against a freshly built labextension. On the
test machine Claude and Codex are on PATH, opencode / Pi / Copilot
are not. After the fix the Coding Agent category renders exactly two
tiles ("Claude Code" and "Codex"), confirming the gating works on
both the new branch (5 tiles registered) and the previously-bugged
Claude tile.
|
Thanks @mbektasgh, addressed both points in
The fix is a new Codex CLI added. New Playwright verificationBuilt the labextension and stood up JL with this branch on a machine where only The Coding Agent category now renders exactly two tiles: "Claude Code" and "Codex". Confirms both the gating fix and the new Codex registration.
|
Backstory: the existing Galata scaffold (`ui-tests/extension.spec.ts`) only smoke-tested that the extension activates and the sidebar opens. Five UX-persona agents brainstormed flows; this commit lands a synthesis of the highest-signal ones across four new spec files. The suite stays **developer-triggered**: it's not wired into `.github/workflows/build.yml` because each run boots a real JL process and Chromium. The intent is to reach for `cd ui-tests && jlpm test` before merging PRs that touch one of the covered surfaces. What landed: * `chat-sidebar.spec.ts` (6 tests) — footer icons + labels, gear-icon title, prefix popover via typed `/`, slash-button toggle, workspace file picker open + close, and the plmbr#262 regression (Escape from the picker search input). * `notebook-toolbar.spec.ts` (5 tests) — toolbar button renders, popover structure, the plmbr#231 regression (textarea focus on open), submit gating, Escape + outside-click dismissal. * `cell-output.spec.ts` (3 tests) — hover toolbar renders Explain + Ask on clean cells, Troubleshoot only on errored cells, click activates the hovered cell. * `launcher.spec.ts` (1 test, currently `.fixme`) — pins the plmbr#260/plmbr#268 regression: tiles must add/dispose dynamically when capabilities flip. Marked `.fixme` until PR plmbr#268 merges since main still has the bug; flipping to `test()` once the fix lands turns this into the regression guard the PR description promised. * `helpers.ts` — minimal shared fixtures (`openChatSidebar`, `readNbiConfig`) so each spec stays tight. Harness fixes the scaffold needed to actually run: * `playwright.config.ts`: Galata 5 doesn't re-export `defineConfig`; import from `@playwright/test` directly. Removed the `appPath` use option that didn't typecheck without Galata's extended config. * `jupyter_server_test_config.py`: call `configure_jupyter_server(c)` from `jupyterlab.galata` so Galata's helper hooks land. Without it the harness raised "Failed to activate galata extension" on first navigation. * `package.json`: new `test:list` script for discovering what's available without running anything. Suite runs in ~30s on a warm cache; 16/17 pass against current main, 1 intentionally skipped (the launcher fixme).
Backstory: the existing Galata scaffold (`ui-tests/extension.spec.ts`) only smoke-tested that the extension activates and the sidebar opens. Five UX-persona agents brainstormed flows; this commit lands a synthesis of the highest-signal ones across four new spec files. The suite stays **developer-triggered**: it's not wired into `.github/workflows/build.yml` because each run boots a real JL process and Chromium. The intent is to reach for `cd ui-tests && jlpm test` before merging PRs that touch one of the covered surfaces. What landed: * `chat-sidebar.spec.ts` (6 tests) — footer icons + labels, gear-icon title, prefix popover via typed `/`, slash-button toggle, workspace file picker open + close, and the plmbr#262 regression (Escape from the picker search input). * `notebook-toolbar.spec.ts` (5 tests) — toolbar button renders, popover structure, the plmbr#231 regression (textarea focus on open), submit gating, Escape + outside-click dismissal. * `cell-output.spec.ts` (3 tests) — hover toolbar renders Explain + Ask on clean cells, Troubleshoot only on errored cells, click activates the hovered cell. * `launcher.spec.ts` (1 test, currently `.fixme`) — pins the plmbr#260/plmbr#268 regression: tiles must add/dispose dynamically when capabilities flip. Marked `.fixme` until PR plmbr#268 merges since main still has the bug; flipping to `test()` once the fix lands turns this into the regression guard the PR description promised. * `helpers.ts` — minimal shared fixtures (`openChatSidebar`, `readNbiConfig`) so each spec stays tight. Harness fixes the scaffold needed to actually run: * `playwright.config.ts`: Galata 5 doesn't re-export `defineConfig`; import from `@playwright/test` directly. Removed the `appPath` use option that didn't typecheck without Galata's extended config. * `jupyter_server_test_config.py`: call `configure_jupyter_server(c)` from `jupyterlab.galata` so Galata's helper hooks land. Without it the harness raised "Failed to activate galata extension" on first navigation. * `package.json`: new `test:list` script for discovering what's available without running anything. Suite runs in ~30s on a warm cache; 16/17 pass against current main, 1 intentionally skipped (the launcher fixme).
Summary
The Coding Agent launcher category previously had a single tile (Claude Code). This PR adds three more, matching the first-phase scope from the issue: detect the binary on PATH, show a tile when present, click opens a Jupyter terminal in the file-browser's current directory and runs the CLI. No session picker.
Solution
Backend. Refactored
resolve_claude_cli_pathto delegate to a generic_resolve_cli_path(name, env_var)backed by a single dict cache. Addedresolve_opencode_cli_path,resolve_pi_cli_path,resolve_copilot_cli_pathas one-line wrappers, each with anNBI_<AGENT>_CLI_PATHenv override that mirrors the existing Claude pattern. The capabilities response gainsopencode_cli_available,pi_cli_available,github_copilot_cli_availableflags.Frontend. New
isOpenCodeCliAvailable,isPiCliAvailable,isGitHubCopilotCliAvailablegetters onNBIAPI.config. AregisterAgentCliLauncherhelper takes(commandId, label, caption, icon, cliCommand, isAvailable)and wires the command + tile +configChangedrefresh in one place. The Claude Code tile keeps its bespoke session-picker UX and isn't routed through the helper (folding it in would balloon the helper's API to handle a single outlier). RenamedlaunchClaudeInTerminal→launchCliInTerminalsince three new callers needed it.Icons. opencode and Pi reuse JL's
terminalIconas a first-phase placeholder, which reads as "this opens a terminal" and matches the actual behavior. GitHub Copilot CLI reuses the existinggithubCopilotIcon. Brand-specific icons can be a follow-up if licensing allows.Testing
pytest tests/ --ignore=tests/test_claude_client.py -q(699 passed).jlpm tsc --noEmit,jlpm lint:check,jlpm jestall green. Manual: confirmed the existing Claude tile still works after the helper rename and that the new tiles' visibility tracks the binary presence on PATH.No new automated tests. The util helpers are direct wrappers around the already-exercised memoized
shutil.whichpath, and tile registration is JL framework boilerplate unreachable from jest without a real lab application context.Risks / follow-ups
LabIcon+svgstrpattern used byclaudeIconand pass them into the helper.copilotbinary (the new standalone CLI). If you'd rather supportgh copilot(the extension form), pointNBI_GITHUB_COPILOT_CLI_PATHatghand adjustcliCommandtogh copilot. Worth confirming which surface the project wants to anchor on.Closes #260