feat: add recoup research command with 25 subcommands#20
feat: add recoup research command with 25 subcommands#20sidneyswift wants to merge 6 commits intomainfrom
Conversation
Full CLI for the research API — search, profile, metrics, audience, cities, similar, urls, instagram-posts, playlists, albums, tracks, career, insights, lookup, track, playlist, curator, discover, genres, festivals, web, report, people, extract, enrich. All subcommands support --json for agent consumption. Artist-scoped commands accept names or UUIDs. Tested E2E against local API with live Chartmetric data. Made-with: Cursor
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 49 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds a new top-level Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI as CLI Parser
participant Research as Research Command
participant API as API /api/research/*
participant Output as Formatter
User->>CLI: run "recoup research <subcommand> [args] [--json|--limit|--type]"
CLI->>Research: dispatch to selected subcommand
Research->>Research: validate args/options (e.g., --schema JSON)
Research->>API: GET or POST /api/research/...
API-->>Research: response (data or error)
Research->>Output: choose JSON or formatted view
Output-->>User: print results or Research->>User: printError on failure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/commands/research.ts (4)
361-362: Variable namedeepCommanddoesn't match command name "report".The variable is named
deepCommandbut creates a Command with name"report". Consider renaming toreportCommandfor consistency with other commands where variable names match command names.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 361 - 362, The variable deepCommand is misnamed for the Command("report") instance; rename the variable from deepCommand to reportCommand so the identifier matches the command name and aligns with surrounding command variable naming conventions (update any references to deepCommand to reportCommand).
434-434: UnreachablereturnafterprintError.
printErrorcallsprocess.exit(1)(persrc/output.ts), making thereturnstatement unreachable. While not a functional issue, it can be removed for clarity.🧹 Proposed fix
- try { schema = JSON.parse(opts.schema); } catch { printError("--schema must be valid JSON"); return; } + try { schema = JSON.parse(opts.schema); } catch { printError("--schema must be valid JSON"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` at line 434, The catch block around JSON.parse(opts.schema) contains an unreachable "return" after calling printError("--schema must be valid JSON") because printError exits the process; remove the redundant "return" in the catch clause (locate the try/catch that sets schema from opts.schema and the printError call) so the code is clearer and free of dead statements.
31-31: Consider extracting error handling wrapper to reduce duplication.The
try { ... } catch (err) { printError((err as Error).message); }pattern is repeated in every command action. A helper function could reduce boilerplate:♻️ Example helper pattern
async function withErrorHandling<T>(fn: () => Promise<T>): Promise<T | void> { try { return await fn(); } catch (err) { printError((err as Error).message); } } // Usage in command action: .action(async (artist, opts) => withErrorHandling(async () => { const data = await get("/api/research/profile", { artist }); if (opts.json) return printJson(data); // formatted output... }));Also applies to: 46-46, 59-59, 72-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` at line 31, Extract the repeated try/catch into a reusable helper function (e.g., withErrorHandling<T>(fn: () => Promise<T>): Promise<T | void>) that wraps awaiting fn() in try { await fn(); } catch (err) { printError((err as Error).message); } and return its result; then replace each command .action(...) body (the anonymous async callbacks that currently contain try/catch) with a call to withErrorHandling(() => /* original body without try/catch */) so all handlers (the ones using printError and the command action callbacks) delegate error printing to the new helper while preserving return values and behavior.
54-60: Non-JSON output path still outputs JSON.Several commands (metrics, audience, urls, instagram-posts, playlists, career, lookup, track, playlist, curator) check
opts.jsonbut then callprintJson(data)in both branches, making the--jsonflag ineffective. For consistency with commands likecitiesandsimilarthat provide formatted table output, consider implementing human-readable formatting for the non-JSON path.Example pattern seen in
metricsCommand:if (opts.json) return printJson(data); printJson(data); // ← Should be formatted output🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 54 - 60, The non-JSON branch incorrectly calls printJson(data) in the .action callback for the get("/api/research/metrics", ...) call, making the --json flag ineffective; update the callback so when opts.json is true it returns printJson(data), otherwise format and display a human-readable view (e.g., table or pretty print) instead of printJson — reuse or create a formatter like printMetrics/printTable or a shared prettyPrint function, and ensure printError is still used in the catch block for errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/research.ts`:
- Around line 282-296: The --tiktok-followers option is declared but not used:
add handling in the action similar to the existing spotifyListeners block by
checking opts.tiktokFollowers, splitting it on "," into min and max, and setting
corresponding params entries (e.g., params.tt_followers_min = min and
params.tt_followers_max = max) so the filter is sent to the API; ensure you use
the exact API parameter names expected by the backend (replace
tt_followers_min/tt_followers_max with the correct names if different) and keep
the pattern used with opts.spotifyListeners and the params object.
---
Nitpick comments:
In `@src/commands/research.ts`:
- Around line 361-362: The variable deepCommand is misnamed for the
Command("report") instance; rename the variable from deepCommand to
reportCommand so the identifier matches the command name and aligns with
surrounding command variable naming conventions (update any references to
deepCommand to reportCommand).
- Line 434: The catch block around JSON.parse(opts.schema) contains an
unreachable "return" after calling printError("--schema must be valid JSON")
because printError exits the process; remove the redundant "return" in the catch
clause (locate the try/catch that sets schema from opts.schema and the
printError call) so the code is clearer and free of dead statements.
- Line 31: Extract the repeated try/catch into a reusable helper function (e.g.,
withErrorHandling<T>(fn: () => Promise<T>): Promise<T | void>) that wraps
awaiting fn() in try { await fn(); } catch (err) { printError((err as
Error).message); } and return its result; then replace each command .action(...)
body (the anonymous async callbacks that currently contain try/catch) with a
call to withErrorHandling(() => /* original body without try/catch */) so all
handlers (the ones using printError and the command action callbacks) delegate
error printing to the new helper while preserving return values and behavior.
- Around line 54-60: The non-JSON branch incorrectly calls printJson(data) in
the .action callback for the get("/api/research/metrics", ...) call, making the
--json flag ineffective; update the callback so when opts.json is true it
returns printJson(data), otherwise format and display a human-readable view
(e.g., table or pretty print) instead of printJson — reuse or create a formatter
like printMetrics/printTable or a shared prettyPrint function, and ensure
printError is still used in the catch block for errors.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4e5dde96-52bc-4567-a7a3-35ed89ac96cf
📒 Files selected for processing (2)
src/bin.tssrc/commands/research.ts
| .option("--tiktok-followers <range>", "min,max followers") | ||
| .option("--sort <field>", "Sort field") | ||
| .option("--limit <n>", "Max results", "20") | ||
| .option("--json", "Output as JSON") | ||
| .action(async (opts) => { | ||
| try { | ||
| const params: Record<string, string> = { limit: opts.limit }; | ||
| if (opts.country) params.country = opts.country; | ||
| if (opts.genre) params.genre = opts.genre; | ||
| if (opts.sort) params.sort = opts.sort; | ||
| if (opts.spotifyListeners) { | ||
| const [min, max] = opts.spotifyListeners.split(","); | ||
| if (min) params.sp_monthly_listeners_min = min; | ||
| if (max) params.sp_monthly_listeners_max = max; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check API documentation or other commands for TikTok parameter naming convention
rg -n "tiktok" --type=ts -C2Repository: recoupable/cli
Length of output: 1371
🏁 Script executed:
# Examine the discover command's full action handler around line 282
sed -n '277,310p' src/commands/research.tsRepository: recoupable/cli
Length of output: 1607
🏁 Script executed:
# Search for other TikTok-related parameter handling to understand naming convention
rg -n "tiktok.*min|tiktok.*max" --type=ts -C3Repository: recoupable/cli
Length of output: 679
🏁 Script executed:
# Check if tiktokFollowers is referenced anywhere in the file
rg -n "tiktokFollowers" src/commands/research.tsRepository: recoupable/cli
Length of output: 40
🏁 Script executed:
# Look for API documentation, tests, or comments that might indicate correct parameter names
find . -type f \( -name "*.md" -o -name "*.test.ts" -o -name "*.spec.ts" \) -exec grep -l "tiktok" {} \;Repository: recoupable/cli
Length of output: 40
🏁 Script executed:
# Check if there are any other research-related files or API mocks that show parameter names
fd -t f "research\|api\|discover" -x grep -l "tiktok" {} \;Repository: recoupable/cli
Length of output: 40
🏁 Script executed:
# Search for any documentation or comments that mention TikTok API parameters
rg -i "tiktok.*param|follower.*param|api.*tiktok" --type=ts --type=mdRepository: recoupable/cli
Length of output: 40
--tiktok-followers option is defined but never used.
The --tiktok-followers option is declared on line 282 but is not handled in the action. Users who pass this option will see no effect on results. Follow the same pattern used for --spotify-listeners to process this option, but verify the correct API parameter names for TikTok follower filtering against the API documentation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/research.ts` around lines 282 - 296, The --tiktok-followers
option is declared but not used: add handling in the action similar to the
existing spotifyListeners block by checking opts.tiktokFollowers, splitting it
on "," into min and max, and setting corresponding params entries (e.g.,
params.tt_followers_min = min and params.tt_followers_max = max) so the filter
is sent to the API; ensure you use the exact API parameter names expected by the
backend (replace tt_followers_min/tt_followers_max with the correct names if
different) and keep the pattern used with opts.spotifyListeners and the params
object.
Added Examples sections to --help for research (root), metrics, similar, playlists, discover, and enrich subcommands. Root command now shows help instead of manual usage text when called with no arguments. Follows CLI for Agents pattern: every --help includes real copy-pasteable invocations so agents can pattern-match. Made-with: Cursor
Every subcommand now has copy-pasteable example invocations in its --help output per CLI for Agents best practices. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/commands/research.ts (1)
310-330:⚠️ Potential issue | 🟠 Major
--tiktok-followersis currently a no-op.Line 310 defines the flag, but Lines 321-330 never map
opts.tiktokFollowersinto request params, so user input is silently ignored.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 310 - 330, The --tiktok-followers flag is declared but never forwarded to the request params; update the action handler for the command (the anonymous async .action) to parse opts.tiktokFollowers like opts.spotifyListeners (split on "," into min/max) and set the corresponding request parameters (e.g., tiktok_followers_min and tiktok_followers_max) on the params object before calling get("/api/research/discover", params). Ensure you guard empty values the same way as the spotifyListeners handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/research.ts`:
- Around line 375-381: Validate the numeric CLI flags before using parseInt on
opts.maxResults (and the other flag parsed at the later block) inside the
.action handler; call parseInt and check for NaN (or use Number.isInteger after
parsing) and either set a safe default or return/throw a user-facing error, then
only include the numeric value in the request body (body.max_results and the
other numeric field) when the value is valid; update the code paths that build
body to rely on the validated variable instead of directly serializing
parseInt(opts.*).
- Line 504: Change the exported constant researchCommand (currently set to
searchCommand) into an exported factory function named exportResearchCommand (or
getResearchCommand) that returns the command instance by calling/returning
searchCommand so the module exports a function rather than a constant; update
any callers that import researchCommand to call the new factory (e.g., replace
import { researchCommand } ... and usage of researchCommand with import {
exportResearchCommand } ... and call exportResearchCommand()) and ensure
types/signatures match the original command type returned from searchCommand.
---
Duplicate comments:
In `@src/commands/research.ts`:
- Around line 310-330: The --tiktok-followers flag is declared but never
forwarded to the request params; update the action handler for the command (the
anonymous async .action) to parse opts.tiktokFollowers like
opts.spotifyListeners (split on "," into min/max) and set the corresponding
request parameters (e.g., tiktok_followers_min and tiktok_followers_max) on the
params object before calling get("/api/research/discover", params). Ensure you
guard empty values the same way as the spotifyListeners handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| searchCommand.addCommand(extractCommand); | ||
| searchCommand.addCommand(enrichCommand); | ||
|
|
||
| export const researchCommand = searchCommand; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Export a function instead of a constant to satisfy the SRP export rule.
This file currently exports a constant (researchCommand) instead of one exported function. Please switch to an exported factory function (and update the caller) to align with repository standards.
As per coding guidelines: src/**/*.ts: Follow Single Responsibility Principle with one exported function per file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/research.ts` at line 504, Change the exported constant
researchCommand (currently set to searchCommand) into an exported factory
function named exportResearchCommand (or getResearchCommand) that returns the
command instance by calling/returning searchCommand so the module exports a
function rather than a constant; update any callers that import researchCommand
to call the new factory (e.g., replace import { researchCommand } ... and usage
of researchCommand with import { exportResearchCommand } ... and call
exportResearchCommand()) and ensure types/signatures match the original command
type returned from searchCommand.
There was a problem hiding this comment.
♻️ Duplicate comments (3)
src/commands/research.ts (3)
584-584: 🛠️ Refactor suggestion | 🟠 MajorExport a function (factory/getter) instead of exporting the command constant.
Line 584 exports a constant; switch to one exported function and return the command instance.
💡 Proposed fix
-export const researchCommand = searchCommand; +export function exportResearchCommand(): Command { + return searchCommand; +}As per coding guidelines,
src/**/*.ts: Follow Single Responsibility Principle with one exported function per file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` at line 584, The file currently exports a constant alias export const researchCommand = searchCommand; — change this to export a function that returns the command instance (e.g. export function getResearchCommand() { return searchCommand; }) so the module exposes a getter/factory instead of a direct constant; update the symbol name to getResearchCommand (or similar) and ensure callers import and invoke that function to obtain the command instance; locate and replace references to researchCommand with calls to the new function.
446-447:⚠️ Potential issue | 🟡 MinorValidate numeric flags before using
parseInt.Line 446 and Line 493 parse user input without validation. Invalid values become
NaNand can produce confusing downstream API behavior.💡 Proposed fix
+function parsePositiveInt(value: string, flag: string): number { + const n = Number(value); + if (!Number.isInteger(n) || n <= 0) { + throw new Error(`${flag} must be a positive integer`); + } + return n; +} + const webCommand = new Command("web") @@ - const body: Record<string, unknown> = { query, max_results: parseInt(opts.maxResults) }; + const body: Record<string, unknown> = { + query, + max_results: parsePositiveInt(opts.maxResults, "--max-results"), + }; @@ const peopleCommand = new Command("people") @@ - const data = await post("/api/research/people", { query, num_results: parseInt(opts.numResults) }); + const data = await post("/api/research/people", { + query, + num_results: parsePositiveInt(opts.numResults, "--num-results"), + });#!/bin/bash # Confirm unvalidated numeric parsing callsites. rg -n --type=ts -C2 'parseInt\(opts\.(maxResults|numResults)\)' src/commands/research.ts # Check whether any numeric validation helper already exists in this file. rg -n --type=ts -C2 'Number\.isInteger|isNaN|parsePositiveInt|must be a positive integer' src/commands/research.tsAlso applies to: 493-493
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 446 - 447, The code calls parseInt on user flags opts.maxResults and opts.numResults without validation (used when building body and elsewhere), which can produce NaN; before calling parseInt, validate the raw flag value (opts.maxResults / opts.numResults) is a finite integer (and positive if required) using Number(value) / Number.isInteger or attempt parseInt then check !Number.isNaN(parsed) and parsed > 0, and if invalid either set a safe default or return a clear CLI error; update the sites where parseInt(opts.maxResults) and parseInt(opts.numResults) are used (the body construction and the other parse site) to perform this validation and use the validated numeric variable instead of calling parseInt inline.
363-383:⚠️ Potential issue | 🟠 Major
--tiktok-followersis declared but never applied to request params.Line 363 defines the flag, but no mapping is added before the request at Line 383, so this filter is currently ignored.
💡 Proposed fix
if (opts.spotifyListeners) { const [min, max] = opts.spotifyListeners.split(","); if (min) params.sp_monthly_listeners_min = min; if (max) params.sp_monthly_listeners_max = max; } + if (opts.tiktokFollowers) { + const [min, max] = opts.tiktokFollowers.split(","); + // Replace with exact backend keys if different. + if (min) params.tt_followers_min = min; + if (max) params.tt_followers_max = max; + } const data = await get("/api/research/discover", params);#!/bin/bash # Verify option is declared and whether it's mapped into params. rg -n --type=ts -C3 'tiktok-followers|tiktokFollowers|sp_monthly_listeners_min|followers_min|followers_max' src/commands/research.ts # Verify expected backend/docs naming for discover follower filters. rg -n --type=md -C2 'research discover|tiktok|followers|min|max'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 363 - 383, The --tiktok-followers flag (opts.tiktokFollowers) is declared but never added to the request params; fix by parsing opts.tiktokFollowers (split on ",") and set the appropriate query keys on the params object (e.g. params.tiktok_followers_min and params.tiktok_followers_max or backend-expected followers_min/followers_max) before calling get("/api/research/discover", params); update the code near the spotifyListeners handling where opts.spotifyListeners is parsed so the tiktok parsing mirrors that logic and uses the same naming convention expected by the API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/commands/research.ts`:
- Line 584: The file currently exports a constant alias export const
researchCommand = searchCommand; — change this to export a function that returns
the command instance (e.g. export function getResearchCommand() { return
searchCommand; }) so the module exposes a getter/factory instead of a direct
constant; update the symbol name to getResearchCommand (or similar) and ensure
callers import and invoke that function to obtain the command instance; locate
and replace references to researchCommand with calls to the new function.
- Around line 446-447: The code calls parseInt on user flags opts.maxResults and
opts.numResults without validation (used when building body and elsewhere),
which can produce NaN; before calling parseInt, validate the raw flag value
(opts.maxResults / opts.numResults) is a finite integer (and positive if
required) using Number(value) / Number.isInteger or attempt parseInt then check
!Number.isNaN(parsed) and parsed > 0, and if invalid either set a safe default
or return a clear CLI error; update the sites where parseInt(opts.maxResults)
and parseInt(opts.numResults) are used (the body construction and the other
parse site) to perform this validation and use the validated numeric variable
instead of calling parseInt inline.
- Around line 363-383: The --tiktok-followers flag (opts.tiktokFollowers) is
declared but never added to the request params; fix by parsing
opts.tiktokFollowers (split on ",") and set the appropriate query keys on the
params object (e.g. params.tiktok_followers_min and params.tiktok_followers_max
or backend-expected followers_min/followers_max) before calling
get("/api/research/discover", params); update the code near the spotifyListeners
handling where opts.spotifyListeners is parsed so the tiktok parsing mirrors
that logic and uses the same naming convention expected by the API.
5 new CLI commands with examples in --help. Total: 30 subcommands. Made-with: Cursor
Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/commands/research.ts (3)
690-690: 🛠️ Refactor suggestion | 🟠 MajorExport a factory function instead of exporting a command constant.
This currently violates the repository SRP export rule for TypeScript files.
As per coding guidelines:
src/**/*.ts: Follow Single Responsibility Principle with one exported function per file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` at line 690, The file currently exports a constant alias "researchCommand = searchCommand", violating the SRP export rule; replace this with an exported factory function (e.g., export function createResearchCommand() { return searchCommand; } or construct and return the command inside) and update any call sites to invoke createResearchCommand() instead of importing researchCommand; ensure the new exported symbol name (createResearchCommand) is used throughout and remove the direct constant export of researchCommand.
446-447:⚠️ Potential issue | 🟡 MinorValidate numeric flags before
parseIntpayload serialization.Invalid values for
--max-results/--num-resultsbecomeNaNand can serialize asnull, leading to opaque API errors.Proposed fix
+function parsePositiveInt(value: string, flag: string): number { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) { + throw new Error(`${flag} must be a positive integer`); + } + return parsed; +} + const webCommand = new Command("web") @@ - const body: Record<string, unknown> = { query, max_results: parseInt(opts.maxResults) }; + const body: Record<string, unknown> = { + query, + max_results: parsePositiveInt(opts.maxResults, "--max-results"), + }; @@ const peopleCommand = new Command("people") @@ - const data = await post("/api/research/people", { query, num_results: parseInt(opts.numResults) }); + const data = await post("/api/research/people", { + query, + num_results: parsePositiveInt(opts.numResults, "--num-results"), + });Also applies to: 493-493
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 446 - 447, Validate numeric CLI flags before calling parseInt: check opts.maxResults (and the other flag used at the second occurrence) is a finite integer string (e.g., /^\d+$/ or Number.isFinite after Number(...)); if invalid, return a user-facing error or set a safe default rather than letting parseInt produce NaN. Update the payload construction where body is created (the line using parseInt(opts.maxResults)) and the similar occurrence around line ~493 to perform the validation/normalization first, then assign the validated numeric value (or omit the field) to body.max_results.
363-383:⚠️ Potential issue | 🟡 Minor
--tiktok-followersis declared but not applied to request params.Users can pass the flag, but it currently has no effect in
discoverCommand. Please map it intoparamsthe same way--spotify-listenersis handled.Please verify the exact backend parameter names expected by API PR
#366before wiring (e.g.,tt_followers_min/maxvs another naming convention).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/research.ts` around lines 363 - 383, The --tiktok-followers option is parsed but never added to the request params in the action handler; inside the async action (the callback beginning with "const params: Record<string, string> = { limit: opts.limit }" and before the get("/api/research/discover", params) call) parse opts.tiktokFollowers the same way as opts.spotifyListeners (split on "," to get min and max) and set the corresponding request parameters; confirm and use the exact backend parameter names from API PR `#366` (e.g., tt_followers_min / tt_followers_max or whatever the API expects) when assigning keys into params.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/research.ts`:
- Around line 507-523: The action handler for the ".argument(\"<urls...>\")"
extract command doesn't enforce the documented max 10 URLs; add a guard at the
start of the .action(async (urls, opts) => { ... }) block that checks if
urls.length > 10 and fails fast (e.g., print an error message and exit or throw)
before constructing body or calling post("/api/research/extract"); ensure the
error message references the 10-URL limit and avoid sending the request when the
guard triggers so UX is clearer.
---
Duplicate comments:
In `@src/commands/research.ts`:
- Line 690: The file currently exports a constant alias "researchCommand =
searchCommand", violating the SRP export rule; replace this with an exported
factory function (e.g., export function createResearchCommand() { return
searchCommand; } or construct and return the command inside) and update any call
sites to invoke createResearchCommand() instead of importing researchCommand;
ensure the new exported symbol name (createResearchCommand) is used throughout
and remove the direct constant export of researchCommand.
- Around line 446-447: Validate numeric CLI flags before calling parseInt: check
opts.maxResults (and the other flag used at the second occurrence) is a finite
integer string (e.g., /^\d+$/ or Number.isFinite after Number(...)); if invalid,
return a user-facing error or set a safe default rather than letting parseInt
produce NaN. Update the payload construction where body is created (the line
using parseInt(opts.maxResults)) and the similar occurrence around line ~493 to
perform the validation/normalization first, then assign the validated numeric
value (or omit the field) to body.max_results.
- Around line 363-383: The --tiktok-followers option is parsed but never added
to the request params in the action handler; inside the async action (the
callback beginning with "const params: Record<string, string> = { limit:
opts.limit }" and before the get("/api/research/discover", params) call) parse
opts.tiktokFollowers the same way as opts.spotifyListeners (split on "," to get
min and max) and set the corresponding request parameters; confirm and use the
exact backend parameter names from API PR `#366` (e.g., tt_followers_min /
tt_followers_max or whatever the API expects) when assigning keys into params.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| .argument("<urls...>", "URLs to extract (max 10)") | ||
| .option("--objective <text>", "What information to focus on") | ||
| .option("--full-content", "Return full page instead of excerpts") | ||
| .option("--json", "Output as JSON") | ||
| .addHelpText("after", ` | ||
| Examples: | ||
| recoup research extract "https://en.wikipedia.org/wiki/Drake_(musician)" | ||
| recoup research extract "https://example.com/page" --objective "funding and investors" | ||
| recoup research extract "https://a.com" "https://b.com" --full-content --json`) | ||
| .action(async (urls, opts) => { | ||
| try { | ||
| const body: Record<string, unknown> = { urls }; | ||
| if (opts.objective) body.objective = opts.objective; | ||
| if (opts.fullContent) body.full_content = true; | ||
| const data = await post("/api/research/extract", body); | ||
| const results = (data.results as Record<string, unknown>[]) || []; | ||
| if (opts.json) return printJson(results); |
There was a problem hiding this comment.
Enforce the documented max URL count in extract.
Help text says “max 10”, but there’s no check before calling the API. Add a guard to fail fast client-side for clearer UX.
Proposed fix
.action(async (urls, opts) => {
try {
+ if (urls.length > 10) {
+ printError("A maximum of 10 URLs is allowed");
+ return;
+ }
const body: Record<string, unknown> = { urls };📝 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.
| .argument("<urls...>", "URLs to extract (max 10)") | |
| .option("--objective <text>", "What information to focus on") | |
| .option("--full-content", "Return full page instead of excerpts") | |
| .option("--json", "Output as JSON") | |
| .addHelpText("after", ` | |
| Examples: | |
| recoup research extract "https://en.wikipedia.org/wiki/Drake_(musician)" | |
| recoup research extract "https://example.com/page" --objective "funding and investors" | |
| recoup research extract "https://a.com" "https://b.com" --full-content --json`) | |
| .action(async (urls, opts) => { | |
| try { | |
| const body: Record<string, unknown> = { urls }; | |
| if (opts.objective) body.objective = opts.objective; | |
| if (opts.fullContent) body.full_content = true; | |
| const data = await post("/api/research/extract", body); | |
| const results = (data.results as Record<string, unknown>[]) || []; | |
| if (opts.json) return printJson(results); | |
| .argument("<urls...>", "URLs to extract (max 10)") | |
| .option("--objective <text>", "What information to focus on") | |
| .option("--full-content", "Return full page instead of excerpts") | |
| .option("--json", "Output as JSON") | |
| .addHelpText("after", ` | |
| Examples: | |
| recoup research extract "https://en.wikipedia.org/wiki/Drake_(musician)" | |
| recoup research extract "https://example.com/page" --objective "funding and investors" | |
| recoup research extract "https://a.com" "https://b.com" --full-content --json`) | |
| .action(async (urls, opts) => { | |
| try { | |
| if (urls.length > 10) { | |
| printError("A maximum of 10 URLs is allowed"); | |
| return; | |
| } | |
| const body: Record<string, unknown> = { urls }; | |
| if (opts.objective) body.objective = opts.objective; | |
| if (opts.fullContent) body.full_content = true; | |
| const data = await post("/api/research/extract", body); | |
| const results = (data.results as Record<string, unknown>[]) || []; | |
| if (opts.json) return printJson(results); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/research.ts` around lines 507 - 523, The action handler for the
".argument(\"<urls...>\")" extract command doesn't enforce the documented max 10
URLs; add a guard at the start of the .action(async (urls, opts) => { ... })
block that checks if urls.length > 10 and fails fast (e.g., print an error
message and exit or throw) before constructing body or calling
post("/api/research/extract"); ensure the error message references the 10-URL
limit and avoid sending the request when the guard triggers so UX is clearer.
Made-with: Cursor
Summary
recoup researchcommand with 25 subcommands covering Chartmetric analytics, Perplexity web/deep research, Exa people search, and Parallel extract/enrich--jsonfor agent consumptionCommands
Depends on
Test plan
recoup research --helpshows all 25 subcommandsrecoup research "Kaash Paige"returns search resultsrecoup research cities "Kaash Paige"returns formatted tablerecoup research similar "Kaash Paige"returns competitors with stagesMade with Cursor
Summary by CodeRabbit