|
| 1 | +# StrawPoll CLI |
| 2 | + |
| 3 | +## What This Is |
| 4 | + |
| 5 | +A command-line interface for the StrawPoll API v3, enabling poll creation, management, editing, and results viewing from the terminal. Modeled after [gogcli](https://github.com/steipete/gogcli) and Peter's existing Go CLI toolkit (delijn-cli, harvest-cli, frontapp-cli). Built for power users who live in the terminal and want full StrawPoll lifecycle without leaving it. |
| 6 | + |
| 7 | +## Core Value |
| 8 | + |
| 9 | +Create, manage, and view results of StrawPoll polls entirely from the command line — with the same quality and patterns as the existing Go CLI suite. |
| 10 | + |
| 11 | +## Requirements |
| 12 | + |
| 13 | +### Validated |
| 14 | + |
| 15 | +(None yet — ship to validate) |
| 16 | + |
| 17 | +### Active |
| 18 | + |
| 19 | +- [ ] Auth: store/retrieve/remove API key via system keyring (macOS Keychain, Linux Secret Service, etc.) |
| 20 | +- [ ] Auth: STRAWPOLL_API_KEY env var fallback for CI/headless |
| 21 | +- [ ] Auth: `auth set-key`, `auth status`, `auth remove` commands |
| 22 | +- [ ] Poll: create multiple-choice polls with full config flags (20+ flags for all poll_config fields) |
| 23 | +- [ ] Poll: grouped flag help output (Creation, Voting Rules, Privacy & Access, Display & Scheduling) |
| 24 | +- [ ] Poll: get poll details by ID or full URL |
| 25 | +- [ ] Poll: view results with ASCII table + summary (vote counts, percentages) |
| 26 | +- [ ] Poll: view per-participant breakdown with `--participants` flag |
| 27 | +- [ ] Poll: delete with confirmation prompt (skippable with `--force`) |
| 28 | +- [ ] Poll: update poll title, options, config via `poll update` |
| 29 | +- [ ] Poll: reset poll results/deadline via `poll reset` |
| 30 | +- [ ] Poll: `poll list` showing user's polls from API (remote, paginated) |
| 31 | +- [ ] Meeting: create scheduling polls with combined all-day dates + time ranges in one flow |
| 32 | +- [ ] Meeting: bubbletea TUI for interactive date/time selection when no flags given |
| 33 | +- [ ] Meeting: inline flags for scripted creation (`--date`, `--range`, `--tz`) |
| 34 | +- [ ] Meeting: view results as participant-by-timeslot grid |
| 35 | +- [ ] Meeting: update meeting poll via `meeting update` |
| 36 | +- [ ] Ranking: create ranking polls |
| 37 | +- [ ] Ranking: update ranking poll via `ranking update` |
| 38 | +- [ ] Ranking: results showing weighted summary (Borda count) + position breakdown with `--verbose` |
| 39 | +- [ ] Config: `config show`, `config set`, `config path` commands |
| 40 | +- [ ] Config: default poll creation values in config.yaml (dupcheck, results_visibility, etc.), overridable by flags |
| 41 | +- [ ] Output: `--json` (structured), `--plain` (TSV with headers), `--no-color` modes |
| 42 | +- [ ] Output: `--copy` flag to copy poll URL to clipboard after creation |
| 43 | +- [ ] Output: `--open` flag to open poll in default browser after creation |
| 44 | +- [ ] URL parsing: accept both poll IDs (`NPgxkzPqrn2`) and full URLs (`https://strawpoll.com/NPgxkzPqrn2`) |
| 45 | +- [ ] Shell completion: bash, zsh, fish |
| 46 | +- [ ] Version command with build metadata |
| 47 | + |
| 48 | +### Out of Scope |
| 49 | + |
| 50 | +- Webhooks — complexity not justified for CLI use case; revisit if API adds list/management endpoints |
| 51 | +- Voting — no vote endpoint in OpenAPI spec; voting done via browser |
| 52 | +- OAuth flow — StrawPoll uses simple API keys, not OAuth |
| 53 | +- Result caching — always fetch fresh to avoid stale/privacy issues |
| 54 | +- Live API integration tests in CI — unit tests with httptest mocks only, no API key in CI secrets |
| 55 | +- QR code generation — nice-to-have but not v1 |
| 56 | +- Local poll index (polls.yaml) — API has `GET /users/@me/polls` for server-side listing |
| 57 | +- Custom design/branding — premium-only, poor CLI UX |
| 58 | +- Image/media upload — web UI better for this |
| 59 | + |
| 60 | +## Context |
| 61 | + |
| 62 | +### StrawPoll API v3 |
| 63 | + |
| 64 | +- **Base URL:** `https://api.strawpoll.com/v3` |
| 65 | +- **Auth:** `X-API-KEY` header |
| 66 | +- **Endpoints:** |
| 67 | + - `POST /polls` — create poll (multiple_choice, meeting, ranking types) |
| 68 | + - `GET /polls/{id}` — get poll details |
| 69 | + - `PUT /polls/{id}` — update poll (title, options, config) |
| 70 | + - `GET /polls/{id}/results` — get results with per-participant breakdown |
| 71 | + - `DELETE /polls/{id}/results` — reset poll results/deadline |
| 72 | + - `DELETE /polls/{id}` — permanently delete poll |
| 73 | + - `GET /users/@me/polls` — list user's polls (paginated) |
| 74 | +- **Poll types:** `multiple_choice`, `meeting`, `ranking` |
| 75 | +- **Poll config fields:** is_private, vote_type, allow_comments, allow_indeterminate, allow_other_option, custom_design_colors, deadline_at, duplication_checking (ip/session/none), allow_vpn_users, edit_vote_permissions, force_appearance, hide_participants, is_multiple_choice, multiple_choice_min/max, number_of_winners, randomize_options, require_voter_names, results_visibility, use_custom_design, send_webhooks |
| 76 | +- **Known doc bugs:** `results_visibility: "hidden"` not `"never"`, `type: "ranking"` not `"ranked_choice"` — hardcode corrections |
| 77 | +- **OpenAPI spec:** `github.com/strawpoll-com/strawpoll-api-v3/blob/main/openapi/strawpoll_v3.yml` |
| 78 | +- **Pricing:** Free / Basic (€8/mo) / Pro (€28/mo) / Business (€52/mo) — API available on all plans |
| 79 | +- **Rate limits:** Not documented; implement defensive backoff |
| 80 | + |
| 81 | +### Existing CLI Patterns (Peter's Go CLI Suite) |
| 82 | + |
| 83 | +All of Peter's Go CLIs share a consistent architecture: |
| 84 | + |
| 85 | +- **Framework:** `alecthomas/kong` v1.13.0 with struct-based command definitions |
| 86 | +- **Auth:** `99designs/keyring` v1.2.2 for secure credential storage |
| 87 | +- **Config:** YAML at `~/.config/<app>/config.yaml` via `go.yaml.in/yaml/v3` |
| 88 | +- **Output:** Three modes via RootFlags: `--json`, `--plain`, `--no-color` |
| 89 | +- **Error handling:** `ExitError` struct with exit codes (0=OK, 1=Error, 2=Usage) |
| 90 | +- **Help:** Custom help printer with better formatting than Kong defaults |
| 91 | +- **Completion:** bash/zsh/fish generation |
| 92 | +- **Version:** Build-time injection via ldflags |
| 93 | +- **Project structure:** |
| 94 | + ``` |
| 95 | + cmd/strawpoll/main.go |
| 96 | + internal/cmd/ (CLI commands + root parser) |
| 97 | + internal/config/ (config file handling) |
| 98 | + internal/auth/ (keyring store) |
| 99 | + internal/api/ (HTTP client) |
| 100 | + internal/output/ (formatting: table, JSON, TSV) |
| 101 | + internal/ui/ (bubbletea TUI components) |
| 102 | + ``` |
| 103 | + |
| 104 | +### Reference Implementations |
| 105 | + |
| 106 | +- **Auth pattern:** `/Users/peter/Development/Go/delijn-cli/internal/auth/keyring.go` (simple API key keyring) |
| 107 | +- **Config pattern:** `/Users/peter/Development/Go/delijn-cli/internal/config/config.go` (YAML config) |
| 108 | +- **Help printer:** `/Users/peter/Development/Go/delijn-cli/internal/cmd/help_printer.go` |
| 109 | +- **TUI pattern:** `/Users/peter/Development/Go/harvest-cli/internal/ui/` (bubbletea components) |
| 110 | +- **Root structure:** `/Users/peter/Development/Go/delijn-cli/internal/cmd/root.go` |
| 111 | +- **Exit handling:** `/Users/peter/Development/Go/harvest-cli/internal/cmd/exit.go` |
| 112 | + |
| 113 | +## Constraints |
| 114 | + |
| 115 | +- **Tech stack**: Go 1.25, Kong CLI, keyring, bubbletea, goreleaser — matching existing CLI suite |
| 116 | +- **Module path**: `github.com/dedene/strawpoll-cli` |
| 117 | +- **Binary name**: `strawpoll` |
| 118 | +- **Repo**: `git@github.com:dedene/strawpoll-cli.git` |
| 119 | +- **API limitations**: No vote endpoint in OpenAPI spec, undocumented rate limits |
| 120 | +- **Enum corrections**: Must use actual API values (`hidden`, `ranking`) not documented ones (`never`, `ranked_choice`) |
| 121 | +- **Platform targets**: macOS arm64+amd64, Linux amd64+arm64, Windows amd64, Homebrew tap (`dedene/tap/strawpoll`) |
| 122 | +- **CI**: GitHub Actions (golangci-lint + tests + goreleaser) |
| 123 | +- **Testing**: Unit tests with httptest mocks only, no live API tests in CI |
| 124 | +- **Files**: <500 LOC per file, split/refactor as needed |
| 125 | + |
| 126 | +## Command Tree |
| 127 | + |
| 128 | +``` |
| 129 | +strawpoll |
| 130 | +├── auth |
| 131 | +│ ├── set-key # Store API key in keyring |
| 132 | +│ ├── status # Show whether key is stored |
| 133 | +│ └── remove # Delete stored key |
| 134 | +├── poll |
| 135 | +│ ├── create # Create multiple-choice poll (flags or defaults) |
| 136 | +│ ├── get <id|url> # Get poll details |
| 137 | +│ ├── update <id|url> # Update poll title, options, config |
| 138 | +│ ├── results <id|url> # View results (table + optional --participants) |
| 139 | +│ ├── reset <id|url> # Reset poll results/deadline (with confirmation) |
| 140 | +│ ├── delete <id|url> # Delete poll (with confirmation) |
| 141 | +│ └── list # List user's polls from API (paginated) |
| 142 | +├── meeting |
| 143 | +│ ├── create # Create meeting/scheduling poll (TUI or flags) |
| 144 | +│ ├── get <id|url> # Get meeting details |
| 145 | +│ ├── update <id|url> # Update meeting poll |
| 146 | +│ ├── results <id|url> # View participant grid |
| 147 | +│ ├── delete <id|url> # Delete meeting (with confirmation) |
| 148 | +│ └── list # List user's meeting polls |
| 149 | +├── ranking |
| 150 | +│ ├── create # Create ranking poll |
| 151 | +│ ├── get <id|url> # Get ranking details |
| 152 | +│ ├── update <id|url> # Update ranking poll |
| 153 | +│ ├── results <id|url> # View weighted + position breakdown |
| 154 | +│ ├── delete <id|url> # Delete ranking (with confirmation) |
| 155 | +│ └── list # List user's ranking polls |
| 156 | +├── config |
| 157 | +│ ├── show # Display current config |
| 158 | +│ ├── set <key> <val> # Set config value |
| 159 | +│ └── path # Show config file location |
| 160 | +├── completion # Generate shell completions |
| 161 | +└── version # Show version info |
| 162 | +``` |
| 163 | + |
| 164 | +## Key Decisions |
| 165 | + |
| 166 | +| Decision | Rationale | Outcome | |
| 167 | +|----------|-----------|---------| |
| 168 | +| Kong CLI framework | Matches all existing Go CLIs, struct-based, good help | — Pending | |
| 169 | +| Keyring for API key storage | Secure, cross-platform, proven in delijn-cli | — Pending | |
| 170 | +| Split commands (poll/meeting/ranking) | More discoverable than unified with --type flag | — Pending | |
| 171 | +| Full flags for all poll config | Power users want full control; sensible defaults from config | — Pending | |
| 172 | +| Bubbletea TUI for creation wizards | Proven in harvest-cli/irail-cli, better UX than stdin prompts | — Pending | |
| 173 | +| Remote-only poll listing | API has GET /users/@me/polls; no need for local polls.yaml | — Pending | |
| 174 | +| Skip voting commands | No vote endpoint in OpenAPI spec; voting via browser | — Pending | |
| 175 | +| Poll update via PUT endpoint | Research found PUT /polls/{id} exists; enables editing | — Pending | |
| 176 | +| Results reset command | DELETE /polls/{id}/results available; confirm prompt required | — Pending | |
| 177 | +| go.yaml.in/yaml/v3 over gopkg.in | gopkg.in/yaml.v3 archived April 2025; fork is API-compatible | — Pending | |
| 178 | +| Hardcode enum corrections | Known doc bugs; hardcoding avoids user confusion | — Pending | |
| 179 | +| Human-readable flag names | --dupcheck not --duplication-checking; CLI maps internally | — Pending | |
| 180 | +| TSV for --plain output | Tab-separated with headers; works for both 1D and 2D result data | — Pending | |
| 181 | +| Confirm prompt on delete | Permanent operation; --force for scripting | — Pending | |
| 182 | +| URL + ID parsing | Users copy URLs from browser; auto-extract ID for convenience | — Pending | |
| 183 | +| Config defaults for poll creation | Saves typing for repeat users; flags override | — Pending | |
| 184 | +| Unit tests only | No live API key in CI; httptest mocks sufficient | — Pending | |
| 185 | +| All platforms + Homebrew | Maximum reach; goreleaser handles multi-platform | — Pending | |
| 186 | + |
| 187 | +--- |
| 188 | +*Last updated: 2026-02-05 after research phase — corrected API capabilities, removed voting, added update/reset/remote listing* |
0 commit comments