wiq-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,348 @@
1
+ ---
2
+ name: wiq
3
+ description: Use this skill when the user asks about their WrestlingIQ data — rosters, attendance, check-ins, paid sessions and registrations, the prospects/leads pipeline, financial metrics, reports, USAW/AAU memberships, fundraising, or online store orders. The `wiq` CLI provides read-only access to /api/v1 via personal access tokens. Recognize phrasings like "how is our pipeline?", "who came to practice this week?", "show me the roster", "what's our MRR?", "which kids need USAW renewal?", or anything that maps to a wrestling club's admin workflows.
4
+ ---
5
+
6
+ # WrestlingIQ CLI Skill
7
+
8
+ `wiq` is a read-only Ruby CLI for WrestlingIQ data, designed to be driven
9
+ by both humans and AI agents. It hits `/api/v1` using per-user personal
10
+ access tokens (PATs). This skill orients you to its shape; the CLI itself
11
+ is the source of truth for what's available right now.
12
+
13
+ ## Bootstrap (always do this first)
14
+
15
+ Three calls give you the entire usable surface:
16
+
17
+ ```bash
18
+ wiq doctor # Confirms reach, identity, and which team you're scoped to
19
+ wiq commands # JSON dump of every command, option, enum (42 KB; cache this)
20
+ wiq workflows list # 14 curated multi-step recipes by category
21
+ ```
22
+
23
+ Run these BEFORE inventing commands. The `wiq commands` output is
24
+ authoritative — if it doesn't appear there, it doesn't exist in this
25
+ build of the CLI.
26
+
27
+ ## Three core surfaces
28
+
29
+ **`wiq commands`** — flat machine-readable JSON tree of the CLI. Use this
30
+ when you need to know what commands exist, what options they take, what
31
+ enums are valid. Cache the output in your context; it doesn't change
32
+ within a session.
33
+
34
+ **`wiq workflows`** — named recipes for common questions. Each workflow
35
+ has a structured `parameters` list, a `recipe` of command strings with
36
+ `<placeholder>` and `[--flag <name>]` substitutions, an `admin_only`
37
+ flag, and notes. Map the user's natural-language question to a workflow
38
+ when one fits; fall back to composing commands otherwise.
39
+
40
+ **`wiq reports types`** — curated allowlist of WIQ's 36 report types with
41
+ `recommended:` / `admin_only:` / `prefer:` / `notes:` / `example:`
42
+ metadata. When a user asks for "an attendance report" or "a finance
43
+ report", check this list first — the WIQ team has marked the right pick
44
+ per situation.
45
+
46
+ ## Auth model (load-bearing)
47
+
48
+ - **PATs are bound to a single profile at mint time.** A user with
49
+ CoachProfile + ParentProfile on different teams mints a separate token
50
+ per role. The CLI does not switch profiles at runtime.
51
+ - **Everything is gated by the bound profile's permissions.** Parent or
52
+ wrestler PATs cannot run reports (`enforce_pat_restrictions` returns 403
53
+ with body `"Personal access tokens are read-only..."` — reports is the
54
+ one write currently on the PAT allowlist).
55
+ - **The 9 finance reports require `admin?`.** They're flagged
56
+ `admin_only: true` in `wiq reports types`. Workflows that submit them
57
+ carry the same flag. Non-admin coach PATs 403 on these.
58
+ - **`elite` and `payments_enabled` are NOT API gates.** They're team
59
+ attributes that affect UI tab rendering only. Ignore them when
60
+ reasoning about API access.
61
+
62
+ ## Multi-club (alias mechanic)
63
+
64
+ A single user may have PATs for multiple clubs on the same host
65
+ (`www.wrestlingiq.com`). The CLI stores them by `host → alias` in
66
+ `~/.config/wiq/credentials.json`:
67
+
68
+ ```bash
69
+ wiq auth list # See all stored credentials
70
+ wiq auth status # Resolved alias + bound profile
71
+ wiq --as westside rosters list # Use a specific alias
72
+ WIQ_ALIAS=westside wiq events list # Same via env var
73
+ ```
74
+
75
+ Resolution chain: `--as` flag → `WIQ_ALIAS` env → `.wiq/config.json` →
76
+ sole alias for host → literal `"default"`. Ambiguous-alias errors
77
+ include a list of available aliases in the `hint:` field.
78
+
79
+ ## Output modes
80
+
81
+ | Mode | When it kicks in |
82
+ | --- | --- |
83
+ | `--json` | Full envelope: `{ok, data, summary, breadcrumbs, meta}`. Best for agents needing context. |
84
+ | `--agent` | Bare `data` as JSON, no envelope, no prompts. Best for headless piping. |
85
+ | (default) | Pretty JSON when stdout is a TTY; otherwise bare JSON (same as `--agent`). |
86
+
87
+ The envelope's `breadcrumbs` array suggests obvious next-step commands —
88
+ read it when deciding what to do after a response.
89
+
90
+ ## Error model
91
+
92
+ All errors come back as a stable JSON envelope on stderr with a
93
+ non-zero exit code:
94
+
95
+ ```json
96
+ { "ok": false, "error": "...", "code": "...", "hint": "...", "details": ... }
97
+ ```
98
+
99
+ Common codes and the right response:
100
+
101
+ | `code` | Meaning | What to do |
102
+ | --- | --- | --- |
103
+ | `host_unset` | No host configured (unusual; production is the default) | `wiq auth login` |
104
+ | `not_authenticated` | No PAT stored for the resolved host | `wiq auth login` |
105
+ | `ambiguous_alias` | Multiple aliases stored, none "default" | Pass `--as <alias>` (hint lists choices) |
106
+ | `alias_not_found` | `--as` named a slot that doesn't exist | Check `wiq auth list` |
107
+ | `unauthorized` (401) | Token rejected by server | Re-login; could be revoked |
108
+ | `forbidden` (403) | Pundit denial (often admin gate on a finance report) | Check the report's `admin_only:` flag in `wiq reports types` |
109
+ | `not_found` (404) | Resource doesn't exist for this profile | Confirm the id; cross-team enumeration returns 404 to avoid leaking existence |
110
+ | `validation_failed` (422) | Server-side input rejection | Read `details` for per-field messages |
111
+ | `rate_limited` (429) | 100 req / 3 sec per IP exceeded | Back off |
112
+ | `season_not_found` | `--season <year>` matched zero paid sessions | `wiq paid_sessions list` to see configured periods |
113
+ | `report_failed` | The report ran but errored server-side | Inspect `details` (the report's result jsonb) |
114
+ | `report_timeout` | Polling exceeded `--timeout` | Re-run with longer `--timeout` or check later with `wiq reports show <id>` |
115
+
116
+ ## Rate-limit etiquette
117
+
118
+ WIQ enforces **100 requests per 3 seconds per source IP** (≈ 33 req/sec).
119
+ It's a per-IP throttle, not per-PAT — multi-club aliases on the same
120
+ workstation share the budget.
121
+
122
+ In practice the CLI rarely hits it because:
123
+
124
+ - Pagination (`--all`) walks pages sequentially, naturally rate-limited
125
+ by request latency (100–500ms round-trip = 2–10 req/sec, well under).
126
+ - Report polling backs off exponentially (2s → 30s cap).
127
+ - Transient 429s are auto-retried by the Faraday middleware (3 attempts,
128
+ exponential backoff, `Retry-After` honored).
129
+
130
+ **Agent guidance:** invoke `wiq` commands **sequentially** from scripts.
131
+ Don't parallelize a fan-out (e.g. running three `wiq metrics show`
132
+ calls at once) — the per-IP budget is shared across every concurrent
133
+ process. If you see `code: "rate_limited"` after the auto-retries
134
+ exhaust, treat it as backpressure: wait ~3 seconds, then continue
135
+ sequentially. It's not a bug; you're just moving faster than the
136
+ server wants you to.
137
+
138
+ ## Pagination
139
+
140
+ Index endpoints (`wiq rosters list`, `wiq events list`, etc.) page at 30
141
+ per page by default. The CLI surfaces this in two ways:
142
+
143
+ - Without `--all`: returns page 1 only. The envelope `meta` block carries
144
+ `total` (server-side total record count).
145
+ - With `--all`: follows `Link: rel=next` until exhausted. Required when
146
+ doing CLI-side filtering (`--season`, `--site` if it ever lands) since
147
+ filters apply *after* the fetch.
148
+
149
+ The server's `TotalCount` header is non-standard capitalization (not
150
+ `X-Total-Count`); the CLI handles this internally.
151
+
152
+ ## Reports — choosing the right type
153
+
154
+ Use `wiq reports types` as the decision matrix. Three guidelines:
155
+
156
+ 1. **`recommended: true` entries are the WIQ-blessed picks.** When a
157
+ user's question matches multiple reports, prefer recommended ones
158
+ unless they specifically asked for a non-recommended variant.
159
+ 2. **`recommended: false` with `prefer:` lists alternatives.** Example:
160
+ `PracticeAttendanceReport` is deprecated → use `CheckInSummaryReport`
161
+ or `CheckInFeedReport` instead.
162
+ 3. **`admin_only: true` reports require `admin?` permission.** If the
163
+ PAT isn't admin, the submit returns 403. Check the bound profile's
164
+ permission level before suggesting an admin report.
165
+
166
+ Key reports an agent should know by heart:
167
+
168
+ - **`CheckInSummaryReport`** — "How many practices did each wrestler
169
+ attend?" (one row per wrestler, totals).
170
+ - **`CheckInFeedReport`** — "Who came today?" (one row per check-in).
171
+ - **`ChurnRiskReport`** — Active subscribers who haven't checked in
172
+ within N days (7, 14, 30, 60, 90). Requires `--days-threshold`.
173
+ - **`RosterReport`** — Roster snapshot with optional custom columns via
174
+ `--append-properties <q_ids>`. Discover ids via `wiq registrations questions`.
175
+ - **`LastPracticeAttendedReport`** — "Who hasn't been to practice in a
176
+ while?"
177
+ - **`PaidSessionAccountingReport`** (admin_only) — Line-item charges for
178
+ a paid session.
179
+
180
+ For anything else, `wiq reports types` is faster than guessing.
181
+
182
+ ## Reports — polling
183
+
184
+ Submit + poll is the API's canonical async pattern:
185
+
186
+ ```bash
187
+ wiq reports run <Type> --start <date> --end <date> [args]
188
+ # Submits, then polls every 2s (backoff to 30s cap) until ready.
189
+ # Default timeout 5 min; --timeout overrides.
190
+ ```
191
+
192
+ `--no-wait` returns immediately after submit; poll later with
193
+ `wiq reports show <id> --wait`. Status values: `requested → queued →
194
+ processing → ready` (terminal) | `failed` (terminal). `result` is the
195
+ type-specific jsonb payload.
196
+
197
+ ## Common gotchas
198
+
199
+ - **`roster_id=0` = "all rosters"** in report args. UI convention; if
200
+ you want all rosters, pass `--roster 0` explicitly. Reports always
201
+ accept it.
202
+ - **`paid_session_id=0` = "all sessions"** is accepted by ONE report
203
+ only: `UsawExportReport`. Anywhere else, `0` causes a 404 on
204
+ `PaidSession.find(0)`.
205
+ - **`--season <year>` is CLI-side filtering, not a server param.** It
206
+ resolves to paid_session ids whose date range overlaps the calendar
207
+ year, then filters client-side. There's no first-class Season entity
208
+ in WIQ.
209
+ - **`Event.location` is free-text and not filterable.** No `--site` or
210
+ `--location` flag exists yet. A structured location concept is on the
211
+ WIQ roadmap.
212
+ - **Index responses are wrapped.** Every `/api/v1` index returns
213
+ `{"<resource>": [...]}` — the CLI unwraps internally, but if you ever
214
+ hit the API directly remember to unwrap.
215
+ - **Custom registration columns are `RosterReport`-only.** The
216
+ `--append-properties` flag is accepted by other reports but only
217
+ RosterReport's model code reads it.
218
+ - **`days_threshold` for `ChurnRiskReport`** accepts only `7, 14, 30,
219
+ 60, 90`. Any other value silently falls back to 30 server-side.
220
+ - **`wrestler_id` vs `wrestler_profile_id`.** The API uses both
221
+ interchangeably in different endpoints — the CLI normalizes, but if
222
+ you're constructing URLs yourself, expect the inconsistency.
223
+
224
+ ## Integer-backed enums (Ransack gotcha for future write paths)
225
+
226
+ Some WIQ models back enums with integers (Rails default). Ransack 4.x
227
+ does NOT translate enum strings to integers on those columns — sending
228
+ `q[status_eq]=failed` against an integer column silently drops the
229
+ predicate and returns every row, not zero rows.
230
+
231
+ The CLI translates internally for the surfaces it exposes
232
+ (`wiq charges list --status`, `wiq check_ins event --status`). If you
233
+ construct ad-hoc `q[...]` filters via `curl` or a future surface, watch
234
+ for this. Known integer-enum columns the CLI touches today:
235
+
236
+ | Model | Field | Values |
237
+ | --- | --- | --- |
238
+ | Charge | status | successful (0), failed (1) |
239
+ | CheckIn | status | unknown (0), present (1), absent (2), excused (3), unexcused (4), late (5), injured (6), other (7) |
240
+ | WrestlerProfile | gender | male (0), female (1), other (2) |
241
+ | PaidSession | usaw_override / aau_override | disabled (0), optional (1), require_id_and_expires (2), require_all (3) |
242
+
243
+ String columns (Prospect.stage, WrestlerProfile.profile_type / academic_class)
244
+ do NOT have this issue — Ransack matches the string directly.
245
+
246
+ ## Payment debugging
247
+
248
+ For "did Johnny pay X?" / "what's failing right now?" / "is this family's
249
+ subscription healthy?" — use the charges surface (admin coach PAT only):
250
+
251
+ ```bash
252
+ wiq charges list --status failed --since 2026-04-01
253
+ wiq charges list --billing-profile <id> --since 2026-04-01 --all
254
+ wiq billing_profiles show <parent_profile_id> --profile-type ParentProfile
255
+ ```
256
+
257
+ **Critical: a failed charge alone is NEVER actionable.** Stripe/Justifi
258
+ retry subscriptions automatically and customers re-enter cards after
259
+ declines, so most failures resolve themselves silently. Before
260
+ flagging anything as needing follow-up, cross-check that no successful
261
+ charge for the same `(billing_profile_id, chargeable_id,
262
+ chargeable_type)` tuple exists AFTER the failure's `created_at`. The
263
+ canonical pattern is in `wiq workflows show failed-payments-recent`.
264
+
265
+ To go from a wrestler name to a billing_profile_id:
266
+
267
+ ```bash
268
+ wiq wrestlers list --query "Johnny Smith" # find wrestler_id
269
+ wiq wrestlers show <wrestler_id> # find parent_profile_id
270
+ wiq billing_profiles show <parent_id> --profile-type ParentProfile # billing_profile_id
271
+ wiq charges list --billing-profile <bp_id> # their payment history
272
+ ```
273
+
274
+ WrestlerProfile cannot have billing profiles directly — always walk
275
+ through a parent.
276
+
277
+ ## ID discovery
278
+
279
+ ### Disambiguating "who is X?"
280
+
281
+ WIQ tracks people in three states. When the user asks about a person by
282
+ name (e.g., "show me Johnny"), decide which scope to search FIRST,
283
+ based on context:
284
+
285
+ | If recent context is about… | Search this |
286
+ | --- | --- |
287
+ | Leads / pipeline / trials / "potential new kid" | `wiq prospect_families list --query <name>` |
288
+ | Active club members / attendance / rosters / subscriptions | `wiq wrestlers list --query <name>` |
289
+ | Former members / graduates | `wiq wrestlers list --query <name> --profile-type alumnus` |
290
+ | Truly ambiguous, no prior context | **Ask the user.** Don't fan out across both. |
291
+
292
+ Don't default to wrestlers because it's listed first alphabetically —
293
+ that wastes a call when the user is clearly asking about a lead.
294
+
295
+ ### How `--query` actually matches
296
+
297
+ - **`wiq wrestlers list --query`** — name search (first/last) against
298
+ active WrestlerProfile rows on the team.
299
+ - **`wiq prospect_families list --query`** — searches BOTH family
300
+ contact (name, email, phone — digits stripped for phone match) AND
301
+ child first/last names via a subquery. A "Johnny" search matches a
302
+ parent named Johnny OR a child named Johnny; check
303
+ `child_first_name` on the embedded prospects array to tell which.
304
+ - **`wiq prospects list --query`** — same scope as families above, but
305
+ currently returns HTTP 500 due to a server-side ambiguous-column bug
306
+ (tracked in `docs/deferred.md`). Use `prospect_families list --query`
307
+ instead until the WIQ-app fix ships.
308
+
309
+ ### Wrestlers-specific filters
310
+
311
+ ```bash
312
+ wiq wrestlers list --query "Jane Smith"
313
+ wiq wrestlers list --last-name Smith
314
+ wiq wrestlers list --roster 42 --weight-class 132
315
+ ```
316
+
317
+ Narrow surface on purpose: default page size 20, no `--all` flag.
318
+ For exhaustive exports go through `wiq reports run RosterReport`
319
+ (with `--append-properties` for custom columns) or
320
+ `wiq reports run FullExportWrestlerReport`. The list defaults to
321
+ `profile_type=teammate` (matching the WIQ web UI default); pass
322
+ `--profile-type alumnus|guest|all` to widen.
323
+
324
+ ## What's NOT available (yet)
325
+
326
+ The CLI is read-only by design except for report submission. You
327
+ cannot via this CLI:
328
+
329
+ - Create/edit prospects, families, notes, check-ins, events, paid
330
+ sessions, rosters, or any other resource
331
+ - Mint, list, or revoke PATs (use the web UI at
332
+ `<host>/settings/personal_access_tokens`)
333
+ - Mark attendance, advance prospect stages, log contact notes
334
+ - Trigger event-change notifications or roster re-sync jobs
335
+
336
+ If the user asks for a write operation, tell them it's not yet exposed
337
+ and point them at the WIQ web UI for now.
338
+
339
+ ## When to compose vs run a workflow
340
+
341
+ - **Run a workflow** when the user's question matches one closely. They
342
+ encode WIQ's recommended patterns and reduce drift risk.
343
+ - **Compose ad-hoc** when the user wants something workflow-shaped but
344
+ with twists (date range, roster scope, additional filtering).
345
+ Reference the workflow's recipe as a template, then adapt.
346
+ - **Never** compose finance commands without first checking
347
+ `wiq auth status` to confirm `admin?` permission — saves the agent
348
+ from running into avoidable 403s mid-sequence.
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wiq-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - WrestlingIQ
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday-retry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.13'
69
+ description: Agent-native CLI for reading WrestlingIQ data via /api/v1 personal access
70
+ tokens.
71
+ email:
72
+ - support@wrestlingiq.com
73
+ executables:
74
+ - wiq
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - LICENSE
79
+ - README.md
80
+ - bin/wiq
81
+ - docs/deferred.md
82
+ - docs/wiq_api_notes.md
83
+ - lib/wiq.rb
84
+ - lib/wiq/cli.rb
85
+ - lib/wiq/client.rb
86
+ - lib/wiq/commands/auth.rb
87
+ - lib/wiq/commands/base.rb
88
+ - lib/wiq/commands/billing_profiles.rb
89
+ - lib/wiq/commands/charges.rb
90
+ - lib/wiq/commands/check_ins.rb
91
+ - lib/wiq/commands/doctor.rb
92
+ - lib/wiq/commands/events.rb
93
+ - lib/wiq/commands/metrics.rb
94
+ - lib/wiq/commands/paid_sessions.rb
95
+ - lib/wiq/commands/prospect_families.rb
96
+ - lib/wiq/commands/prospects.rb
97
+ - lib/wiq/commands/registrations.rb
98
+ - lib/wiq/commands/reports.rb
99
+ - lib/wiq/commands/rosters.rb
100
+ - lib/wiq/commands/setup.rb
101
+ - lib/wiq/commands/workflows.rb
102
+ - lib/wiq/commands/wrestlers.rb
103
+ - lib/wiq/config.rb
104
+ - lib/wiq/credentials.rb
105
+ - lib/wiq/errors.rb
106
+ - lib/wiq/introspection.rb
107
+ - lib/wiq/output.rb
108
+ - lib/wiq/pagination.rb
109
+ - lib/wiq/season_resolver.rb
110
+ - lib/wiq/version.rb
111
+ - lib/wiq/workflows.rb
112
+ - share/skills/wiq/SKILL.md
113
+ homepage: https://github.com/wrestlingiq/wiq-cli
114
+ licenses:
115
+ - MIT
116
+ metadata:
117
+ homepage_uri: https://github.com/wrestlingiq/wiq-cli
118
+ source_code_uri: https://github.com/wrestlingiq/wiq-cli
119
+ bug_tracker_uri: https://github.com/wrestlingiq/wiq-cli/issues
120
+ documentation_uri: https://github.com/wrestlingiq/wiq-cli#readme
121
+ rubygems_mfa_required: 'true'
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 3.1.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.3.27
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Command-line interface for the WrestlingIQ API
141
+ test_files: []