Add static docs site and agent-friendly CLI output#10
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughReplaces Oclif with a custom CLI core (parse/output/policy/schema/MCP), overhauls setup/install with server-env normalization, adds dry-run across many commands, updates docs to generated registry pages, adjusts tests/workflows, and removes legacy plugins/scripts. ChangesBeeper CLI overhaul
Sequence Diagram(s)sequenceDiagram
rect rgba(0, 150, 255, 0.5)
participant User
participant CLI
participant Parser
participant Policy
participant Command
participant Output
end
User->>CLI: runCli(argv)
CLI->>Parser: parseCommand(argv, commands)
Parser-->>CLI: {command, flags, args}
CLI->>Policy: enforcePolicy(command, flags)
Policy-->>CLI: ok
CLI->>Command: command.run(ctx)
Command-->>CLI: result
CLI->>Output: writeResult(result, flags, command)
Output-->>User: human/json/jsonl
Estimated code review effort🎯 5 (Critical) | ⏱️ ~150 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/commands/setup.ts (1)
283-304:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNon-interactive “current target unreachable” output still ignores installed server state.
You added
serverInstalledfor interactive recovery here, but the JSON/non-TTY path still callscurrentTargetBrokenOutput(...), which always emitsinstall-server. This makes automation output inconsistent and can incorrectly suggest reinstalling an already installed server.Suggested fix
- if (flags.json || !process.stdin.isTTY) { - await printData(currentTargetBrokenOutput(target, readiness), flags.json ? 'json' : 'human') + if (flags.json || !process.stdin.isTTY) { + const serverInstalled = await isServerInstalled() + await printData(currentTargetBrokenOutput(target, readiness, serverInstalled), flags.json ? 'json' : 'human') return }-function currentTargetBrokenOutput(target: Target, readiness: Readiness): Record<string, unknown> { +function currentTargetBrokenOutput(target: Target, readiness: Readiness, serverInstalled: boolean): Record<string, unknown> { + const serverAction = installedServerAction(serverInstalled) return { state: 'current-target-unreachable', message: `Beeper CLI is set up for ${target.name ?? target.id}, but it is not reachable.`, target: publicTarget(target), readiness, recommendedAction: action('retry-current', `beeper setup -t ${target.id}`), availableActions: [ action('retry-current', `beeper setup -t ${target.id}`), action('use-desktop', 'beeper setup --desktop'), - action('install-server', 'beeper setup --server --install --yes'), + serverAction, action('connect-remote', 'beeper setup --remote <url>'), ], } }🤖 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/cli/src/commands/setup.ts` around lines 283 - 304, The non-interactive path always outputs "install-server" because currentTargetBrokenOutput is called without the actual installed state; update the flow so the result of isServerInstalled() (serverInstalled) is used to determine the JSON/non-TTY output—either by passing serverInstalled into currentTargetBrokenOutput or by adding logic in the non-interactive branch to emit "use-installed-server" when serverInstalled is true (identify the isServerInstalled() call and the currentTargetBrokenOutput(...) invocation to modify). Ensure the JSON/TTY output mirrors the interactive prompt choice text for option 3 instead of always suggesting installation.
🤖 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 `@packages/npm/scripts/build.ts`:
- Around line 132-137: The milestone logic in the download progress handler
(variables percent, nextLoggedPercent, downloaded, total, function logStep) can
skip intermediate thresholds when percent jumps past multiple milestones; change
the check to a loop that while percent >= nextLoggedPercent (or percent === 100)
emits logStep for each milestone and increments nextLoggedPercent by 25 each
iteration (ensuring the final 100% is emitted with milestone 100), so every 25%
milestone is logged even if a single data event advances progress across several
thresholds.
- Around line 113-118: The download function follows redirects recursively
without a cap; add a maxRedirects parameter (e.g., maxRedirects = 10) and a
currentRedirects counter to the download function signature, increment it on
each redirect and if it exceeds the limit reject with a clear error instead of
recursing forever; update the redirect branch that calls download(nextURL,
destination) to call download(nextURL, destination, currentRedirects + 1,
maxRedirects) (or pass a default when initiating), and ensure all caller sites
provide the new parameter or rely on the default.
---
Outside diff comments:
In `@packages/cli/src/commands/setup.ts`:
- Around line 283-304: The non-interactive path always outputs "install-server"
because currentTargetBrokenOutput is called without the actual installed state;
update the flow so the result of isServerInstalled() (serverInstalled) is used
to determine the JSON/non-TTY output—either by passing serverInstalled into
currentTargetBrokenOutput or by adding logic in the non-interactive branch to
emit "use-installed-server" when serverInstalled is true (identify the
isServerInstalled() call and the currentTargetBrokenOutput(...) invocation to
modify). Ensure the JSON/TTY output mirrors the interactive prompt choice text
for option 3 instead of always suggesting installation.
🪄 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: 33f5d751-b2d8-4103-bce8-43f1439e85eb
📒 Files selected for processing (5)
packages/cli/README.mdpackages/cli/src/commands/setup.tspackages/cli/src/commands/watch.tspackages/cli/test/cli-smoke.tspackages/npm/scripts/build.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (ubuntu-latest / bun 1.3.10)
- GitHub Check: test (macos-latest / bun 1.3.10)
🧰 Additional context used
🪛 ESLint
packages/cli/src/commands/setup.ts
[error] 416-416: Do not use useless undefined.
(unicorn/no-useless-undefined)
[error] 641-641: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 656-656: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 672-672: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
🔇 Additional comments (4)
packages/cli/src/commands/watch.ts (1)
17-18: LGTM!packages/cli/README.md (1)
2418-2419: LGTM!packages/cli/src/commands/setup.ts (1)
106-107: LGTM!Also applies to: 254-275, 347-351, 406-435, 513-530, 626-690
packages/cli/test/cli-smoke.ts (1)
3-3: LGTM!Also applies to: 238-262
|
@indent review |
|
There was a problem hiding this comment.
Actionable comments posted: 12
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/cli/src/commands/setup.ts (1)
170-173:⚠️ Potential issue | 🟠 Major | ⚡ Quick winJSON fallback still hardcodes
install-serverfor broken current target.The non-interactive/JSON branch still emits
install-serverinavailableActionsinstead of preferringuse-installed-serverwhen a local server is already present, which conflicts with this PR’s fallback objective.Suggested fix
- await printData(currentTargetBrokenOutput(target, readiness), flags.json ? 'json' : 'human') + const serverInstalled = await isServerInstalled() + await printData(currentTargetBrokenOutput(target, readiness, serverInstalled), flags.json ? 'json' : 'human') return } -function currentTargetBrokenOutput(target: Target, readiness: Readiness): Record<string, unknown> { +function currentTargetBrokenOutput(target: Target, readiness: Readiness, serverInstalled: boolean): Record<string, unknown> { return { @@ availableActions: [ action('retry-current', `beeper setup -t ${target.id}`), action('use-desktop', 'beeper setup --desktop'), - action('install-server', 'beeper setup --server --install --yes'), + installedServerAction(serverInstalled), action('connect-remote', 'beeper setup --remote <url>'), ], } }Also applies to: 708-720
packages/cli/src/commands/auth/logout.ts (1)
12-19:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSkip the env-token hard error in dry-run mode.
Line 12’s guard runs before the dry-run return, so
auth logout --dry-runcan error even though it performs no mutation. Move the dry-run branch earlier (or gate the throw with!flags['dry-run']).Proposed fix
const { flags } = await this.parse(AuthLogout) ensureWritable(flags) const target = await resolveTarget({ target: flags.target, baseURL: flags['base-url'] }) + const token = target.auth?.accessToken + if (flags['dry-run']) { + await printDryRun('auth.logout', { target: target.id, baseURL: target.baseURL, hadToken: Boolean(token), revokeToken: Boolean(token) }, flags.json ? 'json' : 'human') + return + } if (process.env.BEEPER_ACCESS_TOKEN && !target.auth?.accessToken) { throw new Error('auth logout cannot clear BEEPER_ACCESS_TOKEN from the environment; unset it in the calling process.') } - const token = target.auth?.accessToken - if (flags['dry-run']) { - await printDryRun('auth.logout', { target: target.id, baseURL: target.baseURL, hadToken: Boolean(token), revokeToken: Boolean(token) }, flags.json ? 'json' : 'human') - return - }🤖 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/cli/src/commands/auth/logout.ts` around lines 12 - 19, The guard that throws when process.env.BEEPER_ACCESS_TOKEN is set runs before the dry-run branch, causing `auth logout --dry-run` to error; to fix, ensure the dry-run short-circuit happens before that throw (or wrap the throw in a `if (!flags['dry-run'])` check). Concretely, in the logout command move the `if (flags['dry-run']) { await printDryRun('auth.logout', ...) ; return }` block to run before checking `process.env.BEEPER_ACCESS_TOKEN && !target.auth?.accessToken`, or add `&& !flags['dry-run']` to that throw condition so `auth.logout --dry-run` does not throw while still preserving the original guard for real runs.packages/cli/src/commands/send/voice.ts (1)
23-25:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
send voice --dry-runshould not require writable mode.The command checks writability before the dry-run early return, so read-only mode can prevent preview-only execution.
Proposed fix
async run(): Promise<void> { const { flags } = await this.parse(SendVoice) - ensureWritable(flags) + if (!flags['dry-run']) ensureWritable(flags) const client = await createClient(flags)Also applies to: 39-42
🤖 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/cli/src/commands/send/voice.ts` around lines 23 - 25, The command enforces writable mode before checking dry-run; update SendVoice command so that the dry-run path bypasses writability: parse flags via this.parse(SendVoice), check flags.dryRun (or flags["dry-run"]) and return the preview path before calling ensureWritable(flags) or createClient(flags), and apply the same change to the other occurrence that currently calls ensureWritable before the dry-run check; keep references to ensureWritable, createClient and SendVoice when locating the code to reorder the checks.packages/cli/src/commands/accounts/add.ts (1)
30-33:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
--dry-runis blocked by the write guard.
ensureWritable(flags)runs before the dry-run early return, so read-only mode prevents previewing this command. Gate the write check when--dry-runis set.Proposed fix
async run(): Promise<void> { const { args, flags } = await this.parse(AccountsAdd) - ensureWritable(flags) const client = await createClient(flags) @@ if (flags['dry-run']) { await printDryRun('accounts.add', { @@ return } + + ensureWritable(flags) const step = await client.bridges.loginSessions.create(accountType.id, {Also applies to: 70-84
🤖 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/cli/src/commands/accounts/add.ts` around lines 30 - 33, The current run() in AccountsAdd calls ensureWritable(flags) before handling the --dry-run flag, which prevents previewing in read-only mode; modify run() (and any other locations in the same file around the 70-84 block) to check for flags['dry-run'] (or flags.dryRun) first and early-return/preview when set, and only call ensureWritable(flags) for non-dry-run flows; keep the same behavior and messaging but gate the write-guard behind the dry-run check so ensureWritable is skipped during dry runs.
🟡 Minor comments (25)
packages/cli/src/commands/chats/notify-anyway.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDrop unused
printSuccessimport.
printSuccessisn’t used and currently causes a lint error.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/notify-anyway.ts` at line 5, Remove the unused import symbol printSuccess from the import list in the notify-anyway module to resolve the lint error: update the import statement that currently reads import { printData, printDryRun, printSuccess } from '../../lib/output.js' to only import the symbols actually used (printData, printDryRun) so that printSuccess is no longer referenced in the file.packages/cli/src/commands/chats/remind.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printDataimport.
printDatais no longer used in this file and currently triggers lint failure.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printDryRun, printSuccess } from '../../lib/output.js'🤖 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/cli/src/commands/chats/remind.ts` at line 5, The import list includes an unused symbol printData in the import statement that causes a lint error; remove printData from the import (keep printDryRun and printSuccess) in the top-level import line where printData is referenced so the file no longer imports an unused identifier (or alternatively use printData where intended), ensuring the import matches actual usage in the remind.ts module.packages/cli/src/commands/chats/draft.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis unused and currently fails lint.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/draft.ts` at line 5, The import list in draft.ts includes an unused symbol printSuccess which triggers the linter; remove printSuccess from the import statement (leave printData and printDryRun intact) in the import from '../../lib/output.js' so the file only imports the symbols actually used.packages/cli/src/lib/output.ts-102-102 (1)
102-102:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unnecessary fallback object in spread.
The
?? {}fallback is redundant in object spread and currently hits lint.Suggested fix
- writeJSON(jsonPayload({ message: opts.message, detail: opts.detail, entity: opts.entity, ...(opts.data ?? {}) }), format) + writeJSON(jsonPayload({ message: opts.message, detail: opts.detail, entity: opts.entity, ...opts.data }), format)🤖 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/cli/src/lib/output.ts` at line 102, The object spread uses a redundant fallback; update the call to writeJSON/jsonPayload to spread opts.data directly instead of using the unnecessary "...(opts.data ?? {})" fallback—i.e., change the spread to "...(opts.data)" within the jsonPayload invocation so writeJSON(jsonPayload({ message: opts.message, detail: opts.detail, entity: opts.entity, ...(opts.data) }), format) and remove the "?? {}" portion.packages/cli/src/lib/output.ts-281-281 (1)
281-281:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse strict null checks in
printText.The nullish check uses
==, which violates the configured lint rules.Suggested fix
- if (item == null) continue + if (item === null || item === undefined) continue🤖 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/cli/src/lib/output.ts` at line 281, In function printText replace the loose nullish check on the variable item (currently using ==) with a strict check that explicitly tests for null and undefined so lint rules pass; locate the line "if (item == null) continue" in printText and change it to an explicit null and undefined check for item (i.e., check both null and undefined) so the branch behavior remains the same but uses strict comparison.packages/cli/src/commands/chats/mark-unread.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis no longer used and currently triggers lint failure.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/mark-unread.ts` at line 5, Remove the unused import to fix the lint error: in the import statement that currently imports printData, printDryRun, printSuccess (from '../../lib/output.js') drop printSuccess so only printData and printDryRun are imported; update the import line where printSuccess is referenced to ensure no other code uses it (e.g., check for any occurrences of printSuccess in mark-unread.ts) and run linter to confirm the error is resolved.packages/cli/src/commands/chats/archive.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDrop unused
printSuccessimport.
printSuccessis unused in this command and should be removed.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/archive.ts` at line 5, The import list includes an unused symbol printSuccess from the module imported in the top-level import statement (the line that currently reads import { printData, printDryRun, printSuccess } from '../../lib/output.js'); remove printSuccess from that import so only printData and printDryRun are imported, and run a quick lint/type check to ensure no other references to printSuccess remain in the chats/archive command.packages/cli/src/commands/chats/mark-read.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessfrom imports.
printSuccessis not used and currently triggersno-unused-vars.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/mark-read.ts` at line 5, The import list in mark-read.ts includes an unused symbol printSuccess which triggers no-unused-vars; remove printSuccess from the import statement that currently reads import { printData, printDryRun, printSuccess } from '../../lib/output.js' so the import only includes the actually used symbols (printData, printDryRun) and save the file.packages/cli/src/commands/send/file.ts-33-37 (1)
33-37:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd required blank line before the final statement.
This block currently violates
@stylistic/padding-line-between-statements.Suggested fix
if (flags['dry-run']) { await printDryRun('send.file', request, flags.json ? 'json' : 'human') return } + await printData(await sendMessage(client, request), flags.json ? 'json' : 'human')🤖 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/cli/src/commands/send/file.ts` around lines 33 - 37, Add a blank line between the dry-run early-return block and the final await so the file complies with padding-line-between-statements: after the if (flags['dry-run']) { ... return } block (which calls printDryRun) insert one empty line before the final await printData(await sendMessage(client, request), flags.json ? 'json' : 'human'); ensure the surrounding code using sendMessage, printData and printDryRun remains unchanged.packages/cli/src/commands/chats/description.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove the unused
printSuccessimport.
printSuccessisn’t referenced and should be dropped to satisfy lint rules.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/description.ts` at line 5, Remove the unused import symbol printSuccess from the import statement that currently reads import { printData, printDryRun, printSuccess } from '../../lib/output.js'; update the import to only include referenced symbols (printData, printDryRun) so the unused printSuccess is eliminated and lint errors are resolved.packages/cli/src/commands/resolve/account.ts-25-26 (1)
25-26:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
--pick 0is incorrectly treated as “not provided”.Using a truthy check lets
0bypass validation and fall into default selection logic.Suggested fix
- const selected = flags.pick ? candidates[flags.pick - 1] : candidates.length === 1 ? candidates[0] : undefined - if (flags.pick && !selected) throw notFound(`--pick ${flags.pick} is outside the ${candidates.length} matching accounts`, { selector: args.selector, pick: flags.pick, count: candidates.length }) + const pick = flags.pick + const selected = pick !== undefined ? candidates[pick - 1] : candidates.length === 1 ? candidates[0] : undefined + if (pick !== undefined && !selected) throw notFound(`--pick ${pick} is outside the ${candidates.length} matching accounts`, { selector: args.selector, pick, count: candidates.length })🤖 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/cli/src/commands/resolve/account.ts` around lines 25 - 26, The code treats flags.pick==0 as "not provided" due to a truthy check; update the selection logic to explicitly detect when a pick was provided (e.g., use typeof flags.pick !== 'undefined' or flags.pick != null) and validate it as an integer in range 1..candidates.length before indexing candidates (use flags.pick - 1). Replace the current ternary that references flags.pick with this explicit check and ensure the notFound error (and its message) is thrown when the provided pick is outside the valid range.packages/cli/src/commands/resolve/contact.ts-33-34 (1)
33-34:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
--pick 0bypasses candidate range validation.Because selection is guarded by truthiness,
0behaves like “unset” instead of invalid input.Suggested fix
- const selected = flags.pick ? candidates[flags.pick - 1] : candidates.length === 1 ? candidates[0] : undefined - if (flags.pick && !selected) throw notFound(`--pick ${flags.pick} is outside the ${candidates.length} matching contacts`, { selector: args.selector, pick: flags.pick, count: candidates.length }) + const pick = flags.pick + const selected = pick !== undefined ? candidates[pick - 1] : candidates.length === 1 ? candidates[0] : undefined + if (pick !== undefined && !selected) throw notFound(`--pick ${pick} is outside the ${candidates.length} matching contacts`, { selector: args.selector, pick, count: candidates.length })🤖 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/cli/src/commands/resolve/contact.ts` around lines 33 - 34, The validation incorrectly treats flags.pick as truthy so a value of 0 bypasses range checking; update the logic around selected and the subsequent throw to explicitly detect when flags.pick is provided (e.g., flags.pick !== undefined && flags.pick !== null), compute the index as flags.pick - 1 to set selected from candidates, and validate that flags.pick is between 1 and candidates.length (throw notFound using the existing notFound, args.selector, and flags.pick if out of range); ensure selected remains undefined when no pick was provided and keep references to selected, flags.pick, candidates, notFound, and args.selector.packages/cli/src/commands/targets/start.ts-20-27 (1)
20-27:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAddress padding-line lint failures in the new dry-run branches.
Line 20 and Line 24/27 need the blank-line separation required by
@stylistic/padding-line-between-statements; this will likely fail lint as-is.🤖 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/cli/src/commands/targets/start.ts` around lines 20 - 27, The new branches around launchDesktopApp/printSuccess and the subsequent conditionals violate padding-line rules; add the required blank lines to separate statement groups: insert a blank line after the "return" following printSuccess (the launchDesktopApp/printSuccess block) and ensure there's a blank line before the if that checks "if (!target.managed || target.type !== 'server')" and before "if (flags['dry-run'])" so each logical statement group (launchDesktopApp/printSuccess/return, the non-managed/type check, and the dry-run branch) is separated; update the block containing launchDesktopApp, printSuccess, the return, the non-managed/type if, and the flags['dry-run'] if to include those blank lines.packages/cli/src/lib/command.ts-148-158 (1)
148-158:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix switch-case lint violations in
errorCode.Line 151–157 currently violates
unicorn/switch-case-braces(and ordering if your lint config enforces it), which can block CI.Proposed fix
function errorCode(code: number, isBug: boolean): string { if (isBug) return 'internal_error' switch (code) { - case ExitCodes.Usage: return 'usage_error' - case ExitCodes.AuthRequired: return 'auth_required' - case ExitCodes.NotReady: return 'not_ready' - case ExitCodes.NotFound: return 'not_found' - case ExitCodes.Ambiguous: return 'ambiguous_selector' - case ExitCodes.CommandNotFound: return 'command_not_found' - default: return 'runtime_error' + case ExitCodes.Ambiguous: { return 'ambiguous_selector' } + case ExitCodes.AuthRequired: { return 'auth_required' } + case ExitCodes.CommandNotFound: { return 'command_not_found' } + case ExitCodes.NotFound: { return 'not_found' } + case ExitCodes.NotReady: { return 'not_ready' } + case ExitCodes.Usage: { return 'usage_error' } + default: { return 'runtime_error' } } }🤖 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/cli/src/lib/command.ts` around lines 148 - 158, The switch in function errorCode violates unicorn/switch-case-braces—wrap each case body in braces and use explicit breaks (or returns inside braces) to satisfy the rule; update the cases for ExitCodes.Usage, ExitCodes.AuthRequired, ExitCodes.NotReady, ExitCodes.NotFound, ExitCodes.Ambiguous, and ExitCodes.CommandNotFound inside errorCode to use { return '...'; } for each case (and reorder cases if your linter enforces a specific order) so the switch follows the lint rule and CI will pass.packages/cli/src/commands/chats/unpin.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis unused and should be dropped to satisfy lint.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/unpin.ts` at line 5, The import list includes an unused symbol printSuccess which triggers a lint error; remove printSuccess from the import statement that currently imports printData, printDryRun, printSuccess so the import only includes the used symbols (printData and printDryRun).packages/cli/src/commands/chats/unmute.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis not used and triggers the reported ESLint error.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/unmute.ts` at line 5, The import statement currently brings in printData, printDryRun and printSuccess but printSuccess is unused; edit the import in the module to remove the unused symbol (remove printSuccess from the import list in the import { printData, printDryRun, printSuccess } line) and run linting to confirm no remaining references to printSuccess in the file (check functions or usages around unmute command handlers).packages/cli/src/commands/chats/pin.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis unused in this command and should be removed.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/pin.ts` at line 5, The import list in the chats pin command currently includes an unused symbol `printSuccess`; remove `printSuccess` from the import statement that reads `import { printData, printDryRun, printSuccess } from '../../lib/output.js'` so only used functions (e.g., `printData` and `printDryRun`) are imported to eliminate the unused import and lint warnings.packages/cli/src/commands/chats/focus.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis not used in this command and is currently reported by ESLint.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/focus.ts` at line 5, Remove the unused printSuccess import from the import list in the focus command; edit the import line that currently reads "import { printData, printDryRun, printSuccess } from '../../lib/output.js'" and drop printSuccess so only used symbols (printData, printDryRun) are imported to satisfy ESLint.packages/cli/src/commands/chats/avatar.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis not referenced and is flagged by ESLint.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/avatar.ts` at line 5, The import statement in avatar.ts includes an unused symbol printSuccess which ESLint flags; remove printSuccess from the import list (leave printData and printDryRun intact) so the import becomes only the used symbols and run lint to confirm no other unused imports remain.packages/cli/src/commands/chats/unarchive.ts-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused
printSuccessimport.
printSuccessis unused in this file and currently lint-failing.Suggested fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printData, printDryRun } from '../../lib/output.js'🤖 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/cli/src/commands/chats/unarchive.ts` at line 5, The import list in unarchive.ts unnecessarily includes printSuccess which is not used and causes a linter error; remove printSuccess from the import statement that currently reads "import { printData, printDryRun, printSuccess } from '../../lib/output.js'" so it only imports the used symbols (printData, printDryRun).packages/cli/src/commands/send/text.ts-36-36 (1)
36-36:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd blank line before this statement.
The project's ESLint configuration requires a blank line between the preceding statement block and this line.
📏 Proposed fix
if (flags['dry-run']) { await printDryRun('send.text', request, flags.json ? 'json' : 'human') return } + await printData(await sendMessage(client, request), flags.json ? 'json' : 'human')🤖 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/cli/src/commands/send/text.ts` at line 36, Add a blank line before the statement that awaits printData(await sendMessage(client, request), flags.json ? 'json' : 'human'); specifically insert a newline separating the preceding block from the call that uses sendMessage and printData (which reference client, request, and flags.json) so the ESLint rule requiring a blank line before that statement is satisfied.packages/cli/src/commands/targets/remove.ts-7-7 (1)
7-7:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused import.
printDatais imported but never used in this file.🧹 Proposed fix
-import { printData, printDryRun, printSuccess } from '../../lib/output.js' +import { printDryRun, printSuccess } from '../../lib/output.js'🤖 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/cli/src/commands/targets/remove.ts` at line 7, Remove the unused imported symbol printData from the import statement in remove.ts: locate the import line that brings in printData, printDryRun, and printSuccess and delete printData so only printDryRun and printSuccess are imported; ensure no other references to printData remain in the file (e.g., in any helper functions or exports).packages/cli/src/commands/resolve/bridge.ts-32-33 (1)
32-33:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winValidate
--pickas 1-indexed explicitly.
--pick 0is currently treated as “not provided” due the truthy check, which breaks the documented 1-indexed behavior.Suggested fix
- const selected = flags.pick ? candidates[flags.pick - 1] : candidates.length === 1 ? candidates[0] : undefined - if (flags.pick && !selected) throw notFound(`--pick ${flags.pick} is outside the ${candidates.length} matching bridges`, { selector: args.selector, pick: flags.pick, count: candidates.length }) + if (flags.pick !== undefined && flags.pick < 1) { + throw new Error('--pick must be >= 1') + } + const selected = flags.pick !== undefined ? candidates[flags.pick - 1] : candidates.length === 1 ? candidates[0] : undefined + if (flags.pick !== undefined && !selected) throw notFound(`--pick ${flags.pick} is outside the ${candidates.length} matching bridges`, { selector: args.selector, pick: flags.pick, count: candidates.length })🤖 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/cli/src/commands/resolve/bridge.ts` around lines 32 - 33, The code treats flags.pick as truthy, so --pick 0 is ignored; change the logic to explicitly detect when a pick was provided and validate it as a 1-indexed integer within range: use a check like "typeof flags.pick !== 'undefined'" (or Number.isInteger(flags.pick)) to decide selection (set selected = candidates[flags.pick - 1] only when pick is a valid integer between 1 and candidates.length), keep the fallback of selecting the sole candidate when candidates.length === 1, and if a pick was provided but is out of range throw the existing notFound(...) with the same context (selector, pick, count).docs/astro.config.mjs-30-36 (1)
30-36:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRepository links appear to target the wrong GitHub repo.
The docs config points to
beeper/desktop-api-cli, while this project context isbeeper/cli. Social, edit-link, and command-reference links should be aligned to avoid broken/misdirected docs navigation.Proposed fix
- href: 'https://github.com/beeper/desktop-api-cli', + href: 'https://github.com/beeper/cli', @@ - 'https://github.com/beeper/desktop-api-cli/edit/main/docs/', + 'https://github.com/beeper/cli/edit/main/docs/', @@ - link: 'https://github.com/beeper/desktop-api-cli/blob/main/packages/cli/README.md', + link: 'https://github.com/beeper/cli/blob/main/packages/cli/README.md',Also applies to: 87-87
🤖 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 `@docs/astro.config.mjs` around lines 30 - 36, The repo links in the docs config point to the wrong GitHub repository; update all references that use 'beeper/desktop-api-cli' to 'beeper/cli' (including the social href entries, editLink.baseUrl, and any command-reference or similar link fields) so navigation and edit links resolve to the correct repo; search for occurrences of the string 'beeper/desktop-api-cli' in this file and replace them with 'beeper/cli' (ensure editLink.baseUrl remains correctly suffixed with 'docs/' and other link templates keep their existing path structure).packages/cli/src/commands/send/voice.ts-43-45 (1)
43-45:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLint error: missing blank line before
await printData.This matches the reported ESLint failure and should be fixed to avoid pipeline breakage.
Proposed fix
if (flags['dry-run']) { await printDryRun('send.voice', request, flags.json ? 'json' : 'human') return } + await printData( await sendMessage(client, request), flags.json ? 'json' : 'human',🤖 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/cli/src/commands/send/voice.ts` around lines 43 - 45, The linter flagged a missing blank line before the call to await printData; fix it by inserting a single blank line immediately above the statement that calls await printData(...) (the invocation that awaits sendMessage(client, request) and uses flags.json ? 'json' : 'human'), keeping the surrounding code and indentation intact so the statements referencing sendMessage, client, request, printData, and flags.json remain unchanged.
🧹 Nitpick comments (3)
packages/cli/src/commands/messages/export.ts (1)
26-41: ⚡ Quick winDry-run check happens after API calls.
The
--dry-runcheck at line 29 occurs afterresolveChatID(line 28), which performs an API call to look up the chat. For a true dry-run that avoids side effects, consider checkingflags['dry-run']beforecreateClientandresolveChatID, and printing a dry-run output with the raw selector string instead of the resolvedchatID.If validating the chat selector in dry-run mode is intentional, this is acceptable, but it's inconsistent with other commands in this PR (e.g.,
verify/start.tschecks dry-run before API calls).♻️ Suggested refactor to check dry-run earlier
async run(): Promise<void> { const { flags } = await this.parse(MessagesExport) if (flags['before-cursor'] && flags['after-cursor']) throw new Error('Use only one of --before-cursor or --after-cursor') - if (flags.output !== '-') ensureWritable(flags) - const client = await createClient(flags) - const chatID = await resolveChatID(client, flags.chat, { pick: flags.pick }) if (flags['dry-run']) { await printDryRun('messages.export', { - chatID, + chat: flags.chat, + pick: flags.pick, output: flags.output, beforeCursor: flags['before-cursor'], afterCursor: flags['after-cursor'], after: flags.after, before: flags.before, limit: flags.limit, asc: flags.asc, }, flags.json ? 'json' : 'human') return } + if (flags.output !== '-') ensureWritable(flags) + const client = await createClient(flags) + const chatID = await resolveChatID(client, flags.chat, { pick: flags.pick })🤖 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/cli/src/commands/messages/export.ts` around lines 26 - 41, The dry-run flag is checked after performing API calls (createClient and resolveChatID), causing side effects; move the flags['dry-run'] check to occur before calling createClient and resolveChatID in messages.export, and when in dry-run mode call printDryRun('messages.export', {...}) using the raw selector (flags.chat) and the other CLI flags (output, before-cursor, after-cursor, after, before, limit, asc) rather than the resolved chatID so no API lookup is performed; keep printDryRun usage and the json/human format selection the same.packages/cli/src/commands/contacts/search.ts (1)
35-35: 💤 Low valueConsider simplifying the machine-readable output check.
The expression
!isMachineReadableOutput(flags.json ? 'json' : 'human')is functionally equivalent to!flags.jsonsince those are the only two output formats. The current implementation adds indirection without clear benefit.If additional output formats are planned, the current approach provides future flexibility. Otherwise, consider using
!flags.jsondirectly.🤖 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/cli/src/commands/contacts/search.ts` at line 35, Replace the indirection in constructing useSpinner by directly using the JSON flag: instead of computing useSpinner with isMachineReadableOutput(flags.json ? 'json' : 'human'), set useSpinner based on flags.json (i.e., useSpinner should be the negation of flags.json); if you intend to support more output formats later, keep the isMachineReadableOutput call but document that intent—refer to the useSpinner variable and isMachineReadableOutput(flags.json ? 'json' : 'human') expression and the flags.json flag when making the change.packages/cli/src/commands/presence.ts (1)
20-28: ⚡ Quick winDry-run check happens after API calls.
The
--dry-runcheck at line 25 occurs afterresolveChatID(line 24), which performs an API call. For a true dry-run that avoids side effects, consider checkingflags['dry-run']beforecreateClientandresolveChatID.This is the same pattern as in
messages/export.ts. If you want dry-run to validate selectors, this is acceptable, but it means dry-run mode still makes API calls.🤖 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/cli/src/commands/presence.ts` around lines 20 - 28, The dry-run branch currently runs after createClient and resolveChatID which cause API calls; move the flags['dry-run'] check up so it runs before createClient and resolveChatID to avoid side effects. Specifically, in presence command (around ensureWritable, createClient, resolveChatID, printDryRun) check flags['dry-run'] immediately after ensureWritable (or before any API-creating calls), and if true call printDryRun and return; only call createClient and resolveChatID in the non-dry-run path. If you still need selector validation without API calls, replace resolveChatID with a local validation step for flags.chat/pick before exiting in dry-run.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e1909ded-8979-41e7-934b-9c9261157a6e
⛔ Files ignored due to path filters (3)
docs/bun.lockis excluded by!**/*.lockdocs/public/favicon.svgis excluded by!**/*.svgdocs/src/assets/logo.svgis excluded by!**/*.svg
📒 Files selected for processing (122)
README.mddocs/.gitignoredocs/astro.config.mjsdocs/package.jsondocs/src/content.config.tsdocs/src/content/docs/accounts.mdxdocs/src/content/docs/api.mdxdocs/src/content/docs/auth.mdxdocs/src/content/docs/chats.mdxdocs/src/content/docs/config.mdxdocs/src/content/docs/connect.mdxdocs/src/content/docs/contacts.mdxdocs/src/content/docs/exit-codes.mdxdocs/src/content/docs/export.mdxdocs/src/content/docs/index.mdxdocs/src/content/docs/install.mdxdocs/src/content/docs/media.mdxdocs/src/content/docs/messages.mdxdocs/src/content/docs/plugins.mdxdocs/src/content/docs/presence.mdxdocs/src/content/docs/quickstart.mdxdocs/src/content/docs/rpc.mdxdocs/src/content/docs/scripting.mdxdocs/src/content/docs/send.mdxdocs/src/content/docs/targets.mdxdocs/src/content/docs/update.mdxdocs/src/content/docs/watch.mdxdocs/src/styles/theme.cssdocs/tsconfig.jsonpackages/cli/README.mdpackages/cli/docs/api.mdpackages/cli/docs/config.mdpackages/cli/docs/export.mdpackages/cli/docs/setup.mdpackages/cli/docs/update.mdpackages/cli/docs/watch.mdpackages/cli/package.jsonpackages/cli/scripts/generate-command-map.tspackages/cli/scripts/generate-readme.tspackages/cli/src/commands.generated.tspackages/cli/src/commands/accounts/add.tspackages/cli/src/commands/accounts/remove.tspackages/cli/src/commands/accounts/use.tspackages/cli/src/commands/api/post.tspackages/cli/src/commands/api/request.tspackages/cli/src/commands/auth/email/response.tspackages/cli/src/commands/auth/logout.tspackages/cli/src/commands/chats/archive.tspackages/cli/src/commands/chats/avatar.tspackages/cli/src/commands/chats/description.tspackages/cli/src/commands/chats/disappear.tspackages/cli/src/commands/chats/draft.tspackages/cli/src/commands/chats/focus.tspackages/cli/src/commands/chats/mark-read.tspackages/cli/src/commands/chats/mark-unread.tspackages/cli/src/commands/chats/mute.tspackages/cli/src/commands/chats/notify-anyway.tspackages/cli/src/commands/chats/pin.tspackages/cli/src/commands/chats/priority.tspackages/cli/src/commands/chats/remind.tspackages/cli/src/commands/chats/rename.tspackages/cli/src/commands/chats/start.tspackages/cli/src/commands/chats/unarchive.tspackages/cli/src/commands/chats/unmute.tspackages/cli/src/commands/chats/unpin.tspackages/cli/src/commands/chats/unremind.tspackages/cli/src/commands/config/reset.tspackages/cli/src/commands/config/set.tspackages/cli/src/commands/contacts/search.tspackages/cli/src/commands/export.tspackages/cli/src/commands/install/desktop.tspackages/cli/src/commands/install/server.tspackages/cli/src/commands/man.tspackages/cli/src/commands/media/download.tspackages/cli/src/commands/messages/delete.tspackages/cli/src/commands/messages/edit.tspackages/cli/src/commands/messages/export.tspackages/cli/src/commands/messages/search.tspackages/cli/src/commands/presence.tspackages/cli/src/commands/resolve/account.tspackages/cli/src/commands/resolve/bridge.tspackages/cli/src/commands/resolve/chat.tspackages/cli/src/commands/resolve/contact.tspackages/cli/src/commands/resolve/target.tspackages/cli/src/commands/schema.tspackages/cli/src/commands/send/file.tspackages/cli/src/commands/send/react.tspackages/cli/src/commands/send/sticker.tspackages/cli/src/commands/send/text.tspackages/cli/src/commands/send/unreact.tspackages/cli/src/commands/send/voice.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/targets/add/desktop.tspackages/cli/src/commands/targets/add/remote.tspackages/cli/src/commands/targets/add/server.tspackages/cli/src/commands/targets/disable.tspackages/cli/src/commands/targets/enable.tspackages/cli/src/commands/targets/remove.tspackages/cli/src/commands/targets/restart.tspackages/cli/src/commands/targets/start.tspackages/cli/src/commands/targets/stop.tspackages/cli/src/commands/targets/use.tspackages/cli/src/commands/update.tspackages/cli/src/commands/verify.tspackages/cli/src/commands/verify/approve.tspackages/cli/src/commands/verify/cancel.tspackages/cli/src/commands/verify/qr-confirm.tspackages/cli/src/commands/verify/qr-scan.tspackages/cli/src/commands/verify/recovery-key.tspackages/cli/src/commands/verify/reset-recovery-key.tspackages/cli/src/commands/verify/sas-confirm.tspackages/cli/src/commands/verify/sas.tspackages/cli/src/commands/verify/start.tspackages/cli/src/commands/watch.tspackages/cli/src/lib/command-metadata.tspackages/cli/src/lib/command.tspackages/cli/src/lib/errors.tspackages/cli/src/lib/manifest.tspackages/cli/src/lib/output.tspackages/cli/src/lib/resolve.tspackages/cli/test/cli-smoke.tspackages/cli/test/messages-search-validation.test.ts
💤 Files with no reviewable changes (6)
- packages/cli/docs/api.md
- packages/cli/docs/update.md
- packages/cli/docs/watch.md
- packages/cli/docs/export.md
- packages/cli/docs/setup.md
- packages/cli/docs/config.md
✅ Files skipped from review due to trivial changes (18)
- docs/tsconfig.json
- docs/.gitignore
- docs/src/content.config.ts
- docs/src/content/docs/contacts.mdx
- docs/src/content/docs/media.mdx
- docs/src/content/docs/exit-codes.mdx
- docs/src/content/docs/presence.mdx
- docs/src/content/docs/export.mdx
- docs/src/content/docs/watch.mdx
- docs/src/content/docs/rpc.mdx
- docs/src/content/docs/auth.mdx
- docs/src/content/docs/update.mdx
- docs/src/content/docs/connect.mdx
- docs/src/content/docs/api.mdx
- docs/src/content/docs/plugins.mdx
- README.md
- packages/cli/src/lib/manifest.ts
- packages/cli/README.md
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/README.md (1)
9-9:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace example.com placeholder with actual documentation URL.
The README contains multiple references to
example.comas a placeholder for the documentation site. Before merging, these should be updated to the actual documentation URL.Occurrences:
- Line 9: docs badge
- Line 25: "Read the docs" link
- Lines 205-220: Documentation topic table
Also applies to: 25-25, 205-220
🤖 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/cli/README.md` at line 9, Update the placeholder documentation URL in packages/cli/README.md by replacing all occurrences of "example.com" with the actual docs URL: update the docs badge link (the line containing the badge image), the "Read the docs" anchor, and every link in the documentation topic table (previously lines showing topics) so they point to the real documentation site; double-check for any other "example.com" occurrences in the file and replace them consistently.
🤖 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 `@packages/cli/src/lib/installations.ts`:
- Line 151: installDesktop() currently throws when request.serverEnv !== 'prod',
which breaks non-prod installs; change the install behavior to stop hard-failing
and instead map the desktop update feed to 'prod' while still passing the
original request.serverEnv to the launched app: in installDesktop(), remove the
throw for request.serverEnv !== 'prod', introduce a local variable (e.g.,
feedEnv) that is set to 'prod' when request.serverEnv !== 'prod' and use feedEnv
for any feed/update URLs, but continue to pass request.serverEnv (or
target.serverEnv) into the app launch/config so the app sees the requested
environment; also add a short warning log when mapping non-prod -> prod so users
know the feed was remapped.
---
Outside diff comments:
In `@packages/cli/README.md`:
- Line 9: Update the placeholder documentation URL in packages/cli/README.md by
replacing all occurrences of "example.com" with the actual docs URL: update the
docs badge link (the line containing the badge image), the "Read the docs"
anchor, and every link in the documentation topic table (previously lines
showing topics) so they point to the real documentation site; double-check for
any other "example.com" occurrences in the file and replace them consistently.
🪄 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: 189263df-a84a-4970-9808-6c723d61c15d
📒 Files selected for processing (11)
docs/src/content/docs/targets.mdxpackages/cli/README.mdpackages/cli/src/commands/install/server.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/targets/add/desktop.tspackages/cli/src/commands/targets/add/server.tspackages/cli/src/lib/installations.tspackages/cli/src/lib/manifest.tspackages/cli/src/lib/server-env.tspackages/cli/src/lib/targets.tspackages/cli/test/cli-smoke.ts
✅ Files skipped from review due to trivial changes (1)
- docs/src/content/docs/targets.mdx
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/cli/src/commands/install/server.ts
- packages/cli/src/commands/targets/add/server.ts
- packages/cli/src/lib/manifest.ts
- packages/cli/src/commands/setup.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (ubuntu-latest / bun 1.3.10)
- GitHub Check: test (ubuntu-latest / bun 1.3.10)
- GitHub Check: test (macos-latest / bun 1.3.10)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-05-27T22:40:16.795Z
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:16.795Z
Learning: In beeper CLI documentation, the correct recovery-key verification command syntax is:
`beeper verify recovery-key -t <target> --key "<key>"`.
Use the top-level `verify` subcommand and pass the recovery key via the `--key` flag. Do not use the previously incorrect form like `beeper auth verify recovery-key --code KEY`—update any doc snippets to match the supported syntax.
Applied to files:
packages/cli/README.md
🔇 Additional comments (7)
packages/cli/src/lib/targets.ts (1)
6-6: LGTM!Also applies to: 205-205
packages/cli/src/lib/server-env.ts (1)
1-16: LGTM!packages/cli/test/cli-smoke.ts (1)
252-252: LGTM!Also applies to: 284-311, 346-346
packages/cli/src/commands/targets/add/desktop.ts (1)
12-12: LGTM!Also applies to: 19-22
packages/cli/src/lib/installations.ts (1)
92-93: LGTM!Also applies to: 106-106, 120-120
packages/cli/README.md (2)
452-452: LGTM!Also applies to: 501-501, 594-594, 623-623
2578-2578: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
packages/cli/src/lib/command.ts (1)
50-50:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNon-
CLIErrorvalidation failures are being mislabeled as internal bugs.Line 50 now marks every non-
CLIErrorasisBug. Several commands still throw plainErrorfor user validation, so expected usage failures can surface askind: "bug"/internal_errorand show the bug panel.Proposed fix
- const isBug = error instanceof BugError || !(error instanceof CLIError) + const isBug = error instanceof BugError || (!(error instanceof CLIError) && inferredCode === undefined)🤖 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/cli/src/lib/command.ts` at line 50, The current logic sets isBug for any error that isn't a CLIError, causing plain Error-based validation failures to be treated as internal bugs; update the check in command.ts so only explicit BugError instances are treated as bugs (e.g., replace const isBug = error instanceof BugError || !(error instanceof CLIError) with const isBug = error instanceof BugError) and ensure any user-facing validation errors are thrown as CLIError (or converted earlier) so they aren't misclassified.
🤖 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 `@packages/cli/src/commands/presence.ts`:
- Line 26: Add a blank line before the ensureWritable(flags) statement to
satisfy the `@stylistic/padding-line-between-statements` lint rule; locate the
ensureWritable(flags) call in the presence command (ensureWritable) and insert
one empty line above it so there is padding between the previous statement/block
and the ensureWritable(flags) invocation.
In `@packages/cli/src/commands/schema.ts`:
- Around line 27-29: The code builds requested by filtering this.argv which
contains raw tokens (flags and their values); change it to use the parsed argv
returned from await this.parse(Schema) (e.g., const parsed = await
this.parse(Schema)) and filter parsed.argv instead of this.argv so flag values
like "json" aren’t mistaken for positional paths; update the pathArgs
computation (variable name pathArgs) and the requested assignment to use
parsed.argv.
In `@packages/cli/src/commands/verify/approve.ts`:
- Line 16: Add a blank line before the ensureWritable(flags) call in the
approve.ts file to satisfy the `@stylistic/padding-line-between-statements` rule;
locate the ensureWritable(flags) invocation (and the surrounding write/client
setup code that uses flags) and insert a single empty line immediately above it
so the statement is separated from the preceding block.
In `@packages/cli/src/lib/command.ts`:
- Around line 147-167: The switch in errorCode is triggering stylistic lint
errors because there are no blank lines between case blocks; update the switch
in the errorCode function (the case entries for ExitCodes.Ambiguous,
AuthRequired, CommandNotFound, NotFound, NotReady, Usage, and the default) to
include a blank line between each case block (i.e., a blank line after each
closing brace/return) so `@stylistic/padding-line-between-statements` is
satisfied.
In `@packages/npm/scripts/build.ts`:
- Line 48: The generated launcher() function returns a template literal that
itself contains unescaped/backticked template literals (e.g., the line
containing console.error(`beeper-cli does not ship a binary for
${process.platform}/${process.arch}.`)), which prematurely close the outer
string; fix by replacing inner backticks with regular quotes or string
concatenation (or escape the inner backticks) inside launcher() so the outer
`return \`...\`` remains valid; search for other occurrences inside launcher()
of nested backticks and change them to use single/double quotes or +
concatenation, ensuring any ${...} interpolations intended in the generated code
remain correct.
---
Duplicate comments:
In `@packages/cli/src/lib/command.ts`:
- Line 50: The current logic sets isBug for any error that isn't a CLIError,
causing plain Error-based validation failures to be treated as internal bugs;
update the check in command.ts so only explicit BugError instances are treated
as bugs (e.g., replace const isBug = error instanceof BugError || !(error
instanceof CLIError) with const isBug = error instanceof BugError) and ensure
any user-facing validation errors are thrown as CLIError (or converted earlier)
so they aren't misclassified.
🪄 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: 80a53ca3-850f-4182-af2c-c5f19ff59e28
📒 Files selected for processing (45)
README.mddocs/astro.config.mjspackages/cli/README.mdpackages/cli/scripts/generate-readme.tspackages/cli/src/commands/accounts/add.tspackages/cli/src/commands/auth/logout.tspackages/cli/src/commands/chats/archive.tspackages/cli/src/commands/chats/avatar.tspackages/cli/src/commands/chats/description.tspackages/cli/src/commands/chats/disappear.tspackages/cli/src/commands/chats/draft.tspackages/cli/src/commands/chats/focus.tspackages/cli/src/commands/chats/mark-read.tspackages/cli/src/commands/chats/mark-unread.tspackages/cli/src/commands/chats/notify-anyway.tspackages/cli/src/commands/chats/pin.tspackages/cli/src/commands/chats/remind.tspackages/cli/src/commands/chats/unarchive.tspackages/cli/src/commands/chats/unmute.tspackages/cli/src/commands/chats/unpin.tspackages/cli/src/commands/chats/unremind.tspackages/cli/src/commands/install/server.tspackages/cli/src/commands/messages/export.tspackages/cli/src/commands/presence.tspackages/cli/src/commands/resolve/account.tspackages/cli/src/commands/resolve/bridge.tspackages/cli/src/commands/resolve/contact.tspackages/cli/src/commands/schema.tspackages/cli/src/commands/send/file.tspackages/cli/src/commands/send/text.tspackages/cli/src/commands/send/voice.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/targets/add/desktop.tspackages/cli/src/commands/targets/add/server.tspackages/cli/src/commands/targets/remove.tspackages/cli/src/commands/targets/start.tspackages/cli/src/commands/targets/use.tspackages/cli/src/commands/update.tspackages/cli/src/commands/verify/approve.tspackages/cli/src/lib/command-metadata.tspackages/cli/src/lib/command.tspackages/cli/src/lib/installations.tspackages/cli/src/lib/output.tspackages/cli/src/lib/resolve.tspackages/npm/scripts/build.ts
🚧 Files skipped from review as they are similar to previous changes (22)
- packages/cli/src/commands/chats/mark-unread.ts
- packages/cli/src/commands/chats/unarchive.ts
- packages/cli/src/commands/chats/notify-anyway.ts
- packages/cli/src/commands/chats/avatar.ts
- packages/cli/src/commands/targets/use.ts
- packages/cli/src/commands/chats/draft.ts
- packages/cli/src/commands/chats/unremind.ts
- packages/cli/src/commands/chats/unmute.ts
- packages/cli/src/commands/send/text.ts
- packages/cli/src/commands/targets/start.ts
- packages/cli/src/commands/chats/focus.ts
- packages/cli/src/commands/send/file.ts
- packages/cli/src/commands/targets/add/server.ts
- packages/cli/src/commands/chats/pin.ts
- docs/astro.config.mjs
- packages/cli/src/commands/targets/add/desktop.ts
- packages/cli/src/lib/command-metadata.ts
- packages/cli/src/commands/chats/unpin.ts
- packages/cli/src/commands/targets/remove.ts
- packages/cli/scripts/generate-readme.ts
- packages/cli/src/commands/chats/remind.ts
- packages/cli/src/commands/accounts/add.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-05-27T22:40:16.795Z
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:16.795Z
Learning: In beeper CLI documentation, the correct recovery-key verification command syntax is:
`beeper verify recovery-key -t <target> --key "<key>"`.
Use the top-level `verify` subcommand and pass the recovery key via the `--key` flag. Do not use the previously incorrect form like `beeper auth verify recovery-key --code KEY`—update any doc snippets to match the supported syntax.
Applied to files:
README.mdpackages/cli/README.md
🪛 Biome (2.4.15)
packages/npm/scripts/build.ts
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 48-48: expected ( but instead found $
(parse)
[error] 48-48: expected ; but instead found {
(parse)
[error] 48-48: expected , but instead found .
(parse)
[error] 48-48: expected ; but instead found {
(parse)
[error] 48-48: expected , but instead found .
(parse)
[error] 48-48: Expected an identifier but instead found '`'.
(parse)
[error] 58-58: expected ) but instead found beeper
(parse)
[error] 58-58: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 58-58: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 60-60: Expected a semicolon or an implicit semicolon after a statement, but found none
(parse)
[error] 62-62: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 63-63: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 64-64: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 66-66: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 68-68: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 73-73: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 75-75: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 77-77: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 78-78: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 79-79: await is only allowed within async functions and at the top levels of modules.
(parse)
[error] 80-80: await is only allowed within async functions and at the top levels of modules.
(parse)
🪛 ESLint
packages/cli/src/commands/presence.ts
[error] 26-26: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/chats/disappear.ts
[error] 26-26: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/messages/export.ts
[error] 40-40: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/resolve/account.ts
[error] 25-25: Use object destructuring.
(prefer-destructuring)
[error] 26-26: Unexpected negated condition.
(unicorn/no-negated-condition)
packages/cli/src/commands/resolve/contact.ts
[error] 33-33: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 33-33: Use .length === 0 when checking length is zero.
(unicorn/explicit-length-check)
[error] 34-34: Use object destructuring.
(prefer-destructuring)
[error] 35-35: Unexpected negated condition.
(unicorn/no-negated-condition)
packages/cli/src/lib/installations.ts
[error] 157-157: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/lib/command.ts
[error] 150-150: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 153-153: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 156-156: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 159-159: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 162-162: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 165-165: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/auth/logout.ts
[error] 17-17: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/resolve/bridge.ts
[error] 32-32: Use object destructuring.
(prefer-destructuring)
[error] 33-33: Unexpected negated condition.
(unicorn/no-negated-condition)
packages/cli/src/commands/schema.ts
[error] 124-124: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 127-127: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 130-130: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 133-133: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/verify/approve.ts
[error] 16-16: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/lib/output.ts
[error] 127-127: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 131-131: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
🪛 GitHub Actions: CI / 0_test (ubuntu-latest _ bun 1.3.10).txt
packages/npm/scripts/build.ts
[error] 48-48: Typecheck failed at console.error template string. Error(s): Expected ';' but found 'beeper'; Expected ';' but found 'does'; Expected ';' but found 'ship'; Expected ';' but found 'binary'; Expected '(' but found '$'; Expected '}' but found '.'; Expected ';' but found 'platform'; Unexpected '}'.
🪛 GitHub Actions: CI / 1_test (macos-latest _ bun 1.3.10).txt
packages/npm/scripts/build.ts
[error] 48-48: typecheck failed: Syntax error in console.error template string. Error: Expected ";" but found "beeper". (at packages/npm/scripts/build.ts:48:18)
[error] 48-48: typecheck failed: Syntax error. Error: Expected ";" but found "does". (at packages/npm/scripts/build.ts:48:29)
[error] 48-48: typecheck failed: Syntax error. Error: Expected ";" but found "ship". (at packages/npm/scripts/build.ts:48:38)
[error] 48-48: typecheck failed: Syntax error. Error: Expected ";" but found "binary". (at packages/npm/scripts/build.ts:48:45)
[error] 48-48: typecheck failed: Syntax error. Error: Expected "(" but found "$". (at packages/npm/scripts/build.ts:48:56)
[error] 48-48: typecheck failed: Syntax error. Error: Expected "}" but found ".". (at packages/npm/scripts/build.ts:48:65)
[error] 48-48: typecheck failed: Syntax error. Error: Expected ";" but found "platform". (at packages/npm/scripts/build.ts:48:66)
[error] 48-48: typecheck failed: Syntax error. Error: Unexpected "}". (at packages/npm/scripts/build.ts:48:74)
🪛 GitHub Actions: CI / test (macos-latest _ bun 1.3.10)
packages/npm/scripts/build.ts
[error] 48-48: Typecheck failed: Expected ';' but found 'beeper'.
[error] 48-48: Typecheck failed: Expected ';' but found 'does'.
[error] 48-48: Typecheck failed: Expected ';' but found 'ship'.
[error] 48-48: Typecheck failed: Expected ';' but found 'binary'.
[error] 48-48: Typecheck failed: Expected '(' but found '$'.
[error] 48-48: Typecheck failed: Expected '}' but found '.'.
[error] 48-48: Typecheck failed: Expected ';' but found 'platform'.
[error] 48-48: Typecheck failed: Expected ';' but found 'does'. (Also reported: Unexpected '}' at line 48.)
🪛 GitHub Actions: CI / test (ubuntu-latest _ bun 1.3.10)
packages/npm/scripts/build.ts
[error] 48-48: TypeScript typecheck failed: Expected ";" but found "beeper". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:18)
[error] 48-48: TypeScript typecheck failed: Expected ";" but found "does". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:29)
[error] 48-48: TypeScript typecheck failed: Expected ";" but found "ship". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:38)
[error] 48-48: TypeScript typecheck failed: Expected ";" but found "a". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:45)
[error] 48-48: TypeScript typecheck failed: Expected "(" but found "$". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:56)
[error] 48-48: TypeScript typecheck failed: Expected "}" but found ".". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:65)
[error] 48-48: TypeScript typecheck failed: Expected ";" but found "platform". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:66)
[error] 48-48: TypeScript typecheck failed: Unexpected "}". (at /home/runner/work/cli/cli/packages/npm/scripts/build.ts:48:74)
🔇 Additional comments (19)
packages/cli/src/commands/send/voice.ts (1)
24-26: LGTM!Also applies to: 38-38, 42-44, 46-46
packages/cli/src/commands/chats/description.ts (1)
5-5: LGTM!packages/cli/src/commands/chats/archive.ts (1)
5-5: LGTM!packages/cli/src/commands/update.ts (1)
26-26: LGTM!README.md (1)
8-10: LGTM!Also applies to: 25-25, 205-205, 210-217
packages/cli/src/commands/chats/disappear.ts (1)
23-29: LGTM!packages/cli/src/commands/install/server.ts (1)
6-6: LGTM!Also applies to: 12-12
packages/cli/src/commands/setup.ts (1)
39-39: LGTM!Also applies to: 173-174, 711-722
packages/cli/README.md (1)
9-10: LGTM!Also applies to: 25-25, 205-217, 452-452, 501-501, 594-594, 623-623, 2578-2579
packages/cli/src/commands/auth/logout.ts (1)
10-10: LGTM!Also applies to: 17-19
packages/cli/src/commands/resolve/bridge.ts (1)
32-34: LGTM!packages/cli/src/commands/schema.ts (1)
121-135: LGTM!packages/cli/src/lib/output.ts (1)
102-103: LGTM!Also applies to: 127-134, 289-289
packages/cli/src/commands/messages/export.ts (1)
28-30: LGTM!Also applies to: 40-42
packages/cli/src/commands/resolve/account.ts (1)
25-27: LGTM!packages/cli/src/lib/resolve.ts (1)
39-40: LGTM!packages/cli/src/commands/resolve/contact.ts (1)
28-30: LGTM!Also applies to: 34-36, 58-63
packages/cli/src/commands/chats/mark-read.ts (1)
5-5: LGTM!Also applies to: 17-19
packages/cli/src/lib/installations.ts (1)
151-157: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/cli/src/commands/api/request.ts (1)
28-32: ⚡ Quick winResolve the
@stylistic/padding-line-between-statementslint errors.Static analysis flags missing blank lines before the statements at Lines 28 and 32. Add the expected blank lines to pass the lint check.
🤖 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/cli/src/commands/api/request.ts` around lines 28 - 32, Add the missing blank lines to satisfy the padding-line-between-statements lint rule by inserting a blank line before the conditional branch and before the final await call: ensure there is an empty line before the "if (flags['no-auth']) { … }" block's first statement that calls printData/appRequest and also an empty line before the final "await printData(await appRequest(...), format)" call; locate the usage of flags['no-auth'], appRequest, printData, method and args.path in request.ts and add the blank lines accordingly.packages/cli/src/commands/api/post.ts (1)
28-37: ⚡ Quick winResolve the
@stylistic/padding-line-between-statementslint errors.Static analysis flags missing blank lines before the statements at Lines 28, 32, and 36, which will fail the lint check. Add the expected blank lines before these block statements.
♻️ Proposed fix
} catch { throw new Error(`--body is not valid JSON: ${flags.body}`) } + if (flags['dry-run']) { await printDryRun('api.post', { method: 'POST', path: args.path, body, noAuth: flags['no-auth'], target: flags.target, baseURL: flags['base-url'] }, format) return } + if (flags['no-auth']) { await printData(await appRequest('POST', args.path, { baseURL: flags['base-url'], body, target: flags.target, token: false }), format) return } + const client = await createClient(flags)🤖 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/cli/src/commands/api/post.ts` around lines 28 - 37, The lint rule requires blank lines separating top-level statement blocks; add a single empty line before the "if (flags['dry-run'])" block, before the "if (flags['no-auth'])" block, and before the "const client = await createClient(flags)" statement in this function so there is a blank line between each branch; keep the existing code and callsites (printDryRun, appRequest, printData, createClient, client.post) unchanged and only insert those padding blank lines.packages/cli/src/commands/auth/logout.ts (1)
32-32: ⚡ Quick winResolve the
@stylistic/padding-line-between-statementslint error.Static analysis flags a missing blank line before the
printSuccessstatement at Line 32.♻️ Proposed fix
await clearTargetAuth(target) } + await printSuccess({ message: 'Logged out', detail: token ? 'local token cleared' : 'no token was stored', data: { revoked, hadToken: Boolean(token) } }, format)🤖 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/cli/src/commands/auth/logout.ts` at line 32, Add a blank line before the printSuccess invocation to satisfy the `@stylistic/padding-line-between-statements` rule: inside the logout command in logout.ts, insert a single empty line immediately above the await printSuccess(...) call (the call that uses token, revoked and hadToken) so there is a separating blank line between the preceding statement and the printSuccess await.packages/cli/src/commands/config/set.ts (1)
18-22: ⚡ Quick winResolve the
@stylistic/padding-line-between-statementslint error.Static analysis flags a missing blank line before the
updateConfigstatement at Line 22.♻️ Proposed fix
if (flags['dry-run']) { await printDryRun('config.set', { [args.key]: nextValue }, format) return } + await updateConfig(config => ({ ...config, [args.key]: nextValue }))🤖 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/cli/src/commands/config/set.ts` around lines 18 - 22, The linter wants a blank line separating the conditional dry-run branch from the subsequent side-effect call; inside the config.set logic, add one empty line between the end of the if (flags['dry-run']) { ... return } block and the await updateConfig(...) call so that printDryRun/return is visually separated from updateConfig; locate the flags['dry-run'] check and the await updateConfig(...) invocation and insert a single blank line before updateConfig.
🤖 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 `@packages/cli/src/commands/api/get.ts`:
- Around line 19-26: Add a blank line before the declaration of const client to
satisfy the padding-line-between-statements rule: after the early-return branch
that calls printData(await appRequest(...)) leave one empty line, then declare
const client = await createClient(flags) and continue with await printData(await
client.get(args.path), format); this touches the block using format, appRequest,
createClient, printData, flags and args.path.
In `@packages/cli/src/commands/config/reset.ts`:
- Around line 12-17: The padding-line lint rule is failing because there is no
blank line between the early return block and the subsequent reset call; add a
single blank line between the if-block that handles flags['dry-run']/printDryRun
and the await resetConfig() call so the sequence (printDryRun -> return) is
visually separated from resetConfig(), then keep the following await
printSuccess({ message: 'Config reset' }, format) as-is; this involves editing
the code around printDryRun, resetConfig, and printSuccess in
packages/cli/src/commands/config/reset.ts.
In `@packages/cli/src/commands/verify/cancel.ts`:
- Around line 16-18: Add a blank line before the declaration of const client in
the verify cancel command to satisfy the
`@stylistic/padding-line-between-statements` ESLint rule: locate the function in
cancel.ts where createClient(flags) is called, and insert an empty line between
the preceding statement/block and the line calling createClient so the sequence
reads with a blank line before const client; keep the rest of the logic using
createClient, client.app.verifications.cancel(flags.id ?? 'active', {}), and
printData unchanged.
---
Nitpick comments:
In `@packages/cli/src/commands/api/post.ts`:
- Around line 28-37: The lint rule requires blank lines separating top-level
statement blocks; add a single empty line before the "if (flags['dry-run'])"
block, before the "if (flags['no-auth'])" block, and before the "const client =
await createClient(flags)" statement in this function so there is a blank line
between each branch; keep the existing code and callsites (printDryRun,
appRequest, printData, createClient, client.post) unchanged and only insert
those padding blank lines.
In `@packages/cli/src/commands/api/request.ts`:
- Around line 28-32: Add the missing blank lines to satisfy the
padding-line-between-statements lint rule by inserting a blank line before the
conditional branch and before the final await call: ensure there is an empty
line before the "if (flags['no-auth']) { … }" block's first statement that calls
printData/appRequest and also an empty line before the final "await
printData(await appRequest(...), format)" call; locate the usage of
flags['no-auth'], appRequest, printData, method and args.path in request.ts and
add the blank lines accordingly.
In `@packages/cli/src/commands/auth/logout.ts`:
- Line 32: Add a blank line before the printSuccess invocation to satisfy the
`@stylistic/padding-line-between-statements` rule: inside the logout command in
logout.ts, insert a single empty line immediately above the await
printSuccess(...) call (the call that uses token, revoked and hadToken) so there
is a separating blank line between the preceding statement and the printSuccess
await.
In `@packages/cli/src/commands/config/set.ts`:
- Around line 18-22: The linter wants a blank line separating the conditional
dry-run branch from the subsequent side-effect call; inside the config.set
logic, add one empty line between the end of the if (flags['dry-run']) { ...
return } block and the await updateConfig(...) call so that printDryRun/return
is visually separated from updateConfig; locate the flags['dry-run'] check and
the await updateConfig(...) invocation and insert a single blank line before
updateConfig.
🪄 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: 888ea263-2436-4542-a190-b1662ae9d9a0
📒 Files selected for processing (53)
.gitignorepackages/cli/src/commands/api/get.tspackages/cli/src/commands/api/post.tspackages/cli/src/commands/api/request.tspackages/cli/src/commands/auth/email/response.tspackages/cli/src/commands/auth/logout.tspackages/cli/src/commands/bridges/show.tspackages/cli/src/commands/chats/archive.tspackages/cli/src/commands/chats/avatar.tspackages/cli/src/commands/chats/description.tspackages/cli/src/commands/chats/focus.tspackages/cli/src/commands/chats/mark-read.tspackages/cli/src/commands/chats/mark-unread.tspackages/cli/src/commands/chats/notify-anyway.tspackages/cli/src/commands/chats/pin.tspackages/cli/src/commands/chats/remind.tspackages/cli/src/commands/chats/unarchive.tspackages/cli/src/commands/chats/unmute.tspackages/cli/src/commands/chats/unpin.tspackages/cli/src/commands/chats/unremind.tspackages/cli/src/commands/config/reset.tspackages/cli/src/commands/config/set.tspackages/cli/src/commands/doctor.tspackages/cli/src/commands/media/download.tspackages/cli/src/commands/messages/search.tspackages/cli/src/commands/plugins/available.tspackages/cli/src/commands/presence.tspackages/cli/src/commands/resolve/chat.tspackages/cli/src/commands/schema.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/targets/disable.tspackages/cli/src/commands/targets/enable.tspackages/cli/src/commands/targets/list.tspackages/cli/src/commands/targets/logs.tspackages/cli/src/commands/targets/remove.tspackages/cli/src/commands/targets/restart.tspackages/cli/src/commands/targets/show.tspackages/cli/src/commands/targets/start.tspackages/cli/src/commands/targets/status.tspackages/cli/src/commands/targets/stop.tspackages/cli/src/commands/targets/use.tspackages/cli/src/commands/verify/approve.tspackages/cli/src/commands/verify/cancel.tspackages/cli/src/lib/command-metadata.tspackages/cli/src/lib/command.tspackages/cli/src/lib/export/index.tspackages/cli/src/lib/ink/render.tsxpackages/cli/src/lib/ink/theme.tspackages/cli/src/lib/installations.tspackages/cli/src/lib/profiles.tspackages/cli/src/lib/resolve.tspackages/cli/src/lib/setup-login.tspackages/npm/scripts/build.ts
💤 Files with no reviewable changes (14)
- packages/cli/src/lib/export/index.ts
- packages/cli/src/commands/chats/unmute.ts
- packages/cli/src/commands/chats/unarchive.ts
- packages/cli/src/commands/chats/unpin.ts
- packages/cli/src/commands/chats/mark-read.ts
- packages/cli/src/commands/chats/mark-unread.ts
- packages/cli/src/commands/chats/archive.ts
- packages/cli/src/commands/chats/pin.ts
- packages/cli/src/commands/chats/focus.ts
- packages/cli/src/commands/chats/remind.ts
- packages/cli/src/commands/chats/notify-anyway.ts
- packages/cli/src/commands/chats/avatar.ts
- packages/cli/src/commands/chats/unremind.ts
- packages/cli/src/commands/chats/description.ts
✅ Files skipped from review due to trivial changes (7)
- .gitignore
- packages/cli/src/commands/plugins/available.ts
- packages/cli/src/commands/targets/list.ts
- packages/cli/src/lib/ink/theme.ts
- packages/cli/src/commands/targets/logs.ts
- packages/cli/src/commands/targets/show.ts
- packages/cli/src/commands/targets/status.ts
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/cli/src/commands/media/download.ts
- packages/cli/src/commands/targets/enable.ts
- packages/cli/src/commands/targets/start.ts
- packages/cli/src/commands/targets/disable.ts
- packages/cli/src/commands/targets/restart.ts
- packages/cli/src/commands/verify/approve.ts
- packages/cli/src/commands/presence.ts
- packages/cli/src/commands/schema.ts
- packages/cli/src/lib/command-metadata.ts
- packages/npm/scripts/build.ts
- packages/cli/src/commands/auth/email/response.ts
- packages/cli/src/commands/targets/remove.ts
- packages/cli/src/lib/installations.ts
- packages/cli/src/lib/command.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (macos-latest / bun 1.3.10)
- GitHub Check: test (ubuntu-latest / bun 1.3.10)
- GitHub Check: test (ubuntu-latest / bun 1.3.10)
🧰 Additional context used
🪛 ESLint
packages/cli/src/commands/config/set.ts
[error] 22-22: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/auth/logout.ts
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/api/post.ts
[error] 28-28: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 36-36: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/setup.ts
[error] 140-140: Use switch instead of multiple else-if.
(unicorn/prefer-switch)
packages/cli/src/commands/api/get.ts
[error] 24-24: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/api/request.ts
[error] 28-28: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/config/reset.ts
[error] 16-16: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/verify/cancel.ts
[error] 17-17: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
🔇 Additional comments (13)
packages/cli/src/commands/resolve/chat.ts (1)
19-59: LGTM!packages/cli/src/commands/setup.ts (2)
110-197: LGTM!
39-39: ⚡ Quick winUpdate concern: default/alias regression scope and oclif ordering. This CLI defines
--server-envwithdefault: 'prod'(not'production'), andoptions: [...SERVER_ENVIRONMENTS]plusparse: normalizeServerEnv.normalizeServerEnvmaps'production'→'prod', but whether'production'is accepted depends on whether oclif validates theoptionslist against the raw input before running the customparsefunction; the available evidence doesn’t show that ordering precisely.packages/cli/src/commands/targets/stop.ts (1)
13-22: LGTM!packages/cli/src/commands/targets/use.ts (1)
9-21: LGTM!packages/cli/src/commands/api/request.ts (1)
24-27: 💤 Low valueConfirm intentional GET pass-through under
--dry-run.
--dry-runonly short-circuits for non-GETmethods, soapi request GET … --dry-runstill performs the live request. This is consistent with theensureWritablegating at Line 21 (reads are side-effect-free), so it looks intentional—just confirming it's expected.packages/cli/src/commands/bridges/show.ts (1)
33-39: LGTM!packages/cli/src/commands/doctor.ts (1)
13-19: LGTM!packages/cli/src/commands/messages/search.ts (1)
61-68: LGTM!packages/cli/src/lib/ink/render.tsx (1)
1-1: LGTM!Also applies to: 31-31, 130-143
packages/cli/src/lib/profiles.ts (1)
148-162: LGTM!packages/cli/src/lib/resolve.ts (1)
60-103: LGTM!packages/cli/src/lib/setup-login.ts (1)
31-43: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/cli/src/commands/setup.ts (1)
53-76:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
setup --dry-runstill requires a writable environment.Line 54 runs
ensureWritable(flags)before the new dry-run early return, so the preview fails anywhere the CLI config is read-only. Since this branch only prints derived actions, it should behave like theauth logoutdry-run path and skip the write precondition.🔧 Minimal fix
async run(): Promise<void> { const { flags } = await this.parse(Setup) - ensureWritable(flags) const targetModeCount = [Boolean(flags.remote), flags.server, flags.desktop].filter(Boolean).length if (targetModeCount > 1) throw new Error('Specify at most one of --remote, --server, or --desktop') const authModeCount = [flags.local, flags.oauth, Boolean(flags.email)].filter(Boolean).length if (authModeCount > 1) throw new Error('Specify at most one of --local, --oauth, or --email') if ((flags.local || flags.oauth) && (flags.remote || flags.server || flags.desktop)) { throw new Error('Use --local or --oauth with an existing target, not with --remote, --server, or --desktop.') } + if (!flags['dry-run']) ensureWritable(flags) if (flags['dry-run']) { await printDryRun('setup', { target: flags.target, baseURL: flags['base-url'],🤖 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/cli/src/commands/setup.ts` around lines 53 - 76, The dry-run path currently calls ensureWritable(flags) before deciding to return, causing read-only environments to fail; update setup command logic so the dry-run shortcut is checked and returned before calling ensureWritable(flags) (or make ensureWritable a no-op when flags['dry-run'] is true): move the flags['dry-run'] check (which calls printDryRun) to run prior to invoking ensureWritable(flags) (or add an early return when flags['dry-run'] is set) so printDryRun can run in read-only configs; touch the Setup command entry where ensureWritable and printDryRun are used to implement this change.packages/cli/src/commands/auth/logout.ts (2)
21-32:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd the missing separator before
printSuccess().Line 32 still violates
@stylistic/padding-line-between-statements, which will keep CI red.🎨 Minimal fix
if (token) { const response = await fetch(new URL('/oauth/revoke', target.baseURL), { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token, token_type_hint: 'access_token' }), signal: AbortSignal.timeout(5000), }).catch(() => undefined) revoked = Boolean(response?.ok) await clearTargetAuth(target) } + await printSuccess({ message: 'Logged out', detail: token ? 'local token cleared' : 'no token was stored', data: { revoked, hadToken: Boolean(token) } }, format)🤖 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/cli/src/commands/auth/logout.ts` around lines 21 - 32, The last statement calling printSuccess(...) needs a blank separator line before it to satisfy stylistic rules; insert a single empty line between the token-handling block (where you call clearTargetAuth(target) and compute revoked) and the final await printSuccess(...) call so there is padding between the if/await block and the printSuccess invocation; target symbols to locate are the revoked/token variables, the fetch + clearTargetAuth(target) sequence, and the await printSuccess(...) call.
14-20:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDry-run hides the
BEEPER_ACCESS_TOKENfailure path.When auth only comes from
BEEPER_ACCESS_TOKEN, the real command throws at Lines 18-20, but--dry-runnow returns a normal payload withhadToken: false. That makes the preview lie about whether logout can actually proceed.🔧 Minimal fix
const format = flags.json ? 'json' : 'human' const target = await resolveTarget({ target: flags.target, baseURL: flags['base-url'] }) const token = target.auth?.accessToken + + if (process.env.BEEPER_ACCESS_TOKEN && !token) { + throw new Error('auth logout cannot clear BEEPER_ACCESS_TOKEN from the environment; unset it in the calling process.') + } + if (flags['dry-run']) { await printDryRun('auth.logout', { target: target.id, baseURL: target.baseURL, hadToken: Boolean(token), revokeToken: Boolean(token) }, format) return } - if (process.env.BEEPER_ACCESS_TOKEN && !target.auth?.accessToken) { - throw new Error('auth logout cannot clear BEEPER_ACCESS_TOKEN from the environment; unset it in the calling process.') - }🤖 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/cli/src/commands/auth/logout.ts` around lines 14 - 20, The dry-run path currently returns a normal payload even when logout would fail due to BEEPER_ACCESS_TOKEN being set and no target.auth?.accessToken; move or duplicate the env check so the same error is thrown during dry-run: before calling printDryRun('auth.logout', ...) check if process.env.BEEPER_ACCESS_TOKEN && !target.auth?.accessToken and throw the same Error('auth logout cannot clear BEEPER_ACCESS_TOKEN from the environment; unset it in the calling process.'), so printDryRun only runs when the real command would proceed; reference flags['dry-run'], printDryRun, process.env.BEEPER_ACCESS_TOKEN, and target.auth?.accessToken.
♻️ Duplicate comments (1)
packages/cli/src/commands/api/get.ts (1)
24-24:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRestore the blank line before
const client.CI still reports
@stylistic/padding-line-between-statementshere after the early return block.🤖 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/cli/src/commands/api/get.ts` at line 24, Add a blank line before the statement "const client = await createClient(flags)" so there is a padding line separating it from the preceding early-return block; locate the code in the get command where createClient(flags) is awaited and ensure a single empty line exists above that const declaration to satisfy `@stylistic/padding-line-between-statements`.
🤖 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 `@packages/cli/src/commands/api/post.ts`:
- Around line 28-37: Add missing blank lines between the conditional branches in
the api.post command: insert a blank line before the if (flags['dry-run'])
branch, another blank line before the if (flags['no-auth']) branch, and a blank
line before the const client = await createClient(flags) statement so the
sequence of checks involving flags['dry-run'], printDryRun, flags['no-auth'],
appRequest, createClient and client.post complies with
`@stylistic/padding-line-between-statements`.
In `@packages/cli/src/commands/api/request.ts`:
- Around line 24-32: Add the required blank line padding between the conditional
blocks in the api.request flow: insert a blank line before the "if
(flags['no-auth'])" branch and another blank line before the final "await
printData(await appRequest(...))" call so that the sequence (dry-run -> no-auth
-> default) has separating empty lines; adjust the block around printDryRun,
appRequest, and printData to satisfy `@stylistic/padding-line-between-statements`
without changing logic.
In `@packages/cli/src/commands/config/reset.ts`:
- Around line 9-14: The ensureWritable check is applied even for dry-run; change
the control flow so that when flags['dry-run'] is true the code calls
printDryRun('config.reset', {}, format) and returns before calling
ensureWritable. Concretely, either move the ensureWritable(flags) call below the
dry-run branch or wrap it in if (!flags['dry-run']) so ensureWritable is only
enforced for real mutations; keep references to ensureWritable, flags,
printDryRun and ConfigReset to locate the change.
In `@packages/cli/src/commands/config/set.ts`:
- Around line 18-22: The code lacks the required blank separator before the
subsequent statement: after the conditional that checks flags['dry-run'] and
calls printDryRun (and returns), insert a blank line before the call to
updateConfig to satisfy `@stylistic/padding-line-between-statements`;
specifically, ensure there is an empty line between the return from the
printDryRun branch and the updateConfig(() => ...) invocation that updates
[args.key] to nextValue.
- Around line 17-20: The dry-run hides a "clear" intent because nextValue is
undefined when args.value === '' so printDryRun('config.set', { [args.key]:
nextValue }, ...) omits the key; change the value used for the dry-run payload
so an explicit marker is shown (e.g., use null) — compute a separate
payloadValue (payloadValue = args.value === '' ? null : args.value) and pass
that to printDryRun while leaving nextValue/actual set logic unchanged; update
references in the config.set flow (args.value, nextValue, printDryRun) so JSON
dry-run shows the clear intent.
In `@packages/npm/scripts/build.ts`:
- Line 33: The download() helper lacks request/socket timeouts and doesn't clean
up partial files on stall/errors; update download() to use an AbortController
(or set a socket timeout) to abort the request after a configurable timeout, and
on abort/error ensure the created write stream (createWriteStream(destination))
is closed and the partial destination file (and tempDir if applicable) is
removed (use rm with force) before rejecting; also attach cleanup logic to
response 'error', file 'error', and the outer request 'error' handlers so
artifact.file/destination is always removed on failure and redirects continue to
propagate the same cleanup behavior.
---
Outside diff comments:
In `@packages/cli/src/commands/auth/logout.ts`:
- Around line 21-32: The last statement calling printSuccess(...) needs a blank
separator line before it to satisfy stylistic rules; insert a single empty line
between the token-handling block (where you call clearTargetAuth(target) and
compute revoked) and the final await printSuccess(...) call so there is padding
between the if/await block and the printSuccess invocation; target symbols to
locate are the revoked/token variables, the fetch + clearTargetAuth(target)
sequence, and the await printSuccess(...) call.
- Around line 14-20: The dry-run path currently returns a normal payload even
when logout would fail due to BEEPER_ACCESS_TOKEN being set and no
target.auth?.accessToken; move or duplicate the env check so the same error is
thrown during dry-run: before calling printDryRun('auth.logout', ...) check if
process.env.BEEPER_ACCESS_TOKEN && !target.auth?.accessToken and throw the same
Error('auth logout cannot clear BEEPER_ACCESS_TOKEN from the environment; unset
it in the calling process.'), so printDryRun only runs when the real command
would proceed; reference flags['dry-run'], printDryRun,
process.env.BEEPER_ACCESS_TOKEN, and target.auth?.accessToken.
In `@packages/cli/src/commands/setup.ts`:
- Around line 53-76: The dry-run path currently calls ensureWritable(flags)
before deciding to return, causing read-only environments to fail; update setup
command logic so the dry-run shortcut is checked and returned before calling
ensureWritable(flags) (or make ensureWritable a no-op when flags['dry-run'] is
true): move the flags['dry-run'] check (which calls printDryRun) to run prior to
invoking ensureWritable(flags) (or add an early return when flags['dry-run'] is
set) so printDryRun can run in read-only configs; touch the Setup command entry
where ensureWritable and printDryRun are used to implement this change.
---
Duplicate comments:
In `@packages/cli/src/commands/api/get.ts`:
- Line 24: Add a blank line before the statement "const client = await
createClient(flags)" so there is a padding line separating it from the preceding
early-return block; locate the code in the get command where createClient(flags)
is awaited and ensure a single empty line exists above that const declaration to
satisfy `@stylistic/padding-line-between-statements`.
🪄 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: 1f36d472-5015-49fd-bfa0-e4c40634eba9
📒 Files selected for processing (53)
.gitignorepackages/cli/src/commands/api/get.tspackages/cli/src/commands/api/post.tspackages/cli/src/commands/api/request.tspackages/cli/src/commands/auth/email/response.tspackages/cli/src/commands/auth/logout.tspackages/cli/src/commands/bridges/show.tspackages/cli/src/commands/chats/archive.tspackages/cli/src/commands/chats/avatar.tspackages/cli/src/commands/chats/description.tspackages/cli/src/commands/chats/focus.tspackages/cli/src/commands/chats/mark-read.tspackages/cli/src/commands/chats/mark-unread.tspackages/cli/src/commands/chats/notify-anyway.tspackages/cli/src/commands/chats/pin.tspackages/cli/src/commands/chats/remind.tspackages/cli/src/commands/chats/unarchive.tspackages/cli/src/commands/chats/unmute.tspackages/cli/src/commands/chats/unpin.tspackages/cli/src/commands/chats/unremind.tspackages/cli/src/commands/config/reset.tspackages/cli/src/commands/config/set.tspackages/cli/src/commands/doctor.tspackages/cli/src/commands/media/download.tspackages/cli/src/commands/messages/search.tspackages/cli/src/commands/plugins/available.tspackages/cli/src/commands/presence.tspackages/cli/src/commands/resolve/chat.tspackages/cli/src/commands/schema.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/targets/disable.tspackages/cli/src/commands/targets/enable.tspackages/cli/src/commands/targets/list.tspackages/cli/src/commands/targets/logs.tspackages/cli/src/commands/targets/remove.tspackages/cli/src/commands/targets/restart.tspackages/cli/src/commands/targets/show.tspackages/cli/src/commands/targets/start.tspackages/cli/src/commands/targets/status.tspackages/cli/src/commands/targets/stop.tspackages/cli/src/commands/targets/use.tspackages/cli/src/commands/verify/approve.tspackages/cli/src/commands/verify/cancel.tspackages/cli/src/lib/command-metadata.tspackages/cli/src/lib/command.tspackages/cli/src/lib/export/index.tspackages/cli/src/lib/ink/render.tsxpackages/cli/src/lib/ink/theme.tspackages/cli/src/lib/installations.tspackages/cli/src/lib/profiles.tspackages/cli/src/lib/resolve.tspackages/cli/src/lib/setup-login.tspackages/npm/scripts/build.ts
💤 Files with no reviewable changes (14)
- packages/cli/src/commands/chats/notify-anyway.ts
- packages/cli/src/commands/chats/mark-read.ts
- packages/cli/src/commands/chats/archive.ts
- packages/cli/src/lib/export/index.ts
- packages/cli/src/commands/chats/unremind.ts
- packages/cli/src/commands/chats/avatar.ts
- packages/cli/src/commands/chats/unpin.ts
- packages/cli/src/commands/chats/unmute.ts
- packages/cli/src/commands/chats/pin.ts
- packages/cli/src/commands/chats/unarchive.ts
- packages/cli/src/commands/chats/remind.ts
- packages/cli/src/commands/chats/mark-unread.ts
- packages/cli/src/commands/chats/focus.ts
- packages/cli/src/commands/chats/description.ts
✅ Files skipped from review due to trivial changes (7)
- .gitignore
- packages/cli/src/commands/plugins/available.ts
- packages/cli/src/commands/targets/list.ts
- packages/cli/src/commands/bridges/show.ts
- packages/cli/src/commands/targets/show.ts
- packages/cli/src/lib/ink/theme.ts
- packages/cli/src/commands/targets/status.ts
🚧 Files skipped from review as they are similar to previous changes (22)
- packages/cli/src/commands/targets/use.ts
- packages/cli/src/commands/targets/logs.ts
- packages/cli/src/commands/targets/start.ts
- packages/cli/src/commands/targets/stop.ts
- packages/cli/src/commands/targets/restart.ts
- packages/cli/src/commands/auth/email/response.ts
- packages/cli/src/commands/media/download.ts
- packages/cli/src/lib/setup-login.ts
- packages/cli/src/commands/targets/disable.ts
- packages/cli/src/commands/targets/enable.ts
- packages/cli/src/commands/targets/remove.ts
- packages/cli/src/commands/verify/approve.ts
- packages/cli/src/lib/ink/render.tsx
- packages/cli/src/commands/messages/search.ts
- packages/cli/src/commands/presence.ts
- packages/cli/src/lib/command-metadata.ts
- packages/cli/src/lib/resolve.ts
- packages/cli/src/commands/resolve/chat.ts
- packages/cli/src/lib/installations.ts
- packages/cli/src/lib/profiles.ts
- packages/cli/src/lib/command.ts
- packages/cli/src/commands/schema.ts
📜 Review details
🧰 Additional context used
🪛 ESLint
packages/cli/src/commands/verify/cancel.ts
[error] 17-17: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/api/get.ts
[error] 24-24: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/api/post.ts
[error] 28-28: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 36-36: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/api/request.ts
[error] 28-28: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/auth/logout.ts
[error] 32-32: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/config/reset.ts
[error] 16-16: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/config/set.ts
[error] 22-22: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/setup.ts
[error] 140-140: Use switch instead of multiple else-if.
(unicorn/prefer-switch)
🔇 Additional comments (2)
packages/cli/src/commands/verify/cancel.ts (1)
13-17: Missing blank line beforeconst clientstill fails lint.This is the same
@stylistic/padding-line-between-statementsviolation already called out earlier, and it still appears at Line 17.packages/cli/src/commands/doctor.ts (1)
13-17: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
packages/cli/src/lib/bridges/websocket-proxy.ts (1)
64-64: ⚡ Quick winAbort listener accumulates on the shared signal across reconnects.
runSingleProxyadds a new'abort'listener on the samesignalon every reconnect without ever removing it. On a long-lived proxy with many reconnects this retains references to closedwsinstances and can trigger Node'sMaxListenersExceededWarning. Remove the listener once the session ends.♻️ Proposed fix
- signal.addEventListener('abort', () => ws.close()) + const onAbort = () => ws.close() + signal.addEventListener('abort', onAbort) await new Promise<void>((resolve, reject) => { ws.once('open', () => { send(ws, { command: 'bridge_status', data: { stateEvent: 'UNCONFIGURED' } }) keepalive(signal, ws).catch(error => process.stderr.write(`Websocket ping returned error: ${(error as Error).message}\n`)) resolve() }) ws.once('error', reject) }) - await new Promise<void>((resolve, reject) => { + try { + await new Promise<void>((resolve, reject) => { ws.on('message', data => { handleMessage(ws, registration, JSON.parse(String(data))).catch(error => { process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`) }) }) ws.once('close', (code, reason) => { if (code === 4001 || String(reason).includes('conn_replaced')) reject(new Error('conn_replaced')) else resolve() }) ws.once('error', reject) - }) + }) + } finally { + signal.removeEventListener('abort', onAbort) + }🤖 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/cli/src/lib/bridges/websocket-proxy.ts` at line 64, runSingleProxy currently adds an anonymous 'abort' listener to the shared signal on each reconnect which leaks closed ws instances; change it to add a named handler (e.g., onAbort) when calling signal.addEventListener('abort', onAbort) and ensure you call signal.removeEventListener('abort', onAbort) when the ws session ends (for example inside ws.on('close')/ws.on('error') or immediately after ws.close() completes) so the listener is removed and references to the closed ws are released.packages/cli/src/lib/bridges/manager.ts (1)
499-508: 💤 Low valueStatic
renameimport instead of dynamic import.
await import('node:fs/promises').then(fs => fs.rename(...))can be simplified by importingrenameat the top alongside the othernode:fs/promisesmembers, avoiding a per-call module resolution.🤖 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/cli/src/lib/bridges/manager.ts` around lines 499 - 508, The downloadFile function currently uses a dynamic import for rename; change this to a static import by adding rename to the existing node:fs/promises imports and then replace the dynamic import call in downloadFile with a direct await rename(tmp, path). Update the import list where mkdir, chmod, rm, createWriteStream, etc. are imported to include rename so module resolution happens once at load time rather than per call.packages/cli/src/lib/bridges/catalog.ts (1)
65-67: ⚡ Quick winWrap plugin JSON parsing with file context.
JSON.parseon a user/env-provided catalog file will throw a native, contextless error (e.g.Unexpected token ...) and abort the CLI without indicating which file failed. Adding the path makes misconfigured custom catalogs debuggable.♻️ Proposed change
- const plugin = JSON.parse(await readFile(path, 'utf8')) as CatalogPlugin + let plugin: CatalogPlugin + try { + plugin = JSON.parse(await readFile(path, 'utf8')) as CatalogPlugin + } catch (err) { + throw new Error(`Failed to parse bridge catalog "${path}": ${(err as Error).message}`) + }🤖 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/cli/src/lib/bridges/catalog.ts` around lines 65 - 67, In loadCatalogPlugin, JSON.parse on the file contents can throw an opaque error; wrap the parse in a try/catch around the readFile/JSON.parse (the code that assigns plugin) and when catching, throw or rethrow an Error that includes the catalog file path (path) and the original error message/stack so the CLI reports which file failed (e.g., `Error parsing catalog JSON at ${path}: ${err.message}`) while preserving the original error for debugging.packages/cli/src/commands/bridges/whoami.ts (1)
22-67: ⚡ Quick winResolve the error-level ESLint findings to avoid a lint/CI failure.
Static analysis reports error-level violations that will likely break the lint step: missing blank lines before statements (Lines 22, 26, 58),
perfectionist/sort-array-includes(Line 65, expectsandroidsmsbeforehungryserv), andunicorn/explicit-length-check(Line 67).♻️ Suggested fixes
- if (['hungryserv', 'androidsms', 'imessage'].includes(name)) return '' + if (['androidsms', 'hungryserv', 'imessage'].includes(name)) return '' const states = Object.values(bridge.remoteState ?? {}) - if (!states.length) return bridge.bridgeState?.isSelfHosted ? '' : 'not logged in' + if (states.length === 0) return bridge.bridgeState?.isSelfHosted ? '' : 'not logged in'These are auto-fixable via
eslint --fix.🤖 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/cli/src/commands/bridges/whoami.ts` around lines 22 - 67, Add the missing blank lines and fix the lint rule violations: in printWhoami and formatBridge add blank lines before logical statement blocks to satisfy formatting; in formatBridgeRemotes change the includes array to the correct sorted order (put "androidsms" before "hungryserv") and replace the explicit falsy length check (!states.length) with an explicit length comparison (states.length === 0) so it satisfies unicorn/explicit-length-check. After making these small edits to printWhoami, formatBridge, and formatBridgeRemotes, run eslint --fix to auto-apply remaining formatting fixes.
🤖 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 `@packages/cli/src/commands/bridges/delete.ts`:
- Around line 27-37: The current delete flow uses flags.force for skipping the
whoami/self-hosted checks but uses isForce(flags) to skip the confirmation,
causing inconsistent gating; update the logic so both checks use the same
predicate (preferably isForce(flags)) — i.e., replace occurrences of checks
against flags.force with isForce(flags) when deciding whether to call whoami()
and validate bridgeInfo (refer to whoami, bridgeInfo, args.bridge, flags.force
and isForce(flags)), and keep the confirmation gated by isForce(flags) and
confirm() as currently written to ensure consistent behavior.
In `@packages/cli/src/lib/bridges/manager.ts`:
- Line 5: The import list at the top of manager.ts includes readFile but it's
unused and causing a lint error; remove readFile from the named import (i.e.,
change "import { access, chmod, mkdir, readFile, rm, writeFile } ..." to exclude
readFile) and run the linter/CI to confirm the no-unused-vars violation is
resolved.
In `@packages/cli/src/lib/bridges/websocket-proxy.ts`:
- Around line 74-78: The 'message' listener currently calls JSON.parse(...) as
an argument to handleMessage, so a parse error will throw synchronously and
bypass the handleMessage(...).catch; fix by parsing inside the listener with a
try/catch: call JSON.parse(String(data)) inside a try block, on parse error
write to process.stderr and return early, otherwise call handleMessage(ws,
registration, parsed).catch(...) to handle async errors from handleMessage;
reference the ws.on('message', ...) listener and handleMessage function in your
change.
- Around line 53-55: runSingleProxy builds wsURL using an absolute path which
drops any base path like /_hungryserv/<username>, and also has two bugs around
message parsing and accumulating abort listeners; fix by constructing wsURL
using the homeserverURL’s pathname as the base (e.g., join
homeserverURL.pathname with the relative path
'_matrix/client/unstable/fi.mau.as_sync' so the per-user prefix is preserved)
instead of new URL('/_matrix/...', homeserverURL), change the ws.on('message',
...) handler so JSON.parse is wrapped in try/catch or moved inside the async
handleMessage call so parse errors are handled by the existing .catch, and avoid
adding a new abort listener on every reconnect by registering a single named
abort handler (or add the listener once before reconnect attempts and remove it
when appropriate) rather than calling signal.addEventListener('abort', () =>
ws.close()) repeatedly.
In `@packages/cli/src/types/ws.d.ts`:
- Line 2: Remove the unused import IncomingHttpHeaders from the top of the
ws.d.ts file; it’s imported but never referenced (headers are typed as
Record<string, string>), so delete the line "import type { IncomingHttpHeaders }
from 'node:http'" or replace any intended usage with the existing Record<string,
string> type and save to satisfy ESLint/CI.
---
Nitpick comments:
In `@packages/cli/src/commands/bridges/whoami.ts`:
- Around line 22-67: Add the missing blank lines and fix the lint rule
violations: in printWhoami and formatBridge add blank lines before logical
statement blocks to satisfy formatting; in formatBridgeRemotes change the
includes array to the correct sorted order (put "androidsms" before
"hungryserv") and replace the explicit falsy length check (!states.length) with
an explicit length comparison (states.length === 0) so it satisfies
unicorn/explicit-length-check. After making these small edits to printWhoami,
formatBridge, and formatBridgeRemotes, run eslint --fix to auto-apply remaining
formatting fixes.
In `@packages/cli/src/lib/bridges/catalog.ts`:
- Around line 65-67: In loadCatalogPlugin, JSON.parse on the file contents can
throw an opaque error; wrap the parse in a try/catch around the
readFile/JSON.parse (the code that assigns plugin) and when catching, throw or
rethrow an Error that includes the catalog file path (path) and the original
error message/stack so the CLI reports which file failed (e.g., `Error parsing
catalog JSON at ${path}: ${err.message}`) while preserving the original error
for debugging.
In `@packages/cli/src/lib/bridges/manager.ts`:
- Around line 499-508: The downloadFile function currently uses a dynamic import
for rename; change this to a static import by adding rename to the existing
node:fs/promises imports and then replace the dynamic import call in
downloadFile with a direct await rename(tmp, path). Update the import list where
mkdir, chmod, rm, createWriteStream, etc. are imported to include rename so
module resolution happens once at load time rather than per call.
In `@packages/cli/src/lib/bridges/websocket-proxy.ts`:
- Line 64: runSingleProxy currently adds an anonymous 'abort' listener to the
shared signal on each reconnect which leaks closed ws instances; change it to
add a named handler (e.g., onAbort) when calling
signal.addEventListener('abort', onAbort) and ensure you call
signal.removeEventListener('abort', onAbort) when the ws session ends (for
example inside ws.on('close')/ws.on('error') or immediately after ws.close()
completes) so the listener is removed and references to the closed ws are
released.
🪄 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: 038dcfc3-417b-424a-b907-9e8dd7cd0a75
⛔ Files ignored due to path filters (1)
.DS_Storeis excluded by!**/.DS_Store
📒 Files selected for processing (22)
packages/cli/package.jsonpackages/cli/scripts/generate-command-map.tspackages/cli/scripts/sync-bridge-manager-config.tspackages/cli/src/commands.generated.tspackages/cli/src/commands/bridges/config.tspackages/cli/src/commands/bridges/delete.tspackages/cli/src/commands/bridges/list.tspackages/cli/src/commands/bridges/login-password.tspackages/cli/src/commands/bridges/login.tspackages/cli/src/commands/bridges/logout.tspackages/cli/src/commands/bridges/proxy.tspackages/cli/src/commands/bridges/register.tspackages/cli/src/commands/bridges/run.tspackages/cli/src/commands/bridges/show.tspackages/cli/src/commands/bridges/whoami.tspackages/cli/src/lib/bridges/catalog.tspackages/cli/src/lib/bridges/command.tspackages/cli/src/lib/bridges/generated.tspackages/cli/src/lib/bridges/go-template.tspackages/cli/src/lib/bridges/manager.tspackages/cli/src/lib/bridges/websocket-proxy.tspackages/cli/src/types/ws.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/cli/package.json
- packages/cli/src/commands.generated.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:21.941Z
Learning: In the beeper CLI, the correct command for recovery-key verification is `beeper verify recovery-key -t <target> --key "<key>"` (top-level `verify` subcommand, `--key` flag). The `auth.md` docs previously had incorrect syntax (`beeper auth verify recovery-key --code KEY`), which was fixed in commit be4ec8b of PR `#13`.
🪛 ast-grep (0.43.0)
packages/cli/scripts/sync-bridge-manager-config.ts
[warning] 60-60: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(var ${name} = map\\[string\\]bool\\{([\\s\\S]*?)\\n\\})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 67-67: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(var ${name} = map\\[string\\]string\\{([\\s\\S]*?)\\n\\})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🪛 ESLint
packages/cli/src/types/ws.d.ts
[error] 2-2: 'IncomingHttpHeaders' is defined but never used.
(@typescript-eslint/no-unused-vars)
packages/cli/src/commands/bridges/whoami.ts
[error] 22-22: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 26-26: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 58-58: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 65-65: Expected "androidsms" to come before "hungryserv".
(perfectionist/sort-array-includes)
[error] 67-67: Use .length === 0 when checking length is zero.
(unicorn/explicit-length-check)
packages/cli/src/commands/bridges/register.ts
[error] 34-34: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/commands/bridges/delete.ts
[error] 38-38: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 60-60: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 63-63: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/lib/bridges/websocket-proxy.ts
[error] 15-15: Invalid group length in numeric value.
(unicorn/numeric-separators-style)
[error] 24-24: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 27-27: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 45-45: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 49-49: Do not use useless undefined.
(unicorn/no-useless-undefined)
[error] 104-104: 'HeadersInit' is not defined.
(no-undef)
[error] 111-111: Use for…of instead of .forEach(…).
(unicorn/no-array-for-each)
[error] 116-116: Unnecessary return statement.
(no-useless-return)
[error] 124-124: Do not use useless undefined.
(unicorn/no-useless-undefined)
[error] 139-139: 'BodyInit' is not defined.
(no-undef)
packages/cli/src/commands/bridges/run.ts
[error] 62-62: Unexpected negated condition.
(unicorn/no-negated-condition)
[error] 74-74: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 118-118: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 120-120: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 124-124: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 132-132: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 157-157: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 163-163: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 167-167: Do not use useless undefined.
(unicorn/no-useless-undefined)
packages/cli/src/lib/bridges/go-template.ts
[error] 45-45: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 45-45: Use .length > 0 when checking length is not zero.
(unicorn/explicit-length-check)
[error] 58-58: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 74-74: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 89-89: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 94-94: Use .length === 0 when checking length is zero.
(unicorn/explicit-length-check)
[error] 97-97: Missing braces in case clause.
(unicorn/switch-case-braces)
[error] 102-102: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 107-107: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 113-114: Expected "eq" to come before "setfield".
(perfectionist/sort-switch-case)
[error] 113-113: Missing braces in case clause.
(unicorn/switch-case-braces)
[error] 113-113: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 121-121: Missing braces in case clause.
(unicorn/switch-case-braces)
[error] 121-121: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 144-144: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 151-151: Use a for-of loop instead of this for loop.
(unicorn/no-for-loop)
[error] 168-168: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 172-172: function 'truthy' is equivalent to Boolean. Use Boolean directly.
(unicorn/prefer-native-coercion-functions)
packages/cli/src/commands/bridges/show.ts
[error] 26-26: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
packages/cli/src/lib/bridges/manager.ts
[error] 5-5: 'readFile' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 111-111: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 172-172: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 189-189: Use object destructuring.
(prefer-destructuring)
[error] 210-210: Expected "imessage" to come before "imessagecloud".
(perfectionist/sort-array-includes)
[error] 247-247: Use .length > 0 when checking length is not zero.
(unicorn/explicit-length-check)
[error] 305-305: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 361-361: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 388-388: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 393-393: Unexpected negated condition.
(unicorn/no-negated-condition)
[error] 409-409: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 412-412: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 426-426: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 453-453: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 457-457: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 463-463: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 494-494: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 511-511: Expected "discord" to come before "whatsapp".
(perfectionist/sort-array-includes)
[error] 511-511: Expected "gmessages" to come before "slack".
(perfectionist/sort-array-includes)
[error] 511-511: Expected "imessagego" to come before "signal".
(perfectionist/sort-array-includes)
[error] 511-511: Expected "bluesky" to come before "twitter".
(perfectionist/sort-array-includes)
[error] 555-555: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 557-557: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 574-582: Unexpected labeled statement.
(no-labels)
[error] 579-579: Unexpected label in break statement.
(no-labels)
[error] 584-584: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 591-591: Use switch instead of multiple else-if.
(unicorn/prefer-switch)
[error] 604-604: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 604-604: Expected "facebook" to come before "instagram".
(perfectionist/sort-array-includes)
[error] 607-607: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 620-620: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 623-623: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 636-636: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 644-644: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 657-657: Use for…of instead of .forEach(…).
(unicorn/no-array-for-each)
[error] 690-690: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 694-695: This if statement can be replaced by a ternary expression.
(unicorn/prefer-ternary)
[error] 697-697: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
[error] 729-729: Arrow function should not return assignment.
(no-return-assign)
[error] 730-730: Arrow function should not return assignment.
(no-return-assign)
[error] 753-753: Unexpected use of '&'.
(no-bitwise)
[error] 753-753: Unexpected use of '^'.
(no-bitwise)
[error] 753-753: Invalid group length in numeric value.
(unicorn/numeric-separators-style)
[error] 753-753: Unexpected use of '>>>'.
(no-bitwise)
[error] 753-753: Unexpected use of '>>>'.
(no-bitwise)
[error] 754-754: Unexpected use of '>>>'.
(no-bitwise)
[error] 758-758: Invalid group length in numeric value.
(unicorn/numeric-separators-style)
[error] 759-759: Unexpected use of '^'.
(no-bitwise)
[error] 759-759: Unexpected use of '&'.
(no-bitwise)
[error] 759-759: Unexpected use of '^'.
(no-bitwise)
[error] 759-759: Unexpected use of '>>>'.
(no-bitwise)
[error] 760-760: Unexpected use of '>>>'.
(no-bitwise)
[error] 760-760: Unexpected use of '^'.
(no-bitwise)
[error] 760-760: Invalid group length in numeric value.
(unicorn/numeric-separators-style)
packages/cli/src/commands/bridges/config.ts
[error] 37-37: Expected "discord" to come before "whatsapp".
(perfectionist/sort-array-includes)
[error] 37-37: Expected "gmessages" to come before "slack".
(perfectionist/sort-array-includes)
[error] 37-37: Expected "meta" to come before "signal".
(perfectionist/sort-array-includes)
[error] 37-37: Expected "bluesky" to come before "twitter".
(perfectionist/sort-array-includes)
[error] 48-48: Expected blank line before this statement.
(@stylistic/padding-line-between-statements)
🔇 Additional comments (15)
packages/cli/src/commands/bridges/proxy.ts (1)
6-23: LGTM!Note: the
/_hungryserv/${username}segment you build intohomeserverURLis silently dropped downstream — see the comment inwebsocket-proxy.ts(Line 54).packages/cli/scripts/generate-command-map.ts (1)
12-23: LGTM!packages/cli/scripts/sync-bridge-manager-config.ts (1)
52-78: LGTM!The ast-grep ReDoS warnings on the dynamic
new RegExp(...)are false positives here —nameis always a hardcoded literal ('websocketBridges','bridgeIPSuffix'), never external input.packages/cli/src/commands/bridges/run.ts (1)
40-90: LGTM!Also applies to: 135-170
packages/cli/src/commands/bridges/login.ts (1)
9-36: LGTM!packages/cli/src/commands/bridges/login-password.ts (1)
9-37: LGTM!packages/cli/src/commands/bridges/list.ts (1)
6-33: LGTM!packages/cli/src/lib/bridges/go-template.ts (2)
77-145: LGTM!
59-61: ⚡ Quick winFix
{{-left-trim to remove preceding newlines/whitespace
Inpackages/cli/src/lib/bridges/go-template.ts(lines 59-61), left-trim currently doestext.replace(/[ \t]*$/, ''), which trims spaces/tabs but not the preceding newline—making{{- ... }}whitespace handling asymmetric with the right-trim that already consumes an optional newline.🐛 Proposed fix to match Go trim semantics
- if (trimLeft) text = text.replace(/[ \t]*$/, '') + if (trimLeft) text = text.replace(/\s*$/, '')packages/cli/src/lib/bridges/manager.ts (1)
124-153: LGTM!packages/cli/src/commands/bridges/config.ts (1)
18-50: LGTM!packages/cli/src/commands/bridges/register.ts (1)
22-29: ⚡ Quick win
forceis inherited fromBeeperCommand.baseFlags.
BridgeCommandspreads...BeeperCommand.baseFlags(packages/cli/src/lib/bridges/command.ts), andBeeperCommand.baseFlagsdefinesforcein packages/cli/src/lib/command.ts, soflags.forceused in register.ts is expected to be present.packages/cli/src/commands/bridges/logout.ts (1)
13-19: LGTM!packages/cli/src/lib/bridges/command.ts (1)
4-9: LGTM!packages/cli/src/commands/bridges/show.ts (1)
18-36: LGTM!
| if (!flags.force) { | ||
| const info = await whoami(env) | ||
| const bridgeInfo = info.user.bridges?.[args.bridge] | ||
| if (!bridgeInfo) throw new Error(`You don't have a ${args.bridge} bridge.`) | ||
| if (!bridgeInfo.bridgeState?.isSelfHosted) throw new Error(`Your ${args.bridge} bridge is not self-hosted.`) | ||
| } | ||
|
|
||
| if (!isForce(flags)) { | ||
| const confirmed = await confirm(`Are you sure you want to permanently delete ${args.bridge}?`) | ||
| if (!confirmed) throw new Error('bridge delete cancelled') | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep --pattern 'export function isForce($_) { $$$ }'
rg -nP --type=ts -C3 '\bisForce\b' -g '!**/*.test.ts'Repository: beeper/cli
Length of output: 3052
Fix inconsistent force/yes gating in bridge delete
bridge delete skips the confirmation when isForce(flags) is true (it includes flags.force, flags.yes, and process.env.BEEPER_FORCE === '1'), but it still runs the whoami + self-hosted validation whenever flags.force is not set. Align both gates on the same predicate.
🐛 Proposed fix
- if (!flags.force) {
+ if (!isForce(flags)) {
const info = await whoami(env)
const bridgeInfo = info.user.bridges?.[args.bridge]
if (!bridgeInfo) throw new Error(`You don't have a ${args.bridge} bridge.`)
if (!bridgeInfo.bridgeState?.isSelfHosted) throw new Error(`Your ${args.bridge} bridge is not self-hosted.`)
}📝 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.
| if (!flags.force) { | |
| const info = await whoami(env) | |
| const bridgeInfo = info.user.bridges?.[args.bridge] | |
| if (!bridgeInfo) throw new Error(`You don't have a ${args.bridge} bridge.`) | |
| if (!bridgeInfo.bridgeState?.isSelfHosted) throw new Error(`Your ${args.bridge} bridge is not self-hosted.`) | |
| } | |
| if (!isForce(flags)) { | |
| const confirmed = await confirm(`Are you sure you want to permanently delete ${args.bridge}?`) | |
| if (!confirmed) throw new Error('bridge delete cancelled') | |
| } | |
| if (!isForce(flags)) { | |
| const info = await whoami(env) | |
| const bridgeInfo = info.user.bridges?.[args.bridge] | |
| if (!bridgeInfo) throw new Error(`You don't have a ${args.bridge} bridge.`) | |
| if (!bridgeInfo.bridgeState?.isSelfHosted) throw new Error(`Your ${args.bridge} bridge is not self-hosted.`) | |
| } | |
| if (!isForce(flags)) { | |
| const confirmed = await confirm(`Are you sure you want to permanently delete ${args.bridge}?`) | |
| if (!confirmed) throw new Error('bridge delete cancelled') | |
| } |
🤖 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/cli/src/commands/bridges/delete.ts` around lines 27 - 37, The
current delete flow uses flags.force for skipping the whoami/self-hosted checks
but uses isForce(flags) to skip the confirmation, causing inconsistent gating;
update the logic so both checks use the same predicate (preferably
isForce(flags)) — i.e., replace occurrences of checks against flags.force with
isForce(flags) when deciding whether to call whoami() and validate bridgeInfo
(refer to whoami, bridgeInfo, args.bridge, flags.force and isForce(flags)), and
keep the confirmation gated by isForce(flags) and confirm() as currently written
to ensure consistent behavior.
| import { createInterface } from 'node:readline/promises' | ||
| import { stdin as input, stdout as output } from 'node:process' | ||
| import { constants as fsConstants } from 'node:fs' | ||
| import { access, chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises' |
There was a problem hiding this comment.
Remove unused readFile import.
readFile is imported but not used anywhere in this module, which trips the no-unused-vars lint and can fail CI.
🧹 Proposed fix
-import { access, chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
+import { access, chmod, mkdir, rm, writeFile } from 'node:fs/promises'📝 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.
| import { access, chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises' | |
| import { access, chmod, mkdir, rm, writeFile } from 'node:fs/promises' |
🧰 Tools
🪛 ESLint
[error] 5-5: 'readFile' is defined but never used.
(@typescript-eslint/no-unused-vars)
🤖 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/cli/src/lib/bridges/manager.ts` at line 5, The import list at the
top of manager.ts includes readFile but it's unused and causing a lint error;
remove readFile from the named import (i.e., change "import { access, chmod,
mkdir, readFile, rm, writeFile } ..." to exclude readFile) and run the linter/CI
to confirm the no-unused-vars violation is resolved.
| async function runSingleProxy(signal: AbortSignal, homeserverURL: string, registration: AppserviceRegistration): Promise<void> { | ||
| const wsURL = new URL('/_matrix/client/unstable/fi.mau.as_sync', homeserverURL) | ||
| wsURL.protocol = wsURL.protocol === 'http:' ? 'ws:' : 'wss:' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# How is homeserver_url constructed/used, and is the _hungryserv prefix expected for as_sync?
rg -nP --type=ts -C3 'homeserver_url|_hungryserv|fi\.mau\.as_sync'Repository: beeper/cli
Length of output: 50366
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/src/lib/bridges/websocket-proxy.ts"
# Show the relevant sections around the reported lines
nl -ba "$FILE" | sed -n '1,140p'Repository: beeper/cli
Length of output: 97
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/src/lib/bridges/websocket-proxy.ts"
# Focus searches for abort listener + message handler/JSON.parse
rg -n --type=ts "addEventListener\(|removeEventListener\(|abort|JSON\.parse|handleMessage|message\s*[\)\]]" "$FILE" || trueRepository: beeper/cli
Length of output: 822
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/src/lib/bridges/websocket-proxy.ts"
# Find reconnection loop / retry logic
rg -n --type=ts -C2 "reconnect|retry|backoff|setTimeout|onopen|onclose|onerror|close\W*=>" "$FILE" || trueRepository: beeper/cli
Length of output: 1220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/src/lib/bridges/websocket-proxy.ts"
# Show the key sections with line numbers
cat -n "$FILE" | sed -n '1,140p'
cat -n "$FILE" | sed -n '140,260p'Repository: beeper/cli
Length of output: 7622
🏁 Script executed:
rg -n --type=ts --type=tsx --type=js --type=json --type=md --type=yaml --type=yml "fi\.mau\.as_sync|websocket_proxy|_hungryserv" .Repository: beeper/cli
Length of output: 82
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Re-run without unsupported file types
rg -n "fi\.mau\.as_sync|websocket_proxy|_hungryserv" .Repository: beeper/cli
Length of output: 17539
Fix websocket URL construction to preserve /_hungryserv/<username>
runSingleProxy() builds the websocket URL with new URL('/_matrix/client/unstable/fi.mau.as_sync', homeserverURL) (websocket-proxy.ts:54). But homeserverURL is constructed as https://matrix.${env.domain}/_hungryserv/${encodeURIComponent(username)} (proxy.ts:19; manager.ts:683). Using an absolute path drops the /_hungryserv/<username> prefix, so the websocket targets https://matrix.${env.domain}/_matrix/... instead of the per-user hungryserv endpoint.
🔧 Proposed fix (preserve base path)
- const wsURL = new URL('/_matrix/client/unstable/fi.mau.as_sync', homeserverURL)
+ const wsURL = new URL(`${homeserverURL.replace(/\/$/, '')}/_matrix/client/unstable/fi.mau.as_sync`)
wsURL.protocol = wsURL.protocol === 'http:' ? 'ws:' : 'wss:'ws.on('message', ...)parses JSON inline:JSON.parse(...)happens beforehandleMessage(...).catch(...), so non-JSON frames can throw synchronously and bypass the catch (websocket-proxy.ts:75).- Each reconnect adds
signal.addEventListener('abort', () => ws.close())without removing it, accumulating listeners across reconnects (websocket-proxy.ts:64).
📝 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.
| async function runSingleProxy(signal: AbortSignal, homeserverURL: string, registration: AppserviceRegistration): Promise<void> { | |
| const wsURL = new URL('/_matrix/client/unstable/fi.mau.as_sync', homeserverURL) | |
| wsURL.protocol = wsURL.protocol === 'http:' ? 'ws:' : 'wss:' | |
| async function runSingleProxy(signal: AbortSignal, homeserverURL: string, registration: AppserviceRegistration): Promise<void> { | |
| const wsURL = new URL(`${homeserverURL.replace(/\/$/, '')}/_matrix/client/unstable/fi.mau.as_sync`) | |
| wsURL.protocol = wsURL.protocol === 'http:' ? 'ws:' : 'wss:' |
🤖 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/cli/src/lib/bridges/websocket-proxy.ts` around lines 53 - 55,
runSingleProxy builds wsURL using an absolute path which drops any base path
like /_hungryserv/<username>, and also has two bugs around message parsing and
accumulating abort listeners; fix by constructing wsURL using the
homeserverURL’s pathname as the base (e.g., join homeserverURL.pathname with the
relative path '_matrix/client/unstable/fi.mau.as_sync' so the per-user prefix is
preserved) instead of new URL('/_matrix/...', homeserverURL), change the
ws.on('message', ...) handler so JSON.parse is wrapped in try/catch or moved
inside the async handleMessage call so parse errors are handled by the existing
.catch, and avoid adding a new abort listener on every reconnect by registering
a single named abort handler (or add the listener once before reconnect attempts
and remove it when appropriate) rather than calling
signal.addEventListener('abort', () => ws.close()) repeatedly.
| ws.on('message', data => { | ||
| handleMessage(ws, registration, JSON.parse(String(data))).catch(error => { | ||
| process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
JSON.parse runs before handleMessage, so a parse error escapes the .catch.
JSON.parse(String(data)) is evaluated as the argument expression before handleMessage is invoked, so it throws synchronously inside the 'message' listener and is not caught by the attached .catch. A single non-JSON or truncated frame would surface as an uncaught exception.
🛡️ Proposed fix
ws.on('message', data => {
- handleMessage(ws, registration, JSON.parse(String(data))).catch(error => {
- process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`)
- })
+ Promise.resolve()
+ .then(() => handleMessage(ws, registration, JSON.parse(String(data))))
+ .catch(error => {
+ process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`)
+ })
})📝 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.
| ws.on('message', data => { | |
| handleMessage(ws, registration, JSON.parse(String(data))).catch(error => { | |
| process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`) | |
| }) | |
| }) | |
| ws.on('message', data => { | |
| Promise.resolve() | |
| .then(() => handleMessage(ws, registration, JSON.parse(String(data)))) | |
| .catch(error => { | |
| process.stderr.write(`Failed to handle websocket message: ${(error as Error).message}\n`) | |
| }) | |
| }) |
🤖 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/cli/src/lib/bridges/websocket-proxy.ts` around lines 74 - 78, The
'message' listener currently calls JSON.parse(...) as an argument to
handleMessage, so a parse error will throw synchronously and bypass the
handleMessage(...).catch; fix by parsing inside the listener with a try/catch:
call JSON.parse(String(data)) inside a try block, on parse error write to
process.stderr and return early, otherwise call handleMessage(ws, registration,
parsed).catch(...) to handle async errors from handleMessage; reference the
ws.on('message', ...) listener and handleMessage function in your change.
| @@ -0,0 +1,23 @@ | |||
| declare module 'ws' { | |||
| import type { IncomingHttpHeaders } from 'node:http' | |||
There was a problem hiding this comment.
Remove the unused IncomingHttpHeaders import.
It's imported but never referenced (headers are typed as Record<string, string>), and ESLint flags this as an error that will fail lint/CI.
🧹 Proposed fix
declare module 'ws' {
- import type { IncomingHttpHeaders } from 'node:http'
-
class WebSocket {📝 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.
| import type { IncomingHttpHeaders } from 'node:http' | |
| declare module 'ws' { | |
| class WebSocket { |
🧰 Tools
🪛 ESLint
[error] 2-2: 'IncomingHttpHeaders' is defined but never used.
(@typescript-eslint/no-unused-vars)
🤖 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/cli/src/types/ws.d.ts` at line 2, Remove the unused import
IncomingHttpHeaders from the top of the ws.d.ts file; it’s imported but never
referenced (headers are typed as Record<string, string>), so delete the line
"import type { IncomingHttpHeaders } from 'node:http'" or replace any intended
usage with the existing Record<string, string> type and save to satisfy
ESLint/CI.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/cli/src/lib/installations.ts (1)
205-209:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winSanitize artifact filenames before building file paths.
Line 205 trusts server-provided filename data and uses it directly in
join(...), which permits path traversal/absolute-path writes (for bothfinalPathandtmpPath).🔒 Proposed fix
- const filename = filenameFromResponse(response) ?? (basename(new URL(response.url).pathname) || `beeper-download-${Date.now()}`) - const finalPath = join(destinationDir, filename) - const tmpPath = join(tmpdir(), `${filename}.${process.pid}.${Date.now()}.tmp`) + const rawFilename = filenameFromResponse(response) ?? (basename(new URL(response.url).pathname) || `beeper-download-${Date.now()}`) + const filename = basename(rawFilename).replace(/[<>:"/\\|?*\u0000-\u001F]/g, '_') + if (!filename || filename === '.' || filename === '..') throw new Error('Invalid download filename.') + const finalPath = join(destinationDir, filename) + const tmpPath = join(tmpdir(), `${filename}.${process.pid}.${Date.now()}.tmp`)🤖 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/cli/src/lib/installations.ts` around lines 205 - 209, The code trusts the server-provided filename and embeds it into paths (filename, finalPath, tmpPath) which enables path traversal/absolute-write risks; fix by sanitizing the value returned from filenameFromResponse before using join — call a sanitizer that strips any directory components and path separators (e.g., path.basename or equivalent), forbids absolute paths, removes nulls/control chars, replaces suspicious sequences like ".." and "/" or "\" with safe characters, and enforces a reasonable max length and a safe fallback (e.g., beeper-download-<ts>) so that both finalPath and tmpPath are constructed from the sanitized filename prior to calling writeResponseToFile/rename.packages/cli/src/lib/profiles.ts (1)
49-64:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse platform-specific desktop launch command.
launchDesktopAppnow discovers Windows/Linux app paths but still always executesopen(macOS-only). This breaks desktop profile startup on non-macOS hosts.🛠️ Proposed fix
export async function launchDesktopApp(target?: Target): Promise<{ id: string; startedAt: string }> { const appPath = await findDesktopAppPath() - const args = appPath ? ['-n', appPath, '--args'] : ['-n', '-a', 'Beeper', '--args'] - args.push('--no-enforce-app-location') - if (target?.port) args.push(`--pas-port=${target.port}`) - if (target?.serverEnv) args.push(`--server-env=${target.serverEnv}`) + const appArgs = ['--no-enforce-app-location'] + if (target?.port) appArgs.push(`--pas-port=${target.port}`) + if (target?.serverEnv) appArgs.push(`--server-env=${target.serverEnv}`) const env = target?.dataDir ? { ...process.env, ALLOW_MULTIPLE_INSTANCES: 'true', BEEPER_PROFILE: target.profile ?? target.id, BEEPER_USER_DATA_DIR: target.dataDir, } : process.env - spawn('open', args, { detached: true, stdio: 'ignore', env }).unref() + if (process.platform === 'darwin') { + const openArgs = appPath ? ['-n', appPath, '--args', ...appArgs] : ['-n', '-a', 'Beeper', '--args', ...appArgs] + spawn('open', openArgs, { detached: true, stdio: 'ignore', env }).unref() + } else { + if (!appPath) throw new Error('Beeper Desktop is not installed.') + spawn(appPath, appArgs, { detached: true, stdio: 'ignore', env }).unref() + } return { id: target?.id ?? 'desktop', startedAt: new Date().toISOString() } }🤖 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/cli/src/lib/profiles.ts` around lines 49 - 64, launchDesktopApp currently always spawns the macOS "open" command which fails on Windows/Linux; update launchDesktopApp to pick the platform-specific launcher based on process.platform (e.g., 'open' for darwin, 'cmd' with [' /c', 'start', ...] or using the app executable on win32, and 'xdg-open' or the discovered Linux app path on linux) and use the same args/env/detached/stdio handling as before; ensure you still call findDesktopAppPath to prefer a discovered path, build the appropriate argument array for each platform, and call spawn with the chosen command and args so BEEPER_PROFILE/BEEPER_USER_DATA_DIR and port/serverEnv flags are preserved when launching on non-macOS systems.
🧹 Nitpick comments (3)
packages/cli/src/cli/policy.ts (1)
15-23: ⚡ Quick winConsider wrapping YAML parsing in try/catch for clearer error messages.
If the YAML file is malformed, the
yamlpackage throws an error that may not clearly indicate it's a safety profile parsing issue. Wrapping with a descriptive error would improve debugging.Suggested improvement
function loadSafetyProfile(nameOrPath: string): { allow: Set<string>; name: string } { const path = resolveProfilePath(nameOrPath) if (!path) throw usage(`unknown safety profile "${nameOrPath}"`) - const root = parseYAML(readFileSync(path, 'utf8')) as Record<string, unknown> | undefined + let root: Record<string, unknown> | undefined + try { + root = parseYAML(readFileSync(path, 'utf8')) as Record<string, unknown> | undefined + } catch (error) { + const detail = error instanceof Error ? error.message : String(error) + throw usage(`failed to load safety profile "${nameOrPath}": ${detail}`) + } return { allow: rules(root?.allow), name: typeof root?.name === 'string' && root.name.trim() ? root.name.trim() : 'unnamed', } }🤖 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/cli/src/cli/policy.ts` around lines 15 - 23, The YAML parsing in loadSafetyProfile should be wrapped in a try/catch so malformed YAML yields a clear, contextual error; surround the call to parseYAML(readFileSync(path, 'utf8')) with try/catch, on error throw a usage(...) (same style as the existing unknown profile error) that includes the profile path/name and the original error.message (or error.toString()) to aid debugging, then continue to call rules(root?.allow) and compute the name as before.packages/cli/src/cli/mcp.ts (1)
12-30: 💤 Low valueConsider using
spliceinstead of settinglength = 0for clarity.
buffer.length = 0works butbuffer.splice(0)or reassigningbuffer = [](if usinglet) is more idiomatic. However, this is a minor style preference.Alternative approach
- buffer.length = 0 + buffer.splice(0)🤖 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/cli/src/cli/mcp.ts` around lines 12 - 30, In serveMcp, replace the non-idiomatic buffer clearing line "buffer.length = 0" with a clearer operation; because buffer is declared as a const array, use buffer.splice(0) to empty it (or change the declaration to let buffer = [] and reassign buffer = [] if you prefer reassignment). Update the buffer clearing inside serveMcp accordingly so the code uses buffer.splice(0) (or switch to let and reassign) instead of setting buffer.length = 0.packages/cli/src/cli/commands.ts (1)
1671-1678: 💤 Low valueConsider adding return type annotation for
apiClient.Using
anyas return type loses type safety. IfBeeperDesktophas a proper TypeScript interface, consider using that type or at least documenting what methods are expected.🤖 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/cli/src/cli/commands.ts` around lines 1671 - 1678, The apiClient function is typed as Promise<any>, losing type safety; change its return annotation from Promise<any> to a concrete type that matches BeeperDesktop (e.g., Promise<BeeperDesktop> or a defined interface describing the methods used), import or export the BeeperDesktop type if available, or define a minimal interface (with the specific methods your callers use) and use Promise<ThatInterface> as the return type for apiClient; update the function signature and any necessary imports so callers get proper typings while leaving implementation (resolveTarget, targetToken, new BeeperDesktop(...)) unchanged.
🤖 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.
Outside diff comments:
In `@packages/cli/src/lib/installations.ts`:
- Around line 205-209: The code trusts the server-provided filename and embeds
it into paths (filename, finalPath, tmpPath) which enables path
traversal/absolute-write risks; fix by sanitizing the value returned from
filenameFromResponse before using join — call a sanitizer that strips any
directory components and path separators (e.g., path.basename or equivalent),
forbids absolute paths, removes nulls/control chars, replaces suspicious
sequences like ".." and "/" or "\" with safe characters, and enforces a
reasonable max length and a safe fallback (e.g., beeper-download-<ts>) so that
both finalPath and tmpPath are constructed from the sanitized filename prior to
calling writeResponseToFile/rename.
In `@packages/cli/src/lib/profiles.ts`:
- Around line 49-64: launchDesktopApp currently always spawns the macOS "open"
command which fails on Windows/Linux; update launchDesktopApp to pick the
platform-specific launcher based on process.platform (e.g., 'open' for darwin,
'cmd' with [' /c', 'start', ...] or using the app executable on win32, and
'xdg-open' or the discovered Linux app path on linux) and use the same
args/env/detached/stdio handling as before; ensure you still call
findDesktopAppPath to prefer a discovered path, build the appropriate argument
array for each platform, and call spawn with the chosen command and args so
BEEPER_PROFILE/BEEPER_USER_DATA_DIR and port/serverEnv flags are preserved when
launching on non-macOS systems.
---
Nitpick comments:
In `@packages/cli/src/cli/commands.ts`:
- Around line 1671-1678: The apiClient function is typed as Promise<any>, losing
type safety; change its return annotation from Promise<any> to a concrete type
that matches BeeperDesktop (e.g., Promise<BeeperDesktop> or a defined interface
describing the methods used), import or export the BeeperDesktop type if
available, or define a minimal interface (with the specific methods your callers
use) and use Promise<ThatInterface> as the return type for apiClient; update the
function signature and any necessary imports so callers get proper typings while
leaving implementation (resolveTarget, targetToken, new BeeperDesktop(...))
unchanged.
In `@packages/cli/src/cli/mcp.ts`:
- Around line 12-30: In serveMcp, replace the non-idiomatic buffer clearing line
"buffer.length = 0" with a clearer operation; because buffer is declared as a
const array, use buffer.splice(0) to empty it (or change the declaration to let
buffer = [] and reassign buffer = [] if you prefer reassignment). Update the
buffer clearing inside serveMcp accordingly so the code uses buffer.splice(0)
(or switch to let and reassign) instead of setting buffer.length = 0.
In `@packages/cli/src/cli/policy.ts`:
- Around line 15-23: The YAML parsing in loadSafetyProfile should be wrapped in
a try/catch so malformed YAML yields a clear, contextual error; surround the
call to parseYAML(readFileSync(path, 'utf8')) with try/catch, on error throw a
usage(...) (same style as the existing unknown profile error) that includes the
profile path/name and the original error.message (or error.toString()) to aid
debugging, then continue to call rules(root?.allow) and compute the name as
before.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e1444ea2-a6fb-49ae-9345-72a07fd8b87a
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (221)
.changeset/README.md.changeset/config.json.github/workflows/ci.yml.github/workflows/publish-release.yml.github/workflows/release-doctor.ymlREADME.mdeslint.config.mjspackage.jsonpackages/cli-plugin-cloudflare/LICENSEpackages/cli-plugin-cloudflare/README.mdpackages/cli-plugin-cloudflare/package.jsonpackages/cli-plugin-cloudflare/src/commands/targets/tunnel.tspackages/cli-plugin-cloudflare/src/index.tspackages/cli-plugin-cloudflare/test/smoke.mjspackages/cli-plugin-cloudflare/tsconfig.jsonpackages/cli/CHANGELOG.mdpackages/cli/README.mdpackages/cli/SECURITY.mdpackages/cli/beeper-setup-redesign-spec.mdpackages/cli/bin/binary-bootstrap.jspackages/cli/bin/check-release-environmentpackages/cli/bin/cli.jspackages/cli/bin/dev.jspackages/cli/bin/logo.jspackages/cli/bin/run.jspackages/cli/links.txtpackages/cli/package.jsonpackages/cli/release-please-config.jsonpackages/cli/safety-profiles/agent-safe.yamlpackages/cli/safety-profiles/full.yamlpackages/cli/safety-profiles/readonly.yamlpackages/cli/scripts/bootstrappackages/cli/scripts/buildpackages/cli/scripts/build-binaries.tspackages/cli/scripts/build-homebrew-archive.tspackages/cli/scripts/check-api-copy.tspackages/cli/scripts/check-manifest.tspackages/cli/scripts/formatpackages/cli/scripts/generate-command-map.tspackages/cli/scripts/generate-readme.tspackages/cli/scripts/linkpackages/cli/scripts/lintpackages/cli/scripts/mockpackages/cli/scripts/publish-homebrew-formula.tspackages/cli/scripts/publish-local-release.tspackages/cli/scripts/read-signing-secrets.rbpackages/cli/scripts/runpackages/cli/scripts/sign-macos-binaries.tspackages/cli/scripts/testpackages/cli/scripts/unlinkpackages/cli/src/cli/commands.tspackages/cli/src/cli/main.tspackages/cli/src/cli/mcp.tspackages/cli/src/cli/output.tspackages/cli/src/cli/parse.tspackages/cli/src/cli/policy.tspackages/cli/src/cli/schema.tspackages/cli/src/cli/setup.tspackages/cli/src/cli/types.tspackages/cli/src/commands.generated.tspackages/cli/src/commands/_complete.tspackages/cli/src/commands/accounts/add.tspackages/cli/src/commands/accounts/list.tspackages/cli/src/commands/accounts/remove.tspackages/cli/src/commands/accounts/show.tspackages/cli/src/commands/accounts/use.tspackages/cli/src/commands/api/get.tspackages/cli/src/commands/api/post.tspackages/cli/src/commands/api/request.tspackages/cli/src/commands/auth/email/response.tspackages/cli/src/commands/auth/email/start.tspackages/cli/src/commands/auth/logout.tspackages/cli/src/commands/auth/status.tspackages/cli/src/commands/autocomplete.tspackages/cli/src/commands/bridges/list.tspackages/cli/src/commands/bridges/show.tspackages/cli/src/commands/chats/archive.tspackages/cli/src/commands/chats/avatar.tspackages/cli/src/commands/chats/description.tspackages/cli/src/commands/chats/disappear.tspackages/cli/src/commands/chats/draft.tspackages/cli/src/commands/chats/focus.tspackages/cli/src/commands/chats/list.tspackages/cli/src/commands/chats/mark-read.tspackages/cli/src/commands/chats/mark-unread.tspackages/cli/src/commands/chats/mute.tspackages/cli/src/commands/chats/notify-anyway.tspackages/cli/src/commands/chats/pin.tspackages/cli/src/commands/chats/priority.tspackages/cli/src/commands/chats/remind.tspackages/cli/src/commands/chats/rename.tspackages/cli/src/commands/chats/search.tspackages/cli/src/commands/chats/show.tspackages/cli/src/commands/chats/start.tspackages/cli/src/commands/chats/unarchive.tspackages/cli/src/commands/chats/unmute.tspackages/cli/src/commands/chats/unpin.tspackages/cli/src/commands/chats/unremind.tspackages/cli/src/commands/completion.tspackages/cli/src/commands/config/get.tspackages/cli/src/commands/config/path.tspackages/cli/src/commands/config/reset.tspackages/cli/src/commands/config/set.tspackages/cli/src/commands/contacts/list.tspackages/cli/src/commands/contacts/search.tspackages/cli/src/commands/contacts/show.tspackages/cli/src/commands/docs.tspackages/cli/src/commands/doctor.tspackages/cli/src/commands/export.tspackages/cli/src/commands/install/desktop.tspackages/cli/src/commands/install/server.tspackages/cli/src/commands/man.tspackages/cli/src/commands/media/download.tspackages/cli/src/commands/messages/context.tspackages/cli/src/commands/messages/delete.tspackages/cli/src/commands/messages/edit.tspackages/cli/src/commands/messages/export.tspackages/cli/src/commands/messages/list.tspackages/cli/src/commands/messages/search.tspackages/cli/src/commands/messages/show.tspackages/cli/src/commands/plugins.tspackages/cli/src/commands/plugins/available.tspackages/cli/src/commands/presence.tspackages/cli/src/commands/rpc.tspackages/cli/src/commands/send/file.tspackages/cli/src/commands/send/react.tspackages/cli/src/commands/send/sticker.tspackages/cli/src/commands/send/text.tspackages/cli/src/commands/send/unreact.tspackages/cli/src/commands/send/voice.tspackages/cli/src/commands/setup.tspackages/cli/src/commands/status.tspackages/cli/src/commands/targets/add/desktop.tspackages/cli/src/commands/targets/add/remote.tspackages/cli/src/commands/targets/add/server.tspackages/cli/src/commands/targets/disable.tspackages/cli/src/commands/targets/enable.tspackages/cli/src/commands/targets/list.tspackages/cli/src/commands/targets/logs.tspackages/cli/src/commands/targets/remove.tspackages/cli/src/commands/targets/restart.tspackages/cli/src/commands/targets/show.tspackages/cli/src/commands/targets/start.tspackages/cli/src/commands/targets/status.tspackages/cli/src/commands/targets/stop.tspackages/cli/src/commands/targets/use.tspackages/cli/src/commands/update.tspackages/cli/src/commands/verify.tspackages/cli/src/commands/verify/approve.tspackages/cli/src/commands/verify/cancel.tspackages/cli/src/commands/verify/list.tspackages/cli/src/commands/verify/qr-confirm.tspackages/cli/src/commands/verify/qr-scan.tspackages/cli/src/commands/verify/recovery-key.tspackages/cli/src/commands/verify/reset-recovery-key.tspackages/cli/src/commands/verify/sas-confirm.tspackages/cli/src/commands/verify/sas.tspackages/cli/src/commands/verify/show.tspackages/cli/src/commands/verify/start.tspackages/cli/src/commands/verify/status.tspackages/cli/src/commands/version.tspackages/cli/src/commands/watch.tspackages/cli/src/lib/account-login.tspackages/cli/src/lib/api-values.tspackages/cli/src/lib/app-api.tspackages/cli/src/lib/app-state.tspackages/cli/src/lib/argv.tspackages/cli/src/lib/client.tspackages/cli/src/lib/cloudflare-tunnel.tspackages/cli/src/lib/command.tspackages/cli/src/lib/copy.tspackages/cli/src/lib/desktop-auth.tspackages/cli/src/lib/did-you-mean.tspackages/cli/src/lib/env.tspackages/cli/src/lib/errors.tspackages/cli/src/lib/export.tspackages/cli/src/lib/ink/components.tsxpackages/cli/src/lib/ink/format.tspackages/cli/src/lib/ink/render.tsxpackages/cli/src/lib/ink/spinner.tsxpackages/cli/src/lib/ink/theme.tspackages/cli/src/lib/installations.tspackages/cli/src/lib/local-desktop.tspackages/cli/src/lib/manifest.tspackages/cli/src/lib/oauth.tspackages/cli/src/lib/output.tspackages/cli/src/lib/paging.tspackages/cli/src/lib/pkce.tspackages/cli/src/lib/profiles.tspackages/cli/src/lib/prompts.tspackages/cli/src/lib/recommended-plugins.tspackages/cli/src/lib/resolve.tspackages/cli/src/lib/runner.tspackages/cli/src/lib/send-message.tspackages/cli/src/lib/server-env.tspackages/cli/src/lib/setup-login.tspackages/cli/src/lib/target-status.tspackages/cli/src/lib/targets.tspackages/cli/src/lib/update-banner.tspackages/cli/src/lib/wait.tspackages/cli/src/plugin-sdk.tspackages/cli/src/types/qrcode.d.tspackages/cli/test/account-login.test.tspackages/cli/test/cli-smoke.tspackages/cli/test/cloudflare-tunnel.test.tspackages/cli/test/e2e-staging.tspackages/cli/test/e2e-staging/README.mdpackages/cli/test/errors.test.tspackages/cli/test/fixtures/fake-client.tspackages/cli/test/messages-list-filter.test.tspackages/cli/test/messages-search-validation.test.tspackages/cli/test/plugin-sdk.test.tspackages/cli/test/resolve.test.tspackages/cli/test/watch-filter.test.tspackages/cli/tsconfig.jsonpackages/npm/.gitignorepackages/npm/package.jsonpackages/npm/scripts/build.tsrun.shscripts/publish-packages.tsscripts/release.ts
💤 Files with no reviewable changes (167)
- packages/cli-plugin-cloudflare/LICENSE
- packages/cli/release-please-config.json
- packages/cli/scripts/mock
- packages/cli/scripts/test
- packages/cli/scripts/lint
- packages/cli/CHANGELOG.md
- packages/cli/beeper-setup-redesign-spec.md
- packages/cli/SECURITY.md
- packages/cli/scripts/unlink
- packages/cli-plugin-cloudflare/src/index.ts
- packages/cli/scripts/build-binaries.ts
- packages/cli/scripts/check-manifest.ts
- .changeset/config.json
- packages/cli/src/commands/chats/search.ts
- packages/cli/src/commands/version.ts
- packages/cli/scripts/check-api-copy.ts
- packages/cli/src/commands/plugins.ts
- packages/cli/src/commands/config/path.ts
- packages/cli/src/commands/contacts/list.ts
- packages/cli/src/commands/update.ts
- packages/cli-plugin-cloudflare/package.json
- packages/cli/src/lib/client.ts
- packages/cli/src/lib/pkce.ts
- packages/cli/src/lib/runner.ts
- packages/cli/src/commands/status.ts
- packages/cli/src/commands/rpc.ts
- packages/cli/src/lib/env.ts
- packages/cli/src/commands/auth/status.ts
- packages/cli/src/lib/copy.ts
- packages/cli/scripts/build-homebrew-archive.ts
- packages/cli/src/commands/accounts/list.ts
- packages/cli/src/commands/_complete.ts
- packages/cli/src/commands/auth/logout.ts
- packages/cli/src/commands/verify/status.ts
- packages/cli/scripts/run
- packages/cli/src/commands/docs.ts
- eslint.config.mjs
- packages/cli/src/commands/auth/email/start.ts
- packages/cli/src/lib/did-you-mean.ts
- packages/cli/scripts/publish-local-release.ts
- .github/workflows/release-doctor.yml
- packages/cli/src/commands/chats/show.ts
- packages/cli/src/lib/send-message.ts
- packages/cli/src/lib/wait.ts
- packages/cli/scripts/format
- packages/cli/src/lib/argv.ts
- packages/cli/src/commands/autocomplete.ts
- packages/cli/src/commands/media/download.ts
- packages/cli/src/commands/chats/list.ts
- packages/cli/src/commands/verify/list.ts
- packages/cli/src/commands/accounts/show.ts
- packages/cli/src/commands/contacts/show.ts
- packages/cli/src/commands/messages/export.ts
- packages/cli/links.txt
- packages/cli/src/commands/verify/approve.ts
- packages/cli/src/commands/verify/sas-confirm.ts
- packages/cli/scripts/build
- packages/cli/src/lib/ink/spinner.tsx
- packages/cli-plugin-cloudflare/tsconfig.json
- packages/cli/scripts/read-signing-secrets.rb
- packages/cli/src/lib/recommended-plugins.ts
- packages/cli/bin/run.js
- packages/cli-plugin-cloudflare/test/smoke.mjs
- packages/cli/src/commands/targets/add/server.ts
- packages/cli/src/commands/targets/enable.ts
- packages/cli/src/commands/verify/reset-recovery-key.ts
- packages/cli/src/lib/ink/components.tsx
- packages/cli-plugin-cloudflare/README.md
- packages/cli/bin/logo.js
- packages/cli/scripts/generate-readme.ts
- packages/cli/src/commands/doctor.ts
- packages/cli/src/commands/messages/search.ts
- packages/cli/src/lib/update-banner.ts
- packages/cli/bin/binary-bootstrap.js
- packages/cli/src/commands/targets/logs.ts
- packages/cli/src/lib/output.ts
- packages/cli/scripts/bootstrap
- packages/cli/src/commands/api/request.ts
- packages/cli/src/commands/messages/context.ts
- packages/cli/src/lib/ink/format.ts
- packages/cli/scripts/link
- packages/cli/bin/check-release-environment
- packages/cli/src/lib/server-env.ts
- packages/cli/src/commands/plugins/available.ts
- packages/cli/src/commands/verify/qr-scan.ts
- packages/cli/src/commands/chats/mark-read.ts
- packages/cli/src/commands/verify/show.ts
- packages/cli/src/commands/targets/restart.ts
- packages/cli/src/commands/targets/show.ts
- .changeset/README.md
- packages/cli/src/commands/completion.ts
- packages/cli/src/commands/config/get.ts
- packages/cli-plugin-cloudflare/src/commands/targets/tunnel.ts
- packages/cli/src/commands/chats/priority.ts
- packages/cli/src/commands/api/post.ts
- packages/cli/src/commands/chats/unmute.ts
- packages/cli/src/commands/accounts/use.ts
- packages/cli/src/commands/man.ts
- packages/cli/src/commands/send/text.ts
- packages/cli/src/lib/target-status.ts
- packages/cli/src/commands/export.ts
- packages/cli/src/commands/accounts/remove.ts
- packages/cli/scripts/publish-homebrew-formula.ts
- packages/cli/src/commands/send/react.ts
- packages/cli/src/commands/watch.ts
- packages/cli/src/commands/api/get.ts
- packages/cli/src/commands/install/desktop.ts
- packages/cli/src/commands/install/server.ts
- packages/cli/src/commands/chats/pin.ts
- packages/cli/src/commands/messages/list.ts
- packages/cli/src/commands/messages/show.ts
- packages/cli/src/commands/verify/sas.ts
- packages/cli/src/commands/accounts/add.ts
- packages/cli/src/commands/verify.ts
- packages/cli/src/commands/send/sticker.ts
- packages/cli/src/commands/targets/stop.ts
- packages/cli/src/commands/chats/start.ts
- packages/cli/src/commands/chats/focus.ts
- packages/cli/src/commands/messages/edit.ts
- packages/cli/src/commands/chats/draft.ts
- packages/cli/src/commands/targets/list.ts
- packages/cli/src/commands/chats/unremind.ts
- packages/cli/src/commands/contacts/search.ts
- packages/cli/src/commands/targets/remove.ts
- packages/cli/src/commands/chats/mark-unread.ts
- packages/cli/src/commands/messages/delete.ts
- packages/cli/src/commands/send/unreact.ts
- packages/cli/src/lib/ink/theme.ts
- packages/cli/scripts/sign-macos-binaries.ts
- packages/cli/src/commands/presence.ts
- packages/cli/src/commands/bridges/list.ts
- packages/cli/src/commands/chats/description.ts
- packages/cli/src/commands.generated.ts
- packages/cli/src/commands/chats/remind.ts
- packages/cli/src/commands/verify/recovery-key.ts
- packages/cli/src/commands/auth/email/response.ts
- packages/cli/src/lib/manifest.ts
- packages/cli/src/commands/targets/disable.ts
- packages/cli/src/commands/targets/use.ts
- packages/cli/src/commands/send/file.ts
- packages/cli/src/commands/chats/unpin.ts
- packages/cli/README.md
- packages/cli/src/commands/chats/archive.ts
- packages/cli/src/commands/chats/disappear.ts
- packages/cli/src/commands/chats/mute.ts
- packages/cli/src/commands/config/reset.ts
- packages/cli/src/commands/chats/unarchive.ts
- packages/cli/src/commands/bridges/show.ts
- packages/cli/src/commands/targets/add/desktop.ts
- packages/cli/src/commands/chats/avatar.ts
- packages/cli/src/commands/targets/add/remote.ts
- packages/cli/scripts/generate-command-map.ts
- packages/cli/src/commands/config/set.ts
- packages/cli/src/commands/send/voice.ts
- packages/cli/src/commands/targets/start.ts
- packages/cli/src/commands/verify/qr-confirm.ts
- packages/cli/src/lib/setup-login.ts
- packages/cli/src/commands/chats/notify-anyway.ts
- packages/cli/src/commands/chats/rename.ts
- packages/cli/src/lib/ink/render.tsx
- packages/cli/src/commands/verify/cancel.ts
- packages/cli/src/lib/command.ts
- packages/cli/src/commands/verify/start.ts
- packages/cli/src/commands/targets/status.ts
- packages/cli/src/commands/setup.ts
- packages/cli/src/lib/targets.ts
- packages/cli/src/lib/resolve.ts
✅ Files skipped from review due to trivial changes (1)
- packages/cli/safety-profiles/readonly.yaml
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:21.941Z
Learning: In the beeper CLI, the correct command for recovery-key verification is `beeper verify recovery-key -t <target> --key "<key>"` (top-level `verify` subcommand, `--key` flag). The `auth.md` docs previously had incorrect syntax (`beeper auth verify recovery-key --code KEY`), which was fixed in commit be4ec8b of PR `#13`.
📚 Learning: 2026-05-27T22:40:16.795Z
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:16.795Z
Learning: In beeper CLI documentation, the correct recovery-key verification command syntax is:
`beeper verify recovery-key -t <target> --key "<key>"`.
Use the top-level `verify` subcommand and pass the recovery key via the `--key` flag. Do not use the previously incorrect form like `beeper auth verify recovery-key --code KEY`—update any doc snippets to match the supported syntax.
Applied to files:
README.md
🔇 Additional comments (30)
.github/workflows/ci.yml (1)
2-2: LGTM!.github/workflows/publish-release.yml (1)
27-41: LGTM!README.md (1)
3-5: LGTM!package.json (1)
2-2: LGTM!Also applies to: 14-14, 16-16, 18-18
packages/cli/bin/cli.js (1)
2-2: LGTM!Also applies to: 4-4
packages/cli/bin/dev.js (1)
2-2: LGTM!Also applies to: 4-4
packages/cli/package.json (1)
2-2: LGTM!Also applies to: 8-8, 14-14, 16-16, 21-21, 25-25, 31-32, 37-37
packages/cli/safety-profiles/agent-safe.yaml (1)
1-23: LGTM!packages/cli/safety-profiles/full.yaml (1)
1-5: LGTM!packages/cli/src/cli/commands.ts (1)
98-738: LGTM!Also applies to: 740-767, 769-1993
packages/cli/src/cli/main.ts (1)
1-33: LGTM!packages/cli/src/cli/mcp.ts (1)
53-86: LGTM!Also applies to: 88-122
packages/cli/src/cli/output.ts (1)
1-155: LGTM!packages/cli/src/cli/parse.ts (1)
1-183: LGTM!packages/cli/src/cli/policy.ts (1)
8-13: LGTM!Also applies to: 25-53
packages/cli/src/cli/schema.ts (1)
1-103: LGTM!packages/cli/src/cli/setup.ts (1)
1-639: LGTM!packages/cli/src/lib/account-login.ts (1)
1-380: LGTM!packages/cli/src/lib/api-values.ts (1)
1-11: LGTM!packages/cli/src/lib/app-api.ts (1)
1-25: LGTM!packages/cli/src/lib/app-state.ts (1)
1-132: LGTM!packages/cli/src/lib/cloudflare-tunnel.ts (1)
1-217: LGTM!packages/cli/src/lib/errors.ts (1)
1-45: LGTM!packages/cli/src/lib/export.ts (1)
1-545: LGTM!packages/cli/src/lib/local-desktop.ts (1)
7-8: LGTM!Also applies to: 48-60, 99-108, 152-161
packages/cli/src/lib/oauth.ts (1)
1-2: LGTM!Also applies to: 25-33, 64-78
packages/cli/src/lib/paging.ts (1)
1-9: LGTM!packages/cli/src/lib/prompts.ts (1)
5-12: LGTM!Also applies to: 14-18, 20-35
packages/cli/src/cli/types.ts (1)
1-49: LGTM!packages/cli/src/lib/desktop-auth.ts (1)
1-3: LGTM!Also applies to: 35-57, 60-69
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/cli/policy.ts (1)
54-59:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRecognize Windows-style relative profile paths.
resolveProfilePath only treats custom paths as explicit when
isAbsolute(nameOrPath)ornameOrPath.includes('/')is true; Windows-style paths using\(e.g.,.\team.yamlorprofiles\team.yaml) won’t resolve and will instead fall back tosafety-profiles/<name>.yaml.Proposed fix
function resolveProfilePath(nameOrPath: string): string | undefined { - if (isAbsolute(nameOrPath) || nameOrPath.includes('/')) return existsSync(nameOrPath) ? nameOrPath : undefined + if (isAbsolute(nameOrPath) || /[\\/]/.test(nameOrPath)) { + return existsSync(nameOrPath) ? nameOrPath : undefined + } const filename = nameOrPath.endsWith('.yaml') ? nameOrPath : `${nameOrPath}.yaml` const here = dirname(fileURLToPath(import.meta.url)) const path = join(dirname(dirname(here)), 'safety-profiles', filename) return existsSync(path) ? path : undefined }🤖 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/cli/src/cli/policy.ts` around lines 54 - 59, In resolveProfilePath, detect Windows-style relative paths as explicit paths by treating backslashes the same as forward slashes; change the conditional that currently uses nameOrPath.includes('/') to check for either slash or backslash (e.g., /[\\/]/.test(nameOrPath) or nameOrPath.includes('\\') || nameOrPath.includes('/')), so inputs like ".\\team.yaml" or "profiles\\team.yaml" will call existsSync(nameOrPath) directly instead of falling back to safety-profiles resolution; keep the rest of the logic (filename creation and safety-profiles lookup) unchanged.
🤖 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 `@packages/cli/docs/commands/messages-search.md`:
- Line 21: The docs show the same flag `-a, --account <value>, --acct` used
twice with conflicting meanings; update the `messages search` docs so the flag
has a single, unambiguous purpose: either (A) filter search results by account
(e.g., "Limit search results to account selector; repeatable") or (B) set the
active account context for the command (e.g., "Select active account context for
the command"), and remove or rename the duplicate entry that describes the
alternate behavior; search for usages of `-a`, `--account`, and `--acct` in the
`messages search` documentation and make the descriptions consistent and unique
across the file.
In `@packages/cli/docs/commands/README.md`:
- Around line 5-75: The generated commands index dropped the top-level verify
commands; restore entries by adding table rows for `verify` and its `verify
recovery-key` subcommand pointing to their docs (e.g., add links to verify.md
and verify/recovery-key.md) so the recovery-key flow is discoverable; update the
description for `verify recovery-key` to mention the syntax `beeper verify
recovery-key -t <target> --key "<key>"` and reference the existing command
implementations `packages/cli/src/commands/verify.ts` and
`packages/cli/src/commands/verify/recovery-key.ts` when locating where to add
these rows.
In `@packages/cli/scripts/generate-command-docs.ts`:
- Around line 67-70: The alias rendering omits value placeholders for
non-boolean flags: update the aliases mapping (the expression using
flag.aliases?.map(...)) to append the same placeholder as the primary flag when
flag.type !== 'boolean' (e.g. map alias => `--${alias}${flag.type === 'boolean'
? '' : ` <${flag.placeholder ?? 'value'}>`}`), so flag.aliases, flag.type and
flag.placeholder are used consistently when building the joined array of flag
strings.
- Around line 7-8: The script constructs root using new URL('..',
import.meta.url).pathname which produces unsafe Windows paths; replace that with
fileURLToPath(new URL('..', import.meta.url')) to get a proper filesystem path
and then compute docsDir as join(root, 'docs', 'commands'); update any uses of
docsDir (rm, mkdir, writeFile) to rely on this corrected path. Ensure you import
fileURLToPath from 'url' at the top and keep existing join usage intact so
functions like rm, mkdir, and writeFile operate on a valid filesystem path.
In `@packages/cli/src/cli/main.ts`:
- Around line 25-30: runWithTimeout currently only races promises so timed-out
commands keep running; change it to create an AbortController and pass
controller.signal into the command execution context so the command can be
cancelled. Update the call in main.ts where you invoke command.run(...) to
include an AbortSignal field (e.g., add signal: controller.signal to the object
passed to command.run) and modify runWithTimeout to abort the controller when
the timeout elapses; also propagate the signal type through the command.run
signature and any helper functions (and have mutating helpers check
signal.aborted or listen for signal) so timeout becomes a real cancellation
boundary rather than just a raced promise.
- Around line 32-34: When parseCommand() throws before parsed is set, the
fallback flag detection only checks argv for "--json" and misses aliases; update
the fallback assignment around parsed?.globalFlags (in main.ts) so the computed
flags passed into writeError() honors JSON aliases by checking
argv.includes('--json') || argv.includes('-j') || argv.includes('--machine')
(and keep the existing events detection), so writeError(error, flags) receives
the correct machine/json flag instead of falling back to human stderr; reference
parsed, argv, writeError, and ExitCodes.Generic when making the change.
In `@packages/cli/src/cli/mcp.ts`:
- Around line 124-133: The withTimeout function uses Promise.race and does not
cancel the underlying tool execution (allowing write-capable MCP tools to
continue mutating after timeout); update withTimeout (and callers like tool.run)
to support cancellation by plumbing an AbortSignal: create an AbortController
inside withTimeout, pass controller.signal into run() (or into tool.run(...))
and call controller.abort() when the timeout fires, and ensure run
implementations check the signal or accept it; if immediate cancellation
plumbing is not possible, restrict withTimeout to only invoke read-only tools
and add a guard (e.g., a flag or API like tool.isReadOnly) to prevent wrapping
mutating tools until they support AbortSignal.
- Around line 139-142: truncate currently slices by character count and appends
"..." which can exceed maxBytes (and mis-handle multibyte UTF-8). Modify the
truncate function to ensure the returned string's Buffer.byteLength is always <=
maxBytes: if truncation is needed, reserve space for the ellipsis bytes (compute
Buffer.byteLength('...')) and iteratively reduce the slice length (or
binary-search on slice end) until Buffer.byteLength(slice + '...') <= maxBytes,
then return that concatenation; if maxBytes is too small to include the
ellipsis, return a properly trimmed prefix that fits without adding "..." (use
Buffer.byteLength throughout). Ensure all references to truncate use this
updated behavior.
In `@packages/cli/src/cli/output.ts`:
- Around line 15-16: The current pipeline applies selectFields to the full
envelope then extracts primaryResult, which causes --results-only to lose
fields; instead compute the base result first by applying flags.resultsOnly via
primaryResult to value (use primaryResult(value) when flags.resultsOnly is
true), then apply selectFields to that result if flags.select is present (call
selectFields(result, flags.select)); update the variable order around
selectFields/primaryResult accordingly so selected/result reflect the requested
flags.
In `@packages/cli/src/lib/errors.ts`:
- Around line 14-16: The exit-code constants collide because AuthRequired and
NotReady both use value 4; change one of them (either AuthRequired or NotReady)
to a unique integer (e.g., bump NotReady to 5 or AuthRequired to 5) in the
constants block (symbols: AuthRequired, NotReady, EmptyResults) and update any
header comment and relevant tests that assert the published beeper exit-codes
contract so they reference the new distinct value; ensure all references in the
repository that rely on the previous numeric value are updated to the new
constant to preserve semantics.
---
Outside diff comments:
In `@packages/cli/src/cli/policy.ts`:
- Around line 54-59: In resolveProfilePath, detect Windows-style relative paths
as explicit paths by treating backslashes the same as forward slashes; change
the conditional that currently uses nameOrPath.includes('/') to check for either
slash or backslash (e.g., /[\\/]/.test(nameOrPath) or nameOrPath.includes('\\')
|| nameOrPath.includes('/')), so inputs like ".\\team.yaml" or
"profiles\\team.yaml" will call existsSync(nameOrPath) directly instead of
falling back to safety-profiles resolution; keep the rest of the logic (filename
creation and safety-profiles lookup) unchanged.
🪄 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: a6b32504-150b-4dd9-bee8-7027428679ff
📒 Files selected for processing (85)
INTENT.mdpackages/cli/README.mdpackages/cli/docs/commands/README.mdpackages/cli/docs/commands/accounts-add.mdpackages/cli/docs/commands/accounts-list.mdpackages/cli/docs/commands/api-request.mdpackages/cli/docs/commands/auth-email-response.mdpackages/cli/docs/commands/auth-email-start.mdpackages/cli/docs/commands/auth-logout.mdpackages/cli/docs/commands/chats-archive.mdpackages/cli/docs/commands/chats-avatar.mdpackages/cli/docs/commands/chats-description.mdpackages/cli/docs/commands/chats-disappear.mdpackages/cli/docs/commands/chats-draft.mdpackages/cli/docs/commands/chats-focus.mdpackages/cli/docs/commands/chats-list.mdpackages/cli/docs/commands/chats-mute.mdpackages/cli/docs/commands/chats-notify-anyway.mdpackages/cli/docs/commands/chats-pin.mdpackages/cli/docs/commands/chats-priority.mdpackages/cli/docs/commands/chats-read.mdpackages/cli/docs/commands/chats-remind.mdpackages/cli/docs/commands/chats-rename.mdpackages/cli/docs/commands/chats-show.mdpackages/cli/docs/commands/chats-start.mdpackages/cli/docs/commands/completion.mdpackages/cli/docs/commands/config-get.mdpackages/cli/docs/commands/config-keys.mdpackages/cli/docs/commands/config-list.mdpackages/cli/docs/commands/config-path.mdpackages/cli/docs/commands/config-set.mdpackages/cli/docs/commands/config-unset.mdpackages/cli/docs/commands/contacts-list.mdpackages/cli/docs/commands/doctor.mdpackages/cli/docs/commands/exit-codes.mdpackages/cli/docs/commands/export.mdpackages/cli/docs/commands/install-desktop.mdpackages/cli/docs/commands/install-server.mdpackages/cli/docs/commands/mcp.mdpackages/cli/docs/commands/media-download.mdpackages/cli/docs/commands/messages-context.mdpackages/cli/docs/commands/messages-delete.mdpackages/cli/docs/commands/messages-edit.mdpackages/cli/docs/commands/messages-list.mdpackages/cli/docs/commands/messages-search.mdpackages/cli/docs/commands/remove-account.mdpackages/cli/docs/commands/remove-target.mdpackages/cli/docs/commands/resolve-account.mdpackages/cli/docs/commands/resolve-bridge.mdpackages/cli/docs/commands/resolve-chat.mdpackages/cli/docs/commands/resolve-contact.mdpackages/cli/docs/commands/resolve-target.mdpackages/cli/docs/commands/schema.mdpackages/cli/docs/commands/send-file.mdpackages/cli/docs/commands/send-presence.mdpackages/cli/docs/commands/send-react.mdpackages/cli/docs/commands/send-sticker.mdpackages/cli/docs/commands/send-text.mdpackages/cli/docs/commands/send-voice.mdpackages/cli/docs/commands/setup.mdpackages/cli/docs/commands/status.mdpackages/cli/docs/commands/targets-add.mdpackages/cli/docs/commands/targets-list.mdpackages/cli/docs/commands/targets-logs.mdpackages/cli/docs/commands/targets-runtime-restart.mdpackages/cli/docs/commands/targets-runtime-start.mdpackages/cli/docs/commands/targets-runtime-stop.mdpackages/cli/docs/commands/targets-tunnel.mdpackages/cli/docs/commands/use-account.mdpackages/cli/docs/commands/use-target.mdpackages/cli/docs/commands/version.mdpackages/cli/docs/commands/watch.mdpackages/cli/package.jsonpackages/cli/scripts/generate-command-docs.tspackages/cli/src/cli/commands.tspackages/cli/src/cli/main.tspackages/cli/src/cli/mcp.tspackages/cli/src/cli/output.tspackages/cli/src/cli/parse.tspackages/cli/src/cli/policy.tspackages/cli/src/cli/schema.tspackages/cli/src/cli/types.tspackages/cli/src/lib/errors.tspackages/cli/src/lib/targets.tspackages/cli/test/cli-smoke.ts
✅ Files skipped from review due to trivial changes (58)
- packages/cli/docs/commands/setup.md
- packages/cli/docs/commands/chats-draft.md
- packages/cli/docs/commands/use-target.md
- packages/cli/docs/commands/schema.md
- packages/cli/docs/commands/contacts-list.md
- packages/cli/docs/commands/targets-runtime-restart.md
- packages/cli/docs/commands/chats-archive.md
- packages/cli/docs/commands/chats-read.md
- packages/cli/docs/commands/messages-list.md
- packages/cli/docs/commands/targets-list.md
- packages/cli/docs/commands/resolve-account.md
- packages/cli/docs/commands/targets-logs.md
- packages/cli/docs/commands/chats-rename.md
- packages/cli/docs/commands/status.md
- packages/cli/docs/commands/accounts-list.md
- packages/cli/docs/commands/targets-runtime-start.md
- packages/cli/docs/commands/chats-priority.md
- packages/cli/docs/commands/send-voice.md
- packages/cli/docs/commands/resolve-target.md
- packages/cli/docs/commands/version.md
- packages/cli/docs/commands/targets-runtime-stop.md
- packages/cli/docs/commands/use-account.md
- packages/cli/docs/commands/install-desktop.md
- packages/cli/docs/commands/chats-description.md
- packages/cli/docs/commands/config-set.md
- packages/cli/docs/commands/messages-edit.md
- packages/cli/docs/commands/config-list.md
- packages/cli/docs/commands/exit-codes.md
- packages/cli/docs/commands/media-download.md
- packages/cli/docs/commands/config-keys.md
- packages/cli/docs/commands/targets-add.md
- packages/cli/docs/commands/resolve-chat.md
- packages/cli/docs/commands/resolve-contact.md
- packages/cli/docs/commands/chats-list.md
- packages/cli/docs/commands/mcp.md
- packages/cli/docs/commands/config-get.md
- packages/cli/docs/commands/chats-start.md
- packages/cli/docs/commands/chats-remind.md
- packages/cli/docs/commands/chats-focus.md
- packages/cli/docs/commands/export.md
- packages/cli/docs/commands/auth-email-response.md
- packages/cli/docs/commands/messages-context.md
- packages/cli/docs/commands/config-unset.md
- packages/cli/docs/commands/completion.md
- packages/cli/docs/commands/send-react.md
- packages/cli/docs/commands/chats-notify-anyway.md
- packages/cli/docs/commands/send-text.md
- packages/cli/docs/commands/auth-email-start.md
- packages/cli/docs/commands/remove-target.md
- packages/cli/docs/commands/remove-account.md
- packages/cli/docs/commands/chats-pin.md
- packages/cli/docs/commands/targets-tunnel.md
- packages/cli/docs/commands/chats-show.md
- packages/cli/docs/commands/messages-delete.md
- packages/cli/docs/commands/chats-disappear.md
- packages/cli/docs/commands/auth-logout.md
- packages/cli/docs/commands/install-server.md
- packages/cli/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/cli/src/cli/schema.ts
- packages/cli/package.json
- packages/cli/src/cli/parse.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:21.941Z
Learning: In the beeper CLI, the correct command for recovery-key verification is `beeper verify recovery-key -t <target> --key "<key>"` (top-level `verify` subcommand, `--key` flag). The `auth.md` docs previously had incorrect syntax (`beeper auth verify recovery-key --code KEY`), which was fixed in commit be4ec8b of PR `#13`.
📚 Learning: 2026-05-27T22:40:16.795Z
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: README.md:178-178
Timestamp: 2026-05-27T22:40:16.795Z
Learning: In beeper CLI documentation, the correct recovery-key verification command syntax is:
`beeper verify recovery-key -t <target> --key "<key>"`.
Use the top-level `verify` subcommand and pass the recovery key via the `--key` flag. Do not use the previously incorrect form like `beeper auth verify recovery-key --code KEY`—update any doc snippets to match the supported syntax.
Applied to files:
packages/cli/docs/commands/watch.mdpackages/cli/docs/commands/send-sticker.mdpackages/cli/docs/commands/README.mdpackages/cli/docs/commands/config-path.mdpackages/cli/docs/commands/accounts-add.mdpackages/cli/docs/commands/doctor.mdpackages/cli/docs/commands/send-file.mdpackages/cli/docs/commands/chats-avatar.mdpackages/cli/docs/commands/messages-search.mdpackages/cli/docs/commands/api-request.mdpackages/cli/docs/commands/chats-mute.mdpackages/cli/docs/commands/send-presence.mdpackages/cli/docs/commands/resolve-bridge.mdINTENT.md
📚 Learning: 2026-05-27T22:40:20.614Z
Learnt from: juulieen
Repo: beeper/cli PR: 13
File: packages/cli/docs/setup.md:47-47
Timestamp: 2026-05-27T22:40:20.614Z
Learning: In beeper/cli documentation, ensure the recovery-key verification command is documented as a top-level subcommand: `beeper verify recovery-key --key "RECOVERY_KEY"`. Do not document it as `beeper auth verify recovery-key --code ...`, and use the `--key` flag (not `--code`).
Applied to files:
packages/cli/docs/commands/watch.mdpackages/cli/docs/commands/send-sticker.mdpackages/cli/docs/commands/README.mdpackages/cli/docs/commands/config-path.mdpackages/cli/docs/commands/accounts-add.mdpackages/cli/docs/commands/doctor.mdpackages/cli/docs/commands/send-file.mdpackages/cli/docs/commands/chats-avatar.mdpackages/cli/docs/commands/messages-search.mdpackages/cli/docs/commands/api-request.mdpackages/cli/docs/commands/chats-mute.mdpackages/cli/docs/commands/send-presence.mdpackages/cli/docs/commands/resolve-bridge.md
🪛 LanguageTool
INTENT.md
[style] ~113-~113: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... just a second output mode to delete. - Decide whether --events is product behavior ...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~114-~114: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...s product behavior or debug plumbing. - Decide whether --wrap-untrusted and safety p...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🪛 OpenGrep (1.22.0)
packages/cli/src/cli/main.ts
[ERROR] 57-57: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
|
|
||
| | Name | Description | | ||
| | --- | --- | | ||
| | `-a, --account <value>, --acct` | Limit to account selector Repeatable. | |
There was a problem hiding this comment.
Disambiguate the duplicated --account flag docs.
Line 21 and Line 39 document the same -a, --account, --acct switch with different meanings. That makes it unclear whether messages search uses this flag to filter results, select the active account context, or both. Please clarify the distinction here or avoid documenting the same flag twice with conflicting descriptions.
Also applies to: 39-39
🤖 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/cli/docs/commands/messages-search.md` at line 21, The docs show the
same flag `-a, --account <value>, --acct` used twice with conflicting meanings;
update the `messages search` docs so the flag has a single, unambiguous purpose:
either (A) filter search results by account (e.g., "Limit search results to
account selector; repeatable") or (B) set the active account context for the
command (e.g., "Select active account context for the command"), and remove or
rename the duplicate entry that describes the alternate behavior; search for
usages of `-a`, `--account`, and `--acct` in the `messages search` documentation
and make the descriptions consistent and unique across the file.
| | Command | Description | Aliases | | ||
| | --- | --- | --- | | ||
| | [`accounts add`](accounts-add.md) | Connect a chat account by bridge | | | ||
| | [`accounts list`](accounts-list.md) | List connected accounts | | | ||
| | [`api request`](api-request.md) | Call a raw Desktop API path with any supported HTTP method | | | ||
| | [`auth email response`](auth-email-response.md) | Finish email sign-in for a target | | | ||
| | [`auth email start`](auth-email-start.md) | Start email sign-in for a target | | | ||
| | [`auth logout`](auth-logout.md) | Clear stored authentication | | | ||
| | [`chats archive`](chats-archive.md) | Archive or unarchive a chat | | | ||
| | [`chats avatar`](chats-avatar.md) | Set or clear a chat avatar | | | ||
| | [`chats description`](chats-description.md) | Set or clear a chat description | | | ||
| | [`chats disappear`](chats-disappear.md) | Set a disappearing-message timer | | | ||
| | [`chats draft`](chats-draft.md) | Set or clear a chat draft | | | ||
| | [`chats focus`](chats-focus.md) | Focus a chat in Beeper | | | ||
| | [`chats list`](chats-list.md) | List chats | `chats ls` | | ||
| | [`chats mute`](chats-mute.md) | Mute or unmute a chat | | | ||
| | [`chats notify-anyway`](chats-notify-anyway.md) | Notify a chat anyway | | | ||
| | [`chats pin`](chats-pin.md) | Pin or unpin a chat | | | ||
| | [`chats priority`](chats-priority.md) | Set chat priority | | | ||
| | [`chats read`](chats-read.md) | Mark a chat read or unread | | | ||
| | [`chats remind`](chats-remind.md) | Set or clear a chat reminder | | | ||
| | [`chats rename`](chats-rename.md) | Rename a chat | | | ||
| | [`chats show`](chats-show.md) | Show chat details | | | ||
| | [`chats start`](chats-start.md) | Start a chat | | | ||
| | [`completion`](completion.md) | Generate shell completion scripts | | | ||
| | [`config get`](config-get.md) | Get a config value | `config show` | | ||
| | [`config keys`](config-keys.md) | List available config keys | `config list-keys`, `config names` | | ||
| | [`config list`](config-list.md) | List all config values | `config ls`, `config all` | | ||
| | [`config path`](config-path.md) | Print config file path | `config where` | | ||
| | [`config set`](config-set.md) | Set a config value | `config add`, `config update` | | ||
| | [`config unset`](config-unset.md) | Unset a config value | `config rm`, `config del`, `config remove` | | ||
| | [`contacts list`](contacts-list.md) | List contacts | `contacts search`, `contacts find` | | ||
| | [`doctor`](doctor.md) | Run diagnostics for config, target reachability, auth, and readiness | | | ||
| | [`exit-codes`](exit-codes.md) | Print stable exit codes for automation | `agent exit-codes`, `exitcodes` | | ||
| | [`export`](export.md) | Export accounts, chats, messages, transcripts, and attachments | | | ||
| | [`install desktop`](install-desktop.md) | Install Beeper Desktop locally | | | ||
| | [`install server`](install-server.md) | Install Beeper Server locally | | | ||
| | [`mcp`](mcp.md) | Run a typed MCP stdio server | | | ||
| | [`media download`](media-download.md) | Download message media | | | ||
| | [`messages context`](messages-context.md) | Show a message with surrounding context | | | ||
| | [`messages delete`](messages-delete.md) | Delete a message | | | ||
| | [`messages edit`](messages-edit.md) | Edit a message | | | ||
| | [`messages list`](messages-list.md) | List chat messages | `messages ls` | | ||
| | [`messages search`](messages-search.md) | Search messages across chats | `messages find` | | ||
| | [`remove account`](remove-account.md) | Remove an account | `accounts remove`, `accounts rm` | | ||
| | [`remove target`](remove-target.md) | Remove a target | `targets remove`, `targets rm` | | ||
| | [`resolve account`](resolve-account.md) | Resolve an account selector | | | ||
| | [`resolve bridge`](resolve-bridge.md) | Resolve a bridge selector | | | ||
| | [`resolve chat`](resolve-chat.md) | Resolve a chat selector | | | ||
| | [`resolve contact`](resolve-contact.md) | Resolve a contact selector | | | ||
| | [`resolve target`](resolve-target.md) | Resolve a target selector | | | ||
| | [`schema`](schema.md) | Print machine-readable command and flag schema | `help-json`, `helpjson` | | ||
| | [`send file`](send-file.md) | Send a file message | | | ||
| | [`send presence`](send-presence.md) | Send a typing indicator | | | ||
| | [`send react`](send-react.md) | Send or remove a reaction | | | ||
| | [`send sticker`](send-sticker.md) | Send a sticker | | | ||
| | [`send text`](send-text.md) | Send a text message | | | ||
| | [`send voice`](send-voice.md) | Send a voice note | | | ||
| | [`setup`](setup.md) | Make the selected target ready for messaging | | | ||
| | [`status`](status.md) | Show selected target and setup readiness | `st` | | ||
| | [`targets add`](targets-add.md) | Add a remote Beeper Desktop or Server target | | | ||
| | [`targets list`](targets-list.md) | List configured Beeper targets | `targets ls` | | ||
| | [`targets logs`](targets-logs.md) | Print logs for a local Beeper Desktop or Server install | | | ||
| | [`targets runtime restart`](targets-runtime-restart.md) | Restart a local server runtime | | | ||
| | [`targets runtime start`](targets-runtime-start.md) | Start a local target runtime | | | ||
| | [`targets runtime stop`](targets-runtime-stop.md) | Stop a local server runtime | | | ||
| | [`targets tunnel`](targets-tunnel.md) | Expose a target through Cloudflare Tunnel | | | ||
| | [`use account`](use-account.md) | Select the default account | `accounts use` | | ||
| | [`use target`](use-target.md) | Select the default target | `targets use` | | ||
| | [`version`](version.md) | Print CLI version | | | ||
| | [`watch`](watch.md) | Stream Desktop API WebSocket events | | |
There was a problem hiding this comment.
Restore the verify command family to the generated index.
This table appears to have dropped the top-level verify commands entirely, including the recovery-key flow. That makes a still-supported path undiscoverable in the published docs, even though the CLI still has packages/cli/src/commands/verify.ts and packages/cli/src/commands/verify/recovery-key.ts.
Based on learnings: In beeper CLI documentation, the correct recovery-key verification command syntax is beeper verify recovery-key -t <target> --key "<key>"; use the top-level verify subcommand and the --key flag.
🤖 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/cli/docs/commands/README.md` around lines 5 - 75, The generated
commands index dropped the top-level verify commands; restore entries by adding
table rows for `verify` and its `verify recovery-key` subcommand pointing to
their docs (e.g., add links to verify.md and verify/recovery-key.md) so the
recovery-key flow is discoverable; update the description for `verify
recovery-key` to mention the syntax `beeper verify recovery-key -t <target>
--key "<key>"` and reference the existing command implementations
`packages/cli/src/commands/verify.ts` and
`packages/cli/src/commands/verify/recovery-key.ts` when locating where to add
these rows.
| const root = new URL('..', import.meta.url).pathname | ||
| const docsDir = join(root, 'docs', 'commands') |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the target file and print the relevant top section with line numbers
if [ -f packages/cli/scripts/generate-command-docs.ts ]; then
echo "== File: packages/cli/scripts/generate-command-docs.ts (top 80 lines) =="
nl -ba packages/cli/scripts/generate-command-docs.ts | sed -n '1,120p'
else
echo "Missing: packages/cli/scripts/generate-command-docs.ts" >&2
exit 1
fi
# Search for other uses of URL(...).pathname in the repo (to understand patterns)
echo "== Search: URL(...).pathname =="
rg -n "new URL\\([^\\)]*\\)\\.pathname|\\.pathname\\b" packages/cli/scripts packages/cli -S || true
# Search for fileURLToPath usage (to see if already standardized)
echo "== Search: fileURLToPath =="
rg -n "fileURLToPath" packages/cli -S || trueRepository: beeper/cli
Length of output: 188
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/scripts/generate-command-docs.ts"
if [ -f "$FILE" ]; then
echo "== File: $FILE (top 120 lines) =="
cat -n "$FILE" | sed -n '1,160p'
else
echo "Missing: $FILE" >&2
exit 1
fi
echo "== Search: URL(...).pathname in scripts =="
rg -n "new URL\\([^\\)]*\\)\\.pathname|\\.pathname\\b" packages/cli/scripts packages/cli -S || true
echo "== Search: fileURLToPath usage =="
rg -n "fileURLToPath" packages/cli -S || trueRepository: beeper/cli
Length of output: 7037
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/scripts/prepare-desktop-api.ts"
if [ -f "$FILE" ]; then
echo "== File: $FILE (top 40 lines) =="
cat -n "$FILE" | sed -n '1,80p'
fiRepository: beeper/cli
Length of output: 3396
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/cli/scripts/prepare-desktop-api.ts"
if [ -f "$FILE" ]; then
echo "== File: $FILE (top 80 lines) =="
cat -n "$FILE" | sed -n '1,90p'
fiRepository: beeper/cli
Length of output: 3396
Use fileURLToPath() for the script root.
packages/cli/scripts/generate-command-docs.ts derives root from new URL('..', import.meta.url).pathname, which is not a safe filesystem path on Windows (leading slash before drive letter and percent-encoding). Since root feeds into docsDir for rm, mkdir, and writeFile, doc generation can target the wrong location or fail.
Proposed fix
+import { fileURLToPath } from 'node:url'
import { mkdir, rm, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { commands } from '../src/cli/commands.js'
import { globalFlagSpecs } from '../src/cli/parse.js'
import type { ArgSpec, CommandSpec, FlagSpec } from '../src/cli/types.js'
-const root = new URL('..', import.meta.url).pathname
+const root = fileURLToPath(new URL('..', import.meta.url))
const docsDir = join(root, 'docs', 'commands')📝 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.
| const root = new URL('..', import.meta.url).pathname | |
| const docsDir = join(root, 'docs', 'commands') | |
| import { fileURLToPath } from 'node:url' | |
| import { mkdir, rm, writeFile } from 'node:fs/promises' | |
| import { join } from 'node:path' | |
| import { commands } from '../src/cli/commands.js' | |
| import { globalFlagSpecs } from '../src/cli/parse.js' | |
| import type { ArgSpec, CommandSpec, FlagSpec } from '../src/cli/types.js' | |
| const root = fileURLToPath(new URL('..', import.meta.url)) | |
| const docsDir = join(root, 'docs', 'commands') |
🤖 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/cli/scripts/generate-command-docs.ts` around lines 7 - 8, The script
constructs root using new URL('..', import.meta.url).pathname which produces
unsafe Windows paths; replace that with fileURLToPath(new URL('..',
import.meta.url')) to get a proper filesystem path and then compute docsDir as
join(root, 'docs', 'commands'); update any uses of docsDir (rm, mkdir,
writeFile) to rely on this corrected path. Ensure you import fileURLToPath from
'url' at the top and keep existing join usage intact so functions like rm,
mkdir, and writeFile operate on a valid filesystem path.
| flag.short ? `-${flag.short}` : undefined, | ||
| `--${flag.name}${flag.type === 'boolean' ? '' : ` <${flag.placeholder ?? 'value'}>`}`, | ||
| ...(flag.aliases ?? []).map(alias => `--${alias}`), | ||
| ].filter(Boolean).join(', ') |
There was a problem hiding this comment.
Render value placeholders for non-boolean aliases too.
Right now aliased valued flags are emitted like --select <value>, --fields, --project, which documents --fields and --project as bare booleans even though they take the same value as the primary flag.
Proposed fix
function flagRow(flag: FlagSpec): string {
+ const valueSuffix = flag.type === 'boolean' ? '' : ` <${flag.placeholder ?? 'value'}>`
const tokens = [
flag.short ? `-${flag.short}` : undefined,
- `--${flag.name}${flag.type === 'boolean' ? '' : ` <${flag.placeholder ?? 'value'}>`}`,
- ...(flag.aliases ?? []).map(alias => `--${alias}`),
+ `--${flag.name}${valueSuffix}`,
+ ...(flag.aliases ?? []).map(alias => `--${alias}${valueSuffix}`),
].filter(Boolean).join(', ')🤖 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/cli/scripts/generate-command-docs.ts` around lines 67 - 70, The
alias rendering omits value placeholders for non-boolean flags: update the
aliases mapping (the expression using flag.aliases?.map(...)) to append the same
placeholder as the primary flag when flag.type !== 'boolean' (e.g. map alias =>
`--${alias}${flag.type === 'boolean' ? '' : ` <${flag.placeholder ??
'value'}>`}`), so flag.aliases, flag.type and flag.placeholder are used
consistently when building the joined array of flag strings.
| const result = await runWithTimeout(() => command.run({ | ||
| args: positionals, | ||
| commandPath: command.path, | ||
| flags, | ||
| globalFlags, | ||
| }), globalFlags.timeout) |
There was a problem hiding this comment.
--timeout does not actually stop the command.
runWithTimeout() only races promises; it never cancels the in-flight command.run(). A timed-out mutating command can keep running and still apply side effects after the CLI has already reported failure.
Thread an AbortSignal through the command context and have the API/helpers honor it so timeout is an actual cancellation boundary, not just an alternate return path.
Also applies to: 38-53
🤖 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/cli/src/cli/main.ts` around lines 25 - 30, runWithTimeout currently
only races promises so timed-out commands keep running; change it to create an
AbortController and pass controller.signal into the command execution context so
the command can be cancelled. Update the call in main.ts where you invoke
command.run(...) to include an AbortSignal field (e.g., add signal:
controller.signal to the object passed to command.run) and modify runWithTimeout
to abort the controller when the timeout elapses; also propagate the signal type
through the command.run signature and any helper functions (and have mutating
helpers check signal.aborted or listen for signal) so timeout becomes a real
cancellation boundary rather than just a raced promise.
| } catch (error) { | ||
| const flags = parsed?.globalFlags ?? { events: argv.includes('--events'), json: argv.includes('--json') } | ||
| process.exitCode = writeError(error, flags) || ExitCodes.Generic |
There was a problem hiding this comment.
Honor JSON aliases in the parse-failure fallback path.
When parseCommand() throws before parsed is assigned, Line 33 only checks raw argv for --json. Requests that used -j or --machine fall back to human stderr, so machine-readable error handling breaks exactly on invalid input.
Suggested fix
- const flags = parsed?.globalFlags ?? { events: argv.includes('--events'), json: argv.includes('--json') }
+ const flags = parsed?.globalFlags ?? {
+ events: argv.includes('--events'),
+ json: argv.includes('--json') || argv.includes('-j') || argv.includes('--machine'),
+ }📝 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.
| } catch (error) { | |
| const flags = parsed?.globalFlags ?? { events: argv.includes('--events'), json: argv.includes('--json') } | |
| process.exitCode = writeError(error, flags) || ExitCodes.Generic | |
| } catch (error) { | |
| const flags = parsed?.globalFlags ?? { | |
| events: argv.includes('--events'), | |
| json: argv.includes('--json') || argv.includes('-j') || argv.includes('--machine'), | |
| } | |
| process.exitCode = writeError(error, flags) || ExitCodes.Generic |
🤖 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/cli/src/cli/main.ts` around lines 32 - 34, When parseCommand()
throws before parsed is set, the fallback flag detection only checks argv for
"--json" and misses aliases; update the fallback assignment around
parsed?.globalFlags (in main.ts) so the computed flags passed into writeError()
honors JSON aliases by checking argv.includes('--json') || argv.includes('-j')
|| argv.includes('--machine') (and keep the existing events detection), so
writeError(error, flags) receives the correct machine/json flag instead of
falling back to human stderr; reference parsed, argv, writeError, and
ExitCodes.Generic when making the change.
| async function withTimeout<T>(seconds: number, run: () => Promise<T>): Promise<T> { | ||
| if (seconds <= 0) throw new Error('--timeout-seconds must be greater than zero') | ||
| let timeout: NodeJS.Timeout | undefined | ||
| try { | ||
| return await Promise.race([ | ||
| run(), | ||
| new Promise<never>((_, reject) => { | ||
| timeout = setTimeout(() => reject(new Error(`MCP tool timed out after ${seconds}s`)), seconds * 1000) | ||
| }), | ||
| ]) |
There was a problem hiding this comment.
The timeout does not cancel the underlying tool execution.
Promise.race() only stops waiting. For write-capable MCP tools, the command can keep mutating state after the client has already received a timeout and may retry.
If cancellation is not plumbed yet, consider either passing an AbortSignal through tool.run(...) or limiting timeout-enforced MCP execution to read-only tools until commands can be interrupted safely.
🤖 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/cli/src/cli/mcp.ts` around lines 124 - 133, The withTimeout function
uses Promise.race and does not cancel the underlying tool execution (allowing
write-capable MCP tools to continue mutating after timeout); update withTimeout
(and callers like tool.run) to support cancellation by plumbing an AbortSignal:
create an AbortController inside withTimeout, pass controller.signal into run()
(or into tool.run(...)) and call controller.abort() when the timeout fires, and
ensure run implementations check the signal or accept it; if immediate
cancellation plumbing is not possible, restrict withTimeout to only invoke
read-only tools and add a guard (e.g., a flag or API like tool.isReadOnly) to
prevent wrapping mutating tools until they support AbortSignal.
| function truncate(value: string, maxBytes: number): string { | ||
| if (maxBytes <= 0) return value | ||
| return Buffer.byteLength(value) <= maxBytes ? value : `${value.slice(0, maxBytes)}...` | ||
| } |
There was a problem hiding this comment.
truncate() can exceed the configured byte cap.
When truncation happens, this returns maxBytes + 3 bytes for ASCII because ... is appended after slicing, and UTF-8 multibyte content makes the bound even less accurate. That breaks the max-output-bytes contract for MCP clients.
Proposed fix
function truncate(value: string, maxBytes: number): string {
if (maxBytes <= 0) return value
- return Buffer.byteLength(value) <= maxBytes ? value : `${value.slice(0, maxBytes)}...`
+ const bytes = Buffer.from(value)
+ if (bytes.byteLength <= maxBytes) return value
+ if (maxBytes <= 3) return '.'.repeat(maxBytes)
+ return `${bytes.subarray(0, maxBytes - 3).toString('utf8')}...`
}🤖 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/cli/src/cli/mcp.ts` around lines 139 - 142, truncate currently
slices by character count and appends "..." which can exceed maxBytes (and
mis-handle multibyte UTF-8). Modify the truncate function to ensure the returned
string's Buffer.byteLength is always <= maxBytes: if truncation is needed,
reserve space for the ellipsis bytes (compute Buffer.byteLength('...')) and
iteratively reduce the slice length (or binary-search on slice end) until
Buffer.byteLength(slice + '...') <= maxBytes, then return that concatenation; if
maxBytes is too small to include the ellipsis, return a properly trimmed prefix
that fits without adding "..." (use Buffer.byteLength throughout). Ensure all
references to truncate use this updated behavior.
| const selected = flags.select ? selectFields(value, flags.select) : value | ||
| const result = flags.resultsOnly ? primaryResult(selected) : selected |
There was a problem hiding this comment.
Apply --results-only before --select.
Right now selectFields() runs on the full envelope, so --json --results-only --select id,text collapses to {} for responses shaped like { ok: true, data: [...] }. That breaks the new machine-readable flow on enveloped results.
💡 Suggested fix
if (flags.json) {
- const selected = flags.select ? selectFields(value, flags.select) : value
- const result = flags.resultsOnly ? primaryResult(selected) : selected
- const data = flags.wrapUntrusted ? wrapUntrusted(result) : result
+ const result = flags.resultsOnly ? primaryResult(value) : value
+ const selected = flags.select ? selectFields(result, flags.select) : result
+ const data = flags.wrapUntrusted ? wrapUntrusted(selected) : selected
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
} else writeText(value, flags.plain, command, flags.full)📝 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.
| const selected = flags.select ? selectFields(value, flags.select) : value | |
| const result = flags.resultsOnly ? primaryResult(selected) : selected | |
| if (flags.json) { | |
| const result = flags.resultsOnly ? primaryResult(value) : value | |
| const selected = flags.select ? selectFields(result, flags.select) : result | |
| const data = flags.wrapUntrusted ? wrapUntrusted(selected) : selected | |
| process.stdout.write(`${JSON.stringify(data, null, 2)}\n`) | |
| } else writeText(value, flags.plain, command, flags.full) |
🤖 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/cli/src/cli/output.ts` around lines 15 - 16, The current pipeline
applies selectFields to the full envelope then extracts primaryResult, which
causes --results-only to lose fields; instead compute the base result first by
applying flags.resultsOnly via primaryResult to value (use primaryResult(value)
when flags.resultsOnly is true), then apply selectFields to that result if
flags.select is present (call selectFields(result, flags.select)); update the
variable order around selectFields/primaryResult accordingly so selected/result
reflect the requested flags.
| EmptyResults: 3, | ||
| AuthRequired: 4, | ||
| NotReady: 4, |
There was a problem hiding this comment.
Give AuthRequired and NotReady distinct exit codes.
These constants now collide on value 4, but beeper exit-codes still exposes auth_required and not_ready as separate states. Automation can no longer tell “log in first” from “setup is incomplete”.
Pick a unique code for one of them and update the header comment/tests to match the published contract.
🤖 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/cli/src/lib/errors.ts` around lines 14 - 16, The exit-code constants
collide because AuthRequired and NotReady both use value 4; change one of them
(either AuthRequired or NotReady) to a unique integer (e.g., bump NotReady to 5
or AuthRequired to 5) in the constants block (symbols: AuthRequired, NotReady,
EmptyResults) and update any header comment and relevant tests that assert the
published beeper exit-codes contract so they reference the new distinct value;
ensure all references in the repository that rely on the previous numeric value
are updated to the new constant to preserve semantics.
| AuthRequired: 3, | ||
| EmptyResults: 3, | ||
| AuthRequired: 4, | ||
| NotReady: 4, |
There was a problem hiding this comment.
Critical: AuthRequired and NotReady collide on value 4. When EmptyResults was inserted at 3, AuthRequired was bumped to 4 but NotReady was left at 4 — the two now share the same exit code. In packages/cli/src/cli/output.ts:errorCode() the AuthRequired branch runs first, so every NotReady failure surfaces as code: "auth_required" to JSON consumers. The new beeper exit-codes command (commands.ts:963-977) still publishes them as two distinct codes, so the documented contract lies. The header comment at L1-9 also dropped its line for not_ready entirely. Bump one of them to a free integer (e.g., NotReady: 7) and restore the header line.
| NotReady: 4, | |
| AuthRequired: 4, | |
| NotReady: 7, |
Summary
docs/, refresh the top-level README, and regenerate the CLI README/docs with links to the published docs.beeper schema, command aliases likebeeper send,beeper search, andbeeper ls, command metadata, broader--dry-runcoverage for mutating commands, and machine-readable output controls for formats, field selection, results-only output, text, and ids.ok/error, improve resolver and schema validation errors, and make spinners respect machine-readable output modes.local,dev,staging, andprod.Testing
messages searchvalidation test updated for the new structured error payload.