@levr-one/cli 0.12.9 → 0.12.11

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.
@@ -1,7 +1,7 @@
1
1
  import { configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import { getApiUrl } from "./env-Cv-anxJA.js";
4
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
4
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
5
5
  import "./workspace-store-BJyB_WEG.js";
6
6
  import { fetchSites } from "./resolve-workspace-CJN1plpd.js";
7
7
  import { describeWorkspaces, findWorkspaceByKeyOrName, isScopedMcpUrl, resolveMcpUrl, scopedMcpUrl, servedBySessionApi } from "./find-workspace-IWolSyrf.js";
@@ -1,10 +1,10 @@
1
1
  import { appendActualResult } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import "./env-Cv-anxJA.js";
4
- import "./resolve-token-D1zteEfm.js";
4
+ import "./resolve-token-BeX-Xegl.js";
5
5
  import "./workspace-store-BJyB_WEG.js";
6
6
  import "./resolve-workspace-CJN1plpd.js";
7
- import { connect } from "./connect-A5VSJUv5.js";
7
+ import { connect } from "./connect-B_Bva8BM.js";
8
8
  import "./sleep-DNLj0FMk.js";
9
9
  import { embedMarkdown, parseTarget, uploadArtifactFile } from "./artifact-upload-t6kL20b5.js";
10
10
  import { readFileSync } from "node:fs";
@@ -1,10 +1,10 @@
1
1
  import { appendActualResult, createComment } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import "./env-Cv-anxJA.js";
4
- import "./resolve-token-D1zteEfm.js";
4
+ import "./resolve-token-BeX-Xegl.js";
5
5
  import "./workspace-store-BJyB_WEG.js";
6
6
  import "./resolve-workspace-CJN1plpd.js";
7
- import { connect } from "./connect-A5VSJUv5.js";
7
+ import { connect } from "./connect-B_Bva8BM.js";
8
8
  import "./sleep-DNLj0FMk.js";
9
9
  import { TargetParseError, embedMarkdown, parseTarget, uploadArtifactFile } from "./artifact-upload-t6kL20b5.js";
10
10
  import { readFileSync } from "node:fs";
@@ -1,8 +1,8 @@
1
1
  import "./gate-op-BINrIT2l.js";
2
2
  import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
3
- import "./token-refresh-DEmYbJ8V.js";
3
+ import "./token-refresh-cvAydkok.js";
4
4
  import "./env-Cv-anxJA.js";
5
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
5
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
6
6
  import "./workspace-store-BJyB_WEG.js";
7
7
  import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
8
8
  import { z } from "zod";
package/dist/cli.js CHANGED
@@ -226,7 +226,7 @@ Examples:
226
226
  aliases: { y: "yes" }
227
227
  },
228
228
  loader: async () => {
229
- const { mcpAddHandler } = await import("./addHandler-CD1pda0z.js");
229
+ const { mcpAddHandler } = await import("./addHandler-CJOweVZf.js");
230
230
  return mcpAddHandler;
231
231
  }
232
232
  });
@@ -314,7 +314,7 @@ Examples:
314
314
  },
315
315
  parameters: {},
316
316
  loader: async () => {
317
- const { statusHandler } = await import("./statusHandler-ByTUMdKK.js");
317
+ const { statusHandler } = await import("./statusHandler-BgQTktJm.js");
318
318
  return statusHandler;
319
319
  }
320
320
  });
@@ -443,7 +443,7 @@ levr attach --manifest or later uploads.`
443
443
  }
444
444
  },
445
445
  loader: async () => {
446
- const { pushHandler } = await import("./pushHandler-BkCmzQz7.js");
446
+ const { pushHandler } = await import("./pushHandler-D02XXrQZ.js");
447
447
  return pushHandler;
448
448
  }
449
449
  });
@@ -563,7 +563,7 @@ Examples:
563
563
  }
564
564
  },
565
565
  loader: async () => {
566
- const { importHandler } = await import("./importHandler-86cjc2Zs.js");
566
+ const { importHandler } = await import("./importHandler-t4UNZ-tS.js");
567
567
  return importHandler;
568
568
  }
569
569
  });
@@ -584,7 +584,7 @@ Examples:
584
584
  },
585
585
  parameters: {},
586
586
  loader: async () => {
587
- const { listHandler } = await import("./listHandler-BoCzGUDK.js");
587
+ const { listHandler } = await import("./listHandler-D-W39_YC.js");
588
588
  return listHandler;
589
589
  }
590
590
  });
@@ -622,7 +622,7 @@ Examples:
622
622
  flags: {}
623
623
  },
624
624
  loader: async () => {
625
- const { selectHandler } = await import("./selectHandler-Dp74pefc.js");
625
+ const { selectHandler } = await import("./selectHandler-9pNaEvpf.js");
626
626
  return selectHandler;
627
627
  }
628
628
  });
@@ -688,7 +688,7 @@ Examples:
688
688
  }
689
689
  },
690
690
  loader: async () => {
691
- const { gatesBindingsExportHandler } = await import("./bindingsHandler-Dxx2LIci.js");
691
+ const { gatesBindingsExportHandler } = await import("./bindingsHandler-BR_vk1eC.js");
692
692
  return gatesBindingsExportHandler;
693
693
  }
694
694
  });
@@ -761,7 +761,7 @@ Examples:
761
761
  aliases: { w: "workspace-id" }
762
762
  },
763
763
  loader: async () => {
764
- const { gatesBindingsImportHandler } = await import("./bindingsHandler-Dxx2LIci.js");
764
+ const { gatesBindingsImportHandler } = await import("./bindingsHandler-BR_vk1eC.js");
765
765
  return gatesBindingsImportHandler;
766
766
  }
767
767
  });
@@ -1052,7 +1052,7 @@ Examples:
1052
1052
  }
1053
1053
  },
1054
1054
  loader: async () => {
1055
- const { gatesRunHandler } = await import("./runHandler-BLkPzImG.js");
1055
+ const { gatesRunHandler } = await import("./runHandler-DMJO--tc.js");
1056
1056
  return gatesRunHandler;
1057
1057
  }
1058
1058
  });
@@ -1165,7 +1165,7 @@ Examples:
1165
1165
  }
1166
1166
  },
1167
1167
  loader: async () => {
1168
- const { attachHandler } = await import("./attachHandler-Ojk7mc2E.js");
1168
+ const { attachHandler } = await import("./attachHandler-DFlDUObZ.js");
1169
1169
  return attachHandler;
1170
1170
  }
1171
1171
  });
@@ -1241,14 +1241,14 @@ Examples:
1241
1241
  }
1242
1242
  },
1243
1243
  loader: async () => {
1244
- const { resultAppendHandler } = await import("./appendHandler-BYOoCNXJ.js");
1244
+ const { resultAppendHandler } = await import("./appendHandler-mYa2KKNC.js");
1245
1245
  return resultAppendHandler;
1246
1246
  }
1247
1247
  });
1248
1248
 
1249
1249
  //#endregion
1250
1250
  //#region package.json
1251
- var version = "0.12.9";
1251
+ var version = "0.12.11";
1252
1252
 
1253
1253
  //#endregion
1254
1254
  //#region src/app.ts
@@ -1,5 +1,5 @@
1
1
  import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
2
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
3
3
  import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
4
4
 
5
5
  //#region src/utils/connect.ts
@@ -1,7 +1,7 @@
1
1
  import { authGetProfileV1, client, configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import { getApiUrl, getTeamId } from "./env-Cv-anxJA.js";
4
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
4
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
5
5
  import "./workspace-store-BJyB_WEG.js";
6
6
  import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
7
7
  import chalk from "chalk";
@@ -1,7 +1,7 @@
1
1
  import { authGetSitesV1, configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import "./env-Cv-anxJA.js";
4
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
4
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
5
5
  import { loadWorkspace } from "./workspace-store-BJyB_WEG.js";
6
6
 
7
7
  //#region src/commands/workspace/listHandler.ts
@@ -1,7 +1,7 @@
1
1
  import { client, configureClient, uploadImport } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-Cv-anxJA.js";
4
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
4
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
5
5
  import "./workspace-store-BJyB_WEG.js";
6
6
  import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
7
7
  import "./sleep-DNLj0FMk.js";
@@ -1,4 +1,4 @@
1
- import { classifyToken, isTokenExpired, refreshToken } from "./token-refresh-DEmYbJ8V.js";
1
+ import { classifyToken, isTokenExpired, refreshToken } from "./token-refresh-cvAydkok.js";
2
2
  import { getApiUrl, getPatToken, readCredentials } from "./env-Cv-anxJA.js";
3
3
 
4
4
  //#region src/auth/resolve-token.ts
@@ -1,9 +1,9 @@
1
1
  import "./gate-op-BINrIT2l.js";
2
2
  import { executeInspect } from "./inspect-CinRBSv6.js";
3
3
  import { client, configureClient } from "./sdk-client-CSy2LOEh.js";
4
- import "./token-refresh-DEmYbJ8V.js";
4
+ import "./token-refresh-cvAydkok.js";
5
5
  import "./env-Cv-anxJA.js";
6
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
6
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
7
7
  import "./workspace-store-BJyB_WEG.js";
8
8
  import { resolveWorkspaceFor } from "./resolve-workspace-CJN1plpd.js";
9
9
  import { existsSync } from "node:fs";
@@ -1,7 +1,7 @@
1
1
  import { authGetSitesV1, configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import "./token-refresh-DEmYbJ8V.js";
2
+ import "./token-refresh-cvAydkok.js";
3
3
  import "./env-Cv-anxJA.js";
4
- import { resolveToken } from "./resolve-token-D1zteEfm.js";
4
+ import { resolveToken } from "./resolve-token-BeX-Xegl.js";
5
5
  import { saveWorkspace } from "./workspace-store-BJyB_WEG.js";
6
6
 
7
7
  //#region src/commands/workspace/selectHandler.ts
@@ -9,8 +9,8 @@ async function selectHandler(_flags, workspaceId) {
9
9
  let auth;
10
10
  try {
11
11
  auth = await resolveToken();
12
- } catch {
13
- this.logger.error("Not authenticated. Run 'levr auth login' first.");
12
+ } catch (err) {
13
+ this.logger.error(err instanceof Error ? err.message : "Not authenticated. Run 'levr auth login' first.");
14
14
  this.process.exitCode = 1;
15
15
  return;
16
16
  }
@@ -1,5 +1,5 @@
1
1
  import { authGetProfileV1, configureClient } from "./sdk-client-CSy2LOEh.js";
2
- import { classifyToken, isTokenExpired } from "./token-refresh-DEmYbJ8V.js";
2
+ import { classifyToken, isTokenExpired } from "./token-refresh-cvAydkok.js";
3
3
  import { getApiUrl, getPatToken, readCredentials } from "./env-Cv-anxJA.js";
4
4
  import chalk from "chalk";
5
5
 
@@ -0,0 +1,116 @@
1
+ import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, readCredentials, writeCredentials } from "./env-Cv-anxJA.js";
2
+
3
+ //#region src/auth/token-shape.ts
4
+ /**
5
+ * Classify a bearer token by its SHAPE — [issue:ENG-6068].
6
+ *
7
+ * The server decides how to verify a token by looking at the token, not at
8
+ * where it came from (`isPersonalAccessToken` in the backend's auth
9
+ * middleware): a Levr personal access token starts with `tq_`, and anything
10
+ * else is handed to the JWT verifier. `LEVR_TOKEN` carries either kind — a
11
+ * PAT minted in Settings, or an OAuth access token a device-code login
12
+ * printed for CI — so the CLI must apply the same rule. Until ENG-6068 every
13
+ * `LEVR_TOKEN` was called a PAT, and the JWT-only steps (resolving
14
+ * `--workspace-id` / `LEVR_WORKSPACE_ID`, `levr workspace list|select`) were
15
+ * silently skipped for an OAuth token, which has no workspace of its own.
16
+ *
17
+ * Leaf module on purpose: no imports, so `resolve-token`, the handlers and
18
+ * their tests can all read it without pulling in credentials or the SDK.
19
+ */
20
+ const PAT_PREFIX = "tq_";
21
+ /** `tq_…` is a personal access token; everything else is treated as a JWT. */
22
+ function classifyToken(token) {
23
+ return token.startsWith(PAT_PREFIX) ? "pat" : "jwt";
24
+ }
25
+
26
+ //#endregion
27
+ //#region src/auth/token-refresh.ts
28
+ const REFRESH_BUFFER_MS = 300 * 1e3;
29
+ /**
30
+ * Check if stored JWT credentials are expired (or within 5 min of expiry).
31
+ */
32
+ function isTokenExpired(creds) {
33
+ const expiresAt = new Date(creds.expires_at).getTime();
34
+ return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
35
+ }
36
+ /**
37
+ * Refresh JWT credentials using the stored refresh token.
38
+ *
39
+ * Returns the updated credentials, or `null` only when the server REFUSED
40
+ * the refresh token itself (400/401 `invalid_grant`): those credentials are
41
+ * dead and are deleted, and the caller asks for `levr auth login`. Every
42
+ * other failure (a 5xx, a 429, another error code, a proxy's HTML page, an
43
+ * older backend's 400, a network error, an unusable success body) THROWS and
44
+ * keeps the stored credentials, so the next command retries: deleting them
45
+ * signed the user out on any backend hiccup (ENG-7829). Same rule as the web
46
+ * client (`apps/client/app/services/tokenRefresh.ts`), including its
47
+ * compare-before-clear: a refusal of a token another process already
48
+ * rotated returns the newer stored pair.
49
+ */
50
+ async function refreshToken(creds) {
51
+ const apiUrl = getApiUrl();
52
+ let res;
53
+ try {
54
+ res = await fetch(`${apiUrl}/v1/oauth/token`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
57
+ body: new URLSearchParams({
58
+ grant_type: "refresh_token",
59
+ refresh_token: creds.refresh_token,
60
+ client_id: CLI_CLIENT_ID
61
+ })
62
+ });
63
+ } catch (error) {
64
+ throw transientFailure(`network error: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ if (!res.ok) {
67
+ if (await isRefusal(res)) {
68
+ const stored = readCredentials();
69
+ if (stored && stored.refresh_token !== creds.refresh_token) return sameServer(stored, creds.api_url) ? stored : null;
70
+ deleteCredentials();
71
+ return null;
72
+ }
73
+ throw transientFailure(`HTTP ${res.status}`);
74
+ }
75
+ const data = await readTokenResponse(res);
76
+ if (!data) throw transientFailure("unusable response");
77
+ const updated = {
78
+ ...creds,
79
+ access_token: data.access_token,
80
+ refresh_token: data.refresh_token,
81
+ expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
82
+ };
83
+ writeCredentials(updated);
84
+ return updated;
85
+ }
86
+ function sameServer(stored, apiUrl) {
87
+ return stored.api_url.replace(/\/+$/, "") === apiUrl.replace(/\/+$/, "");
88
+ }
89
+ function transientFailure(reason) {
90
+ return /* @__PURE__ */ new Error(`Token refresh failed (${reason}). Your login is kept; try again, or run 'levr auth login' if it keeps failing.`);
91
+ }
92
+ /** The server refused the refresh token itself: a 400/401 `invalid_grant`. */
93
+ async function isRefusal(res) {
94
+ if (res.status !== 400 && res.status !== 401) return false;
95
+ try {
96
+ return (await res.json())?.error === "invalid_grant";
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ /**
102
+ * The token response, or `null` when it is not one (a captive portal's 200,
103
+ * a body missing a token): writing it would store a login that cannot be
104
+ * refreshed.
105
+ */
106
+ async function readTokenResponse(res) {
107
+ try {
108
+ const data = await res.json();
109
+ return typeof data?.access_token === "string" && data.access_token !== "" && typeof data.refresh_token === "string" && data.refresh_token !== "" && typeof data.expires_in === "number" && Number.isFinite(data.expires_in) ? data : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ //#endregion
116
+ export { classifyToken, isTokenExpired, refreshToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levr-one/cli",
3
- "version": "0.12.9",
3
+ "version": "0.12.11",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,72 +0,0 @@
1
- import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-Cv-anxJA.js";
2
-
3
- //#region src/auth/token-shape.ts
4
- /**
5
- * Classify a bearer token by its SHAPE — [issue:ENG-6068].
6
- *
7
- * The server decides how to verify a token by looking at the token, not at
8
- * where it came from (`isPersonalAccessToken` in the backend's auth
9
- * middleware): a Levr personal access token starts with `tq_`, and anything
10
- * else is handed to the JWT verifier. `LEVR_TOKEN` carries either kind — a
11
- * PAT minted in Settings, or an OAuth access token a device-code login
12
- * printed for CI — so the CLI must apply the same rule. Until ENG-6068 every
13
- * `LEVR_TOKEN` was called a PAT, and the JWT-only steps (resolving
14
- * `--workspace-id` / `LEVR_WORKSPACE_ID`, `levr workspace list|select`) were
15
- * silently skipped for an OAuth token, which has no workspace of its own.
16
- *
17
- * Leaf module on purpose: no imports, so `resolve-token`, the handlers and
18
- * their tests can all read it without pulling in credentials or the SDK.
19
- */
20
- const PAT_PREFIX = "tq_";
21
- /** `tq_…` is a personal access token; everything else is treated as a JWT. */
22
- function classifyToken(token) {
23
- return token.startsWith(PAT_PREFIX) ? "pat" : "jwt";
24
- }
25
-
26
- //#endregion
27
- //#region src/auth/token-refresh.ts
28
- const REFRESH_BUFFER_MS = 300 * 1e3;
29
- /**
30
- * Check if stored JWT credentials are expired (or within 5 min of expiry).
31
- */
32
- function isTokenExpired(creds) {
33
- const expiresAt = new Date(creds.expires_at).getTime();
34
- return Date.now() >= expiresAt - REFRESH_BUFFER_MS;
35
- }
36
- /**
37
- * Refresh JWT credentials using the stored refresh token.
38
- * Returns updated credentials or null if refresh failed.
39
- */
40
- async function refreshToken(creds) {
41
- const apiUrl = getApiUrl();
42
- try {
43
- const res = await fetch(`${apiUrl}/v1/oauth/token`, {
44
- method: "POST",
45
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
46
- body: new URLSearchParams({
47
- grant_type: "refresh_token",
48
- refresh_token: creds.refresh_token,
49
- client_id: CLI_CLIENT_ID
50
- })
51
- });
52
- if (!res.ok) {
53
- deleteCredentials();
54
- return null;
55
- }
56
- const data = await res.json();
57
- const updated = {
58
- ...creds,
59
- access_token: data.access_token,
60
- refresh_token: data.refresh_token,
61
- expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
62
- };
63
- writeCredentials(updated);
64
- return updated;
65
- } catch {
66
- deleteCredentials();
67
- return null;
68
- }
69
- }
70
-
71
- //#endregion
72
- export { classifyToken, isTokenExpired, refreshToken };