@levr-one/cli 0.3.0 → 0.3.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/README.md CHANGED
@@ -1,8 +1,13 @@
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 two 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.
6
11
 
7
12
  ## Install
8
13
 
@@ -15,82 +20,68 @@ npx @levr-one/cli --help
15
20
  The package is self-contained — no peer setup required. A global install
16
21
  replaces the `levr` bin from the deprecated `@levr-one/setup` package.
17
22
 
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
- ```
27
-
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:
31
-
32
- ```bash
33
- npx @levr-one/cli mcp add # detect installed clients and pick interactively
34
- ```
35
-
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)
23
+ ## Quick start
41
24
 
42
- Tab-completion is an explicit opt-in step (it is not installed automatically):
25
+ **Using an AI client?** Wire the Levr MCP server into it no login needed;
26
+ the client opens a browser to authorize the first time it connects:
43
27
 
44
28
  ```bash
45
- levr install # add shell completion for the current shell
46
- levr uninstall # remove it
29
+ npx @levr-one/cli mcp add
47
30
  ```
48
31
 
49
- ## Authentication
50
-
51
- The CLI supports three authentication modes.
52
-
53
- ### Interactive (browser) — default
32
+ **Pushing test results?** Authentication is all you need — log in once (or
33
+ set `LEVR_TOKEN` in CI):
54
34
 
55
35
  ```bash
56
- levr auth login
36
+ levr auth login # browser PKCE (or --device-code for SSH/headless)
37
+ levr push ./results.xml
57
38
  ```
58
39
 
59
- Opens a browser for PKCE-based OAuth login.
40
+ ## Connect AI clients: `levr mcp add`
60
41
 
61
- ### Device code (SSH / headless)
42
+ Detects the MCP-capable clients installed on your machine, lets you pick
43
+ which to set up, and writes the Levr MCP server into each one's config:
62
44
 
63
45
  ```bash
64
- levr auth login --device-code
46
+ levr mcp add # detect clients and pick interactively
47
+ levr mcp add --all # set up every detected client
48
+ levr mcp add --dry-run # preview the changes without writing
49
+ levr mcp add --client cursor,zed --yes # non-interactive selection
65
50
  ```
66
51
 
67
- A code is displayed in the terminal. Open the provided URL on any device, enter
68
- the code, and approve.
52
+ What it writes is **credential-free** a `levr` server entry that connects
53
+ via `mcp-remote`. No token or secret is stored; your client opens the browser
54
+ to authorize with Levr on its first connection. After a run, restart the
55
+ client(s) and authorize once.
69
56
 
70
- ### Personal Access Token (CI/CD)
57
+ **Supported clients:**
71
58
 
72
- Set the `LEVR_TOKEN` environment variable and the CLI uses it automatically —
73
- no interactive login:
59
+ | Client | How it's configured |
60
+ | -------------- | ------------------------------------------------------------------------------ |
61
+ | Claude Desktop | config file (`claude_desktop_config.json`) |
62
+ | Claude Code | prints the `claude mcp add --transport http levr <url>` command for you to run |
63
+ | Cursor | config file (`~/.cursor/mcp.json`) |
64
+ | Windsurf | config file (`~/.codeium/windsurf/mcp_config.json`) |
65
+ | Zed | config file (`settings.json`, `context_servers`) |
66
+ | VS Code, Codex | listed but not yet installable (coming soon) |
74
67
 
75
- ```bash
76
- export LEVR_TOKEN=<your-personal-access-token>
77
- levr push ./results.xml
78
- ```
79
-
80
- ### Other auth commands
68
+ Config edits are **safe and repeatable**: existing MCP servers and comments
69
+ in your config files are preserved (jsonc-aware merge), and re-running is a
70
+ no-op that reports "already set up".
81
71
 
82
- ```bash
83
- levr auth status # show current authentication state
84
- levr auth logout # clear stored credentials
85
- ```
72
+ **Flags:**
86
73
 
87
- ## Workspaces
74
+ | Flag | Alias | Description |
75
+ | ----------------- | ----- | ----------------------------------------------------- |
76
+ | `--client <id,…>` | | Set up these client ids (comma-separated or repeated) |
77
+ | `--all` | | Set up every detected, installable client |
78
+ | `--yes` | `-y` | Non-interactive; auto-select detected clients |
79
+ | `--dry-run` | | Show the changes without writing |
80
+ | `--url <url>` | | MCP server URL (default derived from the API server) |
88
81
 
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
- ```
82
+ Runs non-interactively whenever `--all`, `--client`, or `--yes` is passed —
83
+ or automatically when not attached to a terminal (CI). Unknown client ids and
84
+ failed writes exit non-zero.
94
85
 
95
86
  ## Push test results
96
87
 
@@ -168,6 +159,61 @@ withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
168
159
  }
169
160
  ```
170
161
 
162
+ ## Authentication
163
+
164
+ Needed for `push` and `workspace` commands (`mcp add` needs none). Three modes:
165
+
166
+ ### Interactive (browser) — default
167
+
168
+ ```bash
169
+ levr auth login
170
+ ```
171
+
172
+ Opens a browser for PKCE-based OAuth login.
173
+
174
+ ### Device code (SSH / headless)
175
+
176
+ ```bash
177
+ levr auth login --device-code
178
+ ```
179
+
180
+ A code is displayed in the terminal. Open the provided URL on any device, enter
181
+ the code, and approve.
182
+
183
+ ### Personal Access Token (CI/CD)
184
+
185
+ Set the `LEVR_TOKEN` environment variable and the CLI uses it automatically —
186
+ no interactive login:
187
+
188
+ ```bash
189
+ export LEVR_TOKEN=<your-personal-access-token>
190
+ levr push ./results.xml
191
+ ```
192
+
193
+ ### Other auth commands
194
+
195
+ ```bash
196
+ levr auth status # show current authentication state
197
+ levr auth logout # clear stored credentials
198
+ ```
199
+
200
+ ## Workspaces
201
+
202
+ ```bash
203
+ levr workspace list # list the workspaces you belong to
204
+ levr workspace select # choose the active workspace
205
+ levr workspace current # show the active workspace
206
+ ```
207
+
208
+ ## Shell completion (optional)
209
+
210
+ Tab-completion is an explicit opt-in step (it is not installed automatically):
211
+
212
+ ```bash
213
+ levr install # add shell completion for the current shell
214
+ levr uninstall # remove it
215
+ ```
216
+
171
217
  ## Configuration
172
218
 
173
219
  All configuration is via environment variables. Flags take precedence.
@@ -183,6 +229,14 @@ All configuration is via environment variables. Flags take precedence.
183
229
 
184
230
  ## Troubleshooting
185
231
 
232
+ **The `levr` server doesn't appear in my AI client after `mcp add`** — Restart
233
+ the client; MCP servers are read at startup. Verify the entry with
234
+ `levr mcp add --dry-run` (it reports "already set up" when the config is in
235
+ place).
236
+
237
+ **My client asks me to authorize Levr** — Expected on the first connection:
238
+ the config is credential-free, so each client authorizes once in the browser.
239
+
186
240
  **`Authentication required. Run: levr auth login`** — No valid credentials
187
241
  found. Run `levr auth login` or set `LEVR_TOKEN`.
188
242
 
package/dist/cli.js CHANGED
@@ -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-Dfg89AD2.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-DljfUcC2.js");
190
190
  return statusHandler;
191
191
  }
192
192
  });
@@ -297,7 +297,7 @@ Examples:
297
297
  }
298
298
  },
299
299
  loader: async () => {
300
- const { pushHandler } = await import("./pushHandler-DBBa6hKz.js");
300
+ const { pushHandler } = await import("./pushHandler-CC709aNJ.js");
301
301
  return pushHandler;
302
302
  }
303
303
  });
@@ -318,7 +318,7 @@ Examples:
318
318
  },
319
319
  parameters: {},
320
320
  loader: async () => {
321
- const { listHandler } = await import("./listHandler-EsB3AeOB.js");
321
+ const { listHandler } = await import("./listHandler-7DZhE8jn.js");
322
322
  return listHandler;
323
323
  }
324
324
  });
@@ -351,7 +351,7 @@ Examples:
351
351
  flags: {}
352
352
  },
353
353
  loader: async () => {
354
- const { selectHandler } = await import("./selectHandler-XOUHdyea.js");
354
+ const { selectHandler } = await import("./selectHandler-nOUsPW9p.js");
355
355
  return selectHandler;
356
356
  }
357
357
  });
@@ -375,7 +375,7 @@ Examples:
375
375
 
376
376
  //#endregion
377
377
  //#region package.json
378
- var version = "0.3.0";
378
+ var version = "0.3.1";
379
379
 
380
380
  //#endregion
381
381
  //#region src/app.ts
@@ -1,5 +1,5 @@
1
1
  import "./env-NxtzJJPk.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-BeE6mXns.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-BWrIbeUg.js";
3
3
  import { loadWorkspace } from "./workspace-store-4hfvsEHS.js";
4
4
  import "./token-refresh-Cz-FqDtC.js";
5
5
  import { resolveToken } from "./resolve-token-BRPrqdG-.js";
@@ -1,7 +1,7 @@
1
1
  import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-NxtzJJPk.js";
2
- import { configureClient } from "./sdk-client-BeE6mXns.js";
2
+ import { configureClient } from "./sdk-client-BWrIbeUg.js";
3
3
  import "./workspace-store-4hfvsEHS.js";
4
- import { autoSelectWorkspace } from "./resolve-workspace-Dj7jgtE3.js";
4
+ import { autoSelectWorkspace } from "./resolve-workspace-vJZku4FK.js";
5
5
  import chalk from "chalk";
6
6
  import { createHash, randomBytes } from "node:crypto";
7
7
  import { createServer } from "node:http";
@@ -1,7 +1,7 @@
1
1
  import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-NxtzJJPk.js";
2
- import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-BeE6mXns.js";
2
+ import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-BWrIbeUg.js";
3
3
  import "./workspace-store-4hfvsEHS.js";
4
- import { resolveWorkspace } from "./resolve-workspace-Dj7jgtE3.js";
4
+ import { resolveWorkspace } from "./resolve-workspace-vJZku4FK.js";
5
5
  import "./token-refresh-Cz-FqDtC.js";
6
6
  import { resolveToken } from "./resolve-token-BRPrqdG-.js";
7
7
  import { readFileSync, statSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { authGetSitesV1 } from "./sdk-client-BeE6mXns.js";
1
+ import { authGetSitesV1 } from "./sdk-client-BWrIbeUg.js";
2
2
  import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-4hfvsEHS.js";
3
3
 
4
4
  //#region src/workspace/resolve-workspace.ts
@@ -14060,6 +14060,11 @@ const zCreateGithubIssueLinkDto = z.object({
14060
14060
  sync_direction: z.string().optional().describe(""),
14061
14061
  origin: z.string().optional().describe(""),
14062
14062
  role: z.string().optional().describe(""),
14063
+ last_synced_fingerprint: z.string().nullable().optional().describe(""),
14064
+ last_synced_origin: z.string().nullable().optional().describe(""),
14065
+ last_synced_snapshot: z.unknown().optional().describe(""),
14066
+ sync_status: z.string().optional().describe(""),
14067
+ conflict_id: z.string().nullable().optional().describe(""),
14063
14068
  tq_issue_id: z.string(),
14064
14069
  github_repository_id: z.string(),
14065
14070
  team_id: z.string()
@@ -14071,6 +14076,11 @@ const zResponseGithubIssueLinkDto = z.object({
14071
14076
  sync_direction: z.string().optional().describe(""),
14072
14077
  origin: z.string().optional().describe(""),
14073
14078
  role: z.string().optional().describe(""),
14079
+ last_synced_fingerprint: z.string().nullable().optional().describe(""),
14080
+ last_synced_origin: z.string().nullable().optional().describe(""),
14081
+ last_synced_snapshot: z.unknown().optional().describe(""),
14082
+ sync_status: z.string().optional().describe(""),
14083
+ conflict_id: z.string().nullable().optional().describe(""),
14074
14084
  tq_issue_id: z.string(),
14075
14085
  github_repository_id: z.string(),
14076
14086
  team_id: z.string(),
@@ -14090,6 +14100,11 @@ const zUpdateGithubIssueLinkDto = z.object({
14090
14100
  sync_direction: z.string().optional().describe(""),
14091
14101
  origin: z.string().optional().describe(""),
14092
14102
  role: z.string().optional().describe(""),
14103
+ last_synced_fingerprint: z.string().nullable().optional().describe(""),
14104
+ last_synced_origin: z.string().nullable().optional().describe(""),
14105
+ last_synced_snapshot: z.unknown().optional().describe(""),
14106
+ sync_status: z.string().optional().describe(""),
14107
+ conflict_id: z.string().nullable().optional().describe(""),
14093
14108
  tq_issue_id: z.string().optional(),
14094
14109
  github_repository_id: z.string().optional(),
14095
14110
  team_id: z.string().optional()
@@ -27227,7 +27242,6 @@ const zIngestRequestDto = z.object({
27227
27242
  force_upsert: z.boolean().optional()
27228
27243
  });
27229
27244
  const zSyncGetLastSyncIdV1Data = z.object({ url: z.literal("/v1/sync/last-sync-id") });
27230
- const zSyncResetSyncV1Data = z.object({ url: z.literal("/v1/sync/reset") });
27231
27245
  const zSyncBootstrapV1Data = z.object({
27232
27246
  body: zBootstrapRequestDto,
27233
27247
  url: z.literal("/v1/sync/bootstrap")
@@ -1,5 +1,5 @@
1
1
  import "./env-NxtzJJPk.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-BeE6mXns.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-BWrIbeUg.js";
3
3
  import { saveWorkspace } from "./workspace-store-4hfvsEHS.js";
4
4
  import "./token-refresh-Cz-FqDtC.js";
5
5
  import { resolveToken } from "./resolve-token-BRPrqdG-.js";
@@ -1,5 +1,5 @@
1
1
  import { getApiUrl, getPatToken, readCredentials } from "./env-NxtzJJPk.js";
2
- import { authGetProfileV1, configureClient } from "./sdk-client-BeE6mXns.js";
2
+ import { authGetProfileV1, configureClient } from "./sdk-client-BWrIbeUg.js";
3
3
  import { isTokenExpired } from "./token-refresh-Cz-FqDtC.js";
4
4
  import chalk from "chalk";
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levr-one/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",