Skip to content

feat: add ElevenLabs Scribe support, fix noise-injected transcriptions, improve retry UX - #167

Merged
yashdev9274 merged 2 commits into
mainfrom
supercode-cli
Jul 3, 2026
Merged

feat: add ElevenLabs Scribe support, fix noise-injected transcriptions, improve retry UX#167
yashdev9274 merged 2 commits into
mainfrom
supercode-cli

Conversation

@yashdev9274

@yashdev9274 yashdev9274 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Description

  • Add ElevenLabs STT provider (scribe_v1) with tag_audio_events: false to prevent sound descriptions like "(Clicking noise)" from appearing in text
  • Keep Groq as fallback provider via STT_PROVIDER env var
  • Add sanitizeTranscription() regex as provider-agnostic fallback for remaining parenthetical noise labels
  • Return "" instead of throwing on no-speech, enabling clean retry flow
  • Show persistent "No speech detected — press voice key to retry" message (no longer auto-clears after 3s)
  • Dismiss footer message on any typed character
  • Switch primary voice key from F2 to Ctrl+V (more ergonomic), keep F2 as fallback
  • Remove console.error stack traces from voice failure paths
  • Clean up abortCapture → stopCapture naming, remove unused user-abort flag

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

  • bun test passes
  • bun run typecheck passes
  • bun run lint passes (if applicable)

Checklist:

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Summary by CodeRabbit

  • New Features

    • Added voice input support in the CLI with a new shortcut, toggle recording behavior, and improved transcription handling.
    • Added support for multiple speech-to-text providers and updated configuration options for voice setup.
    • Introduced shared secret-loading support for apps using Infisical.
  • Bug Fixes

    • Improved voice capture status messages, cleanup, and error handling.
    • Sanitized transcribed text to remove unwanted descriptions before inserting it into chat.
  • Documentation

    • Updated setup docs with voice input requirements, shortcuts, and required environment variables.
    • Expanded environment variable examples and monorepo documentation.

…s, improve retry UX

- Add ElevenLabs STT provider (scribe_v1) with tag_audio_events: false to
  prevent sound descriptions like "(Clicking noise)" from appearing in text
- Keep Groq as fallback provider via STT_PROVIDER env var
- Add sanitizeTranscription() regex as provider-agnostic fallback for
  remaining parenthetical noise labels
- Return "" instead of throwing on no-speech, enabling clean retry flow
- Show persistent "No speech detected — press voice key to retry" message
  (no longer auto-clears after 3s)
- Dismiss footer message on any typed character
- Switch primary voice key from F2 to Ctrl+V (more ergonomic), keep F2 as
  fallback
- Remove console.error stack traces from voice failure paths
- Clean up abortCapture → stopCapture naming, remove unused user-abort flag

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
supercli Ready Ready Preview, Comment Jul 3, 2026 6:00pm
supercli-client Ready Ready Preview, Comment Jul 3, 2026 6:00pm
supercli-docs Ready Ready Preview, Comment Jul 3, 2026 6:00pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yashdev9274, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ab0b4b4-543e-4183-a278-729003f66f4a

📥 Commits

Reviewing files that changed from the base of the PR and between deedd4c and 31e4cb6.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • packages/secrets/package.json
  • packages/secrets/src/client.ts
  • packages/secrets/src/index.ts

Walkthrough

This PR adds multi-provider voice speech-to-text support (ElevenLabs/Groq) to the CLI chat with a new Ctrl+V toggle, refactors capture stop logic, and introduces a new packages/secrets Infisical wrapper package for centralized secret resolution and loading, along with associated documentation and environment variable updates.

Changes

Voice Capture and STT Provider Support

Layer / File(s) Summary
STT provider selection and transcription
apps/supercode-cli/server/src/voice/speech.ts, apps/supercode-cli/server/src/voice/__tests__/speech.test.ts
Adds getSttProvider(), getFfmpegPath(), stopCapture() replacing abortCapture(), provider-aware canVoiceCapture(), a new transcribeElevenLabs() implementation, sanitized transcription output, and unit tests covering provider selection/failure reasons.
Chat CLI keybinding and UI wiring
apps/supercode-cli/server/src/cli/ai/chat/chat.ts
Switches voice trigger to Ctrl+V (with F2 fallback), toggles recording start/stop, stops capture on Enter/Escape, updates footer status messaging, and updates the startup hint.
Voice docs, env template, and version bump
apps/supercode-cli/server/README.md, AGENTS.md, apps/web/.env.example, apps/supercode-cli/server/package.json
Documents the Voice Input feature and STT env variables, rewrites .env.example to include STT/Infisical variables and trims unrelated ones, and bumps the server package version.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Infisical Secrets Package

Layer / File(s) Summary
Token resolution and client singleton
packages/secrets/src/resolve-token.ts, packages/secrets/src/client.ts, packages/secrets/src/errors.ts
Resolves an Infisical token from env vars or local session config, throwing MissingInfisicalTokenError if none found, and implements a lazily-initialized InfisicalClient singleton with getClient/resetClient.
applySecrets, loadSecrets, and package exports
packages/secrets/src/apply.ts, packages/secrets/src/index.ts, packages/secrets/index.ts, packages/secrets/package.json, packages/secrets/tsconfig.json
Adds applySecrets (schema validation + process.env writes), loadSecrets (fetches and applies secrets from Infisical), and wires up the package's public exports, manifest, and TypeScript config.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ChatCLI
  participant Speech as speech.ts
  participant STTProvider as ElevenLabs/Groq

  User->>ChatCLI: Press Ctrl+V
  ChatCLI->>Speech: startVoiceCapture()
  Speech->>Speech: canVoiceCapture()
  Speech->>Speech: captureAudio() via ffmpeg
  User->>ChatCLI: Press Ctrl+V / Enter / Escape
  ChatCLI->>Speech: stopCapture()
  Speech->>STTProvider: transcribeAudio(filePath)
  STTProvider-->>Speech: transcription text
  Speech->>Speech: sanitizeTranscription()
  Speech-->>ChatCLI: cleaned text
  ChatCLI-->>User: insert text into input
Loading
sequenceDiagram
  participant Caller
  participant Index as secrets/src/index.ts
  participant Client as client.ts
  participant Infisical as Infisical API
  participant Apply as apply.ts

  Caller->>Index: loadSecrets(options)
  Index->>Client: getClient(app)
  Client->>Client: resolveToken(app)
  Client->>Infisical: create InfisicalClient
  Index->>Infisical: listSecrets(app, env, projectId)
  Infisical-->>Index: secrets list
  Index->>Apply: applySecrets(flat, app, env, schema)
  Apply-->>Caller: process.env populated
Loading

Related Issues: Not specified in the provided information.

Related PRs: Not specified in the provided information.

Suggested labels: feature, documentation, cli

Suggested reviewers: yashdev9274

Poem

A rabbit twitches ears with glee,
Ctrl+V now sets voice free,
ElevenLabs and Groq both sing,
secrets tucked in Infisical's wing.
Hop, transcribe, and clean the text —
🐇 onward to the feature next!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: ElevenLabs Scribe support, transcript noise cleanup, and improved retry behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch supercode-cli

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/supercode-cli/server/src/voice/speech.ts (1)

91-127: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unify the short-recording no-speech path

captureAudio() still rejects recordings under 100 bytes, so the manual-stop/no-speech flow falls into the generic Voice failed branch instead of the intended retry message. Return an empty result there and treat it as no speech in voiceCaptureFlow().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/voice/speech.ts` around lines 91 - 127, The
short-recording path in captureAudio() still rejects when data.length is under
100 bytes, which prevents the manual-stop/no-speech flow from reaching the
intended retry handling. Change that branch to return an empty result instead of
rejecting, then update voiceCaptureFlow() to recognize the empty/no-speech
result and route it through the no speech retry message rather than the generic
Voice failed path.
🧹 Nitpick comments (4)
packages/secrets/src/index.ts (1)

17-17: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Default project ID captured at module-load time.

DEFAULT_PROJECT_ID is resolved once when the module is first imported. If INFISICAL_PROJECT_ID is set later (e.g., by a lazy .env load or another init step before loadSecrets is actually called), this stale value (undefined) will be used for every call.

♻️ Proposed fix: resolve lazily inside `loadSecrets`
-const DEFAULT_PROJECT_ID = process.env.INFISICAL_PROJECT_ID
-
 export async function loadSecrets(options: LoadSecretsOptions): Promise<void> {
-  const { app, env, schema, projectId = DEFAULT_PROJECT_ID } = options
+  const { app, env, schema, projectId = process.env.INFISICAL_PROJECT_ID } = options
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/index.ts` at line 17, The module-level
DEFAULT_PROJECT_ID in the secrets index is captured too early and can stay stale
if INFISICAL_PROJECT_ID is populated later. Move the project ID lookup into
loadSecrets so it is resolved at call time, and use that live value wherever
DEFAULT_PROJECT_ID is currently referenced. Keep the change localized around
loadSecrets and any helper logic that consumes the project ID.
packages/secrets/src/apply.ts (2)

3-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Type-only circular import between apply.ts and index.ts.

apply.ts imports AppId/EnvName from ./index, while index.ts imports applySecrets from ./apply. Type-only imports are erased so this is safe at runtime, but consider hoisting AppId/EnvName into resolve-token.ts or a dedicated types.ts to avoid the circular reference entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/apply.ts` at line 3, The `apply.ts` and `index.ts`
modules currently form a type-only circular reference through `AppId` and
`EnvName`, so move those shared types out of `./index` into `resolve-token.ts`
or a new dedicated `types.ts`. Update `apply.ts` to import the types from the
new location, and adjust any related imports in `index.ts` so `applySecrets` no
longer depends on `index` for type definitions. Keep the runtime behavior
unchanged while removing the circular module dependency.

21-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider surfacing skipped/overridden secrets for observability.

Secrets whose keys already exist in process.env are silently skipped. This can be intentional (let explicit env override secrets), but with no logging, it makes debugging "my secret didn't apply" hard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/apply.ts` around lines 21 - 25, The applySecrets logic
silently skips secrets whose keys already exist in process.env, so add
observability in the apply.ts loop to surface when a secret is ignored because
an env var is already set. Update the existing iteration over
Object.entries(secrets) to log or otherwise record the skipped key in a clear
way, while preserving the current override behavior for process.env and keeping
the applySecrets function as the central place for this decision.
apps/supercode-cli/server/src/voice/__tests__/speech.test.ts (1)

1-84: 📐 Maintainability & Code Quality | 🔵 Trivial

Good coverage for canVoiceCapture/getSttProvider; missing coverage for the new transcription/sanitization behavior.

Tests thoroughly cover provider-gating and default-provider selection, matching the implementation. Consider adding coverage for sanitizeTranscription() (regex stripping) and the no-speech-detected empty-string path in voiceCaptureFlow()/captureAudio(), since those are core new behaviors introduced by this PR and are currently untested.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/voice/__tests__/speech.test.ts` around lines 1
- 84, Add tests covering the new transcription sanitization and empty-result
handling in the voice flow. Extend the existing speech tests to verify
`sanitizeTranscription()` strips the regex-marked text correctly, and add a case
for the no-speech-detected path in `voiceCaptureFlow()` or `captureAudio()` that
returns an empty string without failing. Use the existing `speech.ts` exports
and the `canVoiceCapture`/`getSttProvider` test file as the place to locate and
mirror the new assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Line 113: The Voice/STT environment guidance is missing the Groq credential
even though speech.ts supports the groq path via STT_PROVIDER and requires
GROQ_API_KEY. Update the env list in AGENTS.md alongside ELEVENLABS_API_KEY,
STT_PROVIDER, ELEVENLABS_MODEL, and STT_LANGUAGE to include GROQ_API_KEY so the
fallback provider is clearly documented.

In `@apps/supercode-cli/server/README.md`:
- Line 57: The README shortcut documentation is out of sync with the actual chat
key binding in chat.ts. Update the voice capture shortcut text to match the CLI
binding used by the chat trigger logic, which uses Ctrl+V as the primary
shortcut with F2 as the fallback, so users are instructed to press the correct
keys.

In `@apps/supercode-cli/server/src/cli/ai/chat/chat.ts`:
- Around line 1052-1059: The voice capture success path in chat.ts leaves the
“Recording...” footer stale after transcription completes. In the voice capture
handling around voiceCaptureFlow(), clear or reset activeFooter’s status message
when text is returned, before or after updating stdinInput/stdinCursor, so the
footer no longer suggests recording is still active; keep the existing “No
speech detected” message only for the empty-text branch.

In `@apps/supercode-cli/server/src/voice/speech.ts`:
- Around line 11-13: `STT_LANGUAGE` is defined in `speech.ts` but never used, so
both transcription paths ignore the configured language. Update the ElevenLabs
and Groq request builders in `speech.ts` to forward this value: pass
`language_code` in the ElevenLabs transcription call and `language` in the Groq
transcription call, using `STT_LANGUAGE` as the source. Keep the changes
localized to the transcription request logic so the existing `ELEVENLABS_URL`,
`ELEVENLABS_MODEL`, and related helpers continue to work unchanged.

In `@packages/secrets/src/client.ts`:
- Around line 5-16: The module-level singleton in getClient currently caches a
single InfisicalClient in shared, so later calls with a different app reuse the
first app’s token. Update the caching logic in getClient and the shared state to
be keyed by AppId instead of one global instance, so each app gets its own
client from resolveToken(app) while still reusing clients per app. Keep the
lookup/creation logic localized to getClient and the shared cache declaration.
- Line 1: Update the Infisical client initialization in client.ts to use the
supported machine-identity auth shape for `@infisical/sdk` v0.0.30. The issue is
that the current client setup is using the legacy token-based shape, which does
not match the SDK’s expected credentials. Fix the InfisicalClient construction
to pass clientId and clientSecret instead of token, and make sure the auth
object aligns with the new SDK API.

In `@packages/secrets/src/index.ts`:
- Around line 28-33: The async Infisical call in getClient/listSecrets is
missing defensive handling, so SDK failures can escape as raw rejections and a
hung request can block indefinitely. Wrap the client.listSecrets(...) await in
try-catch, handle/log the error in this function path, and add a timeout or
abort mechanism around the external call so secrets loading fails fast and
predictably.

In `@packages/secrets/src/resolve-token.ts`:
- Around line 12-22: The local session lookup in readLocalSessionToken is
reading ~/.infisical/infisical-config.json, but the CLI stores login state in
the system keyring with an encrypted file vault fallback, so this path will not
return a valid session token. Update readLocalSessionToken in resolve-token.ts
to query the CLI’s वास्तविक credential store instead of parsing
config.token/config.accessToken from that JSON file, and make sure the returned
value matches the token source used by the logged-in Infisical CLI session so
MissingInfisicalTokenError is not triggered for normal users.

---

Outside diff comments:
In `@apps/supercode-cli/server/src/voice/speech.ts`:
- Around line 91-127: The short-recording path in captureAudio() still rejects
when data.length is under 100 bytes, which prevents the manual-stop/no-speech
flow from reaching the intended retry handling. Change that branch to return an
empty result instead of rejecting, then update voiceCaptureFlow() to recognize
the empty/no-speech result and route it through the no speech retry message
rather than the generic Voice failed path.

---

Nitpick comments:
In `@apps/supercode-cli/server/src/voice/__tests__/speech.test.ts`:
- Around line 1-84: Add tests covering the new transcription sanitization and
empty-result handling in the voice flow. Extend the existing speech tests to
verify `sanitizeTranscription()` strips the regex-marked text correctly, and add
a case for the no-speech-detected path in `voiceCaptureFlow()` or
`captureAudio()` that returns an empty string without failing. Use the existing
`speech.ts` exports and the `canVoiceCapture`/`getSttProvider` test file as the
place to locate and mirror the new assertions.

In `@packages/secrets/src/apply.ts`:
- Line 3: The `apply.ts` and `index.ts` modules currently form a type-only
circular reference through `AppId` and `EnvName`, so move those shared types out
of `./index` into `resolve-token.ts` or a new dedicated `types.ts`. Update
`apply.ts` to import the types from the new location, and adjust any related
imports in `index.ts` so `applySecrets` no longer depends on `index` for type
definitions. Keep the runtime behavior unchanged while removing the circular
module dependency.
- Around line 21-25: The applySecrets logic silently skips secrets whose keys
already exist in process.env, so add observability in the apply.ts loop to
surface when a secret is ignored because an env var is already set. Update the
existing iteration over Object.entries(secrets) to log or otherwise record the
skipped key in a clear way, while preserving the current override behavior for
process.env and keeping the applySecrets function as the central place for this
decision.

In `@packages/secrets/src/index.ts`:
- Line 17: The module-level DEFAULT_PROJECT_ID in the secrets index is captured
too early and can stay stale if INFISICAL_PROJECT_ID is populated later. Move
the project ID lookup into loadSecrets so it is resolved at call time, and use
that live value wherever DEFAULT_PROJECT_ID is currently referenced. Keep the
change localized around loadSecrets and any helper logic that consumes the
project ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c32684f2-7c13-4b57-b762-56783da7d494

📥 Commits

Reviewing files that changed from the base of the PR and between 423684b and deedd4c.

📒 Files selected for processing (15)
  • AGENTS.md
  • apps/supercode-cli/server/README.md
  • apps/supercode-cli/server/package.json
  • apps/supercode-cli/server/src/cli/ai/chat/chat.ts
  • apps/supercode-cli/server/src/voice/__tests__/speech.test.ts
  • apps/supercode-cli/server/src/voice/speech.ts
  • apps/web/.env.example
  • packages/secrets/index.ts
  • packages/secrets/package.json
  • packages/secrets/src/apply.ts
  • packages/secrets/src/client.ts
  • packages/secrets/src/errors.ts
  • packages/secrets/src/index.ts
  • packages/secrets/src/resolve-token.ts
  • packages/secrets/tsconfig.json

Comment thread AGENTS.md
- Web app: `apps/web/.env` or `apps/.env.local`
- Required for auth: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `NEXT_PUBLIC_BETTER_AUTH_URL`
- Required for DB: `DATABASE_URL`
- Voice/STT: `ELEVENLABS_API_KEY` (default provider), `STT_PROVIDER` (`elevenlabs`|`groq`), `ELEVENLABS_MODEL`, `STT_LANGUAGE`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the Groq key to the Voice/STT env list.

STT_PROVIDER=groq is supported, and speech.ts requires GROQ_API_KEY on that path, so leaving it out of the env guidance makes the fallback provider look unsupported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 113, The Voice/STT environment guidance is missing the
Groq credential even though speech.ts supports the groq path via STT_PROVIDER
and requires GROQ_API_KEY. Update the env list in AGENTS.md alongside
ELEVENLABS_API_KEY, STT_PROVIDER, ELEVENLABS_MODEL, and STT_LANGUAGE to include
GROQ_API_KEY so the fallback provider is clearly documented.

| `GROQ_API_KEY` | Groq API key (required when `STT_PROVIDER=groq`) | — |
| `STT_LANGUAGE` | Transcription language | `en` |

Press **Ctrl+Shift+V** during a chat session to start voice capture.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match the documented shortcut to the CLI binding.

chat.ts now uses Ctrl+V as the primary trigger, with F2 as fallback, so documenting Ctrl+Shift+V points users at the wrong key combo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/README.md` at line 57, The README shortcut
documentation is out of sync with the actual chat key binding in chat.ts. Update
the voice capture shortcut text to match the CLI binding used by the chat
trigger logic, which uses Ctrl+V as the primary shortcut with F2 as the
fallback, so users are instructed to press the correct keys.

Comment on lines 1052 to 1059
const text = await voiceCaptureFlow()
if (text) {
stdinInput =
stdinInput.slice(0, stdinCursor) + text + " " + stdinInput.slice(stdinCursor)
stdinCursor += text.length + 1
// Don't call renderInput here — the chat loop will call chatInput() next,
// which preserves stdinInput (via voiceJustCaptured) and renders it once.
} else {
activeFooter?.setStatusMessage("🎤 No speech detected — press voice key to retry")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"Recording..." footer message never cleared on successful transcription.

When voiceCaptureFlow() returns non-empty text, the code inserts it into stdinInput but never clears the earlier "🎤 Recording... (voice key or Enter to stop)" footer message. It lingers until the user types a character (line 1030) or another status update overwrites it, which can confuse the user into thinking recording is still active right after a successful capture.

🧹 Proposed fix
     const text = await voiceCaptureFlow()
     if (text) {
+      activeFooter?.setStatusMessage("")
       stdinInput =
         stdinInput.slice(0, stdinCursor) + text + " " + stdinInput.slice(stdinCursor)
       stdinCursor += text.length + 1
     } else {
📝 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
const text = await voiceCaptureFlow()
if (text) {
stdinInput =
stdinInput.slice(0, stdinCursor) + text + " " + stdinInput.slice(stdinCursor)
stdinCursor += text.length + 1
// Don't call renderInput here — the chat loop will call chatInput() next,
// which preserves stdinInput (via voiceJustCaptured) and renders it once.
} else {
activeFooter?.setStatusMessage("🎤 No speech detected — press voice key to retry")
}
const text = await voiceCaptureFlow()
if (text) {
activeFooter?.setStatusMessage("")
stdinInput =
stdinInput.slice(0, stdinCursor) + text + " " + stdinInput.slice(stdinCursor)
stdinCursor += text.length + 1
} else {
activeFooter?.setStatusMessage("🎤 No speech detected — press voice key to retry")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/cli/ai/chat/chat.ts` around lines 1052 - 1059,
The voice capture success path in chat.ts leaves the “Recording...” footer stale
after transcription completes. In the voice capture handling around
voiceCaptureFlow(), clear or reset activeFooter’s status message when text is
returned, before or after updating stdinInput/stdinCursor, so the footer no
longer suggests recording is still active; keep the existing “No speech
detected” message only for the empty-text branch.

Comment on lines +11 to +13
const ELEVENLABS_URL = "https://api.elevenlabs.io/v1/speech-to-text"
const ELEVENLABS_MODEL = process.env.ELEVENLABS_MODEL || "scribe_v1"
const STT_LANGUAGE = process.env.STT_LANGUAGE || "en"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,240p' apps/supercode-cli/server/src/voice/speech.ts | cat -n

Repository: yashdev9274/supercli

Length of output: 8360


🌐 Web query:

ElevenLabs speech-to-text API language_code form field official docs Groq audio transcriptions language field official docs

💡 Result:

The language parameter fields for ElevenLabs and Groq speech-to-text APIs are documented as follows: ElevenLabs Speech-to-Text API The ElevenLabs API uses the parameter name language_code [1][2][3]. This field is optional and accepts an ISO-639-1 or ISO-639-3 language code (e.g., "en", "eng", "es", "fr", "de") [1][4][5]. Providing this code acts as a language hint, which can improve transcription performance if the language is known beforehand [1][4][5]. If the field is not provided, the API automatically detects the language [4][3]. Groq Speech-to-Text API The Groq API (which uses OpenAI-compatible endpoints for Whisper models) uses the parameter name language [6][7]. This field is optional and accepts a language code representing the language of the audio file [6][7]. It is recommended to provide this parameter explicitly for multilingual audio to avoid ambiguity, although language detection functions automatically if it is omitted [7]. The parameter is typically passed within the client.audio.transcriptions.create method [8][7].

Citations:


Pass STT_LANGUAGE into both transcription requests
STT_LANGUAGE is currently ignored, so setting it has no effect. ElevenLabs expects language_code and Groq expects language; forwarding it to both requests makes the config work as intended.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync, spawn, type ChildProcess } from "child_process"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/voice/speech.ts` around lines 11 - 13,
`STT_LANGUAGE` is defined in `speech.ts` but never used, so both transcription
paths ignore the configured language. Update the ElevenLabs and Groq request
builders in `speech.ts` to forward this value: pass `language_code` in the
ElevenLabs transcription call and `language` in the Groq transcription call,
using `STT_LANGUAGE` as the source. Keep the changes localized to the
transcription request logic so the existing `ELEVENLABS_URL`,
`ELEVENLABS_MODEL`, and related helpers continue to work unchanged.

Comment thread packages/secrets/src/client.ts Outdated
@@ -0,0 +1,20 @@
import { InfisicalClient } from "@infisical/sdk"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What constructor options did @infisical/sdk version 0.0.30 InfisicalClient accept, and did it support a plain "token" field?

💡 Result:

The @infisical/sdk package, which is the current Infisical Node.js SDK, does not use an InfisicalClient constructor that accepts a plain token field [1][2]. Instead, authentication in modern versions of @infisical/sdk (including v0.0.30 and later) typically requires using machine identity credentials (clientId and clientSecret) or specific authentication methods provided by the SDK [3][1]. It appears the user may be confusing @infisical/sdk with the legacy SDK, infisical-node [4][5]. The legacy infisical-node package did feature an InfisicalClient constructor that explicitly accepted a plain token field (e.g., { token: "YOUR_INFISICAL_TOKEN" }) [4][5]. For the @infisical/sdk package, standard constructor options typically include fields such as: - clientId: The machine identity client ID [1]. - clientSecret: The machine identity client secret [1]. - logLevel: The level of logs to output [1]. - cacheTtl: The time-to-live for cached secrets [1]. - siteUrl: The URL for a self-hosted Infisical instance [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## files\n'
git ls-files packages/secrets | sed 's#^`#-` #'

printf '\n## package.json excerpt\n'
sed -n '1,220p' packages/secrets/package.json

printf '\n## client.ts excerpt\n'
cat -n packages/secrets/src/client.ts

printf '\n## search InfisicalClient usages\n'
rg -n "InfisicalClient|`@infisical/sdk`|infisical-node|siteUrl|token|clientId|clientSecret" packages/secrets -S

Repository: yashdev9274/supercli

Length of output: 2988


🌐 Web query:

@infisical/sdk v0.0.30 InfisicalClient constructor token siteUrl clientId clientSecret source definition README

💡 Result:

In the current @infisical/sdk (Node.js SDK), the InfisicalClient constructor typically takes an options object to initialize the client, but the implementation relies on Machine Identity (using clientId and clientSecret) rather than the legacy token approach [1]. The constructor for the current InfisicalClient is defined as follows [1]: new InfisicalClient({ clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", logLevel?: LogLevel // Optional }); Key details regarding this implementation: 1. Authentication: The modern SDK uses Machine Identity (Universal Auth) [1]. You provide your clientId and clientSecret directly to the constructor to authenticate [1]. 2. Site URL: Unlike the legacy infisical-node package, the current @infisical/sdk handles the connection to Infisical Cloud by default [1]. If you are using a self-hosted instance, configuration patterns may vary, but current documentation emphasizes the use of Machine Identities [1][2]. 3. Legacy vs. Current: The package infisical-node is a legacy SDK [3]. That legacy version used an Infisical Token and allowed for a siteURL parameter in its constructor [3]. The current @infisical/sdk, often referred to in v2+ contexts, has shifted away from the direct token constructor in favor of the Machine Identity login flow [1][4]. If you are following tutorials that reference siteUrl, clientId, or clientSecret in a constructor, ensure you are not mixing the legacy infisical-node API with the modern @infisical/sdk [3]. In the modern SDK, authentication is handled via the credentials provided at initialization [1].

Citations:


Use the supported Infisical auth shape here. @infisical/sdk v0.0.30 expects machine-identity credentials (clientId/clientSecret), so passing token here looks like the legacy infisical-node API and will prevent the client from authenticating.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/client.ts` at line 1, Update the Infisical client
initialization in client.ts to use the supported machine-identity auth shape for
`@infisical/sdk` v0.0.30. The issue is that the current client setup is using the
legacy token-based shape, which does not match the SDK’s expected credentials.
Fix the InfisicalClient construction to pass clientId and clientSecret instead
of token, and make sure the auth object aligns with the new SDK API.

Comment thread packages/secrets/src/client.ts Outdated
Comment on lines +5 to +16
let shared: InfisicalClient | null = null

export function getClient(app: AppId): InfisicalClient {
if (!shared) {
const { token, source } = resolveToken(app)
shared = new InfisicalClient({
token,
siteUrl: process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com",
})
}
return shared
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Singleton shared client ignores app on subsequent calls.

shared is a single module-level instance keyed by nothing. Once created for one app, every later getClient(otherApp) returns the same client authenticated with the first app's token — secrets for the second app would be fetched (or fail) using the wrong identity.

🐛 Proposed fix: key the singleton cache by app
-let shared: InfisicalClient | null = null
+const clients = new Map<AppId, InfisicalClient>()
 
 export function getClient(app: AppId): InfisicalClient {
-  if (!shared) {
-    const { token, source } = resolveToken(app)
-    shared = new InfisicalClient({
-      token,
-      siteUrl: process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com",
-    })
-  }
-  return shared
+  if (!clients.has(app)) {
+    const { token } = resolveToken(app)
+    clients.set(
+      app,
+      new InfisicalClient({
+        token,
+        siteUrl: process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com",
+      }),
+    )
+  }
+  return clients.get(app)!
 }
 
 export function resetClient(): void {
-  shared = null
+  clients.clear()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/client.ts` around lines 5 - 16, The module-level
singleton in getClient currently caches a single InfisicalClient in shared, so
later calls with a different app reuse the first app’s token. Update the caching
logic in getClient and the shared state to be keyed by AppId instead of one
global instance, so each app gets its own client from resolveToken(app) while
still reusing clients per app. Keep the lookup/creation logic localized to
getClient and the shared cache declaration.

Comment thread packages/secrets/src/index.ts Outdated
Comment on lines +28 to +33
const client = getClient(app)
const secrets = await client.listSecrets({
environment: env,
projectId,
attachToProcessEnv: false,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No error handling or timeout around the external Infisical call.

client.listSecrets(...) is awaited without try/catch, so any SDK failure (auth error, network issue, rate limit) propagates as an unhandled/raw rejection, and there's no timeout to bound a hung request. As per coding guidelines, **/*.{ts,tsx} files should "Use try-catch for async operations."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/index.ts` around lines 28 - 33, The async Infisical call
in getClient/listSecrets is missing defensive handling, so SDK failures can
escape as raw rejections and a hung request can block indefinitely. Wrap the
client.listSecrets(...) await in try-catch, handle/log the error in this
function path, and add a timeout or abort mechanism around the external call so
secrets loading fails fast and predictably.

Source: Coding guidelines

Comment on lines +12 to +22
function readLocalSessionToken(): string | null {
const configPath = join(homedir(), ".infisical", "infisical-config.json")
if (!existsSync(configPath)) return null
try {
const raw = readFileSync(configPath, "utf-8")
const config = JSON.parse(raw)
return config?.token ?? config?.accessToken ?? null
} catch {
return null
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Where does the Infisical CLI store login/session tokens on disk when using the default credential store, and what is the JSON schema if using the file vault?

💡 Result:

The Infisical CLI uses an auto-detect mechanism by default to determine the best credential store for your environment [1][2]. When the default auto-detect mode is unable to locate a suitable system keyring (such as Keychain on macOS, Credential Manager on Windows, or Secret Service/KWallet on Linux), it falls back to a file-based storage method [1][3]. If you explicitly configure the CLI to use the file vault, or if the auto-detect mechanism falls back to it, the credentials are stored in a local configuration file [1][2]. While the exact file path can vary by operating system according to standard configuration directory conventions (typically within the user's home directory, such as ~/.config/infisical/ on Linux/macOS), the file vault is explicitly described as an encrypted file store [1]. There is no public, static JSON schema for the file vault's contents, as the file is intended to be managed directly by the CLI's internal vault implementation, which performs the necessary encryption to safeguard your sensitive tokens [1]. Users are generally expected to interact with this data exclusively through Infisical CLI commands (e.g., infisical login, infisical vault) rather than manual file manipulation [4][2]. If you are scripting or require access to the token for programmatic use, you should rely on the CLI's output mechanisms—such as the --plain and --silent flags—rather than attempting to parse the vault file directly [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## repo files\n'
git ls-files 'packages/secrets/src/resolve-token.ts' 'packages/secrets/src/*' | sed -n '1,80p'

printf '\n## outline resolve-token.ts\n'
ast-grep outline packages/secrets/src/resolve-token.ts --view expanded || true

printf '\n## nearby contents\n'
cat -n packages/secrets/src/resolve-token.ts | sed -n '1,220p'

printf '\n## search for config path/schema references\n'
rg -n '"\.infisical"|infisical-config|accessToken|readLocalSessionToken|resolveToken|MissingInfisicalTokenError' packages/secrets/src packages -g '!**/dist/**' -g '!**/build/**' || true

Repository: yashdev9274/supercli

Length of output: 4989


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## resolve-token.ts\n'
cat -n packages/secrets/src/resolve-token.ts | sed -n '1,220p'

printf '\n## references\n'
rg -n 'infisical-config|readLocalSessionToken|MissingInfisicalTokenError|accessToken|token' packages/secrets/src -g '!**/dist/**' -g '!**/build/**' || true

Repository: yashdev9274/supercli

Length of output: 3329


🌐 Web query:

Infisical CLI vault docs keyring file vault encrypted file schema token accessToken ~/.infisical/infisical-config.json

💡 Result:

The Infisical CLI manages user authentication and credentials primarily through a system keyring or an encrypted file [1]. By default, the CLI uses an 'auto' vault type, which attempts to select the most appropriate native system keyring [1]. If a system keyring is unavailable or fails, users can explicitly configure the CLI to use a 'file' vault [1][2]. The configuration of this storage behavior is handled via the infisical vault command [1]: - To view or change the current vault type, use: infisical vault set (where is 'auto' or 'file') [1][2]. It is important to distinguish between these storage mechanisms and the.infisical.json file: - System Keyring / Encrypted File: Used to store sensitive user login credentials (like the access token obtained after running infisical login) securely on the local machine [1][3]. -.infisical.json: This is a configuration file created by infisical init in the root directory of a project [4][5]. It stores non-sensitive project-specific settings, such as the workspace ID, default environment, and custom API domain (e.g., {"workspaceId": "...", "defaultEnvironment": "dev", "domain": "..."}) [4][6][7]. It is typically committed to version control and does not contain sensitive authentication tokens [6]. When using machine identities or service tokens, credentials are often handled via the INFISICAL_TOKEN environment variable or the --token flag rather than being stored in the local keyring [8][9].

Citations:


Use the CLI’s actual credential store here instead of ~/.infisical/infisical-config.json. The Infisical CLI keeps login state in the system keyring by default and falls back to an encrypted file vault, so config.token/config.accessToken won’t resolve a normal logged-in session and this will still throw MissingInfisicalTokenError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/secrets/src/resolve-token.ts` around lines 12 - 22, The local
session lookup in readLocalSessionToken is reading
~/.infisical/infisical-config.json, but the CLI stores login state in the system
keyring with an encrypted file vault fallback, so this path will not return a
valid session token. Update readLocalSessionToken in resolve-token.ts to query
the CLI’s वास्तविक credential store instead of parsing
config.token/config.accessToken from that JSON file, and make sure the returned
value matches the token source used by the logged-in Infisical CLI session so
MissingInfisicalTokenError is not triggered for normal users.

- Bump @infisical/sdk to ^5.0.2 (v0.0.30 was never published to npm)
- Replace InfisicalClient with InfisicalSDK, authenticate via
  separate .authenticate(token) call per v5 API
- Destructure .secrets from listSecrets response (v5 returns
  { secrets: Secret[] } instead of a bare array)
- Fixes "No version matching ^0.0.30 found" on Vercel deployment
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