@levr-one/cli 0.11.1 → 0.11.3
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/README.md +69 -32
- package/dist/cli.js +10 -5
- package/dist/{runHandler-CCxnGJjS.js → runHandler-9tfiD3ZK.js} +17 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ replaces the `levr` bin from the deprecated `@levr-one/setup` package.
|
|
|
23
23
|
### Or run it without a global install
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
npx @levr-one/cli --help
|
|
26
|
+
npx @levr-one/cli@latest --help
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
`npx` is right for one-shot use (`mcp add`, trying a command). Two things to
|
|
@@ -40,18 +40,22 @@ In scripts and CI, pass `--yes` — **in a terminal the prompt blocks until it i
|
|
|
40
40
|
answered**, so an unattended run with a TTY hangs rather than failing:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
npx --yes @levr-one/cli push ./test-results.xml
|
|
43
|
+
npx --yes @levr-one/cli@latest push ./test-results.xml
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Three things to get right:
|
|
47
47
|
|
|
48
|
-
- **
|
|
48
|
+
- **Add `@latest`.** Without a version, npx runs any copy of `@levr-one/cli`
|
|
49
|
+
already installed on the machine, however old, and an old copy does not know
|
|
50
|
+
newer commands and flags (`No flag registered for --workspace`).
|
|
51
|
+
- **Always `npx @levr-one/cli@latest`, never `npx levr`.** npx treats the first
|
|
49
52
|
positional as a _package_ name, so `npx levr` would fetch and run whatever
|
|
50
53
|
package is published under the unscoped name `levr` — which is not ours.
|
|
51
|
-
- **npm's flags go before the package name.** `npx @levr-one/cli --yes` passes
|
|
54
|
+
- **npm's flags go before the package name.** `npx @levr-one/cli@latest --yes` passes
|
|
52
55
|
`--yes` to `levr`, not to npm.
|
|
53
56
|
|
|
54
|
-
For reproducible CI,
|
|
57
|
+
The examples here use `@latest`. For reproducible CI, replace it with a
|
|
58
|
+
version you have validated, such as `@levr-one/cli@0.11.1`.
|
|
55
59
|
|
|
56
60
|
## Quick start
|
|
57
61
|
|
|
@@ -59,7 +63,7 @@ For reproducible CI, pin `@levr-one/cli` to a version you have validated.
|
|
|
59
63
|
the client opens a browser to authorize the first time it connects:
|
|
60
64
|
|
|
61
65
|
```bash
|
|
62
|
-
npx @levr-one/cli mcp add
|
|
66
|
+
npx @levr-one/cli@latest mcp add
|
|
63
67
|
```
|
|
64
68
|
|
|
65
69
|
**Pushing test results?** Authentication is all you need — log in once (or
|
|
@@ -82,40 +86,72 @@ levr mcp add --dry-run # preview the changes without writing
|
|
|
82
86
|
levr mcp add --client cursor,zed --yes # non-interactive selection
|
|
83
87
|
```
|
|
84
88
|
|
|
85
|
-
What it writes is **credential-free** — a `levr` server entry
|
|
86
|
-
|
|
87
|
-
to authorize with Levr on its first connection. After a run, restart
|
|
88
|
-
client(s) and authorize once.
|
|
89
|
+
What it writes is **credential-free** — a `levr` server entry holding only
|
|
90
|
+
the server's address. No token or secret is stored; your client opens the
|
|
91
|
+
browser to authorize with Levr on its first connection. After a run, restart
|
|
92
|
+
the client(s) and authorize once.
|
|
93
|
+
|
|
94
|
+
Clients that speak HTTP MCP themselves (Claude Code, VS Code, Gemini CLI,
|
|
95
|
+
Codex CLI, Grok Build, Antigravity) connect to the URL directly. Claude
|
|
96
|
+
Desktop, Cursor, Windsurf and Zed get an entry that runs
|
|
97
|
+
`npx -y mcp-remote <url>`, which connects on their behalf.
|
|
89
98
|
|
|
90
99
|
**Supported clients:**
|
|
91
100
|
|
|
92
|
-
| Client
|
|
93
|
-
|
|
|
94
|
-
| Claude Desktop
|
|
95
|
-
| Claude Code
|
|
96
|
-
| Cursor
|
|
97
|
-
| Windsurf
|
|
98
|
-
| Zed
|
|
99
|
-
| VS Code
|
|
101
|
+
| Client | How it's configured |
|
|
102
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| Claude Desktop | config file (`claude_desktop_config.json`) |
|
|
104
|
+
| Claude Code | runs `claude mcp add --transport http --scope user levr <url>` for you (prints it instead when `claude` is not on your `PATH`) |
|
|
105
|
+
| Cursor | config file (`~/.cursor/mcp.json`) |
|
|
106
|
+
| Windsurf | config file (`~/.codeium/windsurf/mcp_config.json`) |
|
|
107
|
+
| Zed | config file (`settings.json`, `context_servers`) |
|
|
108
|
+
| VS Code | config file (`Code/User/mcp.json` in your VS Code settings folder; `.vscode/mcp.json` with `--scope project`) |
|
|
109
|
+
| Gemini CLI | config file (`~/.gemini/settings.json`) |
|
|
110
|
+
| Codex CLI | config file (`~/.codex/config.toml`, TOML) |
|
|
111
|
+
| Grok Build | config file (`~/.grok/config.toml`, TOML) |
|
|
112
|
+
| Antigravity (CLI + IDE) | config file (`~/.gemini/config/mcp_config.json`) |
|
|
100
113
|
|
|
101
114
|
Config edits are **safe and repeatable**: existing MCP servers and comments
|
|
102
|
-
in your config files are preserved
|
|
103
|
-
|
|
115
|
+
in your config files are preserved, and re-running reports "already set up".
|
|
116
|
+
An entry of the same name that points at a different address is left alone
|
|
117
|
+
and reported; pass `--replace` to switch it.
|
|
104
118
|
|
|
105
119
|
**Flags:**
|
|
106
120
|
|
|
107
|
-
| Flag
|
|
108
|
-
|
|
|
109
|
-
| `--client <id,…>`
|
|
110
|
-
| `--all`
|
|
111
|
-
| `--yes`
|
|
112
|
-
| `--dry-run`
|
|
113
|
-
| `--
|
|
121
|
+
| Flag | Alias | Description |
|
|
122
|
+
| -------------------------------- | ----- | ----------------------------------------------------------------------------------- |
|
|
123
|
+
| `--client <id,…>` | | Set up these client ids (comma-separated or repeated) |
|
|
124
|
+
| `--all` | | Set up every detected, installable client |
|
|
125
|
+
| `--yes` | `-y` | Non-interactive; auto-select detected clients |
|
|
126
|
+
| `--dry-run` | | Show the changes without writing |
|
|
127
|
+
| `--scope <user\|project\|local>` | | Where the entry lands: every project (default), this repo for everyone, or just you |
|
|
128
|
+
| `--workspace <url_key\|name>` | | Pin the entry to one workspace (needs `levr auth login`) |
|
|
129
|
+
| `--name <entry>` | | Name of the client entry (default `levr`), to keep a second one beside it |
|
|
130
|
+
| `--replace` | | Switch an existing entry of this name to the new address |
|
|
131
|
+
| `--url <url>` | | MCP server URL (default derived from the API server) |
|
|
114
132
|
|
|
115
133
|
Runs non-interactively whenever `--all`, `--client`, or `--yes` is passed —
|
|
116
134
|
or automatically when not attached to a terminal (CI). Unknown client ids and
|
|
117
135
|
failed writes exit non-zero.
|
|
118
136
|
|
|
137
|
+
### More than one workspace
|
|
138
|
+
|
|
139
|
+
If your account is in more than one workspace, the plain MCP URL does not know
|
|
140
|
+
which one you mean, and its tools reply that no workspace is selected. Connect
|
|
141
|
+
each workspace by its own URL, `https://ai.levr.one/api/v1/mcp/w/<url_key>`:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
levr auth login
|
|
145
|
+
levr mcp add --workspace acme # pin the levr entry to acme
|
|
146
|
+
levr mcp add --workspace beta --name levr-beta # add a second entry beside it
|
|
147
|
+
levr mcp add --workspace beta --replace # or switch the entry to beta
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Each entry lists every Levr tool once more. A URL for a workspace you are not
|
|
151
|
+
in is refused with `403`; connect the plain URL and ask for `list_workspaces`
|
|
152
|
+
to see the URLs you can use. Full guide:
|
|
153
|
+
[levr CLI Overview › More than one workspace](https://doc.levr.one/cli/levr-cli-overview#more-than-one-workspace).
|
|
154
|
+
|
|
119
155
|
## Push test results
|
|
120
156
|
|
|
121
157
|
```bash
|
|
@@ -233,7 +269,7 @@ levr import ./cases.csv --team-id <uuid> --map "State=labels:state"
|
|
|
233
269
|
env:
|
|
234
270
|
LEVR_TOKEN: ${{ secrets.LEVR_TOKEN }}
|
|
235
271
|
# LEVR_TEAM_ID is optional — server resolves from automation source or workspace default
|
|
236
|
-
run: npx --yes @levr-one/cli push ./test-results.xml
|
|
272
|
+
run: npx --yes @levr-one/cli@latest push ./test-results.xml
|
|
237
273
|
```
|
|
238
274
|
|
|
239
275
|
### GitLab CI
|
|
@@ -241,7 +277,7 @@ levr import ./cases.csv --team-id <uuid> --map "State=labels:state"
|
|
|
241
277
|
```yaml
|
|
242
278
|
push-results:
|
|
243
279
|
script:
|
|
244
|
-
- npx --yes @levr-one/cli push ./test-results.xml
|
|
280
|
+
- npx --yes @levr-one/cli@latest push ./test-results.xml
|
|
245
281
|
variables:
|
|
246
282
|
LEVR_TOKEN: $LEVR_TOKEN
|
|
247
283
|
```
|
|
@@ -250,13 +286,14 @@ push-results:
|
|
|
250
286
|
|
|
251
287
|
```groovy
|
|
252
288
|
withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
|
|
253
|
-
sh 'npx --yes @levr-one/cli push ./test-results.xml'
|
|
289
|
+
sh 'npx --yes @levr-one/cli@latest push ./test-results.xml'
|
|
254
290
|
}
|
|
255
291
|
```
|
|
256
292
|
|
|
257
293
|
## Authentication
|
|
258
294
|
|
|
259
|
-
Needed for `push` and `workspace` commands
|
|
295
|
+
Needed for `push` and `workspace` commands, and for `mcp add --workspace`.
|
|
296
|
+
Three modes:
|
|
260
297
|
|
|
261
298
|
### Interactive (browser) — default
|
|
262
299
|
|
package/dist/cli.js
CHANGED
|
@@ -135,7 +135,7 @@ or when not running in a terminal (CI). Config edits preserve existing
|
|
|
135
135
|
servers and comments, and re-running is a no-op.
|
|
136
136
|
|
|
137
137
|
Examples:
|
|
138
|
-
npx @levr-one/cli mcp add
|
|
138
|
+
npx @levr-one/cli@latest mcp add # detect clients and pick interactively
|
|
139
139
|
levr mcp add --all # set up every detected client
|
|
140
140
|
levr mcp add --client cursor --yes
|
|
141
141
|
levr mcp add --scope project # commit the config to this repo
|
|
@@ -563,12 +563,17 @@ Examples:
|
|
|
563
563
|
//#region src/commands/workspace/select.ts
|
|
564
564
|
const selectCommand = buildCommand({
|
|
565
565
|
docs: {
|
|
566
|
-
brief: "Select
|
|
566
|
+
brief: "Select the CLI workspace (MCP client configs are unchanged)",
|
|
567
567
|
fullDescription: `Select a workspace by ID.
|
|
568
568
|
|
|
569
|
-
The selected workspace is used for all subsequent commands.
|
|
569
|
+
The selected workspace is used for all subsequent levr commands.
|
|
570
570
|
Use 'levr workspace list' to see available workspaces.
|
|
571
571
|
|
|
572
|
+
It does not change MCP client configs: each AI client keeps using the
|
|
573
|
+
workspace its MCP URL names. To point a client at a workspace, run
|
|
574
|
+
'levr mcp add --workspace <url_key>' (add --replace to switch an existing
|
|
575
|
+
entry).
|
|
576
|
+
|
|
572
577
|
Requires JWT authentication (levr auth login).
|
|
573
578
|
|
|
574
579
|
Examples:
|
|
@@ -673,7 +678,7 @@ Examples:
|
|
|
673
678
|
}
|
|
674
679
|
},
|
|
675
680
|
loader: async () => {
|
|
676
|
-
const { gatesRunHandler } = await import("./runHandler-
|
|
681
|
+
const { gatesRunHandler } = await import("./runHandler-9tfiD3ZK.js");
|
|
677
682
|
return gatesRunHandler;
|
|
678
683
|
}
|
|
679
684
|
});
|
|
@@ -869,7 +874,7 @@ Examples:
|
|
|
869
874
|
|
|
870
875
|
//#endregion
|
|
871
876
|
//#region package.json
|
|
872
|
-
var version = "0.11.
|
|
877
|
+
var version = "0.11.3";
|
|
873
878
|
|
|
874
879
|
//#endregion
|
|
875
880
|
//#region src/app.ts
|
|
@@ -88,6 +88,20 @@ const issueGateVerificationReportGateResultsV1 = (options) => {
|
|
|
88
88
|
const GATE_STEP_TAIL_LIMIT = 4e3;
|
|
89
89
|
/** How long one command may run before it is killed (15 min). */
|
|
90
90
|
const GATE_COMMAND_TIMEOUT_MS = 900 * 1e3;
|
|
91
|
+
/**
|
|
92
|
+
* How long an L4 semantic judge may run (3 min) — ENG-4469.
|
|
93
|
+
*
|
|
94
|
+
* The L4 templates used to carry their own `timeout 120`, which bounded the
|
|
95
|
+
* judge on Linux and broke it on stock macOS (no GNU `timeout`). With the
|
|
96
|
+
* wrapper gone, the runner holds the budget, as qinetic-mcp's executor does
|
|
97
|
+
* with the same 180s. A hung judge (agy waiting on a prompt that never
|
|
98
|
+
* renders) would otherwise hold `levr gates run` for the full 15 minutes.
|
|
99
|
+
*/
|
|
100
|
+
const GATE_SEMANTIC_TIMEOUT_MS = 180 * 1e3;
|
|
101
|
+
/** The kill budget for one command: semantic judges get the shorter one. */
|
|
102
|
+
function commandTimeoutMs(command) {
|
|
103
|
+
return command.technique === "semantic" ? GATE_SEMANTIC_TIMEOUT_MS : GATE_COMMAND_TIMEOUT_MS;
|
|
104
|
+
}
|
|
91
105
|
/** Keep the END of a stream — where runner summaries and verdicts are. */
|
|
92
106
|
function tail(value, limit = GATE_STEP_TAIL_LIMIT) {
|
|
93
107
|
const s = value ?? "";
|
|
@@ -145,12 +159,12 @@ function shellExists(shell) {
|
|
|
145
159
|
* can report ENOBUFS with a numeric status (ENG-5106 L1 review round 4
|
|
146
160
|
* F-010/F-011).
|
|
147
161
|
*/
|
|
148
|
-
const runInShell = (command) => {
|
|
162
|
+
const runInShell = (command, timeoutMs = GATE_COMMAND_TIMEOUT_MS) => {
|
|
149
163
|
const shell = chooseShell();
|
|
150
164
|
const r = spawnSync(shell.file, shell.args(command), {
|
|
151
165
|
encoding: "utf8",
|
|
152
166
|
maxBuffer: 64 * 1024 * 1024,
|
|
153
|
-
timeout:
|
|
167
|
+
timeout: timeoutMs
|
|
154
168
|
});
|
|
155
169
|
const stderr = (r.stderr ?? "") + (r.error ? `\n[runner] ${r.error.message}` : "") + (r.signal ? `\n[runner] killed by ${r.signal}` : "");
|
|
156
170
|
const killed = r.error !== void 0 || r.signal !== null;
|
|
@@ -189,7 +203,7 @@ function toReportResult(command, outcome) {
|
|
|
189
203
|
function runLocalCommands(commands, run = runInShell, onEach, onBefore) {
|
|
190
204
|
return commands.map((command, index) => {
|
|
191
205
|
onBefore?.(command, index);
|
|
192
|
-
const result = toReportResult(command, run(command.command));
|
|
206
|
+
const result = toReportResult(command, run(command.command, commandTimeoutMs(command)));
|
|
193
207
|
onEach?.(command, result);
|
|
194
208
|
return result;
|
|
195
209
|
});
|