@mutagent/cli 0.1.379 → 0.1.381

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.
Files changed (3) hide show
  1. package/dist/bin/cli.js +945 -760
  2. package/dist/index.js +515 -411
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -255,6 +255,164 @@ var init_known_secrets = __esm(() => {
255
255
  known = new Set;
256
256
  });
257
257
 
258
+ // src/lib/config.ts
259
+ import { cosmiconfigSync } from "cosmiconfig";
260
+ import { z } from "zod";
261
+ import { homedir } from "os";
262
+ import { join } from "path";
263
+ import { existsSync, readFileSync, mkdirSync } from "fs";
264
+ function parseJsonSafe(content, schema) {
265
+ try {
266
+ const parsed = JSON.parse(content);
267
+ return schema.parse(parsed);
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+ function loadConfig() {
273
+ const explorer = cosmiconfigSync("mutagent");
274
+ const result = explorer.search();
275
+ const rcConfig = result?.config;
276
+ let credentials = null;
277
+ if (existsSync(CREDENTIALS_FILE)) {
278
+ credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
279
+ }
280
+ const envKey = process.env.MUTAGENT_API_KEY;
281
+ const fileDescribesKey = envKey === undefined || envKey === credentials?.apiKey;
282
+ const envWorkspace = process.env.MUTAGENT_WORKSPACE_ID?.trim() ?? "";
283
+ const fileSelection = fileDescribesKey ? credentials?.defaultWorkspace : undefined;
284
+ const selected = fileSelection ?? rcConfig?.defaultWorkspace;
285
+ const defaultWorkspace = envWorkspace !== "" ? envWorkspace : selected;
286
+ const merged = {
287
+ providers: {},
288
+ environment: "production",
289
+ ...rcConfig,
290
+ ...credentials,
291
+ apiKey: envKey ?? credentials?.apiKey ?? rcConfig?.apiKey,
292
+ endpoint: process.env.MUTAGENT_ENDPOINT ?? credentials?.endpoint ?? rcConfig?.endpoint,
293
+ defaultWorkspace,
294
+ workspaceOrigin: envWorkspace !== "" ? "override" : selected ? "selection" : undefined,
295
+ workspaceName: envWorkspace === "" && fileSelection !== undefined && fileSelection === selected ? credentials?.workspaceName : undefined,
296
+ defaultOrganization: fileDescribesKey ? credentials?.defaultOrganization ?? rcConfig?.defaultOrganization : undefined,
297
+ organizationName: fileDescribesKey ? credentials?.organizationName : undefined,
298
+ organizationSlug: fileDescribesKey ? credentials?.organizationSlug : undefined,
299
+ keyScope: fileDescribesKey ? credentials?.keyScope : undefined
300
+ };
301
+ return configSchema.parse(merged);
302
+ }
303
+ function readCredentialsFile() {
304
+ if (!existsSync(CREDENTIALS_FILE))
305
+ return {};
306
+ try {
307
+ return JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
308
+ } catch {
309
+ return {};
310
+ }
311
+ }
312
+ function updateCredentials(patch) {
313
+ if (!existsSync(CREDENTIALS_DIR)) {
314
+ mkdirSync(CREDENTIALS_DIR, { recursive: true });
315
+ }
316
+ const updated = Object.fromEntries(Object.entries({ ...readCredentialsFile(), ...patch }).filter(([key, value]) => value !== undefined && key !== "defaultWorkspaceSource"));
317
+ writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
318
+ }
319
+ function saveCredentials(apiKey, endpoint) {
320
+ if (!existsSync(CREDENTIALS_DIR)) {
321
+ mkdirSync(CREDENTIALS_DIR, { recursive: true });
322
+ }
323
+ const config = loadConfig();
324
+ const credentials = {
325
+ apiKey,
326
+ endpoint: endpoint ?? config.endpoint
327
+ };
328
+ writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
329
+ }
330
+ function saveFullCredentials(creds) {
331
+ const existing = readCredentialsFile();
332
+ updateCredentials({
333
+ apiKey: creds.apiKey,
334
+ endpoint: creds.endpoint ?? existing.endpoint ?? "https://api.mutagent.io",
335
+ defaultWorkspace: creds.workspaceId,
336
+ workspaceName: creds.workspaceName,
337
+ defaultOrganization: creds.organizationId,
338
+ organizationName: creds.organizationName,
339
+ organizationSlug: creds.organizationSlug,
340
+ keyScope: creds.keyScope,
341
+ expiresAt: creds.expiresAt
342
+ });
343
+ }
344
+ function clearCredentials() {
345
+ if (existsSync(CREDENTIALS_FILE)) {
346
+ writeSecureFile(CREDENTIALS_FILE, JSON.stringify({}));
347
+ }
348
+ }
349
+ function hasCredentials() {
350
+ if (process.env.MUTAGENT_API_KEY)
351
+ return true;
352
+ if (!existsSync(CREDENTIALS_FILE))
353
+ return false;
354
+ const credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
355
+ return credentials?.apiKey !== undefined && credentials.apiKey !== "";
356
+ }
357
+ function envKeyReplacesFile() {
358
+ const envKey = process.env.MUTAGENT_API_KEY;
359
+ if (envKey === undefined)
360
+ return false;
361
+ if (!existsSync(CREDENTIALS_FILE))
362
+ return true;
363
+ const credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
364
+ return envKey !== credentials?.apiKey;
365
+ }
366
+ function getApiKey() {
367
+ const apiKey = process.env.MUTAGENT_API_KEY ?? loadConfig().apiKey;
368
+ registerSecret(apiKey);
369
+ return apiKey;
370
+ }
371
+ function setWorkspaceSelection(workspaceId, workspaceName) {
372
+ updateCredentials({ defaultWorkspace: workspaceId, workspaceName });
373
+ }
374
+ function recordKeyScope(self) {
375
+ updateCredentials({
376
+ keyScope: self.scope,
377
+ defaultOrganization: self.organization?.id,
378
+ organizationName: self.organization?.name,
379
+ organizationSlug: self.organization?.slug,
380
+ ...self.scope === "workspace" && self.workspace ? { defaultWorkspace: self.workspace.id, workspaceName: self.workspace.name } : {}
381
+ });
382
+ }
383
+ var keyScopeSchema, configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
384
+ var init_config = __esm(() => {
385
+ init_secure_file();
386
+ init_known_secrets();
387
+ keyScopeSchema = z.enum(["organization", "workspace", "user"]);
388
+ configSchema = z.object({
389
+ apiKey: z.string().optional(),
390
+ endpoint: z.string().default("https://api.mutagent.io"),
391
+ format: z.enum(["table", "json"]).default("table"),
392
+ timeout: z.number().default(30000),
393
+ defaultWorkspace: z.string().optional(),
394
+ workspaceOrigin: z.enum(["override", "selection"]).optional(),
395
+ workspaceName: z.string().optional(),
396
+ defaultOrganization: z.string().optional(),
397
+ organizationName: z.string().optional(),
398
+ organizationSlug: z.string().optional(),
399
+ keyScope: keyScopeSchema.optional()
400
+ });
401
+ credentialsSchema = z.object({
402
+ apiKey: z.string().optional(),
403
+ endpoint: z.string().optional(),
404
+ defaultWorkspace: z.string().optional(),
405
+ workspaceName: z.string().optional(),
406
+ defaultOrganization: z.string().optional(),
407
+ organizationName: z.string().optional(),
408
+ organizationSlug: z.string().optional(),
409
+ keyScope: keyScopeSchema.optional(),
410
+ expiresAt: z.string().optional()
411
+ }).loose();
412
+ CREDENTIALS_DIR = join(homedir(), ".config", "mutagent");
413
+ CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
414
+ });
415
+
258
416
  // src/lib/errors.ts
259
417
  function createErrorFromUnknown(error) {
260
418
  if (error instanceof MutagentError) {
@@ -278,7 +436,7 @@ function handleError(error, isJson) {
278
436
  }
279
437
  if (error instanceof WorkspaceContextError) {
280
438
  jsonOutput.remediation = {
281
- setWorkspace: "mutagent config set workspace <workspace-id>",
439
+ setWorkspace: "mutagent workspaces use <name>",
282
440
  listWorkspaces: "mutagent workspaces list"
283
441
  };
284
442
  }
@@ -300,7 +458,7 @@ function handleError(error, isJson) {
300
458
  console.log(JSON.stringify(scrubKnownDeep(jsonOutput), null, 2));
301
459
  } else {
302
460
  console.error(`Error: ${scrubKnown(error.message)}`);
303
- if (error instanceof AuthenticationError) {
461
+ if (error.name === "TenancyError") {} else if (error instanceof AuthenticationError) {
304
462
  console.error("");
305
463
  console.error(AUTH_REMEDIATION_MESSAGE);
306
464
  } else if (error instanceof WorkspaceContextError) {
@@ -403,14 +561,14 @@ var init_errors = __esm(() => {
403
561
  }
404
562
  };
405
563
  WORKSPACE_REMEDIATION_MESSAGE = [
406
- "Workspace context missing. To fix:",
407
- " mutagent config set workspace <workspace-id> # Set default workspace",
408
- " mutagent workspaces list # List available workspaces"
564
+ "No workspace selected. To fix:",
565
+ " mutagent workspaces use <name> # Select the workspace every later command works in",
566
+ " mutagent workspaces list # Your workspaces"
409
567
  ].join(`
410
568
  `);
411
569
  WorkspaceContextError = class WorkspaceContextError extends MutagentError {
412
570
  constructor(message) {
413
- super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent config set workspace <workspace-id>", 3);
571
+ super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent workspaces use <name>", 3);
414
572
  }
415
573
  };
416
574
  ValidationError = class ValidationError extends MutagentError {
@@ -444,6 +602,290 @@ var init_errors = __esm(() => {
444
602
  };
445
603
  });
446
604
 
605
+ // src/lib/organization-scope.ts
606
+ var exports_organization_scope = {};
607
+ __export(exports_organization_scope, {
608
+ unreachableServerError: () => unreachableServerError,
609
+ tenancyHeaders: () => tenancyHeaders,
610
+ tenancyFailure: () => tenancyFailure,
611
+ setWorkspaceFlag: () => setWorkspaceFlag,
612
+ setOrgAssertion: () => setOrgAssertion,
613
+ resolveKeyTenancy: () => resolveKeyTenancy,
614
+ otherOrganizationHint: () => otherOrganizationHint,
615
+ orgMismatch: () => orgMismatch,
616
+ noWorkspaceSelected: () => noWorkspaceSelected,
617
+ matchesOrganization: () => matchesOrganization,
618
+ looksLikeWorkspaceId: () => looksLikeWorkspaceId,
619
+ listKeyWorkspaces: () => listKeyWorkspaces,
620
+ findWorkspace: () => findWorkspace,
621
+ fetchOrganizations: () => fetchOrganizations,
622
+ fetchKeySelf: () => fetchKeySelf,
623
+ envKeyHasNoSelection: () => envKeyHasNoSelection,
624
+ deadKeyError: () => deadKeyError,
625
+ configTenancyHeaders: () => configTenancyHeaders,
626
+ TenancyError: () => TenancyError,
627
+ TENANCY_DENIED: () => TENANCY_DENIED
628
+ });
629
+ function looksLikeWorkspaceId(value) {
630
+ return UUID_PATTERN.test(value);
631
+ }
632
+ function tenancyHeaders(t) {
633
+ const headers = {};
634
+ if (!t.workspaceId)
635
+ return headers;
636
+ if (t.keyScope === "workspace") {
637
+ if (t.workspaceOrigin === "override")
638
+ headers["x-workspace-id"] = t.workspaceId;
639
+ return headers;
640
+ }
641
+ headers["x-workspace-id"] = t.workspaceId;
642
+ if (t.keyScope === "user" && t.organizationId)
643
+ headers["x-organization-id"] = t.organizationId;
644
+ return headers;
645
+ }
646
+ function configTenancyHeaders(config) {
647
+ return tenancyHeaders({
648
+ keyScope: config.keyScope,
649
+ workspaceId: config.defaultWorkspace,
650
+ workspaceOrigin: config.workspaceOrigin,
651
+ organizationId: config.defaultOrganization
652
+ });
653
+ }
654
+ function parseBody(body) {
655
+ try {
656
+ const parsed = JSON.parse(body);
657
+ if (parsed && typeof parsed === "object")
658
+ return parsed;
659
+ } catch {}
660
+ return {};
661
+ }
662
+ function deadKeyError() {
663
+ return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login", 2);
664
+ }
665
+ function tenancyFailure(status, body, ctx) {
666
+ const parsed = parseBody(body);
667
+ if (status === 401)
668
+ return deadKeyError();
669
+ if (status === 403 && parsed.code === TENANCY_DENIED) {
670
+ const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
671
+ if (ctx.workspaceOrigin === "selection") {
672
+ return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
673
+ }
674
+ const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
675
+ return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
676
+ }
677
+ if (status === 403 && ctx.keyScope === "workspace" && ctx.workspaceOrigin === "override") {
678
+ return new TenancyError("WORKSPACE_KEY_MISMATCH", "This API key only works in its own workspace.", "Run: mutagent login (for a key that can switch workspaces)");
679
+ }
680
+ if (status === 400 && (parsed.message ?? body).toLowerCase().includes("x-workspace-id")) {
681
+ return noWorkspaceSelected();
682
+ }
683
+ return;
684
+ }
685
+ function noWorkspaceSelected() {
686
+ const fix = envKeyReplacesFile() ? ENV_KEY_WORKSPACE_FIX : "Run: mutagent workspaces use <name>";
687
+ return new TenancyError("WORKSPACE_REQUIRED", "No workspace selected.", fix, 3);
688
+ }
689
+ function envKeyHasNoSelection() {
690
+ return new TenancyError("WORKSPACE_FROM_ENV", "With MUTAGENT_API_KEY set, the workspace comes from --workspace or MUTAGENT_WORKSPACE_ID.", ENV_KEY_WORKSPACE_FIX);
691
+ }
692
+ async function tenancyFetch(url, init) {
693
+ try {
694
+ return await fetch(url, init);
695
+ } catch {
696
+ throw unreachableServerError(url);
697
+ }
698
+ }
699
+ function unreachableServerError(url) {
700
+ let origin = url;
701
+ try {
702
+ origin = new URL(url).origin;
703
+ } catch {}
704
+ return new MutagentError("REQUEST_FAILED", `Unable to connect to the Mutagent server at ${origin}.`, "Check your connection and the endpoint: mutagent config get endpoint");
705
+ }
706
+ async function fetchKeySelf(apiKey, endpoint, opts = {}) {
707
+ const response = await tenancyFetch(`${endpoint}/api/api-keys/self`, {
708
+ headers: { "x-api-key": apiKey, ...opts.headers }
709
+ });
710
+ if (!response.ok) {
711
+ const body = await response.text().catch(() => "");
712
+ throw tenancyFailure(response.status, body, opts.ctx ?? {}) ?? new MutagentError("KEY_SCOPE_UNAVAILABLE", `Could not read this API key's scope (${String(response.status)}).`, "Check the endpoint with: mutagent config get endpoint");
713
+ }
714
+ return await response.json();
715
+ }
716
+ async function fetchOrganizations(apiKey, endpoint) {
717
+ try {
718
+ const response = await fetch(`${endpoint}/api/organizations`, {
719
+ headers: { "x-api-key": apiKey }
720
+ });
721
+ if (!response.ok)
722
+ return [];
723
+ const data = await response.json();
724
+ return data.data ?? [];
725
+ } catch {
726
+ return [];
727
+ }
728
+ }
729
+ function matchesOrganization(value, org) {
730
+ if (!org)
731
+ return false;
732
+ if (value === org.id || org.slug !== undefined && value === org.slug)
733
+ return true;
734
+ return org.name !== undefined && value.toLowerCase() === org.name.toLowerCase();
735
+ }
736
+ function orgMismatch(value, org) {
737
+ const shown = org.slug ? `${org.name} (${org.slug})` : org.name;
738
+ return new TenancyError("ORG_MISMATCH", `Your key is for ${shown}.`, `Run: mutagent login --org ${value}`);
739
+ }
740
+ function otherOrganizationHint(wanted, keyOrg, orgs) {
741
+ const others = orgs.filter((o) => o.id !== keyOrg?.id);
742
+ const where = keyOrg ? ` in ${keyOrg.name}` : "";
743
+ const what = `No workspace "${wanted}"${where}.`;
744
+ const named = others.map((o) => `${o.name} (${o.slug ?? o.id})`).join(", ");
745
+ if (others.length === 1 && others[0]) {
746
+ const only = others[0];
747
+ return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organization: ${named}.`, `To switch: mutagent login --org ${only.slug ?? only.id}`);
748
+ }
749
+ if (others.length > 1) {
750
+ return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organizations: ${named}.`, "To switch: mutagent login --org <slug>");
751
+ }
752
+ return new TenancyError("WORKSPACE_NOT_FOUND", what, "See: mutagent workspaces list");
753
+ }
754
+ function setOrgAssertion(value) {
755
+ const trimmed = value?.trim();
756
+ orgAssertion = trimmed === "" ? undefined : trimmed;
757
+ }
758
+ function setWorkspaceFlag(value) {
759
+ const trimmed = value?.trim();
760
+ workspaceFlag = trimmed === "" ? undefined : trimmed;
761
+ }
762
+ function endpointOf(configured) {
763
+ const trimmed = configured?.trim();
764
+ return (trimmed === undefined || trimmed === "" ? "https://api.mutagent.io" : trimmed).replace(/\/+$/, "");
765
+ }
766
+ async function probeKeyScope(apiKey, endpoint, workspaceNamed) {
767
+ const fromFile = !envKeyReplacesFile();
768
+ if (!fromFile && workspaceNamed && orgAssertion === undefined)
769
+ return;
770
+ try {
771
+ const self = await fetchKeySelf(apiKey, endpoint);
772
+ if (fromFile)
773
+ recordKeyScope(self);
774
+ return self;
775
+ } catch (error) {
776
+ if (error instanceof MutagentError && (error.code === "AUTH_EXPIRED" || error.code === "REQUEST_FAILED"))
777
+ throw error;
778
+ return;
779
+ }
780
+ }
781
+ function enforceOrgAssertion(org) {
782
+ if (orgAssertion === undefined)
783
+ return;
784
+ if (org === undefined) {
785
+ throw new TenancyError("ORG_UNKNOWN", "This key's organization is unknown, so --org cannot be checked.", "Run: mutagent workspaces current");
786
+ }
787
+ if (!matchesOrganization(orgAssertion, org)) {
788
+ throw orgMismatch(orgAssertion, org);
789
+ }
790
+ }
791
+ async function resolveKeyTenancy(apiKey, opts = {}) {
792
+ const config = loadConfig();
793
+ const endpoint = endpointOf(config.endpoint);
794
+ let keyScope = config.keyScope;
795
+ let organization = config.defaultOrganization ? { id: config.defaultOrganization, name: config.organizationName ?? config.defaultOrganization, slug: config.organizationSlug } : undefined;
796
+ let workspaceId = config.defaultWorkspace;
797
+ let workspaceName = config.workspaceName;
798
+ const workspaceOrigin = config.workspaceOrigin;
799
+ if (keyScope === undefined && (opts.probe !== false || orgAssertion !== undefined)) {
800
+ const self = await probeKeyScope(apiKey, endpoint, workspaceId !== undefined);
801
+ if (self) {
802
+ keyScope = self.scope;
803
+ organization = self.organization ?? undefined;
804
+ if (self.scope === "workspace" && self.workspace && workspaceOrigin !== "override") {
805
+ workspaceId = self.workspace.id;
806
+ workspaceName = self.workspace.name;
807
+ }
808
+ }
809
+ }
810
+ enforceOrgAssertion(organization);
811
+ if (opts.resolveOverride !== false && workspaceFlag !== undefined && workspaceId === workspaceFlag && !looksLikeWorkspaceId(workspaceId)) {
812
+ const found = await findWorkspace(workspaceId, { apiKey, endpoint, keyScope, organization, workspaceId: config.defaultWorkspace, workspaceName });
813
+ workspaceId = found.id;
814
+ workspaceName = found.name;
815
+ process.env.MUTAGENT_WORKSPACE_ID = found.id;
816
+ }
817
+ return { endpoint, keyScope, organization, workspaceId, workspaceName, workspaceOrigin };
818
+ }
819
+ async function listKeyWorkspaces(apiKey, endpoint, t) {
820
+ const headers = { "x-api-key": apiKey };
821
+ if (t.keyScope === "user" && t.organization)
822
+ headers["x-organization-id"] = t.organization.id;
823
+ const response = await tenancyFetch(`${endpoint}/api/workspaces`, { headers });
824
+ if (!response.ok) {
825
+ const body = await response.text().catch(() => "");
826
+ throw tenancyFailure(response.status, body, { keyScope: t.keyScope, organizationName: t.organization?.name }) ?? new MutagentError("REQUEST_FAILED", `Could not list workspaces (${String(response.status)}).`, "Run: mutagent workspaces current");
827
+ }
828
+ const data = await response.json();
829
+ return data.workspaces ?? [];
830
+ }
831
+ async function findWorkspace(wanted, t) {
832
+ if (t.keyScope === "workspace") {
833
+ if (t.workspaceId && (wanted === t.workspaceId || wanted === t.workspaceName)) {
834
+ return { id: t.workspaceId, name: t.workspaceName ?? t.workspaceId };
835
+ }
836
+ throw new TenancyError("WORKSPACE_KEY_MISMATCH", `This key only works in ${t.workspaceName ?? t.workspaceId ?? "its own workspace"}.`, "Run: mutagent login (for a key that can switch workspaces)");
837
+ }
838
+ const rows = await listKeyWorkspaces(t.apiKey, t.endpoint, t);
839
+ const byId = rows.find((w) => w.id === wanted);
840
+ if (byId)
841
+ return { id: byId.id, name: byId.name };
842
+ let matches = rows.filter((w) => w.name === wanted || w.slug === wanted);
843
+ if (matches.length === 0)
844
+ matches = rows.filter((w) => w.name.toLowerCase() === wanted.toLowerCase());
845
+ if (matches.length === 1 && matches[0])
846
+ return { id: matches[0].id, name: matches[0].name };
847
+ if (matches.length > 1) {
848
+ throw new TenancyError("WORKSPACE_AMBIGUOUS", `Several workspaces are named "${wanted}": ${matches.map((w) => `${w.name} (${w.id})`).join(", ")}.`, "Run: mutagent workspaces use <id>");
849
+ }
850
+ const orgs = await fetchOrganizations(t.apiKey, t.endpoint);
851
+ throw otherOrganizationHint(wanted, t.organization, orgs);
852
+ }
853
+ var TENANCY_DENIED = "TENANCY_DENIED", UUID_PATTERN, TenancyError, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag;
854
+ var init_organization_scope = __esm(() => {
855
+ init_errors();
856
+ init_config();
857
+ UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
858
+ TenancyError = class TenancyError extends MutagentError {
859
+ constructor(code, what, fix, exitCode = 1) {
860
+ super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""), exitCode);
861
+ this.name = "TenancyError";
862
+ }
863
+ };
864
+ });
865
+
866
+ // src/lib/provider-request.ts
867
+ function buildCreateProviderBody(data) {
868
+ const body = {
869
+ name: data.name,
870
+ provider: data.provider,
871
+ isDefault: data.isDefault ?? false
872
+ };
873
+ if (data.apiKey)
874
+ body.apiKey = data.apiKey;
875
+ if (data.baseUrl)
876
+ body.baseUrl = data.baseUrl;
877
+ if (data.hostedFamily)
878
+ body.hostedFamily = data.hostedFamily;
879
+ if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
880
+ body.credentialFields = data.credentialFields;
881
+ }
882
+ if (data.metadata && Object.keys(data.metadata).length > 0)
883
+ body.metadata = data.metadata;
884
+ if (data.supportedModels !== undefined)
885
+ body.supportedModels = data.supportedModels;
886
+ return body;
887
+ }
888
+
447
889
  // src/lib/sdk-debug.ts
448
890
  function isDebugEnabled(env = process.env) {
449
891
  return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
@@ -538,282 +980,6 @@ var init_sdk_debug = __esm(() => {
538
980
  };
539
981
  });
540
982
 
541
- // src/lib/organization-scope.ts
542
- function isWellFormedOrganizationId(id) {
543
- return ORGANIZATION_ID_PATTERN.test(id);
544
- }
545
- function isOrganizationScopeMismatch(status, body) {
546
- if (status !== 403)
547
- return false;
548
- return body.toLowerCase().includes("organization scope mismatch");
549
- }
550
- async function lookupOrganizations(apiKey, endpoint) {
551
- try {
552
- const response = await fetch(`${endpoint}/api/organizations`, {
553
- headers: { "x-api-key": apiKey }
554
- });
555
- if (!response.ok)
556
- return [];
557
- const data = await response.json();
558
- return data.data ?? [];
559
- } catch {
560
- return [];
561
- }
562
- }
563
- async function fetchOrganizations(apiKey, endpoint) {
564
- return lookupOrganizations(apiKey, endpoint);
565
- }
566
- async function assertUsableOrganizationId(id, lookup) {
567
- const orgs = lookup.apiKey ? await lookup.fetchOrganizations(lookup.apiKey, lookup.endpoint).catch(() => []) : [];
568
- if (orgs.length === 0) {
569
- if (isWellFormedOrganizationId(id))
570
- return;
571
- throw new MutagentError("INVALID_ORGANIZATION_ID", `"${id}" is not an organization id.`, `Organization ids look like org_xxxxxxxxxxxxxxxxxxxxx.
572
- ` + "Log in so the id can be checked against your account: mutagent auth login --browser");
573
- }
574
- if (orgs.some((org) => org.id === id))
575
- return;
576
- throw new MutagentError("UNKNOWN_ORGANIZATION", `"${id}" is not an organization this API key can access.`, `Available: ${orgs.map((org) => org.id).join(", ")}
577
- ` + "A stored organization that does not match the key makes every request fail with 403.");
578
- }
579
- function createOrgScopeRecoveringFetcher(hooks) {
580
- let recoveryUsed = false;
581
- async function recover() {
582
- if (recoveryUsed)
583
- return;
584
- recoveryUsed = true;
585
- const stale = hooks.currentOrganization();
586
- if (!stale)
587
- return;
588
- hooks.adoptOrganization(undefined);
589
- hooks.forgetStoredOrganization?.();
590
- const orgs = await lookupOrganizations(hooks.apiKey, hooks.endpoint);
591
- const resolved = orgs[0]?.id;
592
- if (!resolved || resolved === stale)
593
- return;
594
- hooks.adoptOrganization(resolved);
595
- if (isDebugEnabled()) {
596
- process.stderr.write(`[mutagent] Stored organization was rejected by the API; re-resolved and retried once.
597
- `);
598
- }
599
- return resolved;
600
- }
601
- return async function fetchWithOrgRecovery(input, init) {
602
- const retryTemplate = input instanceof Request ? input.clone() : undefined;
603
- const response = init == null ? await fetch(input) : await fetch(input, init);
604
- if (response.status !== 403)
605
- return response;
606
- const body = await response.clone().text().catch(() => "");
607
- if (!isOrganizationScopeMismatch(response.status, body))
608
- return response;
609
- const resolved = await recover();
610
- if (!resolved)
611
- return response;
612
- if (retryTemplate) {
613
- retryTemplate.headers.set("x-organization-id", resolved);
614
- return fetch(retryTemplate);
615
- }
616
- const headers = new Headers(init?.headers);
617
- headers.set("x-organization-id", resolved);
618
- return fetch(input, { ...init, headers });
619
- };
620
- }
621
- var ORGANIZATION_ID_PATTERN;
622
- var init_organization_scope = __esm(() => {
623
- init_errors();
624
- init_sdk_debug();
625
- ORGANIZATION_ID_PATTERN = /^org_[A-Za-z0-9_-]{8,}$/;
626
- });
627
-
628
- // src/lib/config.ts
629
- import { cosmiconfigSync } from "cosmiconfig";
630
- import { z } from "zod";
631
- import { homedir } from "os";
632
- import { join } from "path";
633
- import { existsSync, readFileSync, mkdirSync } from "fs";
634
- function parseJsonSafe(content, schema) {
635
- try {
636
- const parsed = JSON.parse(content);
637
- return schema.parse(parsed);
638
- } catch {
639
- return null;
640
- }
641
- }
642
- function loadConfig() {
643
- const explorer = cosmiconfigSync("mutagent");
644
- const result = explorer.search();
645
- const rcConfig = result?.config;
646
- let credentials = null;
647
- if (existsSync(CREDENTIALS_FILE)) {
648
- credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
649
- }
650
- const merged = {
651
- providers: {},
652
- environment: "production",
653
- ...rcConfig,
654
- ...credentials,
655
- apiKey: process.env.MUTAGENT_API_KEY ?? credentials?.apiKey ?? rcConfig?.apiKey,
656
- endpoint: process.env.MUTAGENT_ENDPOINT ?? credentials?.endpoint ?? rcConfig?.endpoint,
657
- defaultWorkspace: process.env.MUTAGENT_WORKSPACE_ID ?? credentials?.defaultWorkspace ?? rcConfig?.defaultWorkspace,
658
- defaultWorkspaceSource: process.env.MUTAGENT_WORKSPACE_ID ? "user" : credentials?.defaultWorkspace ? credentials.defaultWorkspaceSource ?? "login-inferred" : rcConfig?.defaultWorkspace ? "login-inferred" : undefined,
659
- defaultOrganization: credentials?.defaultOrganization ?? rcConfig?.defaultOrganization
660
- };
661
- return configSchema.parse(merged);
662
- }
663
- function saveCredentials(apiKey, endpoint) {
664
- if (!existsSync(CREDENTIALS_DIR)) {
665
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
666
- }
667
- const config = loadConfig();
668
- const credentials = {
669
- apiKey,
670
- endpoint: endpoint ?? config.endpoint
671
- };
672
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
673
- }
674
- function saveFullCredentials(creds) {
675
- if (!existsSync(CREDENTIALS_DIR)) {
676
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
677
- }
678
- let existingCredentials = {};
679
- if (existsSync(CREDENTIALS_FILE)) {
680
- try {
681
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
682
- } catch {}
683
- }
684
- const existingSource = existingCredentials.defaultWorkspaceSource;
685
- const workspaceChanged = creds.workspaceId !== undefined && creds.workspaceId !== existingCredentials.defaultWorkspace;
686
- const source = creds.workspaceId === undefined ? existingSource : workspaceChanged ? "login-inferred" : existingSource ?? "login-inferred";
687
- const credentials = {
688
- ...existingCredentials,
689
- apiKey: creds.apiKey,
690
- endpoint: creds.endpoint ?? existingCredentials.endpoint ?? "https://api.mutagent.io",
691
- defaultWorkspace: creds.workspaceId ?? existingCredentials.defaultWorkspace,
692
- defaultWorkspaceSource: source,
693
- defaultOrganization: creds.organizationId ?? existingCredentials.defaultOrganization,
694
- expiresAt: creds.expiresAt
695
- };
696
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
697
- }
698
- function clearCredentials() {
699
- if (existsSync(CREDENTIALS_FILE)) {
700
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify({}));
701
- }
702
- }
703
- function hasCredentials() {
704
- if (process.env.MUTAGENT_API_KEY)
705
- return true;
706
- if (!existsSync(CREDENTIALS_FILE))
707
- return false;
708
- const credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
709
- return credentials?.apiKey !== undefined && credentials.apiKey !== "";
710
- }
711
- function getApiKey() {
712
- const apiKey = process.env.MUTAGENT_API_KEY ?? loadConfig().apiKey;
713
- registerSecret(apiKey);
714
- return apiKey;
715
- }
716
- function setDefaultWorkspace(workspaceId, source = "user") {
717
- if (!existsSync(CREDENTIALS_DIR)) {
718
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
719
- }
720
- let existingCredentials = {};
721
- if (existsSync(CREDENTIALS_FILE)) {
722
- try {
723
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
724
- } catch {}
725
- }
726
- const updated = {
727
- ...existingCredentials,
728
- defaultWorkspace: workspaceId,
729
- defaultWorkspaceSource: source
730
- };
731
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
732
- }
733
- function setDefaultOrganization(organizationId) {
734
- if (!isWellFormedOrganizationId(organizationId)) {
735
- throw new MutagentError("INVALID_ORGANIZATION_ID", `"${organizationId}" is not an organization id.`, `Organization ids look like org_xxxxxxxxxxxxxxxxxxxxx.
736
- ` + "List the ones you can use with: mutagent auth status");
737
- }
738
- if (!existsSync(CREDENTIALS_DIR)) {
739
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
740
- }
741
- let existingCredentials = {};
742
- if (existsSync(CREDENTIALS_FILE)) {
743
- try {
744
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
745
- } catch {}
746
- }
747
- const updated = {
748
- ...existingCredentials,
749
- defaultOrganization: organizationId
750
- };
751
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
752
- }
753
- function clearDefaultOrganization() {
754
- if (!existsSync(CREDENTIALS_FILE))
755
- return;
756
- let existingCredentials;
757
- try {
758
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
759
- } catch {
760
- return;
761
- }
762
- if (!("defaultOrganization" in existingCredentials))
763
- return;
764
- delete existingCredentials.defaultOrganization;
765
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(existingCredentials, null, 2));
766
- }
767
- var configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
768
- var init_config = __esm(() => {
769
- init_secure_file();
770
- init_known_secrets();
771
- init_organization_scope();
772
- init_errors();
773
- configSchema = z.object({
774
- apiKey: z.string().optional(),
775
- endpoint: z.string().default("https://api.mutagent.io"),
776
- format: z.enum(["table", "json"]).default("table"),
777
- timeout: z.number().default(30000),
778
- defaultWorkspace: z.string().optional(),
779
- defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
780
- defaultOrganization: z.string().optional()
781
- });
782
- credentialsSchema = z.object({
783
- apiKey: z.string().optional(),
784
- endpoint: z.string().optional(),
785
- defaultWorkspace: z.string().optional(),
786
- defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
787
- defaultOrganization: z.string().optional(),
788
- expiresAt: z.string().optional()
789
- }).loose();
790
- CREDENTIALS_DIR = join(homedir(), ".config", "mutagent");
791
- CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
792
- });
793
-
794
- // src/lib/provider-request.ts
795
- function buildCreateProviderBody(data) {
796
- const body = {
797
- name: data.name,
798
- provider: data.provider,
799
- isDefault: data.isDefault ?? false
800
- };
801
- if (data.apiKey)
802
- body.apiKey = data.apiKey;
803
- if (data.baseUrl)
804
- body.baseUrl = data.baseUrl;
805
- if (data.hostedFamily)
806
- body.hostedFamily = data.hostedFamily;
807
- if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
808
- body.credentialFields = data.credentialFields;
809
- }
810
- if (data.metadata && Object.keys(data.metadata).length > 0)
811
- body.metadata = data.metadata;
812
- if (data.supportedModels !== undefined)
813
- body.supportedModels = data.supportedModels;
814
- return body;
815
- }
816
-
817
983
  // src/lib/sdk-mappers.ts
818
984
  import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
819
985
  function isProviderType(value) {
@@ -902,15 +1068,6 @@ var init_sdk_mappers = __esm(() => {
902
1068
  });
903
1069
 
904
1070
  // src/lib/sdk-client.ts
905
- var exports_sdk_client = {};
906
- __export(exports_sdk_client, {
907
- validateApiKey: () => validateApiKey,
908
- resetSDKClient: () => resetSDKClient,
909
- getSDKClient: () => getSDKClient,
910
- fetchWorkspaces: () => fetchWorkspaces,
911
- fetchOrganizations: () => fetchOrganizations,
912
- MutagentSDK: () => SDKClientWrapper
913
- });
914
1071
  import { Mutagent, HTTPClient } from "@mutagent/sdk";
915
1072
 
916
1073
  class SDKClientWrapper {
@@ -919,32 +1076,24 @@ class SDKClientWrapper {
919
1076
  endpoint;
920
1077
  workspaceId;
921
1078
  organizationId;
922
- workspaceResolution = "none";
1079
+ tenancy;
923
1080
  debugSecrets = new Set;
924
- recoveringFetch;
925
1081
  constructor(opts) {
926
1082
  this.apiKey = opts.apiKey;
927
1083
  this.endpoint = opts.serverURL ?? "http://localhost:3003";
928
1084
  this.workspaceId = opts.workspaceId;
929
1085
  this.organizationId = opts.organizationId;
930
- if (opts.workspaceId && opts.workspaceSource === "user") {
931
- this.workspaceResolution = "configured";
932
- }
933
- this.recoveringFetch = createOrgScopeRecoveringFetcher({
934
- apiKey: this.apiKey,
935
- endpoint: this.endpoint,
936
- currentOrganization: () => this.organizationId,
937
- adoptOrganization: (id) => {
938
- this.organizationId = id;
939
- },
940
- forgetStoredOrganization: opts.clearStoredOrganization
941
- });
942
- const httpClient = new HTTPClient({ fetcher: this.recoveringFetch });
1086
+ this.tenancy = {
1087
+ keyScope: opts.keyScope,
1088
+ workspaceId: opts.workspaceId,
1089
+ workspaceName: opts.workspaceName,
1090
+ workspaceOrigin: opts.workspaceOrigin ?? (opts.workspaceId ? "selection" : undefined),
1091
+ organizationName: opts.organizationName
1092
+ };
1093
+ const httpClient = new HTTPClient;
943
1094
  httpClient.addHook("beforeRequest", (req) => {
944
- if (this.workspaceId)
945
- req.headers.set("x-workspace-id", this.workspaceId);
946
- if (this.organizationId)
947
- req.headers.set("x-organization-id", this.organizationId);
1095
+ for (const [name, value] of Object.entries(this.tenancyHeaders()))
1096
+ req.headers.set(name, value);
948
1097
  return req;
949
1098
  });
950
1099
  this.debugSecrets.add(this.apiKey);
@@ -959,8 +1108,16 @@ class SDKClientWrapper {
959
1108
  });
960
1109
  }
961
1110
  handleError(error) {
1111
+ if (error instanceof Error && SDK_TRANSPORT_ERRORS.has(error.name)) {
1112
+ throw unreachableServerError(this.endpoint);
1113
+ }
962
1114
  if (error && typeof error === "object") {
963
1115
  const err = error;
1116
+ if (err.statusCode) {
1117
+ const tenancy = tenancyFailure(err.statusCode, err.body ?? "", this.tenancy);
1118
+ if (tenancy)
1119
+ throw tenancy;
1120
+ }
964
1121
  if (err.statusCode === 401) {
965
1122
  throw new AuthenticationError;
966
1123
  }
@@ -981,7 +1138,7 @@ class SDKClientWrapper {
981
1138
  }
982
1139
  if (error instanceof TypeError && typeof error.message === "string") {
983
1140
  if (error.message.includes("fetch") || error.message.includes("network") || error.message.includes("ECONNREFUSED")) {
984
- throw new MutagentError("NETWORK_ERROR", "Unable to connect to the Mutagent server", "Check your internet connection and verify the server URL with: mutagent config list");
1141
+ throw unreachableServerError(this.endpoint);
985
1142
  }
986
1143
  }
987
1144
  if (error instanceof Error) {
@@ -999,28 +1156,31 @@ class SDKClientWrapper {
999
1156
  } else if (optHeaders) {
1000
1157
  extraHeaders = optHeaders;
1001
1158
  }
1002
- const tenancyHeaders = {};
1003
- if (this.workspaceId)
1004
- tenancyHeaders["x-workspace-id"] = this.workspaceId;
1005
- if (this.organizationId)
1006
- tenancyHeaders["x-organization-id"] = this.organizationId;
1007
- const response = await this.recoveringFetch(`${this.endpoint}${path}`, {
1008
- ...options,
1009
- headers: {
1010
- "x-api-key": this.apiKey,
1011
- "Content-Type": "application/json",
1012
- ...tenancyHeaders,
1013
- ...extraHeaders
1014
- }
1015
- });
1159
+ let response;
1160
+ try {
1161
+ response = await fetch(`${this.endpoint}${path}`, {
1162
+ ...options,
1163
+ headers: {
1164
+ "x-api-key": this.apiKey,
1165
+ "Content-Type": "application/json",
1166
+ ...this.tenancyHeaders(),
1167
+ ...extraHeaders
1168
+ }
1169
+ });
1170
+ } catch {
1171
+ throw unreachableServerError(this.endpoint);
1172
+ }
1016
1173
  if (!response.ok) {
1174
+ const error = await response.text();
1175
+ const tenancy = tenancyFailure(response.status, error, this.tenancy);
1176
+ if (tenancy)
1177
+ throw tenancy;
1017
1178
  if (response.status === 401) {
1018
1179
  throw new AuthenticationError;
1019
1180
  }
1020
1181
  if (response.status === 403) {
1021
- throw new ApiError(403, await response.text() || "Access denied");
1182
+ throw new ApiError(403, error || "Access denied");
1022
1183
  }
1023
- const error = await response.text();
1024
1184
  const errorLower = error.toLowerCase();
1025
1185
  if (errorLower.includes("workspace") && (errorLower.includes("missing") || errorLower.includes("required")) || errorLower.includes("x-workspace-id")) {
1026
1186
  throw new WorkspaceContextError(error);
@@ -1166,11 +1326,9 @@ class SDKClientWrapper {
1166
1326
  }
1167
1327
  async getModelsCatalog() {
1168
1328
  try {
1169
- const wsId = this.workspaceId;
1170
- if (!wsId)
1329
+ if (!this.workspaceId)
1171
1330
  return { models: [] };
1172
- const qs = `?workspaceId=${encodeURIComponent(wsId)}`;
1173
- return await this.request(`/api/providers/catalog${qs}`);
1331
+ return await this.request("/api/providers/catalog");
1174
1332
  } catch {
1175
1333
  return { models: [] };
1176
1334
  }
@@ -1178,56 +1336,17 @@ class SDKClientWrapper {
1178
1336
  getCurrentWorkspaceId() {
1179
1337
  return this.workspaceId;
1180
1338
  }
1181
- getCurrentOrgId() {
1182
- return this.organizationId;
1183
- }
1184
- async ensureContext() {
1185
- const needsWorkspaceClassification = !this.workspaceId || this.workspaceResolution === "none";
1186
- if (this.organizationId && !needsWorkspaceClassification)
1187
- return;
1188
- if (!this.organizationId) {
1189
- let orgs = await fetchOrganizations(this.apiKey, this.endpoint).catch(() => []);
1190
- if (orgs.length === 0) {
1191
- await new Promise((r) => setTimeout(r, 1000));
1192
- orgs = await fetchOrganizations(this.apiKey, this.endpoint).catch(() => []);
1193
- }
1194
- if (orgs.length > 0 && orgs[0]) {
1195
- this.organizationId = orgs[0].id;
1196
- }
1197
- }
1198
- if (this.workspaceResolution === "none" && this.organizationId) {
1199
- try {
1200
- const response = await this.sdk.workspaces.listWorkspaces({ includeInactive: false });
1201
- const workspaces = toWorkspaceList(response.workspaces);
1202
- if (workspaces.length === 1 && workspaces[0]) {
1203
- this.workspaceId = workspaces[0].id;
1204
- this.workspaceResolution = "sole";
1205
- } else if (workspaces.length > 1) {
1206
- const defaultWs = workspaces.find((ws) => ws.isDefault);
1207
- if (defaultWs) {
1208
- this.workspaceId = defaultWs.id;
1209
- this.workspaceResolution = "configured";
1210
- } else {
1211
- const first = workspaces[0] ?? undefined;
1212
- if (first) {
1213
- this.workspaceId = this.workspaceId ?? first.id;
1214
- this.workspaceResolution = "ambiguous";
1215
- }
1216
- }
1217
- }
1218
- } catch {}
1219
- }
1220
- }
1221
- getWorkspaceResolution() {
1222
- return this.workspaceResolution;
1339
+ tenancyHeaders() {
1340
+ return tenancyHeaders({
1341
+ keyScope: this.tenancy.keyScope,
1342
+ workspaceId: this.workspaceId,
1343
+ workspaceOrigin: this.tenancy.workspaceOrigin,
1344
+ organizationId: this.organizationId
1345
+ });
1223
1346
  }
1224
- requireUnambiguousWorkspace() {
1225
- if (!this.workspaceId || this.workspaceResolution === "none") {
1226
- throw new MutagentError("WORKSPACE_REQUIRED", "No workspace configured.", "Set one with: mutagent config set workspace <workspace-id> (list them with: mutagent workspaces list)");
1227
- }
1228
- if (this.workspaceResolution === "ambiguous") {
1229
- throw new MutagentError("WORKSPACE_AMBIGUOUS", "Several workspaces are available and none is marked default, so the target for this write is ambiguous.", "Choose one explicitly: mutagent config set workspace <workspace-id> (list them with: mutagent workspaces list)");
1230
- }
1347
+ requireSelectedWorkspace() {
1348
+ if (!this.workspaceId)
1349
+ throw noWorkspaceSelected();
1231
1350
  return this.workspaceId;
1232
1351
  }
1233
1352
  async testProvider(id) {
@@ -1241,7 +1360,7 @@ class SDKClientWrapper {
1241
1360
  }
1242
1361
  }
1243
1362
  async createProvider(data) {
1244
- this.requireUnambiguousWorkspace();
1363
+ this.requireSelectedWorkspace();
1245
1364
  registerSecret(data.apiKey);
1246
1365
  registerSecretFields(data.credentialFields);
1247
1366
  const body = buildCreateProviderBody(data);
@@ -1256,7 +1375,7 @@ class SDKClientWrapper {
1256
1375
  }
1257
1376
  }
1258
1377
  async updateProvider(id, data) {
1259
- const workspaceId = this.requireUnambiguousWorkspace();
1378
+ const workspaceId = this.requireSelectedWorkspace();
1260
1379
  if (data.apiKey)
1261
1380
  this.debugSecrets.add(data.apiKey);
1262
1381
  registerSecret(data.apiKey);
@@ -1295,7 +1414,7 @@ class SDKClientWrapper {
1295
1414
  }
1296
1415
  }
1297
1416
  async deleteProvider(id) {
1298
- const workspaceId = this.requireUnambiguousWorkspace();
1417
+ const workspaceId = this.requireSelectedWorkspace();
1299
1418
  try {
1300
1419
  await this.sdk.llmProviders.deleteProviderConfig({ id });
1301
1420
  return { workspaceId };
@@ -1310,21 +1429,18 @@ async function getSDKClient() {
1310
1429
  if (!apiKey) {
1311
1430
  throw new AuthenticationError;
1312
1431
  }
1313
- const config = loadConfig();
1432
+ const tenancy = await resolveKeyTenancy(apiKey);
1433
+ if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
1434
+ throw noWorkspaceSelected();
1314
1435
  sdkClient = new SDKClientWrapper({
1315
1436
  apiKey,
1316
- serverURL: config.endpoint,
1317
- workspaceId: config.defaultWorkspace,
1318
- organizationId: config.defaultOrganization,
1319
- workspaceSource: config.defaultWorkspaceSource,
1320
- clearStoredOrganization: clearDefaultOrganization
1321
- });
1322
- await sdkClient.ensureContext().catch((err) => {
1323
- if (isDebugEnabled()) {
1324
- const message = err instanceof Error ? err.message : String(err);
1325
- process.stderr.write(`[mutagent] Context resolution failed: ${message}
1326
- `);
1327
- }
1437
+ serverURL: tenancy.endpoint,
1438
+ workspaceId: tenancy.workspaceId,
1439
+ workspaceName: tenancy.workspaceName,
1440
+ workspaceOrigin: tenancy.workspaceOrigin,
1441
+ organizationId: tenancy.organization?.id,
1442
+ organizationName: tenancy.organization?.name,
1443
+ keyScope: tenancy.keyScope
1328
1444
  });
1329
1445
  }
1330
1446
  return sdkClient;
@@ -1342,20 +1458,7 @@ async function validateApiKey(apiKey, endpoint) {
1342
1458
  return false;
1343
1459
  }
1344
1460
  }
1345
- async function fetchWorkspaces(apiKey, endpoint, orgId) {
1346
- try {
1347
- const response = await fetch(`${endpoint}/api/workspaces`, {
1348
- headers: { "x-api-key": apiKey, "x-organization-id": orgId }
1349
- });
1350
- if (!response.ok)
1351
- return [];
1352
- const data = await response.json();
1353
- return data.workspaces ?? [];
1354
- } catch {
1355
- return [];
1356
- }
1357
- }
1358
- var sdkClient = null;
1461
+ var SDK_TRANSPORT_ERRORS, sdkClient = null;
1359
1462
  var init_sdk_client = __esm(() => {
1360
1463
  init_errors();
1361
1464
  init_config();
@@ -1364,6 +1467,7 @@ var init_sdk_client = __esm(() => {
1364
1467
  init_known_secrets();
1365
1468
  init_sdk_mappers();
1366
1469
  init_organization_scope();
1470
+ SDK_TRANSPORT_ERRORS = new Set(["ConnectionError", "RequestTimeoutError", "RequestAbortedError", "UnexpectedClientError"]);
1367
1471
  });
1368
1472
 
1369
1473
  // src/index.ts
@@ -1587,4 +1691,4 @@ export {
1587
1691
  ApiError
1588
1692
  };
1589
1693
 
1590
- //# debugId=437AE9F8B6FD0C9364756E2164756E21
1694
+ //# debugId=6C0EFE359421B56964756E2164756E21