@50heads/cli 2026.1009.1 → 2026.1013.1
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.
- package/CHANGELOG.md +20 -0
- package/README.md +110 -6
- package/dist/cli.js +270 -78
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,24 @@ Pending changes are captured as changesets and written here when the package is
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [2026.1013.1] - 2026-10-13
|
|
12
|
+
|
|
13
|
+
**@50heads/cli 2026.1013.1 adds filing, organisation, webhook, answers export and approval flows, plus new status and limits commands.** It also changes login scopes for organisation admin access and updates wording and output to US English, with no change to commands or tools beyond the MCP server tool count.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **New filing commands: 50heads projects lists, creates, updates and deletes projects; 50heads labels lists labels in use; and 50heads file moves a question into a project, labels, archives or bookmarks it, or sets a reference once.**
|
|
18
|
+
- **50heads webhooks now lists, creates, updates, rotates, tests and deletes endpoints, lists deliveries and replays them, and can verify a captured delivery offline against Standard Webhooks and X-50heads-Signature.**
|
|
19
|
+
- **50heads answers --format jsonl now prints each matching answer as JSON lines, with choice and cohortHash, and --out can write it to a file.**
|
|
20
|
+
- **New organisation commands read domains, DNS records, SCIM provisioning, sessions, service accounts and audit events, with audit export to JSON lines or CSV.**
|
|
21
|
+
- **New status and limits commands show who the sign-in or key acts as, and the rate limits on the key and current usage in the window.**
|
|
22
|
+
- **Spend approvals are now supported in ask, including --wait-approval and 50heads approvals wait or status for an approval already asked.**
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **login now asks for organization:read, members:read and audit:read so an organization admin can use org and audit, and existing sign-ins and API keys keep their current scopes.**
|
|
27
|
+
- **Spelling is now US English throughout, including canceled, unauthorized, organization, authorized and enroll, and ask and estimate now print the answer count and price with no time estimate.**
|
|
28
|
+
|
|
11
29
|
## [2026.1009.1] - 2026-10-09
|
|
12
30
|
|
|
13
31
|
**@50heads/cli 2026.1009.1 adds private asks for your own audience, including shared links, answer limits and set publishing from CSV.** It also updates wording and result output to show verified people, tier details and closed links, and adds more language options for ask.
|
|
@@ -56,3 +74,5 @@ Pending changes are captured as changesets and written here when the package is
|
|
|
56
74
|
[2026.925.1]: https://github.com/50heads/cli/releases/tag/v2026.925.1
|
|
57
75
|
|
|
58
76
|
[2026.1009.1]: https://github.com/50heads/cli/releases/tag/v2026.1009.1
|
|
77
|
+
|
|
78
|
+
[2026.1013.1]: https://github.com/50heads/cli/releases/tag/v2026.1013.1
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 50heads CLI
|
|
2
2
|
|
|
3
|
-
Ask verified people from a script, a Makefile or CI, wait for the answers and
|
|
3
|
+
Ask verified people from a script, a Makefile or CI, wait for the answers and see what they think. 20 credits an answer at Tier 1, priced in your account's currency.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx -y @50heads/cli --help
|
|
@@ -23,7 +23,8 @@ On your own machine you can sign in once in the browser instead:
|
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
25
|
50heads login # kept in the macOS Keychain, the Secret Service, or ~/.config/50heads (mode 600)
|
|
26
|
-
50heads status
|
|
26
|
+
50heads status # how this machine is signed in, and who that acts as
|
|
27
|
+
50heads limits # the rate limits on this key, and how much of each is used
|
|
27
28
|
50heads logout
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -34,7 +35,7 @@ The same sign-in works for `npx @50heads/mcp`. The API key wins when both are th
|
|
|
34
35
|
```sh
|
|
35
36
|
# Price and time first. Never spends.
|
|
36
37
|
50heads estimate --text "Which name sounds friendlier?" --option Pip --option Moss
|
|
37
|
-
# 50 answers,
|
|
38
|
+
# 50 answers, $12.70. 20 credits an answer.
|
|
38
39
|
|
|
39
40
|
# Ask and wait for the answers.
|
|
40
41
|
50heads ask --text "Which name sounds friendlier?" --option Pip --option Moss --wait
|
|
@@ -63,6 +64,16 @@ The same sign-in works for `npx @50heads/mcp`. The API key wins when both are th
|
|
|
63
64
|
|
|
64
65
|
Wrap it as `{"draft": {…}, "then": [{"text": "Why did you pick {winner}?"}]}` to add follow-ups.
|
|
65
66
|
|
|
67
|
+
### Go or no go
|
|
68
|
+
|
|
69
|
+
On a yes/no question, `--go-above 60` asks for a verdict: a go when Yes is above 60% with enough answers, a no go when it is not, and unclear when there are too few answers or the range crosses the line. `--go-min-answers` sets the answers needed (default 80% of `--n`). The line is fixed once the question is live and costs nothing extra. The result prints the verdict first, as how these people answered against your line, not a fact.
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
50heads ask --text "Is this button label clear enough to ship?" --go-above 60 --wait
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Ranking and pairwise results also print who beats whom, and pairwise rows a 95% range on each win share.
|
|
76
|
+
|
|
66
77
|
### Many at once
|
|
67
78
|
|
|
68
79
|
```sh
|
|
@@ -70,7 +81,7 @@ Wrap it as `{"draft": {…}, "then": [{"text": "Why did you pick {winner}?"}]}`
|
|
|
70
81
|
cat asks.jsonl | 50heads ask - --json
|
|
71
82
|
```
|
|
72
83
|
|
|
73
|
-
CSV columns (a header row, any order): `text` (required), `type`, `options` (`A|B|C`) or `option1` to `option8`, `images` (`|`, in option order), `context`, `language`, `n`, `tier`, `rush`, `neither`, `mostly` (on a yes/no row, adds Mostly; otherwise the row is Yes or No), `countries` (`|`), `tags` (`|`), `stimulus_image`, `stimulus_text`. Every row is checked before anything is asked; a row that fails is reported and the rest go ahead (exit 8).
|
|
84
|
+
CSV columns (a header row, any order): `text` (required), `type`, `options` (`A|B|C`) or `option1` to `option8`, `images` (`|`, in option order), `context`, `language`, `n`, `tier`, `rush`, `neither`, `mostly` (on a yes/no row, adds Mostly; otherwise the row is Yes or No), `go_above` and `min_answers` (on a yes/no row, the go line as a whole percentage and the answers needed), `countries` (`|`), `tags` (`|`), `stimulus_image`, `stimulus_text`. Every row is checked before anything is asked; a row that fails is reported and the rest go ahead (exit 8).
|
|
74
85
|
|
|
75
86
|
### Never twice by accident
|
|
76
87
|
|
|
@@ -83,6 +94,7 @@ Every ask carries an idempotency key made from a hash of the question. Running t
|
|
|
83
94
|
50heads results q_123 # the result so far
|
|
84
95
|
50heads answers q_123 --option 0 --limit 100
|
|
85
96
|
50heads answers q_123 --q price --all --json # one answer per line
|
|
97
|
+
50heads answers q_123 --format jsonl --out q_123.jsonl # JSON lines, for scripts and model training
|
|
86
98
|
50heads list --status live
|
|
87
99
|
50heads cancel q_123 # stops it; unanswered heads are refunded
|
|
88
100
|
50heads balance
|
|
@@ -91,6 +103,96 @@ Every ask carries an idempotency key made from a hash of the question. Running t
|
|
|
91
103
|
|
|
92
104
|
`answers` filters by `--option` (0 is the first), `--tier`, `--country`, `--age-band` and `--q` (a word in written answers). Country and age filters show nothing when fewer than 5 answers match. Answers never identify a head.
|
|
93
105
|
|
|
106
|
+
`answers --format jsonl` prints every matching answer as JSON lines (one answer per line), from the API's JSONL export: the question, its options, the answer, `choice` (the answer as option indexes: `index`, `indexes`, `order` or `pair`) and `cohortHash`. `--out FILE` writes it to a file instead of stdout.
|
|
107
|
+
|
|
108
|
+
`results` ends with the cohort hash: the same value means the same kind of audience was asked (who, how many, which tier and targeting). It never identifies anyone.
|
|
109
|
+
|
|
110
|
+
## File
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
50heads projects create "Spring menu" --description "Names and menus for the spring launch"
|
|
114
|
+
50heads projects list # with question counts; --archived for archived ones
|
|
115
|
+
50heads file q_123 --project prj_1 --label naming --label launch --bookmark
|
|
116
|
+
50heads file q_123 --ref JIRA-42 # your own id, set once
|
|
117
|
+
50heads labels # labels in use, with counts
|
|
118
|
+
50heads list --project prj_1 --label naming
|
|
119
|
+
50heads projects update prj_1 --archive
|
|
120
|
+
50heads projects delete prj_1 --yes # the questions are kept, outside any project
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Filing costs nothing. A live question must be canceled before it is archived. A different `--ref` on a question that already has one is refused (exit 5).
|
|
124
|
+
|
|
125
|
+
## Webhooks
|
|
126
|
+
|
|
127
|
+
Instead of polling, have 50heads send events to your own https address as they happen. The key or sign-in needs the `webhooks:read` and `webhooks:write` scopes; in a team, changes need the owner or an admin.
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
50heads webhooks create --url https://example.com/hooks/50heads \
|
|
131
|
+
--event question.completed --event question.underfilled --label launch
|
|
132
|
+
# whe_123 https://example.com/hooks/50heads on: question.completed, question.underfilled; only labels launch
|
|
133
|
+
# Signing secret (shown only now): whsec_…
|
|
134
|
+
50heads webhooks test whe_123 # a signed webhook.test event now; exit 1 if it did not get a 2xx
|
|
135
|
+
50heads webhooks list
|
|
136
|
+
50heads webhooks update whe_123 --pause # --resume, --event, --url, --project, --label, --clear-filter
|
|
137
|
+
50heads webhooks rotate whe_123 --overlap-hours 48
|
|
138
|
+
50heads webhooks deliveries --failed
|
|
139
|
+
50heads webhooks replay dlv_456
|
|
140
|
+
50heads webhooks delete whe_123 --yes
|
|
141
|
+
50heads webhooks events --type question.completed --limit 5 # the latest events, as deliveries carry them
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Events: `question.reserved`, `question.completed`, `question.underfilled`, `question.canceled`, `question.refused`, `credits.purchased`, `credits.low`, `credits.expiring`, `dispute.resolved`, `connection.cap_warning`, `approval.requested`, `approval.decided`, `approval.expired`, `approval.refused`, and for an organization's main team `member.provisioned`, `member.deprovisioned` and `policy.changed`. `--project` and `--label` limit question events to those projects or labels; other events always pass. The secret is printed once, on `create` and on `rotate`; after a rotation the old secret keeps signing for 24 hours unless you say otherwise (0 to 168), so the receiver can switch without missing a delivery.
|
|
145
|
+
|
|
146
|
+
Each delivery is signed twice: the Standard Webhooks headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`), which the Standard Webhooks libraries verify with the secret as given, and `X-50heads-Signature`. To check a delivery you captured, with no network:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
FIFTYHEADS_WEBHOOK_SECRET=whsec_… 50heads webhooks verify --body-file body.json --headers-file headers.txt
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`--headers-file` takes `Name: value` lines (as `curl -D` writes them) or a JSON object. The body must be byte for byte as received. `--tolerance 300` also refuses a signature more than five minutes old, as a live receiver should. Exit 5 when it does not verify.
|
|
153
|
+
|
|
154
|
+
## Slack and Zapier
|
|
155
|
+
|
|
156
|
+
The Slack workspaces 50heads is added to, and the Zapier accounts connected to it. Asks from either spend your account's credits, up to each one's daily cap. Add a workspace and change its cap in the portal, under Settings, then Integrations; Zapier connects from Zapier.
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
50heads apps list
|
|
160
|
+
# app_123 Slack · Acme 400 of 2,000 credits spent today added 2026-10-10 by Ana Price
|
|
161
|
+
# conn_456 Zapier 2 hooks 1,200 of 5,000 credits spent today connected 2026-10-09 by Ana Price
|
|
162
|
+
50heads apps remove app_123 --yes # takes 50heads out of the workspace and revokes its Slack access
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`list` needs the `apps:read` scope; `remove` needs `apps:write` and, in a team, the owner or an admin. Questions already asked from Slack still finish in 50heads after a removal, but their results are not posted to Slack. A Zapier connection's hooks are its Zaps' triggers; remove the connection in the portal, under Settings, then Integrations, and its hooks go with it.
|
|
166
|
+
|
|
167
|
+
## Organization (admins)
|
|
168
|
+
|
|
169
|
+
Read the organization of the team a key acts in. These commands never change anything; domains, sign-in rules, single sign-on, SCIM and service accounts are changed by a person in the portal (Settings, Organization). They need a key (or a sign-in) made for one of the organization's teams by an organization admin, or one of the organization's service accounts, with the scope shown.
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
50heads org show # organization:read: workspaces, domains, single sign-on, SCIM, sign-in rules
|
|
173
|
+
50heads org domains # organization:read: each domain and the TXT record that proves it
|
|
174
|
+
50heads org scim # organization:read: people and groups sent, tokens by label and prefix
|
|
175
|
+
50heads org sessions usr_123 # members:read: where one person is signed in
|
|
176
|
+
50heads org service-accounts # organization:read: scopes, caps, today's spend, last token
|
|
177
|
+
50heads audit list --action domain --limit 50 # audit:read, newest first
|
|
178
|
+
50heads audit export --from 2026-10-01 --out audit.jsonl # audit:read, oldest first, JSON lines (or --format csv)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each export is itself recorded in the audit log. Events never hold secrets, codes or anything that identifies a head.
|
|
182
|
+
|
|
183
|
+
## Spend approvals
|
|
184
|
+
|
|
185
|
+
An organization can hold an agent's bigger asks until a person approves them. A held ask is not an error: the credits are held, nothing goes to heads, and `50heads ask` prints the approval and where a person decides, then exits 9.
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
50heads ask --text "Replace the paper menu with a QR menu?" --type yes_no --n 50 --wait-approval
|
|
189
|
+
# Waiting for approval apr_7QK2M9 … (exit 0 once approved and live; exit 1 if denied or expired)
|
|
190
|
+
50heads approvals wait apr_7QK2M9 # wait for a decision on one you already asked
|
|
191
|
+
50heads approvals status apr_7QK2M9 # read it once (exit 9 while pending)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A denial prints `refused_by_policy` with the approver's role and their reason, never their name. Nobody deciding in time prints `approval_expired`; the credits go back either way. See [Spend approvals](https://50heads.com/help/teams/spend-approvals).
|
|
195
|
+
|
|
94
196
|
## Output and exit codes
|
|
95
197
|
|
|
96
198
|
Human-readable by default. With `--json`, each result is one JSON object on stdout (JSON Lines for many), progress stays on stderr (`--quiet` turns it off), and errors are JSON on stderr: `{"error": {"code", "message", "exit"}}`.
|
|
@@ -102,10 +204,11 @@ Human-readable by default. With `--json`, each result is one JSON object on stdo
|
|
|
102
204
|
| 2 | The command line is wrong |
|
|
103
205
|
| 3 | Not signed in, or the key is not allowed to do that |
|
|
104
206
|
| 4 | Not enough credits, or over the key's daily cap |
|
|
105
|
-
| 5 | The question needs fixing (estimate prints the fixes) |
|
|
106
|
-
| 6 | No question with that id |
|
|
207
|
+
| 5 | The question needs fixing (estimate prints the fixes), or a webhook delivery does not verify |
|
|
208
|
+
| 6 | No question, webhook endpoint or delivery with that id |
|
|
107
209
|
| 7 | 50heads is unavailable or rate limited; try again |
|
|
108
210
|
| 8 | Some asks in a bulk run failed; the rest were asked |
|
|
211
|
+
| 9 | The ask is held for a person to approve (spend approvals); `ask --wait-approval` waits for the decision instead, and a denial or expiry is exit 1 |
|
|
109
212
|
| 124 | `wait` ran out of time and the question is still live |
|
|
110
213
|
|
|
111
214
|
## In CI
|
|
@@ -127,6 +230,7 @@ The key's daily cap is the limit on what a runaway job can spend.
|
|
|
127
230
|
| `FIFTYHEADS_AUTH_URL` | Sign-in server for `login` |
|
|
128
231
|
| `FIFTYHEADS_CREDENTIALS_STORE=file` | Keep the sign-in in a file, not the keychain |
|
|
129
232
|
| `FIFTYHEADS_NO_BROWSER=1` | `login` prints the link without opening a browser |
|
|
233
|
+
| `FIFTYHEADS_WEBHOOK_SECRET` | The signing secret for `webhooks verify` |
|
|
130
234
|
|
|
131
235
|
## Versions
|
|
132
236
|
|