11 KiB
Tech Spec: Gemini CLI Plugin Install & Update Flow
See specs/APP-4067/PRODUCT.md for the product spec.
1. Problem
plugin_manager_for(CLIAgent::Gemini) returns None (plugin_manager/mod.rs:189), and create_handler in the listener also returns None for CLIAgent::Gemini (listener/mod.rs:53). This means Gemini sessions get no install/update chip and their structured OSC 777 notifications are silently dropped.
Gemini CLI has first-class gemini extensions install/update commands, so the implementation follows the Claude Code pattern: auto-install, auto-update, and filesystem-based version detection.
2. Relevant Code
plugin_manager/mod.rs—CliAgentPluginManagertrait,plugin_manager_for()/plugin_manager_for_with_shell()factory,compare_versionsplugin_manager/claude.rs— reference auto-install implementation with filesystem detection andLocalCommandExecutorplugin_manager/opencode.rs— reference manual-only implementation (simpler, no filesystem checks)listener/mod.rs:41-61—is_agent_supported()andcreate_handler()— must addCLIAgent::Geminiagent_input_footer/mod.rs (793-873)—plugin_chip_kind()determines which chip to showagent_input_footer/mod.rs (877-893)—should_use_manual_mode()agent_input_footer/mod.rs (925+)—handle_plugin_operation()shared async handlercrates/warp_features/src/lib.rs:760-769— existingHOANotifications,OpenCodeNotifications,CodexNotificationsflags
3. Current State
The plugin manager infrastructure is fully generalized across Claude (auto-install + filesystem detection), OpenCode (manual-only), and Codex (manual-only, no update support). Adding a new auto-install agent requires:
- A new struct implementing
CliAgentPluginManagerwithcan_auto_install() == true. is_installed()andneeds_update()filesystem checks.install()andupdate()methods that shell out CLI commands viaLocalCommandExecutor.- Wiring into
plugin_manager_for_with_shell(). - Adding the agent to
create_handler()andis_agent_supported()in the listener. - A feature flag to gate the rollout.
4. Proposed Changes
4a. Feature Flag (crates/warp_features/src/lib.rs)
Add GeminiNotifications to the FeatureFlag enum after CodexNotifications:
/// Enables the install/update chip for the Gemini CLI Warp extension.
/// Requires HOANotifications to also be enabled.
GeminiNotifications,
Add to DOGFOOD_FLAGS.
4b. Gemini Plugin Manager (plugin_manager/gemini.rs)
New file. Follows the Claude Code pattern closely.
Constants:
EXTENSION_REPO: &str = "https://github.com/warpdotdev/gemini-cli-warp"— install source.EXTENSION_NAME: &str = "gemini-warp"— the installed directory name under~/.gemini/extensions/. Used forgemini extensions update gemini-warp.MINIMUM_PLUGIN_VERSION: &str = "1.0.0"— matches current plugin version.
Struct:
pub(super) struct GeminiPluginManager {
executor: LocalCommandExecutor,
path_env_var: Option<String>,
}
Same new(shell_path, shell_type, path_env_var) constructor pattern as ClaudeCodePluginManager.
Filesystem detection:
gemini_extensions_dir() — returns ~/.gemini/extensions (no env var override like Claude's CLAUDE_HOME, Gemini CLI doesn't support one).
is_installed() — checks if ~/.gemini/extensions/gemini-warp/gemini-extension.json exists and parses as valid JSON. fs::read_to_string follows symlinks, so gemini extensions link is handled.
installed_version() — reads ~/.gemini/extensions/gemini-warp/gemini-extension.json, parses the version field. The JSON structure is flat: {"name": "warp", "version": "1.0.0", ...}.
needs_update() — calls installed_version(), compares against MINIMUM_PLUGIN_VERSION using compare_versions. Returns true if version is lower, or if installed but no version field.
Auto-install/update:
install():
gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent
--consent skips the interactive security confirmation prompt.
update():
gemini extensions update gemini-warp
Both delegate to the shared run_cli_command_logged() helper in mod.rs via a thin run_logged() wrapper method. The shared helper takes a CLI name, args, executor, and env vars, runs the command via LocalCommandExecutor::execute_local_command_in_login_shell, and returns Result<(), PluginInstallError>.
Instructions (fallback):
Install instructions: single step — gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent.
Update instructions: single step — gemini extensions update gemini-warp. Post-install note: "Restart Gemini CLI to activate the update."
4c. Factory Registration (plugin_manager/mod.rs)
Add pub(crate) mod gemini; to module declarations.
Add match arm in plugin_manager_for_with_shell() before the wildcard:
CLIAgent::Gemini
if FeatureFlag::GeminiNotifications.is_enabled()
&& FeatureFlag::HOANotifications.is_enabled() =>
{
Some(Box::new(GeminiPluginManager::new(
shell_path,
shell_type,
path_env_var,
)))
}
4d. Listener Registration (listener/mod.rs)
is_agent_supported() — add CLIAgent::Gemini:
matches!(
agent,
CLIAgent::Claude | CLIAgent::OpenCode | CLIAgent::Codex | CLIAgent::Gemini
)
create_handler() — add CLIAgent::Gemini alongside Claude/OpenCode (same structured JSON protocol, uses DefaultSessionListener):
CLIAgent::Claude | CLIAgent::OpenCode | CLIAgent::Gemini => {
Some(Box::new(DefaultSessionListener))
}
No feature flag checks needed here — the call sites (register_cli_agent_listener in terminal/view.rs) already gate on HOANotifications.
4e. Chip Behavior
Since can_auto_install() == true and is_installed() does a filesystem check, the chip behavior matches Claude Code exactly:
- Installed + up to date → no chip (no flicker)
- Installed + outdated → update chip immediately
- Not installed → install chip immediately
- The debounce guard (
plugin_chip_ready) only applies to!manager.can_auto_install()agents, so Gemini skips it
5. End-to-End Flow
Install
- User starts Gemini CLI in Warp.
CLIAgent::Geminidetected, session created. plugin_manager_for(Gemini)returnsSome(GeminiPluginManager).- Footer:
plugin_chip_kind()→ no listener,is_installed()checks~/.gemini/extensions/gemini-warp/gemini-extension.json→ not found →PluginChipKind::Install. should_use_manual_mode()→false(auto-install, local, no prior failure).- User clicks →
handle_plugin_operation()→gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent. - Success toast → user restarts Gemini → plugin hooks fire,
SessionStartreportsplugin_version: "1.0.0"→ chip disappears.
Update
MINIMUM_PLUGIN_VERSIONbumped to"1.1.0".- On session start,
is_installed()→true,needs_update()→true(on-disk version"1.0.0"<"1.1.0") →PluginChipKind::Update. - User clicks →
gemini extensions update gemini-warp→ success → restart → version"1.1.0"→ chip gone.
Auto-install failure fallback
gemininot on PATH →install()returnsErr.- Error toast +
has_plugin_auto_failedset. - Next render:
should_use_manual_mode()→true. Chip becomes instructions button. - User clicks → split pane with manual instructions.
6. Risks and Mitigations
Risk: gemini not on PATH in the login shell. npm install -g with nvm may not be on PATH in non-interactive login shells. Mitigation: path_env_var from the terminal session captures the user's interactive PATH. Manual instructions fallback also covers this.
Risk: --consent not available in older Gemini CLI versions. Mitigation: Extensions framework (and --consent) shipped in v0.4.x. Users on older versions wouldn't have extensions support at all, so this is a non-issue.
Risk: ~/.gemini directory doesn't exist yet. A fresh Gemini CLI install may not have created the extensions directory. Mitigation: is_installed() returns false when the path doesn't exist, which is correct — the install chip shows.
7. Testing and Validation
Unit tests (plugin_manager/gemini_tests.rs)
can_auto_install_is_trueminimum_version— returns"1.0.0"install_instructions_has_stepsupdate_instructions_has_stepsinstalled_when_extension_present— write validgemini-extension.jsonto temp dir, verifytruenot_installed_when_extension_missing— empty temp dir, verifyfalsenot_installed_when_json_invalid— invalid JSON in manifest, verifyfalseinstalled_version_returns_version_when_present— verify version string extractioninstalled_version_returns_none_when_no_version_field— verifyNonewhen version field missinginstalled_version_returns_none_when_file_missing— verifyNonewhen no manifestneeds_update_logic_true_when_version_outdated— version"0.9.0"against minimum"1.0.0", verify update neededneeds_update_logic_false_when_version_current— version"1.0.0"against minimum"1.0.0", verify no update needed
Unit tests (plugin_manager/mod_tests.rs)
returns_manager_for_gemini— requires bothGeminiNotificationsandHOANotificationsenabled- Remove
CLIAgent::Geminifromreturns_none_for_unsupported_agents
Manual testing
- Start Gemini CLI session → install chip appears (not installed on disk)
- Click install → auto-install succeeds → toast → restart → notifications work
- Bump minimum version → update chip appears (before plugin connects)
- Click update → auto-update succeeds → toast
- Disconnect
geminifrom PATH → auto fails → manual instructions pane works - Feature flags off → no chip, no listener
8. Follow-Ups
- Publish
warpdotdev/gemini-warpto GitHub — must happen before shipping to external users. - Platform plugin / Oz harness support — future work.
- Promote
GeminiNotificationsfrom dogfood — after validation.
9. Files Changed
- New:
plugin_manager/gemini.rs—GeminiPluginManager, filesystem detection, install/update viaLocalCommandExecutor, instructions - New:
plugin_manager/gemini_tests.rs— unit tests - Modified:
plugin_manager/mod.rs— addpub(crate) mod gemini;, wireCLIAgent::Geminiinto factory, addinstall_success_message/update_success_messagedefault trait methods, extract sharedrun_cli_command_loggedandpath_env_from_varhelpers - Modified:
plugin_manager/claude.rs— overrideinstall_success_messageandupdate_success_messagewith Claude-specific reload-plugins messages, refactor to use sharedrun_cli_command_loggedhelper - Modified:
plugin_manager/mod_tests.rs— add Gemini factory test, update unsupported agents test - Modified:
listener/mod.rs— addCLIAgent::Geminitois_agent_supported()andcreate_handler() - Modified:
agent_input_footer/mod.rs—handle_install_plugin/handle_update_pluginnow query the plugin manager for per-agent success messages - Modified:
crates/warp_features/src/lib.rs— addGeminiNotificationsvariant +DOGFOOD_FLAGS