@levr-one/cli 0.1.0 → 0.2.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
@@ -12,7 +12,19 @@ npm install -g @levr-one/cli # global install for daily use
12
12
  npx @levr-one/cli --help
13
13
  ```
14
14
 
15
- The package is self-contained — no peer setup required.
15
+ The package is self-contained — no peer setup required. A global install
16
+ replaces the `levr` bin from the deprecated `@levr-one/setup` package.
17
+
18
+ ## Get started
19
+
20
+ ```bash
21
+ npx @levr-one/cli init # authenticate and list your workspaces
22
+ ```
23
+
24
+ `levr init` is the first-run onboarding command: it logs you in (browser
25
+ PKCE by default, `--device-code` for SSH/headless) and lists the workspaces
26
+ you can push to. Re-running it reuses your stored session — it never
27
+ re-opens the browser unnecessarily.
16
28
 
17
29
  ### Shell completion (optional)
18
30
 
@@ -149,13 +161,13 @@ withEnv(["LEVR_TOKEN=${LEVR_TOKEN}"]) {
149
161
 
150
162
  All configuration is via environment variables. Flags take precedence.
151
163
 
152
- | Variable | Description | Default |
153
- | --------------- | ---------------------------------------------------------------------------------------- | ----------------------- |
154
- | `LEVR_TOKEN` | Personal Access Token (for CI / headless) | |
155
- | `LEVR_URL` | API base URL | `https://api.levr.one` |
156
- | `LEVR_AUTH_URL` | Auth server URL | `https://auth.levr.one` |
157
- | `LEVR_TEAM_ID` | Default team ID (optional; server resolves from automation source or workspace default) | |
158
- | `LEVR_SOURCE` | Automation source name override (groups imports, remembers team) | |
164
+ | Variable | Description | Default |
165
+ | --------------- | --------------------------------------------------------------------------------------- | ---------------------- |
166
+ | `LEVR_TOKEN` | Personal Access Token (for CI / headless) | |
167
+ | `LEVR_URL` | API base URL (`--url` flag > `LEVR_URL` > URL stored at login > default) | `https://api.levr.one` |
168
+ | `LEVR_AUTH_URL` | Auth server URL for the browser login page (derived from the API URL when unset) | derived |
169
+ | `LEVR_TEAM_ID` | Default team ID (optional; server resolves from automation source or workspace default) | |
170
+ | `LEVR_SOURCE` | Automation source name override (groups imports, remembers team) | |
159
171
 
160
172
  ## Troubleshooting
161
173
 
package/dist/cli.js CHANGED
@@ -1,15 +1,438 @@
1
1
  #!/usr/bin/env node
2
- import { app, buildContext } from "./app-0F0Zyyt7.js";
3
- import { run } from "@stricli/core";
2
+ 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
+ import chalk from "chalk";
7
+ import { buildInstallCommand, buildUninstallCommand } from "@stricli/auto-complete";
4
8
 
9
+ //#region src/utils/logger.ts
10
+ var Logger = class {
11
+ verbose;
12
+ stdout;
13
+ stderr;
14
+ constructor(options) {
15
+ this.verbose = options.verbose ?? false;
16
+ this.stdout = options.stdout ?? process.stdout;
17
+ this.stderr = options.stderr ?? process.stderr;
18
+ }
19
+ info(message) {
20
+ this.stdout.write(`${chalk.blue("info")} ${message}\n`);
21
+ }
22
+ success(message) {
23
+ this.stdout.write(`${chalk.green("ok")} ${message}\n`);
24
+ }
25
+ error(message) {
26
+ this.stderr.write(`${chalk.red("error")} ${message}\n`);
27
+ }
28
+ warning(message) {
29
+ this.stdout.write(`${chalk.yellow("warn")} ${message}\n`);
30
+ }
31
+ debug(message) {
32
+ if (this.verbose) this.stdout.write(`${chalk.gray("debug")} ${message}\n`);
33
+ }
34
+ setVerbose(verbose) {
35
+ this.verbose = verbose;
36
+ }
37
+ };
38
+
39
+ //#endregion
40
+ //#region src/context.ts
41
+ function buildContext(process$1) {
42
+ return {
43
+ process: process$1,
44
+ os,
45
+ fs,
46
+ path,
47
+ logger: new Logger({
48
+ verbose: false,
49
+ stdout: process$1.stdout,
50
+ stderr: process$1.stderr
51
+ })
52
+ };
53
+ }
54
+
55
+ //#endregion
56
+ //#region src/commands/init.ts
57
+ const initCommand = buildCommand({
58
+ docs: {
59
+ brief: "First-run setup: authenticate and list your workspaces",
60
+ fullDescription: `Set up the Levr CLI: authenticate (if needed) and list your workspaces.
61
+
62
+ Reuses an existing session when one is stored — running init again never
63
+ re-opens the browser unnecessarily. For headless environments (SSH,
64
+ containers), use --device-code.
65
+
66
+ Examples:
67
+ npx @levr-one/cli init # First-run onboarding
68
+ levr init --device-code # Headless/SSH onboarding
69
+ levr init --url <api-url> # Target a non-default API server`
70
+ },
71
+ parameters: {
72
+ flags: {
73
+ "device-code": {
74
+ kind: "boolean",
75
+ default: false,
76
+ brief: "Use device code flow (for SSH/headless environments)"
77
+ },
78
+ url: {
79
+ kind: "parsed",
80
+ parse: String,
81
+ brief: "API base URL (default: https://api.levr.one)",
82
+ placeholder: "url",
83
+ optional: true
84
+ }
85
+ },
86
+ aliases: { d: "device-code" }
87
+ },
88
+ loader: async () => {
89
+ const { initHandler } = await import("./initHandler-JN92u8td.js");
90
+ return initHandler;
91
+ }
92
+ });
93
+
94
+ //#endregion
95
+ //#region src/commands/auth/login.ts
96
+ const loginCommand = buildCommand({
97
+ docs: {
98
+ brief: "Authenticate with Levr",
99
+ fullDescription: `Authenticate with Levr using OAuth.
100
+
101
+ By default, opens a browser for PKCE-based authentication.
102
+ For headless environments (SSH, containers), use --device-code
103
+ to authenticate via a code displayed in the terminal.
104
+
105
+ Examples:
106
+ levr auth login # Browser-based PKCE login
107
+ levr auth login --device-code # Device flow for SSH/headless`
108
+ },
109
+ parameters: {
110
+ flags: {
111
+ "device-code": {
112
+ kind: "boolean",
113
+ default: false,
114
+ brief: "Use device code flow (for SSH/headless environments)"
115
+ },
116
+ url: {
117
+ kind: "parsed",
118
+ parse: String,
119
+ brief: "API base URL (default: https://api.levr.one)",
120
+ placeholder: "url",
121
+ optional: true
122
+ }
123
+ },
124
+ aliases: { d: "device-code" }
125
+ },
126
+ loader: async () => {
127
+ const { loginHandler } = await import("./loginHandler-uZCrwdcR.js");
128
+ return loginHandler;
129
+ }
130
+ });
131
+
132
+ //#endregion
133
+ //#region src/commands/auth/logout.ts
134
+ const logoutCommand = buildCommand({
135
+ docs: {
136
+ brief: "Log out of Levr",
137
+ fullDescription: `Remove stored credentials.
138
+
139
+ Note: If using LEVR_TOKEN environment variable, it will remain set.
140
+
141
+ Examples:
142
+ levr auth logout`
143
+ },
144
+ parameters: {},
145
+ loader: async () => {
146
+ const { logoutHandler } = await import("./logoutHandler-jWPGhzls.js");
147
+ return logoutHandler;
148
+ }
149
+ });
150
+
151
+ //#endregion
152
+ //#region src/commands/auth/status.ts
153
+ const statusCommand = buildCommand({
154
+ docs: {
155
+ brief: "Check authentication status",
156
+ fullDescription: `Check the current authentication status.
157
+
158
+ Shows whether you are authenticated, the auth method (PAT or JWT),
159
+ and tests API reachability.
160
+
161
+ Examples:
162
+ levr auth status`
163
+ },
164
+ parameters: {},
165
+ loader: async () => {
166
+ const { statusHandler } = await import("./statusHandler-D6YAxhoN.js");
167
+ return statusHandler;
168
+ }
169
+ });
170
+
171
+ //#endregion
172
+ //#region src/commands/push.ts
173
+ const pushCommand = buildCommand({
174
+ docs: {
175
+ brief: "Push test results to Levr",
176
+ fullDescription: `Upload a test result file to Levr.
177
+
178
+ The backend auto-detects the file format (JUnit XML, Gherkin, Cucumber JSON).
179
+ In CI environments, the automation source name and CI metadata are auto-detected.
180
+
181
+ Team ID is optional. When omitted, the server resolves the team from:
182
+ 1. The existing automation source's team (if --source matches a known source)
183
+ 2. The workspace's default team
184
+
185
+ Examples:
186
+ levr push ./test-results.xml
187
+ levr push ./results.xml --source "backend-unit-tests"
188
+ levr push ./report.json --team-id <uuid> # explicit team
189
+ levr push ./report.json # uses LEVR_TOKEN env var, default team`
190
+ },
191
+ parameters: {
192
+ positional: {
193
+ kind: "tuple",
194
+ parameters: [{
195
+ parse: String,
196
+ brief: "Path to test result file (.xml, .feature, .json)",
197
+ placeholder: "file",
198
+ optional: false
199
+ }]
200
+ },
201
+ flags: {
202
+ "workspace-id": {
203
+ kind: "parsed",
204
+ parse: String,
205
+ brief: "Workspace ID (required for multi-workspace JWT auth)",
206
+ placeholder: "uuid",
207
+ optional: true
208
+ },
209
+ "team-id": {
210
+ kind: "parsed",
211
+ parse: String,
212
+ brief: "Team ID (optional; server resolves default if omitted)",
213
+ placeholder: "uuid",
214
+ optional: true
215
+ },
216
+ source: {
217
+ kind: "parsed",
218
+ parse: String,
219
+ brief: "Automation source name (auto-detected in CI)",
220
+ placeholder: "name",
221
+ optional: true
222
+ },
223
+ "automation-source": {
224
+ kind: "parsed",
225
+ parse: String,
226
+ brief: "Automation source UUID. When set, routes to POST /v1/automation-run/ingest (synchronous, bypasses ImportJob queue) instead of POST /v1/imports.",
227
+ placeholder: "uuid",
228
+ optional: true
229
+ },
230
+ "run-name": {
231
+ kind: "parsed",
232
+ parse: String,
233
+ brief: "Name for the test run",
234
+ placeholder: "name",
235
+ optional: true
236
+ },
237
+ format: {
238
+ kind: "enum",
239
+ values: [
240
+ "junit",
241
+ "gherkin",
242
+ "cucumber-json"
243
+ ],
244
+ brief: "File format (auto-detected if omitted)",
245
+ optional: true
246
+ },
247
+ "parent-folder-id": {
248
+ kind: "parsed",
249
+ parse: String,
250
+ brief: "Destination folder ID",
251
+ placeholder: "uuid",
252
+ optional: true
253
+ },
254
+ "update-mode": {
255
+ kind: "enum",
256
+ values: ["update", "create_new"],
257
+ default: "update",
258
+ brief: "How to handle existing tests"
259
+ },
260
+ verbose: {
261
+ kind: "boolean",
262
+ default: false,
263
+ brief: "Show detailed output"
264
+ }
265
+ },
266
+ aliases: {
267
+ w: "workspace-id",
268
+ t: "team-id",
269
+ s: "source",
270
+ a: "automation-source",
271
+ r: "run-name",
272
+ f: "format",
273
+ v: "verbose"
274
+ }
275
+ },
276
+ loader: async () => {
277
+ const { pushHandler } = await import("./pushHandler-DG96IOVd.js");
278
+ return pushHandler;
279
+ }
280
+ });
281
+
282
+ //#endregion
283
+ //#region src/commands/workspace/list.ts
284
+ const listCommand = buildCommand({
285
+ docs: {
286
+ brief: "List available workspaces",
287
+ fullDescription: `List all workspaces you have access to.
288
+
289
+ The current workspace (if selected) is marked with an asterisk (*).
290
+
291
+ Requires JWT authentication (levr auth login).
292
+
293
+ Examples:
294
+ levr workspace list`
295
+ },
296
+ parameters: {},
297
+ loader: async () => {
298
+ const { listHandler } = await import("./listHandler-5L3YE52T.js");
299
+ return listHandler;
300
+ }
301
+ });
302
+
303
+ //#endregion
304
+ //#region src/commands/workspace/select.ts
305
+ const selectCommand = buildCommand({
306
+ docs: {
307
+ brief: "Select a workspace",
308
+ fullDescription: `Select a workspace by ID.
309
+
310
+ The selected workspace is used for all subsequent commands.
311
+ Use 'levr workspace list' to see available workspaces.
312
+
313
+ Requires JWT authentication (levr auth login).
314
+
315
+ Examples:
316
+ levr workspace select <workspace-id>`
317
+ },
318
+ parameters: {
319
+ positional: {
320
+ kind: "tuple",
321
+ parameters: [{
322
+ parse: String,
323
+ brief: "Workspace ID",
324
+ placeholder: "workspace-id",
325
+ optional: false
326
+ }]
327
+ },
328
+ flags: {}
329
+ },
330
+ loader: async () => {
331
+ const { selectHandler } = await import("./selectHandler-CkIbO2qH.js");
332
+ return selectHandler;
333
+ }
334
+ });
335
+
336
+ //#endregion
337
+ //#region src/commands/workspace/current.ts
338
+ const currentCommand = buildCommand({
339
+ docs: {
340
+ brief: "Show current workspace",
341
+ fullDescription: `Show the currently selected workspace.
342
+
343
+ Examples:
344
+ levr workspace current`
345
+ },
346
+ parameters: {},
347
+ loader: async () => {
348
+ const { currentHandler } = await import("./currentHandler-Dzg819Np.js");
349
+ return currentHandler;
350
+ }
351
+ });
352
+
353
+ //#endregion
354
+ //#region package.json
355
+ var version = "0.2.0";
356
+
357
+ //#endregion
358
+ //#region src/app.ts
359
+ const routes = buildRouteMap({
360
+ routes: {
361
+ init: initCommand,
362
+ auth: buildRouteMap({
363
+ routes: {
364
+ login: loginCommand,
365
+ logout: logoutCommand,
366
+ status: statusCommand
367
+ },
368
+ docs: { brief: "Manage authentication" }
369
+ }),
370
+ workspace: buildRouteMap({
371
+ routes: {
372
+ list: listCommand,
373
+ select: selectCommand,
374
+ current: currentCommand
375
+ },
376
+ docs: { brief: "Manage workspace selection" }
377
+ }),
378
+ push: pushCommand,
379
+ install: buildInstallCommand("levr", { bash: "levr __complete" }),
380
+ uninstall: buildUninstallCommand("levr", { bash: true })
381
+ },
382
+ docs: {
383
+ brief: "The command-line interface for Levr",
384
+ hideRoute: {
385
+ install: true,
386
+ uninstall: true
387
+ }
388
+ }
389
+ });
390
+ const app = buildApplication(routes, {
391
+ name: "levr",
392
+ versionInfo: { currentVersion: version },
393
+ localization: { loadText: () => ({
394
+ ...text_en,
395
+ exceptionWhileParsingArguments: (exc, ansiColor) => {
396
+ const base = text_en.exceptionWhileParsingArguments(exc, ansiColor);
397
+ const hint = "Run `levr <command> --help` for usage information.";
398
+ return ansiColor ? `${base}\n\x1b[2m${hint}\x1b[22m` : `${base}\n${hint}`;
399
+ }
400
+ }) }
401
+ });
402
+
403
+ //#endregion
404
+ //#region src/completion.ts
405
+ /**
406
+ * Compute shell tab-completion suggestions for the hidden `__complete` entrypoint
407
+ * (see src/bin/cli.ts). `levr install` registers a bash function that invokes
408
+ * `levr __complete <COMP_LINE>` on each TAB, so `rawArgs` (process.argv.slice(2))
409
+ * is `['__complete', <targetCommandName>, ...wordsBeingCompleted]`. A COMP_LINE
410
+ * ending in a space means the cursor is on a fresh (empty) word to complete.
411
+ *
412
+ * Never throws — completion must not surface an error to the user's shell.
413
+ */
414
+ async function proposeCompletionLines(rawArgs, compLine, context) {
415
+ const inputs = rawArgs.slice(2);
416
+ if (compLine?.endsWith(" ")) inputs.push("");
417
+ try {
418
+ return (await proposeCompletions(app, inputs, context)).map(({ completion }) => completion);
419
+ } catch {
420
+ return [];
421
+ }
422
+ }
423
+
424
+ //#endregion
5
425
  //#region src/bin/cli.ts
6
- let savedExitCode;
7
- const processProxy = new Proxy(process, { set(target, prop, value) {
8
- if (prop === "exitCode" && typeof value === "number" && value !== 0) savedExitCode = value;
9
- return Reflect.set(target, prop, value);
10
- } });
11
- await run(app, process.argv.slice(2), buildContext(processProxy));
12
- if (savedExitCode !== void 0) process.exitCode = savedExitCode;
426
+ const argv = process.argv.slice(2);
427
+ if (argv[0] === "__complete") for (const line of await proposeCompletionLines(argv, process.env["COMP_LINE"], buildContext(process))) process.stdout.write(`${line}\n`);
428
+ else {
429
+ let savedExitCode;
430
+ await run(app, argv, buildContext(new Proxy(process, { set(target, prop, value) {
431
+ if (prop === "exitCode" && typeof value === "number" && value !== 0) savedExitCode = value;
432
+ return Reflect.set(target, prop, value);
433
+ } })));
434
+ if (savedExitCode !== void 0) process.exitCode = savedExitCode;
435
+ }
13
436
 
14
437
  //#endregion
15
438
  export { };
@@ -0,0 +1,102 @@
1
+ import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ //#region src/auth/credentials.ts
6
+ const CREDENTIALS_PATH = join(join(homedir(), ".config", "levr"), "credentials.json");
7
+ function readCredentials() {
8
+ try {
9
+ const raw = readFileSync(CREDENTIALS_PATH, "utf8");
10
+ return JSON.parse(raw);
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function writeCredentials(creds) {
16
+ mkdirSync(dirname(CREDENTIALS_PATH), { recursive: true });
17
+ writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
18
+ }
19
+ function deleteCredentials() {
20
+ try {
21
+ unlinkSync(CREDENTIALS_PATH);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/utils/env.ts
30
+ const DEFAULT_API_URL = "https://api.levr.one";
31
+ const KNOWN_AUTH_HOSTS = {
32
+ "api.levr.one": "auth.levr.one",
33
+ "api.levr.one": "auth.levr.one"
34
+ };
35
+ /** OAuth client ID for the CLI (seeded in oauth_clients table) */
36
+ const CLI_CLIENT_ID = "3";
37
+ let sessionApiUrl;
38
+ function setSessionApiUrl(url) {
39
+ sessionApiUrl = normalizeUrl(url);
40
+ }
41
+ function normalizeUrl(url) {
42
+ return url.replace(/\/+$/, "");
43
+ }
44
+ /**
45
+ * Resolve the API base URL: `--url` flag > `LEVR_URL` env var > the
46
+ * `api_url` stored in credentials at login > production default.
47
+ *
48
+ * The stored-credentials fallback keeps every later command (push, refresh,
49
+ * workspace list) pointed at the environment the user actually logged into,
50
+ * instead of silently falling back to production when the env var is no
51
+ * longer exported in the current shell.
52
+ */
53
+ function getApiUrl() {
54
+ if (sessionApiUrl) return sessionApiUrl;
55
+ const env = process.env["LEVR_URL"];
56
+ if (env) return normalizeUrl(env);
57
+ if (!getPatToken()) {
58
+ const stored = readCredentials()?.api_url;
59
+ if (stored) return normalizeUrl(stored);
60
+ }
61
+ return DEFAULT_API_URL;
62
+ }
63
+ /**
64
+ * Resolve the auth-server base URL (browser PKCE consent page only):
65
+ * `LEVR_AUTH_URL` env var > derived from the resolved API URL for known
66
+ * Levr hosts. Unrecognized hosts (e.g. localhost dev stacks) require the
67
+ * explicit env var — throwing beats silently opening the production login
68
+ * page for a token exchange that can never succeed.
69
+ */
70
+ function getAuthUrl() {
71
+ const env = process.env["LEVR_AUTH_URL"];
72
+ if (env) return normalizeUrl(env);
73
+ const apiUrl = getApiUrl();
74
+ const derived = deriveAuthUrl(apiUrl);
75
+ if (derived) return derived;
76
+ throw new Error(`Cannot derive the auth server URL from API URL "${apiUrl}". Set LEVR_AUTH_URL to the auth server base URL (e.g. https://auth.levr.one).`);
77
+ }
78
+ function deriveAuthUrl(apiUrl) {
79
+ let parsed;
80
+ try {
81
+ parsed = new URL(apiUrl);
82
+ } catch {
83
+ return;
84
+ }
85
+ const authHost = KNOWN_AUTH_HOSTS[parsed.host];
86
+ return authHost ? `${parsed.protocol}//${authHost}` : void 0;
87
+ }
88
+ function getTeamId(flagValue) {
89
+ return flagValue || process.env["LEVR_TEAM_ID"] || void 0;
90
+ }
91
+ function getPatToken() {
92
+ return process.env["LEVR_TOKEN"];
93
+ }
94
+ function getSourceOverride() {
95
+ return process.env["LEVR_SOURCE"];
96
+ }
97
+ function getAutomationSourceIdOverride() {
98
+ return process.env["LEVR_AUTOMATION_SOURCE_ID"];
99
+ }
100
+
101
+ //#endregion
102
+ export { CLI_CLIENT_ID, deleteCredentials, getApiUrl, getAuthUrl, getAutomationSourceIdOverride, getPatToken, getSourceOverride, getTeamId, readCredentials, setSessionApiUrl, writeCredentials };
@@ -0,0 +1,51 @@
1
+ import { performLogin } from "./loginHandler-BSrs0mHq.js";
2
+ import { setSessionApiUrl } from "./env-hpzB56ay.js";
3
+ import { authGetSitesV1, configureClient } from "./sdk-client-BCOB2qNU.js";
4
+ import "./workspace-store-BcyMJAht.js";
5
+ import "./resolve-workspace-EpjKI71z.js";
6
+ import "./token-refresh-BYu4XO3G.js";
7
+ import { CredentialsMismatchError, resolveToken } from "./resolve-token-DizP2xRY.js";
8
+ import { printSites } from "./listHandler-DDd6scZ8.js";
9
+ import chalk from "chalk";
10
+
11
+ //#region src/commands/initHandler.ts
12
+ /**
13
+ * First-run onboarding: authenticate if needed, then list workspaces.
14
+ * Composes the existing login flow (loginHandler's performLogin) with the
15
+ * workspace listing (listHandler's printSites) — absorbs the retired
16
+ * @levr-one/setup flow (ENG-2361).
17
+ */
18
+ async function initHandler(flags) {
19
+ if (flags.url) setSessionApiUrl(flags.url);
20
+ let auth;
21
+ try {
22
+ auth = await resolveToken();
23
+ this.logger.success("Already authenticated.");
24
+ } catch (resolveError) {
25
+ if (resolveError instanceof CredentialsMismatchError) this.logger.warning(resolveError.message);
26
+ if (!await performLogin(this, { deviceCode: flags["device-code"] })) return;
27
+ try {
28
+ auth = await resolveToken();
29
+ } catch (error) {
30
+ this.logger.error(error instanceof Error ? error.message : "Authentication failed.");
31
+ this.process.exitCode = 1;
32
+ return;
33
+ }
34
+ }
35
+ if (auth.type === "pat") {
36
+ this.logger.info("Authenticated via LEVR_TOKEN (PAT). Workspace listing requires JWT auth — run 'levr auth login' to browse workspaces.");
37
+ return;
38
+ }
39
+ configureClient(auth);
40
+ const result = await authGetSitesV1();
41
+ if (result.error) {
42
+ this.logger.error("Failed to list workspaces.");
43
+ this.process.exitCode = 1;
44
+ return;
45
+ }
46
+ printSites(this, result.data.sites);
47
+ this.process.stdout.write(`Next: ${chalk.cyan("'levr workspace select <id>'")} to pick a workspace, then ${chalk.cyan("'levr push <file>'")} to upload results.\n`);
48
+ }
49
+
50
+ //#endregion
51
+ export { initHandler };
@@ -0,0 +1,8 @@
1
+ import "./env-hpzB56ay.js";
2
+ import "./sdk-client-BCOB2qNU.js";
3
+ import "./workspace-store-BcyMJAht.js";
4
+ import "./token-refresh-BYu4XO3G.js";
5
+ import "./resolve-token-DizP2xRY.js";
6
+ import { listHandler, printSites } from "./listHandler-DDd6scZ8.js";
7
+
8
+ export { listHandler };