@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.
- package/dist/bin/cli.js +945 -760
- package/dist/index.js +515 -411
- package/package.json +2 -2
package/dist/bin/cli.js
CHANGED
|
@@ -268,7 +268,7 @@ function handleError(error, isJson) {
|
|
|
268
268
|
}
|
|
269
269
|
if (error instanceof WorkspaceContextError) {
|
|
270
270
|
jsonOutput.remediation = {
|
|
271
|
-
setWorkspace: "mutagent
|
|
271
|
+
setWorkspace: "mutagent workspaces use <name>",
|
|
272
272
|
listWorkspaces: "mutagent workspaces list"
|
|
273
273
|
};
|
|
274
274
|
}
|
|
@@ -290,7 +290,7 @@ function handleError(error, isJson) {
|
|
|
290
290
|
console.log(JSON.stringify(scrubKnownDeep(jsonOutput), null, 2));
|
|
291
291
|
} else {
|
|
292
292
|
console.error(`Error: ${scrubKnown(error.message)}`);
|
|
293
|
-
if (error instanceof AuthenticationError) {
|
|
293
|
+
if (error.name === "TenancyError") {} else if (error instanceof AuthenticationError) {
|
|
294
294
|
console.error("");
|
|
295
295
|
console.error(AUTH_REMEDIATION_MESSAGE);
|
|
296
296
|
} else if (error instanceof WorkspaceContextError) {
|
|
@@ -393,14 +393,14 @@ var init_errors = __esm(() => {
|
|
|
393
393
|
}
|
|
394
394
|
};
|
|
395
395
|
WORKSPACE_REMEDIATION_MESSAGE = [
|
|
396
|
-
"
|
|
397
|
-
" mutagent
|
|
398
|
-
" mutagent workspaces list
|
|
396
|
+
"No workspace selected. To fix:",
|
|
397
|
+
" mutagent workspaces use <name> # Select the workspace every later command works in",
|
|
398
|
+
" mutagent workspaces list # Your workspaces"
|
|
399
399
|
].join(`
|
|
400
400
|
`);
|
|
401
401
|
WorkspaceContextError = class WorkspaceContextError extends MutagentError {
|
|
402
402
|
constructor(message) {
|
|
403
|
-
super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent
|
|
403
|
+
super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent workspaces use <name>", 3);
|
|
404
404
|
}
|
|
405
405
|
};
|
|
406
406
|
ValidationError = class ValidationError extends MutagentError {
|
|
@@ -16519,6 +16519,448 @@ function writeSecureFile(path, contents) {
|
|
|
16519
16519
|
var SECURE_FILE_MODE = 384;
|
|
16520
16520
|
var init_secure_file = () => {};
|
|
16521
16521
|
|
|
16522
|
+
// src/lib/config.ts
|
|
16523
|
+
import { cosmiconfigSync } from "cosmiconfig";
|
|
16524
|
+
import { z } from "zod";
|
|
16525
|
+
import { homedir as homedir2 } from "os";
|
|
16526
|
+
import { join as join7 } from "path";
|
|
16527
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync } from "fs";
|
|
16528
|
+
function parseJsonSafe(content, schema4) {
|
|
16529
|
+
try {
|
|
16530
|
+
const parsed = JSON.parse(content);
|
|
16531
|
+
return schema4.parse(parsed);
|
|
16532
|
+
} catch {
|
|
16533
|
+
return null;
|
|
16534
|
+
}
|
|
16535
|
+
}
|
|
16536
|
+
function loadConfig() {
|
|
16537
|
+
const explorer = cosmiconfigSync("mutagent");
|
|
16538
|
+
const result = explorer.search();
|
|
16539
|
+
const rcConfig = result?.config;
|
|
16540
|
+
let credentials = null;
|
|
16541
|
+
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16542
|
+
credentials = parseJsonSafe(readFileSync2(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
|
|
16543
|
+
}
|
|
16544
|
+
const envKey = process.env.MUTAGENT_API_KEY;
|
|
16545
|
+
const fileDescribesKey = envKey === undefined || envKey === credentials?.apiKey;
|
|
16546
|
+
const envWorkspace = process.env.MUTAGENT_WORKSPACE_ID?.trim() ?? "";
|
|
16547
|
+
const fileSelection = fileDescribesKey ? credentials?.defaultWorkspace : undefined;
|
|
16548
|
+
const selected = fileSelection ?? rcConfig?.defaultWorkspace;
|
|
16549
|
+
const defaultWorkspace = envWorkspace !== "" ? envWorkspace : selected;
|
|
16550
|
+
const merged = {
|
|
16551
|
+
providers: {},
|
|
16552
|
+
environment: "production",
|
|
16553
|
+
...rcConfig,
|
|
16554
|
+
...credentials,
|
|
16555
|
+
apiKey: envKey ?? credentials?.apiKey ?? rcConfig?.apiKey,
|
|
16556
|
+
endpoint: process.env.MUTAGENT_ENDPOINT ?? credentials?.endpoint ?? rcConfig?.endpoint,
|
|
16557
|
+
defaultWorkspace,
|
|
16558
|
+
workspaceOrigin: envWorkspace !== "" ? "override" : selected ? "selection" : undefined,
|
|
16559
|
+
workspaceName: envWorkspace === "" && fileSelection !== undefined && fileSelection === selected ? credentials?.workspaceName : undefined,
|
|
16560
|
+
defaultOrganization: fileDescribesKey ? credentials?.defaultOrganization ?? rcConfig?.defaultOrganization : undefined,
|
|
16561
|
+
organizationName: fileDescribesKey ? credentials?.organizationName : undefined,
|
|
16562
|
+
organizationSlug: fileDescribesKey ? credentials?.organizationSlug : undefined,
|
|
16563
|
+
keyScope: fileDescribesKey ? credentials?.keyScope : undefined
|
|
16564
|
+
};
|
|
16565
|
+
return configSchema.parse(merged);
|
|
16566
|
+
}
|
|
16567
|
+
function readCredentialsFile() {
|
|
16568
|
+
if (!existsSync2(CREDENTIALS_FILE))
|
|
16569
|
+
return {};
|
|
16570
|
+
try {
|
|
16571
|
+
return JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf-8"));
|
|
16572
|
+
} catch {
|
|
16573
|
+
return {};
|
|
16574
|
+
}
|
|
16575
|
+
}
|
|
16576
|
+
function updateCredentials(patch) {
|
|
16577
|
+
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16578
|
+
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16579
|
+
}
|
|
16580
|
+
const updated = Object.fromEntries(Object.entries({ ...readCredentialsFile(), ...patch }).filter(([key, value2]) => value2 !== undefined && key !== "defaultWorkspaceSource"));
|
|
16581
|
+
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
|
|
16582
|
+
}
|
|
16583
|
+
function saveCredentials(apiKey, endpoint) {
|
|
16584
|
+
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16585
|
+
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16586
|
+
}
|
|
16587
|
+
const config = loadConfig();
|
|
16588
|
+
const credentials = {
|
|
16589
|
+
apiKey,
|
|
16590
|
+
endpoint: endpoint ?? config.endpoint
|
|
16591
|
+
};
|
|
16592
|
+
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
|
|
16593
|
+
}
|
|
16594
|
+
function saveFullCredentials(creds) {
|
|
16595
|
+
const existing = readCredentialsFile();
|
|
16596
|
+
updateCredentials({
|
|
16597
|
+
apiKey: creds.apiKey,
|
|
16598
|
+
endpoint: creds.endpoint ?? existing.endpoint ?? "https://api.mutagent.io",
|
|
16599
|
+
defaultWorkspace: creds.workspaceId,
|
|
16600
|
+
workspaceName: creds.workspaceName,
|
|
16601
|
+
defaultOrganization: creds.organizationId,
|
|
16602
|
+
organizationName: creds.organizationName,
|
|
16603
|
+
organizationSlug: creds.organizationSlug,
|
|
16604
|
+
keyScope: creds.keyScope,
|
|
16605
|
+
expiresAt: creds.expiresAt
|
|
16606
|
+
});
|
|
16607
|
+
}
|
|
16608
|
+
function clearCredentials() {
|
|
16609
|
+
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16610
|
+
writeSecureFile(CREDENTIALS_FILE, JSON.stringify({}));
|
|
16611
|
+
}
|
|
16612
|
+
}
|
|
16613
|
+
function hasCredentials() {
|
|
16614
|
+
if (process.env.MUTAGENT_API_KEY)
|
|
16615
|
+
return true;
|
|
16616
|
+
if (!existsSync2(CREDENTIALS_FILE))
|
|
16617
|
+
return false;
|
|
16618
|
+
const credentials = parseJsonSafe(readFileSync2(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
|
|
16619
|
+
return credentials?.apiKey !== undefined && credentials.apiKey !== "";
|
|
16620
|
+
}
|
|
16621
|
+
function envKeyReplacesFile() {
|
|
16622
|
+
const envKey = process.env.MUTAGENT_API_KEY;
|
|
16623
|
+
if (envKey === undefined)
|
|
16624
|
+
return false;
|
|
16625
|
+
if (!existsSync2(CREDENTIALS_FILE))
|
|
16626
|
+
return true;
|
|
16627
|
+
const credentials = parseJsonSafe(readFileSync2(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
|
|
16628
|
+
return envKey !== credentials?.apiKey;
|
|
16629
|
+
}
|
|
16630
|
+
function getApiKey() {
|
|
16631
|
+
const apiKey = process.env.MUTAGENT_API_KEY ?? loadConfig().apiKey;
|
|
16632
|
+
registerSecret(apiKey);
|
|
16633
|
+
return apiKey;
|
|
16634
|
+
}
|
|
16635
|
+
function setWorkspaceSelection(workspaceId, workspaceName) {
|
|
16636
|
+
updateCredentials({ defaultWorkspace: workspaceId, workspaceName });
|
|
16637
|
+
}
|
|
16638
|
+
function recordKeyScope(self) {
|
|
16639
|
+
updateCredentials({
|
|
16640
|
+
keyScope: self.scope,
|
|
16641
|
+
defaultOrganization: self.organization?.id,
|
|
16642
|
+
organizationName: self.organization?.name,
|
|
16643
|
+
organizationSlug: self.organization?.slug,
|
|
16644
|
+
...self.scope === "workspace" && self.workspace ? { defaultWorkspace: self.workspace.id, workspaceName: self.workspace.name } : {}
|
|
16645
|
+
});
|
|
16646
|
+
}
|
|
16647
|
+
var keyScopeSchema, configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
|
|
16648
|
+
var init_config = __esm(() => {
|
|
16649
|
+
init_secure_file();
|
|
16650
|
+
init_known_secrets();
|
|
16651
|
+
keyScopeSchema = z.enum(["organization", "workspace", "user"]);
|
|
16652
|
+
configSchema = z.object({
|
|
16653
|
+
apiKey: z.string().optional(),
|
|
16654
|
+
endpoint: z.string().default("https://api.mutagent.io"),
|
|
16655
|
+
format: z.enum(["table", "json"]).default("table"),
|
|
16656
|
+
timeout: z.number().default(30000),
|
|
16657
|
+
defaultWorkspace: z.string().optional(),
|
|
16658
|
+
workspaceOrigin: z.enum(["override", "selection"]).optional(),
|
|
16659
|
+
workspaceName: z.string().optional(),
|
|
16660
|
+
defaultOrganization: z.string().optional(),
|
|
16661
|
+
organizationName: z.string().optional(),
|
|
16662
|
+
organizationSlug: z.string().optional(),
|
|
16663
|
+
keyScope: keyScopeSchema.optional()
|
|
16664
|
+
});
|
|
16665
|
+
credentialsSchema = z.object({
|
|
16666
|
+
apiKey: z.string().optional(),
|
|
16667
|
+
endpoint: z.string().optional(),
|
|
16668
|
+
defaultWorkspace: z.string().optional(),
|
|
16669
|
+
workspaceName: z.string().optional(),
|
|
16670
|
+
defaultOrganization: z.string().optional(),
|
|
16671
|
+
organizationName: z.string().optional(),
|
|
16672
|
+
organizationSlug: z.string().optional(),
|
|
16673
|
+
keyScope: keyScopeSchema.optional(),
|
|
16674
|
+
expiresAt: z.string().optional()
|
|
16675
|
+
}).loose();
|
|
16676
|
+
CREDENTIALS_DIR = join7(homedir2(), ".config", "mutagent");
|
|
16677
|
+
CREDENTIALS_FILE = join7(CREDENTIALS_DIR, "credentials.json");
|
|
16678
|
+
});
|
|
16679
|
+
|
|
16680
|
+
// src/lib/organization-scope.ts
|
|
16681
|
+
var exports_organization_scope = {};
|
|
16682
|
+
__export(exports_organization_scope, {
|
|
16683
|
+
unreachableServerError: () => unreachableServerError,
|
|
16684
|
+
tenancyHeaders: () => tenancyHeaders,
|
|
16685
|
+
tenancyFailure: () => tenancyFailure,
|
|
16686
|
+
setWorkspaceFlag: () => setWorkspaceFlag,
|
|
16687
|
+
setOrgAssertion: () => setOrgAssertion,
|
|
16688
|
+
resolveKeyTenancy: () => resolveKeyTenancy,
|
|
16689
|
+
otherOrganizationHint: () => otherOrganizationHint,
|
|
16690
|
+
orgMismatch: () => orgMismatch,
|
|
16691
|
+
noWorkspaceSelected: () => noWorkspaceSelected,
|
|
16692
|
+
matchesOrganization: () => matchesOrganization,
|
|
16693
|
+
looksLikeWorkspaceId: () => looksLikeWorkspaceId,
|
|
16694
|
+
listKeyWorkspaces: () => listKeyWorkspaces,
|
|
16695
|
+
findWorkspace: () => findWorkspace,
|
|
16696
|
+
fetchOrganizations: () => fetchOrganizations,
|
|
16697
|
+
fetchKeySelf: () => fetchKeySelf,
|
|
16698
|
+
envKeyHasNoSelection: () => envKeyHasNoSelection,
|
|
16699
|
+
deadKeyError: () => deadKeyError,
|
|
16700
|
+
configTenancyHeaders: () => configTenancyHeaders,
|
|
16701
|
+
TenancyError: () => TenancyError,
|
|
16702
|
+
TENANCY_DENIED: () => TENANCY_DENIED
|
|
16703
|
+
});
|
|
16704
|
+
function looksLikeWorkspaceId(value2) {
|
|
16705
|
+
return UUID_PATTERN.test(value2);
|
|
16706
|
+
}
|
|
16707
|
+
function tenancyHeaders(t) {
|
|
16708
|
+
const headers = {};
|
|
16709
|
+
if (!t.workspaceId)
|
|
16710
|
+
return headers;
|
|
16711
|
+
if (t.keyScope === "workspace") {
|
|
16712
|
+
if (t.workspaceOrigin === "override")
|
|
16713
|
+
headers["x-workspace-id"] = t.workspaceId;
|
|
16714
|
+
return headers;
|
|
16715
|
+
}
|
|
16716
|
+
headers["x-workspace-id"] = t.workspaceId;
|
|
16717
|
+
if (t.keyScope === "user" && t.organizationId)
|
|
16718
|
+
headers["x-organization-id"] = t.organizationId;
|
|
16719
|
+
return headers;
|
|
16720
|
+
}
|
|
16721
|
+
function configTenancyHeaders(config) {
|
|
16722
|
+
return tenancyHeaders({
|
|
16723
|
+
keyScope: config.keyScope,
|
|
16724
|
+
workspaceId: config.defaultWorkspace,
|
|
16725
|
+
workspaceOrigin: config.workspaceOrigin,
|
|
16726
|
+
organizationId: config.defaultOrganization
|
|
16727
|
+
});
|
|
16728
|
+
}
|
|
16729
|
+
function parseBody(body) {
|
|
16730
|
+
try {
|
|
16731
|
+
const parsed = JSON.parse(body);
|
|
16732
|
+
if (parsed && typeof parsed === "object")
|
|
16733
|
+
return parsed;
|
|
16734
|
+
} catch {}
|
|
16735
|
+
return {};
|
|
16736
|
+
}
|
|
16737
|
+
function deadKeyError() {
|
|
16738
|
+
return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login", 2);
|
|
16739
|
+
}
|
|
16740
|
+
function tenancyFailure(status, body, ctx) {
|
|
16741
|
+
const parsed = parseBody(body);
|
|
16742
|
+
if (status === 401)
|
|
16743
|
+
return deadKeyError();
|
|
16744
|
+
if (status === 403 && parsed.code === TENANCY_DENIED) {
|
|
16745
|
+
const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
|
|
16746
|
+
if (ctx.workspaceOrigin === "selection") {
|
|
16747
|
+
return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
|
|
16748
|
+
}
|
|
16749
|
+
const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
|
|
16750
|
+
return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
|
|
16751
|
+
}
|
|
16752
|
+
if (status === 403 && ctx.keyScope === "workspace" && ctx.workspaceOrigin === "override") {
|
|
16753
|
+
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)");
|
|
16754
|
+
}
|
|
16755
|
+
if (status === 400 && (parsed.message ?? body).toLowerCase().includes("x-workspace-id")) {
|
|
16756
|
+
return noWorkspaceSelected();
|
|
16757
|
+
}
|
|
16758
|
+
return;
|
|
16759
|
+
}
|
|
16760
|
+
function noWorkspaceSelected() {
|
|
16761
|
+
const fix = envKeyReplacesFile() ? ENV_KEY_WORKSPACE_FIX : "Run: mutagent workspaces use <name>";
|
|
16762
|
+
return new TenancyError("WORKSPACE_REQUIRED", "No workspace selected.", fix, 3);
|
|
16763
|
+
}
|
|
16764
|
+
function envKeyHasNoSelection() {
|
|
16765
|
+
return new TenancyError("WORKSPACE_FROM_ENV", "With MUTAGENT_API_KEY set, the workspace comes from --workspace or MUTAGENT_WORKSPACE_ID.", ENV_KEY_WORKSPACE_FIX);
|
|
16766
|
+
}
|
|
16767
|
+
async function tenancyFetch(url, init) {
|
|
16768
|
+
try {
|
|
16769
|
+
return await fetch(url, init);
|
|
16770
|
+
} catch {
|
|
16771
|
+
throw unreachableServerError(url);
|
|
16772
|
+
}
|
|
16773
|
+
}
|
|
16774
|
+
function unreachableServerError(url) {
|
|
16775
|
+
let origin = url;
|
|
16776
|
+
try {
|
|
16777
|
+
origin = new URL(url).origin;
|
|
16778
|
+
} catch {}
|
|
16779
|
+
return new MutagentError("REQUEST_FAILED", `Unable to connect to the Mutagent server at ${origin}.`, "Check your connection and the endpoint: mutagent config get endpoint");
|
|
16780
|
+
}
|
|
16781
|
+
async function fetchKeySelf(apiKey, endpoint, opts = {}) {
|
|
16782
|
+
const response = await tenancyFetch(`${endpoint}/api/api-keys/self`, {
|
|
16783
|
+
headers: { "x-api-key": apiKey, ...opts.headers }
|
|
16784
|
+
});
|
|
16785
|
+
if (!response.ok) {
|
|
16786
|
+
const body = await response.text().catch(() => "");
|
|
16787
|
+
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");
|
|
16788
|
+
}
|
|
16789
|
+
return await response.json();
|
|
16790
|
+
}
|
|
16791
|
+
async function fetchOrganizations(apiKey, endpoint) {
|
|
16792
|
+
try {
|
|
16793
|
+
const response = await fetch(`${endpoint}/api/organizations`, {
|
|
16794
|
+
headers: { "x-api-key": apiKey }
|
|
16795
|
+
});
|
|
16796
|
+
if (!response.ok)
|
|
16797
|
+
return [];
|
|
16798
|
+
const data = await response.json();
|
|
16799
|
+
return data.data ?? [];
|
|
16800
|
+
} catch {
|
|
16801
|
+
return [];
|
|
16802
|
+
}
|
|
16803
|
+
}
|
|
16804
|
+
function matchesOrganization(value2, org) {
|
|
16805
|
+
if (!org)
|
|
16806
|
+
return false;
|
|
16807
|
+
if (value2 === org.id || org.slug !== undefined && value2 === org.slug)
|
|
16808
|
+
return true;
|
|
16809
|
+
return org.name !== undefined && value2.toLowerCase() === org.name.toLowerCase();
|
|
16810
|
+
}
|
|
16811
|
+
function orgMismatch(value2, org) {
|
|
16812
|
+
const shown = org.slug ? `${org.name} (${org.slug})` : org.name;
|
|
16813
|
+
return new TenancyError("ORG_MISMATCH", `Your key is for ${shown}.`, `Run: mutagent login --org ${value2}`);
|
|
16814
|
+
}
|
|
16815
|
+
function otherOrganizationHint(wanted, keyOrg, orgs) {
|
|
16816
|
+
const others = orgs.filter((o) => o.id !== keyOrg?.id);
|
|
16817
|
+
const where = keyOrg ? ` in ${keyOrg.name}` : "";
|
|
16818
|
+
const what = `No workspace "${wanted}"${where}.`;
|
|
16819
|
+
const named = others.map((o) => `${o.name} (${o.slug ?? o.id})`).join(", ");
|
|
16820
|
+
if (others.length === 1 && others[0]) {
|
|
16821
|
+
const only = others[0];
|
|
16822
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organization: ${named}.`, `To switch: mutagent login --org ${only.slug ?? only.id}`);
|
|
16823
|
+
}
|
|
16824
|
+
if (others.length > 1) {
|
|
16825
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organizations: ${named}.`, "To switch: mutagent login --org <slug>");
|
|
16826
|
+
}
|
|
16827
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", what, "See: mutagent workspaces list");
|
|
16828
|
+
}
|
|
16829
|
+
function setOrgAssertion(value2) {
|
|
16830
|
+
const trimmed = value2?.trim();
|
|
16831
|
+
orgAssertion = trimmed === "" ? undefined : trimmed;
|
|
16832
|
+
}
|
|
16833
|
+
function setWorkspaceFlag(value2) {
|
|
16834
|
+
const trimmed = value2?.trim();
|
|
16835
|
+
workspaceFlag = trimmed === "" ? undefined : trimmed;
|
|
16836
|
+
}
|
|
16837
|
+
function endpointOf(configured) {
|
|
16838
|
+
const trimmed = configured?.trim();
|
|
16839
|
+
return (trimmed === undefined || trimmed === "" ? "https://api.mutagent.io" : trimmed).replace(/\/+$/, "");
|
|
16840
|
+
}
|
|
16841
|
+
async function probeKeyScope(apiKey, endpoint, workspaceNamed) {
|
|
16842
|
+
const fromFile = !envKeyReplacesFile();
|
|
16843
|
+
if (!fromFile && workspaceNamed && orgAssertion === undefined)
|
|
16844
|
+
return;
|
|
16845
|
+
try {
|
|
16846
|
+
const self = await fetchKeySelf(apiKey, endpoint);
|
|
16847
|
+
if (fromFile)
|
|
16848
|
+
recordKeyScope(self);
|
|
16849
|
+
return self;
|
|
16850
|
+
} catch (error3) {
|
|
16851
|
+
if (error3 instanceof MutagentError && (error3.code === "AUTH_EXPIRED" || error3.code === "REQUEST_FAILED"))
|
|
16852
|
+
throw error3;
|
|
16853
|
+
return;
|
|
16854
|
+
}
|
|
16855
|
+
}
|
|
16856
|
+
function enforceOrgAssertion(org) {
|
|
16857
|
+
if (orgAssertion === undefined)
|
|
16858
|
+
return;
|
|
16859
|
+
if (org === undefined) {
|
|
16860
|
+
throw new TenancyError("ORG_UNKNOWN", "This key's organization is unknown, so --org cannot be checked.", "Run: mutagent workspaces current");
|
|
16861
|
+
}
|
|
16862
|
+
if (!matchesOrganization(orgAssertion, org)) {
|
|
16863
|
+
throw orgMismatch(orgAssertion, org);
|
|
16864
|
+
}
|
|
16865
|
+
}
|
|
16866
|
+
async function resolveKeyTenancy(apiKey, opts = {}) {
|
|
16867
|
+
const config = loadConfig();
|
|
16868
|
+
const endpoint = endpointOf(config.endpoint);
|
|
16869
|
+
let keyScope = config.keyScope;
|
|
16870
|
+
let organization = config.defaultOrganization ? { id: config.defaultOrganization, name: config.organizationName ?? config.defaultOrganization, slug: config.organizationSlug } : undefined;
|
|
16871
|
+
let workspaceId = config.defaultWorkspace;
|
|
16872
|
+
let workspaceName = config.workspaceName;
|
|
16873
|
+
const workspaceOrigin = config.workspaceOrigin;
|
|
16874
|
+
if (keyScope === undefined && (opts.probe !== false || orgAssertion !== undefined)) {
|
|
16875
|
+
const self = await probeKeyScope(apiKey, endpoint, workspaceId !== undefined);
|
|
16876
|
+
if (self) {
|
|
16877
|
+
keyScope = self.scope;
|
|
16878
|
+
organization = self.organization ?? undefined;
|
|
16879
|
+
if (self.scope === "workspace" && self.workspace && workspaceOrigin !== "override") {
|
|
16880
|
+
workspaceId = self.workspace.id;
|
|
16881
|
+
workspaceName = self.workspace.name;
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
}
|
|
16885
|
+
enforceOrgAssertion(organization);
|
|
16886
|
+
if (opts.resolveOverride !== false && workspaceFlag !== undefined && workspaceId === workspaceFlag && !looksLikeWorkspaceId(workspaceId)) {
|
|
16887
|
+
const found = await findWorkspace(workspaceId, { apiKey, endpoint, keyScope, organization, workspaceId: config.defaultWorkspace, workspaceName });
|
|
16888
|
+
workspaceId = found.id;
|
|
16889
|
+
workspaceName = found.name;
|
|
16890
|
+
process.env.MUTAGENT_WORKSPACE_ID = found.id;
|
|
16891
|
+
}
|
|
16892
|
+
return { endpoint, keyScope, organization, workspaceId, workspaceName, workspaceOrigin };
|
|
16893
|
+
}
|
|
16894
|
+
async function listKeyWorkspaces(apiKey, endpoint, t) {
|
|
16895
|
+
const headers = { "x-api-key": apiKey };
|
|
16896
|
+
if (t.keyScope === "user" && t.organization)
|
|
16897
|
+
headers["x-organization-id"] = t.organization.id;
|
|
16898
|
+
const response = await tenancyFetch(`${endpoint}/api/workspaces`, { headers });
|
|
16899
|
+
if (!response.ok) {
|
|
16900
|
+
const body = await response.text().catch(() => "");
|
|
16901
|
+
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");
|
|
16902
|
+
}
|
|
16903
|
+
const data = await response.json();
|
|
16904
|
+
return data.workspaces ?? [];
|
|
16905
|
+
}
|
|
16906
|
+
async function findWorkspace(wanted, t) {
|
|
16907
|
+
if (t.keyScope === "workspace") {
|
|
16908
|
+
if (t.workspaceId && (wanted === t.workspaceId || wanted === t.workspaceName)) {
|
|
16909
|
+
return { id: t.workspaceId, name: t.workspaceName ?? t.workspaceId };
|
|
16910
|
+
}
|
|
16911
|
+
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)");
|
|
16912
|
+
}
|
|
16913
|
+
const rows = await listKeyWorkspaces(t.apiKey, t.endpoint, t);
|
|
16914
|
+
const byId = rows.find((w) => w.id === wanted);
|
|
16915
|
+
if (byId)
|
|
16916
|
+
return { id: byId.id, name: byId.name };
|
|
16917
|
+
let matches = rows.filter((w) => w.name === wanted || w.slug === wanted);
|
|
16918
|
+
if (matches.length === 0)
|
|
16919
|
+
matches = rows.filter((w) => w.name.toLowerCase() === wanted.toLowerCase());
|
|
16920
|
+
if (matches.length === 1 && matches[0])
|
|
16921
|
+
return { id: matches[0].id, name: matches[0].name };
|
|
16922
|
+
if (matches.length > 1) {
|
|
16923
|
+
throw new TenancyError("WORKSPACE_AMBIGUOUS", `Several workspaces are named "${wanted}": ${matches.map((w) => `${w.name} (${w.id})`).join(", ")}.`, "Run: mutagent workspaces use <id>");
|
|
16924
|
+
}
|
|
16925
|
+
const orgs = await fetchOrganizations(t.apiKey, t.endpoint);
|
|
16926
|
+
throw otherOrganizationHint(wanted, t.organization, orgs);
|
|
16927
|
+
}
|
|
16928
|
+
var TENANCY_DENIED = "TENANCY_DENIED", UUID_PATTERN, TenancyError, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag;
|
|
16929
|
+
var init_organization_scope = __esm(() => {
|
|
16930
|
+
init_errors();
|
|
16931
|
+
init_config();
|
|
16932
|
+
UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16933
|
+
TenancyError = class TenancyError extends MutagentError {
|
|
16934
|
+
constructor(code, what, fix, exitCode = 1) {
|
|
16935
|
+
super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""), exitCode);
|
|
16936
|
+
this.name = "TenancyError";
|
|
16937
|
+
}
|
|
16938
|
+
};
|
|
16939
|
+
});
|
|
16940
|
+
|
|
16941
|
+
// src/lib/provider-request.ts
|
|
16942
|
+
function buildCreateProviderBody(data) {
|
|
16943
|
+
const body = {
|
|
16944
|
+
name: data.name,
|
|
16945
|
+
provider: data.provider,
|
|
16946
|
+
isDefault: data.isDefault ?? false
|
|
16947
|
+
};
|
|
16948
|
+
if (data.apiKey)
|
|
16949
|
+
body.apiKey = data.apiKey;
|
|
16950
|
+
if (data.baseUrl)
|
|
16951
|
+
body.baseUrl = data.baseUrl;
|
|
16952
|
+
if (data.hostedFamily)
|
|
16953
|
+
body.hostedFamily = data.hostedFamily;
|
|
16954
|
+
if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
|
|
16955
|
+
body.credentialFields = data.credentialFields;
|
|
16956
|
+
}
|
|
16957
|
+
if (data.metadata && Object.keys(data.metadata).length > 0)
|
|
16958
|
+
body.metadata = data.metadata;
|
|
16959
|
+
if (data.supportedModels !== undefined)
|
|
16960
|
+
body.supportedModels = data.supportedModels;
|
|
16961
|
+
return body;
|
|
16962
|
+
}
|
|
16963
|
+
|
|
16522
16964
|
// src/lib/sdk-debug.ts
|
|
16523
16965
|
function isDebugEnabled(env = process.env) {
|
|
16524
16966
|
return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
|
|
@@ -16613,315 +17055,39 @@ var init_sdk_debug = __esm(() => {
|
|
|
16613
17055
|
};
|
|
16614
17056
|
});
|
|
16615
17057
|
|
|
16616
|
-
// src/lib/
|
|
16617
|
-
|
|
16618
|
-
|
|
17058
|
+
// src/lib/sdk-mappers.ts
|
|
17059
|
+
import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
|
|
17060
|
+
function isProviderType(value2) {
|
|
17061
|
+
return PROVIDER_TYPES.includes(value2);
|
|
16619
17062
|
}
|
|
16620
|
-
function
|
|
16621
|
-
|
|
16622
|
-
return false;
|
|
16623
|
-
return body.toLowerCase().includes("organization scope mismatch");
|
|
17063
|
+
function narrowProviderType(value2) {
|
|
17064
|
+
return isProviderType(value2) ? value2 : value2;
|
|
16624
17065
|
}
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
});
|
|
16630
|
-
if (!response.ok)
|
|
16631
|
-
return [];
|
|
16632
|
-
const data = await response.json();
|
|
16633
|
-
return data.data ?? [];
|
|
16634
|
-
} catch {
|
|
16635
|
-
return [];
|
|
16636
|
-
}
|
|
17066
|
+
function toIsoString(value2) {
|
|
17067
|
+
if (value2 === null || value2 === undefined)
|
|
17068
|
+
return null;
|
|
17069
|
+
return value2 instanceof Date ? value2.toISOString() : value2;
|
|
16637
17070
|
}
|
|
16638
|
-
|
|
16639
|
-
return
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
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
|
-
|
|
16703
|
-
// src/lib/config.ts
|
|
16704
|
-
import { cosmiconfigSync } from "cosmiconfig";
|
|
16705
|
-
import { z } from "zod";
|
|
16706
|
-
import { homedir as homedir2 } from "os";
|
|
16707
|
-
import { join as join7 } from "path";
|
|
16708
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync } from "fs";
|
|
16709
|
-
function parseJsonSafe(content, schema4) {
|
|
16710
|
-
try {
|
|
16711
|
-
const parsed = JSON.parse(content);
|
|
16712
|
-
return schema4.parse(parsed);
|
|
16713
|
-
} catch {
|
|
16714
|
-
return null;
|
|
16715
|
-
}
|
|
16716
|
-
}
|
|
16717
|
-
function loadConfig() {
|
|
16718
|
-
const explorer = cosmiconfigSync("mutagent");
|
|
16719
|
-
const result = explorer.search();
|
|
16720
|
-
const rcConfig = result?.config;
|
|
16721
|
-
let credentials = null;
|
|
16722
|
-
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16723
|
-
credentials = parseJsonSafe(readFileSync2(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
|
|
16724
|
-
}
|
|
16725
|
-
const merged = {
|
|
16726
|
-
providers: {},
|
|
16727
|
-
environment: "production",
|
|
16728
|
-
...rcConfig,
|
|
16729
|
-
...credentials,
|
|
16730
|
-
apiKey: process.env.MUTAGENT_API_KEY ?? credentials?.apiKey ?? rcConfig?.apiKey,
|
|
16731
|
-
endpoint: process.env.MUTAGENT_ENDPOINT ?? credentials?.endpoint ?? rcConfig?.endpoint,
|
|
16732
|
-
defaultWorkspace: process.env.MUTAGENT_WORKSPACE_ID ?? credentials?.defaultWorkspace ?? rcConfig?.defaultWorkspace,
|
|
16733
|
-
defaultWorkspaceSource: process.env.MUTAGENT_WORKSPACE_ID ? "user" : credentials?.defaultWorkspace ? credentials.defaultWorkspaceSource ?? "login-inferred" : rcConfig?.defaultWorkspace ? "login-inferred" : undefined,
|
|
16734
|
-
defaultOrganization: credentials?.defaultOrganization ?? rcConfig?.defaultOrganization
|
|
16735
|
-
};
|
|
16736
|
-
return configSchema.parse(merged);
|
|
16737
|
-
}
|
|
16738
|
-
function saveCredentials(apiKey, endpoint) {
|
|
16739
|
-
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16740
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16741
|
-
}
|
|
16742
|
-
const config = loadConfig();
|
|
16743
|
-
const credentials = {
|
|
16744
|
-
apiKey,
|
|
16745
|
-
endpoint: endpoint ?? config.endpoint
|
|
16746
|
-
};
|
|
16747
|
-
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
|
|
16748
|
-
}
|
|
16749
|
-
function saveFullCredentials(creds) {
|
|
16750
|
-
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16751
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16752
|
-
}
|
|
16753
|
-
let existingCredentials = {};
|
|
16754
|
-
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16755
|
-
try {
|
|
16756
|
-
existingCredentials = JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf-8"));
|
|
16757
|
-
} catch {}
|
|
16758
|
-
}
|
|
16759
|
-
const existingSource = existingCredentials.defaultWorkspaceSource;
|
|
16760
|
-
const workspaceChanged = creds.workspaceId !== undefined && creds.workspaceId !== existingCredentials.defaultWorkspace;
|
|
16761
|
-
const source2 = creds.workspaceId === undefined ? existingSource : workspaceChanged ? "login-inferred" : existingSource ?? "login-inferred";
|
|
16762
|
-
const credentials = {
|
|
16763
|
-
...existingCredentials,
|
|
16764
|
-
apiKey: creds.apiKey,
|
|
16765
|
-
endpoint: creds.endpoint ?? existingCredentials.endpoint ?? "https://api.mutagent.io",
|
|
16766
|
-
defaultWorkspace: creds.workspaceId ?? existingCredentials.defaultWorkspace,
|
|
16767
|
-
defaultWorkspaceSource: source2,
|
|
16768
|
-
defaultOrganization: creds.organizationId ?? existingCredentials.defaultOrganization,
|
|
16769
|
-
expiresAt: creds.expiresAt
|
|
16770
|
-
};
|
|
16771
|
-
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
|
|
16772
|
-
}
|
|
16773
|
-
function clearCredentials() {
|
|
16774
|
-
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16775
|
-
writeSecureFile(CREDENTIALS_FILE, JSON.stringify({}));
|
|
16776
|
-
}
|
|
16777
|
-
}
|
|
16778
|
-
function hasCredentials() {
|
|
16779
|
-
if (process.env.MUTAGENT_API_KEY)
|
|
16780
|
-
return true;
|
|
16781
|
-
if (!existsSync2(CREDENTIALS_FILE))
|
|
16782
|
-
return false;
|
|
16783
|
-
const credentials = parseJsonSafe(readFileSync2(CREDENTIALS_FILE, "utf-8"), credentialsSchema);
|
|
16784
|
-
return credentials?.apiKey !== undefined && credentials.apiKey !== "";
|
|
16785
|
-
}
|
|
16786
|
-
function getApiKey() {
|
|
16787
|
-
const apiKey = process.env.MUTAGENT_API_KEY ?? loadConfig().apiKey;
|
|
16788
|
-
registerSecret(apiKey);
|
|
16789
|
-
return apiKey;
|
|
16790
|
-
}
|
|
16791
|
-
function setDefaultWorkspace(workspaceId, source2 = "user") {
|
|
16792
|
-
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16793
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16794
|
-
}
|
|
16795
|
-
let existingCredentials = {};
|
|
16796
|
-
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16797
|
-
try {
|
|
16798
|
-
existingCredentials = JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf-8"));
|
|
16799
|
-
} catch {}
|
|
16800
|
-
}
|
|
16801
|
-
const updated = {
|
|
16802
|
-
...existingCredentials,
|
|
16803
|
-
defaultWorkspace: workspaceId,
|
|
16804
|
-
defaultWorkspaceSource: source2
|
|
16805
|
-
};
|
|
16806
|
-
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
|
|
16807
|
-
}
|
|
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
|
-
}
|
|
16813
|
-
if (!existsSync2(CREDENTIALS_DIR)) {
|
|
16814
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
16815
|
-
}
|
|
16816
|
-
let existingCredentials = {};
|
|
16817
|
-
if (existsSync2(CREDENTIALS_FILE)) {
|
|
16818
|
-
try {
|
|
16819
|
-
existingCredentials = JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf-8"));
|
|
16820
|
-
} catch {}
|
|
16821
|
-
}
|
|
16822
|
-
const updated = {
|
|
16823
|
-
...existingCredentials,
|
|
16824
|
-
defaultOrganization: organizationId
|
|
16825
|
-
};
|
|
16826
|
-
writeSecureFile(CREDENTIALS_FILE, JSON.stringify(updated, null, 2));
|
|
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
|
-
}
|
|
16842
|
-
var configSchema, credentialsSchema, CREDENTIALS_DIR, CREDENTIALS_FILE;
|
|
16843
|
-
var init_config = __esm(() => {
|
|
16844
|
-
init_secure_file();
|
|
16845
|
-
init_known_secrets();
|
|
16846
|
-
init_organization_scope();
|
|
16847
|
-
init_errors();
|
|
16848
|
-
configSchema = z.object({
|
|
16849
|
-
apiKey: z.string().optional(),
|
|
16850
|
-
endpoint: z.string().default("https://api.mutagent.io"),
|
|
16851
|
-
format: z.enum(["table", "json"]).default("table"),
|
|
16852
|
-
timeout: z.number().default(30000),
|
|
16853
|
-
defaultWorkspace: z.string().optional(),
|
|
16854
|
-
defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
|
|
16855
|
-
defaultOrganization: z.string().optional()
|
|
16856
|
-
});
|
|
16857
|
-
credentialsSchema = z.object({
|
|
16858
|
-
apiKey: z.string().optional(),
|
|
16859
|
-
endpoint: z.string().optional(),
|
|
16860
|
-
defaultWorkspace: z.string().optional(),
|
|
16861
|
-
defaultWorkspaceSource: z.enum(["user", "login-inferred"]).optional(),
|
|
16862
|
-
defaultOrganization: z.string().optional(),
|
|
16863
|
-
expiresAt: z.string().optional()
|
|
16864
|
-
}).loose();
|
|
16865
|
-
CREDENTIALS_DIR = join7(homedir2(), ".config", "mutagent");
|
|
16866
|
-
CREDENTIALS_FILE = join7(CREDENTIALS_DIR, "credentials.json");
|
|
16867
|
-
});
|
|
16868
|
-
|
|
16869
|
-
// src/lib/provider-request.ts
|
|
16870
|
-
function buildCreateProviderBody(data) {
|
|
16871
|
-
const body = {
|
|
16872
|
-
name: data.name,
|
|
16873
|
-
provider: data.provider,
|
|
16874
|
-
isDefault: data.isDefault ?? false
|
|
16875
|
-
};
|
|
16876
|
-
if (data.apiKey)
|
|
16877
|
-
body.apiKey = data.apiKey;
|
|
16878
|
-
if (data.baseUrl)
|
|
16879
|
-
body.baseUrl = data.baseUrl;
|
|
16880
|
-
if (data.hostedFamily)
|
|
16881
|
-
body.hostedFamily = data.hostedFamily;
|
|
16882
|
-
if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
|
|
16883
|
-
body.credentialFields = data.credentialFields;
|
|
16884
|
-
}
|
|
16885
|
-
if (data.metadata && Object.keys(data.metadata).length > 0)
|
|
16886
|
-
body.metadata = data.metadata;
|
|
16887
|
-
if (data.supportedModels !== undefined)
|
|
16888
|
-
body.supportedModels = data.supportedModels;
|
|
16889
|
-
return body;
|
|
16890
|
-
}
|
|
16891
|
-
|
|
16892
|
-
// src/lib/sdk-mappers.ts
|
|
16893
|
-
import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
|
|
16894
|
-
function isProviderType(value2) {
|
|
16895
|
-
return PROVIDER_TYPES.includes(value2);
|
|
16896
|
-
}
|
|
16897
|
-
function narrowProviderType(value2) {
|
|
16898
|
-
return isProviderType(value2) ? value2 : value2;
|
|
16899
|
-
}
|
|
16900
|
-
function toIsoString(value2) {
|
|
16901
|
-
if (value2 === null || value2 === undefined)
|
|
16902
|
-
return null;
|
|
16903
|
-
return value2 instanceof Date ? value2.toISOString() : value2;
|
|
16904
|
-
}
|
|
16905
|
-
function toAgent(sdk) {
|
|
16906
|
-
return {
|
|
16907
|
-
id: sdk.id,
|
|
16908
|
-
name: sdk.name,
|
|
16909
|
-
slug: sdk.slug,
|
|
16910
|
-
description: sdk.description,
|
|
16911
|
-
systemPrompt: sdk.systemPrompt,
|
|
16912
|
-
model: sdk.model,
|
|
16913
|
-
maxTurns: sdk.maxTurns,
|
|
16914
|
-
permissionMode: sdk.permissionMode,
|
|
16915
|
-
allowedTools: sdk.allowedTools,
|
|
16916
|
-
disallowedTools: sdk.disallowedTools,
|
|
16917
|
-
status: sdk.status,
|
|
16918
|
-
isPublic: sdk.isPublic,
|
|
16919
|
-
metadata: sdk.metadata,
|
|
16920
|
-
tags: sdk.tags,
|
|
16921
|
-
createdBy: sdk.createdBy,
|
|
16922
|
-
createdAt: toIsoString(sdk.createdAt),
|
|
16923
|
-
updatedAt: toIsoString(sdk.updatedAt)
|
|
16924
|
-
};
|
|
17071
|
+
function toAgent(sdk) {
|
|
17072
|
+
return {
|
|
17073
|
+
id: sdk.id,
|
|
17074
|
+
name: sdk.name,
|
|
17075
|
+
slug: sdk.slug,
|
|
17076
|
+
description: sdk.description,
|
|
17077
|
+
systemPrompt: sdk.systemPrompt,
|
|
17078
|
+
model: sdk.model,
|
|
17079
|
+
maxTurns: sdk.maxTurns,
|
|
17080
|
+
permissionMode: sdk.permissionMode,
|
|
17081
|
+
allowedTools: sdk.allowedTools,
|
|
17082
|
+
disallowedTools: sdk.disallowedTools,
|
|
17083
|
+
status: sdk.status,
|
|
17084
|
+
isPublic: sdk.isPublic,
|
|
17085
|
+
metadata: sdk.metadata,
|
|
17086
|
+
tags: sdk.tags,
|
|
17087
|
+
createdBy: sdk.createdBy,
|
|
17088
|
+
createdAt: toIsoString(sdk.createdAt),
|
|
17089
|
+
updatedAt: toIsoString(sdk.updatedAt)
|
|
17090
|
+
};
|
|
16925
17091
|
}
|
|
16926
17092
|
function toAgentList(sdk) {
|
|
16927
17093
|
return sdk.map(toAgent);
|
|
@@ -16977,15 +17143,6 @@ var init_sdk_mappers = __esm(() => {
|
|
|
16977
17143
|
});
|
|
16978
17144
|
|
|
16979
17145
|
// src/lib/sdk-client.ts
|
|
16980
|
-
var exports_sdk_client = {};
|
|
16981
|
-
__export(exports_sdk_client, {
|
|
16982
|
-
validateApiKey: () => validateApiKey,
|
|
16983
|
-
resetSDKClient: () => resetSDKClient,
|
|
16984
|
-
getSDKClient: () => getSDKClient,
|
|
16985
|
-
fetchWorkspaces: () => fetchWorkspaces,
|
|
16986
|
-
fetchOrganizations: () => fetchOrganizations,
|
|
16987
|
-
MutagentSDK: () => SDKClientWrapper
|
|
16988
|
-
});
|
|
16989
17146
|
import { Mutagent, HTTPClient } from "@mutagent/sdk";
|
|
16990
17147
|
|
|
16991
17148
|
class SDKClientWrapper {
|
|
@@ -16994,32 +17151,24 @@ class SDKClientWrapper {
|
|
|
16994
17151
|
endpoint;
|
|
16995
17152
|
workspaceId;
|
|
16996
17153
|
organizationId;
|
|
16997
|
-
|
|
17154
|
+
tenancy;
|
|
16998
17155
|
debugSecrets = new Set;
|
|
16999
|
-
recoveringFetch;
|
|
17000
17156
|
constructor(opts) {
|
|
17001
17157
|
this.apiKey = opts.apiKey;
|
|
17002
17158
|
this.endpoint = opts.serverURL ?? "http://localhost:3003";
|
|
17003
17159
|
this.workspaceId = opts.workspaceId;
|
|
17004
17160
|
this.organizationId = opts.organizationId;
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
this.organizationId = id;
|
|
17014
|
-
},
|
|
17015
|
-
forgetStoredOrganization: opts.clearStoredOrganization
|
|
17016
|
-
});
|
|
17017
|
-
const httpClient = new HTTPClient({ fetcher: this.recoveringFetch });
|
|
17161
|
+
this.tenancy = {
|
|
17162
|
+
keyScope: opts.keyScope,
|
|
17163
|
+
workspaceId: opts.workspaceId,
|
|
17164
|
+
workspaceName: opts.workspaceName,
|
|
17165
|
+
workspaceOrigin: opts.workspaceOrigin ?? (opts.workspaceId ? "selection" : undefined),
|
|
17166
|
+
organizationName: opts.organizationName
|
|
17167
|
+
};
|
|
17168
|
+
const httpClient = new HTTPClient;
|
|
17018
17169
|
httpClient.addHook("beforeRequest", (req) => {
|
|
17019
|
-
|
|
17020
|
-
req.headers.set(
|
|
17021
|
-
if (this.organizationId)
|
|
17022
|
-
req.headers.set("x-organization-id", this.organizationId);
|
|
17170
|
+
for (const [name, value2] of Object.entries(this.tenancyHeaders()))
|
|
17171
|
+
req.headers.set(name, value2);
|
|
17023
17172
|
return req;
|
|
17024
17173
|
});
|
|
17025
17174
|
this.debugSecrets.add(this.apiKey);
|
|
@@ -17034,8 +17183,16 @@ class SDKClientWrapper {
|
|
|
17034
17183
|
});
|
|
17035
17184
|
}
|
|
17036
17185
|
handleError(error3) {
|
|
17186
|
+
if (error3 instanceof Error && SDK_TRANSPORT_ERRORS.has(error3.name)) {
|
|
17187
|
+
throw unreachableServerError(this.endpoint);
|
|
17188
|
+
}
|
|
17037
17189
|
if (error3 && typeof error3 === "object") {
|
|
17038
17190
|
const err = error3;
|
|
17191
|
+
if (err.statusCode) {
|
|
17192
|
+
const tenancy = tenancyFailure(err.statusCode, err.body ?? "", this.tenancy);
|
|
17193
|
+
if (tenancy)
|
|
17194
|
+
throw tenancy;
|
|
17195
|
+
}
|
|
17039
17196
|
if (err.statusCode === 401) {
|
|
17040
17197
|
throw new AuthenticationError;
|
|
17041
17198
|
}
|
|
@@ -17056,7 +17213,7 @@ class SDKClientWrapper {
|
|
|
17056
17213
|
}
|
|
17057
17214
|
if (error3 instanceof TypeError && typeof error3.message === "string") {
|
|
17058
17215
|
if (error3.message.includes("fetch") || error3.message.includes("network") || error3.message.includes("ECONNREFUSED")) {
|
|
17059
|
-
throw
|
|
17216
|
+
throw unreachableServerError(this.endpoint);
|
|
17060
17217
|
}
|
|
17061
17218
|
}
|
|
17062
17219
|
if (error3 instanceof Error) {
|
|
@@ -17074,28 +17231,31 @@ class SDKClientWrapper {
|
|
|
17074
17231
|
} else if (optHeaders) {
|
|
17075
17232
|
extraHeaders = optHeaders;
|
|
17076
17233
|
}
|
|
17077
|
-
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
}
|
|
17234
|
+
let response;
|
|
17235
|
+
try {
|
|
17236
|
+
response = await fetch(`${this.endpoint}${path}`, {
|
|
17237
|
+
...options,
|
|
17238
|
+
headers: {
|
|
17239
|
+
"x-api-key": this.apiKey,
|
|
17240
|
+
"Content-Type": "application/json",
|
|
17241
|
+
...this.tenancyHeaders(),
|
|
17242
|
+
...extraHeaders
|
|
17243
|
+
}
|
|
17244
|
+
});
|
|
17245
|
+
} catch {
|
|
17246
|
+
throw unreachableServerError(this.endpoint);
|
|
17247
|
+
}
|
|
17091
17248
|
if (!response.ok) {
|
|
17249
|
+
const error3 = await response.text();
|
|
17250
|
+
const tenancy = tenancyFailure(response.status, error3, this.tenancy);
|
|
17251
|
+
if (tenancy)
|
|
17252
|
+
throw tenancy;
|
|
17092
17253
|
if (response.status === 401) {
|
|
17093
17254
|
throw new AuthenticationError;
|
|
17094
17255
|
}
|
|
17095
17256
|
if (response.status === 403) {
|
|
17096
|
-
throw new ApiError(403,
|
|
17257
|
+
throw new ApiError(403, error3 || "Access denied");
|
|
17097
17258
|
}
|
|
17098
|
-
const error3 = await response.text();
|
|
17099
17259
|
const errorLower = error3.toLowerCase();
|
|
17100
17260
|
if (errorLower.includes("workspace") && (errorLower.includes("missing") || errorLower.includes("required")) || errorLower.includes("x-workspace-id")) {
|
|
17101
17261
|
throw new WorkspaceContextError(error3);
|
|
@@ -17241,11 +17401,9 @@ class SDKClientWrapper {
|
|
|
17241
17401
|
}
|
|
17242
17402
|
async getModelsCatalog() {
|
|
17243
17403
|
try {
|
|
17244
|
-
|
|
17245
|
-
if (!wsId)
|
|
17404
|
+
if (!this.workspaceId)
|
|
17246
17405
|
return { models: [] };
|
|
17247
|
-
|
|
17248
|
-
return await this.request(`/api/providers/catalog${qs}`);
|
|
17406
|
+
return await this.request("/api/providers/catalog");
|
|
17249
17407
|
} catch {
|
|
17250
17408
|
return { models: [] };
|
|
17251
17409
|
}
|
|
@@ -17253,56 +17411,17 @@ class SDKClientWrapper {
|
|
|
17253
17411
|
getCurrentWorkspaceId() {
|
|
17254
17412
|
return this.workspaceId;
|
|
17255
17413
|
}
|
|
17256
|
-
|
|
17257
|
-
return
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
if (!this.organizationId) {
|
|
17264
|
-
let orgs = await fetchOrganizations(this.apiKey, this.endpoint).catch(() => []);
|
|
17265
|
-
if (orgs.length === 0) {
|
|
17266
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
17267
|
-
orgs = await fetchOrganizations(this.apiKey, this.endpoint).catch(() => []);
|
|
17268
|
-
}
|
|
17269
|
-
if (orgs.length > 0 && orgs[0]) {
|
|
17270
|
-
this.organizationId = orgs[0].id;
|
|
17271
|
-
}
|
|
17272
|
-
}
|
|
17273
|
-
if (this.workspaceResolution === "none" && this.organizationId) {
|
|
17274
|
-
try {
|
|
17275
|
-
const response = await this.sdk.workspaces.listWorkspaces({ includeInactive: false });
|
|
17276
|
-
const workspaces = toWorkspaceList(response.workspaces);
|
|
17277
|
-
if (workspaces.length === 1 && workspaces[0]) {
|
|
17278
|
-
this.workspaceId = workspaces[0].id;
|
|
17279
|
-
this.workspaceResolution = "sole";
|
|
17280
|
-
} else if (workspaces.length > 1) {
|
|
17281
|
-
const defaultWs = workspaces.find((ws) => ws.isDefault);
|
|
17282
|
-
if (defaultWs) {
|
|
17283
|
-
this.workspaceId = defaultWs.id;
|
|
17284
|
-
this.workspaceResolution = "configured";
|
|
17285
|
-
} else {
|
|
17286
|
-
const first = workspaces[0] ?? undefined;
|
|
17287
|
-
if (first) {
|
|
17288
|
-
this.workspaceId = this.workspaceId ?? first.id;
|
|
17289
|
-
this.workspaceResolution = "ambiguous";
|
|
17290
|
-
}
|
|
17291
|
-
}
|
|
17292
|
-
}
|
|
17293
|
-
} catch {}
|
|
17294
|
-
}
|
|
17295
|
-
}
|
|
17296
|
-
getWorkspaceResolution() {
|
|
17297
|
-
return this.workspaceResolution;
|
|
17414
|
+
tenancyHeaders() {
|
|
17415
|
+
return tenancyHeaders({
|
|
17416
|
+
keyScope: this.tenancy.keyScope,
|
|
17417
|
+
workspaceId: this.workspaceId,
|
|
17418
|
+
workspaceOrigin: this.tenancy.workspaceOrigin,
|
|
17419
|
+
organizationId: this.organizationId
|
|
17420
|
+
});
|
|
17298
17421
|
}
|
|
17299
|
-
|
|
17300
|
-
if (!this.workspaceId
|
|
17301
|
-
throw
|
|
17302
|
-
}
|
|
17303
|
-
if (this.workspaceResolution === "ambiguous") {
|
|
17304
|
-
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)");
|
|
17305
|
-
}
|
|
17422
|
+
requireSelectedWorkspace() {
|
|
17423
|
+
if (!this.workspaceId)
|
|
17424
|
+
throw noWorkspaceSelected();
|
|
17306
17425
|
return this.workspaceId;
|
|
17307
17426
|
}
|
|
17308
17427
|
async testProvider(id) {
|
|
@@ -17316,7 +17435,7 @@ class SDKClientWrapper {
|
|
|
17316
17435
|
}
|
|
17317
17436
|
}
|
|
17318
17437
|
async createProvider(data) {
|
|
17319
|
-
this.
|
|
17438
|
+
this.requireSelectedWorkspace();
|
|
17320
17439
|
registerSecret(data.apiKey);
|
|
17321
17440
|
registerSecretFields(data.credentialFields);
|
|
17322
17441
|
const body = buildCreateProviderBody(data);
|
|
@@ -17331,7 +17450,7 @@ class SDKClientWrapper {
|
|
|
17331
17450
|
}
|
|
17332
17451
|
}
|
|
17333
17452
|
async updateProvider(id, data) {
|
|
17334
|
-
const workspaceId = this.
|
|
17453
|
+
const workspaceId = this.requireSelectedWorkspace();
|
|
17335
17454
|
if (data.apiKey)
|
|
17336
17455
|
this.debugSecrets.add(data.apiKey);
|
|
17337
17456
|
registerSecret(data.apiKey);
|
|
@@ -17370,7 +17489,7 @@ class SDKClientWrapper {
|
|
|
17370
17489
|
}
|
|
17371
17490
|
}
|
|
17372
17491
|
async deleteProvider(id) {
|
|
17373
|
-
const workspaceId = this.
|
|
17492
|
+
const workspaceId = this.requireSelectedWorkspace();
|
|
17374
17493
|
try {
|
|
17375
17494
|
await this.sdk.llmProviders.deleteProviderConfig({ id });
|
|
17376
17495
|
return { workspaceId };
|
|
@@ -17385,21 +17504,18 @@ async function getSDKClient() {
|
|
|
17385
17504
|
if (!apiKey) {
|
|
17386
17505
|
throw new AuthenticationError;
|
|
17387
17506
|
}
|
|
17388
|
-
const
|
|
17507
|
+
const tenancy = await resolveKeyTenancy(apiKey);
|
|
17508
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
17509
|
+
throw noWorkspaceSelected();
|
|
17389
17510
|
sdkClient = new SDKClientWrapper({
|
|
17390
17511
|
apiKey,
|
|
17391
|
-
serverURL:
|
|
17392
|
-
workspaceId:
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
if (isDebugEnabled()) {
|
|
17399
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
17400
|
-
process.stderr.write(`[mutagent] Context resolution failed: ${message}
|
|
17401
|
-
`);
|
|
17402
|
-
}
|
|
17512
|
+
serverURL: tenancy.endpoint,
|
|
17513
|
+
workspaceId: tenancy.workspaceId,
|
|
17514
|
+
workspaceName: tenancy.workspaceName,
|
|
17515
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
17516
|
+
organizationId: tenancy.organization?.id,
|
|
17517
|
+
organizationName: tenancy.organization?.name,
|
|
17518
|
+
keyScope: tenancy.keyScope
|
|
17403
17519
|
});
|
|
17404
17520
|
}
|
|
17405
17521
|
return sdkClient;
|
|
@@ -17417,20 +17533,7 @@ async function validateApiKey(apiKey, endpoint) {
|
|
|
17417
17533
|
return false;
|
|
17418
17534
|
}
|
|
17419
17535
|
}
|
|
17420
|
-
|
|
17421
|
-
try {
|
|
17422
|
-
const response = await fetch(`${endpoint}/api/workspaces`, {
|
|
17423
|
-
headers: { "x-api-key": apiKey, "x-organization-id": orgId }
|
|
17424
|
-
});
|
|
17425
|
-
if (!response.ok)
|
|
17426
|
-
return [];
|
|
17427
|
-
const data = await response.json();
|
|
17428
|
-
return data.workspaces ?? [];
|
|
17429
|
-
} catch {
|
|
17430
|
-
return [];
|
|
17431
|
-
}
|
|
17432
|
-
}
|
|
17433
|
-
var sdkClient = null;
|
|
17536
|
+
var SDK_TRANSPORT_ERRORS, sdkClient = null;
|
|
17434
17537
|
var init_sdk_client = __esm(() => {
|
|
17435
17538
|
init_errors();
|
|
17436
17539
|
init_config();
|
|
@@ -17439,6 +17542,7 @@ var init_sdk_client = __esm(() => {
|
|
|
17439
17542
|
init_known_secrets();
|
|
17440
17543
|
init_sdk_mappers();
|
|
17441
17544
|
init_organization_scope();
|
|
17545
|
+
SDK_TRANSPORT_ERRORS = new Set(["ConnectionError", "RequestTimeoutError", "RequestAbortedError", "UnexpectedClientError"]);
|
|
17442
17546
|
});
|
|
17443
17547
|
|
|
17444
17548
|
// src/lib/agent/sdk.ts
|
|
@@ -17558,7 +17662,7 @@ async function createAgentDeploymentAPI() {
|
|
|
17558
17662
|
const context = await getSDKClient();
|
|
17559
17663
|
let workspaceId;
|
|
17560
17664
|
try {
|
|
17561
|
-
workspaceId = context.
|
|
17665
|
+
workspaceId = context.requireSelectedWorkspace();
|
|
17562
17666
|
} catch (error3) {
|
|
17563
17667
|
if (error3 instanceof MutagentError)
|
|
17564
17668
|
error3.exitCode = 3;
|
|
@@ -18496,17 +18600,13 @@ async function installHelix(options, deps = {}) {
|
|
|
18496
18600
|
|
|
18497
18601
|
// src/lib/install-telemetry.ts
|
|
18498
18602
|
init_config();
|
|
18603
|
+
init_organization_scope();
|
|
18499
18604
|
var TELEMETRY_TIMEOUT_MS = 1500;
|
|
18500
18605
|
function resolveApiAuth() {
|
|
18501
18606
|
const apiKey = getApiKey();
|
|
18502
18607
|
const config = loadConfig();
|
|
18503
18608
|
const apiBase = config.endpoint ?? "https://api.mutagent.io";
|
|
18504
|
-
|
|
18505
|
-
if (config.defaultWorkspace)
|
|
18506
|
-
headers["x-workspace-id"] = config.defaultWorkspace;
|
|
18507
|
-
if (config.defaultOrganization)
|
|
18508
|
-
headers["x-organization-id"] = config.defaultOrganization;
|
|
18509
|
-
return { apiBase, apiKey, headers };
|
|
18609
|
+
return { apiBase, apiKey, headers: configTenancyHeaders(config) };
|
|
18510
18610
|
}
|
|
18511
18611
|
async function reportInstall(event, deps = {}) {
|
|
18512
18612
|
try {
|
|
@@ -18619,6 +18719,7 @@ async function runPostOnboarding(deps = {}) {
|
|
|
18619
18719
|
// src/lib/auth-flow.ts
|
|
18620
18720
|
init_config();
|
|
18621
18721
|
init_sdk_client();
|
|
18722
|
+
init_organization_scope();
|
|
18622
18723
|
init_errors();
|
|
18623
18724
|
import { input, password, select } from "@inquirer/prompts";
|
|
18624
18725
|
import chalk3 from "chalk";
|
|
@@ -18667,6 +18768,17 @@ async function openBrowser(url) {
|
|
|
18667
18768
|
throw new BrowserAuthError("BROWSER_OPEN_FAILED", `Could not open browser automatically. Please visit: ${url}`);
|
|
18668
18769
|
}
|
|
18669
18770
|
}
|
|
18771
|
+
function withOrgPreselect(authUrl, org) {
|
|
18772
|
+
if (!org)
|
|
18773
|
+
return authUrl;
|
|
18774
|
+
try {
|
|
18775
|
+
const url = new URL(authUrl);
|
|
18776
|
+
url.searchParams.set("org", org);
|
|
18777
|
+
return url.toString();
|
|
18778
|
+
} catch {
|
|
18779
|
+
return `${authUrl}${authUrl.includes("?") ? "&" : "?"}org=${encodeURIComponent(org)}`;
|
|
18780
|
+
}
|
|
18781
|
+
}
|
|
18670
18782
|
function sleep(ms) {
|
|
18671
18783
|
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
18672
18784
|
}
|
|
@@ -18680,7 +18792,7 @@ async function performBrowserAuth(options, onStatusUpdate) {
|
|
|
18680
18792
|
const cliToken = generateCliToken();
|
|
18681
18793
|
onStatusUpdate?.("Initializing browser authentication...");
|
|
18682
18794
|
const initResponse = await initBrowserAuth(endpoint, cliToken);
|
|
18683
|
-
const
|
|
18795
|
+
const authUrl = withOrgPreselect(initResponse.authUrl, options.org);
|
|
18684
18796
|
if (!skipBrowserOpen) {
|
|
18685
18797
|
onStatusUpdate?.("Opening browser for authentication...");
|
|
18686
18798
|
try {
|
|
@@ -18797,7 +18909,7 @@ async function performLoginAction(opts) {
|
|
|
18797
18909
|
useBrowserAuth = method === "browser";
|
|
18798
18910
|
}
|
|
18799
18911
|
if (useBrowserAuth) {
|
|
18800
|
-
return loginWithBrowser(endpoint, output, wasFirstLogin);
|
|
18912
|
+
return loginWithBrowser(endpoint, output, wasFirstLogin, opts.org);
|
|
18801
18913
|
}
|
|
18802
18914
|
return loginWithPastedKey(endpoint, output, wasFirstLogin);
|
|
18803
18915
|
}
|
|
@@ -18814,55 +18926,76 @@ function buildLoginJsonResponse(result) {
|
|
|
18814
18926
|
}
|
|
18815
18927
|
};
|
|
18816
18928
|
}
|
|
18817
|
-
async function
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18929
|
+
async function describeKey(apiKey, endpoint, pick3) {
|
|
18930
|
+
let self;
|
|
18931
|
+
try {
|
|
18932
|
+
self = await fetchKeySelf(apiKey, endpoint);
|
|
18933
|
+
} catch {
|
|
18934
|
+
return {};
|
|
18822
18935
|
}
|
|
18823
|
-
const
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
}
|
|
18936
|
+
const organization = self.organization ?? undefined;
|
|
18937
|
+
const scope = {
|
|
18938
|
+
keyScope: self.scope,
|
|
18939
|
+
organizationId: organization?.id,
|
|
18940
|
+
organizationName: organization?.name,
|
|
18941
|
+
organizationSlug: organization?.slug
|
|
18942
|
+
};
|
|
18943
|
+
if (self.workspace) {
|
|
18944
|
+
return { ...scope, workspaceId: self.workspace.id, workspaceName: self.workspace.name };
|
|
18945
|
+
}
|
|
18946
|
+
const envWorkspace = process.env.MUTAGENT_WORKSPACE_ID?.trim();
|
|
18947
|
+
const rows = await listKeyWorkspaces(apiKey, endpoint, { keyScope: self.scope, organization }).catch(() => []);
|
|
18948
|
+
let chosen;
|
|
18949
|
+
if (envWorkspace)
|
|
18950
|
+
chosen = rows.find((w) => w.id === envWorkspace);
|
|
18951
|
+
else if (rows.length === 1)
|
|
18952
|
+
chosen = rows[0];
|
|
18953
|
+
else if (pick3 && rows.length > 1) {
|
|
18954
|
+
const id = await pick3(rows);
|
|
18955
|
+
chosen = rows.find((w) => w.id === id);
|
|
18956
|
+
}
|
|
18957
|
+
return chosen ? { ...scope, workspaceId: chosen.id, workspaceName: chosen.name } : scope;
|
|
18958
|
+
}
|
|
18959
|
+
function loginResult(apiKey, endpoint, described, wasFirstLogin) {
|
|
18844
18960
|
return {
|
|
18845
18961
|
authenticated: true,
|
|
18846
18962
|
apiKey,
|
|
18847
18963
|
endpoint,
|
|
18848
|
-
workspace:
|
|
18849
|
-
organization:
|
|
18964
|
+
workspace: described.workspaceId ? { id: described.workspaceId, name: described.workspaceName } : null,
|
|
18965
|
+
organization: described.organizationId ? { id: described.organizationId, name: described.organizationName } : null,
|
|
18850
18966
|
wasFirstLogin
|
|
18851
18967
|
};
|
|
18852
18968
|
}
|
|
18853
|
-
async function
|
|
18969
|
+
async function loginWithExistingKey(apiKey, endpoint, output, wasFirstLogin) {
|
|
18970
|
+
output.info("Validating API key...");
|
|
18971
|
+
const isValid = await validateApiKey(apiKey, endpoint);
|
|
18972
|
+
if (!isValid) {
|
|
18973
|
+
throw new MutagentError("INVALID_API_KEY", "Invalid API key or endpoint", "Check your API key and try again");
|
|
18974
|
+
}
|
|
18975
|
+
const described = await describeKey(apiKey, endpoint);
|
|
18976
|
+
saveFullCredentials({ apiKey, endpoint, ...described });
|
|
18977
|
+
return loginResult(apiKey, endpoint, described, wasFirstLogin);
|
|
18978
|
+
}
|
|
18979
|
+
async function loginWithBrowser(endpoint, output, wasFirstLogin, org) {
|
|
18854
18980
|
const spinner = ora2({ text: "Opening browser for authentication...", spinner: "dots" });
|
|
18855
18981
|
try {
|
|
18856
18982
|
spinner.start();
|
|
18857
|
-
const result = await performBrowserAuth({ endpoint, timeout: 300000, pollInterval: 2000 }, (message) => {
|
|
18983
|
+
const result = await performBrowserAuth({ endpoint, timeout: 300000, pollInterval: 2000, org }, (message) => {
|
|
18858
18984
|
spinner.text = message;
|
|
18859
18985
|
});
|
|
18860
18986
|
spinner.succeed("Authenticated successfully!");
|
|
18987
|
+
const self = await fetchKeySelf(result.apiKey, endpoint).catch(() => {
|
|
18988
|
+
return;
|
|
18989
|
+
});
|
|
18861
18990
|
saveFullCredentials({
|
|
18862
18991
|
apiKey: result.apiKey,
|
|
18863
18992
|
endpoint,
|
|
18864
18993
|
workspaceId: result.workspaceId,
|
|
18994
|
+
workspaceName: result.workspaceName,
|
|
18865
18995
|
organizationId: result.organizationId,
|
|
18996
|
+
organizationName: result.organizationName,
|
|
18997
|
+
organizationSlug: self?.organization?.slug,
|
|
18998
|
+
keyScope: self?.scope,
|
|
18866
18999
|
expiresAt: result.expiresAt
|
|
18867
19000
|
});
|
|
18868
19001
|
if (result.workspaceName)
|
|
@@ -18900,55 +19033,12 @@ async function loginWithPastedKey(initialEndpoint, output, wasFirstLogin) {
|
|
|
18900
19033
|
if (!isValid) {
|
|
18901
19034
|
throw new MutagentError("INVALID_API_KEY", "Invalid API key or endpoint", "Check your API key and try again");
|
|
18902
19035
|
}
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
selectedOrgId = orgs[0].id;
|
|
18910
|
-
selectedOrgName = orgs[0].name;
|
|
18911
|
-
} else if (orgs.length > 1) {
|
|
18912
|
-
selectedOrgId = await select({
|
|
18913
|
-
message: "Select organization:",
|
|
18914
|
-
choices: orgs.map((o) => ({ name: o.name, value: o.id }))
|
|
18915
|
-
});
|
|
18916
|
-
selectedOrgName = orgs.find((o) => o.id === selectedOrgId)?.name;
|
|
18917
|
-
}
|
|
18918
|
-
if (selectedOrgId) {
|
|
18919
|
-
const workspaces = await fetchWorkspaces(apiKey, endpoint, selectedOrgId);
|
|
18920
|
-
const defaultWs = workspaces.find((w) => w.isDefault);
|
|
18921
|
-
if (workspaces.length === 1 && workspaces[0]) {
|
|
18922
|
-
selectedWsId = workspaces[0].id;
|
|
18923
|
-
selectedWsName = workspaces[0].name;
|
|
18924
|
-
} else if (defaultWs) {
|
|
18925
|
-
selectedWsId = defaultWs.id;
|
|
18926
|
-
selectedWsName = defaultWs.name;
|
|
18927
|
-
} else if (workspaces.length > 1) {
|
|
18928
|
-
selectedWsId = await select({
|
|
18929
|
-
message: "Select workspace:",
|
|
18930
|
-
choices: workspaces.map((w) => ({
|
|
18931
|
-
name: w.name + (w.isDefault ? " (default)" : ""),
|
|
18932
|
-
value: w.id
|
|
18933
|
-
}))
|
|
18934
|
-
});
|
|
18935
|
-
selectedWsName = workspaces.find((w) => w.id === selectedWsId)?.name;
|
|
18936
|
-
}
|
|
18937
|
-
}
|
|
18938
|
-
saveFullCredentials({
|
|
18939
|
-
apiKey,
|
|
18940
|
-
endpoint,
|
|
18941
|
-
workspaceId: selectedWsId,
|
|
18942
|
-
organizationId: selectedOrgId
|
|
18943
|
-
});
|
|
18944
|
-
return {
|
|
18945
|
-
authenticated: true,
|
|
18946
|
-
apiKey,
|
|
18947
|
-
endpoint,
|
|
18948
|
-
workspace: selectedWsId ? { id: selectedWsId, name: selectedWsName } : null,
|
|
18949
|
-
organization: selectedOrgId ? { id: selectedOrgId, name: selectedOrgName } : null,
|
|
18950
|
-
wasFirstLogin
|
|
18951
|
-
};
|
|
19036
|
+
const described = await describeKey(apiKey, endpoint, async (rows) => rows.find((w) => w.isDefault)?.id ?? select({
|
|
19037
|
+
message: "Select workspace:",
|
|
19038
|
+
choices: rows.map((w) => ({ name: w.name, value: w.id }))
|
|
19039
|
+
}));
|
|
19040
|
+
saveFullCredentials({ apiKey, endpoint, ...described });
|
|
19041
|
+
return loginResult(apiKey, endpoint, described, wasFirstLogin);
|
|
18952
19042
|
}
|
|
18953
19043
|
|
|
18954
19044
|
// src/commands/auth.ts
|
|
@@ -18978,6 +19068,7 @@ See mutagent login --help for the full description.
|
|
|
18978
19068
|
endpoint: options.endpoint,
|
|
18979
19069
|
browser: options.browser,
|
|
18980
19070
|
nonInteractive: options.nonInteractive,
|
|
19071
|
+
org: auth.optsWithGlobals().org,
|
|
18981
19072
|
isJson,
|
|
18982
19073
|
output
|
|
18983
19074
|
});
|
|
@@ -18990,7 +19081,7 @@ See mutagent login --help for the full description.
|
|
|
18990
19081
|
if (result.workspace?.name)
|
|
18991
19082
|
output.info(`Workspace: ${result.workspace.name}`);
|
|
18992
19083
|
output.info(`Endpoint: ${result.endpoint}`);
|
|
18993
|
-
output.info("Next: mutagent workspaces
|
|
19084
|
+
output.info(result.workspace ? "Next: mutagent workspaces current" : "Next: mutagent workspaces use <name>");
|
|
18994
19085
|
}
|
|
18995
19086
|
if (result.wasFirstLogin && process.stdin.isTTY && !isJson) {
|
|
18996
19087
|
await runPostOnboarding();
|
|
@@ -19094,10 +19185,13 @@ Examples:
|
|
|
19094
19185
|
${chalk5.dim("$")} mutagent login ${chalk5.dim("# In a terminal, choose browser or API key")}
|
|
19095
19186
|
${chalk5.dim("$")} mutagent login --browser ${chalk5.dim("# Sign in with the browser")}
|
|
19096
19187
|
${chalk5.dim("$")} mutagent login --non-interactive ${chalk5.dim("# Browser sign-in without the method prompt (a person approves in the browser)")}
|
|
19188
|
+
${chalk5.dim("$")} mutagent login --org <slug> ${chalk5.dim("# Sign in for another organization (preselected on the approval page)")}
|
|
19097
19189
|
${chalk5.dim("$")} MUTAGENT_API_KEY=mt_... mutagent login --json ${chalk5.dim("# Sign in with a key (CI)")}
|
|
19098
19190
|
|
|
19099
19191
|
Browser sign-in opens app.mutagent.io, where you sign in or sign up, finish onboarding
|
|
19100
19192
|
and approve this CLI. The CLI waits up to 5 minutes and saves the credentials.
|
|
19193
|
+
The key works in every workspace you are a member of in the approved organization, for
|
|
19194
|
+
30 days. The approved workspace is selected; switch with mutagent workspaces use <name>.
|
|
19101
19195
|
|
|
19102
19196
|
When MUTAGENT_API_KEY is set, login skips the browser and validates the key.
|
|
19103
19197
|
|
|
@@ -19108,8 +19202,8 @@ Environment:
|
|
|
19108
19202
|
CI=true Same as --non-interactive
|
|
19109
19203
|
|
|
19110
19204
|
Then:
|
|
19111
|
-
${chalk5.dim("$")} mutagent
|
|
19112
|
-
${chalk5.dim("$")} mutagent workspaces
|
|
19205
|
+
${chalk5.dim("$")} mutagent workspaces current
|
|
19206
|
+
${chalk5.dim("$")} mutagent workspaces use <name>
|
|
19113
19207
|
|
|
19114
19208
|
mutagent auth login runs the same sign-in.
|
|
19115
19209
|
`).action(async (options) => {
|
|
@@ -19120,6 +19214,7 @@ mutagent auth login runs the same sign-in.
|
|
|
19120
19214
|
endpoint: options.endpoint,
|
|
19121
19215
|
browser: options.browser,
|
|
19122
19216
|
nonInteractive: options.nonInteractive,
|
|
19217
|
+
org: login.optsWithGlobals().org,
|
|
19123
19218
|
isJson,
|
|
19124
19219
|
output
|
|
19125
19220
|
});
|
|
@@ -19132,7 +19227,7 @@ mutagent auth login runs the same sign-in.
|
|
|
19132
19227
|
if (result.workspace?.name)
|
|
19133
19228
|
output.info(`Workspace: ${result.workspace.name}`);
|
|
19134
19229
|
output.info(`Endpoint: ${result.endpoint}`);
|
|
19135
|
-
output.info("Next: mutagent workspaces
|
|
19230
|
+
output.info(result.workspace ? "Next: mutagent workspaces current" : "Next: mutagent workspaces use <name>");
|
|
19136
19231
|
}
|
|
19137
19232
|
if (result.wasFirstLogin && process.stdin.isTTY && !isJson) {
|
|
19138
19233
|
await runPostOnboarding();
|
|
@@ -19150,163 +19245,163 @@ mutagent auth login runs the same sign-in.
|
|
|
19150
19245
|
|
|
19151
19246
|
// src/commands/config.ts
|
|
19152
19247
|
init_config();
|
|
19248
|
+
import { Command as Command5 } from "commander";
|
|
19249
|
+
import chalk7 from "chalk";
|
|
19250
|
+
init_errors();
|
|
19251
|
+
init_organization_scope();
|
|
19252
|
+
|
|
19253
|
+
// src/commands/workspaces.ts
|
|
19254
|
+
init_sdk_client();
|
|
19153
19255
|
import { Command as Command4 } from "commander";
|
|
19154
19256
|
import chalk6 from "chalk";
|
|
19155
19257
|
init_errors();
|
|
19156
|
-
|
|
19258
|
+
init_config();
|
|
19157
19259
|
init_organization_scope();
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19260
|
+
async function useWorkspace(value2) {
|
|
19261
|
+
const apiKey = getApiKey();
|
|
19262
|
+
if (!apiKey)
|
|
19263
|
+
throw new AuthenticationError;
|
|
19264
|
+
if (envKeyReplacesFile())
|
|
19265
|
+
throw envKeyHasNoSelection();
|
|
19266
|
+
const tenancy = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19267
|
+
const config = loadConfig();
|
|
19268
|
+
const workspace = await findWorkspace(value2, {
|
|
19269
|
+
apiKey,
|
|
19270
|
+
endpoint: tenancy.endpoint,
|
|
19271
|
+
keyScope: tenancy.keyScope,
|
|
19272
|
+
organization: tenancy.organization,
|
|
19273
|
+
workspaceId: config.workspaceOrigin === "selection" ? tenancy.workspaceId : undefined,
|
|
19274
|
+
workspaceName: config.workspaceOrigin === "selection" ? tenancy.workspaceName : undefined
|
|
19275
|
+
});
|
|
19276
|
+
setWorkspaceSelection(workspace.id, workspace.name);
|
|
19277
|
+
resetSDKClient();
|
|
19278
|
+
const org = tenancy.organization;
|
|
19279
|
+
return { workspace, organization: org ? { id: org.id, name: org.name } : null };
|
|
19280
|
+
}
|
|
19281
|
+
function createWorkspacesCommand() {
|
|
19282
|
+
const workspaces = new Command4("workspaces").alias("workspace").description("List, select and inspect workspaces").addHelpText("after", `
|
|
19161
19283
|
Examples:
|
|
19162
|
-
${chalk6.dim("$")} mutagent
|
|
19163
|
-
${chalk6.dim("$")} mutagent
|
|
19164
|
-
${chalk6.dim("$")} mutagent
|
|
19165
|
-
${chalk6.dim("$")} mutagent
|
|
19284
|
+
${chalk6.dim("$")} mutagent workspaces list
|
|
19285
|
+
${chalk6.dim("$")} mutagent workspaces use <name|id> ${chalk6.dim("# Every later command works in this workspace")}
|
|
19286
|
+
${chalk6.dim("$")} mutagent workspaces current --json
|
|
19287
|
+
${chalk6.dim("$")} mutagent providers list --workspace <name|id> ${chalk6.dim("# One command in another workspace")}
|
|
19288
|
+
|
|
19289
|
+
The CLI key from \`mutagent login\` works in every workspace you are a member of in its
|
|
19290
|
+
organization. Workspace precedence: --workspace, then MUTAGENT_WORKSPACE_ID, then the
|
|
19291
|
+
workspace chosen with \`workspaces use\`.
|
|
19292
|
+
To work in another organization: mutagent login --org <slug>
|
|
19293
|
+
Create or change workspaces in the dashboard: ${getAppBaseUrl()}
|
|
19166
19294
|
`);
|
|
19167
|
-
|
|
19295
|
+
workspaces.command("list").description("List your workspaces in the key's organization (* = selected)").option("-l, --limit <n>", "Limit results", "50").option("-o, --offset <n>", "Offset for pagination").addHelpText("after", `
|
|
19168
19296
|
Examples:
|
|
19169
|
-
${chalk6.dim("$")} mutagent
|
|
19170
|
-
${chalk6.dim("$")} mutagent
|
|
19171
|
-
`).action(() => {
|
|
19172
|
-
const isJson = getJsonFlag(config);
|
|
19173
|
-
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19174
|
-
const cfg = loadConfig();
|
|
19175
|
-
const display = {
|
|
19176
|
-
...cfg,
|
|
19177
|
-
apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : undefined
|
|
19178
|
-
};
|
|
19179
|
-
output.output(display);
|
|
19180
|
-
});
|
|
19181
|
-
config.command("get").description("Get configuration value").argument("<key>", "Configuration key (apiKey, endpoint, format, timeout, defaultWorkspace, defaultOrganization)").addHelpText("after", `
|
|
19182
|
-
Examples:
|
|
19183
|
-
${chalk6.dim("$")} mutagent config get endpoint
|
|
19184
|
-
${chalk6.dim("$")} mutagent config get defaultWorkspace
|
|
19185
|
-
${chalk6.dim("$")} mutagent config get apiKey --json
|
|
19297
|
+
${chalk6.dim("$")} mutagent workspaces list
|
|
19298
|
+
${chalk6.dim("$")} mutagent workspaces list --limit 10 --json
|
|
19186
19299
|
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
`).action((
|
|
19190
|
-
const isJson = getJsonFlag(
|
|
19300
|
+
Lists only workspaces you are a member of. Works even when the selected workspace is no
|
|
19301
|
+
longer available, so you can pick another with: mutagent workspaces use <name>
|
|
19302
|
+
`).action(async (options) => {
|
|
19303
|
+
const isJson = getJsonFlag(workspaces);
|
|
19191
19304
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19192
19305
|
try {
|
|
19193
|
-
const
|
|
19194
|
-
if (!
|
|
19195
|
-
throw new
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
const
|
|
19199
|
-
const
|
|
19200
|
-
const
|
|
19306
|
+
const apiKey = getApiKey();
|
|
19307
|
+
if (!apiKey)
|
|
19308
|
+
throw new AuthenticationError;
|
|
19309
|
+
const tenancy = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19310
|
+
const rows = await listKeyWorkspaces(apiKey, tenancy.endpoint, tenancy);
|
|
19311
|
+
const offset = options.offset ? parseInt(options.offset, 10) : 0;
|
|
19312
|
+
const limit = parseInt(options.limit, 10) || 50;
|
|
19313
|
+
const page = rows.slice(offset, offset + limit);
|
|
19314
|
+
const selected = tenancy.workspaceId;
|
|
19201
19315
|
if (isJson) {
|
|
19202
|
-
output.output({
|
|
19316
|
+
output.output({
|
|
19317
|
+
data: page.map((w) => ({
|
|
19318
|
+
id: w.id,
|
|
19319
|
+
name: w.name,
|
|
19320
|
+
slug: w.slug,
|
|
19321
|
+
isDefault: w.isDefault,
|
|
19322
|
+
selected: w.id === selected,
|
|
19323
|
+
_links: workspaceLinks(w.id)
|
|
19324
|
+
})),
|
|
19325
|
+
total: rows.length
|
|
19326
|
+
});
|
|
19203
19327
|
} else {
|
|
19204
|
-
output.
|
|
19328
|
+
output.output(page.map((w) => ({
|
|
19329
|
+
selected: w.id === selected ? "*" : "",
|
|
19330
|
+
id: w.id,
|
|
19331
|
+
name: w.name,
|
|
19332
|
+
slug: w.slug,
|
|
19333
|
+
url: workspaceLink(w.id)
|
|
19334
|
+
})));
|
|
19205
19335
|
}
|
|
19206
19336
|
} catch (error3) {
|
|
19207
19337
|
handleError(error3, isJson);
|
|
19208
19338
|
}
|
|
19209
19339
|
});
|
|
19210
|
-
|
|
19211
|
-
set2.help();
|
|
19212
|
-
});
|
|
19213
|
-
set2.command("workspace").description("Set default workspace ID").argument("<id>", "Workspace ID to set as default").addHelpText("after", `
|
|
19340
|
+
workspaces.command("use").description("Select the workspace every later command works in").argument("<name|id>", "Workspace name or id").addHelpText("after", `
|
|
19214
19341
|
Examples:
|
|
19215
|
-
${chalk6.dim("$")} mutagent
|
|
19342
|
+
${chalk6.dim("$")} mutagent workspaces use staging
|
|
19343
|
+
${chalk6.dim("$")} mutagent workspaces use <workspace-id> --json
|
|
19216
19344
|
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
resetSDKClient();
|
|
19223
|
-
if (isJson) {
|
|
19224
|
-
output.output({ success: true, defaultWorkspace: id });
|
|
19225
|
-
} else {
|
|
19226
|
-
output.success(`Default workspace set to ${id}`);
|
|
19227
|
-
}
|
|
19228
|
-
});
|
|
19229
|
-
set2.command("org").description("Set default organization ID").argument("<id>", "Organization ID to set as default").addHelpText("after", `
|
|
19230
|
-
Examples:
|
|
19231
|
-
${chalk6.dim("$")} mutagent config set org <org-id>
|
|
19232
|
-
|
|
19233
|
-
${chalk6.dim("Persists organization ID for org-scoped API keys.")}
|
|
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) => {
|
|
19237
|
-
const isJson = getJsonFlag(config);
|
|
19345
|
+
Resolves against your workspaces in the key's organization and saves the choice. No
|
|
19346
|
+
browser and no prompt. A workspace in another organization needs a key for that
|
|
19347
|
+
organization: mutagent login --org <slug>. Two workspaces with the same name: pass the id.
|
|
19348
|
+
`).action(async (value2) => {
|
|
19349
|
+
const isJson = getJsonFlag(workspaces);
|
|
19238
19350
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19239
19351
|
try {
|
|
19240
|
-
await
|
|
19241
|
-
apiKey: getApiKey(),
|
|
19242
|
-
endpoint: loadConfig().endpoint ?? "https://api.mutagent.io",
|
|
19243
|
-
fetchOrganizations
|
|
19244
|
-
});
|
|
19245
|
-
setDefaultOrganization(id);
|
|
19246
|
-
resetSDKClient();
|
|
19352
|
+
const result = await useWorkspace(value2);
|
|
19247
19353
|
if (isJson) {
|
|
19248
|
-
output.output({ success: true,
|
|
19354
|
+
output.output({ success: true, ...result, _links: workspaceLinks(result.workspace.id) });
|
|
19249
19355
|
} else {
|
|
19250
|
-
output.success(`
|
|
19356
|
+
output.success(`Workspace: ${result.workspace.name} (${result.workspace.id})`);
|
|
19251
19357
|
}
|
|
19252
19358
|
} catch (error3) {
|
|
19253
19359
|
handleError(error3, isJson);
|
|
19254
19360
|
}
|
|
19255
19361
|
});
|
|
19256
|
-
|
|
19257
|
-
return config;
|
|
19258
|
-
}
|
|
19259
|
-
|
|
19260
|
-
// src/commands/workspaces.ts
|
|
19261
|
-
init_sdk_client();
|
|
19262
|
-
import { Command as Command5 } from "commander";
|
|
19263
|
-
import chalk7 from "chalk";
|
|
19264
|
-
init_errors();
|
|
19265
|
-
function createWorkspacesCommand() {
|
|
19266
|
-
const workspaces = new Command5("workspaces").description("View workspaces (read-only)").addHelpText("after", `
|
|
19362
|
+
workspaces.command("current").description("Show the key's scope, organization and the verified workspace").addHelpText("after", `
|
|
19267
19363
|
Examples:
|
|
19268
|
-
${
|
|
19269
|
-
${
|
|
19364
|
+
${chalk6.dim("$")} mutagent workspaces current
|
|
19365
|
+
${chalk6.dim("$")} mutagent workspaces current --json
|
|
19270
19366
|
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
`)
|
|
19274
|
-
workspaces.command("list").description("List all workspaces").option("-l, --limit <n>", "Limit results", "50").option("-o, --offset <n>", "Offset for pagination").addHelpText("after", `
|
|
19275
|
-
Examples:
|
|
19276
|
-
${chalk7.dim("$")} mutagent workspaces list
|
|
19277
|
-
${chalk7.dim("$")} mutagent workspaces list --limit 10 --json
|
|
19278
|
-
`).action(async (options) => {
|
|
19367
|
+
Asks the server (GET /api/api-keys/self) with the workspace this command would use, so
|
|
19368
|
+
the answer is verified, not read from local config.
|
|
19369
|
+
`).action(async () => {
|
|
19279
19370
|
const isJson = getJsonFlag(workspaces);
|
|
19280
19371
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19281
19372
|
try {
|
|
19282
|
-
const
|
|
19283
|
-
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
if (
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
|
|
19373
|
+
const apiKey = getApiKey();
|
|
19374
|
+
if (!apiKey)
|
|
19375
|
+
throw new AuthenticationError;
|
|
19376
|
+
const tenancy = await resolveKeyTenancy(apiKey);
|
|
19377
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
19378
|
+
throw noWorkspaceSelected();
|
|
19379
|
+
const headers = tenancyHeaders({
|
|
19380
|
+
keyScope: tenancy.keyScope,
|
|
19381
|
+
workspaceId: tenancy.workspaceId,
|
|
19382
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
19383
|
+
organizationId: tenancy.organization?.id
|
|
19384
|
+
});
|
|
19385
|
+
const self = await fetchKeySelf(apiKey, tenancy.endpoint, { headers, ctx: tenancy });
|
|
19386
|
+
if (!envKeyReplacesFile())
|
|
19387
|
+
recordKeyScope(self);
|
|
19388
|
+
if (!self.workspace && self.scope === "organization")
|
|
19389
|
+
throw noWorkspaceSelected();
|
|
19290
19390
|
if (isJson) {
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
_links: workspaceLinks(w.id)
|
|
19298
|
-
}));
|
|
19299
|
-
output.output({ ...result, data: withLinks });
|
|
19391
|
+
output.output({
|
|
19392
|
+
scope: self.scope,
|
|
19393
|
+
organization: self.organization,
|
|
19394
|
+
workspace: self.workspace,
|
|
19395
|
+
expiresAt: self.expiresAt ?? null
|
|
19396
|
+
});
|
|
19300
19397
|
} else {
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
}));
|
|
19309
|
-
output.output(formatted);
|
|
19398
|
+
output.info(`Key scope: ${self.scope}`);
|
|
19399
|
+
if (self.organization)
|
|
19400
|
+
output.info(`Organization: ${self.organization.name} (${self.organization.id})`);
|
|
19401
|
+
if (self.workspace)
|
|
19402
|
+
output.info(`Workspace: ${self.workspace.name} (${self.workspace.id})`);
|
|
19403
|
+
if (self.expiresAt)
|
|
19404
|
+
output.info(`Key expires: ${self.expiresAt}`);
|
|
19310
19405
|
}
|
|
19311
19406
|
} catch (error3) {
|
|
19312
19407
|
handleError(error3, isJson);
|
|
@@ -19314,8 +19409,8 @@ Examples:
|
|
|
19314
19409
|
});
|
|
19315
19410
|
workspaces.command("get").description("Get workspace details").argument("<id>", "Workspace ID").addHelpText("after", `
|
|
19316
19411
|
Examples:
|
|
19317
|
-
${
|
|
19318
|
-
${
|
|
19412
|
+
${chalk6.dim("$")} mutagent workspaces get <workspace-id>
|
|
19413
|
+
${chalk6.dim("$")} mutagent workspaces get <workspace-id> --json
|
|
19319
19414
|
`).action(async (id) => {
|
|
19320
19415
|
const isJson = getJsonFlag(workspaces);
|
|
19321
19416
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
@@ -19345,6 +19440,115 @@ Examples:
|
|
|
19345
19440
|
return workspaces;
|
|
19346
19441
|
}
|
|
19347
19442
|
|
|
19443
|
+
// src/commands/config.ts
|
|
19444
|
+
var VALID_CONFIG_KEYS = ["apiKey", "endpoint", "format", "timeout", "defaultWorkspace", "workspaceName", "defaultOrganization", "organizationName", "keyScope"];
|
|
19445
|
+
function createConfigCommand() {
|
|
19446
|
+
const config = new Command5("config").description("Manage CLI configuration").addHelpText("after", `
|
|
19447
|
+
Examples:
|
|
19448
|
+
${chalk7.dim("$")} mutagent config list
|
|
19449
|
+
${chalk7.dim("$")} mutagent config get endpoint
|
|
19450
|
+
${chalk7.dim("$")} mutagent config set workspace <name|id> ${chalk7.dim("# Same as: mutagent workspaces use")}
|
|
19451
|
+
`);
|
|
19452
|
+
config.command("list").description("List all configuration").addHelpText("after", `
|
|
19453
|
+
Examples:
|
|
19454
|
+
${chalk7.dim("$")} mutagent config list
|
|
19455
|
+
${chalk7.dim("$")} mutagent config list --json
|
|
19456
|
+
`).action(() => {
|
|
19457
|
+
const isJson = getJsonFlag(config);
|
|
19458
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19459
|
+
const cfg = loadConfig();
|
|
19460
|
+
const display = {
|
|
19461
|
+
...cfg,
|
|
19462
|
+
apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : undefined
|
|
19463
|
+
};
|
|
19464
|
+
output.output(display);
|
|
19465
|
+
});
|
|
19466
|
+
config.command("get").description("Get configuration value").argument("<key>", `Configuration key (${VALID_CONFIG_KEYS.join(", ")})`).addHelpText("after", `
|
|
19467
|
+
Examples:
|
|
19468
|
+
${chalk7.dim("$")} mutagent config get endpoint
|
|
19469
|
+
${chalk7.dim("$")} mutagent config get defaultWorkspace
|
|
19470
|
+
${chalk7.dim("$")} mutagent config get apiKey --json
|
|
19471
|
+
|
|
19472
|
+
${chalk7.dim(`Keys: ${VALID_CONFIG_KEYS.join(", ")}`)}
|
|
19473
|
+
${chalk7.dim("A key with no value set is reported as unknown.")}
|
|
19474
|
+
`).action((key) => {
|
|
19475
|
+
const isJson = getJsonFlag(config);
|
|
19476
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19477
|
+
try {
|
|
19478
|
+
const cfg = loadConfig();
|
|
19479
|
+
if (!(key in cfg)) {
|
|
19480
|
+
throw new MutagentError("INVALID_ARGUMENTS", `Unknown config key: "${key}"`, `Valid keys: ${VALID_CONFIG_KEYS.join(", ")}
|
|
19481
|
+
` + "Example: mutagent config get endpoint");
|
|
19482
|
+
}
|
|
19483
|
+
const value2 = cfg[key];
|
|
19484
|
+
const stringValue = typeof value2 === "object" ? JSON.stringify(value2) : String(value2 ?? "");
|
|
19485
|
+
const displayValue = key === "apiKey" && value2 ? `${stringValue.slice(0, 8)}...` : stringValue;
|
|
19486
|
+
if (isJson) {
|
|
19487
|
+
output.output({ [key]: displayValue });
|
|
19488
|
+
} else {
|
|
19489
|
+
output.info(displayValue);
|
|
19490
|
+
}
|
|
19491
|
+
} catch (error3) {
|
|
19492
|
+
handleError(error3, isJson);
|
|
19493
|
+
}
|
|
19494
|
+
});
|
|
19495
|
+
const set2 = new Command5("set").description("Set configuration value").action(() => {
|
|
19496
|
+
set2.help();
|
|
19497
|
+
});
|
|
19498
|
+
set2.command("workspace").description("Select the workspace every later command works in (same as: workspaces use)").argument("<name|id>", "Workspace name or id").addHelpText("after", `
|
|
19499
|
+
Examples:
|
|
19500
|
+
${chalk7.dim("$")} mutagent config set workspace staging
|
|
19501
|
+
${chalk7.dim("$")} mutagent config set workspace <workspace-id>
|
|
19502
|
+
|
|
19503
|
+
${chalk7.dim("Runs mutagent workspaces use: resolves the name among your workspaces and saves it.")}
|
|
19504
|
+
`).action(async (value2) => {
|
|
19505
|
+
const isJson = getJsonFlag(config);
|
|
19506
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19507
|
+
try {
|
|
19508
|
+
const result = await useWorkspace(value2);
|
|
19509
|
+
if (isJson) {
|
|
19510
|
+
output.output({ success: true, defaultWorkspace: result.workspace.id, ...result });
|
|
19511
|
+
} else {
|
|
19512
|
+
output.success(`Workspace: ${result.workspace.name} (${result.workspace.id})`);
|
|
19513
|
+
}
|
|
19514
|
+
} catch (error3) {
|
|
19515
|
+
handleError(error3, isJson);
|
|
19516
|
+
}
|
|
19517
|
+
});
|
|
19518
|
+
set2.command("org").description("Confirm the key's organization (it comes from the key; nothing is stored)").argument("<id|slug>", "Organization id or slug").addHelpText("after", `
|
|
19519
|
+
Examples:
|
|
19520
|
+
${chalk7.dim("$")} mutagent config set org <org-id|slug>
|
|
19521
|
+
|
|
19522
|
+
${chalk7.dim("The CLI key belongs to one organization. The same organization: nothing to do.")}
|
|
19523
|
+
${chalk7.dim("Another organization: sign in for it with mutagent login --org <id|slug>.")}
|
|
19524
|
+
`).action(async (value2) => {
|
|
19525
|
+
const isJson = getJsonFlag(config);
|
|
19526
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19527
|
+
try {
|
|
19528
|
+
const apiKey = getApiKey();
|
|
19529
|
+
if (!apiKey)
|
|
19530
|
+
throw new AuthenticationError;
|
|
19531
|
+
setOrgAssertion(value2);
|
|
19532
|
+
const { organization } = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19533
|
+
if (!organization) {
|
|
19534
|
+
throw new MutagentError("ORG_UNKNOWN", "This key's organization is unknown.", "Run: mutagent workspaces current");
|
|
19535
|
+
}
|
|
19536
|
+
if (!matchesOrganization(value2, organization)) {
|
|
19537
|
+
throw orgMismatch(value2, organization);
|
|
19538
|
+
}
|
|
19539
|
+
if (isJson) {
|
|
19540
|
+
output.output({ success: true, organization });
|
|
19541
|
+
} else {
|
|
19542
|
+
output.success(`Organization: ${organization.name} (${organization.id}), from the key`);
|
|
19543
|
+
}
|
|
19544
|
+
} catch (error3) {
|
|
19545
|
+
handleError(error3, isJson);
|
|
19546
|
+
}
|
|
19547
|
+
});
|
|
19548
|
+
config.addCommand(set2);
|
|
19549
|
+
return config;
|
|
19550
|
+
}
|
|
19551
|
+
|
|
19348
19552
|
// src/commands/providers/index.ts
|
|
19349
19553
|
init_sdk_client();
|
|
19350
19554
|
import { Command as Command6 } from "commander";
|
|
@@ -19837,7 +20041,7 @@ anthropic for an endpoint that speaks the Anthropic API; unset, the server defau
|
|
|
19837
20041
|
The base URL must be a public address: hosts ending in .internal, .local, .localhost or
|
|
19838
20042
|
.home.arpa are refused.
|
|
19839
20043
|
|
|
19840
|
-
Adds the LLM provider to the configured workspace (mutagent
|
|
20044
|
+
Adds the LLM provider to the configured workspace (mutagent workspaces use <name>, or --workspace for one command).
|
|
19841
20045
|
The key is encrypted and never returned.
|
|
19842
20046
|
Workspace default model: when the workspace has none, adding a keyed LLM provider sets
|
|
19843
20047
|
one on the server. An existing workspace default model is never changed.
|
|
@@ -19875,7 +20079,7 @@ AI Agent Directive:
|
|
|
19875
20079
|
validateApiFormat(providerType, options.apiFormat);
|
|
19876
20080
|
}
|
|
19877
20081
|
const client = await getSDKClient();
|
|
19878
|
-
const workspaceId = client.
|
|
20082
|
+
const workspaceId = client.requireSelectedWorkspace();
|
|
19879
20083
|
const credentialFields = collectCredentialFields(options);
|
|
19880
20084
|
if (secretWireName !== "apiKey")
|
|
19881
20085
|
credentialFields[secretWireName] = secret;
|
|
@@ -20787,7 +20991,7 @@ function statusGlyph(result) {
|
|
|
20787
20991
|
}
|
|
20788
20992
|
}
|
|
20789
20993
|
function registerMirrorCommand(parent) {
|
|
20790
|
-
parent.command("mirror").description("Copy the API keys from your local Helix login store into the workspace as LLM providers").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave LLM providers the workspace already has untouched").option("--
|
|
20994
|
+
parent.command("mirror").description("Copy the API keys from your local Helix login store into the workspace as LLM providers").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave LLM providers the workspace already has untouched").option("--channel <channel>", `Helix channel whose store is read: ${HELIX_CHANNELS2.join(" | ")}`, "stable").option("--provider <id>", "Only this local LLM provider id (repeatable)", (value2, prev = []) => [...prev, value2]).addHelpText("after", `
|
|
20791
20995
|
Examples:
|
|
20792
20996
|
${chalk11.dim("$")} mutagent providers mirror
|
|
20793
20997
|
${chalk11.dim("$")} mutagent providers mirror --json ${chalk11.dim("Show the plan without sending anything")}
|
|
@@ -20816,6 +21020,9 @@ default model is never changed. Change it with
|
|
|
20816
21020
|
|
|
20817
21021
|
Mirroring does not test the keys: check \`mutagent helix models\`, then run a model.
|
|
20818
21022
|
|
|
21023
|
+
--workspace <name|id> still works here: it is the global flag and targets this run only
|
|
21024
|
+
(mutagent workspaces use <name> changes the default).
|
|
21025
|
+
|
|
20819
21026
|
Requires \`mutagent login\` and a workspace. Keys are sent only to the configured
|
|
20820
21027
|
endpoint and are never printed. Consent is asked once per run; without a terminal,
|
|
20821
21028
|
--yes is required. Without it the run exits 2 (with --json it returns the plan).
|
|
@@ -20835,12 +21042,9 @@ AI Agent Directive:
|
|
|
20835
21042
|
try {
|
|
20836
21043
|
const apiKey = getApiKey();
|
|
20837
21044
|
const config = loadConfig();
|
|
20838
|
-
const workspaceId =
|
|
21045
|
+
const workspaceId = config.defaultWorkspace;
|
|
20839
21046
|
if (!apiKey || !workspaceId) {
|
|
20840
|
-
throw new MutagentError("AUTH_REQUIRED", "No platform identity for this machine — run `mutagent auth login` first.", "mutagent
|
|
20841
|
-
}
|
|
20842
|
-
if (options.workspace) {
|
|
20843
|
-
process.env.MUTAGENT_WORKSPACE_ID = options.workspace;
|
|
21047
|
+
throw new MutagentError("AUTH_REQUIRED", "No platform identity for this machine — run `mutagent auth login` first.", "mutagent login (then, if needed: mutagent workspaces use <name>)", MIRROR_EXIT_NO_IDENTITY);
|
|
20844
21048
|
}
|
|
20845
21049
|
if (!HELIX_CHANNELS2.includes(options.channel)) {
|
|
20846
21050
|
throw new MutagentError("INVALID_OPTION", `Unknown channel '${options.channel}'.`, `Use --channel ${HELIX_CHANNELS2.join(" or ")}`);
|
|
@@ -20851,7 +21055,7 @@ AI Agent Directive:
|
|
|
20851
21055
|
for (const secret of localCredentialSecrets(store.entries))
|
|
20852
21056
|
registerSecret(secret);
|
|
20853
21057
|
const client = await getSDKClient();
|
|
20854
|
-
const target = client.
|
|
21058
|
+
const target = client.requireSelectedWorkspace();
|
|
20855
21059
|
const rows = (await client.listProviders({ limit: 1000 })).data.map((row) => ({
|
|
20856
21060
|
id: row.id,
|
|
20857
21061
|
name: row.name,
|
|
@@ -21782,22 +21986,28 @@ exits — run \`mutagent login\` first, then re-run \`mutagent init\`.
|
|
|
21782
21986
|
|
|
21783
21987
|
\`\`\`bash
|
|
21784
21988
|
mutagent config list --json # show all config
|
|
21785
|
-
mutagent config get <key> --json # keys: apiKey, endpoint, format, timeout,
|
|
21786
|
-
#
|
|
21787
|
-
mutagent config set workspace <id> --json #
|
|
21788
|
-
mutagent config set org <id> --json
|
|
21989
|
+
mutagent config get <key> --json # keys: apiKey, endpoint, format, timeout, defaultWorkspace,
|
|
21990
|
+
# workspaceName, defaultOrganization, organizationName, keyScope
|
|
21991
|
+
mutagent config set workspace <name|id> --json # same as: mutagent workspaces use
|
|
21992
|
+
mutagent config set org <id|slug> --json # confirms the key's organization; another org exits 1
|
|
21789
21993
|
\`\`\`
|
|
21790
21994
|
|
|
21791
21995
|
---
|
|
21792
21996
|
|
|
21793
|
-
## 4. workspaces
|
|
21997
|
+
## 4. workspaces
|
|
21794
21998
|
|
|
21795
21999
|
\`\`\`bash
|
|
21796
|
-
mutagent workspaces list --json #
|
|
22000
|
+
mutagent workspaces list --json # your workspaces in the key's organization (selected: true)
|
|
22001
|
+
mutagent workspaces use <name|id> --json # select the workspace later commands work in (no browser, no prompt)
|
|
22002
|
+
mutagent workspaces current --json # key scope, organization, verified workspace, key expiry
|
|
21797
22003
|
mutagent workspaces get <id> --json # workspace details
|
|
21798
22004
|
\`\`\`
|
|
21799
22005
|
|
|
21800
|
-
|
|
22006
|
+
The login key works in every workspace the user is a member of in its organization.
|
|
22007
|
+
For one command in another workspace, pass \`--workspace <name|id>\` (it beats
|
|
22008
|
+
\`MUTAGENT_WORKSPACE_ID\`, which beats the selection). For another organization:
|
|
22009
|
+
\`mutagent login --org <slug>\`. A tenancy error is one line ending in the fix command:
|
|
22010
|
+
run that command, never retry the same request.
|
|
21801
22011
|
|
|
21802
22012
|
---
|
|
21803
22013
|
|
|
@@ -22008,51 +22218,33 @@ It never prompts. Without a sign-in it prints what to run and exits.
|
|
|
22008
22218
|
} else {
|
|
22009
22219
|
const config = (deps.loadConfig ?? loadConfig)();
|
|
22010
22220
|
const endpoint = config.endpoint ?? "https://api.mutagent.io";
|
|
22011
|
-
|
|
22221
|
+
const workspace = config.defaultWorkspace;
|
|
22012
22222
|
let workspaceValidation;
|
|
22013
|
-
let resolvedOrgId;
|
|
22014
22223
|
try {
|
|
22015
22224
|
const apiKey = (deps.getApiKey ?? getApiKey)();
|
|
22016
|
-
if (apiKey) {
|
|
22017
|
-
const
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
}
|
|
22025
|
-
if (orgId) {
|
|
22026
|
-
resolvedOrgId = orgId;
|
|
22027
|
-
const workspaces = await fetchWs(apiKey, endpoint, orgId);
|
|
22028
|
-
if (workspaces.length > 0) {
|
|
22029
|
-
const found = workspace ? workspaces.find((w) => w.id === workspace) : undefined;
|
|
22030
|
-
if (found) {
|
|
22031
|
-
workspaceValidation = { id: found.id, name: found.name, validated: true, corrected: false };
|
|
22032
|
-
} else if (workspaces[0]) {
|
|
22033
|
-
const first = workspaces[0];
|
|
22034
|
-
(deps.setDefaultWorkspace ?? setDefaultWorkspace)(first.id, "login-inferred");
|
|
22035
|
-
workspace = first.id;
|
|
22036
|
-
workspaceValidation = { id: first.id, name: first.name, validated: true, corrected: true };
|
|
22037
|
-
}
|
|
22038
|
-
}
|
|
22039
|
-
}
|
|
22225
|
+
if (apiKey && workspace) {
|
|
22226
|
+
const list = deps.listWorkspaces ?? (async (key) => {
|
|
22227
|
+
const { listKeyWorkspaces: listKeyWorkspaces2, resolveKeyTenancy: resolveKeyTenancy2 } = await Promise.resolve().then(() => (init_organization_scope(), exports_organization_scope));
|
|
22228
|
+
const tenancy = await resolveKeyTenancy2(key, { resolveOverride: false });
|
|
22229
|
+
return listKeyWorkspaces2(key, tenancy.endpoint, tenancy);
|
|
22230
|
+
});
|
|
22231
|
+
const found = (await list(apiKey)).find((w) => w.id === workspace);
|
|
22232
|
+
workspaceValidation = { id: workspace, name: found?.name, found: found !== undefined };
|
|
22040
22233
|
}
|
|
22041
22234
|
} catch {}
|
|
22042
22235
|
rcConfig = {
|
|
22043
22236
|
endpoint,
|
|
22044
|
-
...workspace ? { defaultWorkspace: workspace } : {}
|
|
22045
|
-
...resolvedOrgId ? { defaultOrganization: resolvedOrgId } : {}
|
|
22237
|
+
...workspace ? { defaultWorkspace: workspace } : {}
|
|
22046
22238
|
};
|
|
22047
22239
|
writeRcConfig(rcConfig, cwd);
|
|
22048
22240
|
if (!isJson) {
|
|
22049
22241
|
output.success("Created .mutagentrc.json");
|
|
22050
|
-
if (workspaceValidation) {
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
}
|
|
22054
|
-
|
|
22055
|
-
|
|
22242
|
+
if (workspaceValidation?.found) {
|
|
22243
|
+
output.info(`Workspace: ${workspaceValidation.name ?? workspaceValidation.id} (${workspaceValidation.id})`);
|
|
22244
|
+
} else if (workspaceValidation) {
|
|
22245
|
+
output.warn(`Your saved workspace ${workspaceValidation.id} is not among your workspaces. Run: mutagent workspaces use <name>`);
|
|
22246
|
+
} else if (!workspace) {
|
|
22247
|
+
output.warn("No workspace selected. Run: mutagent workspaces use <name>");
|
|
22056
22248
|
}
|
|
22057
22249
|
}
|
|
22058
22250
|
}
|
|
@@ -22236,22 +22428,8 @@ function parseTranscript(raw) {
|
|
|
22236
22428
|
// src/commands/hooks/api.ts
|
|
22237
22429
|
init_config();
|
|
22238
22430
|
init_known_secrets();
|
|
22431
|
+
init_organization_scope();
|
|
22239
22432
|
var API_TIMEOUT_MS = 5000;
|
|
22240
|
-
function resolveTenancyHeaders(config) {
|
|
22241
|
-
if (config.defaultWorkspace && !config.defaultOrganization) {
|
|
22242
|
-
return {
|
|
22243
|
-
ok: false,
|
|
22244
|
-
warning: "[mutagent hooks] Warning: a default workspace is set but no default organization, " + "so the server will reject every trace batch. " + `Fix with: mutagent config set organization <org-id>
|
|
22245
|
-
`
|
|
22246
|
-
};
|
|
22247
|
-
}
|
|
22248
|
-
const headers = {};
|
|
22249
|
-
if (config.defaultWorkspace)
|
|
22250
|
-
headers["x-workspace-id"] = config.defaultWorkspace;
|
|
22251
|
-
if (config.defaultOrganization)
|
|
22252
|
-
headers["x-organization-id"] = config.defaultOrganization;
|
|
22253
|
-
return { ok: true, headers };
|
|
22254
|
-
}
|
|
22255
22433
|
async function sendBatchTrace(traces) {
|
|
22256
22434
|
const apiKey = getApiKey();
|
|
22257
22435
|
if (!apiKey) {
|
|
@@ -22265,12 +22443,7 @@ async function sendBatchTrace(traces) {
|
|
|
22265
22443
|
"x-api-key": apiKey,
|
|
22266
22444
|
"Content-Type": "application/json"
|
|
22267
22445
|
};
|
|
22268
|
-
|
|
22269
|
-
if (!tenancy.ok) {
|
|
22270
|
-
process.stderr.write(tenancy.warning);
|
|
22271
|
-
return;
|
|
22272
|
-
}
|
|
22273
|
-
Object.assign(headers, tenancy.headers);
|
|
22446
|
+
Object.assign(headers, configTenancyHeaders(config));
|
|
22274
22447
|
const controller = new AbortController;
|
|
22275
22448
|
const timeout = setTimeout(() => {
|
|
22276
22449
|
controller.abort();
|
|
@@ -23282,6 +23455,7 @@ import { join as join18, dirname as dirname5 } from "path";
|
|
|
23282
23455
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
23283
23456
|
init_errors();
|
|
23284
23457
|
init_config();
|
|
23458
|
+
init_organization_scope();
|
|
23285
23459
|
|
|
23286
23460
|
// src/lib/transcript.ts
|
|
23287
23461
|
init_errors();
|
|
@@ -23691,15 +23865,12 @@ function buildAutoContext(harness) {
|
|
|
23691
23865
|
nodeVersion: process.version
|
|
23692
23866
|
};
|
|
23693
23867
|
}
|
|
23694
|
-
async function postToServer(payload, endpoint, apiKey,
|
|
23868
|
+
async function postToServer(payload, endpoint, apiKey, tenancy) {
|
|
23695
23869
|
const headers = {
|
|
23696
23870
|
"Content-Type": "application/json",
|
|
23697
|
-
"x-api-key": apiKey
|
|
23871
|
+
"x-api-key": apiKey,
|
|
23872
|
+
...tenancy
|
|
23698
23873
|
};
|
|
23699
|
-
if (workspaceId)
|
|
23700
|
-
headers["x-workspace-id"] = workspaceId;
|
|
23701
|
-
if (organizationId)
|
|
23702
|
-
headers["x-organization-id"] = organizationId;
|
|
23703
23874
|
let response;
|
|
23704
23875
|
try {
|
|
23705
23876
|
response = await fetch(`${endpoint}/api/feedback`, {
|
|
@@ -23823,7 +23994,7 @@ Or set an API key: mutagent config set apiKey <key>`);
|
|
|
23823
23994
|
const payload = buildPayload(feedbackBody, category, options.title, options.session, transcript);
|
|
23824
23995
|
const config = loadConfig();
|
|
23825
23996
|
const endpoint = config.endpoint ?? "https://api.mutagent.io";
|
|
23826
|
-
const result = await postToServer(payload, endpoint, apiKey, config
|
|
23997
|
+
const result = await postToServer(payload, endpoint, apiKey, configTenancyHeaders(config));
|
|
23827
23998
|
if (isJson) {
|
|
23828
23999
|
output.output({
|
|
23829
24000
|
success: true,
|
|
@@ -24657,12 +24828,14 @@ init_errors();
|
|
|
24657
24828
|
|
|
24658
24829
|
// src/lib/sandbox-api.ts
|
|
24659
24830
|
init_config();
|
|
24831
|
+
init_organization_scope();
|
|
24660
24832
|
init_errors();
|
|
24661
24833
|
|
|
24662
24834
|
// src/lib/sandbox-token.ts
|
|
24663
24835
|
init_secure_file();
|
|
24664
24836
|
init_errors();
|
|
24665
24837
|
init_known_secrets();
|
|
24838
|
+
init_organization_scope();
|
|
24666
24839
|
import { existsSync as existsSync12, readFileSync as readFileSync12, mkdirSync as mkdirSync5, rmSync as rmSync2 } from "fs";
|
|
24667
24840
|
import { homedir as homedir6 } from "os";
|
|
24668
24841
|
import { join as join20, dirname as dirname6 } from "path";
|
|
@@ -24743,12 +24916,11 @@ async function exchangeToken(input2, deps = {}) {
|
|
|
24743
24916
|
const detail = error3 instanceof Error ? error3.message : String(error3);
|
|
24744
24917
|
throw new MutagentError("SANDBOX_UNREACHABLE", `Could not reach the MutagenT API at ${input2.endpoint}: ${detail}`, "Check your connection, then verify the endpoint with: mutagent config list", 5);
|
|
24745
24918
|
}
|
|
24746
|
-
if (response.status === 401)
|
|
24747
|
-
throw
|
|
24748
|
-
}
|
|
24919
|
+
if (response.status === 401)
|
|
24920
|
+
throw deadKeyError();
|
|
24749
24921
|
if (response.status === 403) {
|
|
24750
24922
|
const detail = scrubKnown(messageFrom(await response.text().catch(() => "")));
|
|
24751
|
-
throw new MutagentError("SANDBOX_WORKSPACE_FORBIDDEN", detail !== "" ? detail : `Your API key is valid, but this account cannot use workspace "${input2.workspaceId}".`, `Pick a workspace you belong to: mutagent workspaces list, then mutagent
|
|
24923
|
+
throw new MutagentError("SANDBOX_WORKSPACE_FORBIDDEN", detail !== "" ? detail : `Your API key is valid, but this account cannot use workspace "${input2.workspaceId}".`, `Pick a workspace you belong to: mutagent workspaces list, then mutagent workspaces use <name>`, 3);
|
|
24752
24924
|
}
|
|
24753
24925
|
if (!response.ok) {
|
|
24754
24926
|
const detail = scrubKnown(messageFrom(await response.text().catch(() => "")));
|
|
@@ -24821,22 +24993,25 @@ async function withStoppingRetry(send, sleep2 = (ms) => new Promise((resolve9) =
|
|
|
24821
24993
|
|
|
24822
24994
|
// src/lib/sandbox-api.ts
|
|
24823
24995
|
init_known_secrets();
|
|
24824
|
-
function resolveConnection() {
|
|
24996
|
+
async function resolveConnection() {
|
|
24825
24997
|
const apiKey = getApiKey();
|
|
24826
24998
|
if (!apiKey)
|
|
24827
24999
|
throw new AuthenticationError;
|
|
24828
|
-
const
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
const workspaceId = requireWorkspace(
|
|
24832
|
-
const headers = {
|
|
24833
|
-
|
|
24834
|
-
|
|
24835
|
-
|
|
25000
|
+
const tenancy = await resolveKeyTenancy(apiKey, { probe: envKeyReplacesFile() });
|
|
25001
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
25002
|
+
throw noWorkspaceSelected();
|
|
25003
|
+
const workspaceId = requireWorkspace(tenancy.workspaceId);
|
|
25004
|
+
const headers = tenancyHeaders({
|
|
25005
|
+
keyScope: tenancy.keyScope,
|
|
25006
|
+
workspaceId,
|
|
25007
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
25008
|
+
organizationId: tenancy.organization?.id
|
|
25009
|
+
});
|
|
25010
|
+
return { endpoint: tenancy.endpoint, apiKey, workspaceId, headers };
|
|
24836
25011
|
}
|
|
24837
25012
|
function requireWorkspace(workspaceId) {
|
|
24838
25013
|
if (workspaceId === undefined || workspaceId.trim() === "") {
|
|
24839
|
-
throw new WorkspaceContextError("
|
|
25014
|
+
throw new WorkspaceContextError("No workspace selected: sandbox access is minted per workspace, not per account.");
|
|
24840
25015
|
}
|
|
24841
25016
|
return workspaceId.trim();
|
|
24842
25017
|
}
|
|
@@ -24852,7 +25027,7 @@ async function fetchWithTokenRetry(url, init, baseHeaders, transport) {
|
|
|
24852
25027
|
return attempt(true);
|
|
24853
25028
|
}
|
|
24854
25029
|
async function sandboxFetch(path, init, deps = {}) {
|
|
24855
|
-
const connection = resolveConnection();
|
|
25030
|
+
const connection = await resolveConnection();
|
|
24856
25031
|
const { endpoint } = connection;
|
|
24857
25032
|
try {
|
|
24858
25033
|
const response = await fetchWithTokenRetry(`${endpoint}${path}`, init, { ...connection.headers, ...toHeaderRecord(init.headers) }, {
|
|
@@ -28091,7 +28266,7 @@ Examples:
|
|
|
28091
28266
|
${chalk34.dim("$")} mutagent sandbox ls --json
|
|
28092
28267
|
|
|
28093
28268
|
Lists the sandboxes in the configured workspace
|
|
28094
|
-
(mutagent
|
|
28269
|
+
(mutagent workspaces use <name>).
|
|
28095
28270
|
An empty list means none are running.
|
|
28096
28271
|
|
|
28097
28272
|
AI Agent Directive:
|
|
@@ -28323,7 +28498,7 @@ AI Agent Directive:
|
|
|
28323
28498
|
spawned sandbox runs until it is removed or stopped by the idle policy: confirm
|
|
28324
28499
|
with the user before 'spawn' and before 'rm', and tell them the sandbox ID.
|
|
28325
28500
|
|
|
28326
|
-
Requires a workspace: mutagent
|
|
28501
|
+
Requires a workspace: mutagent workspaces use <name> (or --workspace for one command)
|
|
28327
28502
|
`);
|
|
28328
28503
|
registerSpawnCommand(sandbox);
|
|
28329
28504
|
registerLsCommand(sandbox);
|
|
@@ -28459,7 +28634,7 @@ Values are write-only. \`show\` prints each entry's name and a sha256[:8] finger
|
|
|
28459
28634
|
Names: Environments use letters, digits, '.', '_' and '-' (up to 64); variables use
|
|
28460
28635
|
A-Z, 0-9 and _, not starting with a digit. An Environment holds up to 64 KiB.
|
|
28461
28636
|
|
|
28462
|
-
Requires a workspace: mutagent
|
|
28637
|
+
Requires a workspace: mutagent workspaces use <name> (or --workspace for one command)
|
|
28463
28638
|
|
|
28464
28639
|
AI Agent Directive:
|
|
28465
28640
|
Use 'set' to create or update — it creates the Environment when absent and
|
|
@@ -28694,6 +28869,7 @@ function printEntries(environment) {
|
|
|
28694
28869
|
|
|
28695
28870
|
// src/bin/cli.ts
|
|
28696
28871
|
init_config();
|
|
28872
|
+
init_organization_scope();
|
|
28697
28873
|
|
|
28698
28874
|
// src/lib/brand.ts
|
|
28699
28875
|
var BRAND = {
|
|
@@ -28762,7 +28938,7 @@ ${lines.join(`
|
|
|
28762
28938
|
}
|
|
28763
28939
|
|
|
28764
28940
|
// src/lib/global-version-flag.ts
|
|
28765
|
-
var GLOBAL_VALUE_OPTIONS = new Set(["--api-key", "--endpoint"]);
|
|
28941
|
+
var GLOBAL_VALUE_OPTIONS = new Set(["--api-key", "--endpoint", "--workspace", "--org"]);
|
|
28766
28942
|
function findSubcommandIndex(rawArgs) {
|
|
28767
28943
|
for (let i = 0;i < rawArgs.length; i++) {
|
|
28768
28944
|
const arg = rawArgs[i];
|
|
@@ -28862,7 +29038,7 @@ To pass the Mutagent platform key instead, put it before the command (mutagent -
|
|
|
28862
29038
|
function rootValueOptionRefusal(option, label) {
|
|
28863
29039
|
const command = label.replace(/^mutagent /, "");
|
|
28864
29040
|
const what = option === "--endpoint" ? "its own server endpoint, and would send the Mutagent platform key there" : "its own option";
|
|
28865
|
-
const env = option === "--endpoint" ? " or export MUTAGENT_ENDPOINT" : "";
|
|
29041
|
+
const env = option === "--endpoint" ? " or export MUTAGENT_ENDPOINT" : option === "--workspace" ? " or export MUTAGENT_WORKSPACE_ID" : "";
|
|
28866
29042
|
return new MutagentError("NOT_SUPPORTED", `${option} after '${label}' and before a -- is refused: the mutagent CLI would read it as ${what}, and the command it forwards would never see it.`, `For the mutagent CLI, put it before the command (mutagent ${option} <value> ${command} …)${env}.
|
|
28867
29043
|
For the forwarded command, put it after --: ${label} … -- … ${option} <value>.`, 2);
|
|
28868
29044
|
}
|
|
@@ -29254,7 +29430,7 @@ var program = new Command18;
|
|
|
29254
29430
|
program.name("mutagent").description(`Mutagent CLI - command-line client for the Mutagent platform
|
|
29255
29431
|
|
|
29256
29432
|
Documentation: https://docs.mutagent.io/cli
|
|
29257
|
-
Dashboard: https://app.mutagent.io`).option("--json", "Output results as JSON (for AI agents)").option("--api-key <key>", "Mutagent API key").option("--endpoint <url>", "Mutagent server endpoint").option("--non-interactive", "Disable interactive prompts (for CI/AI agents)").configureHelp({
|
|
29433
|
+
Dashboard: https://app.mutagent.io`).option("--json", "Output results as JSON (for AI agents)").option("--api-key <key>", "Mutagent API key").option("--endpoint <url>", "Mutagent server endpoint").option("--workspace <name|id>", "Workspace for this command only (beats MUTAGENT_WORKSPACE_ID and the selection)").option("--org <id|slug>", "Check that the key belongs to this organization (its id, slug or name); a mismatch exits 1 and sends nothing (with login: the organization to sign in to)").option("--non-interactive", "Disable interactive prompts (for CI/AI agents)").configureHelp({
|
|
29258
29434
|
sortSubcommands: true,
|
|
29259
29435
|
showGlobalOptions: true
|
|
29260
29436
|
});
|
|
@@ -29272,9 +29448,11 @@ ${chalk37.yellow("Managed agents:")}
|
|
|
29272
29448
|
mutagent agent --help ${chalk37.dim("check, pack, run, deploy, activate, list, inspect, retire")}
|
|
29273
29449
|
|
|
29274
29450
|
${chalk37.yellow("Workspace:")}
|
|
29275
|
-
mutagent
|
|
29276
|
-
mutagent workspaces
|
|
29277
|
-
mutagent
|
|
29451
|
+
mutagent workspaces use <name> ${chalk37.dim("Choose the workspace every later command works in")}
|
|
29452
|
+
mutagent workspaces current ${chalk37.dim("The key's scope, organization and the verified workspace")}
|
|
29453
|
+
mutagent workspaces list ${chalk37.dim("Your workspaces in the key's organization")}
|
|
29454
|
+
--workspace <name|id> ${chalk37.dim("One command in another workspace (then MUTAGENT_WORKSPACE_ID, then the selection)")}
|
|
29455
|
+
mutagent login --org <slug> ${chalk37.dim("Sign in for another organization")}
|
|
29278
29456
|
mutagent providers list ${chalk37.dim("LLM providers in the workspace")}
|
|
29279
29457
|
mutagent helix models ${chalk37.dim("Models Helix can use in the cloud, and the workspace default model")}
|
|
29280
29458
|
mutagent env ls ${chalk37.dim("Environments: variables and secrets for tools")}
|
|
@@ -29337,7 +29515,7 @@ var rootOptionError = forwardedRootOptionRefusal(rawArgs);
|
|
|
29337
29515
|
if (rootOptionError !== undefined) {
|
|
29338
29516
|
handleError(rootOptionError, rawArgs.includes("--json"));
|
|
29339
29517
|
}
|
|
29340
|
-
program.hook("preAction", (thisCommand) => {
|
|
29518
|
+
program.hook("preAction", (thisCommand, actionCommand) => {
|
|
29341
29519
|
const globalOpts = thisCommand.optsWithGlobals();
|
|
29342
29520
|
if (globalOpts.apiKey && !process.env.MUTAGENT_API_KEY) {
|
|
29343
29521
|
process.env.MUTAGENT_API_KEY = globalOpts.apiKey;
|
|
@@ -29345,6 +29523,13 @@ program.hook("preAction", (thisCommand) => {
|
|
|
29345
29523
|
if (globalOpts.endpoint && !process.env.MUTAGENT_ENDPOINT) {
|
|
29346
29524
|
process.env.MUTAGENT_ENDPOINT = globalOpts.endpoint;
|
|
29347
29525
|
}
|
|
29526
|
+
if (globalOpts.workspace) {
|
|
29527
|
+
process.env.MUTAGENT_WORKSPACE_ID = globalOpts.workspace;
|
|
29528
|
+
setWorkspaceFlag(globalOpts.workspace);
|
|
29529
|
+
}
|
|
29530
|
+
if (actionCommand.name() !== "login") {
|
|
29531
|
+
setOrgAssertion(globalOpts.org);
|
|
29532
|
+
}
|
|
29348
29533
|
if (globalOpts.nonInteractive || process.env.CI === "true" || !process.stdin.isTTY) {
|
|
29349
29534
|
process.env.MUTAGENT_NON_INTERACTIVE = "true";
|
|
29350
29535
|
}
|
|
@@ -29388,4 +29573,4 @@ if (isAgentInvocation(rawArgs)) {
|
|
|
29388
29573
|
program.parse(argvForCommander, { from: "user" });
|
|
29389
29574
|
}
|
|
29390
29575
|
|
|
29391
|
-
//# debugId=
|
|
29576
|
+
//# debugId=22EA0CB4BCE602F064756E2164756E21
|