Skip to content

Commit 07c63b6

Browse files
committed
feat: initial release of strawpoll-cli
StrawPoll API v3 CLI — create, manage, and view polls from the terminal. - Auth: keyring storage + env var fallback - Polls: create/get/update/results/reset/delete/list with 20+ config flags - Meetings: date/time scheduling with availability grid - Rankings: Borda count scoring with position breakdown - TUI: interactive wizards for poll and meeting creation (huh forms) - Output: JSON, TSV, colored tables with --copy/--open - Config: YAML defaults, shell completions (bash/zsh/fish) - CI/CD: golangci-lint v2, goreleaser, GitHub Actions
0 parents  commit 07c63b6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+6950
-0
lines changed

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
lint:
14+
name: Lint
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-go@v5
19+
with:
20+
go-version-file: go.mod
21+
- uses: golangci/golangci-lint-action@v7
22+
with:
23+
version: v2.8.0
24+
25+
test:
26+
name: Test
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- uses: actions/setup-go@v5
31+
with:
32+
go-version-file: go.mod
33+
- run: go test ./... -v -race

.github/workflows/release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
name: Release
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
- uses: actions/setup-go@v5
20+
with:
21+
go-version-file: go.mod
22+
- uses: goreleaser/goreleaser-action@v6
23+
with:
24+
version: "~> v2"
25+
args: release --clean
26+
env:
27+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}

.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Created by https://www.toptal.com/developers/gitignore/api/go
2+
# Edit at https://www.toptal.com/developers/gitignore?templates=go
3+
4+
### Go ###
5+
# If you prefer the allow list template instead of the deny list, see community template:
6+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
7+
#
8+
# Binaries for programs and plugins
9+
*.exe
10+
*.exe~
11+
*.dll
12+
*.so
13+
*.dylib
14+
15+
# Test binary, built with `go test -c`
16+
*.test
17+
18+
# Output of the go coverage tool, specifically when used with LiteIDE
19+
*.out
20+
21+
# Dependency directories (remove the comment below to include it)
22+
# vendor/
23+
24+
# Go workspace file
25+
go.work
26+
27+
# Built binaries and local tools
28+
/bin/
29+
/.tools/
30+
31+
# End of https://www.toptal.com/developers/gitignore/api/go
32+
.claude
33+
.planning/
34+
/tmp

.golangci.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
version: "2"
2+
linters:
3+
default: standard
4+
enable:
5+
- errcheck
6+
- govet
7+
- staticcheck
8+
- unused
9+
- ineffassign
10+
settings:
11+
errcheck:
12+
check-type-assertions: true
13+
exclude-functions:
14+
- fmt.Fprint
15+
- fmt.Fprintf
16+
- fmt.Fprintln
17+
exclusions:
18+
rules:
19+
- path: _test\.go
20+
linters:
21+
- errcheck

.goreleaser.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
version: 2
2+
project_name: strawpoll
3+
4+
builds:
5+
- main: ./cmd/strawpoll
6+
binary: strawpoll
7+
env:
8+
- CGO_ENABLED=0
9+
goos:
10+
- darwin
11+
- linux
12+
- windows
13+
goarch:
14+
- amd64
15+
- arm64
16+
ignore:
17+
- goos: windows
18+
goarch: arm64
19+
ldflags:
20+
- -s -w
21+
- -X github.com/dedene/strawpoll-cli/internal/cmd.version={{ .Tag }}
22+
- -X github.com/dedene/strawpoll-cli/internal/cmd.commit={{ .ShortCommit }}
23+
- -X github.com/dedene/strawpoll-cli/internal/cmd.date={{ .Date }}
24+
25+
archives:
26+
- formats:
27+
- tar.gz
28+
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
29+
format_overrides:
30+
- goos: windows
31+
formats:
32+
- zip
33+
34+
homebrew_casks:
35+
- repository:
36+
owner: dedene
37+
name: homebrew-tap
38+
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
39+
homepage: "https://github.com/dedene/strawpoll-cli"
40+
description: "StrawPoll CLI - Create and manage polls from the command line"
41+
license: "MIT"
42+
43+
checksum:
44+
name_template: "checksums.txt"
45+
46+
changelog:
47+
sort: asc
48+
filters:
49+
exclude:
50+
- "^docs:"
51+
- "^test:"
52+
- "^chore:"

.planning/PROJECT.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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*

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Peter Dedene
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)