@mutagent/cli 0.1.358 → 0.1.360

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 +237 -118
  2. package/dist/index.js +293 -186
  3. package/package.json +2 -2
package/dist/bin/cli.js CHANGED
@@ -16519,6 +16519,187 @@ function writeSecureFile(path, contents) {
16519
16519
  var SECURE_FILE_MODE = 384;
16520
16520
  var init_secure_file = () => {};
16521
16521
 
16522
+ // src/lib/sdk-debug.ts
16523
+ function isDebugEnabled(env = process.env) {
16524
+ return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
16525
+ }
16526
+ function redactValue(value2, depth = 0) {
16527
+ if (depth > 32)
16528
+ return REDACTED;
16529
+ if (Array.isArray(value2))
16530
+ return value2.map((item) => redactValue(item, depth + 1));
16531
+ if (value2 === null || typeof value2 !== "object")
16532
+ return value2;
16533
+ const out = {};
16534
+ for (const [name, field] of Object.entries(value2)) {
16535
+ if (/^credentialfields$/i.test(name) && field !== null && typeof field === "object") {
16536
+ out[name] = Object.fromEntries(Object.keys(field).map((key) => [key, REDACTED]));
16537
+ } else if (isSensitiveFieldName(name) && field !== null && field !== undefined) {
16538
+ out[name] = REDACTED;
16539
+ } else {
16540
+ out[name] = redactValue(field, depth + 1);
16541
+ }
16542
+ }
16543
+ return out;
16544
+ }
16545
+ function redactText(text, secrets) {
16546
+ const header = HEADER_LINE.exec(text);
16547
+ let out = header?.[1] && SENSITIVE_HEADER.test(header[1]) ? `${header[1]}: ${REDACTED}` : text;
16548
+ out = out.replace(JSON_STRING_FIELD, (match, name, sep5) => isSensitiveFieldName(name) ? `"${name}"${sep5}"${REDACTED}"` : match);
16549
+ out = out.replace(AUTH_SCHEME, `$1 ${REDACTED}`);
16550
+ return scrubSecrets(out, secrets);
16551
+ }
16552
+ function format(arg, secrets) {
16553
+ if (typeof arg === "string")
16554
+ return redactText(arg, secrets);
16555
+ if (arg !== null && typeof arg === "object") {
16556
+ let json;
16557
+ try {
16558
+ json = JSON.stringify(redactValue(arg), null, 2);
16559
+ } catch {
16560
+ json = undefined;
16561
+ }
16562
+ return redactText(json ?? "<unserialisable>", secrets);
16563
+ }
16564
+ return redactText(String(arg), secrets);
16565
+ }
16566
+ function createSdkDebugLogger(opts = {}) {
16567
+ if (!isDebugEnabled(opts.env ?? process.env))
16568
+ return SILENT;
16569
+ const write = opts.write ?? ((chunk) => {
16570
+ process.stderr.write(chunk);
16571
+ });
16572
+ const secrets = opts.secrets ?? (() => []);
16573
+ let depth = 0;
16574
+ const emit = (text) => {
16575
+ const pad = " ".repeat(depth);
16576
+ write(`${text.split(`
16577
+ `).map((line) => pad + line).join(`
16578
+ `)}
16579
+ `);
16580
+ };
16581
+ return {
16582
+ group(label) {
16583
+ if (label !== undefined)
16584
+ emit(redactText(label, secrets()));
16585
+ depth += 1;
16586
+ },
16587
+ groupEnd() {
16588
+ depth = Math.max(0, depth - 1);
16589
+ },
16590
+ log(message, ...args) {
16591
+ emit([message, ...args].map((arg) => format(arg, secrets())).join(" "));
16592
+ }
16593
+ };
16594
+ }
16595
+ var TRUTHY, SENSITIVE_HEADER, HEADER_LINE, JSON_STRING_FIELD, AUTH_SCHEME, SILENT;
16596
+ var init_sdk_debug = __esm(() => {
16597
+ init_secret_scrub();
16598
+ TRUTHY = new Set(["1", "true", "yes"]);
16599
+ SENSITIVE_HEADER = /^(x-api-key|authorization|proxy-authorization|cookie|set-cookie|x-[\w-]*(?:key|token|secret|auth)[\w-]*)$/i;
16600
+ HEADER_LINE = /^([A-Za-z0-9-]+):\s(.*)$/s;
16601
+ JSON_STRING_FIELD = /"((?:[^"\\]|\\.)*)"(\s*:\s*)"(?:[^"\\]|\\.)*"/g;
16602
+ AUTH_SCHEME = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi;
16603
+ SILENT = {
16604
+ group: () => {
16605
+ return;
16606
+ },
16607
+ groupEnd: () => {
16608
+ return;
16609
+ },
16610
+ log: () => {
16611
+ return;
16612
+ }
16613
+ };
16614
+ });
16615
+
16616
+ // src/lib/organization-scope.ts
16617
+ function isWellFormedOrganizationId(id) {
16618
+ return ORGANIZATION_ID_PATTERN.test(id);
16619
+ }
16620
+ function isOrganizationScopeMismatch(status, body) {
16621
+ if (status !== 403)
16622
+ return false;
16623
+ return body.toLowerCase().includes("organization scope mismatch");
16624
+ }
16625
+ async function lookupOrganizations(apiKey, endpoint) {
16626
+ try {
16627
+ const response = await fetch(`${endpoint}/api/organizations`, {
16628
+ headers: { "x-api-key": apiKey }
16629
+ });
16630
+ if (!response.ok)
16631
+ return [];
16632
+ const data = await response.json();
16633
+ return data.data ?? [];
16634
+ } catch {
16635
+ return [];
16636
+ }
16637
+ }
16638
+ async function fetchOrganizations(apiKey, endpoint) {
16639
+ return lookupOrganizations(apiKey, endpoint);
16640
+ }
16641
+ async function assertUsableOrganizationId(id, lookup) {
16642
+ const orgs = lookup.apiKey ? await lookup.fetchOrganizations(lookup.apiKey, lookup.endpoint).catch(() => []) : [];
16643
+ if (orgs.length === 0) {
16644
+ if (isWellFormedOrganizationId(id))
16645
+ return;
16646
+ throw new MutagentError("INVALID_ORGANIZATION_ID", `"${id}" is not an organization id.`, `Organization ids look like org_xxxxxxxxxxxxxxxxxxxxx.
16647
+ ` + "Log in so the id can be checked against your account: mutagent auth login --browser");
16648
+ }
16649
+ if (orgs.some((org) => org.id === id))
16650
+ return;
16651
+ throw new MutagentError("UNKNOWN_ORGANIZATION", `"${id}" is not an organization this API key can access.`, `Available: ${orgs.map((org) => org.id).join(", ")}
16652
+ ` + "A stored organization that does not match the key makes every request fail with 403.");
16653
+ }
16654
+ function createOrgScopeRecoveringFetcher(hooks) {
16655
+ let recoveryUsed = false;
16656
+ async function recover() {
16657
+ if (recoveryUsed)
16658
+ return;
16659
+ recoveryUsed = true;
16660
+ const stale = hooks.currentOrganization();
16661
+ if (!stale)
16662
+ return;
16663
+ hooks.adoptOrganization(undefined);
16664
+ hooks.forgetStoredOrganization?.();
16665
+ const orgs = await lookupOrganizations(hooks.apiKey, hooks.endpoint);
16666
+ const resolved = orgs[0]?.id;
16667
+ if (!resolved || resolved === stale)
16668
+ return;
16669
+ hooks.adoptOrganization(resolved);
16670
+ if (isDebugEnabled()) {
16671
+ process.stderr.write(`[mutagent] Stored organization was rejected by the API; re-resolved and retried once.
16672
+ `);
16673
+ }
16674
+ return resolved;
16675
+ }
16676
+ return async function fetchWithOrgRecovery(input, init) {
16677
+ const retryTemplate = input instanceof Request ? input.clone() : undefined;
16678
+ const response = init == null ? await fetch(input) : await fetch(input, init);
16679
+ if (response.status !== 403)
16680
+ return response;
16681
+ const body = await response.clone().text().catch(() => "");
16682
+ if (!isOrganizationScopeMismatch(response.status, body))
16683
+ return response;
16684
+ const resolved = await recover();
16685
+ if (!resolved)
16686
+ return response;
16687
+ if (retryTemplate) {
16688
+ retryTemplate.headers.set("x-organization-id", resolved);
16689
+ return fetch(retryTemplate);
16690
+ }
16691
+ const headers = new Headers(init?.headers);
16692
+ headers.set("x-organization-id", resolved);
16693
+ return fetch(input, { ...init, headers });
16694
+ };
16695
+ }
16696
+ var ORGANIZATION_ID_PATTERN;
16697
+ var init_organization_scope = __esm(() => {
16698
+ init_errors();
16699
+ init_sdk_debug();
16700
+ ORGANIZATION_ID_PATTERN = /^org_[A-Za-z0-9_-]{8,}$/;
16701
+ });
16702
+
16522
16703
  // src/lib/config.ts
16523
16704
  import { cosmiconfigSync } from "cosmiconfig";
16524
16705
  import { z } from "zod";
@@ -16625,6 +16806,10 @@ function setDefaultWorkspace(workspaceId, source2 = "user") {
16625
16806
  writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
16626
16807
  }
16627
16808
  function setDefaultOrganization(organizationId) {
16809
+ if (!isWellFormedOrganizationId(organizationId)) {
16810
+ throw new MutagentError("INVALID_ORGANIZATION_ID", `"${organizationId}" is not an organization id.`, `Organization ids look like org_xxxxxxxxxxxxxxxxxxxxx.
16811
+ ` + "List the ones you can use with: mutagent auth status");
16812
+ }
16628
16813
  if (!existsSync2(CREDENTIALS_DIR)) {
16629
16814
  mkdirSync(CREDENTIALS_DIR, { recursive: true });
16630
16815
  }
@@ -16640,10 +16825,26 @@ function setDefaultOrganization(organizationId) {
16640
16825
  };
16641
16826
  writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
16642
16827
  }
16828
+ function clearDefaultOrganization() {
16829
+ if (!existsSync2(CREDENTIALS_FILE))
16830
+ return;
16831
+ let existingCredentials;
16832
+ try {
16833
+ existingCredentials = JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf-8"));
16834
+ } catch {
16835
+ return;
16836
+ }
16837
+ if (!("defaultOrganization" in existingCredentials))
16838
+ return;
16839
+ delete existingCredentials.defaultOrganization;
16840
+ writeSecureFile(CREDENTIALS_FILE, JSON.stringify(existingCredentials, null, 2));
16841
+ }
16643
16842
  var configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
16644
16843
  var init_config = __esm(() => {
16645
16844
  init_secure_file();
16646
16845
  init_known_secrets();
16846
+ init_organization_scope();
16847
+ init_errors();
16647
16848
  configSchema = z.object({
16648
16849
  apiKey: z.string().optional(),
16649
16850
  endpoint: z.string().default("https://api.mutagent.io"),
@@ -16688,100 +16889,6 @@ function buildCreateProviderBody(data) {
16688
16889
  return body;
16689
16890
  }
16690
16891
 
16691
- // src/lib/sdk-debug.ts
16692
- function isDebugEnabled(env = process.env) {
16693
- return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
16694
- }
16695
- function redactValue(value2, depth = 0) {
16696
- if (depth > 32)
16697
- return REDACTED;
16698
- if (Array.isArray(value2))
16699
- return value2.map((item) => redactValue(item, depth + 1));
16700
- if (value2 === null || typeof value2 !== "object")
16701
- return value2;
16702
- const out = {};
16703
- for (const [name, field] of Object.entries(value2)) {
16704
- if (/^credentialfields$/i.test(name) && field !== null && typeof field === "object") {
16705
- out[name] = Object.fromEntries(Object.keys(field).map((key) => [key, REDACTED]));
16706
- } else if (isSensitiveFieldName(name) && field !== null && field !== undefined) {
16707
- out[name] = REDACTED;
16708
- } else {
16709
- out[name] = redactValue(field, depth + 1);
16710
- }
16711
- }
16712
- return out;
16713
- }
16714
- function redactText(text, secrets) {
16715
- const header = HEADER_LINE.exec(text);
16716
- let out = header?.[1] && SENSITIVE_HEADER.test(header[1]) ? `${header[1]}: ${REDACTED}` : text;
16717
- out = out.replace(JSON_STRING_FIELD, (match, name, sep5) => isSensitiveFieldName(name) ? `"${name}"${sep5}"${REDACTED}"` : match);
16718
- out = out.replace(AUTH_SCHEME, `$1 ${REDACTED}`);
16719
- return scrubSecrets(out, secrets);
16720
- }
16721
- function format(arg, secrets) {
16722
- if (typeof arg === "string")
16723
- return redactText(arg, secrets);
16724
- if (arg !== null && typeof arg === "object") {
16725
- let json;
16726
- try {
16727
- json = JSON.stringify(redactValue(arg), null, 2);
16728
- } catch {
16729
- json = undefined;
16730
- }
16731
- return redactText(json ?? "<unserialisable>", secrets);
16732
- }
16733
- return redactText(String(arg), secrets);
16734
- }
16735
- function createSdkDebugLogger(opts = {}) {
16736
- if (!isDebugEnabled(opts.env ?? process.env))
16737
- return SILENT;
16738
- const write = opts.write ?? ((chunk) => {
16739
- process.stderr.write(chunk);
16740
- });
16741
- const secrets = opts.secrets ?? (() => []);
16742
- let depth = 0;
16743
- const emit = (text) => {
16744
- const pad = " ".repeat(depth);
16745
- write(`${text.split(`
16746
- `).map((line) => pad + line).join(`
16747
- `)}
16748
- `);
16749
- };
16750
- return {
16751
- group(label) {
16752
- if (label !== undefined)
16753
- emit(redactText(label, secrets()));
16754
- depth += 1;
16755
- },
16756
- groupEnd() {
16757
- depth = Math.max(0, depth - 1);
16758
- },
16759
- log(message, ...args) {
16760
- emit([message, ...args].map((arg) => format(arg, secrets())).join(" "));
16761
- }
16762
- };
16763
- }
16764
- var TRUTHY, SENSITIVE_HEADER, HEADER_LINE, JSON_STRING_FIELD, AUTH_SCHEME, SILENT;
16765
- var init_sdk_debug = __esm(() => {
16766
- init_secret_scrub();
16767
- TRUTHY = new Set(["1", "true", "yes"]);
16768
- SENSITIVE_HEADER = /^(x-api-key|authorization|proxy-authorization|cookie|set-cookie|x-[\w-]*(?:key|token|secret|auth)[\w-]*)$/i;
16769
- HEADER_LINE = /^([A-Za-z0-9-]+):\s(.*)$/s;
16770
- JSON_STRING_FIELD = /"((?:[^"\\]|\\.)*)"(\s*:\s*)"(?:[^"\\]|\\.)*"/g;
16771
- AUTH_SCHEME = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi;
16772
- SILENT = {
16773
- group: () => {
16774
- return;
16775
- },
16776
- groupEnd: () => {
16777
- return;
16778
- },
16779
- log: () => {
16780
- return;
16781
- }
16782
- };
16783
- });
16784
-
16785
16892
  // src/lib/sdk-mappers.ts
16786
16893
  import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
16787
16894
  function isProviderType(value2) {
@@ -16889,6 +16996,7 @@ class SDKClientWrapper {
16889
16996
  organizationId;
16890
16997
  workspaceResolution = "none";
16891
16998
  debugSecrets = new Set;
16999
+ recoveringFetch;
16892
17000
  constructor(opts) {
16893
17001
  this.apiKey = opts.apiKey;
16894
17002
  this.endpoint = opts.serverURL ?? "http://localhost:3003";
@@ -16897,7 +17005,16 @@ class SDKClientWrapper {
16897
17005
  if (opts.workspaceId && opts.workspaceSource === "user") {
16898
17006
  this.workspaceResolution = "configured";
16899
17007
  }
16900
- const httpClient = new HTTPClient;
17008
+ this.recoveringFetch = createOrgScopeRecoveringFetcher({
17009
+ apiKey: this.apiKey,
17010
+ endpoint: this.endpoint,
17011
+ currentOrganization: () => this.organizationId,
17012
+ adoptOrganization: (id) => {
17013
+ this.organizationId = id;
17014
+ },
17015
+ forgetStoredOrganization: opts.clearStoredOrganization
17016
+ });
17017
+ const httpClient = new HTTPClient({ fetcher: this.recoveringFetch });
16901
17018
  httpClient.addHook("beforeRequest", (req) => {
16902
17019
  if (this.workspaceId)
16903
17020
  req.headers.set("x-workspace-id", this.workspaceId);
@@ -16962,7 +17079,7 @@ class SDKClientWrapper {
16962
17079
  tenancyHeaders["x-workspace-id"] = this.workspaceId;
16963
17080
  if (this.organizationId)
16964
17081
  tenancyHeaders["x-organization-id"] = this.organizationId;
16965
- const response = await fetch(`${this.endpoint}${path}`, {
17082
+ const response = await this.recoveringFetch(`${this.endpoint}${path}`, {
16966
17083
  ...options,
16967
17084
  headers: {
16968
17085
  "x-api-key": this.apiKey,
@@ -17274,7 +17391,8 @@ async function getSDKClient() {
17274
17391
  serverURL: config.endpoint,
17275
17392
  workspaceId: config.defaultWorkspace,
17276
17393
  organizationId: config.defaultOrganization,
17277
- workspaceSource: config.defaultWorkspaceSource
17394
+ workspaceSource: config.defaultWorkspaceSource,
17395
+ clearStoredOrganization: clearDefaultOrganization
17278
17396
  });
17279
17397
  await sdkClient.ensureContext().catch((err) => {
17280
17398
  if (isDebugEnabled()) {
@@ -17299,19 +17417,6 @@ async function validateApiKey(apiKey, endpoint) {
17299
17417
  return false;
17300
17418
  }
17301
17419
  }
17302
- async function fetchOrganizations(apiKey, endpoint) {
17303
- try {
17304
- const response = await fetch(`${endpoint}/api/organizations`, {
17305
- headers: { "x-api-key": apiKey }
17306
- });
17307
- if (!response.ok)
17308
- return [];
17309
- const data = await response.json();
17310
- return data.data ?? [];
17311
- } catch {
17312
- return [];
17313
- }
17314
- }
17315
17420
  async function fetchWorkspaces(apiKey, endpoint, orgId) {
17316
17421
  try {
17317
17422
  const response = await fetch(`${endpoint}/api/workspaces`, {
@@ -17329,9 +17434,11 @@ var sdkClient = null;
17329
17434
  var init_sdk_client = __esm(() => {
17330
17435
  init_errors();
17331
17436
  init_config();
17437
+ init_organization_scope();
17332
17438
  init_sdk_debug();
17333
17439
  init_known_secrets();
17334
17440
  init_sdk_mappers();
17441
+ init_organization_scope();
17335
17442
  });
17336
17443
 
17337
17444
  // src/lib/agent/sdk.ts
@@ -19047,6 +19154,7 @@ import { Command as Command4 } from "commander";
19047
19154
  import chalk6 from "chalk";
19048
19155
  init_errors();
19049
19156
  init_sdk_client();
19157
+ init_organization_scope();
19050
19158
  var VALID_CONFIG_KEYS = ["apiKey", "endpoint", "format", "timeout", "defaultWorkspace", "defaultOrganization"];
19051
19159
  function createConfigCommand() {
19052
19160
  const config = new Command4("config").description("Manage CLI configuration").addHelpText("after", `
@@ -19123,15 +19231,26 @@ Examples:
19123
19231
  ${chalk6.dim("$")} mutagent config set org <org-id>
19124
19232
 
19125
19233
  ${chalk6.dim("Persists organization ID for org-scoped API keys.")}
19126
- `).action((id) => {
19234
+ ${chalk6.dim("The ID is checked against your account first — a stored organization the")}
19235
+ ${chalk6.dim("API key does not own makes every later command fail with 403.")}
19236
+ `).action(async (id) => {
19127
19237
  const isJson = getJsonFlag(config);
19128
19238
  const output = new OutputFormatter(isJson ? "json" : "table");
19129
- setDefaultOrganization(id);
19130
- resetSDKClient();
19131
- if (isJson) {
19132
- output.output({ success: true, defaultOrganization: id });
19133
- } else {
19134
- output.success(`Default organization set to ${id}`);
19239
+ try {
19240
+ await assertUsableOrganizationId(id, {
19241
+ apiKey: getApiKey(),
19242
+ endpoint: loadConfig().endpoint ?? "https://api.mutagent.io",
19243
+ fetchOrganizations
19244
+ });
19245
+ setDefaultOrganization(id);
19246
+ resetSDKClient();
19247
+ if (isJson) {
19248
+ output.output({ success: true, defaultOrganization: id });
19249
+ } else {
19250
+ output.success(`Default organization set to ${id}`);
19251
+ }
19252
+ } catch (error3) {
19253
+ handleError(error3, isJson);
19135
19254
  }
19136
19255
  });
19137
19256
  config.addCommand(set2);
@@ -29237,4 +29356,4 @@ if (isAgentInvocation(rawArgs)) {
29237
29356
  program.parse(argvForCommander, { from: "user" });
29238
29357
  }
29239
29358
 
29240
- //# debugId=EBAEE05DDF8C12AA64756E2164756E21
29359
+ //# debugId=76A898BDAFE9975F64756E2164756E21
package/dist/index.js CHANGED
@@ -255,152 +255,6 @@ 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 merged = {
281
- providers: {},
282
- environment: "production",
283
- ...rcConfig,
284
- ...credentials,
285
- apiKey: process.env.MUTAGENT_API_KEY ?? credentials?.apiKey ?? rcConfig?.apiKey,
286
- endpoint: process.env.MUTAGENT_ENDPOINT ?? credentials?.endpoint ?? rcConfig?.endpoint,
287
- defaultWorkspace: process.env.MUTAGENT_WORKSPACE_ID ?? credentials?.defaultWorkspace ?? rcConfig?.defaultWorkspace,
288
- defaultWorkspaceSource: process.env.MUTAGENT_WORKSPACE_ID ? "user" : credentials?.defaultWorkspace ? credentials.defaultWorkspaceSource ?? "login-inferred" : rcConfig?.defaultWorkspace ? "login-inferred" : undefined,
289
- defaultOrganization: credentials?.defaultOrganization ?? rcConfig?.defaultOrganization
290
- };
291
- return configSchema.parse(merged);
292
- }
293
- function saveCredentials(apiKey, endpoint) {
294
- if (!existsSync(CREDENTIALS_DIR)) {
295
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
296
- }
297
- const config = loadConfig();
298
- const credentials = {
299
- apiKey,
300
- endpoint: endpoint ?? config.endpoint
301
- };
302
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
303
- }
304
- function saveFullCredentials(creds) {
305
- if (!existsSync(CREDENTIALS_DIR)) {
306
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
307
- }
308
- let existingCredentials = {};
309
- if (existsSync(CREDENTIALS_FILE)) {
310
- try {
311
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
312
- } catch {}
313
- }
314
- const existingSource = existingCredentials.defaultWorkspaceSource;
315
- const workspaceChanged = creds.workspaceId !== undefined && creds.workspaceId !== existingCredentials.defaultWorkspace;
316
- const source = creds.workspaceId === undefined ? existingSource : workspaceChanged ? "login-inferred" : existingSource ?? "login-inferred";
317
- const credentials = {
318
- ...existingCredentials,
319
- apiKey: creds.apiKey,
320
- endpoint: creds.endpoint ?? existingCredentials.endpoint ?? "https://api.mutagent.io",
321
- defaultWorkspace: creds.workspaceId ?? existingCredentials.defaultWorkspace,
322
- defaultWorkspaceSource: source,
323
- defaultOrganization: creds.organizationId ?? existingCredentials.defaultOrganization,
324
- expiresAt: creds.expiresAt
325
- };
326
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
327
- }
328
- function clearCredentials() {
329
- if (existsSync(CREDENTIALS_FILE)) {
330
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify({}));
331
- }
332
- }
333
- function hasCredentials() {
334
- if (process.env.MUTAGENT_API_KEY)
335
- return true;
336
- if (!existsSync(CREDENTIALS_FILE))
337
- return false;
338
- const credentials = parseJsonSafe(readFileSync(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
339
- return credentials?.apiKey !== undefined && credentials.apiKey !== "";
340
- }
341
- function getApiKey() {
342
- const apiKey = process.env.MUTAGENT_API_KEY ?? loadConfig().apiKey;
343
- registerSecret(apiKey);
344
- return apiKey;
345
- }
346
- function setDefaultWorkspace(workspaceId, source = "user") {
347
- if (!existsSync(CREDENTIALS_DIR)) {
348
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
349
- }
350
- let existingCredentials = {};
351
- if (existsSync(CREDENTIALS_FILE)) {
352
- try {
353
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
354
- } catch {}
355
- }
356
- const updated = {
357
- ...existingCredentials,
358
- defaultWorkspace: workspaceId,
359
- defaultWorkspaceSource: source
360
- };
361
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
362
- }
363
- function setDefaultOrganization(organizationId) {
364
- if (!existsSync(CREDENTIALS_DIR)) {
365
- mkdirSync(CREDENTIALS_DIR, { recursive: true });
366
- }
367
- let existingCredentials = {};
368
- if (existsSync(CREDENTIALS_FILE)) {
369
- try {
370
- existingCredentials = JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
371
- } catch {}
372
- }
373
- const updated = {
374
- ...existingCredentials,
375
- defaultOrganization: organizationId
376
- };
377
- writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
378
- }
379
- var configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
380
- var init_config = __esm(() => {
381
- init_secure_file();
382
- init_known_secrets();
383
- configSchema = z.object({
384
- apiKey: z.string().optional(),
385
- endpoint: z.string().default("https://api.mutagent.io"),
386
- format: z.enum(["table", "json"]).default("table"),
387
- timeout: z.number().default(30000),
388
- defaultWorkspace: z.string().optional(),
389
- defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
390
- defaultOrganization: z.string().optional()
391
- });
392
- credentialsSchema = z.object({
393
- apiKey: z.string().optional(),
394
- endpoint: z.string().optional(),
395
- defaultWorkspace: z.string().optional(),
396
- defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
397
- defaultOrganization: z.string().optional(),
398
- expiresAt: z.string().optional()
399
- }).loose();
400
- CREDENTIALS_DIR = join(homedir(), ".config", "mutagent");
401
- CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
402
- });
403
-
404
258
  // src/lib/errors.ts
405
259
  function createErrorFromUnknown(error) {
406
260
  if (error instanceof MutagentError) {
@@ -590,29 +444,6 @@ var init_errors = __esm(() => {
590
444
  };
591
445
  });
592
446
 
593
- // src/lib/provider-request.ts
594
- function buildCreateProviderBody(data) {
595
- const body = {
596
- name: data.name,
597
- provider: data.provider,
598
- isDefault: data.isDefault ?? false
599
- };
600
- if (data.apiKey)
601
- body.apiKey = data.apiKey;
602
- if (data.baseUrl)
603
- body.baseUrl = data.baseUrl;
604
- if (data.hostedFamily)
605
- body.hostedFamily = data.hostedFamily;
606
- if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
607
- body.credentialFields = data.credentialFields;
608
- }
609
- if (data.metadata && Object.keys(data.metadata).length > 0)
610
- body.metadata = data.metadata;
611
- if (data.supportedModels !== undefined)
612
- body.supportedModels = data.supportedModels;
613
- return body;
614
- }
615
-
616
447
  // src/lib/sdk-debug.ts
617
448
  function isDebugEnabled(env = process.env) {
618
449
  return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
@@ -707,6 +538,282 @@ var init_sdk_debug = __esm(() => {
707
538
  };
708
539
  });
709
540
 
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
+
710
817
  // src/lib/sdk-mappers.ts
711
818
  import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
712
819
  function isProviderType(value) {
@@ -814,6 +921,7 @@ class SDKClientWrapper {
814
921
  organizationId;
815
922
  workspaceResolution = "none";
816
923
  debugSecrets = new Set;
924
+ recoveringFetch;
817
925
  constructor(opts) {
818
926
  this.apiKey = opts.apiKey;
819
927
  this.endpoint = opts.serverURL ?? "http://localhost:3003";
@@ -822,7 +930,16 @@ class SDKClientWrapper {
822
930
  if (opts.workspaceId && opts.workspaceSource === "user") {
823
931
  this.workspaceResolution = "configured";
824
932
  }
825
- const httpClient = new HTTPClient;
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 });
826
943
  httpClient.addHook("beforeRequest", (req) => {
827
944
  if (this.workspaceId)
828
945
  req.headers.set("x-workspace-id", this.workspaceId);
@@ -887,7 +1004,7 @@ class SDKClientWrapper {
887
1004
  tenancyHeaders["x-workspace-id"] = this.workspaceId;
888
1005
  if (this.organizationId)
889
1006
  tenancyHeaders["x-organization-id"] = this.organizationId;
890
- const response = await fetch(`${this.endpoint}${path}`, {
1007
+ const response = await this.recoveringFetch(`${this.endpoint}${path}`, {
891
1008
  ...options,
892
1009
  headers: {
893
1010
  "x-api-key": this.apiKey,
@@ -1199,7 +1316,8 @@ async function getSDKClient() {
1199
1316
  serverURL: config.endpoint,
1200
1317
  workspaceId: config.defaultWorkspace,
1201
1318
  organizationId: config.defaultOrganization,
1202
- workspaceSource: config.defaultWorkspaceSource
1319
+ workspaceSource: config.defaultWorkspaceSource,
1320
+ clearStoredOrganization: clearDefaultOrganization
1203
1321
  });
1204
1322
  await sdkClient.ensureContext().catch((err) => {
1205
1323
  if (isDebugEnabled()) {
@@ -1224,19 +1342,6 @@ async function validateApiKey(apiKey, endpoint) {
1224
1342
  return false;
1225
1343
  }
1226
1344
  }
1227
- async function fetchOrganizations(apiKey, endpoint) {
1228
- try {
1229
- const response = await fetch(`${endpoint}/api/organizations`, {
1230
- headers: { "x-api-key": apiKey }
1231
- });
1232
- if (!response.ok)
1233
- return [];
1234
- const data = await response.json();
1235
- return data.data ?? [];
1236
- } catch {
1237
- return [];
1238
- }
1239
- }
1240
1345
  async function fetchWorkspaces(apiKey, endpoint, orgId) {
1241
1346
  try {
1242
1347
  const response = await fetch(`${endpoint}/api/workspaces`, {
@@ -1254,9 +1359,11 @@ var sdkClient = null;
1254
1359
  var init_sdk_client = __esm(() => {
1255
1360
  init_errors();
1256
1361
  init_config();
1362
+ init_organization_scope();
1257
1363
  init_sdk_debug();
1258
1364
  init_known_secrets();
1259
1365
  init_sdk_mappers();
1366
+ init_organization_scope();
1260
1367
  });
1261
1368
 
1262
1369
  // src/index.ts
@@ -1480,4 +1587,4 @@ export {
1480
1587
  ApiError
1481
1588
  };
1482
1589
 
1483
- //# debugId=0E9FCC198FD63F3C64756E2164756E21
1590
+ //# debugId=437AE9F8B6FD0C9364756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutagent/cli",
3
- "version": "0.1.358",
3
+ "version": "0.1.360",
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": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@inquirer/prompts": "^8.7.2",
45
- "@mutagent/sdk": "0.6.2",
45
+ "@mutagent/sdk": "0.6.3",
46
46
  "chalk": "^5.3.0",
47
47
  "commander": "^12.1.0",
48
48
  "cosmiconfig": "^9.0.0",