Skip to content

feat: add recoup research command with 25 subcommands#20

Open
sidneyswift wants to merge 6 commits intomainfrom
feature/research-command
Open

feat: add recoup research command with 25 subcommands#20
sidneyswift wants to merge 6 commits intomainfrom
feature/research-command

Conversation

@sidneyswift
Copy link
Copy Markdown
Contributor

@sidneyswift sidneyswift commented Mar 29, 2026

Summary

  • New recoup research command with 25 subcommands covering Chartmetric analytics, Perplexity web/deep research, Exa people search, and Parallel extract/enrich
  • All subcommands support --json for agent consumption
  • Artist-scoped commands accept names or UUIDs — the API resolves internally

Commands

recoup research "Drake"                              # search
recoup research cities "Drake"                       # cities
recoup research similar "Drake" --audience high      # competitors
recoup research metrics "Drake" --source spotify     # streaming data
recoup research web "Drake brand partnerships"       # web search
recoup research report "Drake"                       # deep research
recoup research people "A&R reps Atlantic Records"   # people search
recoup research extract "https://..." --objective X  # scrape URL
recoup research enrich "Drake" --schema '{...}'      # structured data

Depends on

Test plan

  • Build succeeds (tsup)
  • recoup research --help shows all 25 subcommands
  • recoup research "Kaash Paige" returns search results
  • recoup research cities "Kaash Paige" returns formatted table
  • recoup research similar "Kaash Paige" returns competitors with stages

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Added a top-level "research" CLI command with many subcommands for querying music/industry research (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, milestones, venues, rank, charts, radio).
    • Flexible output: tabular console views or JSON export.
    • Advanced options: filtering, limits, type selection, and schema-validated enrichment with clear error reporting.

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
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@sidneyswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 49 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfc8065c-5924-48cf-9217-bc3b2eaa6b67

📥 Commits

Reviewing files that changed from the base of the PR and between 56ce3fc and 0945ee8.

📒 Files selected for processing (1)
  • src/commands/research.ts
📝 Walkthrough

Walkthrough

Adds a new top-level research CLI command (exported as researchCommand) with many subcommands that call /api/research/* endpoints; src/bin.ts now registers researchCommand with the Commander CLI. Outputs are JSON or formatted console views; errors use printError.

Changes

Cohort / File(s) Summary
CLI entry
src/bin.ts
Imports and registers the new researchCommand with the Commander program via program.addCommand(researchCommand); no other CLI wiring changed.
Research command implementation
src/commands/research.ts
New exported researchCommand implementing a top-level research command and many subcommands (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, milestones, venues, rank, charts, radio, etc.). Each subcommand parses args/options (notably --schema parsed as JSON for enrich), calls GET/POST /api/research/*, handles errors via printError, and prints JSON (--json) or formatted console output (tables/blocks).

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hop through byte and branch so spry,

A new "research" trail beneath the sky,
Subcommands nibble data's root,
Tables twitch and JSON shoots,
A carrot insight — swift and sly.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new 'research' command with 25 subcommands to the CLI tool.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/research-command

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/commands/research.ts (4)

361-362: Variable name deepCommand doesn't match command name "report".

The variable is named deepCommand but creates a Command with name "report". Consider renaming to reportCommand for 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: Unreachable return after printError.

printError calls process.exit(1) (per src/output.ts), making the return statement 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.json but then call printJson(data) in both branches, making the --json flag ineffective. For consistency with commands like cities and similar that 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8746ebf and 3e1fa82.

📒 Files selected for processing (2)
  • src/bin.ts
  • src/commands/research.ts

Comment on lines +282 to +296
.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;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check API documentation or other commands for TikTok parameter naming convention
rg -n "tiktok" --type=ts -C2

Repository: 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.ts

Repository: 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 -C3

Repository: recoupable/cli

Length of output: 679


🏁 Script executed:

# Check if tiktokFollowers is referenced anywhere in the file
rg -n "tiktokFollowers" src/commands/research.ts

Repository: 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=md

Repository: 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
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/commands/research.ts (1)

310-330: ⚠️ Potential issue | 🟠 Major

--tiktok-followers is currently a no-op.

Line 310 defines the flag, but Lines 321-330 never map opts.tiktokFollowers into 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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbace50d-d872-4fe2-bcf1-5698f4d4a5a0

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1fa82 and 5fe3a23.

📒 Files selected for processing (1)
  • src/commands/research.ts

searchCommand.addCommand(extractCommand);
searchCommand.addCommand(enrichCommand);

export const researchCommand = searchCommand;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
src/commands/research.ts (3)

584-584: 🛠️ Refactor suggestion | 🟠 Major

Export 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 | 🟡 Minor

Validate numeric flags before using parseInt.

Line 446 and Line 493 parse user input without validation. Invalid values become NaN and 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.ts

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, 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-followers is 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8af43867-f2e8-44e9-a7a9-9f1dee77aa80

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe3a23 and 06adf59.

📒 Files selected for processing (1)
  • src/commands/research.ts

5 new CLI commands with examples in --help.
Total: 30 subcommands.

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/commands/research.ts (3)

690-690: 🛠️ Refactor suggestion | 🟠 Major

Export 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 | 🟡 Minor

Validate numeric flags before parseInt payload serialization.

Invalid values for --max-results / --num-results become NaN and can serialize as null, 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-followers is declared but not applied to request params.

Users can pass the flag, but it currently has no effect in discoverCommand. Please map it into params the same way --spotify-listeners is handled.

Please verify the exact backend parameter names expected by API PR #366 before wiring (e.g., tt_followers_min/max vs 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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74c67871-3e4e-4197-a913-8d82244949e9

📥 Commits

Reviewing files that changed from the base of the PR and between 06adf59 and 56ce3fc.

📒 Files selected for processing (1)
  • src/commands/research.ts

Comment on lines +507 to +523
.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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
.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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant