You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
events list does not expose my own RSVP status for each event, even though the Partiful API returns it. The raw /getMyUpcomingEventsForHomePage response includes a per-event guest object — my personal attendance record — but the CLI's output mapping drops it. This issue adds a myRsvp field to the event output and fixes the related, currently-broken isHost flag (same root cause: we don't know who "me" is).
Motivation
Primary use case: a Partiful → Google Calendar sync that copies only the events I've actually accepted (GOING/MAYBE) into my personal calendar, so my calendar stops showing every invite as a false commitment. That sync needs to read my per-event RSVP — which the CLI can't currently surface. More broadly, "which of these am I going to?" is a basic question the event list should answer.
Root cause
The API returns everything we need; the CLI discards it.
src/commands/events.js (lines ~74–85) maps each event to just 9 fields:
constmapped=(eventList||[]).map(e=>({id: e.id,title: e.title,startDate: e.startDate,endDate: e.endDate||null,location: e.location||null,status: e.status,// EVENT status (PUBLISHED), NOT my RSVPisHost: e.ownerIds?.includes(config.userId)||false,// BROKEN: config.userId is undefinedgoing: e.guestStatusCounts?.GOING||0,// aggregate count, not memaybe: e.guestStatusCounts?.MAYBE||0,url: `https://partiful.com/e/${e.id}`,}));// DROPPED: e.guest = { userId: <me>, status: "GOING" | "MAYBE" | "DECLINED" | "SENT" }
Two defects fall out of this:
myRsvp missing — e.guest.status (my personal RSVP) is never surfaced.
isHost always false — it tests config.userId, but that is undefined in the stored auth config (the same userId: null that partiful doctor flags). So no event is ever reported as hosted.
Data model (verified live against my account, 15 events)
If I'm a guest, the event carries guest: { userId: <me>, status: <MY_RSVP> } where status ∈ GOING | MAYBE | DECLINED | SENT (SENT = invited, no reply yet).
If I host, there is noguest object, and my id appears in ownerIds.
So: guest present → use guest.status; guest absent + my id in ownerIds → I'm the host.
Identity resolution — decode the JWT (fixes the root cause cleanly)
config.userId is unreliable (null/undefined). The Firebase access token already carries my id. Verified: the JWT payload's user_id (and sub) claim equals my guest.userId. Decoding it is the robust, self-healing source of truth for "me" — no dependency on what was saved at auth save time.
Proposed helper in src/lib/auth.js:
// Decode the Firebase JWT payload and return the authenticated user id.exportfunctiongetUserIdFromToken(token){try{constpayload=JSON.parse(Buffer.from(token.split('.')[1],'base64').toString('utf8'));returnpayload.user_id||payload.sub||null;}catch{returnnull;}}
This also makes isHost correct without touching stored config, and can backfill config.userId on refresh if desired.
Proposed changes
Code
src/lib/auth.js — add getUserIdFromToken(token) (JWT decode). Optionally set config.userId from it inside getValidToken so the whole CLI benefits and doctor stops flagging null.
src/commands/events.js (events list) — resolve const me = config.userId || getUserIdFromToken(token); then add to the mapping:
myRsvp: e.guest?.status ?? null (null when I host / no record)
fix isHost: e.ownerIds?.includes(me) || false
Extract the inline .map() into a pure, exported mapEventSummary(e, me) so the mapping is unit-testable without a live API (all current event tests are --dry-run and never exercise real response shaping).
events get — also should return myRsvp, but see blocker below; /getEvent currently 404s, so this part is gated on that fix.
Docs
README.md — update the events list output example to include myRsvp + document the field's value set.
AGENTS.md — note that identity is now derived from the token (userId no longer needs to be stored); update the "userId can be null and things still work" section to mention myRsvp/isHost now work regardless.
Bundled skill skills/partiful-events/SKILL.md (ships in the npm files array) — document myRsvp on events list.
Tests (tests/, vitest)
Unit test mapEventSummary: guest present → myRsvp set + isHost false; host case (no guest, id in ownerIds) → myRsvp null + isHost true.
Unit test getUserIdFromToken with a hand-built unsigned JWT (header.base64(payload).sig), asserting user_id/sub extraction and graceful null on garbage.
Keep the existing 106 tests green.
Discovered blocker (separate bug) — events get returns 404
While researching I found partiful events get <id> fails for every event (mine and hosted), both via the CLI and a direct /getEvent call:
{"status":"error","error":{"code":4,"type":"not_found","message":"API POST /getEvent failed","details":{"statusCode":404}}}
This looks like a Partiful endpoint change unrelated to this feature. It blocks adding myRsvp to events get. Recommend filing as its own bug; this issue can ship the events list improvement independently (which is all the calendar-sync use case needs).
Release / distribution steps (I own this CLI)
"Making the change" end-to-end, beyond the code:
Branch, implement, npm test green.
Manual verify against live account: partiful events list shows correct myRsvp (GOING for Daily Mile 365; MAYBE for the two wild-waves; SENT for the five unanswered) and isHost: true for my 4 owned events.
Bump package.json version (2.0.0 → 2.1.0, minor — additive field).
Refresh the global install — /opt/homebrew/lib/node_modules/partiful-cli is a real directory (installed via npm install -g ., not a symlink), so repo edits don't take effect until npm install -g . is re-run (or switch dev to npm link). The partiful command Hermes/agents call won't change until this step.
Package is published to the npm registry at 2.0.0 — if public distribution is wanted, npm publish after the bump. Otherwise skip and just reinstall locally.
No CI to update (no .github/ workflows present) and no CHANGELOG file exists — optionally add one entry if you start tracking.
Acceptance criteria
partiful events list output objects include myRsvp ∈ {GOING, MAYBE, DECLINED, SENT, null}.
isHost is true for events I own, false otherwise — independent of whether userId was saved.
Identity derives from the token when config.userId is absent.
New unit tests cover the mapping + JWT decode; full suite green.
[P1] RSVP tracking and notifications #17 [P1] RSVP tracking and notifications (closed) — that was about tracking other guests' RSVPs to events I host (host-side guest list/summary). This issue is the inverse: my own RSVP as an invitee, on the events list. Different direction of the same relation.
[P1] Google Calendar sync #19 [P1] Google Calendar sync (closed) — the broader two-way sync vision. This issue is the missing primitive underneath it: you cannot "sync only events I've accepted" without first exposing my per-event RSVP. Landing myRsvp unblocks a focused, read-only "copy my GOING events to gcal" implementation without the full two-way scope.
Effort
Small — ~1–2 hours. Additive, backward-compatible, no API-contract risk (pure read-path shaping).
Summary
events listdoes not expose my own RSVP status for each event, even though the Partiful API returns it. The raw/getMyUpcomingEventsForHomePageresponse includes a per-eventguestobject — my personal attendance record — but the CLI's output mapping drops it. This issue adds amyRsvpfield to the event output and fixes the related, currently-brokenisHostflag (same root cause: we don't know who "me" is).Motivation
Primary use case: a Partiful → Google Calendar sync that copies only the events I've actually accepted (
GOING/MAYBE) into my personal calendar, so my calendar stops showing every invite as a false commitment. That sync needs to read my per-event RSVP — which the CLI can't currently surface. More broadly, "which of these am I going to?" is a basic question the event list should answer.Root cause
The API returns everything we need; the CLI discards it.
src/commands/events.js(lines ~74–85) maps each event to just 9 fields:Two defects fall out of this:
myRsvpmissing —e.guest.status(my personal RSVP) is never surfaced.isHostalwaysfalse— it testsconfig.userId, but that isundefinedin the stored auth config (the sameuserId: nullthatpartiful doctorflags). So no event is ever reported as hosted.Data model (verified live against my account, 15 events)
guest: { userId: <me>, status: <MY_RSVP> }where status ∈GOING | MAYBE | DECLINED | SENT(SENT= invited, no reply yet).guestobject, and my id appears inownerIds.guestpresent → useguest.status;guestabsent + my id inownerIds→ I'm the host.Identity resolution — decode the JWT (fixes the root cause cleanly)
config.userIdis unreliable (null/undefined). The Firebase access token already carries my id. Verified: the JWT payload'suser_id(andsub) claim equals myguest.userId. Decoding it is the robust, self-healing source of truth for "me" — no dependency on what was saved atauth savetime.Proposed helper in
src/lib/auth.js:This also makes
isHostcorrect without touching stored config, and can backfillconfig.userIdon refresh if desired.Proposed changes
Code
src/lib/auth.js— addgetUserIdFromToken(token)(JWT decode). Optionally setconfig.userIdfrom it insidegetValidTokenso the whole CLI benefits anddoctorstops flagging null.src/commands/events.js(events list) — resolveconst me = config.userId || getUserIdFromToken(token);then add to the mapping:myRsvp: e.guest?.status ?? null(null when I host / no record)isHost: e.ownerIds?.includes(me) || false.map()into a pure, exportedmapEventSummary(e, me)so the mapping is unit-testable without a live API (all current event tests are--dry-runand never exercise real response shaping).events get— also should returnmyRsvp, but see blocker below;/getEventcurrently 404s, so this part is gated on that fix.Docs
README.md— update theevents listoutput example to includemyRsvp+ document the field's value set.AGENTS.md— note that identity is now derived from the token (userId no longer needs to be stored); update the "userId can be null and things still work" section to mentionmyRsvp/isHostnow work regardless.skills/partiful-events/SKILL.md(ships in the npmfilesarray) — documentmyRsvponevents list.Tests (
tests/, vitest)mapEventSummary: guest present →myRsvpset +isHostfalse; host case (no guest, id inownerIds) →myRsvpnull +isHosttrue.getUserIdFromTokenwith a hand-built unsigned JWT (header.base64(payload).sig), assertinguser_id/subextraction and gracefulnullon garbage.Discovered blocker (separate bug) —
events getreturns 404While researching I found
partiful events get <id>fails for every event (mine and hosted), both via the CLI and a direct/getEventcall:This looks like a Partiful endpoint change unrelated to this feature. It blocks adding
myRsvptoevents get. Recommend filing as its own bug; this issue can ship theevents listimprovement independently (which is all the calendar-sync use case needs).Release / distribution steps (I own this CLI)
"Making the change" end-to-end, beyond the code:
npm testgreen.partiful events listshows correctmyRsvp(GOINGfor Daily Mile 365;MAYBEfor the two wild-waves;SENTfor the five unanswered) andisHost: truefor my 4 owned events.package.jsonversion (2.0.0 → 2.1.0, minor — additive field)./opt/homebrew/lib/node_modules/partiful-cliis a real directory (installed vianpm install -g ., not a symlink), so repo edits don't take effect untilnpm install -g .is re-run (or switch dev tonpm link). Thepartifulcommand Hermes/agents call won't change until this step.npm publishafter the bump. Otherwise skip and just reinstall locally..github/workflows present) and no CHANGELOG file exists — optionally add one entry if you start tracking.Acceptance criteria
partiful events listoutput objects includemyRsvp ∈ {GOING, MAYBE, DECLINED, SENT, null}.isHostistruefor events I own,falseotherwise — independent of whetheruserIdwas saved.config.userIdis absent.myRsvp.Related issues (orthogonal, not duplicate)
myRsvpunblocks a focused, read-only "copy my GOING events to gcal" implementation without the full two-way scope.Effort
Small — ~1–2 hours. Additive, backward-compatible, no API-contract risk (pure read-path shaping).