@mutagent/cli 0.2.53 → 0.2.55
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 +146 -68
- package/dist/index.js +79 -23
- package/package.json +1 -1
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,21 +1164,101 @@ 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
|
|
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
|
});
|
|
1165
1176
|
|
|
1177
|
+
// src/lib/key-scope-heal.ts
|
|
1178
|
+
function isOrgKeyWithoutWorkspace(status, body) {
|
|
1179
|
+
return status === 400 && body.toLowerCase().includes(ORG_KEY_NEEDS_WORKSPACE);
|
|
1180
|
+
}
|
|
1181
|
+
function codeOf(body) {
|
|
1182
|
+
try {
|
|
1183
|
+
return JSON.parse(body)?.code;
|
|
1184
|
+
} catch {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
async function readKeySelf(key, fetchImpl) {
|
|
1189
|
+
try {
|
|
1190
|
+
const response = await fetchImpl(`${key.endpoint}/api/api-keys/self`, { headers: { "x-api-key": key.apiKey } });
|
|
1191
|
+
const self = response.ok ? await response.json() : undefined;
|
|
1192
|
+
return self?.scope === "workspace" || self?.scope === "organization" || self?.scope === "user" ? self : undefined;
|
|
1193
|
+
} catch {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
async function explainSelectionDenial(status, body, ctx, key, fetchImpl = fetch) {
|
|
1198
|
+
if (status !== 403 || ctx.workspaceOrigin !== "selection" || ctx.keyScope !== "workspace" || ctx.keyWorkspace)
|
|
1199
|
+
return;
|
|
1200
|
+
if (codeOf(body) !== TENANCY_DENIED)
|
|
1201
|
+
return;
|
|
1202
|
+
const self = await readKeySelf(key, fetchImpl);
|
|
1203
|
+
if (!self)
|
|
1204
|
+
return;
|
|
1205
|
+
ctx.keyScope = self.scope;
|
|
1206
|
+
if (self.scope === "workspace") {
|
|
1207
|
+
ctx.keyWorkspace = self.workspace ?? undefined;
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
if (!envKeyReplacesFile())
|
|
1211
|
+
recordKeyScope(self);
|
|
1212
|
+
}
|
|
1213
|
+
async function explainThrownDenial(error, ctx, key) {
|
|
1214
|
+
if (ctx === undefined || !(error instanceof Error) || !("statusCode" in error) || typeof error.statusCode !== "number")
|
|
1215
|
+
return;
|
|
1216
|
+
const body = "body" in error && typeof error.body === "string" ? error.body : "";
|
|
1217
|
+
await explainSelectionDenial(error.statusCode, body, ctx, key);
|
|
1218
|
+
}
|
|
1219
|
+
function sameHeaders(a, b) {
|
|
1220
|
+
const keys = Object.keys(a);
|
|
1221
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
1222
|
+
}
|
|
1223
|
+
async function fetchWithScopeHeal(input, init, target) {
|
|
1224
|
+
const spare = input instanceof Request ? input.clone() : undefined;
|
|
1225
|
+
const response = await (init == null ? fetch(input) : fetch(input, init));
|
|
1226
|
+
if (response.status === 403 && target.tenancy) {
|
|
1227
|
+
const denied = await response.clone().text().catch(() => "");
|
|
1228
|
+
await explainSelectionDenial(403, denied, target.tenancy, target);
|
|
1229
|
+
return response;
|
|
1230
|
+
}
|
|
1231
|
+
if (response.status !== 400)
|
|
1232
|
+
return response;
|
|
1233
|
+
const body = await response.clone().text().catch(() => "");
|
|
1234
|
+
if (!isOrgKeyWithoutWorkspace(response.status, body))
|
|
1235
|
+
return response;
|
|
1236
|
+
const before = target.headers();
|
|
1237
|
+
const self = await confirmKeyScope(target.apiKey, target.endpoint).catch(() => {
|
|
1238
|
+
return;
|
|
1239
|
+
});
|
|
1240
|
+
if (!self)
|
|
1241
|
+
return response;
|
|
1242
|
+
target.adopt(self);
|
|
1243
|
+
const after = target.headers();
|
|
1244
|
+
if (sameHeaders(before, after))
|
|
1245
|
+
return response;
|
|
1246
|
+
if (spare) {
|
|
1247
|
+
const headers = new Headers(spare.headers);
|
|
1248
|
+
for (const [name, value] of Object.entries(after))
|
|
1249
|
+
headers.set(name, value);
|
|
1250
|
+
return fetch(new Request(spare, { headers }));
|
|
1251
|
+
}
|
|
1252
|
+
const headers = { ...init?.headers, ...after };
|
|
1253
|
+
return fetch(input, { ...init, headers });
|
|
1254
|
+
}
|
|
1255
|
+
var ORG_KEY_NEEDS_WORKSPACE = "x-workspace-id header required";
|
|
1256
|
+
var init_key_scope_heal = __esm(() => {
|
|
1257
|
+
init_config();
|
|
1258
|
+
init_organization_scope();
|
|
1259
|
+
init_tenancy_error();
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1166
1262
|
// src/lib/sdk-debug.ts
|
|
1167
1263
|
function isDebugEnabled(env = process.env) {
|
|
1168
1264
|
return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
|
|
@@ -1477,46 +1573,6 @@ function buildCreateProviderBody(data) {
|
|
|
1477
1573
|
return body;
|
|
1478
1574
|
}
|
|
1479
1575
|
|
|
1480
|
-
// src/lib/key-scope-heal.ts
|
|
1481
|
-
function isOrgKeyWithoutWorkspace(status, body) {
|
|
1482
|
-
return status === 400 && body.toLowerCase().includes(ORG_KEY_NEEDS_WORKSPACE);
|
|
1483
|
-
}
|
|
1484
|
-
function sameHeaders(a, b) {
|
|
1485
|
-
const keys = Object.keys(a);
|
|
1486
|
-
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
1487
|
-
}
|
|
1488
|
-
async function fetchWithScopeHeal(input, init, target) {
|
|
1489
|
-
const spare = input instanceof Request ? input.clone() : undefined;
|
|
1490
|
-
const response = await (init == null ? fetch(input) : fetch(input, init));
|
|
1491
|
-
if (response.status !== 400)
|
|
1492
|
-
return response;
|
|
1493
|
-
const body = await response.clone().text().catch(() => "");
|
|
1494
|
-
if (!isOrgKeyWithoutWorkspace(response.status, body))
|
|
1495
|
-
return response;
|
|
1496
|
-
const before = target.headers();
|
|
1497
|
-
const self = await confirmKeyScope(target.apiKey, target.endpoint).catch(() => {
|
|
1498
|
-
return;
|
|
1499
|
-
});
|
|
1500
|
-
if (!self)
|
|
1501
|
-
return response;
|
|
1502
|
-
target.adopt(self);
|
|
1503
|
-
const after = target.headers();
|
|
1504
|
-
if (sameHeaders(before, after))
|
|
1505
|
-
return response;
|
|
1506
|
-
if (spare) {
|
|
1507
|
-
const headers = new Headers(spare.headers);
|
|
1508
|
-
for (const [name, value] of Object.entries(after))
|
|
1509
|
-
headers.set(name, value);
|
|
1510
|
-
return fetch(new Request(spare, { headers }));
|
|
1511
|
-
}
|
|
1512
|
-
const headers = { ...init?.headers, ...after };
|
|
1513
|
-
return fetch(input, { ...init, headers });
|
|
1514
|
-
}
|
|
1515
|
-
var ORG_KEY_NEEDS_WORKSPACE = "x-workspace-id header required";
|
|
1516
|
-
var init_key_scope_heal = __esm(() => {
|
|
1517
|
-
init_organization_scope();
|
|
1518
|
-
});
|
|
1519
|
-
|
|
1520
1576
|
// src/lib/sdk-mappers.ts
|
|
1521
1577
|
import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
|
|
1522
1578
|
function isProviderType(value) {
|
|
@@ -1714,9 +1770,8 @@ class SDKClientWrapper {
|
|
|
1714
1770
|
const tenancy = tenancyFailure(response.status, error, this.tenancy);
|
|
1715
1771
|
if (tenancy)
|
|
1716
1772
|
throw tenancy;
|
|
1717
|
-
if (response.status === 401)
|
|
1773
|
+
if (response.status === 401)
|
|
1718
1774
|
throw new AuthenticationError;
|
|
1719
|
-
}
|
|
1720
1775
|
if (response.status === 403) {
|
|
1721
1776
|
throw new ApiError(403, error || "Access denied");
|
|
1722
1777
|
}
|
|
@@ -1887,6 +1942,7 @@ class SDKClientWrapper {
|
|
|
1887
1942
|
apiKey: this.apiKey,
|
|
1888
1943
|
endpoint: this.endpoint,
|
|
1889
1944
|
headers: () => this.tenancyHeaders(),
|
|
1945
|
+
tenancy: this.tenancy,
|
|
1890
1946
|
adopt: (self) => {
|
|
1891
1947
|
this.tenancy.keyScope = self.scope;
|
|
1892
1948
|
if (!this.workspaceId && self.scope === "workspace" && self.workspace)
|
|
@@ -18030,6 +18086,7 @@ import chalk2 from "chalk";
|
|
|
18030
18086
|
// src/lib/agent/spec-api.ts
|
|
18031
18087
|
init_config();
|
|
18032
18088
|
init_errors();
|
|
18089
|
+
init_key_scope_heal();
|
|
18033
18090
|
init_known_secrets();
|
|
18034
18091
|
init_organization_scope();
|
|
18035
18092
|
init_sdk_debug();
|
|
@@ -18107,6 +18164,7 @@ async function connectAgentSpec() {
|
|
|
18107
18164
|
try {
|
|
18108
18165
|
return await fn(sdk.managedAgents);
|
|
18109
18166
|
} catch (error) {
|
|
18167
|
+
await explainThrownDenial(error, tenancy, { apiKey, endpoint: resolved.endpoint });
|
|
18110
18168
|
throw agentSpecError(error, { endpoint: resolved.endpoint, tenancy, slug });
|
|
18111
18169
|
}
|
|
18112
18170
|
}
|
|
@@ -27316,6 +27374,7 @@ init_secure_file();
|
|
|
27316
27374
|
init_errors();
|
|
27317
27375
|
init_known_secrets();
|
|
27318
27376
|
init_organization_scope();
|
|
27377
|
+
init_key_scope_heal();
|
|
27319
27378
|
import { existsSync as existsSync15, readFileSync as readFileSync16, mkdirSync as mkdirSync5, rmSync as rmSync2 } from "fs";
|
|
27320
27379
|
import { homedir as homedir6 } from "os";
|
|
27321
27380
|
import { join as join21, dirname as dirname8 } from "path";
|
|
@@ -27401,6 +27460,7 @@ async function exchangeToken(input, deps = {}) {
|
|
|
27401
27460
|
if (response.status === 403) {
|
|
27402
27461
|
const context = { ...input.tenancy, workspaceId: input.workspaceId };
|
|
27403
27462
|
const body = await response.text().catch(() => "");
|
|
27463
|
+
await explainSelectionDenial(403, body, context, input, doFetch);
|
|
27404
27464
|
throw tenancyFailure(403, body, context) ?? workspaceDenied(context);
|
|
27405
27465
|
}
|
|
27406
27466
|
if (!response.ok) {
|
|
@@ -31699,6 +31759,7 @@ import chalk42 from "chalk";
|
|
|
31699
31759
|
|
|
31700
31760
|
// src/lib/gateway-api.ts
|
|
31701
31761
|
init_config();
|
|
31762
|
+
init_key_scope_heal();
|
|
31702
31763
|
init_known_secrets();
|
|
31703
31764
|
init_organization_scope();
|
|
31704
31765
|
class GatewayApiError extends Error {
|
|
@@ -31799,6 +31860,7 @@ async function gatewayRequest(path, init = {}) {
|
|
|
31799
31860
|
if (!response.ok) {
|
|
31800
31861
|
const bodyText = await response.text().catch(() => "");
|
|
31801
31862
|
if (response.status === 403 && tenancyCodeOf(bodyText) === TENANCY_DENIED) {
|
|
31863
|
+
await explainSelectionDenial(403, bodyText, connection.tenancy, connection);
|
|
31802
31864
|
const denied = tenancyFailure(response.status, bodyText, connection.tenancy);
|
|
31803
31865
|
if (denied)
|
|
31804
31866
|
throw denied;
|
|
@@ -33666,6 +33728,7 @@ init_known_secrets();
|
|
|
33666
33728
|
|
|
33667
33729
|
// src/lib/gateway-sdk.ts
|
|
33668
33730
|
import { HTTPClient as HTTPClient4, Mutagent as Mutagent4 } from "@mutagent/sdk";
|
|
33731
|
+
init_key_scope_heal();
|
|
33669
33732
|
init_organization_scope();
|
|
33670
33733
|
init_sdk_debug();
|
|
33671
33734
|
var SDK_TRANSPORT_ERRORS3 = new Set(["ConnectionError", "RequestTimeoutError", "RequestAbortedError", "UnexpectedClientError"]);
|
|
@@ -33683,12 +33746,12 @@ function gatewaySdkError(error, connection, what) {
|
|
|
33683
33746
|
const body = "body" in error && typeof error.body === "string" ? error.body : "";
|
|
33684
33747
|
if (status === 401)
|
|
33685
33748
|
return deadKeyError();
|
|
33686
|
-
|
|
33687
|
-
if (status === 403 && parsed.code === TENANCY_DENIED) {
|
|
33749
|
+
if (status === 403 && tenancyCodeOf(body) === TENANCY_DENIED) {
|
|
33688
33750
|
const denied = tenancyFailure(status, body, connection.tenancy);
|
|
33689
33751
|
if (denied)
|
|
33690
33752
|
return denied;
|
|
33691
33753
|
}
|
|
33754
|
+
const parsed = parseGatewayErrorBody(status, body, "");
|
|
33692
33755
|
return new GatewayApiError(status, parsed.code, parsed.reason, parsed.fix);
|
|
33693
33756
|
}
|
|
33694
33757
|
return error;
|
|
@@ -33714,6 +33777,7 @@ async function callGatewaySdk(what, fn) {
|
|
|
33714
33777
|
try {
|
|
33715
33778
|
return await fn(sdk.gateway, connection);
|
|
33716
33779
|
} catch (error) {
|
|
33780
|
+
await explainThrownDenial(error, connection.tenancy, connection);
|
|
33717
33781
|
throw gatewaySdkError(error, connection, what);
|
|
33718
33782
|
}
|
|
33719
33783
|
}
|
|
@@ -34145,6 +34209,7 @@ import chalk50 from "chalk";
|
|
|
34145
34209
|
|
|
34146
34210
|
// src/lib/gateway-api-events.ts
|
|
34147
34211
|
import { GatewayStreamError, streamGatewayUpdates } from "@mutagent/sdk/custom/gateway-stream.js";
|
|
34212
|
+
init_key_scope_heal();
|
|
34148
34213
|
init_known_secrets();
|
|
34149
34214
|
init_organization_scope();
|
|
34150
34215
|
function iso3(value) {
|
|
@@ -34164,11 +34229,13 @@ async function listNotices(query) {
|
|
|
34164
34229
|
return scrubKnownSuccessDeep({ items, nextCursor: page.nextCursor });
|
|
34165
34230
|
});
|
|
34166
34231
|
}
|
|
34167
|
-
function streamError(error,
|
|
34232
|
+
async function streamError(error, connection) {
|
|
34168
34233
|
if (error.status === 401)
|
|
34169
34234
|
return deadKeyError();
|
|
34170
34235
|
if (error.status === 403 && error.code === TENANCY_DENIED) {
|
|
34171
|
-
const
|
|
34236
|
+
const body = JSON.stringify({ code: error.code });
|
|
34237
|
+
await explainSelectionDenial(403, body, connection.tenancy, connection);
|
|
34238
|
+
const denied = tenancyFailure(403, body, connection.tenancy);
|
|
34172
34239
|
if (denied)
|
|
34173
34240
|
return denied;
|
|
34174
34241
|
}
|
|
@@ -34187,7 +34254,7 @@ async function* followGateway(signal) {
|
|
|
34187
34254
|
});
|
|
34188
34255
|
} catch (error) {
|
|
34189
34256
|
if (error instanceof GatewayStreamError)
|
|
34190
|
-
throw streamError(error, connection
|
|
34257
|
+
throw await streamError(error, connection);
|
|
34191
34258
|
throw error;
|
|
34192
34259
|
}
|
|
34193
34260
|
}
|
|
@@ -34728,6 +34795,7 @@ init_output();
|
|
|
34728
34795
|
// src/lib/reports-api.ts
|
|
34729
34796
|
init_config();
|
|
34730
34797
|
init_errors();
|
|
34798
|
+
init_key_scope_heal();
|
|
34731
34799
|
init_known_secrets();
|
|
34732
34800
|
init_organization_scope();
|
|
34733
34801
|
init_sdk_debug();
|
|
@@ -34797,6 +34865,10 @@ function reportsError(error, ctx) {
|
|
|
34797
34865
|
return new MutagentError("REQUEST_FAILED", error instanceof Error ? error.message : "Request failed", "Run: mutagent reports --help");
|
|
34798
34866
|
}
|
|
34799
34867
|
function connection(sdk, ctx) {
|
|
34868
|
+
const explain = async (error) => {
|
|
34869
|
+
if (ctx.apiKey !== undefined)
|
|
34870
|
+
await explainThrownDenial(error, ctx.tenancy, { apiKey: ctx.apiKey, endpoint: ctx.endpoint });
|
|
34871
|
+
};
|
|
34800
34872
|
return {
|
|
34801
34873
|
endpoint: ctx.endpoint,
|
|
34802
34874
|
credential: ctx.credential,
|
|
@@ -34805,6 +34877,7 @@ function connection(sdk, ctx) {
|
|
|
34805
34877
|
try {
|
|
34806
34878
|
return await fn(sdk.reports);
|
|
34807
34879
|
} catch (error) {
|
|
34880
|
+
await explain(error);
|
|
34808
34881
|
throw reportsError(error, ctx);
|
|
34809
34882
|
}
|
|
34810
34883
|
},
|
|
@@ -34812,6 +34885,7 @@ function connection(sdk, ctx) {
|
|
|
34812
34885
|
try {
|
|
34813
34886
|
return await fn(sdk);
|
|
34814
34887
|
} catch (error) {
|
|
34888
|
+
await explain(error);
|
|
34815
34889
|
throw reportsError(error, ctx);
|
|
34816
34890
|
}
|
|
34817
34891
|
}
|
|
@@ -34865,6 +34939,7 @@ async function connectReports(opts) {
|
|
|
34865
34939
|
return connection(sdk, {
|
|
34866
34940
|
endpoint: tenancy.endpoint,
|
|
34867
34941
|
credential: "api-key",
|
|
34942
|
+
apiKey,
|
|
34868
34943
|
tenancy: {
|
|
34869
34944
|
keyScope: tenancy.keyScope,
|
|
34870
34945
|
workspaceId: tenancy.workspaceId,
|
|
@@ -36548,6 +36623,7 @@ function fixFor(code, serverFix, ctx) {
|
|
|
36548
36623
|
init_config();
|
|
36549
36624
|
init_errors();
|
|
36550
36625
|
import { HTTPClient as HTTPClient6, Mutagent as Mutagent6 } from "@mutagent/sdk";
|
|
36626
|
+
init_key_scope_heal();
|
|
36551
36627
|
init_known_secrets();
|
|
36552
36628
|
init_organization_scope();
|
|
36553
36629
|
init_sdk_debug();
|
|
@@ -36616,6 +36692,7 @@ async function connectIntegrations() {
|
|
|
36616
36692
|
const tenancy = await resolveKeyTenancy(apiKey);
|
|
36617
36693
|
if (tenancy.keyScope !== "workspace" && !tenancy.workspaceId)
|
|
36618
36694
|
throw noWorkspaceSelected();
|
|
36695
|
+
const key = { apiKey, endpoint: tenancy.endpoint };
|
|
36619
36696
|
const headers = tenancyHeaders({
|
|
36620
36697
|
keyScope: tenancy.keyScope,
|
|
36621
36698
|
workspaceId: tenancy.workspaceId,
|
|
@@ -36667,6 +36744,7 @@ async function connectIntegrations() {
|
|
|
36667
36744
|
try {
|
|
36668
36745
|
return await fn(sdk.integrations);
|
|
36669
36746
|
} catch (error) {
|
|
36747
|
+
await explainThrownDenial(error, tenancyCtx, key);
|
|
36670
36748
|
throw integrationsError(error, { endpoint: tenancy.endpoint, tenancy: tenancyCtx, call: callCtx });
|
|
36671
36749
|
}
|
|
36672
36750
|
}
|
|
@@ -38163,4 +38241,4 @@ if (process.env.CLI_VERSION) {
|
|
|
38163
38241
|
}
|
|
38164
38242
|
runCli(process.argv.slice(2), cliVersion);
|
|
38165
38243
|
|
|
38166
|
-
//# debugId=
|
|
38244
|
+
//# debugId=CAC2930F9F23245664756E2164756E21
|
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
|
|
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,44 @@ 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
|
+
}
|
|
1480
|
+
async function explainThrownDenial(error, ctx, key) {
|
|
1481
|
+
if (ctx === undefined || !(error instanceof Error) || !("statusCode" in error) || typeof error.statusCode !== "number")
|
|
1482
|
+
return;
|
|
1483
|
+
const body = "body" in error && typeof error.body === "string" ? error.body : "";
|
|
1484
|
+
await explainSelectionDenial(error.statusCode, body, ctx, key);
|
|
1485
|
+
}
|
|
1437
1486
|
function sameHeaders(a, b) {
|
|
1438
1487
|
const keys = Object.keys(a);
|
|
1439
1488
|
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
@@ -1441,6 +1490,11 @@ function sameHeaders(a, b) {
|
|
|
1441
1490
|
async function fetchWithScopeHeal(input, init, target) {
|
|
1442
1491
|
const spare = input instanceof Request ? input.clone() : undefined;
|
|
1443
1492
|
const response = await (init == null ? fetch(input) : fetch(input, init));
|
|
1493
|
+
if (response.status === 403 && target.tenancy) {
|
|
1494
|
+
const denied = await response.clone().text().catch(() => "");
|
|
1495
|
+
await explainSelectionDenial(403, denied, target.tenancy, target);
|
|
1496
|
+
return response;
|
|
1497
|
+
}
|
|
1444
1498
|
if (response.status !== 400)
|
|
1445
1499
|
return response;
|
|
1446
1500
|
const body = await response.clone().text().catch(() => "");
|
|
@@ -1467,7 +1521,9 @@ async function fetchWithScopeHeal(input, init, target) {
|
|
|
1467
1521
|
}
|
|
1468
1522
|
var ORG_KEY_NEEDS_WORKSPACE = "x-workspace-id header required";
|
|
1469
1523
|
var init_key_scope_heal = __esm(() => {
|
|
1524
|
+
init_config();
|
|
1470
1525
|
init_organization_scope();
|
|
1526
|
+
init_tenancy_error();
|
|
1471
1527
|
});
|
|
1472
1528
|
|
|
1473
1529
|
// src/lib/sdk-mappers.ts
|
|
@@ -1667,9 +1723,8 @@ class SDKClientWrapper {
|
|
|
1667
1723
|
const tenancy = tenancyFailure(response.status, error, this.tenancy);
|
|
1668
1724
|
if (tenancy)
|
|
1669
1725
|
throw tenancy;
|
|
1670
|
-
if (response.status === 401)
|
|
1726
|
+
if (response.status === 401)
|
|
1671
1727
|
throw new AuthenticationError;
|
|
1672
|
-
}
|
|
1673
1728
|
if (response.status === 403) {
|
|
1674
1729
|
throw new ApiError(403, error || "Access denied");
|
|
1675
1730
|
}
|
|
@@ -1840,6 +1895,7 @@ class SDKClientWrapper {
|
|
|
1840
1895
|
apiKey: this.apiKey,
|
|
1841
1896
|
endpoint: this.endpoint,
|
|
1842
1897
|
headers: () => this.tenancyHeaders(),
|
|
1898
|
+
tenancy: this.tenancy,
|
|
1843
1899
|
adopt: (self) => {
|
|
1844
1900
|
this.tenancy.keyScope = self.scope;
|
|
1845
1901
|
if (!this.workspaceId && self.scope === "workspace" && self.workspace)
|
|
@@ -1989,4 +2045,4 @@ export {
|
|
|
1989
2045
|
version
|
|
1990
2046
|
};
|
|
1991
2047
|
|
|
1992
|
-
//# debugId=
|
|
2048
|
+
//# debugId=C31E7F3B419630CE64756E2164756E21
|
package/package.json
CHANGED