@levr-one/cli 0.7.0 → 0.7.2

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.
@@ -1044,6 +1044,11 @@ const KNOWN_MCP_URLS = {
1044
1044
  * deployments) derives `<api-url>/v1/mcp`.
1045
1045
  */
1046
1046
  function resolveMcpUrl(flagUrl) {
1047
+ const resolved = resolveRaw(flagUrl);
1048
+ assertUsableMcpUrl(resolved.url, resolved.source);
1049
+ return resolved;
1050
+ }
1051
+ function resolveRaw(flagUrl) {
1047
1052
  if (flagUrl) return {
1048
1053
  url: stripSlash(flagUrl),
1049
1054
  source: "flag"
@@ -1059,6 +1064,41 @@ function resolveMcpUrl(flagUrl) {
1059
1064
  source: `derived:${apiUrl}`
1060
1065
  };
1061
1066
  }
1067
+ /** Name the input the user has to change, not our internal source tag. */
1068
+ function sourceLabel(source) {
1069
+ if (source === "flag") return "--url";
1070
+ if (source === "env:LEVR_MCP_URL") return "LEVR_MCP_URL";
1071
+ return `the API URL (${source.slice(8)})`;
1072
+ }
1073
+ /**
1074
+ * Reject a URL that must not reach a client config or a spawned CLI (ENG-4159).
1075
+ *
1076
+ * Three things this stops:
1077
+ *
1078
+ * 1. **No scheme.** `claude mcp add --transport http --scope user levr <value>`
1079
+ * is spawned with fixed argv since ENG-4156, so a value like `--foo` is read
1080
+ * by that CLI as a FLAG rather than as its URL operand — argument injection.
1081
+ * Requiring a parseable absolute URL removes the shape entirely.
1082
+ * 2. **A non-http(s) scheme.** `file:` / `javascript:` is never a valid MCP
1083
+ * endpoint, but would be written verbatim into every client's config.
1084
+ * 3. **Embedded credentials.** `https://user:pass@host` would be written into
1085
+ * a config file — and since ENG-4151 a config file can be `--scope project`,
1086
+ * which the user is told to COMMIT. That turns a bad URL into a secret in
1087
+ * git history. MCP authenticates via OAuth in the browser; credentials in
1088
+ * the URL are never needed.
1089
+ */
1090
+ function assertUsableMcpUrl(url, source) {
1091
+ const from = sourceLabel(source);
1092
+ let parsed;
1093
+ try {
1094
+ parsed = new URL(url);
1095
+ } catch {
1096
+ throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} is not a URL. Expected an absolute http(s) URL, e.g. https://ai.levr.one/api/v1/mcp`);
1097
+ }
1098
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} uses "${parsed.protocol}" — only http and https are supported.`);
1099
+ if (!parsed.hostname) throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} has no host.`);
1100
+ if (parsed.username || parsed.password) throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} embeds credentials. They would be written into client config files — including project-scoped ones you are told to commit. Levr authenticates in the browser; remove the user:password@ prefix.`);
1101
+ }
1062
1102
  function knownMcpUrl(apiUrl) {
1063
1103
  try {
1064
1104
  return KNOWN_MCP_URLS[new URL(apiUrl).host];
@@ -1268,7 +1308,15 @@ const defaultDeps = {
1268
1308
  install: defaultInstall
1269
1309
  };
1270
1310
  async function mcpAddHandler(flags) {
1271
- const { url, source } = resolveMcpUrl(flags.url);
1311
+ let url;
1312
+ let source;
1313
+ try {
1314
+ ({url, source} = resolveMcpUrl(flags.url));
1315
+ } catch (err) {
1316
+ this.logger.error(err instanceof Error ? err.message : "Could not resolve the MCP URL.");
1317
+ this.process.exitCode = 1;
1318
+ return;
1319
+ }
1272
1320
  const clients = (flags.client ?? []).flatMap((c) => c.split(",").map((s) => s.trim()).filter(Boolean));
1273
1321
  const options = {
1274
1322
  all: flags.all,
package/dist/cli.js CHANGED
@@ -172,7 +172,7 @@ Examples:
172
172
  aliases: { y: "yes" }
173
173
  },
174
174
  loader: async () => {
175
- const { mcpAddHandler } = await import("./addHandler-7QMKiy7o.js");
175
+ const { mcpAddHandler } = await import("./addHandler-Brr4ndcR.js");
176
176
  return mcpAddHandler;
177
177
  }
178
178
  });
@@ -210,7 +210,7 @@ Examples:
210
210
  aliases: { d: "device-code" }
211
211
  },
212
212
  loader: async () => {
213
- const { loginHandler } = await import("./loginHandler-BE8MQq83.js");
213
+ const { loginHandler } = await import("./loginHandler-BNmD3R0O.js");
214
214
  return loginHandler;
215
215
  }
216
216
  });
@@ -249,7 +249,7 @@ Examples:
249
249
  },
250
250
  parameters: {},
251
251
  loader: async () => {
252
- const { statusHandler } = await import("./statusHandler-C2_XOUal.js");
252
+ const { statusHandler } = await import("./statusHandler-ChymW9I9.js");
253
253
  return statusHandler;
254
254
  }
255
255
  });
@@ -361,7 +361,7 @@ Examples:
361
361
  }
362
362
  },
363
363
  loader: async () => {
364
- const { pushHandler } = await import("./pushHandler-Bf1F1Ltk.js");
364
+ const { pushHandler } = await import("./pushHandler-B0s2pHMU.js");
365
365
  return pushHandler;
366
366
  }
367
367
  });
@@ -473,7 +473,7 @@ Examples:
473
473
  }
474
474
  },
475
475
  loader: async () => {
476
- const { importHandler } = await import("./importHandler-D43Q8Jey.js");
476
+ const { importHandler } = await import("./importHandler-DhF7psRj.js");
477
477
  return importHandler;
478
478
  }
479
479
  });
@@ -494,7 +494,7 @@ Examples:
494
494
  },
495
495
  parameters: {},
496
496
  loader: async () => {
497
- const { listHandler } = await import("./listHandler-jEyTQhBk.js");
497
+ const { listHandler } = await import("./listHandler-hpPpE3WN.js");
498
498
  return listHandler;
499
499
  }
500
500
  });
@@ -527,7 +527,7 @@ Examples:
527
527
  flags: {}
528
528
  },
529
529
  loader: async () => {
530
- const { selectHandler } = await import("./selectHandler-KNiDUd9H.js");
530
+ const { selectHandler } = await import("./selectHandler-D4dL2zUJ.js");
531
531
  return selectHandler;
532
532
  }
533
533
  });
@@ -551,7 +551,7 @@ Examples:
551
551
 
552
552
  //#endregion
553
553
  //#region package.json
554
- var version = "0.7.0";
554
+ var version = "0.7.2";
555
555
 
556
556
  //#endregion
557
557
  //#region src/app.ts
@@ -1,7 +1,7 @@
1
1
  import { getApiUrl } from "./env-CHeKHu5S.js";
2
- import { configureClient, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-DKifoBwD.js";
2
+ import { configureClient, testCaseImportCommitV1, testCaseImportPreviewV1 } from "./sdk-client-CMgf3JjV.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { resolveWorkspace } from "./resolve-workspace-DfEX0LDh.js";
4
+ import { resolveWorkspace } from "./resolve-workspace-CpmC9eWS.js";
5
5
  import "./token-refresh-Cu5RpkLJ.js";
6
6
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
7
7
  import chalk from "chalk";
@@ -1,5 +1,5 @@
1
1
  import "./env-CHeKHu5S.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-DKifoBwD.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-CMgf3JjV.js";
3
3
  import { loadWorkspace } from "./workspace-store-DDOxnut1.js";
4
4
  import "./token-refresh-Cu5RpkLJ.js";
5
5
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
@@ -1,7 +1,7 @@
1
1
  import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CHeKHu5S.js";
2
- import { configureClient } from "./sdk-client-DKifoBwD.js";
2
+ import { configureClient } from "./sdk-client-CMgf3JjV.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { autoSelectWorkspace } from "./resolve-workspace-DfEX0LDh.js";
4
+ import { autoSelectWorkspace } from "./resolve-workspace-CpmC9eWS.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-CHeKHu5S.js";
2
- import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-DKifoBwD.js";
2
+ import { client, configureClient, uploadAutomationIngest, uploadImport } from "./sdk-client-CMgf3JjV.js";
3
3
  import "./workspace-store-DDOxnut1.js";
4
- import { resolveWorkspace } from "./resolve-workspace-DfEX0LDh.js";
4
+ import { resolveWorkspace } from "./resolve-workspace-CpmC9eWS.js";
5
5
  import "./token-refresh-Cu5RpkLJ.js";
6
6
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
7
7
  import { readFileSync, statSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { authGetSitesV1 } from "./sdk-client-DKifoBwD.js";
1
+ import { authGetSitesV1 } from "./sdk-client-CMgf3JjV.js";
2
2
  import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-DDOxnut1.js";
3
3
 
4
4
  //#region src/workspace/resolve-workspace.ts
@@ -49,7 +49,8 @@ const zResponseIssueDto = z.object({
49
49
  deleted_by: z.string().nullable().describe(""),
50
50
  archived_at: z.unknown().describe(""),
51
51
  archived_by: z.string().nullable().describe(""),
52
- auto_archived_at: z.unknown().describe("")
52
+ auto_archived_at: z.unknown().describe(""),
53
+ web_url: z.string().nullable().optional().describe("")
53
54
  });
54
55
  const zPaginatedMetaDocumented = z.object({
55
56
  itemsPerPage: z.number(),
@@ -2896,7 +2897,8 @@ const zResponseAutomationRunDto = z.object({
2896
2897
  deleted_at: z.unknown().describe(""),
2897
2898
  deleted_by: z.string().nullable().describe(""),
2898
2899
  archived_at: z.unknown().describe(""),
2899
- archived_by: z.string().nullable().describe("")
2900
+ archived_by: z.string().nullable().describe(""),
2901
+ web_url: z.string().nullable().optional().describe("")
2900
2902
  });
2901
2903
  const zUpdateAutomationRunDto = z.object({
2902
2904
  id: z.string().optional().describe(""),
@@ -3312,7 +3314,8 @@ const zResponseAutomationRunResultDto = z.object({
3312
3314
  deleted_at: z.unknown().describe(""),
3313
3315
  deleted_by: z.string().nullable().describe(""),
3314
3316
  archived_at: z.unknown().describe(""),
3315
- archived_by: z.string().nullable().describe("")
3317
+ archived_by: z.string().nullable().describe(""),
3318
+ web_url: z.string().nullable().optional().describe("")
3316
3319
  });
3317
3320
  const zUpdateAutomationRunResultDto = z.object({
3318
3321
  id: z.string().optional().describe(""),
@@ -9947,7 +9950,8 @@ const zResponseCycleDto = z.object({
9947
9950
  updated_by: z.string().describe(""),
9948
9951
  workspace_id: z.string().describe(""),
9949
9952
  deleted_at: z.unknown().describe(""),
9950
- deleted_by: z.string().nullable().describe("")
9953
+ deleted_by: z.string().nullable().describe(""),
9954
+ web_url: z.string().nullable().optional().describe("")
9951
9955
  });
9952
9956
  const zUpdateCycleDto = z.object({
9953
9957
  id: z.string().optional().describe(""),
@@ -14921,7 +14925,8 @@ const zResponseGithubPullRequestDto = z.object({
14921
14925
  epoch: z.number().describe(""),
14922
14926
  created_by: z.string().describe(""),
14923
14927
  updated_by: z.string().describe(""),
14924
- workspace_id: z.string().describe("")
14928
+ workspace_id: z.string().describe(""),
14929
+ web_url: z.string().nullable().optional().describe("")
14925
14930
  });
14926
14931
  const zGithubPullRequestFindAllV1Data = z.object({
14927
14932
  query: z.object({
@@ -15278,7 +15283,8 @@ const zResponseInitiativeDto = z.object({
15278
15283
  deleted_at: z.unknown().describe(""),
15279
15284
  deleted_by: z.string().nullable().describe(""),
15280
15285
  archived_at: z.unknown().describe(""),
15281
- archived_by: z.string().nullable().describe("")
15286
+ archived_by: z.string().nullable().describe(""),
15287
+ web_url: z.string().nullable().optional().describe("")
15282
15288
  });
15283
15289
  const zUpdateInitiativeDto = z.object({
15284
15290
  id: z.string().optional().describe(""),
@@ -21213,7 +21219,8 @@ const zResponseProjectDto = z.object({
21213
21219
  deleted_by: z.string().nullable().describe(""),
21214
21220
  archived_at: z.unknown().describe(""),
21215
21221
  archived_by: z.string().nullable().describe(""),
21216
- auto_archived_at: z.unknown().describe("")
21222
+ auto_archived_at: z.unknown().describe(""),
21223
+ web_url: z.string().nullable().optional().describe("")
21217
21224
  });
21218
21225
  const zUpdateProjectDto = z.object({
21219
21226
  id: z.string().optional().describe(""),
@@ -27468,7 +27475,8 @@ const zResponseTeamDto = z.object({
27468
27475
  deleted_at: z.unknown().describe(""),
27469
27476
  deleted_by: z.string().nullable().describe(""),
27470
27477
  archived_at: z.unknown().describe(""),
27471
- archived_by: z.string().nullable().describe("")
27478
+ archived_by: z.string().nullable().describe(""),
27479
+ web_url: z.string().nullable().optional().describe("")
27472
27480
  });
27473
27481
  const zUpdateTeamDto = z.object({
27474
27482
  id: z.string().optional().describe(""),
@@ -1,5 +1,5 @@
1
1
  import "./env-CHeKHu5S.js";
2
- import { authGetSitesV1, configureClient } from "./sdk-client-DKifoBwD.js";
2
+ import { authGetSitesV1, configureClient } from "./sdk-client-CMgf3JjV.js";
3
3
  import { saveWorkspace } from "./workspace-store-DDOxnut1.js";
4
4
  import "./token-refresh-Cu5RpkLJ.js";
5
5
  import { resolveToken } from "./resolve-token-DbQsmn03.js";
@@ -1,5 +1,5 @@
1
1
  import { getApiUrl, getPatToken, readCredentials } from "./env-CHeKHu5S.js";
2
- import { authGetProfileV1, configureClient } from "./sdk-client-DKifoBwD.js";
2
+ import { authGetProfileV1, configureClient } from "./sdk-client-CMgf3JjV.js";
3
3
  import { isTokenExpired } from "./token-refresh-Cu5RpkLJ.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.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",