Skip to content

feat(launcher): add tiles for opencode, Pi, and GitHub Copilot CLI#268

Merged
mbektas merged 2 commits into
plmbr:mainfrom
pjdoland:feat/260-launcher-agent-cli-tiles
May 16, 2026
Merged

feat(launcher): add tiles for opencode, Pi, and GitHub Copilot CLI#268
mbektas merged 2 commits into
plmbr:mainfrom
pjdoland:feat/260-launcher-agent-cli-tiles

Conversation

@pjdoland
Copy link
Copy Markdown
Collaborator

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_path to delegate to 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 one-line wrappers, each with an NBI_<AGENT>_CLI_PATH env override that mirrors the existing Claude pattern. The capabilities response gains opencode_cli_available, pi_cli_available, github_copilot_cli_available flags.

Frontend. New isOpenCodeCliAvailable, isPiCliAvailable, isGitHubCopilotCliAvailable getters on NBIAPI.config. A registerAgentCliLauncher helper takes (commandId, label, caption, icon, cliCommand, isAvailable) and wires the command + tile + configChanged refresh 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). Renamed launchClaudeInTerminallaunchCliInTerminal since three new callers needed it.

Icons. opencode and Pi reuse JL's terminalIcon as a first-phase placeholder, which reads as "this opens a terminal" and matches the actual behavior. GitHub Copilot CLI reuses the existing githubCopilotIcon. 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 jest all 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.which path, and tile registration is JL framework boilerplate unreachable from jest without a real lab application context.

Risks / follow-ups

  • Icons. Generic terminal icon is intentional for first-phase. If the project wants brand icons for opencode / Pi, add them via the same LabIcon + svgstr pattern used by claudeIcon and pass them into the helper.
  • CLI invocation. GitHub Copilot CLI is detected as the copilot binary (the new standalone CLI). If you'd rather support gh copilot (the extension form), point NBI_GITHUB_COPILOT_CLI_PATH at gh and adjust cliCommand to gh copilot. Worth confirming which surface the project wants to anchor on.
  • Session resume. Out of scope; the issue scopes this to "Launching a Jupyter Terminal in the active working directory is good enough for the first phase." A follow-up could mirror the Claude Code session-picker UX once those CLIs grow comparable history surfaces.

Closes #260

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
@mbektas
Copy link
Copy Markdown
Collaborator

mbektas commented May 16, 2026

thanks @pjdoland , could you also add support for codex?

Comment thread src/index.ts
caption: 'Start a Pi session in a Jupyter terminal',
icon: terminalIcon,
cliCommand: 'pi',
isAvailable: () => NBIAPI.config.isPiCliAvailable
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isAvailable doesn't seem to have effect. I have only Claude CLI available but all tiles are visible & enabled.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@pjdoland
Copy link
Copy Markdown
Collaborator Author

Thanks @mbektasgh, addressed both points in d3ca902:

isAvailable had no effect. Confirmed — JL's Launcher widget iterates every entry in its model and renders one card per item, regardless of the backing command's isVisible. isVisible only gates the command palette. So calling launcher.add(...) unconditionally and trusting the predicate to hide the tile was a no-op. (The older Claude tile had the same latent bug; you didn't see it because your claude CLI is on PATH.)

The fix is a new syncLauncherEntry(commandId, options, isAvailable) helper that adds the launcher item when isAvailable() returns true and disposes the entry's IDisposable handle when it flips back. It runs once on activate and again on every NBIAPI.configChanged, so a late capabilities round-trip or a future CLI hot-install makes the right tile appear without a page refresh. Applied to all five tiles, including the Claude one.

Codex CLI added. New 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).

Playwright verification

Built the labextension and stood up JL with this branch on a machine where only claude and codex are on PATH:

claude_cli_available: True
opencode_cli_available: False
pi_cli_available: False
github_copilot_cli_available: False
codex_cli_available: True

The Coding Agent category now renders exactly two tiles: "Claude Code" and "Codex". Confirms both the gating fix and the new Codex registration.

pytest tests/ --ignore=tests/test_claude_client.py -q (699 passed), jlpm tsc --noEmit, jlpm lint:check, jlpm jest (131 passed) all green.

@pjdoland pjdoland added the enhancement New feature or request label May 16, 2026
pjdoland added a commit to pjdoland/notebook-intelligence that referenced this pull request May 16, 2026
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).
pjdoland added a commit to pjdoland/notebook-intelligence that referenced this pull request May 16, 2026
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).
@mbektas mbektas merged commit 1229d11 into plmbr:main May 16, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add other agent CLI tiles to launcher Coding Agent section

2 participants