@levr-one/cli 0.4.0 → 0.5.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 +70 -9
- package/dist/{addHandler-DFtvzTci.js → addHandler-K2a4rVv8.js} +14 -14
- package/dist/cli.js +59 -30
- package/dist/completionHandler-DtG_CsK0.js +3 -0
- package/dist/completionHandler-O2G_WZLf.js +95 -0
- package/dist/{currentHandler-BD-h0j-S.js → currentHandler-CHydiFui.js} +1 -1
- package/dist/{env-NxtzJJPk.js → env-CHeKHu5S.js} +1 -1
- package/dist/{importHandler-ChywPYeL.js → importHandler-D27Xrx73.js} +9 -9
- package/dist/{listHandler-fK9Mapr3.js → listHandler-8uqFClxw.js} +5 -5
- package/dist/{loginHandler-D9k-tQLX.js → loginHandler-BhvCWTd8.js} +4 -4
- package/dist/{logoutHandler-BC8laIAB.js → logoutHandler-BFutKash.js} +2 -2
- package/dist/{pushHandler-COg5WbGS.js → pushHandler-v4-lHK6A.js} +6 -6
- package/dist/{resolve-token-Dq8cAuiu.js → resolve-token-DbQsmn03.js} +2 -2
- package/dist/{resolve-workspace-CO8d3IBz.js → resolve-workspace-BnTDxvYq.js} +2 -2
- package/dist/{sdk-client-DyRTezC5.js → sdk-client-DP7uCfXP.js} +73 -2
- package/dist/{selectHandler-BzQRF8E7.js → selectHandler-DeEZe6PU.js} +5 -5
- package/dist/{statusHandler-DuVZJMtw.js → statusHandler-B6XRPg4k.js} +3 -3
- package/dist/{token-refresh-Cz-FqDtC.js → token-refresh-Cu5RpkLJ.js} +1 -1
- package/dist/{workspace-store-4hfvsEHS.js → workspace-store-DDOxnut1.js} +1 -1
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -14,14 +14,45 @@ The command-line interface for [Levr](https://www.levr.one). The binary is
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install -g @levr-one/cli #
|
|
18
|
-
# or run once, no install:
|
|
19
|
-
npx @levr-one/cli --help
|
|
17
|
+
npm install -g @levr-one/cli # gives you a persistent `levr` command
|
|
20
18
|
```
|
|
21
19
|
|
|
22
20
|
The package is self-contained — no peer setup required. A global install
|
|
23
21
|
replaces the `levr` bin from the deprecated `@levr-one/setup` package.
|
|
24
22
|
|
|
23
|
+
### Or run it without a global install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @levr-one/cli --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`npx` is right for one-shot use (`mcp add`, trying a command). Two things to
|
|
30
|
+
expect:
|
|
31
|
+
|
|
32
|
+
- **`levr` will not be on your PATH afterwards.** npx puts the command on PATH
|
|
33
|
+
only for the duration of that one invocation. Shell completion also assumes a
|
|
34
|
+
global install, since the generated script wires up the `levr` command.
|
|
35
|
+
- **It does download the package** — roughly 8 MB into npm's `~/.npm/_npx`
|
|
36
|
+
cache, kept indefinitely. The first run asks `Ok to proceed?`; later runs are
|
|
37
|
+
silent until a new version is published.
|
|
38
|
+
|
|
39
|
+
In scripts and CI, pass `--yes` — **in a terminal the prompt blocks until it is
|
|
40
|
+
answered**, so an unattended run with a TTY hangs rather than failing:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx --yes @levr-one/cli push ./test-results.xml
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Two things to get right:
|
|
47
|
+
|
|
48
|
+
- **Always `npx @levr-one/cli`, never `npx levr`.** npx treats the first
|
|
49
|
+
positional as a _package_ name, so `npx levr` would fetch and run whatever
|
|
50
|
+
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
|
|
52
|
+
`--yes` to `levr`, not to npm.
|
|
53
|
+
|
|
54
|
+
For reproducible CI, pin `@levr-one/cli` to a version you have validated.
|
|
55
|
+
|
|
25
56
|
## Quick start
|
|
26
57
|
|
|
27
58
|
**Using an AI client?** Wire the Levr MCP server into it — no login needed;
|
|
@@ -203,7 +234,7 @@ levr import ./cases.csv --team-id <uuid> --map "State=labels:state"
|
|
|
203
234
|
env:
|
|
204
235
|
LEVR_TOKEN: ${{ secrets.LEVR_TOKEN }}
|
|
205
236
|
# LEVR_TEAM_ID is optional — server resolves from automation source or workspace default
|
|
206
|
-
run: npx @levr-one/cli push ./test-results.xml
|
|
237
|
+
run: npx --yes @levr-one/cli push ./test-results.xml
|
|
207
238
|
```
|
|
208
239
|
|
|
209
240
|
### GitLab CI
|
|
@@ -211,7 +242,7 @@ levr import ./cases.csv --team-id <uuid> --map "State=labels:state"
|
|
|
211
242
|
```yaml
|
|
212
243
|
push-results:
|
|
213
244
|
script:
|
|
214
|
-
- npx @levr-one/cli push ./test-results.xml
|
|
245
|
+
- npx --yes @levr-one/cli push ./test-results.xml
|
|
215
246
|
variables:
|
|
216
247
|
LEVR_TOKEN: $LEVR_TOKEN
|
|
217
248
|
```
|
|
@@ -220,7 +251,7 @@ push-results:
|
|
|
220
251
|
|
|
221
252
|
```groovy
|
|
222
253
|
withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
|
|
223
|
-
sh 'npx @levr-one/cli push ./test-results.xml'
|
|
254
|
+
sh 'npx --yes @levr-one/cli push ./test-results.xml'
|
|
224
255
|
}
|
|
225
256
|
```
|
|
226
257
|
|
|
@@ -272,13 +303,43 @@ levr workspace current # show the active workspace
|
|
|
272
303
|
|
|
273
304
|
## Shell completion (optional)
|
|
274
305
|
|
|
275
|
-
|
|
306
|
+
Completion needs a global install: the generated script wires up the `levr`
|
|
307
|
+
command, so it does nothing if `levr` is not on your PATH (which it is not when
|
|
308
|
+
you run via `npx`).
|
|
309
|
+
|
|
310
|
+
`levr completion` prints a completion script to **stdout**. It never edits your
|
|
311
|
+
shell config — you choose where the script goes:
|
|
276
312
|
|
|
277
313
|
```bash
|
|
278
|
-
|
|
279
|
-
levr
|
|
314
|
+
# This session only
|
|
315
|
+
eval "$(levr completion bash)"
|
|
316
|
+
eval "$(levr completion zsh)"
|
|
317
|
+
|
|
318
|
+
# Persist it
|
|
319
|
+
levr completion bash >> ~/.bashrc
|
|
320
|
+
levr completion zsh >> ~/.zshrc
|
|
321
|
+
|
|
322
|
+
# Or system-wide (bash)
|
|
323
|
+
levr completion bash > /etc/bash_completion.d/levr
|
|
280
324
|
```
|
|
281
325
|
|
|
326
|
+
**zsh:** load it _after_ `compinit`, which is what defines `compdef`:
|
|
327
|
+
|
|
328
|
+
```zsh
|
|
329
|
+
autoload -Uz compinit && compinit
|
|
330
|
+
eval "$(levr completion zsh)"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
If `compinit` has not run, the script says so on stderr instead of failing with
|
|
334
|
+
a bare `command not found: compdef`.
|
|
335
|
+
|
|
336
|
+
Supported shells: **bash** and **zsh**. The shell is detected from `$SHELL` when
|
|
337
|
+
you omit the argument — pass it explicitly in scripts, or to generate a script
|
|
338
|
+
for a shell other than the one you are running. An unsupported or undetectable
|
|
339
|
+
shell writes an error to stderr and exits non-zero.
|
|
340
|
+
|
|
341
|
+
Nothing is installed automatically: the package ships no `postinstall` hook.
|
|
342
|
+
|
|
282
343
|
## Configuration
|
|
283
344
|
|
|
284
345
|
All configuration is via environment variables. Flags take precedence.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getApiUrl } from "./env-
|
|
1
|
+
import { getApiUrl } from "./env-CHeKHu5S.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { delimiter, dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
5
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
6
6
|
|
|
7
7
|
//#region ../mcp-harnesses/dist/catalog.js
|
|
@@ -356,17 +356,17 @@ function signalMatches(signal, env) {
|
|
|
356
356
|
return existsSync(signal.startsWith("~") ? expandTilde(signal, env.homedir) : signal);
|
|
357
357
|
}
|
|
358
358
|
/** Read a text file, or `null` if it doesn't exist / can't be read. */
|
|
359
|
-
function readTextOrNull(path
|
|
359
|
+
function readTextOrNull(path) {
|
|
360
360
|
try {
|
|
361
|
-
return readFileSync(path
|
|
361
|
+
return readFileSync(path, "utf8");
|
|
362
362
|
} catch {
|
|
363
363
|
return null;
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
/** Safe nested lookup over an unknown-typed parsed JSON value. */
|
|
367
|
-
function getAtPath(obj, path
|
|
367
|
+
function getAtPath(obj, path) {
|
|
368
368
|
let cur = obj;
|
|
369
|
-
for (const key of path
|
|
369
|
+
for (const key of path) {
|
|
370
370
|
if (cur === null || typeof cur !== "object") return void 0;
|
|
371
371
|
cur = cur[key];
|
|
372
372
|
}
|
|
@@ -430,8 +430,8 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
430
430
|
alreadyConfigured: false,
|
|
431
431
|
dryRun
|
|
432
432
|
};
|
|
433
|
-
const path
|
|
434
|
-
if (!path
|
|
433
|
+
const path = resolveConfigPath(harness, env);
|
|
434
|
+
if (!path) return {
|
|
435
435
|
ok: false,
|
|
436
436
|
wrote: false,
|
|
437
437
|
path: "",
|
|
@@ -440,7 +440,7 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
440
440
|
};
|
|
441
441
|
const entryValue = buildServerEntry(harness, mcpUrl)[SERVER_NAME];
|
|
442
442
|
const modPath = [harness.serverPropertyName, SERVER_NAME];
|
|
443
|
-
const existing = readTextOrNull(path
|
|
443
|
+
const existing = readTextOrNull(path);
|
|
444
444
|
const baseText = existing && existing.trim() ? existing : "{}";
|
|
445
445
|
const current = getAtPath(parse(baseText), modPath);
|
|
446
446
|
const alreadyConfigured = current !== void 0 && sameValue(current, entryValue);
|
|
@@ -448,7 +448,7 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
448
448
|
if (alreadyConfigured) return {
|
|
449
449
|
ok: true,
|
|
450
450
|
wrote: false,
|
|
451
|
-
path
|
|
451
|
+
path,
|
|
452
452
|
alreadyConfigured: true,
|
|
453
453
|
dryRun,
|
|
454
454
|
preview: nextText
|
|
@@ -456,18 +456,18 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
|
|
|
456
456
|
if (dryRun) return {
|
|
457
457
|
ok: true,
|
|
458
458
|
wrote: false,
|
|
459
|
-
path
|
|
459
|
+
path,
|
|
460
460
|
alreadyConfigured: false,
|
|
461
461
|
dryRun: true,
|
|
462
462
|
preview: nextText
|
|
463
463
|
};
|
|
464
|
-
mkdirSync(dirname(path
|
|
464
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
465
465
|
const finalText = nextText.endsWith("\n") ? nextText : `${nextText}\n`;
|
|
466
|
-
writeFileSync(path
|
|
466
|
+
writeFileSync(path, finalText, "utf8");
|
|
467
467
|
return {
|
|
468
468
|
ok: true,
|
|
469
469
|
wrote: true,
|
|
470
|
-
path
|
|
470
|
+
path,
|
|
471
471
|
alreadyConfigured: false,
|
|
472
472
|
dryRun: false,
|
|
473
473
|
preview: finalText
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { COMPLETION_SHELLS } from "./completionHandler-O2G_WZLf.js";
|
|
2
3
|
import { buildApplication, buildCommand, buildRouteMap, proposeCompletions, run, text_en } from "@stricli/core";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
4
|
import chalk from "chalk";
|
|
7
|
-
import { buildInstallCommand, buildUninstallCommand } from "@stricli/auto-complete";
|
|
8
5
|
|
|
9
6
|
//#region src/utils/logger.ts
|
|
10
7
|
var Logger = class {
|
|
@@ -41,9 +38,6 @@ var Logger = class {
|
|
|
41
38
|
function buildContext(process$1) {
|
|
42
39
|
return {
|
|
43
40
|
process: process$1,
|
|
44
|
-
os,
|
|
45
|
-
fs,
|
|
46
|
-
path,
|
|
47
41
|
logger: new Logger({
|
|
48
42
|
verbose: false,
|
|
49
43
|
stdout: process$1.stdout,
|
|
@@ -52,6 +46,45 @@ function buildContext(process$1) {
|
|
|
52
46
|
};
|
|
53
47
|
}
|
|
54
48
|
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/commands/completion.ts
|
|
51
|
+
const completionCommand = buildCommand({
|
|
52
|
+
docs: {
|
|
53
|
+
brief: "Print the shell completion script for bash or zsh",
|
|
54
|
+
fullDescription: `Prints a shell completion script to stdout. You decide where it goes —
|
|
55
|
+
this command never edits your shell config.
|
|
56
|
+
|
|
57
|
+
Load it for the current session:
|
|
58
|
+
eval "$(levr completion bash)"
|
|
59
|
+
eval "$(levr completion zsh)"
|
|
60
|
+
|
|
61
|
+
Or persist it:
|
|
62
|
+
levr completion bash >> ~/.bashrc
|
|
63
|
+
levr completion zsh >> ~/.zshrc
|
|
64
|
+
levr completion bash > /etc/bash_completion.d/levr # system-wide
|
|
65
|
+
|
|
66
|
+
The shell is detected from $SHELL when omitted; pass it explicitly in scripts
|
|
67
|
+
or when generating a script for a shell other than the one you are running.`
|
|
68
|
+
},
|
|
69
|
+
parameters: {
|
|
70
|
+
positional: {
|
|
71
|
+
kind: "tuple",
|
|
72
|
+
parameters: [{
|
|
73
|
+
parse: String,
|
|
74
|
+
proposeCompletions: (partial) => COMPLETION_SHELLS.filter((shell) => shell.startsWith(partial)),
|
|
75
|
+
brief: `Shell to emit (${COMPLETION_SHELLS.join("|")}); detected from $SHELL if omitted`,
|
|
76
|
+
placeholder: "shell",
|
|
77
|
+
optional: true
|
|
78
|
+
}]
|
|
79
|
+
},
|
|
80
|
+
flags: {}
|
|
81
|
+
},
|
|
82
|
+
loader: async () => {
|
|
83
|
+
const { completionHandler } = await import("./completionHandler-DtG_CsK0.js");
|
|
84
|
+
return completionHandler;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
55
88
|
//#endregion
|
|
56
89
|
//#region src/commands/mcp/add.ts
|
|
57
90
|
const mcpAddCommand = buildCommand({
|
|
@@ -109,7 +142,7 @@ Examples:
|
|
|
109
142
|
aliases: { y: "yes" }
|
|
110
143
|
},
|
|
111
144
|
loader: async () => {
|
|
112
|
-
const { mcpAddHandler } = await import("./addHandler-
|
|
145
|
+
const { mcpAddHandler } = await import("./addHandler-K2a4rVv8.js");
|
|
113
146
|
return mcpAddHandler;
|
|
114
147
|
}
|
|
115
148
|
});
|
|
@@ -147,7 +180,7 @@ Examples:
|
|
|
147
180
|
aliases: { d: "device-code" }
|
|
148
181
|
},
|
|
149
182
|
loader: async () => {
|
|
150
|
-
const { loginHandler } = await import("./loginHandler-
|
|
183
|
+
const { loginHandler } = await import("./loginHandler-BhvCWTd8.js");
|
|
151
184
|
return loginHandler;
|
|
152
185
|
}
|
|
153
186
|
});
|
|
@@ -166,7 +199,7 @@ Examples:
|
|
|
166
199
|
},
|
|
167
200
|
parameters: {},
|
|
168
201
|
loader: async () => {
|
|
169
|
-
const { logoutHandler } = await import("./logoutHandler-
|
|
202
|
+
const { logoutHandler } = await import("./logoutHandler-BFutKash.js");
|
|
170
203
|
return logoutHandler;
|
|
171
204
|
}
|
|
172
205
|
});
|
|
@@ -186,7 +219,7 @@ Examples:
|
|
|
186
219
|
},
|
|
187
220
|
parameters: {},
|
|
188
221
|
loader: async () => {
|
|
189
|
-
const { statusHandler } = await import("./statusHandler-
|
|
222
|
+
const { statusHandler } = await import("./statusHandler-B6XRPg4k.js");
|
|
190
223
|
return statusHandler;
|
|
191
224
|
}
|
|
192
225
|
});
|
|
@@ -297,7 +330,7 @@ Examples:
|
|
|
297
330
|
}
|
|
298
331
|
},
|
|
299
332
|
loader: async () => {
|
|
300
|
-
const { pushHandler } = await import("./pushHandler-
|
|
333
|
+
const { pushHandler } = await import("./pushHandler-v4-lHK6A.js");
|
|
301
334
|
return pushHandler;
|
|
302
335
|
}
|
|
303
336
|
});
|
|
@@ -409,7 +442,7 @@ Examples:
|
|
|
409
442
|
}
|
|
410
443
|
},
|
|
411
444
|
loader: async () => {
|
|
412
|
-
const { importHandler } = await import("./importHandler-
|
|
445
|
+
const { importHandler } = await import("./importHandler-D27Xrx73.js");
|
|
413
446
|
return importHandler;
|
|
414
447
|
}
|
|
415
448
|
});
|
|
@@ -430,7 +463,7 @@ Examples:
|
|
|
430
463
|
},
|
|
431
464
|
parameters: {},
|
|
432
465
|
loader: async () => {
|
|
433
|
-
const { listHandler } = await import("./listHandler-
|
|
466
|
+
const { listHandler } = await import("./listHandler-8uqFClxw.js");
|
|
434
467
|
return listHandler;
|
|
435
468
|
}
|
|
436
469
|
});
|
|
@@ -463,7 +496,7 @@ Examples:
|
|
|
463
496
|
flags: {}
|
|
464
497
|
},
|
|
465
498
|
loader: async () => {
|
|
466
|
-
const { selectHandler } = await import("./selectHandler-
|
|
499
|
+
const { selectHandler } = await import("./selectHandler-DeEZe6PU.js");
|
|
467
500
|
return selectHandler;
|
|
468
501
|
}
|
|
469
502
|
});
|
|
@@ -480,14 +513,14 @@ Examples:
|
|
|
480
513
|
},
|
|
481
514
|
parameters: {},
|
|
482
515
|
loader: async () => {
|
|
483
|
-
const { currentHandler } = await import("./currentHandler-
|
|
516
|
+
const { currentHandler } = await import("./currentHandler-CHydiFui.js");
|
|
484
517
|
return currentHandler;
|
|
485
518
|
}
|
|
486
519
|
});
|
|
487
520
|
|
|
488
521
|
//#endregion
|
|
489
522
|
//#region package.json
|
|
490
|
-
var version = "0.
|
|
523
|
+
var version = "0.5.1";
|
|
491
524
|
|
|
492
525
|
//#endregion
|
|
493
526
|
//#region src/app.ts
|
|
@@ -517,16 +550,9 @@ const routes = buildRouteMap({
|
|
|
517
550
|
workspace: workspaceRoutes,
|
|
518
551
|
push: pushCommand,
|
|
519
552
|
import: importCommand,
|
|
520
|
-
|
|
521
|
-
uninstall: buildUninstallCommand("levr", { bash: true })
|
|
553
|
+
completion: completionCommand
|
|
522
554
|
},
|
|
523
|
-
docs: {
|
|
524
|
-
brief: "The command-line interface for Levr",
|
|
525
|
-
hideRoute: {
|
|
526
|
-
install: true,
|
|
527
|
-
uninstall: true
|
|
528
|
-
}
|
|
529
|
-
}
|
|
555
|
+
docs: { brief: "The command-line interface for Levr" }
|
|
530
556
|
});
|
|
531
557
|
const app = buildApplication(routes, {
|
|
532
558
|
name: "levr",
|
|
@@ -545,10 +571,13 @@ const app = buildApplication(routes, {
|
|
|
545
571
|
//#region src/completion.ts
|
|
546
572
|
/**
|
|
547
573
|
* Compute shell tab-completion suggestions for the hidden `__complete` entrypoint
|
|
548
|
-
* (see src/bin/cli.ts).
|
|
549
|
-
* `levr __complete <
|
|
550
|
-
*
|
|
551
|
-
*
|
|
574
|
+
* (see src/bin/cli.ts). The scripts emitted by `levr completion bash|zsh`
|
|
575
|
+
* register a shell function that invokes `levr __complete <words…>` on each TAB,
|
|
576
|
+
* so `rawArgs` (process.argv.slice(2)) is
|
|
577
|
+
* `['__complete', <targetCommandName>, ...wordsBeingCompleted]` — note the
|
|
578
|
+
* command name is passed through and dropped here, which is why the shell
|
|
579
|
+
* scripts pass their FULL word array. A COMP_LINE ending in a space means the
|
|
580
|
+
* cursor is on a fresh (empty) word to complete.
|
|
552
581
|
*
|
|
553
582
|
* Never throws — completion must not surface an error to the user's shell.
|
|
554
583
|
*/
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/commands/completionHandler.ts
|
|
2
|
+
const COMPLETION_SHELLS = ["bash", "zsh"];
|
|
3
|
+
function isCompletionShell(value) {
|
|
4
|
+
return COMPLETION_SHELLS.includes(value);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Best-effort shell detection from `$SHELL`. Matches on the basename so login
|
|
8
|
+
* shells (`-bash`) and non-standard prefixes (`/usr/local/bin/zsh`) still
|
|
9
|
+
* resolve. Returns undefined when the shell is unset or unsupported — the
|
|
10
|
+
* caller must then fail loudly rather than guess.
|
|
11
|
+
*/
|
|
12
|
+
function detectShell(shellPath) {
|
|
13
|
+
const name = shellPath?.split("/").pop() ?? "";
|
|
14
|
+
if (name.includes("zsh")) return "zsh";
|
|
15
|
+
if (name.includes("bash")) return "bash";
|
|
16
|
+
}
|
|
17
|
+
const BASH_SCRIPT = `# levr bash completion.
|
|
18
|
+
# Load it from ~/.bashrc: eval "$(levr completion bash)"
|
|
19
|
+
__levr_complete() {
|
|
20
|
+
export COMP_LINE
|
|
21
|
+
local -a words=( "\${COMP_WORDS[@]}" )
|
|
22
|
+
# Drop a trailing empty word. COMP_LINE's trailing space is already the
|
|
23
|
+
# signal that the cursor is on a fresh word; passing the empty word too
|
|
24
|
+
# would make __complete see it twice and match nothing. Harmless when the
|
|
25
|
+
# last word is non-empty, so this is correct however bash populates
|
|
26
|
+
# COMP_WORDS.
|
|
27
|
+
if [[ \${#words[@]} -gt 0 && -z "\${words[\${#words[@]}-1]}" ]]; then
|
|
28
|
+
words=( "\${words[@]:0:\${#words[@]}-1}" )
|
|
29
|
+
fi
|
|
30
|
+
COMPREPLY=( $(levr __complete "\${words[@]}") )
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
complete -o default -o nospace -F __levr_complete levr
|
|
34
|
+
`;
|
|
35
|
+
const ZSH_SCRIPT = `# levr zsh completion.
|
|
36
|
+
# Load it from ~/.zshrc, AFTER your compinit call:
|
|
37
|
+
# autoload -Uz compinit && compinit
|
|
38
|
+
# eval "$(levr completion zsh)"
|
|
39
|
+
_levr_complete() {
|
|
40
|
+
local -a candidates args
|
|
41
|
+
local raw
|
|
42
|
+
local comp_line="\${(j: :)words}"
|
|
43
|
+
|
|
44
|
+
args=("\${(@)words}")
|
|
45
|
+
if [[ -z "\${args[-1]}" ]]; then
|
|
46
|
+
args=("\${(@)args[1,-2]}")
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Return before splitting when there is nothing to offer, so compadd is
|
|
50
|
+
# never reached with an empty candidate list.
|
|
51
|
+
raw="$(COMP_LINE="$comp_line" levr __complete "\${args[@]}" 2>/dev/null)"
|
|
52
|
+
[[ -n "$raw" ]] || return
|
|
53
|
+
|
|
54
|
+
candidates=(\${(f)raw})
|
|
55
|
+
# -S '' matches bash's \`complete -o nospace\`: no space is appended after a
|
|
56
|
+
# unique match, so subcommands can be chained without deleting one.
|
|
57
|
+
compadd -S '' -- "\${candidates[@]}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# compdef only exists once compinit has run. Without this guard a .zshrc that
|
|
61
|
+
# eval's this before compinit dies with a bare "command not found: compdef" on
|
|
62
|
+
# every new shell; the explicit message says what to actually do.
|
|
63
|
+
if (( $+functions[compdef] )); then
|
|
64
|
+
compdef _levr_complete levr
|
|
65
|
+
else
|
|
66
|
+
print -u2 "levr: completion not registered — run \\\`autoload -Uz compinit && compinit\\\` before loading it."
|
|
67
|
+
fi
|
|
68
|
+
`;
|
|
69
|
+
function completionScript(shell) {
|
|
70
|
+
return shell === "zsh" ? ZSH_SCRIPT : BASH_SCRIPT;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Print the completion script for `shell` (or the detected shell) to stdout.
|
|
74
|
+
*
|
|
75
|
+
* stdout carries the script and NOTHING else, so `eval "$(levr completion zsh)"`
|
|
76
|
+
* is safe; diagnostics go to stderr and set a non-zero exit code. The command
|
|
77
|
+
* never writes to the filesystem — wiring it up is the user's call (ENG-2702).
|
|
78
|
+
*/
|
|
79
|
+
function completionHandler(_flags, shell) {
|
|
80
|
+
const requested = shell === void 0 ? detectShell(this.process.env["SHELL"]) : shell;
|
|
81
|
+
if (requested === void 0) {
|
|
82
|
+
this.process.stderr.write(`Could not detect your shell from \$SHELL. Pass one explicitly: levr completion <${COMPLETION_SHELLS.join("|")}>\n`);
|
|
83
|
+
this.process.exitCode = 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!isCompletionShell(requested)) {
|
|
87
|
+
this.process.stderr.write(`Unsupported shell "${requested}". Supported shells: ${COMPLETION_SHELLS.join(", ")}.\n`);
|
|
88
|
+
this.process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.process.stdout.write(completionScript(requested));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { COMPLETION_SHELLS, completionHandler, completionScript, detectShell, isCompletionShell };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
4
|
|
|
5
5
|
//#region src/auth/credentials.ts
|
|
6
6
|
const CREDENTIALS_PATH = join(join(homedir(), ".config", "levr"), "credentials.json");
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { getApiUrl } from "./env-
|
|
2
|
-
import { configureClient, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-
|
|
3
|
-
import "./workspace-store-
|
|
4
|
-
import { resolveWorkspace } from "./resolve-workspace-
|
|
5
|
-
import "./token-refresh-
|
|
6
|
-
import { resolveToken } from "./resolve-token-
|
|
1
|
+
import { getApiUrl } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { configureClient, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import "./workspace-store-DDOxnut1.js";
|
|
4
|
+
import { resolveWorkspace } from "./resolve-workspace-BnTDxvYq.js";
|
|
5
|
+
import "./token-refresh-Cu5RpkLJ.js";
|
|
6
|
+
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
7
|
+
import chalk from "chalk";
|
|
7
8
|
import { createReadStream } from "node:fs";
|
|
8
9
|
import { basename } from "node:path";
|
|
9
|
-
import chalk from "chalk";
|
|
10
10
|
import { readFile, writeFile } from "node:fs/promises";
|
|
11
11
|
import { createInterface } from "node:readline/promises";
|
|
12
12
|
import ora from "ora";
|
|
@@ -108,9 +108,9 @@ const MATCH_COLORS = {
|
|
|
108
108
|
manual: chalk.green
|
|
109
109
|
};
|
|
110
110
|
/** Stream the file into a Blob — no readFileSync, no sync stall. */
|
|
111
|
-
async function fileToBlob(path
|
|
111
|
+
async function fileToBlob(path) {
|
|
112
112
|
const chunks = [];
|
|
113
|
-
for await (const chunk of createReadStream(path
|
|
113
|
+
for await (const chunk of createReadStream(path)) chunks.push(chunk);
|
|
114
114
|
return new Blob(chunks);
|
|
115
115
|
}
|
|
116
116
|
function printMapping(logger, mapping) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./env-
|
|
2
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
3
|
-
import { loadWorkspace } from "./workspace-store-
|
|
4
|
-
import "./token-refresh-
|
|
5
|
-
import { resolveToken } from "./resolve-token-
|
|
1
|
+
import "./env-CHeKHu5S.js";
|
|
2
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import { loadWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
4
|
+
import "./token-refresh-Cu5RpkLJ.js";
|
|
5
|
+
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/listHandler.ts
|
|
8
8
|
async function listHandler() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-
|
|
2
|
-
import { configureClient } from "./sdk-client-
|
|
3
|
-
import "./workspace-store-
|
|
4
|
-
import { autoSelectWorkspace } from "./resolve-workspace-
|
|
1
|
+
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { configureClient } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import "./workspace-store-DDOxnut1.js";
|
|
4
|
+
import { autoSelectWorkspace } from "./resolve-workspace-BnTDxvYq.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
7
7
|
import { createServer } from "node:http";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { deleteCredentials, getPatToken } from "./env-
|
|
2
|
-
import { clearWorkspace } from "./workspace-store-
|
|
1
|
+
import { deleteCredentials, getPatToken } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { clearWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
3
3
|
|
|
4
4
|
//#region src/commands/auth/logoutHandler.ts
|
|
5
5
|
function logoutHandler() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-
|
|
2
|
-
import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-
|
|
3
|
-
import "./workspace-store-
|
|
4
|
-
import { resolveWorkspace } from "./resolve-workspace-
|
|
5
|
-
import "./token-refresh-
|
|
6
|
-
import { resolveToken } from "./resolve-token-
|
|
1
|
+
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import "./workspace-store-DDOxnut1.js";
|
|
4
|
+
import { resolveWorkspace } from "./resolve-workspace-BnTDxvYq.js";
|
|
5
|
+
import "./token-refresh-Cu5RpkLJ.js";
|
|
6
|
+
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
7
7
|
import { readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { basename } from "node:path";
|
|
9
9
|
import ora from "ora";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getApiUrl, getPatToken, readCredentials } from "./env-
|
|
2
|
-
import { isTokenExpired, refreshToken } from "./token-refresh-
|
|
1
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { isTokenExpired, refreshToken } from "./token-refresh-Cu5RpkLJ.js";
|
|
3
3
|
|
|
4
4
|
//#region src/auth/resolve-token.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { authGetSitesV1 } from "./sdk-client-
|
|
2
|
-
import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-
|
|
1
|
+
import { authGetSitesV1 } from "./sdk-client-DP7uCfXP.js";
|
|
2
|
+
import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
3
3
|
|
|
4
4
|
//#region src/workspace/resolve-workspace.ts
|
|
5
5
|
async function fetchSites() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getApiUrl } from "./env-
|
|
1
|
+
import { getApiUrl } from "./env-CHeKHu5S.js";
|
|
2
2
|
import { z } from "zod/v3";
|
|
3
3
|
|
|
4
4
|
//#region ../sdk/dist/gen/_common/zod.js
|
|
@@ -2426,18 +2426,81 @@ const zAppGetHelloV1Data = z.object({ url: z.literal("/v1") });
|
|
|
2426
2426
|
|
|
2427
2427
|
//#endregion
|
|
2428
2428
|
//#region ../sdk/dist/gen/archive/zod.js
|
|
2429
|
+
const zArchiveListResponseDto = z.object({
|
|
2430
|
+
data: z.array(z.object({
|
|
2431
|
+
id: z.string(),
|
|
2432
|
+
entity_type: z.string(),
|
|
2433
|
+
display_name: z.string(),
|
|
2434
|
+
archived_at: z.string().nullable().optional(),
|
|
2435
|
+
deleted_at: z.string().nullable().optional(),
|
|
2436
|
+
created_at: z.string(),
|
|
2437
|
+
created_by: z.string(),
|
|
2438
|
+
deleted_by: z.string().nullable().optional()
|
|
2439
|
+
})),
|
|
2440
|
+
meta: z.object({
|
|
2441
|
+
itemsPerPage: z.number(),
|
|
2442
|
+
totalItems: z.number(),
|
|
2443
|
+
currentPage: z.number(),
|
|
2444
|
+
totalPages: z.number()
|
|
2445
|
+
})
|
|
2446
|
+
});
|
|
2447
|
+
const zArchiveFacetsResponseDto = z.object({
|
|
2448
|
+
entityTypes: z.array(z.object({
|
|
2449
|
+
value: z.string(),
|
|
2450
|
+
count: z.number()
|
|
2451
|
+
})),
|
|
2452
|
+
owners: z.array(z.object({
|
|
2453
|
+
value: z.string(),
|
|
2454
|
+
count: z.number()
|
|
2455
|
+
})),
|
|
2456
|
+
deleters: z.array(z.object({
|
|
2457
|
+
value: z.string(),
|
|
2458
|
+
count: z.number()
|
|
2459
|
+
}))
|
|
2460
|
+
});
|
|
2429
2461
|
const zArchiveActionDto = z.object({
|
|
2430
2462
|
id: z.string(),
|
|
2431
2463
|
entity_type: z.string()
|
|
2432
2464
|
});
|
|
2465
|
+
const zArchiveBulkActionDto = z.object({ items: z.array(z.object({
|
|
2466
|
+
id: z.string(),
|
|
2467
|
+
entity_type: z.string()
|
|
2468
|
+
})) });
|
|
2469
|
+
const zArchiveBulkResultDto = z.object({
|
|
2470
|
+
results: z.array(z.object({
|
|
2471
|
+
id: z.string(),
|
|
2472
|
+
entity_type: z.string(),
|
|
2473
|
+
ok: z.boolean(),
|
|
2474
|
+
error: z.string().optional()
|
|
2475
|
+
})),
|
|
2476
|
+
succeeded: z.number(),
|
|
2477
|
+
failed: z.number()
|
|
2478
|
+
});
|
|
2433
2479
|
const zArchiveFindAllV1Data = z.object({
|
|
2434
2480
|
query: z.object({
|
|
2435
2481
|
"mode": z.enum(["archived", "deleted"]),
|
|
2436
2482
|
"page": z.number().optional(),
|
|
2437
|
-
"limit": z.number().optional()
|
|
2483
|
+
"limit": z.number().optional(),
|
|
2484
|
+
"sortBy": z.enum([
|
|
2485
|
+
"entity_type",
|
|
2486
|
+
"display_name",
|
|
2487
|
+
"archived_at",
|
|
2488
|
+
"deleted_at",
|
|
2489
|
+
"created_at",
|
|
2490
|
+
"created_by",
|
|
2491
|
+
"deleted_by"
|
|
2492
|
+
]).optional(),
|
|
2493
|
+
"sortDir": z.enum(["asc", "desc"]).optional(),
|
|
2494
|
+
"entityType": z.array(z.string()).optional(),
|
|
2495
|
+
"ownerId": z.array(z.string()).optional(),
|
|
2496
|
+
"deletedById": z.array(z.string()).optional()
|
|
2438
2497
|
}).optional(),
|
|
2439
2498
|
url: z.literal("/v1/archives")
|
|
2440
2499
|
});
|
|
2500
|
+
const zArchiveFacetsV1Data = z.object({
|
|
2501
|
+
query: z.object({ "mode": z.enum(["archived", "deleted"]) }).optional(),
|
|
2502
|
+
url: z.literal("/v1/archives/facets")
|
|
2503
|
+
});
|
|
2441
2504
|
const zArchiveArchiveV1Data = z.object({
|
|
2442
2505
|
body: zArchiveActionDto,
|
|
2443
2506
|
url: z.literal("/v1/archives/archive")
|
|
@@ -2450,6 +2513,14 @@ const zArchiveUnarchiveV1Data = z.object({
|
|
|
2450
2513
|
body: zArchiveActionDto,
|
|
2451
2514
|
url: z.literal("/v1/archives/unarchive")
|
|
2452
2515
|
});
|
|
2516
|
+
const zArchiveRestoreBulkV1Data = z.object({
|
|
2517
|
+
body: zArchiveBulkActionDto,
|
|
2518
|
+
url: z.literal("/v1/archives/restore-bulk")
|
|
2519
|
+
});
|
|
2520
|
+
const zArchiveUnarchiveBulkV1Data = z.object({
|
|
2521
|
+
body: zArchiveBulkActionDto,
|
|
2522
|
+
url: z.literal("/v1/archives/unarchive-bulk")
|
|
2523
|
+
});
|
|
2453
2524
|
|
|
2454
2525
|
//#endregion
|
|
2455
2526
|
//#region ../sdk/dist/gen/artifact-sync/zod.js
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./env-
|
|
2
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
3
|
-
import { saveWorkspace } from "./workspace-store-
|
|
4
|
-
import "./token-refresh-
|
|
5
|
-
import { resolveToken } from "./resolve-token-
|
|
1
|
+
import "./env-CHeKHu5S.js";
|
|
2
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import { saveWorkspace } from "./workspace-store-DDOxnut1.js";
|
|
4
|
+
import "./token-refresh-Cu5RpkLJ.js";
|
|
5
|
+
import { resolveToken } from "./resolve-token-DbQsmn03.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/selectHandler.ts
|
|
8
8
|
async function selectHandler(_flags, workspaceId) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getApiUrl, getPatToken, readCredentials } from "./env-
|
|
2
|
-
import { authGetProfileV1, configureClient } from "./sdk-client-
|
|
3
|
-
import { isTokenExpired } from "./token-refresh-
|
|
1
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-CHeKHu5S.js";
|
|
2
|
+
import { authGetProfileV1, configureClient } from "./sdk-client-DP7uCfXP.js";
|
|
3
|
+
import { isTokenExpired } from "./token-refresh-Cu5RpkLJ.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
|
|
6
6
|
//#region src/commands/auth/statusHandler.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-
|
|
1
|
+
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-CHeKHu5S.js";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/token-refresh.ts
|
|
4
4
|
const REFRESH_BUFFER_MS = 300 * 1e3;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
4
|
|
|
5
5
|
//#region src/workspace/workspace-store.ts
|
|
6
6
|
const WORKSPACE_PATH = join(join(homedir(), ".config", "levr"), "workspace.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@levr-one/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "The command-line interface for Levr",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@clack/prompts": "^1.7.0",
|
|
17
|
-
"@stricli/auto-complete": "^1.2.0",
|
|
18
17
|
"@stricli/core": "^1.2.0",
|
|
19
18
|
"chalk": "^5.6.2",
|
|
20
19
|
"jsonc-parser": "^3.3.1",
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
"eslint-config-prettier": "^10.0.1",
|
|
30
29
|
"eslint-plugin-prettier": "^5.2.2",
|
|
31
30
|
"globals": "^16.0.0",
|
|
32
|
-
"npm-run-all": "^4.1.5",
|
|
33
31
|
"prettier": "^3.4.2",
|
|
34
32
|
"rimraf": "^6.0.1",
|
|
35
33
|
"tsdown": "^0.15.6",
|