Skip to content

feat: auto-discover installed AI CLIs in command palette#89

Open
n00ki wants to merge 2 commits intoerictli:mainfrom
n00ki:feat/ai-agent-discovery
Open

feat: auto-discover installed AI CLIs in command palette#89
n00ki wants to merge 2 commits intoerictli:mainfrom
n00ki:feat/ai-agent-discovery

Conversation

@n00ki
Copy link

@n00ki n00ki commented Mar 5, 2026

  • Add runtime discovery for installed AI CLIs (Claude, Codex, Ollama).
  • Show AI edit commands in the command palette only for providers that are actually available.
  • Keep provider ordering stable and preserve existing modal-level CLI validation.

Changes

  • Added provider discovery helpers in src/services/ai.ts:
    • AI_PROVIDER_ORDER
    • getAvailableAiProviders() (parallel checks, graceful fallback on errors)
  • Updated src/components/command-palette/CommandPalette.tsx:
    • Discover available providers when the palette opens for a selected note
    • Replace hardcoded AI command entries with dynamic commands based on discovered providers
    • Keep existing labels/icons/order conventions
  • Removed unused theme reference in command palette dependencies

Why

  • Reduces command palette noise.
  • Prevents presenting AI actions that cannot run on the user’s machine.
  • Keeps behavior adaptive without adding settings complexity.

Manual checks:

  • No AI CLIs installed -> no AI edit commands shown
  • Only one provider installed -> only that provider command is shown
  • Multiple providers installed -> only installed providers appear, in expected order
  • Existing modal checks still handle missing CLI gracefully if environment changes

Summary by CodeRabbit

  • New Features
    • Command palette now dynamically loads and displays available AI providers instead of showing fixed options.
    • Detects provider availability when the palette opens and updates the AI options shown, improving support for multiple providers (Codex, Ollama, Claude variants).

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds dynamic AI provider discovery and integration: a new ai service exports provider order and a helper to probe availability; CommandPalette now accepts an availableAiProviders prop, fetches providers when opened, and replaces hardcoded AI commands with dynamically generated entries.

Changes

Cohort / File(s) Summary
AI Provider Service
src/services/ai.ts
Added AI_PROVIDER_ORDER constant and getAvailableAiProviders() which concurrently checks provider availability and returns an ordered, filtered list.
CommandPalette AI Integration
src/components/command-palette/CommandPalette.tsx
CommandPalette now accepts availableAiProviders?: AiProvider[]; fetches providers on open; removes hardcoded AI commands in favor of dynamic aiCommands; adjusted theme destructuring and effect/memo dependencies.

Sequence Diagram

sequenceDiagram
    participant User
    participant CommandPalette
    participant aiService
    participant ProviderCheckers

    User->>CommandPalette: Open/trigger palette
    Note over CommandPalette: useEffect (currentNote present)
    CommandPalette->>aiService: getAvailableAiProviders()
    aiService->>ProviderCheckers: Concurrently probe (claude, codex, ollama)
    ProviderCheckers-->>aiService: Availability results
    aiService-->>CommandPalette: availableAiProviders[]
    CommandPalette->>CommandPalette: Build aiCommands from providers
    CommandPalette-->>User: Display commands including AI variants
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • erictli

Poem

🐰 I hopped through code at break of day,

Claudes and Codexes led the way,
I sniffed for Ollama in a row,
Built commands that now can grow,
A joyful palette — watch them play! 🎩✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main feature: auto-discovery of installed AI CLIs to dynamically show AI commands in the command palette.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/services/ai.ts (1)

49-57: Add a timeout fallback so one hung checker doesn’t block all provider discovery.

If a single checker never resolves, discovery can stall indefinitely and hide all AI commands.

Proposed diff
+const PROVIDER_CHECK_TIMEOUT_MS = 1500;
+
+async function checkProviderAvailability(
+  provider: AiProvider,
+): Promise<AiProvider | null> {
+  try {
+    const installed = await Promise.race<boolean>([
+      providerCheckers[provider](),
+      new Promise<boolean>((resolve) =>
+        setTimeout(() => resolve(false), PROVIDER_CHECK_TIMEOUT_MS),
+      ),
+    ]);
+    return installed ? provider : null;
+  } catch {
+    return null;
+  }
+}
+
 export async function getAvailableAiProviders(): Promise<AiProvider[]> {
-  const checks = await Promise.all(
-    AI_PROVIDER_ORDER.map(async (provider) => {
-      try {
-        const installed = await providerCheckers[provider]();
-        return installed ? provider : null;
-      } catch {
-        return null;
-      }
-    }),
-  );
+  const checks = await Promise.all(
+    AI_PROVIDER_ORDER.map((provider) => checkProviderAvailability(provider)),
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/ai.ts` around lines 49 - 57, The provider discovery loop using
AI_PROVIDER_ORDER and providerCheckers can hang if a checker never resolves;
wrap each call to providerCheckers[provider]() in a timeout fallback (e.g.,
Promise.race between the checker promise and a timeout that rejects or resolves
false after a short interval like 2–5s) inside the async mapping function so a
stuck checker returns null/false instead of blocking Promise.all; ensure errors
from the checker or timeout are caught and mapped to null so the existing return
installed ? provider : null logic remains valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/command-palette/CommandPalette.tsx`:
- Around line 129-133: The catch block for provider discovery currently swallows
errors; change the .catch to accept the error (e.g. .catch((err) => { ... }))
and surface a friendly, non-blocking message while still clearing providers:
call setAvailableAiProviders([]) when active, log the error (console.error(err))
and trigger a user-facing notification/state update (for example call an
existing showToast or introduce setAiProviderDiscoveryError state and set a
message like "Could not discover AI providers — some commands may be
unavailable.") so the UI informs the user of the discovery failure.

---

Nitpick comments:
In `@src/services/ai.ts`:
- Around line 49-57: The provider discovery loop using AI_PROVIDER_ORDER and
providerCheckers can hang if a checker never resolves; wrap each call to
providerCheckers[provider]() in a timeout fallback (e.g., Promise.race between
the checker promise and a timeout that rejects or resolves false after a short
interval like 2–5s) inside the async mapping function so a stuck checker returns
null/false instead of blocking Promise.all; ensure errors from the checker or
timeout are caught and mapped to null so the existing return installed ?
provider : null logic remains valid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bad62a2-9df9-4103-95c9-c3dd4eadc2b3

📥 Commits

Reviewing files that changed from the base of the PR and between 3331c1e and 1055009.

📒 Files selected for processing (2)
  • src/components/command-palette/CommandPalette.tsx
  • src/services/ai.ts

Comment on lines +129 to +133
.catch(() => {
if (active) {
setAvailableAiProviders([]);
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don’t fail provider discovery silently.

When discovery throws, users currently just see no AI commands, which is indistinguishable from “nothing installed.” Add a friendly error message for this non-blocking async failure path.

Proposed diff
-      .catch(() => {
+      .catch((error) => {
         if (active) {
           setAvailableAiProviders([]);
+          console.error("Failed to discover AI providers:", error);
+          toast.error("Unable to load AI commands right now");
         }
       });
As per coding guidelines: "Implement proper error handling with user-friendly messages for all non-blocking async operations".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.catch(() => {
if (active) {
setAvailableAiProviders([]);
}
});
.catch((error) => {
if (active) {
setAvailableAiProviders([]);
console.error("Failed to discover AI providers:", error);
toast.error("Unable to load AI commands right now");
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/CommandPalette.tsx` around lines 129 - 133,
The catch block for provider discovery currently swallows errors; change the
.catch to accept the error (e.g. .catch((err) => { ... })) and surface a
friendly, non-blocking message while still clearing providers: call
setAvailableAiProviders([]) when active, log the error (console.error(err)) and
trigger a user-facing notification/state update (for example call an existing
showToast or introduce setAiProviderDiscoveryError state and set a message like
"Could not discover AI providers — some commands may be unavailable.") so the UI
informs the user of the discovery failure.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/components/command-palette/CommandPalette.tsx (1)

122-133: ⚠️ Potential issue | 🟡 Minor

Discovery failures are still silent to users in this flow.

getAvailableAiProviders() currently resolves with [] on checker errors, so this .catch(...) path won’t reliably surface failures; users still can’t distinguish “none installed” vs “discovery failed.” Please surface a user-friendly non-blocking message for discovery failures (either by propagating failure info from the service or by returning diagnostics and handling them here).

As per coding guidelines: "Implement proper error handling with user-friendly messages for all non-blocking async operations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/CommandPalette.tsx` around lines 122 - 133,
The discovery flow currently swallows errors so users can't tell if there are
truly no providers or a discovery failure; update the CommandPalette component
to surface a non-blocking user message on discovery failures by adding a small
UI state (e.g., discoveryError via useState) and setting it in the error path of
aiService.getAvailableAiProviders(), instead of only console.error; update
rendering to show a subtle message when discoveryError is set while still
calling setAvailableAiProviders([]) to preserve behavior, and optionally change
aiService.getAvailableAiProviders() to return diagnostics or throw so this
component can display the diagnostic text.
🧹 Nitpick comments (1)
src/components/command-palette/CommandPalette.tsx (1)

167-190: Make provider-to-command mapping exhaustive-safe.

The if/else fallback currently defaults unknown providers to Claude metadata. Prefer an exhaustive mapping (e.g., Record<AiProvider, ...> or switch + never) so new providers fail at compile time instead of silently mislabeling commands.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/command-palette/CommandPalette.tsx` around lines 167 - 190,
Replace the ad-hoc if/return chain that maps provider to command with an
exhaustive mapping so new AiProvider values fail at compile time: either (A) use
a typed Record<AiProvider, CommandMeta> keyed by the exact AiProvider union and
return record[provider], or (B) use a switch(provider) with explicit cases for
"codex", "ollama", and "claude" that return the same objects (id
"ai-edit-codex"/"ai-edit-ollama"/"ai-edit-claude", label strings, icons
CodexIcon/OllamaIcon/ClaudeIcon and action), and add a default branch that
asserts exhaustiveness (const _exhaustive: never = provider; throw new
Error(`Unhandled provider ${provider}`)); ensure the AiProvider type is used for
the provider variable so the compiler will complain when a new provider is
added.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/command-palette/CommandPalette.tsx`:
- Around line 122-133: The discovery flow currently swallows errors so users
can't tell if there are truly no providers or a discovery failure; update the
CommandPalette component to surface a non-blocking user message on discovery
failures by adding a small UI state (e.g., discoveryError via useState) and
setting it in the error path of aiService.getAvailableAiProviders(), instead of
only console.error; update rendering to show a subtle message when
discoveryError is set while still calling setAvailableAiProviders([]) to
preserve behavior, and optionally change aiService.getAvailableAiProviders() to
return diagnostics or throw so this component can display the diagnostic text.

---

Nitpick comments:
In `@src/components/command-palette/CommandPalette.tsx`:
- Around line 167-190: Replace the ad-hoc if/return chain that maps provider to
command with an exhaustive mapping so new AiProvider values fail at compile
time: either (A) use a typed Record<AiProvider, CommandMeta> keyed by the exact
AiProvider union and return record[provider], or (B) use a switch(provider) with
explicit cases for "codex", "ollama", and "claude" that return the same objects
(id "ai-edit-codex"/"ai-edit-ollama"/"ai-edit-claude", label strings, icons
CodexIcon/OllamaIcon/ClaudeIcon and action), and add a default branch that
asserts exhaustiveness (const _exhaustive: never = provider; throw new
Error(`Unhandled provider ${provider}`)); ensure the AiProvider type is used for
the provider variable so the compiler will complain when a new provider is
added.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c1ec745e-ff47-4bc4-bd3b-f067ee3d5f6d

📥 Commits

Reviewing files that changed from the base of the PR and between 1055009 and 6f1e702.

📒 Files selected for processing (1)
  • src/components/command-palette/CommandPalette.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant