feat: text blast API endpoint — no more browser needed#37
Conversation
Replaced the stub with a working implementation based on browser interception of partiful.com. The endpoint is: POST https://api.partiful.com/createTextBlast Supports: - Message text (max 480 chars) - Target audience by guest status (GOING, MAYBE, DECLINED, etc.) - Show/hide on event activity feed - Dry-run mode for safe testing - Interactive confirmation before sending (skip with --yes) - Full input validation Research doc: docs/research/2026-03-24-text-blast-endpoint.md
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughAdds documentation for Partiful's createTextBlast API, implements a REST-backed Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI as "CLI Parser"
participant Rewriter as "Arg Rewriter"
participant Blasts as "blasts send"
participant Config as "Config Loader"
participant Auth as "Auth Handler"
participant API as "Partiful API"
User->>CLI: run `partiful blasts send <eventId> --message "..." --to "GOING,MAYBE"`
CLI->>Rewriter: inspect argv for deprecated forms
Rewriter-->>CLI: (rewrite or pass-through) + stderr deprecation note if rewritten
CLI->>Blasts: dispatch `blasts send` action
Blasts->>Config: load config & credentials
Config-->>Blasts: return config
Blasts->>Auth: request access token
Auth-->>Blasts: return token
Blasts->>Blasts: validate options, build payload (wrap in data.params)
Blasts->>User: prompt confirm (unless --yes)
User-->>Blasts: confirm
Blasts->>API: POST /createTextBlast (Authorization: Bearer <token>)
API-->>Blasts: response
Blasts-->>User: output JSON result or mapped error
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
docs/research/2026-03-24-text-blast-endpoint.md (1)
11-13: Add language identifier to fenced code block.The code block is missing a language specifier. Adding one improves syntax highlighting and silences the markdownlint warning.
Proposed fix
-``` +```http POST https://api.partiful.com/createTextBlast</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@docs/research/2026-03-24-text-blast-endpoint.mdaround lines 11 - 13, Add
the language identifier "http" to the fenced code block that contains the POST
request "POST https://api.partiful.com/createTextBlast" so it becomeshttp ...; this will enable proper syntax highlighting and remove the markdownlint
warning for the code block.</details> </blockquote></details> <details> <summary>src/cli.js (1)</summary><blockquote> `48-60`: **Deprecation message for `clone` may be confusing.** When `clone` is used, `aliasMap['clone']` returns `['+clone']`, so `newArgs.slice(0, 2).join(' ')` would produce `"+clone <arg>"` in the deprecation message, which doesn't follow the expected `"partiful <group> <command>"` pattern shown for other aliases. Consider either: 1. Adjusting the message logic to handle single-element aliases, or 2. Mapping `clone` to `['events', '+clone']` if that's the intended path. <details> <summary>Suggested fix for clearer deprecation message</summary> ```diff const aliasMap = { 'list': ['events', 'list'], 'get': ['events', 'get'], 'cancel': ['events', 'cancel'], - 'clone': ['+clone'], + 'clone': ['events', '+clone'], // or handle special case in message }; ``` Alternatively, adjust the message logic: ```diff if (args.length > 0 && aliasMap[args[0]]) { const newArgs = [...aliasMap[args[0]], ...args.slice(1)]; - process.stderr.write(`[deprecated] "partiful ${args[0]}" → use "partiful ${newArgs.slice(0, 2).join(' ')}" instead\n`); + const suggestion = newArgs.length === 1 ? newArgs[0] : newArgs.slice(0, 2).join(' '); + process.stderr.write(`[deprecated] "partiful ${args[0]}" → use "partiful ${suggestion}" instead\n`); process.argv = [...process.argv.slice(0, 2), ...newArgs]; } ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@src/cli.js` around lines 48 - 60, The deprecation message logic uses aliasMap and newArgs to build the user-facing string, but for the 'clone' key aliasMap['clone'] is ['+clone'] so process.stderr.write uses newArgs.slice(0, 2).join(' ') and prints a misleading "+clone <arg>" instead of the expected "events +clone"; update either aliasMap to map 'clone' to ['events', '+clone'] or change the message generation to handle single-element aliases (e.g., if aliasMap[args[0]].length === 1, prepend the default group 'events' when constructing the display string) and ensure process.argv is still rewritten to [...process.argv.slice(0,2), ...newArgs] as before. ``` </details> </blockquote></details> <details> <summary>src/commands/blasts.js (1)</summary><blockquote> `17-17`: **Unused constant `MAX_BLASTS_PER_EVENT`.** This constant is defined but never used in the implementation. Consider either removing it or adding validation to check the blast count before sending (which would require an additional API call). <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@src/commands/blasts.js` at line 17, The constant MAX_BLASTS_PER_EVENT is defined but unused; either remove MAX_BLASTS_PER_EVENT to avoid dead code, or enforce it by adding validation where blasts are sent (the command handler that performs the blast/send operation) to check the number of blasts before proceeding and return an error if it exceeds MAX_BLASTS_PER_EVENT; if you choose validation, fetch the current blast count (via the existing API or add a lightweight API call), compare against MAX_BLASTS_PER_EVENT, and short-circuit with a clear error/response from the blast-sending function. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@src/commands/blasts.js:
- Around line 80-83: The jsonOutput calls inside the createTextBlast flow are
missing the opts argument, so the --output path from globalOpts isn’t honored;
update each jsonOutput invocation (the dry-run branch where globalOpts.dryRun is
checked, the cancelled branch, and the success branch returning the response) to
pass globalOpts as the second parameter (i.e., jsonOutput(outputData,
globalOpts)) so the output option is respected; look for calls to jsonOutput in
this file (blasts.js) around the createTextBlast handling and add the opts
argument accordingly.- Around line 110-116: In the catch block handling PartifulError in the
blasts.js command (where you check "if (err instanceof PartifulError)"), replace
the undefined property err.code with the correct err.type when calling jsonError
so the error type is preserved; keep passing err.message and err.exitCode as
before and ensure the jsonError call uses err.type as the third argument.- Around line 65-78: The payload creation sends userId directly from config
which can be undefined when loadConfig() (from src/lib/auth.js) returns only
accessToken/tokenExpiry; update the payload construction in the payload object
(the block that builds data: wrapPayload(...)) to use a safe fallback such as
config.userId || null for the amplitude/userId field so it matches the pattern
used in auth command; ensure you only change the userId expression and keep the
rest of wrapPayload/params/amplitudeSessionId logic intact.
Nitpick comments:
In@docs/research/2026-03-24-text-blast-endpoint.md:
- Around line 11-13: Add the language identifier "http" to the fenced code block
that contains the POST request "POST https://api.partiful.com/createTextBlast"
so it becomeshttp ...; this will enable proper syntax highlighting and
remove the markdownlint warning for the code block.In
@src/cli.js:
- Around line 48-60: The deprecation message logic uses aliasMap and newArgs to
build the user-facing string, but for the 'clone' key aliasMap['clone'] is
['+clone'] so process.stderr.write uses newArgs.slice(0, 2).join(' ') and prints
a misleading "+clone " instead of the expected "events +clone"; update
either aliasMap to map 'clone' to ['events', '+clone'] or change the message
generation to handle single-element aliases (e.g., if aliasMap[args[0]].length
=== 1, prepend the default group 'events' when constructing the display string)
and ensure process.argv is still rewritten to [...process.argv.slice(0,2),
...newArgs] as before.In
@src/commands/blasts.js:
- Line 17: The constant MAX_BLASTS_PER_EVENT is defined but unused; either
remove MAX_BLASTS_PER_EVENT to avoid dead code, or enforce it by adding
validation where blasts are sent (the command handler that performs the
blast/send operation) to check the number of blasts before proceeding and return
an error if it exceeds MAX_BLASTS_PER_EVENT; if you choose validation, fetch the
current blast count (via the existing API or add a lightweight API call),
compare against MAX_BLASTS_PER_EVENT, and short-circuit with a clear
error/response from the blast-sending function.</details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `4da96d24-c92b-4252-9dd6-a52985df7134` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 9d3013446a696001995967ed5812242c4babf678 and 8c903efe766e80e7d63ad60462399822361ae7a2. </details> <details> <summary>📒 Files selected for processing (3)</summary> * `docs/research/2026-03-24-text-blast-endpoint.md` * `src/cli.js` * `src/commands/blasts.js` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| if (globalOpts.dryRun) { | ||
| jsonOutput({ dryRun: true, endpoint: '/createTextBlast', payload }); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Missing globalOpts in jsonOutput calls breaks --output flag.
The jsonOutput function accepts an opts parameter to support --output <path>, but all three calls omit it. This means --output won't work for dry-run, cancelled, or successful responses.
Proposed fix
if (globalOpts.dryRun) {
- jsonOutput({ dryRun: true, endpoint: '/createTextBlast', payload });
+ jsonOutput({ dryRun: true, endpoint: '/createTextBlast', payload }, {}, globalOpts);
return;
} if (!ok) {
- jsonOutput({ cancelled: true, message: 'Blast not sent' });
+ jsonOutput({ cancelled: true, message: 'Blast not sent' }, {}, globalOpts);
return;
}- jsonOutput({
+ jsonOutput({
sent: true,
eventId,
to: toStatuses,
messageLength: opts.message.length,
showOnEventPage,
response: result?.result?.data || result?.result || result,
- });
+ }, {}, globalOpts);Also applies to: 95-96, 102-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/blasts.js` around lines 80 - 83, The jsonOutput calls inside the
createTextBlast flow are missing the opts argument, so the --output path from
globalOpts isn’t honored; update each jsonOutput invocation (the dry-run branch
where globalOpts.dryRun is checked, the cancelled branch, and the success branch
returning the response) to pass globalOpts as the second parameter (i.e.,
jsonOutput(outputData, globalOpts)) so the output option is respected; look for
calls to jsonOutput in this file (blasts.js) around the createTextBlast handling
and add the opts argument accordingly.
…back, alias rewrite - blasts.js: err.code → err.type (PartifulError uses .type not .code) - blasts.js: pass globalOpts to all jsonOutput calls (fixes --output flag) - blasts.js: config.userId || null fallback for PARTIFUL_TOKEN env var - cli.js: alias rewrite scans past global opts (--format table list works) - cli.js: clone maps to ['events', '+clone'] for correct deprecation msg
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/commands/blasts.js (2)
15-17: Unused constantMAX_BLASTS_PER_EVENT.This constant is defined but never used in the file. If it's a placeholder for future rate-limit validation, consider adding a TODO comment. Otherwise, remove it to avoid dead code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/blasts.js` around lines 15 - 17, Remove the dead constant MAX_BLASTS_PER_EVENT from src/commands/blasts.js if it's not used, or if you intend to enforce a per-event blast limit add a clear TODO comment referencing MAX_BLASTS_PER_EVENT and implement the validation in the relevant function (e.g., where blasts are created or sent) so the constant is either actively used for rate-limit checks or explicitly marked for future work.
38-39: Redundant positive flag definition.In Commander.js, defining
--no-show-on-event-pageautomatically createsshowOnEventPagewith a default oftrue. The explicit--show-on-event-pageoption is redundant and could be removed for cleaner CLI definitions.♻️ Proposed simplification
.requiredOption('--message <msg>', 'Message to send (max 480 chars)') .option('--to <statuses>', 'Comma-separated guest statuses to send to (default: GOING)', 'GOING') - .option('--show-on-event-page', 'Show blast in event activity feed (default: true)') .option('--no-show-on-event-page', 'Hide blast from event activity feed')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/blasts.js` around lines 38 - 39, Remove the redundant positive flag definition for show-on-event-page: delete the .option('--show-on-event-page', ...) line and keep only .option('--no-show-on-event-page', 'Hide blast from event activity feed'), relying on Commander to automatically provide a boolean showOnEventPage defaulting to true; verify any code that reads showOnEventPage (e.g., where options.showOnEventPage is used) still works without change and update tests or docs if they referenced the explicit flag.
🤖 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/cli.js`:
- Around line 77-81: The special-case rewrite for "partiful guests <id>" fails
to account for global options; update the logic to use the same cmdIndex
scanning used by the alias handling instead of checking args[0]. Locate the code
around the existing alias handling that computes cmdIndex and change the guests
branch to find the command token at args[cmdIndex], emit the same deprecation
message, and rewrite process.argv by splicing in 'guests','list' at the original
command position (using cmdIndex+2 relative to process.argv.slice(0,2)) so
global flags remain in place; ensure you still guard against allowed subcommands
like 'list','invite','--help','-h' by checking args[cmdIndex+1].
---
Nitpick comments:
In `@src/commands/blasts.js`:
- Around line 15-17: Remove the dead constant MAX_BLASTS_PER_EVENT from
src/commands/blasts.js if it's not used, or if you intend to enforce a per-event
blast limit add a clear TODO comment referencing MAX_BLASTS_PER_EVENT and
implement the validation in the relevant function (e.g., where blasts are
created or sent) so the constant is either actively used for rate-limit checks
or explicitly marked for future work.
- Around line 38-39: Remove the redundant positive flag definition for
show-on-event-page: delete the .option('--show-on-event-page', ...) line and
keep only .option('--no-show-on-event-page', 'Hide blast from event activity
feed'), relying on Commander to automatically provide a boolean showOnEventPage
defaulting to true; verify any code that reads showOnEventPage (e.g., where
options.showOnEventPage is used) still works without change and update tests or
docs if they referenced the explicit flag.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a008d79f-d8bc-4dcd-b47a-147c1aca84ce
📒 Files selected for processing (2)
src/cli.jssrc/commands/blasts.js
| // Special case: `partiful guests <id>` → `partiful guests list <id>` | ||
| if (args[0] === 'guests' && args[1] && !['list', 'invite', '--help', '-h'].includes(args[1])) { | ||
| process.stderr.write(`[deprecated] "partiful guests <id>" → use "partiful guests list <id>" instead\n`); | ||
| process.argv = [...process.argv.slice(0, 2), 'guests', 'list', ...args.slice(1)]; | ||
| } |
There was a problem hiding this comment.
Guests special case doesn't skip global options, inconsistent with alias handling above.
The alias rewriting at lines 57-64 correctly scans past global options to find the command token. However, this special case checks args[0] directly. If a user runs partiful --format json guests abc123, the deprecation rewrite won't trigger because args[0] is '--format', not 'guests'.
🔧 Proposed fix to use cmdIndex for consistency
// Special case: `partiful guests <id>` → `partiful guests list <id>`
- if (args[0] === 'guests' && args[1] && !['list', 'invite', '--help', '-h'].includes(args[1])) {
+ const rewrittenArgs = process.argv.slice(2);
+ // Re-scan for command index after potential alias rewrite
+ let guestsCmdIndex = 0;
+ while (guestsCmdIndex < rewrittenArgs.length && rewrittenArgs[guestsCmdIndex].startsWith('-')) {
+ guestsCmdIndex += optsWithValue.has(rewrittenArgs[guestsCmdIndex]) ? 2 : 1;
+ }
+ if (rewrittenArgs[guestsCmdIndex] === 'guests' && rewrittenArgs[guestsCmdIndex + 1] && !['list', 'invite', '--help', '-h'].includes(rewrittenArgs[guestsCmdIndex + 1])) {
process.stderr.write(`[deprecated] "partiful guests <id>" → use "partiful guests list <id>" instead\n`);
- process.argv = [...process.argv.slice(0, 2), 'guests', 'list', ...args.slice(1)];
+ process.argv = [
+ ...process.argv.slice(0, 2),
+ ...rewrittenArgs.slice(0, guestsCmdIndex),
+ 'guests', 'list',
+ ...rewrittenArgs.slice(guestsCmdIndex + 1),
+ ];
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli.js` around lines 77 - 81, The special-case rewrite for "partiful
guests <id>" fails to account for global options; update the logic to use the
same cmdIndex scanning used by the alias handling instead of checking args[0].
Locate the code around the existing alias handling that computes cmdIndex and
change the guests branch to find the command token at args[cmdIndex], emit the
same deprecation message, and rewrite process.argv by splicing in
'guests','list' at the original command position (using cmdIndex+2 relative to
process.argv.slice(0,2)) so global flags remain in place; ensure you still guard
against allowed subcommands like 'list','invite','--help','-h' by checking
args[cmdIndex+1].
What
Implements
partiful blasts sendusing the discoveredcreateTextBlastAPI endpoint.Before: Stub that said 'use the web UI'
After: Fully working CLI command that sends text blasts via REST API
How it was found
Logged into partiful.com via browser automation, navigated to an event's Text Blast modal, and reverse-engineered the app bundle (
SendTextBlastModal.tsx→6631.793447c2446d40ae.js) to find:Uses the same Firebase callable function pattern as all other Partiful endpoints.
Usage
Safety
--yes)Research
Full endpoint documentation:
docs/research/2026-03-24-text-blast-endpoint.mdSummary by CodeRabbit
New Features
Chores