Skip to content

feat(email): integrate email + alias domain into wspc-cli#7

Merged
yurenju merged 13 commits into
mainfrom
claude/email-integration
May 27, 2026
Merged

feat(email): integrate email + alias domain into wspc-cli#7
yurenju merged 13 commits into
mainfrom
claude/email-integration

Conversation

@yurenju
Copy link
Copy Markdown
Contributor

@yurenju yurenju commented May 27, 2026

@

Summary

Integrates the Email domain (8 message routes + 4 alias routes) into @wspc/cli, following the calendar-paved codegen-first pattern but adding two handwritten commands for cases the declarative codegen deliberately does not model: attachment upload (base64-encoded into the JSON body) and binary stream download.

Depends on sadcoderlabs/wspc#365 — that PR ships the x-cli annotations + display.dataPath field this generator consumes. Merge order: server PR → prod deploy → npm run sync-spec here → release.

Spec / plan in the wspc monorepo:

Commands added (10 total)

Codegen-produced (8)email ls/show/read/unread/rm, alias add/ls/rm. All driven by x-cli hints + display in the OpenAPI spec; no per-command code in this repo.

Handwritten (2):

  • email send — reads local file → detects MIME → base64-encodes → embeds in attachments[] (or parses eml_xxx:idx as an inbound attachment reference). Validates fresh vs reply mode mutual exclusion. Per-attachment ≤ 5 MiB / total ≤ 25 MiB / text body ≤ 100 KiB client-side checks.
  • email attachment <id> <idx> — uses new loadAuthedFetch() helper to get the OAuth-aware fetch + baseUrl, streams application/octet-stream response into a file (--output or filename derived from Content-Disposition).

Codegen improvements

  • _handwritten sentinel: routes flagged with { command: "_handwritten", hidden: true } are skipped by codegen but stay in OpenAPI (SDK function + x-codeSamples still surface)
  • String array passthrough: fixed three bugs in emit.ts that prevented --id <id> --id <id> from mapping to body.ids: string[]valueExprForOption did not honour mapsTo, conversion block had no branch for array-without-parser, and required-array cast was string instead of string[]. Calendar --attendee worked by accident because its parser branch happened to do the right thing
  • main() entry-point guard: tools/cli-codegen/main.ts was running main() at module-load time, which the unit test imports triggered — every npm test re-emitted the entire src/generated/cli/ tree as a side effect, polluting git status. Now guarded with fileURLToPath(import.meta.url) === process.argv[1]. npm test after this PR leaves the generated tree byte-for-byte stable.

Renderer improvements

  • bool-badge format: is_read and similar booleans now render as ✓ read (dim) vs ● unread (bright) in pretty mode
  • display.dataPath drill: in pretty mode the renderer can drill into a wrapper key before rendering. Used by email show whose response is { email, attachments }. JSON mode is unaffected — --json still emits the full payload

New utilities

  • parse-content-disposition.ts — extracts filename= token from a Content-Disposition header. RFC 5987 filename*= intentionally not supported (server does not emit it)
  • mime-from-ext.ts — 13-entry static map, falls back to application/octet-stream. No mime-types dep added — keeping wspc-cli dependency-minimal

Auth: loadAuthedFetch()

New named export from src/handwritten/auth/load-sdk-client.ts. Returns { fetch, baseUrl } where fetch is the interceptor-wrapped version (handles OAuth token refresh transparently). Used by email attachment to bypass Hey API SDK for binary streams.

Tests

  • 130 tests pass (4 new bool-badge unit tests, 5 new Content-Disposition unit tests, 2 new MIME mapping unit tests, 6 new email send tests, 4 new email attachment tests, 4 new dataPath drill tests, 6 new generated email integration tests, 3 new generated alias integration tests, 6 new codegen _handwritten skip + array passthrough tests)
  • npm run typecheck passes
  • npm run build passes (ESM + DTS)

E2E verified (manual smoke against prod)

  • wspc alias add claude-smoke-001@wspc.app → created
  • wspc email send text-only → received in Gmail
  • wspc email send --attach <small.txt> → received in Gmail with attachment
  • wspc email ls (pretty + JSON) → bool-badge column renders correctly
  • wspc email show <id> (pretty) → drills into email, attachments metadata preserved in --json
  • wspc email attachment <id> 0 --output got.png → 2,364,849 bytes byte-for-byte match against original
  • wspc email attachment <id> 0 (no --output) → filename derived from Content-Disposition (handles spaces, e.g. _ mode _ Mobile.png)
  • 404 path → HTTP 404 stderr + exit 1, no partial file written

Implementation-time discoveries

  • email_alias_create body field is email (full address), not local_part as the plan template assumed — plan / spec updated
  • email_alias_delete path param is also email, not id
  • --include-deleted is exposed as <value> (user types --include-deleted true) because the server schema uses stringBool. UX should be a boolean flag — recorded as a codegen follow-up in the research doc
  • process.exitCode cannot be spied via vi.spyOn — tests use set-undefined-then-assert pattern instead
    @

@yurenju yurenju merged commit 9891c29 into main May 27, 2026
1 check passed
@yurenju yurenju deleted the claude/email-integration branch May 27, 2026 11:53
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