feat: phone auth with auto SMS code retrieval#38
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
Replace the bookmarklet-based login with a proper phone auth flow: 1. sendAuthCodeTrusted(phone) → SMS sent (no reCAPTCHA needed) 2. Auto-retrieve code from iMessage (macOS) or Termux (Android) 3. getLoginToken(phone, code) → Partiful custom JWT 4. signInWithCustomToken → Firebase idToken + refreshToken 5. Save to ~/.config/partiful/auth.json Platform detection: - macOS + imsg CLI → auto-retrieves SMS code (tested, works) - Android + termux-sms-list → auto-retrieves SMS code (untested) - All other platforms → prompts for manual code entry Usage: partiful auth login +12066993977 # auto-retrieve code partiful auth login +12066993977 --no-auto # manual entry partiful auth login +12066993977 --code 123456 # skip SMS Research: docs/research/2026-03-24-auth-flow-endpoints.md
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Rate limit exceeded
⌛ 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. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughDocumentation added for Partiful authentication endpoints and text blast API. CLI upgraded with deprecated command rewrites and SMS-based login flow. Text blast endpoint implemented with confirmation and validation, integrating with the discovered API. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/CLI
participant Partiful as Partiful API
participant Firebase as Firebase API
participant SMS as SMS System
User->>Partiful: POST /sendAuthCodeTrusted<br/>(phone, display name, channel)
Partiful->>SMS: Send SMS with auth code
SMS->>User: Code received via SMS
alt Auto-Retrieval Available
User->>User: Platform-specific retrieval<br/>(imsg or termux-sms-list)
else Manual Entry
User->>User: User enters code interactively
end
User->>Partiful: POST /getLoginToken<br/>(phone, auth code)
Partiful->>User: Returns custom Firebase token
User->>Firebase: POST /accounts:signInWithCustomToken<br/>(custom token)
Firebase->>User: Returns idToken, refreshToken, expiresIn
opt User Lookup
User->>Firebase: POST /accounts:lookup<br/>(idToken)
Firebase->>User: User profile data
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 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 unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
src/commands/auth.js (3)
306-310: Document the US-default assumption more prominently.Automatically prepending
+1for numbers without a country code assumes US users. Consider logging this assumption to stderr when applied so international users understand why their number was modified.📝 Suggested improvement
if (!phoneNumber.startsWith('+')) { // Assume US if no country code phoneNumber = '+1' + phoneNumber; + console.error(`Note: No country code provided, assuming US (+1${phone.replace(/[\s\-\(\)]/g, '')})`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/auth.js` around lines 306 - 310, The code currently strips formatting into phoneNumber and blindly prepends '+1' when it doesn't start with '+'; update the logic around the phoneNumber assignment (the branch that adds '+1') to write a clear stderr warning when this US-default assumption is applied (e.g., using console.error or process.stderr.write) so callers see their number was modified; keep the same normalization (phone.replace(/[\s\-\(\)]/g, '')) and only emit the message inside the if (!phoneNumber.startsWith('+')) block and include both the original phone and the normalized phoneNumber in the message for clarity.
59-66:whichcommand is Unix-specific.While the current code doesn't call
hasCommand()on Windows (the platform check returns early), consider adding a comment or guard for future maintainability if this function is reused.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/auth.js` around lines 59 - 66, The helper hasCommand(cmd) uses the Unix-only `which` binary which is not available on Windows; update hasCommand to either include a clear comment noting it is Unix-specific and must not be used on Windows, or add a cross-platform guard that detects process.platform === 'win32' and uses an appropriate Windows check (or returns false) before running execSync; reference the hasCommand function when making this change so future callers know it's platform-specific or has a Windows-safe path.
77-89: Handle empty or malformedimsgoutput gracefully.If
imsg chatsreturns empty output,chatsRaw.trim().split('\n').map(line => JSON.parse(line))will attempt to parse an empty string, throwing an error. While the outer catch silently continues polling, this could mask other issues.🛡️ Suggested defensive fix
const chatsRaw = execSync('imsg chats --limit 30 --json', { encoding: 'utf8', timeout: 10000 }); - const chats = chatsRaw.trim().split('\n').map(line => JSON.parse(line)); + const chats = chatsRaw.trim().split('\n').filter(line => line).map(line => JSON.parse(line)); const partifulChat = chats.find(c => c.identifier === PARTIFUL_SMS_SENDER || c.identifier?.includes('8449460698') ); if (partifulChat) { const historyRaw = execSync(`imsg history --chat-id ${partifulChat.id} --limit 3 --json`, { encoding: 'utf8', timeout: 10000 }); - const messages = historyRaw.trim().split('\n').map(line => JSON.parse(line)); + const messages = historyRaw.trim().split('\n').filter(line => line).map(line => JSON.parse(line));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/auth.js` around lines 77 - 89, The code assumes imsg output is non-empty and JSON-valid; guard parsing of both chatsRaw and historyRaw by first checking that chatsRaw.trim() (and historyRaw.trim()) is non-empty, filter out empty lines before mapping, and safely parse each line (or skip lines that fail JSON.parse) to avoid throwing on empty/malformed output; additionally validate that parsed entries contain expected fields (e.g., c.id, c.identifier) before using them and add a short error/log on parse failure so execSync failures or malformed imsg output are visible (apply these changes around the chatsRaw→chats and historyRaw→messages handling, and keep using partifulChat and its id only after validation).docs/research/2026-03-24-auth-flow-endpoints.md (1)
13-15: Add language specifiers to endpoint URL code blocks.Several fenced code blocks for endpoint URLs lack language specifiers. Adding
textorhttpwould satisfy the markdownlint rule and improve consistency.📝 Example fix for line 13
-``` +```text POST https://api.partiful.com/sendAuthCodeApply similar changes to lines 41, 74, 97, and 112. </details> Also applies to: 41-43, 74-76, 97-99, 112-114 <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-auth-flow-endpoints.mdaround lines 13 - 15, The
fenced code blocks containing plain endpoint URLs (e.g., "POST
https://api.partiful.com/sendAuthCode", "POST
https://api.partiful.com/verifyAuthCode", "GET
https://api.partiful.com/authStatus", "POST
https://api.partiful.com/resendAuthCode", and "DELETE
https://api.partiful.com/revokeAuth") need a language specifier to satisfy
markdownlint; update each triple-backtick fence that wraps those URL lines to
include a short specifier liketext (orhttp) immediately after the
opening backticks so the blocks becometext ...for all instances
referenced in the diff.</details> </blockquote></details> <details> <summary>docs/research/2026-03-24-text-blast-endpoint.md (1)</summary><blockquote> `11-13`: **Add language specifier to fenced code block.** The endpoint URL code block should specify a language for consistency with other code blocks in the document. <details> <summary>📝 Suggested fix</summary> ```diff -``` +```text 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, The
fenced code block containing the endpoint URL (the block with "POST
https://api.partiful.com/createTextBlast") should include a language specifier
for consistency; update that block to use a text fence (e.g., change ``` tosrc/cli.js (1)
48-60: Minor inconsistency in deprecation message forclonealias.For the
clonealias,newArgs.slice(0, 2)yields['+clone'](single element), while other aliases yield two elements like['events', 'list']. The message still works but is slightly inconsistent in format.Consider adjusting for clarity:
🔧 Suggested improvement
- process.stderr.write(`[deprecated] "partiful ${args[0]}" → use "partiful ${newArgs.slice(0, 2).join(' ')}" instead\n`); + process.stderr.write(`[deprecated] "partiful ${args[0]}" → use "partiful ${newArgs[0]}${newArgs.length > 1 ? ' ' + newArgs[1] : ''}" instead\n`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli.js` around lines 48 - 60, The deprecation message is inconsistent for the 'clone' alias because aliasMap['clone'] is ['+clone'] so newArgs.slice(0,2) yields a single token; update the deprecation message logic around process.stderr.write to handle single-token aliases (aliasMap / newArgs / newArgs.slice) by formatting the suggested usage robustly (e.g., if newArgs has only one element, print that element alone or prepend the default command namespace so the message always looks like "partiful <command> <subcommand>"). Ensure the change touches the aliasMap handling and the process.stderr.write call so the message is consistently two-part or clearly formatted for single-part aliases.src/commands/blasts.js (1)
17-17: Unused constant.
MAX_BLASTS_PER_EVENTis defined but never used in the code. Either implement the client-side limit check or remove the constant 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` at line 17, MAX_BLASTS_PER_EVENT is defined but never used; either remove this dead constant or enforce it where blasts are validated/sent by checking the current event's blast count against MAX_BLASTS_PER_EVENT before creating/sending new blasts (e.g., in the routine that validates/creates blasts), returning a clear client error message when the limit is reached and updating related tests and messages accordingly.
🤖 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/blasts.js`:
- Around line 110-116: The catch block is accessing a non-existent err.code on
PartifulError; update the error handling in the catch inside the blasts command
so when err instanceof PartifulError you pass err.type (the actual property on
PartifulError) to jsonError instead of err.code, preserving the other args
(err.message, err.exitCode, err.details) and keeping the fallback branch
unchanged.
---
Nitpick comments:
In `@docs/research/2026-03-24-auth-flow-endpoints.md`:
- Around line 13-15: The fenced code blocks containing plain endpoint URLs
(e.g., "POST https://api.partiful.com/sendAuthCode", "POST
https://api.partiful.com/verifyAuthCode", "GET
https://api.partiful.com/authStatus", "POST
https://api.partiful.com/resendAuthCode", and "DELETE
https://api.partiful.com/revokeAuth") need a language specifier to satisfy
markdownlint; update each triple-backtick fence that wraps those URL lines to
include a short specifier like ```text (or ```http) immediately after the
opening backticks so the blocks become ```text ... ``` for all instances
referenced in the diff.
In `@docs/research/2026-03-24-text-blast-endpoint.md`:
- Around line 11-13: The fenced code block containing the endpoint URL (the
block with "POST https://api.partiful.com/createTextBlast") should include a
language specifier for consistency; update that block to use a text fence (e.g.,
change ``` to ```text) so the code block header reads like the other examples in
the document.
In `@src/cli.js`:
- Around line 48-60: The deprecation message is inconsistent for the 'clone'
alias because aliasMap['clone'] is ['+clone'] so newArgs.slice(0,2) yields a
single token; update the deprecation message logic around process.stderr.write
to handle single-token aliases (aliasMap / newArgs / newArgs.slice) by
formatting the suggested usage robustly (e.g., if newArgs has only one element,
print that element alone or prepend the default command namespace so the message
always looks like "partiful <command> <subcommand>"). Ensure the change touches
the aliasMap handling and the process.stderr.write call so the message is
consistently two-part or clearly formatted for single-part aliases.
In `@src/commands/auth.js`:
- Around line 306-310: The code currently strips formatting into phoneNumber and
blindly prepends '+1' when it doesn't start with '+'; update the logic around
the phoneNumber assignment (the branch that adds '+1') to write a clear stderr
warning when this US-default assumption is applied (e.g., using console.error or
process.stderr.write) so callers see their number was modified; keep the same
normalization (phone.replace(/[\s\-\(\)]/g, '')) and only emit the message
inside the if (!phoneNumber.startsWith('+')) block and include both the original
phone and the normalized phoneNumber in the message for clarity.
- Around line 59-66: The helper hasCommand(cmd) uses the Unix-only `which`
binary which is not available on Windows; update hasCommand to either include a
clear comment noting it is Unix-specific and must not be used on Windows, or add
a cross-platform guard that detects process.platform === 'win32' and uses an
appropriate Windows check (or returns false) before running execSync; reference
the hasCommand function when making this change so future callers know it's
platform-specific or has a Windows-safe path.
- Around line 77-89: The code assumes imsg output is non-empty and JSON-valid;
guard parsing of both chatsRaw and historyRaw by first checking that
chatsRaw.trim() (and historyRaw.trim()) is non-empty, filter out empty lines
before mapping, and safely parse each line (or skip lines that fail JSON.parse)
to avoid throwing on empty/malformed output; additionally validate that parsed
entries contain expected fields (e.g., c.id, c.identifier) before using them and
add a short error/log on parse failure so execSync failures or malformed imsg
output are visible (apply these changes around the chatsRaw→chats and
historyRaw→messages handling, and keep using partifulChat and its id only after
validation).
In `@src/commands/blasts.js`:
- Line 17: MAX_BLASTS_PER_EVENT is defined but never used; either remove this
dead constant or enforce it where blasts are validated/sent by checking the
current event's blast count against MAX_BLASTS_PER_EVENT before creating/sending
new blasts (e.g., in the routine that validates/creates blasts), returning a
clear client error message when the limit is reached and updating related tests
and messages accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1c0202ca-3168-4be8-aac6-4afef173d921
📒 Files selected for processing (5)
docs/research/2026-03-24-auth-flow-endpoints.mddocs/research/2026-03-24-text-blast-endpoint.mdsrc/cli.jssrc/commands/auth.jssrc/commands/blasts.js
- cli.js: alias rewrite scans past global opts, fix clone mapping - blasts.js: err.code → err.type, jsonOutput opts, userId fallback
What
Replaces the bookmarklet login with a proper phone-number auth flow that auto-retrieves SMS verification codes.
Before
After
How It Works
Reverse-engineered the Partiful login flow from the app bundle:
POST /sendAuthCodeTrusted— sends SMS (no reCAPTCHA needed)imsgCLI (macOS) ortermux-sms-list(Android) or manual promptPOST /getLoginToken— exchanges phone + code for a custom JWTPOST identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken— exchanges JWT for Firebase tokensrefreshTokento~/.config/partiful/auth.jsonPlatform Detection
imsgCLIos.platform() === 'darwin'+which imsg/system/build.propexists +which termux-sms-listOptions
--code <code>— provide code directly (skip SMS send)--no-auto— disable auto-retrieval, always prompt manuallyTested
imsg— sent SMS, auto-retrieved code, authenticated, verified API calls workpartiful events listreturns 9 events with new auth tokensResearch
docs/research/2026-03-24-auth-flow-endpoints.mdSummary by CodeRabbit
Release Notes
New Features
Deprecations
events listinstead oflist)Documentation