@getantidote/cli 0.0.0-stage → 0.1.1-rc.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.
- package/LICENSE +21 -0
- package/README.md +348 -2
- package/dist/antidote.mjs +17442 -0
- package/package.json +39 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Duku AI Ltd
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,349 @@
|
|
|
1
|
-
#
|
|
1
|
+
# antidote
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Antidote command-line interface. It signs in to the Antidote platform and runs the
|
|
4
|
+
platform's tools; every tool runs server-side.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Requires Node.js 22.4 or later.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i -g @getantidote/cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Sign in
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
antidote login # opens your browser
|
|
18
|
+
antidote login --device # prints a link and a code to approve on any other device
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`antidote login` uses `--device` automatically over SSH, or on Linux/Unix with no
|
|
22
|
+
`DISPLAY` or `WAYLAND_DISPLAY`. Browser login listens for the redirect on the
|
|
23
|
+
first free port of `127.0.0.1:48211`–`48215`. Logging in again replaces the
|
|
24
|
+
previous session, which is revoked on the server first (if that fails, you get
|
|
25
|
+
a warning and the login goes ahead); if the new sign-in then fails, you are
|
|
26
|
+
signed out. When you approve a `--device` sign-in, the page shows where and
|
|
27
|
+
when it was started: deny any you didn't start yourself.
|
|
28
|
+
|
|
29
|
+
For CI, set an API key from the dashboard (Settings → API keys) instead of
|
|
30
|
+
logging in:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
ANTIDOTE_API_KEY=<key> antidote whoami
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`ANTIDOTE_API_KEY` takes precedence over any stored login, and nothing is written to
|
|
37
|
+
disk in this mode. `antidote login` refuses to run while it holds a key.
|
|
38
|
+
|
|
39
|
+
### CI without a stored secret: service identities
|
|
40
|
+
|
|
41
|
+
A service identity lets a CI job act for your organisation with no secret stored
|
|
42
|
+
in the CI: the CLI exchanges the job's OIDC token for a short-lived Antidote token
|
|
43
|
+
that acts as the identity. There are two ways to say which identity.
|
|
44
|
+
|
|
45
|
+
**Naming it.** Set `ANTIDOTE_SERVICE_IDENTITY` to the id of a service identity from
|
|
46
|
+
the dashboard (Settings → Service identities). One of the identity's trust rules
|
|
47
|
+
must accept the job's token: its issuer, its audience and the rule's conditions.
|
|
48
|
+
The dashboard shows a ready-made job for each CI platform. On GitHub Actions:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
jobs:
|
|
52
|
+
antidote:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
permissions:
|
|
55
|
+
id-token: write # lets the CLI request the job's OIDC token
|
|
56
|
+
steps:
|
|
57
|
+
- run: antidote whoami # with the CLI installed
|
|
58
|
+
env:
|
|
59
|
+
ANTIDOTE_API_URL: https://platform.duku.ai
|
|
60
|
+
ANTIDOTE_SERVICE_IDENTITY: svc_…
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Letting the platform find it.** Set only the job's OIDC token
|
|
64
|
+
(`ANTIDOTE_OIDC_TOKEN` or `ANTIDOTE_OIDC_TOKEN_FILE`), without `ANTIDOTE_SERVICE_IDENTITY`.
|
|
65
|
+
The platform finds the identity from the token itself: the one that belongs to
|
|
66
|
+
your organisation's connection to that CI platform (Settings → Integrations →
|
|
67
|
+
Source control & CI/CD), so there is nothing to create or name. No CI
|
|
68
|
+
platform's connection provides one yet; until one does, the platform refuses
|
|
69
|
+
these tokens. The token's audience must be the platform's URL (the origin of
|
|
70
|
+
`ANTIDOTE_API_URL`). When the platform refuses a token, the CLI prints its reason as
|
|
71
|
+
it gave it.
|
|
72
|
+
|
|
73
|
+
The CLI takes the job's OIDC token from the first of these that is set (a
|
|
74
|
+
variable that is set but empty is an error):
|
|
75
|
+
|
|
76
|
+
1. `ANTIDOTE_OIDC_TOKEN`, the token itself (GitLab's `id_tokens`, or the output of
|
|
77
|
+
`circleci run oidc get` or `buildkite-agent oidc request-token`);
|
|
78
|
+
2. `ANTIDOTE_OIDC_TOKEN_FILE`, a file holding it;
|
|
79
|
+
3. with `ANTIDOTE_SERVICE_IDENTITY` only, on GitHub Actions, a token the CLI
|
|
80
|
+
requests itself, for the audience `ANTIDOTE_OIDC_AUDIENCE` (by default the
|
|
81
|
+
origin of `ANTIDOTE_API_URL`, such as `https://platform.duku.ai`). The job needs
|
|
82
|
+
`permissions: id-token: write`. `ANTIDOTE_OIDC_AUDIENCE` has no effect on a
|
|
83
|
+
token from 1 or 2: its audience is set where it is issued.
|
|
84
|
+
|
|
85
|
+
The Antidote token lives in memory only, and the CLI exchanges again shortly before
|
|
86
|
+
it expires. Each exchange reads `ANTIDOTE_OIDC_TOKEN_FILE` again or requests a new
|
|
87
|
+
token from GitHub, but sends `ANTIDOTE_OIDC_TOKEN` as it is. The platform refuses a
|
|
88
|
+
CI token that has expired, and, naming an identity, one issued over an hour
|
|
89
|
+
ago, so:
|
|
90
|
+
|
|
91
|
+
- Naming an identity, exchange GitLab's `id_tokens` in the job's first hour:
|
|
92
|
+
GitLab issues them when the job starts. Without `ANTIDOTE_SERVICE_IDENTITY`, the
|
|
93
|
+
CLI leaves the token's age to the platform.
|
|
94
|
+
- Buildkite's tokens last 5 minutes unless `--lifetime` says otherwise.
|
|
95
|
+
- A job that outlives its CI token needs a fresh `ANTIDOTE_OIDC_TOKEN` for each
|
|
96
|
+
command, or `ANTIDOTE_OIDC_TOKEN_FILE`.
|
|
97
|
+
|
|
98
|
+
Before sending a token from `ANTIDOTE_OIDC_TOKEN` or the file, the CLI checks it
|
|
99
|
+
itself, because the platform's refusal doesn't say why: it must have an `exp`
|
|
100
|
+
and an `iat`, must not have expired and, naming an identity, must have been
|
|
101
|
+
issued in the last hour. If the platform's token endpoint answers HTTP 429
|
|
102
|
+
`temporarily_unavailable` or 503 `server_error`, or GitHub's token service fails
|
|
103
|
+
or can't be reached, the CLI tries the exchange twice more, waiting as long as
|
|
104
|
+
the platform's `Retry-After` says (5 seconds without one, at most a minute).
|
|
105
|
+
Other failures of the exchange are final, as are a failed discovery and a 503
|
|
106
|
+
from `/mcp`. Nothing is written to disk in either mode.
|
|
107
|
+
|
|
108
|
+
### Which credentials the CLI uses
|
|
109
|
+
|
|
110
|
+
| Set | The CLI acts as |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `ANTIDOTE_API_KEY` | the API key's identity |
|
|
113
|
+
| `ANTIDOTE_SERVICE_IDENTITY`, with a CI token from `ANTIDOTE_OIDC_TOKEN`, `ANTIDOTE_OIDC_TOKEN_FILE` or GitHub Actions | that service identity |
|
|
114
|
+
| `ANTIDOTE_OIDC_TOKEN` or `ANTIDOTE_OIDC_TOKEN_FILE`, without `ANTIDOTE_SERVICE_IDENTITY` | the service identity the platform finds for the token |
|
|
115
|
+
| none of these | whoever ran `antidote login` |
|
|
116
|
+
|
|
117
|
+
Any of these wins over a stored login. An empty `ANTIDOTE_API_KEY` is ignored, as
|
|
118
|
+
if unset: the CLI uses the next row that applies. An `ANTIDOTE_API_KEY` that isn't
|
|
119
|
+
empty can't be set together with `ANTIDOTE_SERVICE_IDENTITY`, `ANTIDOTE_OIDC_TOKEN` or
|
|
120
|
+
`ANTIDOTE_OIDC_TOKEN_FILE` (the CLI exits with a usage error), and
|
|
121
|
+
`ANTIDOTE_SERVICE_IDENTITY`, `ANTIDOTE_OIDC_TOKEN` or `ANTIDOTE_OIDC_TOKEN_FILE` set but
|
|
122
|
+
empty is an error, never a reason to fall back to a stored login. `antidote login`
|
|
123
|
+
refuses to run while any of them is in use.
|
|
124
|
+
|
|
125
|
+
| Variable | Purpose |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `ANTIDOTE_API_URL` | Platform URL, default `https://platform.duku.ai`. Must be https, except `http://localhost` / `127.0.0.1` / `[::1]` for a local stack. |
|
|
128
|
+
| `ANTIDOTE_API_KEY` | An API key, used instead of any login. |
|
|
129
|
+
| `ANTIDOTE_SERVICE_IDENTITY` | A service identity's id (`svc_…`), used instead of any login. |
|
|
130
|
+
| `ANTIDOTE_OIDC_TOKEN` | The CI job's OIDC token: for `ANTIDOTE_SERVICE_IDENTITY`, or on its own to let the platform find the identity. |
|
|
131
|
+
| `ANTIDOTE_OIDC_TOKEN_FILE` | A file holding the job's OIDC token, read again on every exchange. |
|
|
132
|
+
| `ANTIDOTE_OIDC_AUDIENCE` | The audience the CLI requests from GitHub Actions for `ANTIDOTE_SERVICE_IDENTITY` (default: the origin of `ANTIDOTE_API_URL`). |
|
|
133
|
+
| `XDG_CONFIG_HOME` | Parent of the config directory (default `~/.config`). |
|
|
134
|
+
|
|
135
|
+
Each `ANTIDOTE_*` variable was `DUKU_*` before the CLI was renamed. The old name
|
|
136
|
+
still works when the new one is unset; a set `ANTIDOTE_*` variable wins, even an
|
|
137
|
+
empty one.
|
|
138
|
+
|
|
139
|
+
## Commands
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
antidote whoami # the signed-in user (or service identity), organisation and auth mode (a tool command, below)
|
|
143
|
+
antidote tools # the tools available to the CLI
|
|
144
|
+
antidote tools --refresh # re-fetch the tool list
|
|
145
|
+
antidote logout # revoke the session and delete this machine's credentials
|
|
146
|
+
antidote --version
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`antidote logout` removes the stored login only; if `ANTIDOTE_API_KEY`,
|
|
150
|
+
`ANTIDOTE_SERVICE_IDENTITY`, `ANTIDOTE_OIDC_TOKEN` or `ANTIDOTE_OIDC_TOKEN_FILE` is set,
|
|
151
|
+
commands keep using it. If the server can't revoke the session, logout warns and still
|
|
152
|
+
deletes the local credentials. Add `--json` to any command for machine-readable output.
|
|
153
|
+
|
|
154
|
+
## Tool commands
|
|
155
|
+
|
|
156
|
+
Each tool the platform exposes to the CLI is a command, `antidote <tool>`, built from
|
|
157
|
+
the tool's input schema. `antidote <tool> --help` lists its flags.
|
|
158
|
+
|
|
159
|
+
- Each property becomes a kebab-case flag: `maxResults` or `max_results` becomes
|
|
160
|
+
`--max-results`.
|
|
161
|
+
- Strings, numbers, integers, booleans and enums take plain values; object and
|
|
162
|
+
array properties take a JSON value: `--filters '{"status":"failed"}'`.
|
|
163
|
+
- A boolean flag sends `true`; `--no-<flag>` sends `false`.
|
|
164
|
+
- Give a negative number as `--limit=-5`.
|
|
165
|
+
- A property named like a CLI flag (`json`, `help`, `version`, `device`,
|
|
166
|
+
`refresh`) becomes `--arg-<flag>` (e.g. `--arg-json`); a property whose flag
|
|
167
|
+
would clash with another property's, or is not typable, becomes
|
|
168
|
+
`--arg-<property>` with its raw name. A tool whose properties still lack
|
|
169
|
+
usable flags is not offered as a command.
|
|
170
|
+
- Required properties are enforced; the server validates everything else.
|
|
171
|
+
- Tool flags go after the tool name; only `--json` and `--help` may come before
|
|
172
|
+
it.
|
|
173
|
+
- `--json` prints the tool's structured result as JSON.
|
|
174
|
+
- A tool's snake_case name also works in kebab-case: `antidote record-preview` runs
|
|
175
|
+
`record_preview`.
|
|
176
|
+
- An input you don't give as a flag is read from the environment variable named
|
|
177
|
+
`ANTIDOTE_INPUT_` plus the property in capitals (`release_label` is
|
|
178
|
+
`ANTIDOTE_INPUT_RELEASE_LABEL`). A flag wins over the variable, and an empty
|
|
179
|
+
variable counts as not set. Use it to keep a value out of a command line and
|
|
180
|
+
a job log. The prefix keeps every other
|
|
181
|
+
variable out of reach: the CLI's own variables in the table above and the
|
|
182
|
+
`ANTIDOTE_<KEY>` values an earlier job's dotenv file passes on are never read
|
|
183
|
+
as inputs. There is no `DUKU_INPUT_` name. Two
|
|
184
|
+
properties that map to the same variable (`foo_bar` and `fooBar`) are not read
|
|
185
|
+
from the environment; give them as flags.
|
|
186
|
+
- `--dotenv <path>` writes the result's top-level strings, numbers and booleans
|
|
187
|
+
to that file as `ANTIDOTE_<KEY>=<value>` lines (`target_id` becomes
|
|
188
|
+
`ANTIDOTE_TARGET_ID`, `runStatus` becomes `ANTIDOTE_RUN_STATUS`); objects, arrays
|
|
189
|
+
and nulls are left out, and line breaks in a value become a space. A key that
|
|
190
|
+
would make a line starting `ANTIDOTE_INPUT_`, or one naming a variable in the
|
|
191
|
+
table above under either name (`ANTIDOTE_API_KEY`, `ANTIDOTE_OIDC_TOKEN`, ...),
|
|
192
|
+
is left out too, so a result can never set an input or the CLI's own variables
|
|
193
|
+
for a later job. The file is written to a temporary file in the same directory and
|
|
194
|
+
renamed over `<path>`, so a symbolic link at `<path>` is replaced, never
|
|
195
|
+
written through. It is emptied as soon as the command starts, so any failure
|
|
196
|
+
(a usage mistake, no login, a failed call) leaves no values from an earlier
|
|
197
|
+
run. The exception is a path written directly after another flag
|
|
198
|
+
(`--dry-run --dotenv antidote.env`), which could be that flag's value: it is
|
|
199
|
+
emptied once the tool's flags have been read, so write `--dotenv=antidote.env`
|
|
200
|
+
to have it emptied at the start. The result is printed first; if writing the
|
|
201
|
+
file then fails, the CLI says the tool succeeded and writing the file failed,
|
|
202
|
+
and exits 1.
|
|
203
|
+
- Pass free text such as a title as `--title=...` or as `ANTIDOTE_INPUT_TITLE`. A
|
|
204
|
+
value that starts with `-` after a space (`--title "-x"`) is refused as
|
|
205
|
+
ambiguous, so a title such as `--dotenv=/path` can never be taken as a flag
|
|
206
|
+
and nothing is written.
|
|
207
|
+
- `--json`, and the indented JSON printed for a result with nested values, write
|
|
208
|
+
bidi overrides, zero-width characters and control characters as `\uXXXX`
|
|
209
|
+
escapes, so the output stays valid JSON.
|
|
210
|
+
- A call that takes longer than about a minute fails in deployed environments,
|
|
211
|
+
because the load balancer in front of the platform cuts it off. The server may
|
|
212
|
+
still have finished: the dotenv file is then empty, and running the command again
|
|
213
|
+
finds the same target by its build key but starts a second run.
|
|
214
|
+
|
|
215
|
+
### In CI
|
|
216
|
+
|
|
217
|
+
A job that needs a tool's result in a later job writes it as a dotenv file. In
|
|
218
|
+
GitLab, with `ANTIDOTE_API_KEY` set as a masked CI/CD variable and the product's
|
|
219
|
+
id given to the tool as `ANTIDOTE_INPUT_PRODUCT_ID`:
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
antidote-preview:
|
|
223
|
+
stage: test
|
|
224
|
+
variables:
|
|
225
|
+
ANTIDOTE_INPUT_PRODUCT_ID: prod_123
|
|
226
|
+
rules:
|
|
227
|
+
- if: $CI_MERGE_REQUEST_IID
|
|
228
|
+
script:
|
|
229
|
+
- npx -y @getantidote/cli@0.1 record-preview --provider gitlab --host "$CI_SERVER_FQDN" --repository "$CI_PROJECT_PATH" --repository-id "$CI_PROJECT_ID" --number "$CI_MERGE_REQUEST_IID" --head-sha "$CI_COMMIT_SHA" --branch "$CI_COMMIT_REF_NAME" --pipeline-id "$CI_PIPELINE_ID" --dotenv antidote.env
|
|
230
|
+
artifacts:
|
|
231
|
+
when: always
|
|
232
|
+
reports:
|
|
233
|
+
dotenv: antidote.env
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`CI_MERGE_REQUEST_IID` is empty on a branch pipeline, so the `rules` line keeps
|
|
237
|
+
the job to merge request pipelines. `--host` takes `$CI_SERVER_FQDN`, not
|
|
238
|
+
`$CI_SERVER_HOST`, because it keeps the port of a GitLab served on one. Jobs in a
|
|
239
|
+
later stage, or jobs that `needs` this one, then receive the variables in the
|
|
240
|
+
table below. Exit codes are 0 on success, 1 when the tool fails and 2 for a usage
|
|
241
|
+
mistake.
|
|
242
|
+
|
|
243
|
+
`record-preview`, and `record-release` when it is given build fields, refuse a
|
|
244
|
+
signed-in user: a CI job uses `ANTIDOTE_API_KEY` or a CI sign-in
|
|
245
|
+
(`ANTIDOTE_OIDC_TOKEN`). To record the build a pipeline
|
|
246
|
+
deployed to a named environment, send the commit and the repository that make
|
|
247
|
+
its build key (`gitlab:project=acme/web:env=staging:sha=<sha>`; the rule is in
|
|
248
|
+
[the tools README](../../api/platform/src/modules/tools/README.md#build-keys)):
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
antidote-release:
|
|
252
|
+
stage: verify
|
|
253
|
+
needs: [deploy-staging]
|
|
254
|
+
variables:
|
|
255
|
+
ANTIDOTE_INPUT_PRODUCT_ID: prod_123
|
|
256
|
+
script:
|
|
257
|
+
- npx -y @getantidote/cli@0.1 record-release --environment staging --build-sha "$CI_COMMIT_SHA" --provider gitlab --host "$CI_SERVER_FQDN" --repository "$CI_PROJECT_PATH" --repository-id "$CI_PROJECT_ID" --dotenv antidote.env
|
|
258
|
+
artifacts:
|
|
259
|
+
when: always
|
|
260
|
+
reports:
|
|
261
|
+
dotenv: antidote.env
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
On GitLab, `--repository-id` (the numeric `CI_PROJECT_ID`) is required. On
|
|
265
|
+
GitHub, `--repository` is `owner/repo` and the host can only be `github.com`. A
|
|
266
|
+
variable CI leaves unset arrives as an empty string: `--release-label ""` counts
|
|
267
|
+
as no label, while an empty `--environment` or `--environment-id` is refused.
|
|
268
|
+
|
|
269
|
+
`record-release` starts the exploration first and the product's tests second. If
|
|
270
|
+
the tests then fail to start, the command still exits 0, because the exploration
|
|
271
|
+
is running: `ANTIDOTE_TEST_RUN_STATUS` is `failed` and `--json` shows why in
|
|
272
|
+
`warnings`. A job that must fail in that case checks the variable, for example
|
|
273
|
+
`test "$ANTIDOTE_TEST_RUN_STATUS" != failed`.
|
|
274
|
+
|
|
275
|
+
`record-release` records a build that an older pipeline deployed, then run again
|
|
276
|
+
(a re-run of the `staging` job after `1.5.0` has gone out), and the product's
|
|
277
|
+
tests run on it, but no exploration starts: an exploration tests what the
|
|
278
|
+
environment serves now. `ANTIDOTE_RUN_STATUS` is `skipped`, `ANTIDOTE_RUN_ID` is empty,
|
|
279
|
+
and `warnings` names both builds. `--test-paths-per-goal` is at most 10 and
|
|
280
|
+
`--test-runs-per-path` at most 5; a larger value is lowered and `warnings` says
|
|
281
|
+
so.
|
|
282
|
+
|
|
283
|
+
`record-preview` refuses an environment that is persistent when the organisation
|
|
284
|
+
has `version-axis`, because the environment name comes from the branch: a branch
|
|
285
|
+
pipeline on `production` is told to use `record-release`. `--url` must be an
|
|
286
|
+
`http` or `https` URL.
|
|
287
|
+
|
|
288
|
+
A person can record a release by hand with the two label inputs instead,
|
|
289
|
+
`antidote record-release --environment-id <id> --label 1.4.2`. The product's
|
|
290
|
+
"Runs from API keys" limit applies to it, and to every other caller except the
|
|
291
|
+
web app, including a script that calls the GraphQL `recordManualRelease`
|
|
292
|
+
mutation with an API key: 10 runs in any 10 minutes, counting every run on that
|
|
293
|
+
product that was started with an API key or an `antidote login` session. Duku staff
|
|
294
|
+
can raise it in the product's settings. When it is reached the next call is
|
|
295
|
+
refused ("Release run limit reached for this product (10 of 10 in the last 10
|
|
296
|
+
minutes). Wait a few minutes and try again"). The exploration that a call with
|
|
297
|
+
build fields starts is recorded as CI and does not count.
|
|
298
|
+
|
|
299
|
+
A tool that starts a run returns `follow_up`, which names the batch to poll with
|
|
300
|
+
`get_batch`. There is no `--wait`: a job that must wait for the result runs
|
|
301
|
+
`antidote get-batch --batch-id "$ANTIDOTE_RUN_ID" --json` every `poll_seconds` (15)
|
|
302
|
+
until `verdict.final` is `true`, and reads `verdict.outcome` (`passed`,
|
|
303
|
+
`new_issues`, `unverified` or `timed_out`) from it; what each outcome means is
|
|
304
|
+
in [AUTH_ARCHITECTURE.md](../../../docs/AUTH_ARCHITECTURE.md#6-mcp-remote-server-oauth-21-facade)
|
|
305
|
+
under *Follow-up contract*. `get_batch` gives the same answer as the check Duku
|
|
306
|
+
posts on the merge or pull request.
|
|
307
|
+
|
|
308
|
+
When no run started, `ANTIDOTE_RUN_ID` is empty, so poll only when it is not empty
|
|
309
|
+
and `ANTIDOTE_RUN_STATUS` is not `completed` (`record-preview` reports `completed`
|
|
310
|
+
when Duku tests the change with an agentic test instead of an exploration, which
|
|
311
|
+
has no batch to poll). On a product in agentic mode, Duku starts an agentic
|
|
312
|
+
execution instead of an exploration: `ANTIDOTE_RUN_ID` is empty,
|
|
313
|
+
`ANTIDOTE_RUN_STATUS` is `skipped` and `warnings` names the execution.
|
|
314
|
+
`--json` shows the same thing directly: `follow_up` is null when there is
|
|
315
|
+
nothing to poll.
|
|
316
|
+
|
|
317
|
+
The variables each tool writes with `--dotenv`. A null value is left out of
|
|
318
|
+
the file. `ANTIDOTE_COMMENT_ID` is always written empty (`ANTIDOTE_COMMENT_ID=`),
|
|
319
|
+
`ANTIDOTE_PREVIEW_URL_SOURCE` is empty unless `--url` was sent, and `ANTIDOTE_RUN_ID` is
|
|
320
|
+
empty when no run started:
|
|
321
|
+
|
|
322
|
+
| Tool | Variables |
|
|
323
|
+
|---|---|
|
|
324
|
+
| `whoami` | `ANTIDOTE_USER_ID`, `ANTIDOTE_EMAIL`, `ANTIDOTE_ORG_ID`, `ANTIDOTE_AUTH_MODE` |
|
|
325
|
+
| `get_product` | `ANTIDOTE_ID`, `ANTIDOTE_BASE_URL`, `ANTIDOTE_DASHBOARD_URL`, `ANTIDOTE_ENVIRONMENTS_TRUNCATED` |
|
|
326
|
+
| `get_batch` | `ANTIDOTE_ID`, `ANTIDOTE_STATUS`, `ANTIDOTE_RUNS_TRUNCATED`, `ANTIDOTE_DASHBOARD_URL` |
|
|
327
|
+
| `record_release` | `ANTIDOTE_RELEASE_ID`, `ANTIDOTE_TARGET_ID`, `ANTIDOTE_TARGET_NAME`, `ANTIDOTE_TARGET_VERSION`, `ANTIDOTE_RUN_ID`, `ANTIDOTE_RUN_STATUS`, `ANTIDOTE_TEST_RUN_ID`, `ANTIDOTE_TEST_RUN_STATUS`, `ANTIDOTE_DASHBOARD_URL` |
|
|
328
|
+
| `record_preview` | `ANTIDOTE_TARGET_ID`, `ANTIDOTE_TARGET_NAME`, `ANTIDOTE_TARGET_VERSION`, `ANTIDOTE_RUN_ID`, `ANTIDOTE_RUN_STATUS`, `ANTIDOTE_EXPLORATION_BATCH_ID`, `ANTIDOTE_PREVIEW_URL_SOURCE`, `ANTIDOTE_COMMENT_ID` |
|
|
329
|
+
|
|
330
|
+
`ANTIDOTE_RUN_ID` is the batch id. `get_batch`'s verdict and a result's `follow_up`,
|
|
331
|
+
`warnings` and (for `record_release`) `landings` are objects or arrays, so they are not written; use `--json` to
|
|
332
|
+
read them. `list_products` writes only `ANTIDOTE_TRUNCATED`, for the same reason.
|
|
333
|
+
|
|
334
|
+
A built-in command (`login`, `logout`, `tools`) wins over a tool of the same
|
|
335
|
+
name. Tool commands need a login, `ANTIDOTE_API_KEY`, `ANTIDOTE_SERVICE_IDENTITY` or a CI token:
|
|
336
|
+
logged out, the CLI can't read the tool list, so `antidote whoami` tells you to log in.
|
|
337
|
+
|
|
338
|
+
## Where state is kept
|
|
339
|
+
|
|
340
|
+
A signed-in profile per platform URL, in
|
|
341
|
+
`$XDG_CONFIG_HOME/antidote/profiles/<host>[_<port>].json` (default
|
|
342
|
+
`~/.config/antidote/profiles/`): the CLI's client registration, its tokens, and the
|
|
343
|
+
last tool list (so `--help` works offline). Files are mode 0600 in a 0700
|
|
344
|
+
directory. Delete a profile with `antidote logout`.
|
|
345
|
+
|
|
346
|
+
## Releasing
|
|
347
|
+
|
|
348
|
+
Publishing is a `cli-v<version>` tag on main; see the header of
|
|
349
|
+
[`.github/workflows/publish-cli.yml`](../../../.github/workflows/publish-cli.yml).
|