@mutagent/cli 0.2.53 → 0.2.54

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/dist/bin/cli.js CHANGED
@@ -893,6 +893,35 @@ var init_config = __esm(() => {
893
893
  CREDENTIALS_FILE = join2(CREDENTIALS_DIR, "credentials.json");
894
894
  });
895
895
 
896
+ // src/lib/tenancy-error.ts
897
+ function shellQuote(value) {
898
+ if (/^[A-Za-z0-9_-]+$/.test(value))
899
+ return value;
900
+ return `'${value.replace(/'/g, `'\\''`)}'`;
901
+ }
902
+ function workspaceDenied(ctx) {
903
+ const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
904
+ const own = ctx.keyScope === "workspace" ? ctx.keyWorkspace : undefined;
905
+ if (ctx.workspaceOrigin === "selection" && own && own.id !== ctx.workspaceId) {
906
+ return new TenancyError(TENANCY_DENIED, `This API key only works in workspace ${own.name}; your saved selection is ${shown}.`, `Run: mutagent workspaces use ${shellQuote(own.name)}`);
907
+ }
908
+ if (ctx.workspaceOrigin === "selection") {
909
+ return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
910
+ }
911
+ const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
912
+ return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
913
+ }
914
+ var TENANCY_DENIED = "TENANCY_DENIED", TenancyError;
915
+ var init_tenancy_error = __esm(() => {
916
+ init_errors();
917
+ TenancyError = class TenancyError extends MutagentError {
918
+ constructor(code, what, fix) {
919
+ super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""));
920
+ this.name = "TenancyError";
921
+ }
922
+ };
923
+ });
924
+
896
925
  // src/lib/organization-scope.ts
897
926
  function looksLikeWorkspaceId(value) {
898
927
  return UUID_PATTERN.test(value);
@@ -924,14 +953,6 @@ function parseBody(body) {
924
953
  function deadKeyError() {
925
954
  return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login");
926
955
  }
927
- function workspaceDenied(ctx) {
928
- const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
929
- if (ctx.workspaceOrigin === "selection") {
930
- return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
931
- }
932
- const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
933
- return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
934
- }
935
956
  function tenancyFailure(status, body, ctx) {
936
957
  const parsed = parseBody(body);
937
958
  if (status === 401)
@@ -997,11 +1018,6 @@ function matchesOrganization(value, org) {
997
1018
  return true;
998
1019
  return org.name !== undefined && value.toLowerCase() === org.name.toLowerCase();
999
1020
  }
1000
- function shellQuote(value) {
1001
- if (/^[A-Za-z0-9_-]+$/.test(value))
1002
- return value;
1003
- return `'${value.replace(/'/g, `'\\''`)}'`;
1004
- }
1005
1021
  function orgMismatch(value, org) {
1006
1022
  const shown = org.slug ? `${org.name} (${org.slug})` : org.name;
1007
1023
  return new TenancyError("ORG_MISMATCH", `Your key is for ${shown}.`, `Run: mutagent login --org ${shellQuote(value)}`);
@@ -1148,17 +1164,12 @@ async function findWorkspace(wanted, t) {
1148
1164
  const orgs = await fetchOrganizations(t.apiKey, t.endpoint);
1149
1165
  throw otherOrganizationHint(wanted, t.organization, orgs);
1150
1166
  }
1151
- var TENANCY_DENIED = "TENANCY_DENIED", UUID_PATTERN, TenancyError, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag, keySelfProbes, keyScopeConfirmations;
1167
+ var UUID_PATTERN, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag, keySelfProbes, keyScopeConfirmations;
1152
1168
  var init_organization_scope = __esm(() => {
1153
1169
  init_errors();
1154
1170
  init_config();
1171
+ init_tenancy_error();
1155
1172
  UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1156
- TenancyError = class TenancyError extends MutagentError {
1157
- constructor(code, what, fix) {
1158
- super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""));
1159
- this.name = "TenancyError";
1160
- }
1161
- };
1162
1173
  keySelfProbes = new Map;
1163
1174
  keyScopeConfirmations = new Map;
1164
1175
  });
@@ -1481,6 +1492,38 @@ function buildCreateProviderBody(data) {
1481
1492
  function isOrgKeyWithoutWorkspace(status, body) {
1482
1493
  return status === 400 && body.toLowerCase().includes(ORG_KEY_NEEDS_WORKSPACE);
1483
1494
  }
1495
+ function codeOf(body) {
1496
+ try {
1497
+ return JSON.parse(body)?.code;
1498
+ } catch {
1499
+ return;
1500
+ }
1501
+ }
1502
+ async function readKeySelf(key, fetchImpl) {
1503
+ try {
1504
+ const response = await fetchImpl(`${key.endpoint}/api/api-keys/self`, { headers: { "x-api-key": key.apiKey } });
1505
+ const self = response.ok ? await response.json() : undefined;
1506
+ return self?.scope === "workspace" || self?.scope === "organization" || self?.scope === "user" ? self : undefined;
1507
+ } catch {
1508
+ return;
1509
+ }
1510
+ }
1511
+ async function explainSelectionDenial(status, body, ctx, key, fetchImpl = fetch) {
1512
+ if (status !== 403 || ctx.workspaceOrigin !== "selection" || ctx.keyScope !== "workspace" || ctx.keyWorkspace)
1513
+ return;
1514
+ if (codeOf(body) !== TENANCY_DENIED)
1515
+ return;
1516
+ const self = await readKeySelf(key, fetchImpl);
1517
+ if (!self)
1518
+ return;
1519
+ ctx.keyScope = self.scope;
1520
+ if (self.scope === "workspace") {
1521
+ ctx.keyWorkspace = self.workspace ?? undefined;
1522
+ return;
1523
+ }
1524
+ if (!envKeyReplacesFile())
1525
+ recordKeyScope(self);
1526
+ }
1484
1527
  function sameHeaders(a, b) {
1485
1528
  const keys = Object.keys(a);
1486
1529
  return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
@@ -1488,6 +1531,11 @@ function sameHeaders(a, b) {
1488
1531
  async function fetchWithScopeHeal(input, init, target) {
1489
1532
  const spare = input instanceof Request ? input.clone() : undefined;
1490
1533
  const response = await (init == null ? fetch(input) : fetch(input, init));
1534
+ if (response.status === 403 && target.tenancy) {
1535
+ const denied = await response.clone().text().catch(() => "");
1536
+ await explainSelectionDenial(403, denied, target.tenancy, target);
1537
+ return response;
1538
+ }
1491
1539
  if (response.status !== 400)
1492
1540
  return response;
1493
1541
  const body = await response.clone().text().catch(() => "");
@@ -1514,7 +1562,9 @@ async function fetchWithScopeHeal(input, init, target) {
1514
1562
  }
1515
1563
  var ORG_KEY_NEEDS_WORKSPACE = "x-workspace-id header required";
1516
1564
  var init_key_scope_heal = __esm(() => {
1565
+ init_config();
1517
1566
  init_organization_scope();
1567
+ init_tenancy_error();
1518
1568
  });
1519
1569
 
1520
1570
  // src/lib/sdk-mappers.ts
@@ -1714,9 +1764,8 @@ class SDKClientWrapper {
1714
1764
  const tenancy = tenancyFailure(response.status, error, this.tenancy);
1715
1765
  if (tenancy)
1716
1766
  throw tenancy;
1717
- if (response.status === 401) {
1767
+ if (response.status === 401)
1718
1768
  throw new AuthenticationError;
1719
- }
1720
1769
  if (response.status === 403) {
1721
1770
  throw new ApiError(403, error || "Access denied");
1722
1771
  }
@@ -1887,6 +1936,7 @@ class SDKClientWrapper {
1887
1936
  apiKey: this.apiKey,
1888
1937
  endpoint: this.endpoint,
1889
1938
  headers: () => this.tenancyHeaders(),
1939
+ tenancy: this.tenancy,
1890
1940
  adopt: (self) => {
1891
1941
  this.tenancy.keyScope = self.scope;
1892
1942
  if (!this.workspaceId && self.scope === "workspace" && self.workspace)
@@ -27316,6 +27366,7 @@ init_secure_file();
27316
27366
  init_errors();
27317
27367
  init_known_secrets();
27318
27368
  init_organization_scope();
27369
+ init_key_scope_heal();
27319
27370
  import { existsSync as existsSync15, readFileSync as readFileSync16, mkdirSync as mkdirSync5, rmSync as rmSync2 } from "fs";
27320
27371
  import { homedir as homedir6 } from "os";
27321
27372
  import { join as join21, dirname as dirname8 } from "path";
@@ -27401,6 +27452,7 @@ async function exchangeToken(input, deps = {}) {
27401
27452
  if (response.status === 403) {
27402
27453
  const context = { ...input.tenancy, workspaceId: input.workspaceId };
27403
27454
  const body = await response.text().catch(() => "");
27455
+ await explainSelectionDenial(403, body, context, input, doFetch);
27404
27456
  throw tenancyFailure(403, body, context) ?? workspaceDenied(context);
27405
27457
  }
27406
27458
  if (!response.ok) {
@@ -38163,4 +38215,4 @@ if (process.env.CLI_VERSION) {
38163
38215
  }
38164
38216
  runCli(process.argv.slice(2), cliVersion);
38165
38217
 
38166
- //# debugId=F12EBD8D0A6ACFED64756E2164756E21
38218
+ //# debugId=C914B921636AC8AB64756E2164756E21
package/dist/index.js CHANGED
@@ -1043,6 +1043,35 @@ var init_key_check = __esm(() => {
1043
1043
  };
1044
1044
  });
1045
1045
 
1046
+ // src/lib/tenancy-error.ts
1047
+ function shellQuote(value) {
1048
+ if (/^[A-Za-z0-9_-]+$/.test(value))
1049
+ return value;
1050
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1051
+ }
1052
+ function workspaceDenied(ctx) {
1053
+ const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
1054
+ const own = ctx.keyScope === "workspace" ? ctx.keyWorkspace : undefined;
1055
+ if (ctx.workspaceOrigin === "selection" && own && own.id !== ctx.workspaceId) {
1056
+ return new TenancyError(TENANCY_DENIED, `This API key only works in workspace ${own.name}; your saved selection is ${shown}.`, `Run: mutagent workspaces use ${shellQuote(own.name)}`);
1057
+ }
1058
+ if (ctx.workspaceOrigin === "selection") {
1059
+ return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
1060
+ }
1061
+ const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
1062
+ return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
1063
+ }
1064
+ var TENANCY_DENIED = "TENANCY_DENIED", TenancyError;
1065
+ var init_tenancy_error = __esm(() => {
1066
+ init_errors();
1067
+ TenancyError = class TenancyError extends MutagentError {
1068
+ constructor(code, what, fix) {
1069
+ super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""));
1070
+ this.name = "TenancyError";
1071
+ }
1072
+ };
1073
+ });
1074
+
1046
1075
  // src/lib/organization-scope.ts
1047
1076
  function looksLikeWorkspaceId(value) {
1048
1077
  return UUID_PATTERN.test(value);
@@ -1074,14 +1103,6 @@ function parseBody(body) {
1074
1103
  function deadKeyError() {
1075
1104
  return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login");
1076
1105
  }
1077
- function workspaceDenied(ctx) {
1078
- const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
1079
- if (ctx.workspaceOrigin === "selection") {
1080
- return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
1081
- }
1082
- const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
1083
- return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
1084
- }
1085
1106
  function tenancyFailure(status, body, ctx) {
1086
1107
  const parsed = parseBody(body);
1087
1108
  if (status === 401)
@@ -1147,11 +1168,6 @@ function matchesOrganization(value, org) {
1147
1168
  return true;
1148
1169
  return org.name !== undefined && value.toLowerCase() === org.name.toLowerCase();
1149
1170
  }
1150
- function shellQuote(value) {
1151
- if (/^[A-Za-z0-9_-]+$/.test(value))
1152
- return value;
1153
- return `'${value.replace(/'/g, `'\\''`)}'`;
1154
- }
1155
1171
  function orgMismatch(value, org) {
1156
1172
  const shown = org.slug ? `${org.name} (${org.slug})` : org.name;
1157
1173
  return new TenancyError("ORG_MISMATCH", `Your key is for ${shown}.`, `Run: mutagent login --org ${shellQuote(value)}`);
@@ -1298,17 +1314,12 @@ async function findWorkspace(wanted, t) {
1298
1314
  const orgs = await fetchOrganizations(t.apiKey, t.endpoint);
1299
1315
  throw otherOrganizationHint(wanted, t.organization, orgs);
1300
1316
  }
1301
- var TENANCY_DENIED = "TENANCY_DENIED", UUID_PATTERN, TenancyError, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag, keySelfProbes, keyScopeConfirmations;
1317
+ var UUID_PATTERN, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag, keySelfProbes, keyScopeConfirmations;
1302
1318
  var init_organization_scope = __esm(() => {
1303
1319
  init_errors();
1304
1320
  init_config();
1321
+ init_tenancy_error();
1305
1322
  UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1306
- TenancyError = class TenancyError extends MutagentError {
1307
- constructor(code, what, fix) {
1308
- super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""));
1309
- this.name = "TenancyError";
1310
- }
1311
- };
1312
1323
  keySelfProbes = new Map;
1313
1324
  keyScopeConfirmations = new Map;
1314
1325
  });
@@ -1434,6 +1445,38 @@ var init_sdk_debug = __esm(() => {
1434
1445
  function isOrgKeyWithoutWorkspace(status, body) {
1435
1446
  return status === 400 && body.toLowerCase().includes(ORG_KEY_NEEDS_WORKSPACE);
1436
1447
  }
1448
+ function codeOf(body) {
1449
+ try {
1450
+ return JSON.parse(body)?.code;
1451
+ } catch {
1452
+ return;
1453
+ }
1454
+ }
1455
+ async function readKeySelf(key, fetchImpl) {
1456
+ try {
1457
+ const response = await fetchImpl(`${key.endpoint}/api/api-keys/self`, { headers: { "x-api-key": key.apiKey } });
1458
+ const self = response.ok ? await response.json() : undefined;
1459
+ return self?.scope === "workspace" || self?.scope === "organization" || self?.scope === "user" ? self : undefined;
1460
+ } catch {
1461
+ return;
1462
+ }
1463
+ }
1464
+ async function explainSelectionDenial(status, body, ctx, key, fetchImpl = fetch) {
1465
+ if (status !== 403 || ctx.workspaceOrigin !== "selection" || ctx.keyScope !== "workspace" || ctx.keyWorkspace)
1466
+ return;
1467
+ if (codeOf(body) !== TENANCY_DENIED)
1468
+ return;
1469
+ const self = await readKeySelf(key, fetchImpl);
1470
+ if (!self)
1471
+ return;
1472
+ ctx.keyScope = self.scope;
1473
+ if (self.scope === "workspace") {
1474
+ ctx.keyWorkspace = self.workspace ?? undefined;
1475
+ return;
1476
+ }
1477
+ if (!envKeyReplacesFile())
1478
+ recordKeyScope(self);
1479
+ }
1437
1480
  function sameHeaders(a, b) {
1438
1481
  const keys = Object.keys(a);
1439
1482
  return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
@@ -1441,6 +1484,11 @@ function sameHeaders(a, b) {
1441
1484
  async function fetchWithScopeHeal(input, init, target) {
1442
1485
  const spare = input instanceof Request ? input.clone() : undefined;
1443
1486
  const response = await (init == null ? fetch(input) : fetch(input, init));
1487
+ if (response.status === 403 && target.tenancy) {
1488
+ const denied = await response.clone().text().catch(() => "");
1489
+ await explainSelectionDenial(403, denied, target.tenancy, target);
1490
+ return response;
1491
+ }
1444
1492
  if (response.status !== 400)
1445
1493
  return response;
1446
1494
  const body = await response.clone().text().catch(() => "");
@@ -1467,7 +1515,9 @@ async function fetchWithScopeHeal(input, init, target) {
1467
1515
  }
1468
1516
  var ORG_KEY_NEEDS_WORKSPACE = "x-workspace-id header required";
1469
1517
  var init_key_scope_heal = __esm(() => {
1518
+ init_config();
1470
1519
  init_organization_scope();
1520
+ init_tenancy_error();
1471
1521
  });
1472
1522
 
1473
1523
  // src/lib/sdk-mappers.ts
@@ -1667,9 +1717,8 @@ class SDKClientWrapper {
1667
1717
  const tenancy = tenancyFailure(response.status, error, this.tenancy);
1668
1718
  if (tenancy)
1669
1719
  throw tenancy;
1670
- if (response.status === 401) {
1720
+ if (response.status === 401)
1671
1721
  throw new AuthenticationError;
1672
- }
1673
1722
  if (response.status === 403) {
1674
1723
  throw new ApiError(403, error || "Access denied");
1675
1724
  }
@@ -1840,6 +1889,7 @@ class SDKClientWrapper {
1840
1889
  apiKey: this.apiKey,
1841
1890
  endpoint: this.endpoint,
1842
1891
  headers: () => this.tenancyHeaders(),
1892
+ tenancy: this.tenancy,
1843
1893
  adopt: (self) => {
1844
1894
  this.tenancy.keyScope = self.scope;
1845
1895
  if (!this.workspaceId && self.scope === "workspace" && self.workspace)
@@ -1989,4 +2039,4 @@ export {
1989
2039
  version
1990
2040
  };
1991
2041
 
1992
- //# debugId=CB6D96307760100964756E2164756E21
2042
+ //# debugId=B6CD43FAA918848864756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutagent/cli",
3
- "version": "0.2.53",
3
+ "version": "0.2.54",
4
4
  "description": "Bun-native CLI for the MutagenT AI platform - auth, provider config, and lifecycle-tool installer for AI-native workflows",
5
5
  "type": "module",
6
6
  "bin": {