@levr-one/cli 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,8 +1,15 @@
1
1
  # @levr-one/cli
2
2
 
3
- The command-line interface for [Levr](https://www.levr.one). Push test results
4
- from any CI pipeline, manage authentication, and select workspaces. The binary
5
- is `levr`.
3
+ The command-line interface for [Levr](https://www.levr.one). The binary is
4
+ `levr`, and it does three jobs:
5
+
6
+ - **Connect your AI tools** — `levr mcp add` wires the Levr MCP server into
7
+ the AI clients on your machine (Claude Desktop, Claude Code, Cursor,
8
+ Windsurf, Zed) with one command.
9
+ - **Push test results** — `levr push` uploads results from any terminal or CI
10
+ pipeline.
11
+ - **Import test cases** — `levr import` brings existing cases in from CSV,
12
+ Excel, JSON, or Google Sheets.
6
13
 
7
14
  ## Install
8
15
 
@@ -15,82 +22,68 @@ npx @levr-one/cli --help
15
22
  The package is self-contained — no peer setup required. A global install
16
23
  replaces the `levr` bin from the deprecated `@levr-one/setup` package.
17
24
 
18
- ## Get started
19
-
20
- Pushing test results needs authentication and nothing else — log in once
21
- (or set `LEVR_TOKEN` in CI):
22
-
23
- ```bash
24
- levr auth login # browser PKCE (or --device-code for SSH/headless)
25
- levr push ./results.xml
26
- ```
25
+ ## Quick start
27
26
 
28
- Using an AI client (Claude Desktop/Code, Cursor, Windsurf, Zed)? Wire the
29
- Levr MCP server into it with one command no login needed; the client opens
30
- a browser to authorize the first time it connects:
27
+ **Using an AI client?** Wire the Levr MCP server into it — no login needed;
28
+ the client opens a browser to authorize the first time it connects:
31
29
 
32
30
  ```bash
33
- npx @levr-one/cli mcp add # detect installed clients and pick interactively
31
+ npx @levr-one/cli mcp add
34
32
  ```
35
33
 
36
- Config edits preserve your existing MCP servers and comments, and re-running
37
- is a no-op. Use `--all`, `--client <id>`, or `--yes` for non-interactive
38
- runs and `--dry-run` to preview.
39
-
40
- ### Shell completion (optional)
41
-
42
- Tab-completion is an explicit opt-in step (it is not installed automatically):
34
+ **Pushing test results?** Authentication is all you need log in once (or
35
+ set `LEVR_TOKEN` in CI):
43
36
 
44
37
  ```bash
45
- levr install # add shell completion for the current shell
46
- levr uninstall # remove it
47
- ```
48
-
49
- ## Authentication
50
-
51
- The CLI supports three authentication modes.
52
-
53
- ### Interactive (browser) — default
54
-
55
- ```bash
56
- levr auth login
38
+ levr auth login # browser PKCE (or --device-code for SSH/headless)
39
+ levr push ./results.xml
57
40
  ```
58
41
 
59
- Opens a browser for PKCE-based OAuth login.
42
+ ## Connect AI clients: `levr mcp add`
60
43
 
61
- ### Device code (SSH / headless)
44
+ Detects the MCP-capable clients installed on your machine, lets you pick
45
+ which to set up, and writes the Levr MCP server into each one's config:
62
46
 
63
47
  ```bash
64
- levr auth login --device-code
48
+ levr mcp add # detect clients and pick interactively
49
+ levr mcp add --all # set up every detected client
50
+ levr mcp add --dry-run # preview the changes without writing
51
+ levr mcp add --client cursor,zed --yes # non-interactive selection
65
52
  ```
66
53
 
67
- A code is displayed in the terminal. Open the provided URL on any device, enter
68
- the code, and approve.
54
+ What it writes is **credential-free** a `levr` server entry that connects
55
+ via `mcp-remote`. No token or secret is stored; your client opens the browser
56
+ to authorize with Levr on its first connection. After a run, restart the
57
+ client(s) and authorize once.
69
58
 
70
- ### Personal Access Token (CI/CD)
71
-
72
- Set the `LEVR_TOKEN` environment variable and the CLI uses it automatically —
73
- no interactive login:
59
+ **Supported clients:**
74
60
 
75
- ```bash
76
- export LEVR_TOKEN=<your-personal-access-token>
77
- levr push ./results.xml
78
- ```
61
+ | Client | How it's configured |
62
+ | -------------- | ------------------------------------------------------------------------------ |
63
+ | Claude Desktop | config file (`claude_desktop_config.json`) |
64
+ | Claude Code | prints the `claude mcp add --transport http levr <url>` command for you to run |
65
+ | Cursor | config file (`~/.cursor/mcp.json`) |
66
+ | Windsurf | config file (`~/.codeium/windsurf/mcp_config.json`) |
67
+ | Zed | config file (`settings.json`, `context_servers`) |
68
+ | VS Code, Codex | listed but not yet installable (coming soon) |
79
69
 
80
- ### Other auth commands
70
+ Config edits are **safe and repeatable**: existing MCP servers and comments
71
+ in your config files are preserved (jsonc-aware merge), and re-running is a
72
+ no-op that reports "already set up".
81
73
 
82
- ```bash
83
- levr auth status # show current authentication state
84
- levr auth logout # clear stored credentials
85
- ```
74
+ **Flags:**
86
75
 
87
- ## Workspaces
76
+ | Flag | Alias | Description |
77
+ | ----------------- | ----- | ----------------------------------------------------- |
78
+ | `--client <id,…>` | | Set up these client ids (comma-separated or repeated) |
79
+ | `--all` | | Set up every detected, installable client |
80
+ | `--yes` | `-y` | Non-interactive; auto-select detected clients |
81
+ | `--dry-run` | | Show the changes without writing |
82
+ | `--url <url>` | | MCP server URL (default derived from the API server) |
88
83
 
89
- ```bash
90
- levr workspace list # list the workspaces you belong to
91
- levr workspace select # choose the active workspace
92
- levr workspace current # show the active workspace
93
- ```
84
+ Runs non-interactively whenever `--all`, `--client`, or `--yes` is passed —
85
+ or automatically when not attached to a terminal (CI). Unknown client ids and
86
+ failed writes exit non-zero.
94
87
 
95
88
  ## Push test results
96
89
 
@@ -138,6 +131,69 @@ the source to the workspace's default team, or pass `--team-id` to link it to a
138
131
  specific team. Subsequent pushes with the same `--source` route to that team
139
132
  automatically. Source names are normalized (lowercased, trimmed).
140
133
 
134
+ ## Import test cases
135
+
136
+ ```bash
137
+ levr import <file> --team-id <uuid> [options]
138
+ ```
139
+
140
+ Brings existing test cases into Levr from CSV, Excel (`.xlsx`), JSON, or a
141
+ public Google Sheet. It runs in two phases: **preview** proposes a mapping from
142
+ your columns onto the Levr test-case schema (exact and fuzzy matching, with an
143
+ LLM assist for whatever is left over), you review and adjust it, then **commit**
144
+ writes the folders, tests, steps, and preconditions.
145
+
146
+ In a terminal, unmapped and low-confidence columns are walked with a picker. For
147
+ scripts and CI, pass `--yes` — and `--map` or `--mapping-file` to pin the
148
+ mapping so a replay can't drift.
149
+
150
+ **One column must map to `test_name`.** Interactive runs ask for it;
151
+ non-interactive runs exit 1 rather than import unnamed cases.
152
+
153
+ **Examples:**
154
+
155
+ ```bash
156
+ # Interactive import from a CSV export
157
+ levr import ./testrail-export.csv --team-id <uuid>
158
+
159
+ # Excel, with one column pinned up front
160
+ levr import ./cases.xlsx --team-id <uuid> --map "Title=test_name"
161
+
162
+ # A public Google Sheet, no prompts
163
+ levr import --sheets-url "https://docs.google.com/spreadsheets/d/..." --team-id <uuid> --yes
164
+
165
+ # Save the confirmed mapping once, then replay it in CI
166
+ levr import ./cases.csv --team-id <uuid> --save-mapping mapping.json
167
+ levr import ./cases.csv --team-id <uuid> --mapping-file mapping.json --yes
168
+ ```
169
+
170
+ **Flags:**
171
+
172
+ | Flag | Alias | Description |
173
+ | ----------------------- | ----- | --------------------------------------------------------------------------------- |
174
+ | `--team-id <uuid>` | `-t` | Team the imported test cases belong to (required) |
175
+ | `--workspace-id <uuid>` | `-w` | Workspace ID (required for multi-workspace JWT auth) |
176
+ | `--sheets-url <url>` | | Public Google Sheets URL, used instead of a file argument |
177
+ | `--format <type>` | `-f` | Source format: `csv`, `xlsx`, `json` (auto-detected from the filename if omitted) |
178
+ | `--map <pair>` | `-m` | Column override, repeatable — three forms, below |
179
+ | `--mapping-file <path>` | | JSON file holding a saved confirmed mapping (from `--save-mapping`) |
180
+ | `--save-mapping <path>` | | Write the confirmed mapping to this JSON file for CI replay |
181
+ | `--yes` | `-y` | Accept the mapping without prompts (required for non-TTY runs) |
182
+ | `--verbose` | `-v` | Show detailed output |
183
+
184
+ `--map` accepts three forms:
185
+
186
+ | Form | Effect |
187
+ | ------------------------------- | ------------------------------------------------------------------------------ |
188
+ | `"Source Column=target_field"` | Map the column onto that Levr field |
189
+ | `"Source Column="` | Drop the column (empty target) |
190
+ | `"Source Column=labels:prefix"` | Import each value as the label `prefix:value`, keeping the column's provenance |
191
+
192
+ ```bash
193
+ # Keep a TestRail "State" column as state:Draft / state:Approved labels
194
+ levr import ./cases.csv --team-id <uuid> --map "State=labels:state"
195
+ ```
196
+
141
197
  ## CI/CD integration
142
198
 
143
199
  ### GitHub Actions
@@ -168,6 +224,61 @@ withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
168
224
  }
169
225
  ```
170
226
 
227
+ ## Authentication
228
+
229
+ Needed for `push` and `workspace` commands (`mcp add` needs none). Three modes:
230
+
231
+ ### Interactive (browser) — default
232
+
233
+ ```bash
234
+ levr auth login
235
+ ```
236
+
237
+ Opens a browser for PKCE-based OAuth login.
238
+
239
+ ### Device code (SSH / headless)
240
+
241
+ ```bash
242
+ levr auth login --device-code
243
+ ```
244
+
245
+ A code is displayed in the terminal. Open the provided URL on any device, enter
246
+ the code, and approve.
247
+
248
+ ### Personal Access Token (CI/CD)
249
+
250
+ Set the `LEVR_TOKEN` environment variable and the CLI uses it automatically —
251
+ no interactive login:
252
+
253
+ ```bash
254
+ export LEVR_TOKEN=<your-personal-access-token>
255
+ levr push ./results.xml
256
+ ```
257
+
258
+ ### Other auth commands
259
+
260
+ ```bash
261
+ levr auth status # show current authentication state
262
+ levr auth logout # clear stored credentials
263
+ ```
264
+
265
+ ## Workspaces
266
+
267
+ ```bash
268
+ levr workspace list # list the workspaces you belong to
269
+ levr workspace select # choose the active workspace
270
+ levr workspace current # show the active workspace
271
+ ```
272
+
273
+ ## Shell completion (optional)
274
+
275
+ Tab-completion is an explicit opt-in step (it is not installed automatically):
276
+
277
+ ```bash
278
+ levr install # add shell completion for the current shell
279
+ levr uninstall # remove it
280
+ ```
281
+
171
282
  ## Configuration
172
283
 
173
284
  All configuration is via environment variables. Flags take precedence.
@@ -183,6 +294,14 @@ All configuration is via environment variables. Flags take precedence.
183
294
 
184
295
  ## Troubleshooting
185
296
 
297
+ **The `levr` server doesn't appear in my AI client after `mcp add`** — Restart
298
+ the client; MCP servers are read at startup. Verify the entry with
299
+ `levr mcp add --dry-run` (it reports "already set up" when the config is in
300
+ place).
301
+
302
+ **My client asks me to authorize Levr** — Expected on the first connection:
303
+ the config is credential-free, so each client authorizes once in the browser.
304
+
186
305
  **`Authentication required. Run: levr auth login`** — No valid credentials
187
306
  found. Run `levr auth login` or set `LEVR_TOKEN`.
188
307
 
package/dist/cli.js CHANGED
@@ -109,7 +109,7 @@ Examples:
109
109
  aliases: { y: "yes" }
110
110
  },
111
111
  loader: async () => {
112
- const { mcpAddHandler } = await import("./addHandler-boWTd9yY.js");
112
+ const { mcpAddHandler } = await import("./addHandler-DFtvzTci.js");
113
113
  return mcpAddHandler;
114
114
  }
115
115
  });
@@ -147,7 +147,7 @@ Examples:
147
147
  aliases: { d: "device-code" }
148
148
  },
149
149
  loader: async () => {
150
- const { loginHandler } = await import("./loginHandler-BfIUQJrC.js");
150
+ const { loginHandler } = await import("./loginHandler-D9k-tQLX.js");
151
151
  return loginHandler;
152
152
  }
153
153
  });
@@ -186,7 +186,7 @@ Examples:
186
186
  },
187
187
  parameters: {},
188
188
  loader: async () => {
189
- const { statusHandler } = await import("./statusHandler-BaQB40t7.js");
189
+ const { statusHandler } = await import("./statusHandler-DuVZJMtw.js");
190
190
  return statusHandler;
191
191
  }
192
192
  });
@@ -297,11 +297,123 @@ Examples:
297
297
  }
298
298
  },
299
299
  loader: async () => {
300
- const { pushHandler } = await import("./pushHandler-DBBa6hKz.js");
300
+ const { pushHandler } = await import("./pushHandler-COg5WbGS.js");
301
301
  return pushHandler;
302
302
  }
303
303
  });
304
304
 
305
+ //#endregion
306
+ //#region src/commands/import.ts
307
+ const importCommand = buildCommand({
308
+ docs: {
309
+ brief: "Import test cases from CSV, Excel, JSON, or Google Sheets",
310
+ fullDescription: `Two-phase test-case import: preview proposes a column mapping to the
311
+ Levr test-case schema (exact/fuzzy matching with an LLM assist), you review
312
+ and adjust it, then commit writes folders, tests, steps, and preconditions.
313
+
314
+ Interactive mode (default in a terminal) walks unmapped and low-confidence
315
+ columns with a picker. For scripts/CI use --yes and/or --map.
316
+
317
+ Required-field rule: a column must map to test_name or the commit is
318
+ rejected — interactive mode will ask; non-interactive runs exit 1.
319
+
320
+ Examples:
321
+ levr import ./testrail-export.csv --team-id <uuid>
322
+ levr import ./cases.xlsx --team-id <uuid> --map "Title=test_name"
323
+ levr import --sheets-url "https://docs.google.com/spreadsheets/d/..." --team-id <uuid> --yes
324
+ levr import ./cases.csv --team-id <uuid> --save-mapping mapping.json
325
+ levr import ./cases.csv --team-id <uuid> --mapping-file mapping.json --yes # CI replay`
326
+ },
327
+ parameters: {
328
+ positional: {
329
+ kind: "tuple",
330
+ parameters: [{
331
+ parse: String,
332
+ brief: "Path to the source file (.csv, .xlsx, .json)",
333
+ placeholder: "file",
334
+ optional: true
335
+ }]
336
+ },
337
+ flags: {
338
+ "workspace-id": {
339
+ kind: "parsed",
340
+ parse: String,
341
+ brief: "Workspace ID (required for multi-workspace JWT auth)",
342
+ placeholder: "uuid",
343
+ optional: true
344
+ },
345
+ "team-id": {
346
+ kind: "parsed",
347
+ parse: String,
348
+ brief: "Team the imported test cases will belong to",
349
+ placeholder: "uuid",
350
+ optional: false
351
+ },
352
+ "sheets-url": {
353
+ kind: "parsed",
354
+ parse: String,
355
+ brief: "Public Google Sheets URL (instead of a file)",
356
+ placeholder: "url",
357
+ optional: true
358
+ },
359
+ format: {
360
+ kind: "enum",
361
+ values: [
362
+ "csv",
363
+ "xlsx",
364
+ "json"
365
+ ],
366
+ brief: "Source format (auto-detected from the filename if omitted)",
367
+ optional: true
368
+ },
369
+ map: {
370
+ kind: "parsed",
371
+ parse: String,
372
+ brief: "Column override (repeatable): \"Column=target_field\", \"Column=\" to drop, or \"Column=labels:prefix\"",
373
+ placeholder: "pair",
374
+ variadic: true,
375
+ optional: true
376
+ },
377
+ "mapping-file": {
378
+ kind: "parsed",
379
+ parse: String,
380
+ brief: "JSON file with a saved confirmed mapping (from --save-mapping)",
381
+ placeholder: "path",
382
+ optional: true
383
+ },
384
+ "save-mapping": {
385
+ kind: "parsed",
386
+ parse: String,
387
+ brief: "Write the confirmed mapping to this JSON file for CI replay",
388
+ placeholder: "path",
389
+ optional: true
390
+ },
391
+ yes: {
392
+ kind: "boolean",
393
+ default: false,
394
+ brief: "Accept the mapping without prompts (required for non-TTY runs)"
395
+ },
396
+ verbose: {
397
+ kind: "boolean",
398
+ default: false,
399
+ brief: "Show detailed output"
400
+ }
401
+ },
402
+ aliases: {
403
+ w: "workspace-id",
404
+ t: "team-id",
405
+ f: "format",
406
+ m: "map",
407
+ y: "yes",
408
+ v: "verbose"
409
+ }
410
+ },
411
+ loader: async () => {
412
+ const { importHandler } = await import("./importHandler-ChywPYeL.js");
413
+ return importHandler;
414
+ }
415
+ });
416
+
305
417
  //#endregion
306
418
  //#region src/commands/workspace/list.ts
307
419
  const listCommand = buildCommand({
@@ -318,7 +430,7 @@ Examples:
318
430
  },
319
431
  parameters: {},
320
432
  loader: async () => {
321
- const { listHandler } = await import("./listHandler-EsB3AeOB.js");
433
+ const { listHandler } = await import("./listHandler-fK9Mapr3.js");
322
434
  return listHandler;
323
435
  }
324
436
  });
@@ -351,7 +463,7 @@ Examples:
351
463
  flags: {}
352
464
  },
353
465
  loader: async () => {
354
- const { selectHandler } = await import("./selectHandler-XOUHdyea.js");
466
+ const { selectHandler } = await import("./selectHandler-BzQRF8E7.js");
355
467
  return selectHandler;
356
468
  }
357
469
  });
@@ -368,14 +480,14 @@ Examples:
368
480
  },
369
481
  parameters: {},
370
482
  loader: async () => {
371
- const { currentHandler } = await import("./currentHandler-Bm7GFMkj.js");
483
+ const { currentHandler } = await import("./currentHandler-BD-h0j-S.js");
372
484
  return currentHandler;
373
485
  }
374
486
  });
375
487
 
376
488
  //#endregion
377
489
  //#region package.json
378
- var version = "0.3.0";
490
+ var version = "0.4.0";
379
491
 
380
492
  //#endregion
381
493
  //#region src/app.ts
@@ -404,6 +516,7 @@ const routes = buildRouteMap({
404
516
  auth: authRoutes,
405
517
  workspace: workspaceRoutes,
406
518
  push: pushCommand,
519
+ import: importCommand,
407
520
  install: buildInstallCommand("levr", { bash: "levr __complete" }),
408
521
  uninstall: buildUninstallCommand("levr", { bash: true })
409
522
  },