@staqon/cli 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) 2026 VistarKriya Marketings Private Limited. All rights reserved.
2
+
3
+ This software is proprietary. No licence is granted to copy, modify, merge,
4
+ publish, distribute, sublicense or sell it, except as expressly agreed in
5
+ writing with the copyright holder. It is provided "as is", without warranty of
6
+ any kind.
package/README.md CHANGED
@@ -1,3 +1,279 @@
1
- # Temporary Holding Version
1
+ # @staqon/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Command-line client for the StaqOn partner API. Send messages, post comments, run push-approval
4
+ verifications and broadcast ads as your workspace, from a terminal or a script.
5
+
6
+ - Requires **Node.js 20 or newer**.
7
+ - Talks to `https://staqon.one` by default (override with `--base-url` or `STAQON_BASE_URL`).
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install -g @staqon/cli
13
+ staqon --version
14
+ ```
15
+
16
+ ## Getting started
17
+
18
+ 1. **The workspace owner creates an API key first.** Open your workspace at
19
+ `<your-workspace-url>/workspace/settings.php?w=<your-workspace-slug>&tab=api` and, under the
20
+ relevant card (Messaging API, Ad API, etc.), generate a key for the team you want the CLI to
21
+ act as. Messaging and Ad keys both require a **Link** key to already exist on that same tab �
22
+ set that up first if you see a "set up Link API first" notice.
23
+ 2. On that same `api` tab, turn on **"Allow CLI access"** for the workspace. This is a separate
24
+ switch from the API key above � it must be ON before `staqon login` can be approved. Turning it
25
+ OFF immediately signs out every CLI session for that workspace.
26
+ 3. Run `staqon login` from the machine or server you want to use. It prints a URL and a short code.
27
+ 4. The **workspace owner** opens that URL (it's also reachable directly at `/cli`) and approves the
28
+ code � they may be asked for their MPIN if their web session is old.
29
+ 5. Run `staqon whoami`. It lists every key the CLI can use and which teams hold it. If a command you
30
+ need isn't listed, go back to step 1 for that key type � the CLI itself can never create or see
31
+ API keys, only the owner can.
32
+ 6. Send your first message:
33
+ ```sh
34
+ staqon message send --email member@example.com -m "Hello from the CLI"
35
+ ```
36
+ 7. **Member approval (exit code 6).** Most calls that reach a specific member � direct messages,
37
+ comments, internal messages, verification requests � require that member to have approved
38
+ linking with your workspace in their own StaqOn app first. If they haven't, the CLI exits with
39
+ code 6 and a message like "This member hasn't approved linking with this workspace." This isn't
40
+ something the CLI or the owner can bypass � the member has to approve it on their end.
41
+
42
+ ## Sign in
43
+
44
+ There is no separate `init` step: `staqon login` is the whole setup.
45
+
46
+ ```sh
47
+ staqon login # prints a URL and a one-time code
48
+ staqon login --open # also opens the URL in your browser
49
+ ```
50
+
51
+ 1. The CLI shows a verification URL and a code.
52
+ 2. **The workspace owner** opens the URL and approves the code (they may be asked for their MPIN).
53
+ 3. The CLI polls, receives a session token, and stores it.
54
+
55
+ Where the token lives: the OS keychain when available, otherwise a user-only file
56
+ (`credentials.json` in the `staqon` config directory: `%APPDATA%\staqon` on Windows,
57
+ `$XDG_CONFIG_HOME/staqon` or `~/.config/staqon` elsewhere). The CLI says so when it has to use the file.
58
+
59
+ | Command | What it does |
60
+ |---|---|
61
+ | `staqon whoami` | Workspace, session expiry, rate limits, and which teams can do what |
62
+ | `staqon refresh` | Rotate the session token (the old one works for 5 more minutes) |
63
+ | `staqon logout` | Forget the local token. Revoking it is done by the owner in the web UI |
64
+
65
+ ### Running unattended (a server sending automatic alerts)
66
+
67
+ Session tokens are sliding-expiry: **90 days of inactivity invalidates them, and every call extends
68
+ that window**, up to a hard cap of **1 year from the original login**. For a server that calls the
69
+ CLI regularly (e.g. daily alerts), the token effectively never goes stale on its own � but you should
70
+ still rotate it well before the 90-day mark, in case the server is ever idle for a stretch:
71
+
72
+ - Run `staqon refresh` on a schedule (cron / Task Scheduler) every 30-60 days. It rotates the token;
73
+ the old one keeps working for 5 more minutes, so overlap is safe. Save the new token exactly as
74
+ `staqon` does (OS keychain, or the user-only `credentials.json` file) � don't hardcode it elsewhere.
75
+ - Once a year, at the hard cap, `refresh` will stop working and the command exits with an auth error.
76
+ At that point a human (the workspace owner, or whoever can reach the approval URL) has to run
77
+ `staqon login` again � there is no way around this step by design, since it's what lets the owner
78
+ revoke access.
79
+ - Do **not** try to use the partner's raw API key directly as a substitute for a long-lived token:
80
+ the CLI protocol deliberately never hands the API key to the CLI session at all (see the server
81
+ contract) � the CLI only ever gets a revocable, rate-limited session token, scoped to the CLI's own
82
+ separate limits, so the partner's production API key headroom is never affected by CLI traffic.
83
+ `STAQON_TOKEN` (below) is the only supported "skip login" path, and it still expires the same way.
84
+
85
+ For CI, set `STAQON_TOKEN` instead of logging in (and `STAQON_BASE_URL` if not production). Tokens are
86
+ only ever sent to the host that issued them, and the CLI refuses to send one to a different `--base-url`.
87
+ `refresh` is not available for `STAQON_TOKEN` sessions -- if you use this path, you are responsible for
88
+ replacing the value yourself before it expires (same 90-day sliding / 1-year hard cap rules apply).
89
+
90
+ ## Teams
91
+
92
+ Most calls are made on behalf of a **team** that holds the right API key. Selection order:
93
+
94
+ 1. `--team <id|slug>` on the command line
95
+ 2. the saved default (`staqon use <team>`; `staqon use` shows it; `staqon use --clear` removes it)
96
+ 3. if the server answers `team_required`:
97
+ - in a terminal, you are asked to pick one (and can save it as the default);
98
+ - otherwise the CLI exits with code 5 and lists the eligible teams. It never picks one for you.
99
+
100
+ `team_not_eligible` (exit 5) means the chosen team has no key for that call; the error lists the teams that do.
101
+ `staqon whoami` shows which teams can send messages and ads.
102
+
103
+ ## Commands
104
+
105
+ Every command accepts the global options below. `--to` takes an email **or** a username and converts it if the
106
+ endpoint wants the other kind (see [Identity](#identity-email-vs-username)).
107
+
108
+ | Global option | |
109
+ |---|---|
110
+ | `--json` | Machine-readable: raw response body on stdout, errors as JSON on stderr |
111
+ | `--team <team>` | Team to act as |
112
+ | `--base-url <url>` | API host |
113
+ | `--verbose` | Print request lines and trace ids to stderr |
114
+ | `--no-color` | Disable colors |
115
+
116
+ ### Messaging
117
+
118
+ ```sh
119
+ staqon message send --email ada@example.com -m "Hello"
120
+ staqon message send --to ada_username -m "Hello" # username resolved to an email
121
+ staqon message send --email ada@example.com --message-file note.txt # or -m - to read stdin
122
+
123
+ staqon messages --username ada_username --limit 20 # read a thread (needs the member's approved link)
124
+ staqon messages --username ada_username --before 152698 # page backwards (--after pages forwards)
125
+
126
+ staqon comment --username ada_username -c "Looks good" --external-ref order-1042
127
+ staqon comment --username ada_username -c "Status" --card @card.json
128
+
129
+ staqon internal message --email ada@example.com -m "FYI" --title "Heads up" --link-url https://example.com
130
+ ```
131
+
132
+ - Message and comment text: up to 60,000 characters. `--external-ref` (max 190) is an idempotency key: a repeat is
133
+ rejected and nothing is posted.
134
+ - `messages --limit` is 1-200 (default 50). Attachment URLs in results are signed and **expire after 1 day**;
135
+ re-fetch the thread for fresh ones.
136
+ - `internal message` sends to a member's one-way API Messages feed and needs their approval.
137
+
138
+ ### Members
139
+
140
+ ```sh
141
+ staqon members list --search "Ada" --limit 50 --page 2
142
+ staqon member ada@example.com # or: staqon member --member-id 311
143
+ staqon internal members --search ada # internal directory (search also matches username)
144
+ ```
145
+
146
+ `members list` shows email and username side by side. `--limit` is **1-100**: the directory is slow server-side
147
+ (about 0.35 s per row) and larger pages hit the gateway timeout. Member searches match **name or email**, not username.
148
+
149
+ ### Verification (push approval)
150
+
151
+ ```sh
152
+ staqon verify start --username ada_username --action "Approve payout #1042" \
153
+ --context "Payout of 5,000 to account ending 1234" --ttl 60
154
+ staqon verify check <verification_id> # prints Verified. / Not verified: <reason>
155
+ staqon verify check <verification_id> --strict # exit 1 unless verified
156
+ staqon verify cancel <verification_id>
157
+ ```
158
+
159
+ - `--ttl` is clamped to 30-180 seconds (default 30). `--action` max 100 chars, `--context` max 500.
160
+ - `check` reasons include `pending`, `declined`, `timeout` and `cancelled`. Without `--strict`, `check` exits 0 for any
161
+ answer; with it, only a verified result exits 0.
162
+ - `cancel` after the member answered returns exit 1 with "Too late to cancel"; after expiry, "already expired".
163
+ - Needs the member's approved link.
164
+
165
+ ### Ads
166
+
167
+ Ads are delivered asynchronously by a background worker.
168
+
169
+ ```sh
170
+ staqon ad image banner.png # upload only; nothing is delivered
171
+ staqon ad create --body-file ad.txt --button-label "Open" --button-url https://example.com --image banner.png --wait
172
+ staqon ad status <ad_request_id> # or --external-ref <ref>
173
+ ```
174
+
175
+ - Images: JPEG, PNG or WebP (checked on file contents), **max 300 KB**; the server crops to 16:9 and downsizes to
176
+ at most 1280x720, so the stored size differs from your file.
177
+ - `ad create`: body 50-900 characters; success is HTTP 202 plus an `ad_request_id`. `--wait` polls every 5 s for up
178
+ to 10 minutes. `--external-ref` (max 150) is an idempotency key.
179
+
180
+ ### Raw access
181
+
182
+ ```sh
183
+ staqon api external/members -q search=ada -q limit=10
184
+ staqon api external/message -d '{"email":"ada@example.com","message":"Hi"}'
185
+ ```
186
+
187
+ Calls any CLI-proxy endpoint directly: `external/{message,messages,comment,members,member,ad,ad-image,ad-status,
188
+ staqon-verify-start,staqon-verify-check,staqon-verify-cancel}` and `internal/{message,members}`.
189
+ `-d` takes inline JSON, `@file.json` or `-` for stdin; `-F k=v` / `-F k=@path` sends multipart.
190
+
191
+ ### Cards
192
+
193
+ `--card` (comment, internal message, verify start) takes a JSON object, inline, `@file.json` or `-`. The CLI
194
+ validates it before sending: required `header_color` (one of the palette colors), `title` (200) and `body` (2000);
195
+ optional `meta_title`, `sub_body`, `icon`, `button` (`label`, `url`; not allowed on verification cards) and `table`
196
+ (2-6 columns, 1-500 rows). Unknown keys are rejected.
197
+
198
+ ## Identity: email vs. username
199
+
200
+ The endpoints disagree: `message send` wants an email, while `messages`, `comment`, `verify` want a username. Give
201
+ `--email`, `--username` or `--to <either>`; the CLI converts when needed.
202
+
203
+ - email -> username is one fast lookup.
204
+ - username -> email tries the directory search, then looks up the member's name via their thread and searches by
205
+ name (fast, but only for members linked to your workspace), then **pages through the whole directory**. That last
206
+ step can take minutes on a large workspace. **If you know the email, pass `--email`.**
207
+
208
+ ## The consent model (why some calls return 403)
209
+
210
+ Members control what a workspace may do. For `messages`, `comment`, `verify *` and `internal message`, the member must
211
+ have approved the link with your workspace in their app. If they have not, the server answers 403 and the CLI prints
212
+ a friendly message and exits with **code 6**:
213
+
214
+ ```
215
+ Error: This member hasn't approved linking with this workspace.
216
+ trace id: ...
217
+ Server said: No approved link for this member
218
+ The member has to approve the link in their app; the CLI can't do it for them.
219
+ ```
220
+
221
+ The CLI can't work around it. Ask the member to approve, then retry. A 403 that is *not* about consent (CLI access
222
+ switched off, workspace inactive) exits with code 3 instead.
223
+
224
+ ## Exit codes
225
+
226
+ | Code | Meaning |
227
+ |---|---|
228
+ | 0 | Success |
229
+ | 1 | General failure: server rejected the request (4xx/5xx), network error or timeout, `verify check --strict` not verified, cancel too late or expired |
230
+ | 2 | Usage error: bad flags or input, caught before anything is sent; also a token used against a different host |
231
+ | 3 | Authentication: not logged in, session expired/revoked (401), or access denied (non-consent 403) |
232
+ | 4 | Rate limited (429): session 30/min, 3,000/day, or the API key's own ceiling. `Retry after` is shown when sent |
233
+ | 5 | Team or key problem: `team_required` (non-interactive), `team_not_eligible`, `no_key` |
234
+ | 6 | Member has not approved the link (consent 403) |
235
+ | 130 | Interrupted (Ctrl-C) |
236
+
237
+ The CLI never retries on its own, except re-asking once after you pick a team interactively.
238
+ Requests time out after 30 s (120 s for member directory listings); override with `STAQON_TIMEOUT_MS`.
239
+
240
+ ## `--json`
241
+
242
+ With `--json`, success prints the server's response body, unchanged, on stdout. Errors print **one JSON object on
243
+ stderr** and nothing on stdout:
244
+
245
+ ```json
246
+ {
247
+ "error": "This member hasn't approved linking with this workspace.",
248
+ "exit_code": 6,
249
+ "status": 403,
250
+ "trace_id": "30becdb4-daac-46b0-9fb6-395249329632",
251
+ "body": { "ok": false, "error": "No approved link for this member" },
252
+ "hint": "Server said: No approved link for this member\n..."
253
+ }
254
+ ```
255
+
256
+ `status`, `trace_id` and `body` appear for errors that came from the server; client-side errors have only
257
+ `error`, `exit_code` and optionally `hint`. In `--json` mode the CLI never prompts, so team selection must be
258
+ explicit (`--team` or a saved default). Tokens are redacted (`stq_cli_***`) from all output. Quote the `trace_id`
259
+ when contacting support.
260
+
261
+ ## Environment variables
262
+
263
+ | Variable | Purpose |
264
+ |---|---|
265
+ | `STAQON_TOKEN` | Use this session token instead of the stored login |
266
+ | `STAQON_BASE_URL` | API host (default `https://staqon.one`) |
267
+ | `STAQON_TIMEOUT_MS` | Request timeout in milliseconds |
268
+
269
+ ## Development
270
+
271
+ ```sh
272
+ npm install
273
+ npm run build # bundle to dist/
274
+ npm test # 52 mocked tests; no network, no login needed
275
+ npm run test:live # optional; needs a login: STAQON_LIVE_USERNAME=... STAQON_LIVE_EMAIL=... (slow case: SLOW=1)
276
+ ```
277
+
278
+ The mocked tests run the real CLI against a local fake server with an isolated config directory, so they never touch
279
+ your real login or keychain.
package/bin/staqon.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js';