@mutagent/cli 0.1.379 → 0.1.380
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 +908 -750
- package/dist/index.js +479 -402
- 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,430 @@ 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
|
+
tenancyHeaders: () => tenancyHeaders,
|
|
16684
|
+
tenancyFailure: () => tenancyFailure,
|
|
16685
|
+
setWorkspaceFlag: () => setWorkspaceFlag,
|
|
16686
|
+
setOrgAssertion: () => setOrgAssertion,
|
|
16687
|
+
resolveKeyTenancy: () => resolveKeyTenancy,
|
|
16688
|
+
otherOrganizationHint: () => otherOrganizationHint,
|
|
16689
|
+
noWorkspaceSelected: () => noWorkspaceSelected,
|
|
16690
|
+
matchesOrganization: () => matchesOrganization,
|
|
16691
|
+
looksLikeWorkspaceId: () => looksLikeWorkspaceId,
|
|
16692
|
+
listKeyWorkspaces: () => listKeyWorkspaces,
|
|
16693
|
+
findWorkspace: () => findWorkspace,
|
|
16694
|
+
fetchOrganizations: () => fetchOrganizations,
|
|
16695
|
+
fetchKeySelf: () => fetchKeySelf,
|
|
16696
|
+
envKeyHasNoSelection: () => envKeyHasNoSelection,
|
|
16697
|
+
configTenancyHeaders: () => configTenancyHeaders,
|
|
16698
|
+
TenancyError: () => TenancyError,
|
|
16699
|
+
TENANCY_DENIED: () => TENANCY_DENIED
|
|
16700
|
+
});
|
|
16701
|
+
function looksLikeWorkspaceId(value2) {
|
|
16702
|
+
return UUID_PATTERN.test(value2);
|
|
16703
|
+
}
|
|
16704
|
+
function tenancyHeaders(t) {
|
|
16705
|
+
const headers = {};
|
|
16706
|
+
if (!t.workspaceId)
|
|
16707
|
+
return headers;
|
|
16708
|
+
if (t.keyScope === "workspace") {
|
|
16709
|
+
if (t.workspaceOrigin === "override")
|
|
16710
|
+
headers["x-workspace-id"] = t.workspaceId;
|
|
16711
|
+
return headers;
|
|
16712
|
+
}
|
|
16713
|
+
headers["x-workspace-id"] = t.workspaceId;
|
|
16714
|
+
if (t.keyScope === "user" && t.organizationId)
|
|
16715
|
+
headers["x-organization-id"] = t.organizationId;
|
|
16716
|
+
return headers;
|
|
16717
|
+
}
|
|
16718
|
+
function configTenancyHeaders(config) {
|
|
16719
|
+
return tenancyHeaders({
|
|
16720
|
+
keyScope: config.keyScope,
|
|
16721
|
+
workspaceId: config.defaultWorkspace,
|
|
16722
|
+
workspaceOrigin: config.workspaceOrigin,
|
|
16723
|
+
organizationId: config.defaultOrganization
|
|
16724
|
+
});
|
|
16725
|
+
}
|
|
16726
|
+
function parseBody(body) {
|
|
16727
|
+
try {
|
|
16728
|
+
const parsed = JSON.parse(body);
|
|
16729
|
+
if (parsed && typeof parsed === "object")
|
|
16730
|
+
return parsed;
|
|
16731
|
+
} catch {}
|
|
16732
|
+
return {};
|
|
16733
|
+
}
|
|
16734
|
+
function tenancyFailure(status, body, ctx) {
|
|
16735
|
+
const parsed = parseBody(body);
|
|
16736
|
+
if (status === 401) {
|
|
16737
|
+
return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login", 2);
|
|
16738
|
+
}
|
|
16739
|
+
if (status === 403 && parsed.code === TENANCY_DENIED) {
|
|
16740
|
+
const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
|
|
16741
|
+
if (ctx.workspaceOrigin === "selection") {
|
|
16742
|
+
return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
|
|
16743
|
+
}
|
|
16744
|
+
const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
|
|
16745
|
+
return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
|
|
16746
|
+
}
|
|
16747
|
+
if (status === 403 && ctx.keyScope === "workspace" && ctx.workspaceOrigin === "override") {
|
|
16748
|
+
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)");
|
|
16749
|
+
}
|
|
16750
|
+
if (status === 400 && (parsed.message ?? body).toLowerCase().includes("x-workspace-id")) {
|
|
16751
|
+
return noWorkspaceSelected();
|
|
16752
|
+
}
|
|
16753
|
+
return;
|
|
16754
|
+
}
|
|
16755
|
+
function noWorkspaceSelected() {
|
|
16756
|
+
const fix = envKeyReplacesFile() ? ENV_KEY_WORKSPACE_FIX : "Run: mutagent workspaces use <name>";
|
|
16757
|
+
return new TenancyError("WORKSPACE_REQUIRED", "No workspace selected.", fix, 3);
|
|
16758
|
+
}
|
|
16759
|
+
function envKeyHasNoSelection() {
|
|
16760
|
+
return new TenancyError("WORKSPACE_FROM_ENV", "With MUTAGENT_API_KEY set, the workspace comes from --workspace or MUTAGENT_WORKSPACE_ID.", ENV_KEY_WORKSPACE_FIX);
|
|
16761
|
+
}
|
|
16762
|
+
async function tenancyFetch(url, init) {
|
|
16763
|
+
try {
|
|
16764
|
+
return await fetch(url, init);
|
|
16765
|
+
} catch {
|
|
16766
|
+
throw new MutagentError("REQUEST_FAILED", `Unable to connect to the Mutagent server at ${new URL(url).origin}.`, "Check your connection and the endpoint: mutagent config get endpoint");
|
|
16767
|
+
}
|
|
16768
|
+
}
|
|
16769
|
+
async function fetchKeySelf(apiKey, endpoint, opts = {}) {
|
|
16770
|
+
const response = await tenancyFetch(`${endpoint}/api/api-keys/self`, {
|
|
16771
|
+
headers: { "x-api-key": apiKey, ...opts.headers }
|
|
16772
|
+
});
|
|
16773
|
+
if (!response.ok) {
|
|
16774
|
+
const body = await response.text().catch(() => "");
|
|
16775
|
+
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");
|
|
16776
|
+
}
|
|
16777
|
+
return await response.json();
|
|
16778
|
+
}
|
|
16779
|
+
async function fetchOrganizations(apiKey, endpoint) {
|
|
16780
|
+
try {
|
|
16781
|
+
const response = await fetch(`${endpoint}/api/organizations`, {
|
|
16782
|
+
headers: { "x-api-key": apiKey }
|
|
16783
|
+
});
|
|
16784
|
+
if (!response.ok)
|
|
16785
|
+
return [];
|
|
16786
|
+
const data = await response.json();
|
|
16787
|
+
return data.data ?? [];
|
|
16788
|
+
} catch {
|
|
16789
|
+
return [];
|
|
16790
|
+
}
|
|
16791
|
+
}
|
|
16792
|
+
function matchesOrganization(value2, org) {
|
|
16793
|
+
if (!org)
|
|
16794
|
+
return false;
|
|
16795
|
+
return value2 === org.id || org.slug !== undefined && value2 === org.slug;
|
|
16796
|
+
}
|
|
16797
|
+
function otherOrganizationHint(wanted, keyOrg, orgs) {
|
|
16798
|
+
const others = orgs.filter((o) => o.id !== keyOrg?.id);
|
|
16799
|
+
const where = keyOrg ? ` in ${keyOrg.name}` : "";
|
|
16800
|
+
const what = `No workspace "${wanted}"${where}.`;
|
|
16801
|
+
const named = others.map((o) => `${o.name} (${o.slug ?? o.id})`).join(", ");
|
|
16802
|
+
if (others.length === 1 && others[0]) {
|
|
16803
|
+
const only = others[0];
|
|
16804
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organization: ${named}.`, `To switch: mutagent login --org ${only.slug ?? only.id}`);
|
|
16805
|
+
}
|
|
16806
|
+
if (others.length > 1) {
|
|
16807
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", `${what} Your other organizations: ${named}.`, "To switch: mutagent login --org <slug>");
|
|
16808
|
+
}
|
|
16809
|
+
return new TenancyError("WORKSPACE_NOT_FOUND", what, "See: mutagent workspaces list");
|
|
16810
|
+
}
|
|
16811
|
+
function setOrgAssertion(value2) {
|
|
16812
|
+
const trimmed = value2?.trim();
|
|
16813
|
+
orgAssertion = trimmed === "" ? undefined : trimmed;
|
|
16814
|
+
}
|
|
16815
|
+
function setWorkspaceFlag(value2) {
|
|
16816
|
+
const trimmed = value2?.trim();
|
|
16817
|
+
workspaceFlag = trimmed === "" ? undefined : trimmed;
|
|
16818
|
+
}
|
|
16819
|
+
function endpointOf(configured) {
|
|
16820
|
+
const trimmed = configured?.trim();
|
|
16821
|
+
return (trimmed === undefined || trimmed === "" ? "https://api.mutagent.io" : trimmed).replace(/\/+$/, "");
|
|
16822
|
+
}
|
|
16823
|
+
async function probeKeyScope(apiKey, endpoint, workspaceNamed) {
|
|
16824
|
+
const fromFile = !envKeyReplacesFile();
|
|
16825
|
+
if (!fromFile && workspaceNamed && orgAssertion === undefined)
|
|
16826
|
+
return;
|
|
16827
|
+
try {
|
|
16828
|
+
const self = await fetchKeySelf(apiKey, endpoint);
|
|
16829
|
+
if (fromFile)
|
|
16830
|
+
recordKeyScope(self);
|
|
16831
|
+
return self;
|
|
16832
|
+
} catch (error3) {
|
|
16833
|
+
if (error3 instanceof MutagentError && (error3.code === "AUTH_EXPIRED" || error3.code === "REQUEST_FAILED"))
|
|
16834
|
+
throw error3;
|
|
16835
|
+
return;
|
|
16836
|
+
}
|
|
16837
|
+
}
|
|
16838
|
+
function enforceOrgAssertion(org) {
|
|
16839
|
+
if (orgAssertion === undefined)
|
|
16840
|
+
return;
|
|
16841
|
+
if (org === undefined) {
|
|
16842
|
+
throw new TenancyError("ORG_UNKNOWN", "This key's organization is unknown, so --org cannot be checked.", "Run: mutagent workspaces current");
|
|
16843
|
+
}
|
|
16844
|
+
if (!matchesOrganization(orgAssertion, org)) {
|
|
16845
|
+
throw new TenancyError("ORG_MISMATCH", `Your key is for ${org.name}.`, `Run: mutagent login --org ${orgAssertion}`);
|
|
16846
|
+
}
|
|
16847
|
+
}
|
|
16848
|
+
async function resolveKeyTenancy(apiKey, opts = {}) {
|
|
16849
|
+
const config = loadConfig();
|
|
16850
|
+
const endpoint = endpointOf(config.endpoint);
|
|
16851
|
+
let keyScope = config.keyScope;
|
|
16852
|
+
let organization = config.defaultOrganization ? { id: config.defaultOrganization, name: config.organizationName ?? config.defaultOrganization, slug: config.organizationSlug } : undefined;
|
|
16853
|
+
let workspaceId = config.defaultWorkspace;
|
|
16854
|
+
let workspaceName = config.workspaceName;
|
|
16855
|
+
const workspaceOrigin = config.workspaceOrigin;
|
|
16856
|
+
if (keyScope === undefined && (opts.probe !== false || orgAssertion !== undefined)) {
|
|
16857
|
+
const self = await probeKeyScope(apiKey, endpoint, workspaceId !== undefined);
|
|
16858
|
+
if (self) {
|
|
16859
|
+
keyScope = self.scope;
|
|
16860
|
+
organization = self.organization ?? undefined;
|
|
16861
|
+
if (self.scope === "workspace" && self.workspace && workspaceOrigin !== "override") {
|
|
16862
|
+
workspaceId = self.workspace.id;
|
|
16863
|
+
workspaceName = self.workspace.name;
|
|
16864
|
+
}
|
|
16865
|
+
}
|
|
16866
|
+
}
|
|
16867
|
+
enforceOrgAssertion(organization);
|
|
16868
|
+
if (opts.resolveOverride !== false && workspaceFlag !== undefined && workspaceId === workspaceFlag && !looksLikeWorkspaceId(workspaceId)) {
|
|
16869
|
+
const found = await findWorkspace(workspaceId, { apiKey, endpoint, keyScope, organization, workspaceId: config.defaultWorkspace, workspaceName });
|
|
16870
|
+
workspaceId = found.id;
|
|
16871
|
+
workspaceName = found.name;
|
|
16872
|
+
process.env.MUTAGENT_WORKSPACE_ID = found.id;
|
|
16873
|
+
}
|
|
16874
|
+
return { endpoint, keyScope, organization, workspaceId, workspaceName, workspaceOrigin };
|
|
16875
|
+
}
|
|
16876
|
+
async function listKeyWorkspaces(apiKey, endpoint, t) {
|
|
16877
|
+
const headers = { "x-api-key": apiKey };
|
|
16878
|
+
if (t.keyScope === "user" && t.organization)
|
|
16879
|
+
headers["x-organization-id"] = t.organization.id;
|
|
16880
|
+
const response = await tenancyFetch(`${endpoint}/api/workspaces`, { headers });
|
|
16881
|
+
if (!response.ok) {
|
|
16882
|
+
const body = await response.text().catch(() => "");
|
|
16883
|
+
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");
|
|
16884
|
+
}
|
|
16885
|
+
const data = await response.json();
|
|
16886
|
+
return data.workspaces ?? [];
|
|
16887
|
+
}
|
|
16888
|
+
async function findWorkspace(wanted, t) {
|
|
16889
|
+
if (t.keyScope === "workspace") {
|
|
16890
|
+
if (t.workspaceId && (wanted === t.workspaceId || wanted === t.workspaceName)) {
|
|
16891
|
+
return { id: t.workspaceId, name: t.workspaceName ?? t.workspaceId };
|
|
16892
|
+
}
|
|
16893
|
+
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)");
|
|
16894
|
+
}
|
|
16895
|
+
const rows = await listKeyWorkspaces(t.apiKey, t.endpoint, t);
|
|
16896
|
+
const byId = rows.find((w) => w.id === wanted);
|
|
16897
|
+
if (byId)
|
|
16898
|
+
return { id: byId.id, name: byId.name };
|
|
16899
|
+
let matches = rows.filter((w) => w.name === wanted || w.slug === wanted);
|
|
16900
|
+
if (matches.length === 0)
|
|
16901
|
+
matches = rows.filter((w) => w.name.toLowerCase() === wanted.toLowerCase());
|
|
16902
|
+
if (matches.length === 1 && matches[0])
|
|
16903
|
+
return { id: matches[0].id, name: matches[0].name };
|
|
16904
|
+
if (matches.length > 1) {
|
|
16905
|
+
throw new TenancyError("WORKSPACE_AMBIGUOUS", `Several workspaces are named "${wanted}": ${matches.map((w) => `${w.name} (${w.id})`).join(", ")}.`, "Run: mutagent workspaces use <id>");
|
|
16906
|
+
}
|
|
16907
|
+
const orgs = await fetchOrganizations(t.apiKey, t.endpoint);
|
|
16908
|
+
throw otherOrganizationHint(wanted, t.organization, orgs);
|
|
16909
|
+
}
|
|
16910
|
+
var TENANCY_DENIED = "TENANCY_DENIED", UUID_PATTERN, TenancyError, ENV_KEY_WORKSPACE_FIX = "Pass --workspace <name|id> or set MUTAGENT_WORKSPACE_ID", orgAssertion, workspaceFlag;
|
|
16911
|
+
var init_organization_scope = __esm(() => {
|
|
16912
|
+
init_errors();
|
|
16913
|
+
init_config();
|
|
16914
|
+
UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16915
|
+
TenancyError = class TenancyError extends MutagentError {
|
|
16916
|
+
constructor(code, what, fix, exitCode = 1) {
|
|
16917
|
+
super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""), exitCode);
|
|
16918
|
+
this.name = "TenancyError";
|
|
16919
|
+
}
|
|
16920
|
+
};
|
|
16921
|
+
});
|
|
16922
|
+
|
|
16923
|
+
// src/lib/provider-request.ts
|
|
16924
|
+
function buildCreateProviderBody(data) {
|
|
16925
|
+
const body = {
|
|
16926
|
+
name: data.name,
|
|
16927
|
+
provider: data.provider,
|
|
16928
|
+
isDefault: data.isDefault ?? false
|
|
16929
|
+
};
|
|
16930
|
+
if (data.apiKey)
|
|
16931
|
+
body.apiKey = data.apiKey;
|
|
16932
|
+
if (data.baseUrl)
|
|
16933
|
+
body.baseUrl = data.baseUrl;
|
|
16934
|
+
if (data.hostedFamily)
|
|
16935
|
+
body.hostedFamily = data.hostedFamily;
|
|
16936
|
+
if (data.credentialFields && Object.keys(data.credentialFields).length > 0) {
|
|
16937
|
+
body.credentialFields = data.credentialFields;
|
|
16938
|
+
}
|
|
16939
|
+
if (data.metadata && Object.keys(data.metadata).length > 0)
|
|
16940
|
+
body.metadata = data.metadata;
|
|
16941
|
+
if (data.supportedModels !== undefined)
|
|
16942
|
+
body.supportedModels = data.supportedModels;
|
|
16943
|
+
return body;
|
|
16944
|
+
}
|
|
16945
|
+
|
|
16522
16946
|
// src/lib/sdk-debug.ts
|
|
16523
16947
|
function isDebugEnabled(env = process.env) {
|
|
16524
16948
|
return TRUTHY.has((env.MUTAGENT_DEBUG ?? "").trim().toLowerCase());
|
|
@@ -16613,318 +17037,42 @@ var init_sdk_debug = __esm(() => {
|
|
|
16613
17037
|
};
|
|
16614
17038
|
});
|
|
16615
17039
|
|
|
16616
|
-
// src/lib/
|
|
16617
|
-
|
|
16618
|
-
|
|
17040
|
+
// src/lib/sdk-mappers.ts
|
|
17041
|
+
import { LlmProviderType as SDKProviderEnum } from "@mutagent/sdk/models";
|
|
17042
|
+
function isProviderType(value2) {
|
|
17043
|
+
return PROVIDER_TYPES.includes(value2);
|
|
16619
17044
|
}
|
|
16620
|
-
function
|
|
16621
|
-
|
|
16622
|
-
return false;
|
|
16623
|
-
return body.toLowerCase().includes("organization scope mismatch");
|
|
17045
|
+
function narrowProviderType(value2) {
|
|
17046
|
+
return isProviderType(value2) ? value2 : value2;
|
|
16624
17047
|
}
|
|
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
|
-
}
|
|
17048
|
+
function toIsoString(value2) {
|
|
17049
|
+
if (value2 === null || value2 === undefined)
|
|
17050
|
+
return null;
|
|
17051
|
+
return value2 instanceof Date ? value2.toISOString() : value2;
|
|
16637
17052
|
}
|
|
16638
|
-
|
|
16639
|
-
return
|
|
17053
|
+
function toAgent(sdk) {
|
|
17054
|
+
return {
|
|
17055
|
+
id: sdk.id,
|
|
17056
|
+
name: sdk.name,
|
|
17057
|
+
slug: sdk.slug,
|
|
17058
|
+
description: sdk.description,
|
|
17059
|
+
systemPrompt: sdk.systemPrompt,
|
|
17060
|
+
model: sdk.model,
|
|
17061
|
+
maxTurns: sdk.maxTurns,
|
|
17062
|
+
permissionMode: sdk.permissionMode,
|
|
17063
|
+
allowedTools: sdk.allowedTools,
|
|
17064
|
+
disallowedTools: sdk.disallowedTools,
|
|
17065
|
+
status: sdk.status,
|
|
17066
|
+
isPublic: sdk.isPublic,
|
|
17067
|
+
metadata: sdk.metadata,
|
|
17068
|
+
tags: sdk.tags,
|
|
17069
|
+
createdBy: sdk.createdBy,
|
|
17070
|
+
createdAt: toIsoString(sdk.createdAt),
|
|
17071
|
+
updatedAt: toIsoString(sdk.updatedAt)
|
|
17072
|
+
};
|
|
16640
17073
|
}
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
if (orgs.length === 0) {
|
|
16644
|
-
if (isWellFormedOrganizationId(id))
|
|
16645
|
-
return;
|
|
16646
|
-
throw new MutagentError("INVALID_ORGANIZATION_ID", `"${id}" is not an organization id.`, `Organization ids look like org_xxxxxxxxxxxxxxxxxxxxx.
|
|
16647
|
-
` + "Log in so the id can be checked against your account: mutagent auth login --browser");
|
|
16648
|
-
}
|
|
16649
|
-
if (orgs.some((org) => org.id === id))
|
|
16650
|
-
return;
|
|
16651
|
-
throw new MutagentError("UNKNOWN_ORGANIZATION", `"${id}" is not an organization this API key can access.`, `Available: ${orgs.map((org) => org.id).join(", ")}
|
|
16652
|
-
` + "A stored organization that does not match the key makes every request fail with 403.");
|
|
16653
|
-
}
|
|
16654
|
-
function createOrgScopeRecoveringFetcher(hooks) {
|
|
16655
|
-
let recoveryUsed = false;
|
|
16656
|
-
async function recover() {
|
|
16657
|
-
if (recoveryUsed)
|
|
16658
|
-
return;
|
|
16659
|
-
recoveryUsed = true;
|
|
16660
|
-
const stale = hooks.currentOrganization();
|
|
16661
|
-
if (!stale)
|
|
16662
|
-
return;
|
|
16663
|
-
hooks.adoptOrganization(undefined);
|
|
16664
|
-
hooks.forgetStoredOrganization?.();
|
|
16665
|
-
const orgs = await lookupOrganizations(hooks.apiKey, hooks.endpoint);
|
|
16666
|
-
const resolved = orgs[0]?.id;
|
|
16667
|
-
if (!resolved || resolved === stale)
|
|
16668
|
-
return;
|
|
16669
|
-
hooks.adoptOrganization(resolved);
|
|
16670
|
-
if (isDebugEnabled()) {
|
|
16671
|
-
process.stderr.write(`[mutagent] Stored organization was rejected by the API; re-resolved and retried once.
|
|
16672
|
-
`);
|
|
16673
|
-
}
|
|
16674
|
-
return resolved;
|
|
16675
|
-
}
|
|
16676
|
-
return async function fetchWithOrgRecovery(input, init) {
|
|
16677
|
-
const retryTemplate = input instanceof Request ? input.clone() : undefined;
|
|
16678
|
-
const response = init == null ? await fetch(input) : await fetch(input, init);
|
|
16679
|
-
if (response.status !== 403)
|
|
16680
|
-
return response;
|
|
16681
|
-
const body = await response.clone().text().catch(() => "");
|
|
16682
|
-
if (!isOrganizationScopeMismatch(response.status, body))
|
|
16683
|
-
return response;
|
|
16684
|
-
const resolved = await recover();
|
|
16685
|
-
if (!resolved)
|
|
16686
|
-
return response;
|
|
16687
|
-
if (retryTemplate) {
|
|
16688
|
-
retryTemplate.headers.set("x-organization-id", resolved);
|
|
16689
|
-
return fetch(retryTemplate);
|
|
16690
|
-
}
|
|
16691
|
-
const headers = new Headers(init?.headers);
|
|
16692
|
-
headers.set("x-organization-id", resolved);
|
|
16693
|
-
return fetch(input, { ...init, headers });
|
|
16694
|
-
};
|
|
16695
|
-
}
|
|
16696
|
-
var ORGANIZATION_ID_PATTERN;
|
|
16697
|
-
var init_organization_scope = __esm(() => {
|
|
16698
|
-
init_errors();
|
|
16699
|
-
init_sdk_debug();
|
|
16700
|
-
ORGANIZATION_ID_PATTERN = /^org_[A-Za-z0-9_-]{8,}$/;
|
|
16701
|
-
});
|
|
16702
|
-
|
|
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
|
-
};
|
|
16925
|
-
}
|
|
16926
|
-
function toAgentList(sdk) {
|
|
16927
|
-
return sdk.map(toAgent);
|
|
17074
|
+
function toAgentList(sdk) {
|
|
17075
|
+
return sdk.map(toAgent);
|
|
16928
17076
|
}
|
|
16929
17077
|
function toWorkspace(sdk) {
|
|
16930
17078
|
return {
|
|
@@ -16977,15 +17125,6 @@ var init_sdk_mappers = __esm(() => {
|
|
|
16977
17125
|
});
|
|
16978
17126
|
|
|
16979
17127
|
// 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
17128
|
import { Mutagent, HTTPClient } from "@mutagent/sdk";
|
|
16990
17129
|
|
|
16991
17130
|
class SDKClientWrapper {
|
|
@@ -16994,32 +17133,24 @@ class SDKClientWrapper {
|
|
|
16994
17133
|
endpoint;
|
|
16995
17134
|
workspaceId;
|
|
16996
17135
|
organizationId;
|
|
16997
|
-
|
|
17136
|
+
tenancy;
|
|
16998
17137
|
debugSecrets = new Set;
|
|
16999
|
-
recoveringFetch;
|
|
17000
17138
|
constructor(opts) {
|
|
17001
17139
|
this.apiKey = opts.apiKey;
|
|
17002
17140
|
this.endpoint = opts.serverURL ?? "http://localhost:3003";
|
|
17003
17141
|
this.workspaceId = opts.workspaceId;
|
|
17004
17142
|
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 });
|
|
17143
|
+
this.tenancy = {
|
|
17144
|
+
keyScope: opts.keyScope,
|
|
17145
|
+
workspaceId: opts.workspaceId,
|
|
17146
|
+
workspaceName: opts.workspaceName,
|
|
17147
|
+
workspaceOrigin: opts.workspaceOrigin ?? (opts.workspaceId ? "selection" : undefined),
|
|
17148
|
+
organizationName: opts.organizationName
|
|
17149
|
+
};
|
|
17150
|
+
const httpClient = new HTTPClient;
|
|
17018
17151
|
httpClient.addHook("beforeRequest", (req) => {
|
|
17019
|
-
|
|
17020
|
-
req.headers.set(
|
|
17021
|
-
if (this.organizationId)
|
|
17022
|
-
req.headers.set("x-organization-id", this.organizationId);
|
|
17152
|
+
for (const [name, value2] of Object.entries(this.tenancyHeaders()))
|
|
17153
|
+
req.headers.set(name, value2);
|
|
17023
17154
|
return req;
|
|
17024
17155
|
});
|
|
17025
17156
|
this.debugSecrets.add(this.apiKey);
|
|
@@ -17036,6 +17167,11 @@ class SDKClientWrapper {
|
|
|
17036
17167
|
handleError(error3) {
|
|
17037
17168
|
if (error3 && typeof error3 === "object") {
|
|
17038
17169
|
const err = error3;
|
|
17170
|
+
if (err.statusCode) {
|
|
17171
|
+
const tenancy = tenancyFailure(err.statusCode, err.body ?? "", this.tenancy);
|
|
17172
|
+
if (tenancy)
|
|
17173
|
+
throw tenancy;
|
|
17174
|
+
}
|
|
17039
17175
|
if (err.statusCode === 401) {
|
|
17040
17176
|
throw new AuthenticationError;
|
|
17041
17177
|
}
|
|
@@ -17074,28 +17210,26 @@ class SDKClientWrapper {
|
|
|
17074
17210
|
} else if (optHeaders) {
|
|
17075
17211
|
extraHeaders = optHeaders;
|
|
17076
17212
|
}
|
|
17077
|
-
const
|
|
17078
|
-
if (this.workspaceId)
|
|
17079
|
-
tenancyHeaders["x-workspace-id"] = this.workspaceId;
|
|
17080
|
-
if (this.organizationId)
|
|
17081
|
-
tenancyHeaders["x-organization-id"] = this.organizationId;
|
|
17082
|
-
const response = await this.recoveringFetch(`${this.endpoint}${path}`, {
|
|
17213
|
+
const response = await fetch(`${this.endpoint}${path}`, {
|
|
17083
17214
|
...options,
|
|
17084
17215
|
headers: {
|
|
17085
17216
|
"x-api-key": this.apiKey,
|
|
17086
17217
|
"Content-Type": "application/json",
|
|
17087
|
-
...tenancyHeaders,
|
|
17218
|
+
...this.tenancyHeaders(),
|
|
17088
17219
|
...extraHeaders
|
|
17089
17220
|
}
|
|
17090
17221
|
});
|
|
17091
17222
|
if (!response.ok) {
|
|
17223
|
+
const error3 = await response.text();
|
|
17224
|
+
const tenancy = tenancyFailure(response.status, error3, this.tenancy);
|
|
17225
|
+
if (tenancy)
|
|
17226
|
+
throw tenancy;
|
|
17092
17227
|
if (response.status === 401) {
|
|
17093
17228
|
throw new AuthenticationError;
|
|
17094
17229
|
}
|
|
17095
17230
|
if (response.status === 403) {
|
|
17096
|
-
throw new ApiError(403,
|
|
17231
|
+
throw new ApiError(403, error3 || "Access denied");
|
|
17097
17232
|
}
|
|
17098
|
-
const error3 = await response.text();
|
|
17099
17233
|
const errorLower = error3.toLowerCase();
|
|
17100
17234
|
if (errorLower.includes("workspace") && (errorLower.includes("missing") || errorLower.includes("required")) || errorLower.includes("x-workspace-id")) {
|
|
17101
17235
|
throw new WorkspaceContextError(error3);
|
|
@@ -17241,11 +17375,9 @@ class SDKClientWrapper {
|
|
|
17241
17375
|
}
|
|
17242
17376
|
async getModelsCatalog() {
|
|
17243
17377
|
try {
|
|
17244
|
-
|
|
17245
|
-
if (!wsId)
|
|
17378
|
+
if (!this.workspaceId)
|
|
17246
17379
|
return { models: [] };
|
|
17247
|
-
|
|
17248
|
-
return await this.request(`/api/providers/catalog${qs}`);
|
|
17380
|
+
return await this.request("/api/providers/catalog");
|
|
17249
17381
|
} catch {
|
|
17250
17382
|
return { models: [] };
|
|
17251
17383
|
}
|
|
@@ -17253,56 +17385,17 @@ class SDKClientWrapper {
|
|
|
17253
17385
|
getCurrentWorkspaceId() {
|
|
17254
17386
|
return this.workspaceId;
|
|
17255
17387
|
}
|
|
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;
|
|
17388
|
+
tenancyHeaders() {
|
|
17389
|
+
return tenancyHeaders({
|
|
17390
|
+
keyScope: this.tenancy.keyScope,
|
|
17391
|
+
workspaceId: this.workspaceId,
|
|
17392
|
+
workspaceOrigin: this.tenancy.workspaceOrigin,
|
|
17393
|
+
organizationId: this.organizationId
|
|
17394
|
+
});
|
|
17298
17395
|
}
|
|
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
|
-
}
|
|
17396
|
+
requireSelectedWorkspace() {
|
|
17397
|
+
if (!this.workspaceId)
|
|
17398
|
+
throw noWorkspaceSelected();
|
|
17306
17399
|
return this.workspaceId;
|
|
17307
17400
|
}
|
|
17308
17401
|
async testProvider(id) {
|
|
@@ -17316,7 +17409,7 @@ class SDKClientWrapper {
|
|
|
17316
17409
|
}
|
|
17317
17410
|
}
|
|
17318
17411
|
async createProvider(data) {
|
|
17319
|
-
this.
|
|
17412
|
+
this.requireSelectedWorkspace();
|
|
17320
17413
|
registerSecret(data.apiKey);
|
|
17321
17414
|
registerSecretFields(data.credentialFields);
|
|
17322
17415
|
const body = buildCreateProviderBody(data);
|
|
@@ -17331,7 +17424,7 @@ class SDKClientWrapper {
|
|
|
17331
17424
|
}
|
|
17332
17425
|
}
|
|
17333
17426
|
async updateProvider(id, data) {
|
|
17334
|
-
const workspaceId = this.
|
|
17427
|
+
const workspaceId = this.requireSelectedWorkspace();
|
|
17335
17428
|
if (data.apiKey)
|
|
17336
17429
|
this.debugSecrets.add(data.apiKey);
|
|
17337
17430
|
registerSecret(data.apiKey);
|
|
@@ -17370,7 +17463,7 @@ class SDKClientWrapper {
|
|
|
17370
17463
|
}
|
|
17371
17464
|
}
|
|
17372
17465
|
async deleteProvider(id) {
|
|
17373
|
-
const workspaceId = this.
|
|
17466
|
+
const workspaceId = this.requireSelectedWorkspace();
|
|
17374
17467
|
try {
|
|
17375
17468
|
await this.sdk.llmProviders.deleteProviderConfig({ id });
|
|
17376
17469
|
return { workspaceId };
|
|
@@ -17385,21 +17478,18 @@ async function getSDKClient() {
|
|
|
17385
17478
|
if (!apiKey) {
|
|
17386
17479
|
throw new AuthenticationError;
|
|
17387
17480
|
}
|
|
17388
|
-
const
|
|
17481
|
+
const tenancy = await resolveKeyTenancy(apiKey);
|
|
17482
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
17483
|
+
throw noWorkspaceSelected();
|
|
17389
17484
|
sdkClient = new SDKClientWrapper({
|
|
17390
17485
|
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
|
-
}
|
|
17486
|
+
serverURL: tenancy.endpoint,
|
|
17487
|
+
workspaceId: tenancy.workspaceId,
|
|
17488
|
+
workspaceName: tenancy.workspaceName,
|
|
17489
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
17490
|
+
organizationId: tenancy.organization?.id,
|
|
17491
|
+
organizationName: tenancy.organization?.name,
|
|
17492
|
+
keyScope: tenancy.keyScope
|
|
17403
17493
|
});
|
|
17404
17494
|
}
|
|
17405
17495
|
return sdkClient;
|
|
@@ -17417,19 +17507,6 @@ async function validateApiKey(apiKey, endpoint) {
|
|
|
17417
17507
|
return false;
|
|
17418
17508
|
}
|
|
17419
17509
|
}
|
|
17420
|
-
async function fetchWorkspaces(apiKey, endpoint, orgId) {
|
|
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
17510
|
var sdkClient = null;
|
|
17434
17511
|
var init_sdk_client = __esm(() => {
|
|
17435
17512
|
init_errors();
|
|
@@ -17558,7 +17635,7 @@ async function createAgentDeploymentAPI() {
|
|
|
17558
17635
|
const context = await getSDKClient();
|
|
17559
17636
|
let workspaceId;
|
|
17560
17637
|
try {
|
|
17561
|
-
workspaceId = context.
|
|
17638
|
+
workspaceId = context.requireSelectedWorkspace();
|
|
17562
17639
|
} catch (error3) {
|
|
17563
17640
|
if (error3 instanceof MutagentError)
|
|
17564
17641
|
error3.exitCode = 3;
|
|
@@ -18496,17 +18573,13 @@ async function installHelix(options, deps = {}) {
|
|
|
18496
18573
|
|
|
18497
18574
|
// src/lib/install-telemetry.ts
|
|
18498
18575
|
init_config();
|
|
18576
|
+
init_organization_scope();
|
|
18499
18577
|
var TELEMETRY_TIMEOUT_MS = 1500;
|
|
18500
18578
|
function resolveApiAuth() {
|
|
18501
18579
|
const apiKey = getApiKey();
|
|
18502
18580
|
const config = loadConfig();
|
|
18503
18581
|
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 };
|
|
18582
|
+
return { apiBase, apiKey, headers: configTenancyHeaders(config) };
|
|
18510
18583
|
}
|
|
18511
18584
|
async function reportInstall(event, deps = {}) {
|
|
18512
18585
|
try {
|
|
@@ -18619,6 +18692,7 @@ async function runPostOnboarding(deps = {}) {
|
|
|
18619
18692
|
// src/lib/auth-flow.ts
|
|
18620
18693
|
init_config();
|
|
18621
18694
|
init_sdk_client();
|
|
18695
|
+
init_organization_scope();
|
|
18622
18696
|
init_errors();
|
|
18623
18697
|
import { input, password, select } from "@inquirer/prompts";
|
|
18624
18698
|
import chalk3 from "chalk";
|
|
@@ -18667,6 +18741,17 @@ async function openBrowser(url) {
|
|
|
18667
18741
|
throw new BrowserAuthError("BROWSER_OPEN_FAILED", `Could not open browser automatically. Please visit: ${url}`);
|
|
18668
18742
|
}
|
|
18669
18743
|
}
|
|
18744
|
+
function withOrgPreselect(authUrl, org) {
|
|
18745
|
+
if (!org)
|
|
18746
|
+
return authUrl;
|
|
18747
|
+
try {
|
|
18748
|
+
const url = new URL(authUrl);
|
|
18749
|
+
url.searchParams.set("org", org);
|
|
18750
|
+
return url.toString();
|
|
18751
|
+
} catch {
|
|
18752
|
+
return `${authUrl}${authUrl.includes("?") ? "&" : "?"}org=${encodeURIComponent(org)}`;
|
|
18753
|
+
}
|
|
18754
|
+
}
|
|
18670
18755
|
function sleep(ms) {
|
|
18671
18756
|
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
18672
18757
|
}
|
|
@@ -18680,7 +18765,7 @@ async function performBrowserAuth(options, onStatusUpdate) {
|
|
|
18680
18765
|
const cliToken = generateCliToken();
|
|
18681
18766
|
onStatusUpdate?.("Initializing browser authentication...");
|
|
18682
18767
|
const initResponse = await initBrowserAuth(endpoint, cliToken);
|
|
18683
|
-
const
|
|
18768
|
+
const authUrl = withOrgPreselect(initResponse.authUrl, options.org);
|
|
18684
18769
|
if (!skipBrowserOpen) {
|
|
18685
18770
|
onStatusUpdate?.("Opening browser for authentication...");
|
|
18686
18771
|
try {
|
|
@@ -18797,7 +18882,7 @@ async function performLoginAction(opts) {
|
|
|
18797
18882
|
useBrowserAuth = method === "browser";
|
|
18798
18883
|
}
|
|
18799
18884
|
if (useBrowserAuth) {
|
|
18800
|
-
return loginWithBrowser(endpoint, output, wasFirstLogin);
|
|
18885
|
+
return loginWithBrowser(endpoint, output, wasFirstLogin, opts.org);
|
|
18801
18886
|
}
|
|
18802
18887
|
return loginWithPastedKey(endpoint, output, wasFirstLogin);
|
|
18803
18888
|
}
|
|
@@ -18814,55 +18899,76 @@ function buildLoginJsonResponse(result) {
|
|
|
18814
18899
|
}
|
|
18815
18900
|
};
|
|
18816
18901
|
}
|
|
18817
|
-
async function
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18902
|
+
async function describeKey(apiKey, endpoint, pick3) {
|
|
18903
|
+
let self;
|
|
18904
|
+
try {
|
|
18905
|
+
self = await fetchKeySelf(apiKey, endpoint);
|
|
18906
|
+
} catch {
|
|
18907
|
+
return {};
|
|
18822
18908
|
}
|
|
18823
|
-
const
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
}
|
|
18909
|
+
const organization = self.organization ?? undefined;
|
|
18910
|
+
const scope = {
|
|
18911
|
+
keyScope: self.scope,
|
|
18912
|
+
organizationId: organization?.id,
|
|
18913
|
+
organizationName: organization?.name,
|
|
18914
|
+
organizationSlug: organization?.slug
|
|
18915
|
+
};
|
|
18916
|
+
if (self.workspace) {
|
|
18917
|
+
return { ...scope, workspaceId: self.workspace.id, workspaceName: self.workspace.name };
|
|
18918
|
+
}
|
|
18919
|
+
const envWorkspace = process.env.MUTAGENT_WORKSPACE_ID?.trim();
|
|
18920
|
+
const rows = await listKeyWorkspaces(apiKey, endpoint, { keyScope: self.scope, organization }).catch(() => []);
|
|
18921
|
+
let chosen;
|
|
18922
|
+
if (envWorkspace)
|
|
18923
|
+
chosen = rows.find((w) => w.id === envWorkspace);
|
|
18924
|
+
else if (rows.length === 1)
|
|
18925
|
+
chosen = rows[0];
|
|
18926
|
+
else if (pick3 && rows.length > 1) {
|
|
18927
|
+
const id = await pick3(rows);
|
|
18928
|
+
chosen = rows.find((w) => w.id === id);
|
|
18929
|
+
}
|
|
18930
|
+
return chosen ? { ...scope, workspaceId: chosen.id, workspaceName: chosen.name } : scope;
|
|
18931
|
+
}
|
|
18932
|
+
function loginResult(apiKey, endpoint, described, wasFirstLogin) {
|
|
18844
18933
|
return {
|
|
18845
18934
|
authenticated: true,
|
|
18846
18935
|
apiKey,
|
|
18847
18936
|
endpoint,
|
|
18848
|
-
workspace:
|
|
18849
|
-
organization:
|
|
18937
|
+
workspace: described.workspaceId ? { id: described.workspaceId, name: described.workspaceName } : null,
|
|
18938
|
+
organization: described.organizationId ? { id: described.organizationId, name: described.organizationName } : null,
|
|
18850
18939
|
wasFirstLogin
|
|
18851
18940
|
};
|
|
18852
18941
|
}
|
|
18853
|
-
async function
|
|
18942
|
+
async function loginWithExistingKey(apiKey, endpoint, output, wasFirstLogin) {
|
|
18943
|
+
output.info("Validating API key...");
|
|
18944
|
+
const isValid = await validateApiKey(apiKey, endpoint);
|
|
18945
|
+
if (!isValid) {
|
|
18946
|
+
throw new MutagentError("INVALID_API_KEY", "Invalid API key or endpoint", "Check your API key and try again");
|
|
18947
|
+
}
|
|
18948
|
+
const described = await describeKey(apiKey, endpoint);
|
|
18949
|
+
saveFullCredentials({ apiKey, endpoint, ...described });
|
|
18950
|
+
return loginResult(apiKey, endpoint, described, wasFirstLogin);
|
|
18951
|
+
}
|
|
18952
|
+
async function loginWithBrowser(endpoint, output, wasFirstLogin, org) {
|
|
18854
18953
|
const spinner = ora2({ text: "Opening browser for authentication...", spinner: "dots" });
|
|
18855
18954
|
try {
|
|
18856
18955
|
spinner.start();
|
|
18857
|
-
const result = await performBrowserAuth({ endpoint, timeout: 300000, pollInterval: 2000 }, (message) => {
|
|
18956
|
+
const result = await performBrowserAuth({ endpoint, timeout: 300000, pollInterval: 2000, org }, (message) => {
|
|
18858
18957
|
spinner.text = message;
|
|
18859
18958
|
});
|
|
18860
18959
|
spinner.succeed("Authenticated successfully!");
|
|
18960
|
+
const self = await fetchKeySelf(result.apiKey, endpoint).catch(() => {
|
|
18961
|
+
return;
|
|
18962
|
+
});
|
|
18861
18963
|
saveFullCredentials({
|
|
18862
18964
|
apiKey: result.apiKey,
|
|
18863
18965
|
endpoint,
|
|
18864
18966
|
workspaceId: result.workspaceId,
|
|
18967
|
+
workspaceName: result.workspaceName,
|
|
18865
18968
|
organizationId: result.organizationId,
|
|
18969
|
+
organizationName: result.organizationName,
|
|
18970
|
+
organizationSlug: self?.organization?.slug,
|
|
18971
|
+
keyScope: self?.scope,
|
|
18866
18972
|
expiresAt: result.expiresAt
|
|
18867
18973
|
});
|
|
18868
18974
|
if (result.workspaceName)
|
|
@@ -18900,55 +19006,12 @@ async function loginWithPastedKey(initialEndpoint, output, wasFirstLogin) {
|
|
|
18900
19006
|
if (!isValid) {
|
|
18901
19007
|
throw new MutagentError("INVALID_API_KEY", "Invalid API key or endpoint", "Check your API key and try again");
|
|
18902
19008
|
}
|
|
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
|
-
};
|
|
19009
|
+
const described = await describeKey(apiKey, endpoint, async (rows) => rows.find((w) => w.isDefault)?.id ?? select({
|
|
19010
|
+
message: "Select workspace:",
|
|
19011
|
+
choices: rows.map((w) => ({ name: w.name, value: w.id }))
|
|
19012
|
+
}));
|
|
19013
|
+
saveFullCredentials({ apiKey, endpoint, ...described });
|
|
19014
|
+
return loginResult(apiKey, endpoint, described, wasFirstLogin);
|
|
18952
19015
|
}
|
|
18953
19016
|
|
|
18954
19017
|
// src/commands/auth.ts
|
|
@@ -18978,6 +19041,7 @@ See mutagent login --help for the full description.
|
|
|
18978
19041
|
endpoint: options.endpoint,
|
|
18979
19042
|
browser: options.browser,
|
|
18980
19043
|
nonInteractive: options.nonInteractive,
|
|
19044
|
+
org: auth.optsWithGlobals().org,
|
|
18981
19045
|
isJson,
|
|
18982
19046
|
output
|
|
18983
19047
|
});
|
|
@@ -18990,7 +19054,7 @@ See mutagent login --help for the full description.
|
|
|
18990
19054
|
if (result.workspace?.name)
|
|
18991
19055
|
output.info(`Workspace: ${result.workspace.name}`);
|
|
18992
19056
|
output.info(`Endpoint: ${result.endpoint}`);
|
|
18993
|
-
output.info("Next: mutagent workspaces
|
|
19057
|
+
output.info(result.workspace ? "Next: mutagent workspaces current" : "Next: mutagent workspaces use <name>");
|
|
18994
19058
|
}
|
|
18995
19059
|
if (result.wasFirstLogin && process.stdin.isTTY && !isJson) {
|
|
18996
19060
|
await runPostOnboarding();
|
|
@@ -19094,10 +19158,13 @@ Examples:
|
|
|
19094
19158
|
${chalk5.dim("$")} mutagent login ${chalk5.dim("# In a terminal, choose browser or API key")}
|
|
19095
19159
|
${chalk5.dim("$")} mutagent login --browser ${chalk5.dim("# Sign in with the browser")}
|
|
19096
19160
|
${chalk5.dim("$")} mutagent login --non-interactive ${chalk5.dim("# Browser sign-in without the method prompt (a person approves in the browser)")}
|
|
19161
|
+
${chalk5.dim("$")} mutagent login --org <slug> ${chalk5.dim("# Sign in for another organization (preselected on the approval page)")}
|
|
19097
19162
|
${chalk5.dim("$")} MUTAGENT_API_KEY=mt_... mutagent login --json ${chalk5.dim("# Sign in with a key (CI)")}
|
|
19098
19163
|
|
|
19099
19164
|
Browser sign-in opens app.mutagent.io, where you sign in or sign up, finish onboarding
|
|
19100
19165
|
and approve this CLI. The CLI waits up to 5 minutes and saves the credentials.
|
|
19166
|
+
The key works in every workspace you are a member of in the approved organization, for
|
|
19167
|
+
30 days. The approved workspace is selected; switch with mutagent workspaces use <name>.
|
|
19101
19168
|
|
|
19102
19169
|
When MUTAGENT_API_KEY is set, login skips the browser and validates the key.
|
|
19103
19170
|
|
|
@@ -19108,8 +19175,8 @@ Environment:
|
|
|
19108
19175
|
CI=true Same as --non-interactive
|
|
19109
19176
|
|
|
19110
19177
|
Then:
|
|
19111
|
-
${chalk5.dim("$")} mutagent
|
|
19112
|
-
${chalk5.dim("$")} mutagent workspaces
|
|
19178
|
+
${chalk5.dim("$")} mutagent workspaces current
|
|
19179
|
+
${chalk5.dim("$")} mutagent workspaces use <name>
|
|
19113
19180
|
|
|
19114
19181
|
mutagent auth login runs the same sign-in.
|
|
19115
19182
|
`).action(async (options) => {
|
|
@@ -19120,6 +19187,7 @@ mutagent auth login runs the same sign-in.
|
|
|
19120
19187
|
endpoint: options.endpoint,
|
|
19121
19188
|
browser: options.browser,
|
|
19122
19189
|
nonInteractive: options.nonInteractive,
|
|
19190
|
+
org: login.optsWithGlobals().org,
|
|
19123
19191
|
isJson,
|
|
19124
19192
|
output
|
|
19125
19193
|
});
|
|
@@ -19132,7 +19200,7 @@ mutagent auth login runs the same sign-in.
|
|
|
19132
19200
|
if (result.workspace?.name)
|
|
19133
19201
|
output.info(`Workspace: ${result.workspace.name}`);
|
|
19134
19202
|
output.info(`Endpoint: ${result.endpoint}`);
|
|
19135
|
-
output.info("Next: mutagent workspaces
|
|
19203
|
+
output.info(result.workspace ? "Next: mutagent workspaces current" : "Next: mutagent workspaces use <name>");
|
|
19136
19204
|
}
|
|
19137
19205
|
if (result.wasFirstLogin && process.stdin.isTTY && !isJson) {
|
|
19138
19206
|
await runPostOnboarding();
|
|
@@ -19150,163 +19218,163 @@ mutagent auth login runs the same sign-in.
|
|
|
19150
19218
|
|
|
19151
19219
|
// src/commands/config.ts
|
|
19152
19220
|
init_config();
|
|
19221
|
+
import { Command as Command5 } from "commander";
|
|
19222
|
+
import chalk7 from "chalk";
|
|
19223
|
+
init_errors();
|
|
19224
|
+
init_organization_scope();
|
|
19225
|
+
|
|
19226
|
+
// src/commands/workspaces.ts
|
|
19227
|
+
init_sdk_client();
|
|
19153
19228
|
import { Command as Command4 } from "commander";
|
|
19154
19229
|
import chalk6 from "chalk";
|
|
19155
19230
|
init_errors();
|
|
19156
|
-
|
|
19231
|
+
init_config();
|
|
19157
19232
|
init_organization_scope();
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19233
|
+
async function useWorkspace(value2) {
|
|
19234
|
+
const apiKey = getApiKey();
|
|
19235
|
+
if (!apiKey)
|
|
19236
|
+
throw new AuthenticationError;
|
|
19237
|
+
if (envKeyReplacesFile())
|
|
19238
|
+
throw envKeyHasNoSelection();
|
|
19239
|
+
const tenancy = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19240
|
+
const config = loadConfig();
|
|
19241
|
+
const workspace = await findWorkspace(value2, {
|
|
19242
|
+
apiKey,
|
|
19243
|
+
endpoint: tenancy.endpoint,
|
|
19244
|
+
keyScope: tenancy.keyScope,
|
|
19245
|
+
organization: tenancy.organization,
|
|
19246
|
+
workspaceId: config.workspaceOrigin === "selection" ? tenancy.workspaceId : undefined,
|
|
19247
|
+
workspaceName: config.workspaceOrigin === "selection" ? tenancy.workspaceName : undefined
|
|
19248
|
+
});
|
|
19249
|
+
setWorkspaceSelection(workspace.id, workspace.name);
|
|
19250
|
+
resetSDKClient();
|
|
19251
|
+
const org = tenancy.organization;
|
|
19252
|
+
return { workspace, organization: org ? { id: org.id, name: org.name } : null };
|
|
19253
|
+
}
|
|
19254
|
+
function createWorkspacesCommand() {
|
|
19255
|
+
const workspaces = new Command4("workspaces").alias("workspace").description("List, select and inspect workspaces").addHelpText("after", `
|
|
19161
19256
|
Examples:
|
|
19162
|
-
${chalk6.dim("$")} mutagent
|
|
19163
|
-
${chalk6.dim("$")} mutagent
|
|
19164
|
-
${chalk6.dim("$")} mutagent
|
|
19165
|
-
${chalk6.dim("$")} mutagent
|
|
19257
|
+
${chalk6.dim("$")} mutagent workspaces list
|
|
19258
|
+
${chalk6.dim("$")} mutagent workspaces use <name|id> ${chalk6.dim("# Every later command works in this workspace")}
|
|
19259
|
+
${chalk6.dim("$")} mutagent workspaces current --json
|
|
19260
|
+
${chalk6.dim("$")} mutagent providers list --workspace <name|id> ${chalk6.dim("# One command in another workspace")}
|
|
19261
|
+
|
|
19262
|
+
The CLI key from \`mutagent login\` works in every workspace you are a member of in its
|
|
19263
|
+
organization. Workspace precedence: --workspace, then MUTAGENT_WORKSPACE_ID, then the
|
|
19264
|
+
workspace chosen with \`workspaces use\`.
|
|
19265
|
+
To work in another organization: mutagent login --org <slug>
|
|
19266
|
+
Create or change workspaces in the dashboard: https://app.mutagent.io
|
|
19166
19267
|
`);
|
|
19167
|
-
|
|
19168
|
-
Examples:
|
|
19169
|
-
${chalk6.dim("$")} mutagent config list
|
|
19170
|
-
${chalk6.dim("$")} mutagent config list --json
|
|
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", `
|
|
19268
|
+
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", `
|
|
19182
19269
|
Examples:
|
|
19183
|
-
${chalk6.dim("$")} mutagent
|
|
19184
|
-
${chalk6.dim("$")} mutagent
|
|
19185
|
-
${chalk6.dim("$")} mutagent config get apiKey --json
|
|
19270
|
+
${chalk6.dim("$")} mutagent workspaces list
|
|
19271
|
+
${chalk6.dim("$")} mutagent workspaces list --limit 10 --json
|
|
19186
19272
|
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
`).action((
|
|
19190
|
-
const isJson = getJsonFlag(
|
|
19273
|
+
Lists only workspaces you are a member of. Works even when the selected workspace is no
|
|
19274
|
+
longer available, so you can pick another with: mutagent workspaces use <name>
|
|
19275
|
+
`).action(async (options) => {
|
|
19276
|
+
const isJson = getJsonFlag(workspaces);
|
|
19191
19277
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19192
19278
|
try {
|
|
19193
|
-
const
|
|
19194
|
-
if (!
|
|
19195
|
-
throw new
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
const
|
|
19199
|
-
const
|
|
19200
|
-
const
|
|
19279
|
+
const apiKey = getApiKey();
|
|
19280
|
+
if (!apiKey)
|
|
19281
|
+
throw new AuthenticationError;
|
|
19282
|
+
const tenancy = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19283
|
+
const rows = await listKeyWorkspaces(apiKey, tenancy.endpoint, tenancy);
|
|
19284
|
+
const offset = options.offset ? parseInt(options.offset, 10) : 0;
|
|
19285
|
+
const limit = parseInt(options.limit, 10) || 50;
|
|
19286
|
+
const page = rows.slice(offset, offset + limit);
|
|
19287
|
+
const selected = tenancy.workspaceId;
|
|
19201
19288
|
if (isJson) {
|
|
19202
|
-
output.output({
|
|
19289
|
+
output.output({
|
|
19290
|
+
data: page.map((w) => ({
|
|
19291
|
+
id: w.id,
|
|
19292
|
+
name: w.name,
|
|
19293
|
+
slug: w.slug,
|
|
19294
|
+
isDefault: w.isDefault,
|
|
19295
|
+
selected: w.id === selected,
|
|
19296
|
+
_links: workspaceLinks(w.id)
|
|
19297
|
+
})),
|
|
19298
|
+
total: rows.length
|
|
19299
|
+
});
|
|
19203
19300
|
} else {
|
|
19204
|
-
output.
|
|
19301
|
+
output.output(page.map((w) => ({
|
|
19302
|
+
selected: w.id === selected ? "*" : "",
|
|
19303
|
+
id: w.id,
|
|
19304
|
+
name: w.name,
|
|
19305
|
+
slug: w.slug,
|
|
19306
|
+
url: workspaceLink(w.id)
|
|
19307
|
+
})));
|
|
19205
19308
|
}
|
|
19206
19309
|
} catch (error3) {
|
|
19207
19310
|
handleError(error3, isJson);
|
|
19208
19311
|
}
|
|
19209
19312
|
});
|
|
19210
|
-
|
|
19211
|
-
set2.help();
|
|
19212
|
-
});
|
|
19213
|
-
set2.command("workspace").description("Set default workspace ID").argument("<id>", "Workspace ID to set as default").addHelpText("after", `
|
|
19214
|
-
Examples:
|
|
19215
|
-
${chalk6.dim("$")} mutagent config set workspace <workspace-id>
|
|
19216
|
-
|
|
19217
|
-
${chalk6.dim("Commands use this workspace until you change it.")}
|
|
19218
|
-
`).action((id) => {
|
|
19219
|
-
const isJson = getJsonFlag(config);
|
|
19220
|
-
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19221
|
-
setDefaultWorkspace(id);
|
|
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", `
|
|
19313
|
+
workspaces.command("use").description("Select the workspace every later command works in").argument("<name|id>", "Workspace name or id").addHelpText("after", `
|
|
19230
19314
|
Examples:
|
|
19231
|
-
${chalk6.dim("$")} mutagent
|
|
19315
|
+
${chalk6.dim("$")} mutagent workspaces use staging
|
|
19316
|
+
${chalk6.dim("$")} mutagent workspaces use <workspace-id> --json
|
|
19232
19317
|
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
`).action(async (
|
|
19237
|
-
const isJson = getJsonFlag(
|
|
19318
|
+
Resolves against your workspaces in the key's organization and saves the choice. No
|
|
19319
|
+
browser and no prompt. A workspace in another organization needs a key for that
|
|
19320
|
+
organization: mutagent login --org <slug>. Two workspaces with the same name: pass the id.
|
|
19321
|
+
`).action(async (value2) => {
|
|
19322
|
+
const isJson = getJsonFlag(workspaces);
|
|
19238
19323
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19239
19324
|
try {
|
|
19240
|
-
await
|
|
19241
|
-
apiKey: getApiKey(),
|
|
19242
|
-
endpoint: loadConfig().endpoint ?? "https://api.mutagent.io",
|
|
19243
|
-
fetchOrganizations
|
|
19244
|
-
});
|
|
19245
|
-
setDefaultOrganization(id);
|
|
19246
|
-
resetSDKClient();
|
|
19325
|
+
const result = await useWorkspace(value2);
|
|
19247
19326
|
if (isJson) {
|
|
19248
|
-
output.output({ success: true,
|
|
19327
|
+
output.output({ success: true, ...result, _links: workspaceLinks(result.workspace.id) });
|
|
19249
19328
|
} else {
|
|
19250
|
-
output.success(`
|
|
19329
|
+
output.success(`Workspace: ${result.workspace.name} (${result.workspace.id})`);
|
|
19251
19330
|
}
|
|
19252
19331
|
} catch (error3) {
|
|
19253
19332
|
handleError(error3, isJson);
|
|
19254
19333
|
}
|
|
19255
19334
|
});
|
|
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", `
|
|
19335
|
+
workspaces.command("current").description("Show the key's scope, organization and the verified workspace").addHelpText("after", `
|
|
19267
19336
|
Examples:
|
|
19268
|
-
${
|
|
19269
|
-
${
|
|
19337
|
+
${chalk6.dim("$")} mutagent workspaces current
|
|
19338
|
+
${chalk6.dim("$")} mutagent workspaces current --json
|
|
19270
19339
|
|
|
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) => {
|
|
19340
|
+
Asks the server (GET /api/api-keys/self) with the workspace this command would use, so
|
|
19341
|
+
the answer is verified, not read from local config.
|
|
19342
|
+
`).action(async () => {
|
|
19279
19343
|
const isJson = getJsonFlag(workspaces);
|
|
19280
19344
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19281
19345
|
try {
|
|
19282
|
-
const
|
|
19283
|
-
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
if (
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
|
|
19346
|
+
const apiKey = getApiKey();
|
|
19347
|
+
if (!apiKey)
|
|
19348
|
+
throw new AuthenticationError;
|
|
19349
|
+
const tenancy = await resolveKeyTenancy(apiKey);
|
|
19350
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
19351
|
+
throw noWorkspaceSelected();
|
|
19352
|
+
const headers = tenancyHeaders({
|
|
19353
|
+
keyScope: tenancy.keyScope,
|
|
19354
|
+
workspaceId: tenancy.workspaceId,
|
|
19355
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
19356
|
+
organizationId: tenancy.organization?.id
|
|
19357
|
+
});
|
|
19358
|
+
const self = await fetchKeySelf(apiKey, tenancy.endpoint, { headers, ctx: tenancy });
|
|
19359
|
+
if (!envKeyReplacesFile())
|
|
19360
|
+
recordKeyScope(self);
|
|
19361
|
+
if (!self.workspace && self.scope === "organization")
|
|
19362
|
+
throw noWorkspaceSelected();
|
|
19290
19363
|
if (isJson) {
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
_links: workspaceLinks(w.id)
|
|
19298
|
-
}));
|
|
19299
|
-
output.output({ ...result, data: withLinks });
|
|
19364
|
+
output.output({
|
|
19365
|
+
scope: self.scope,
|
|
19366
|
+
organization: self.organization,
|
|
19367
|
+
workspace: self.workspace,
|
|
19368
|
+
expiresAt: self.expiresAt ?? null
|
|
19369
|
+
});
|
|
19300
19370
|
} else {
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
}));
|
|
19309
|
-
output.output(formatted);
|
|
19371
|
+
output.info(`Key scope: ${self.scope}`);
|
|
19372
|
+
if (self.organization)
|
|
19373
|
+
output.info(`Organization: ${self.organization.name} (${self.organization.id})`);
|
|
19374
|
+
if (self.workspace)
|
|
19375
|
+
output.info(`Workspace: ${self.workspace.name} (${self.workspace.id})`);
|
|
19376
|
+
if (self.expiresAt)
|
|
19377
|
+
output.info(`Key expires: ${self.expiresAt}`);
|
|
19310
19378
|
}
|
|
19311
19379
|
} catch (error3) {
|
|
19312
19380
|
handleError(error3, isJson);
|
|
@@ -19314,8 +19382,8 @@ Examples:
|
|
|
19314
19382
|
});
|
|
19315
19383
|
workspaces.command("get").description("Get workspace details").argument("<id>", "Workspace ID").addHelpText("after", `
|
|
19316
19384
|
Examples:
|
|
19317
|
-
${
|
|
19318
|
-
${
|
|
19385
|
+
${chalk6.dim("$")} mutagent workspaces get <workspace-id>
|
|
19386
|
+
${chalk6.dim("$")} mutagent workspaces get <workspace-id> --json
|
|
19319
19387
|
`).action(async (id) => {
|
|
19320
19388
|
const isJson = getJsonFlag(workspaces);
|
|
19321
19389
|
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
@@ -19345,6 +19413,115 @@ Examples:
|
|
|
19345
19413
|
return workspaces;
|
|
19346
19414
|
}
|
|
19347
19415
|
|
|
19416
|
+
// src/commands/config.ts
|
|
19417
|
+
var VALID_CONFIG_KEYS = ["apiKey", "endpoint", "format", "timeout", "defaultWorkspace", "workspaceName", "defaultOrganization", "organizationName", "keyScope"];
|
|
19418
|
+
function createConfigCommand() {
|
|
19419
|
+
const config = new Command5("config").description("Manage CLI configuration").addHelpText("after", `
|
|
19420
|
+
Examples:
|
|
19421
|
+
${chalk7.dim("$")} mutagent config list
|
|
19422
|
+
${chalk7.dim("$")} mutagent config get endpoint
|
|
19423
|
+
${chalk7.dim("$")} mutagent config set workspace <name|id> ${chalk7.dim("# Same as: mutagent workspaces use")}
|
|
19424
|
+
`);
|
|
19425
|
+
config.command("list").description("List all configuration").addHelpText("after", `
|
|
19426
|
+
Examples:
|
|
19427
|
+
${chalk7.dim("$")} mutagent config list
|
|
19428
|
+
${chalk7.dim("$")} mutagent config list --json
|
|
19429
|
+
`).action(() => {
|
|
19430
|
+
const isJson = getJsonFlag(config);
|
|
19431
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19432
|
+
const cfg = loadConfig();
|
|
19433
|
+
const display = {
|
|
19434
|
+
...cfg,
|
|
19435
|
+
apiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 8)}...` : undefined
|
|
19436
|
+
};
|
|
19437
|
+
output.output(display);
|
|
19438
|
+
});
|
|
19439
|
+
config.command("get").description("Get configuration value").argument("<key>", `Configuration key (${VALID_CONFIG_KEYS.join(", ")})`).addHelpText("after", `
|
|
19440
|
+
Examples:
|
|
19441
|
+
${chalk7.dim("$")} mutagent config get endpoint
|
|
19442
|
+
${chalk7.dim("$")} mutagent config get defaultWorkspace
|
|
19443
|
+
${chalk7.dim("$")} mutagent config get apiKey --json
|
|
19444
|
+
|
|
19445
|
+
${chalk7.dim(`Keys: ${VALID_CONFIG_KEYS.join(", ")}`)}
|
|
19446
|
+
${chalk7.dim("A key with no value set is reported as unknown.")}
|
|
19447
|
+
`).action((key) => {
|
|
19448
|
+
const isJson = getJsonFlag(config);
|
|
19449
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19450
|
+
try {
|
|
19451
|
+
const cfg = loadConfig();
|
|
19452
|
+
if (!(key in cfg)) {
|
|
19453
|
+
throw new MutagentError("INVALID_ARGUMENTS", `Unknown config key: "${key}"`, `Valid keys: ${VALID_CONFIG_KEYS.join(", ")}
|
|
19454
|
+
` + "Example: mutagent config get endpoint");
|
|
19455
|
+
}
|
|
19456
|
+
const value2 = cfg[key];
|
|
19457
|
+
const stringValue = typeof value2 === "object" ? JSON.stringify(value2) : String(value2 ?? "");
|
|
19458
|
+
const displayValue = key === "apiKey" && value2 ? `${stringValue.slice(0, 8)}...` : stringValue;
|
|
19459
|
+
if (isJson) {
|
|
19460
|
+
output.output({ [key]: displayValue });
|
|
19461
|
+
} else {
|
|
19462
|
+
output.info(displayValue);
|
|
19463
|
+
}
|
|
19464
|
+
} catch (error3) {
|
|
19465
|
+
handleError(error3, isJson);
|
|
19466
|
+
}
|
|
19467
|
+
});
|
|
19468
|
+
const set2 = new Command5("set").description("Set configuration value").action(() => {
|
|
19469
|
+
set2.help();
|
|
19470
|
+
});
|
|
19471
|
+
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", `
|
|
19472
|
+
Examples:
|
|
19473
|
+
${chalk7.dim("$")} mutagent config set workspace staging
|
|
19474
|
+
${chalk7.dim("$")} mutagent config set workspace <workspace-id>
|
|
19475
|
+
|
|
19476
|
+
${chalk7.dim("Runs mutagent workspaces use: resolves the name among your workspaces and saves it.")}
|
|
19477
|
+
`).action(async (value2) => {
|
|
19478
|
+
const isJson = getJsonFlag(config);
|
|
19479
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19480
|
+
try {
|
|
19481
|
+
const result = await useWorkspace(value2);
|
|
19482
|
+
if (isJson) {
|
|
19483
|
+
output.output({ success: true, defaultWorkspace: result.workspace.id, ...result });
|
|
19484
|
+
} else {
|
|
19485
|
+
output.success(`Workspace: ${result.workspace.name} (${result.workspace.id})`);
|
|
19486
|
+
}
|
|
19487
|
+
} catch (error3) {
|
|
19488
|
+
handleError(error3, isJson);
|
|
19489
|
+
}
|
|
19490
|
+
});
|
|
19491
|
+
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", `
|
|
19492
|
+
Examples:
|
|
19493
|
+
${chalk7.dim("$")} mutagent config set org <org-id|slug>
|
|
19494
|
+
|
|
19495
|
+
${chalk7.dim("The CLI key belongs to one organization. The same organization: nothing to do.")}
|
|
19496
|
+
${chalk7.dim("Another organization: sign in for it with mutagent login --org <id|slug>.")}
|
|
19497
|
+
`).action(async (value2) => {
|
|
19498
|
+
const isJson = getJsonFlag(config);
|
|
19499
|
+
const output = new OutputFormatter(isJson ? "json" : "table");
|
|
19500
|
+
try {
|
|
19501
|
+
const apiKey = getApiKey();
|
|
19502
|
+
if (!apiKey)
|
|
19503
|
+
throw new AuthenticationError;
|
|
19504
|
+
setOrgAssertion(value2);
|
|
19505
|
+
const { organization } = await resolveKeyTenancy(apiKey, { resolveOverride: false });
|
|
19506
|
+
if (!organization) {
|
|
19507
|
+
throw new MutagentError("ORG_UNKNOWN", "This key's organization is unknown.", "Run: mutagent workspaces current");
|
|
19508
|
+
}
|
|
19509
|
+
if (!matchesOrganization(value2, organization)) {
|
|
19510
|
+
throw new TenancyError("ORG_MISMATCH", `Your key is for ${organization.name}.`, `Run: mutagent login --org ${value2}`);
|
|
19511
|
+
}
|
|
19512
|
+
if (isJson) {
|
|
19513
|
+
output.output({ success: true, organization });
|
|
19514
|
+
} else {
|
|
19515
|
+
output.success(`Organization: ${organization.name} (${organization.id}), from the key`);
|
|
19516
|
+
}
|
|
19517
|
+
} catch (error3) {
|
|
19518
|
+
handleError(error3, isJson);
|
|
19519
|
+
}
|
|
19520
|
+
});
|
|
19521
|
+
config.addCommand(set2);
|
|
19522
|
+
return config;
|
|
19523
|
+
}
|
|
19524
|
+
|
|
19348
19525
|
// src/commands/providers/index.ts
|
|
19349
19526
|
init_sdk_client();
|
|
19350
19527
|
import { Command as Command6 } from "commander";
|
|
@@ -19837,7 +20014,7 @@ anthropic for an endpoint that speaks the Anthropic API; unset, the server defau
|
|
|
19837
20014
|
The base URL must be a public address: hosts ending in .internal, .local, .localhost or
|
|
19838
20015
|
.home.arpa are refused.
|
|
19839
20016
|
|
|
19840
|
-
Adds the LLM provider to the configured workspace (mutagent
|
|
20017
|
+
Adds the LLM provider to the configured workspace (mutagent workspaces use <name>, or --workspace for one command).
|
|
19841
20018
|
The key is encrypted and never returned.
|
|
19842
20019
|
Workspace default model: when the workspace has none, adding a keyed LLM provider sets
|
|
19843
20020
|
one on the server. An existing workspace default model is never changed.
|
|
@@ -19875,7 +20052,7 @@ AI Agent Directive:
|
|
|
19875
20052
|
validateApiFormat(providerType, options.apiFormat);
|
|
19876
20053
|
}
|
|
19877
20054
|
const client = await getSDKClient();
|
|
19878
|
-
const workspaceId = client.
|
|
20055
|
+
const workspaceId = client.requireSelectedWorkspace();
|
|
19879
20056
|
const credentialFields = collectCredentialFields(options);
|
|
19880
20057
|
if (secretWireName !== "apiKey")
|
|
19881
20058
|
credentialFields[secretWireName] = secret;
|
|
@@ -20787,7 +20964,7 @@ function statusGlyph(result) {
|
|
|
20787
20964
|
}
|
|
20788
20965
|
}
|
|
20789
20966
|
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("--
|
|
20967
|
+
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
20968
|
Examples:
|
|
20792
20969
|
${chalk11.dim("$")} mutagent providers mirror
|
|
20793
20970
|
${chalk11.dim("$")} mutagent providers mirror --json ${chalk11.dim("Show the plan without sending anything")}
|
|
@@ -20816,6 +20993,9 @@ default model is never changed. Change it with
|
|
|
20816
20993
|
|
|
20817
20994
|
Mirroring does not test the keys: check \`mutagent helix models\`, then run a model.
|
|
20818
20995
|
|
|
20996
|
+
--workspace <name|id> still works here: it is the global flag and targets this run only
|
|
20997
|
+
(mutagent workspaces use <name> changes the default).
|
|
20998
|
+
|
|
20819
20999
|
Requires \`mutagent login\` and a workspace. Keys are sent only to the configured
|
|
20820
21000
|
endpoint and are never printed. Consent is asked once per run; without a terminal,
|
|
20821
21001
|
--yes is required. Without it the run exits 2 (with --json it returns the plan).
|
|
@@ -20835,12 +21015,9 @@ AI Agent Directive:
|
|
|
20835
21015
|
try {
|
|
20836
21016
|
const apiKey = getApiKey();
|
|
20837
21017
|
const config = loadConfig();
|
|
20838
|
-
const workspaceId =
|
|
21018
|
+
const workspaceId = config.defaultWorkspace;
|
|
20839
21019
|
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;
|
|
21020
|
+
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
21021
|
}
|
|
20845
21022
|
if (!HELIX_CHANNELS2.includes(options.channel)) {
|
|
20846
21023
|
throw new MutagentError("INVALID_OPTION", `Unknown channel '${options.channel}'.`, `Use --channel ${HELIX_CHANNELS2.join(" or ")}`);
|
|
@@ -20851,7 +21028,7 @@ AI Agent Directive:
|
|
|
20851
21028
|
for (const secret of localCredentialSecrets(store.entries))
|
|
20852
21029
|
registerSecret(secret);
|
|
20853
21030
|
const client = await getSDKClient();
|
|
20854
|
-
const target = client.
|
|
21031
|
+
const target = client.requireSelectedWorkspace();
|
|
20855
21032
|
const rows = (await client.listProviders({ limit: 1000 })).data.map((row) => ({
|
|
20856
21033
|
id: row.id,
|
|
20857
21034
|
name: row.name,
|
|
@@ -21782,22 +21959,28 @@ exits — run \`mutagent login\` first, then re-run \`mutagent init\`.
|
|
|
21782
21959
|
|
|
21783
21960
|
\`\`\`bash
|
|
21784
21961
|
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
|
|
21962
|
+
mutagent config get <key> --json # keys: apiKey, endpoint, format, timeout, defaultWorkspace,
|
|
21963
|
+
# workspaceName, defaultOrganization, organizationName, keyScope
|
|
21964
|
+
mutagent config set workspace <name|id> --json # same as: mutagent workspaces use
|
|
21965
|
+
mutagent config set org <id|slug> --json # confirms the key's organization; another org exits 1
|
|
21789
21966
|
\`\`\`
|
|
21790
21967
|
|
|
21791
21968
|
---
|
|
21792
21969
|
|
|
21793
|
-
## 4. workspaces
|
|
21970
|
+
## 4. workspaces
|
|
21794
21971
|
|
|
21795
21972
|
\`\`\`bash
|
|
21796
|
-
mutagent workspaces list --json #
|
|
21973
|
+
mutagent workspaces list --json # your workspaces in the key's organization (selected: true)
|
|
21974
|
+
mutagent workspaces use <name|id> --json # select the workspace later commands work in (no browser, no prompt)
|
|
21975
|
+
mutagent workspaces current --json # key scope, organization, verified workspace, key expiry
|
|
21797
21976
|
mutagent workspaces get <id> --json # workspace details
|
|
21798
21977
|
\`\`\`
|
|
21799
21978
|
|
|
21800
|
-
|
|
21979
|
+
The login key works in every workspace the user is a member of in its organization.
|
|
21980
|
+
For one command in another workspace, pass \`--workspace <name|id>\` (it beats
|
|
21981
|
+
\`MUTAGENT_WORKSPACE_ID\`, which beats the selection). For another organization:
|
|
21982
|
+
\`mutagent login --org <slug>\`. A tenancy error is one line ending in the fix command:
|
|
21983
|
+
run that command, never retry the same request.
|
|
21801
21984
|
|
|
21802
21985
|
---
|
|
21803
21986
|
|
|
@@ -22008,51 +22191,33 @@ It never prompts. Without a sign-in it prints what to run and exits.
|
|
|
22008
22191
|
} else {
|
|
22009
22192
|
const config = (deps.loadConfig ?? loadConfig)();
|
|
22010
22193
|
const endpoint = config.endpoint ?? "https://api.mutagent.io";
|
|
22011
|
-
|
|
22194
|
+
const workspace = config.defaultWorkspace;
|
|
22012
22195
|
let workspaceValidation;
|
|
22013
|
-
let resolvedOrgId;
|
|
22014
22196
|
try {
|
|
22015
22197
|
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
|
-
}
|
|
22198
|
+
if (apiKey && workspace) {
|
|
22199
|
+
const list = deps.listWorkspaces ?? (async (key) => {
|
|
22200
|
+
const { listKeyWorkspaces: listKeyWorkspaces2, resolveKeyTenancy: resolveKeyTenancy2 } = await Promise.resolve().then(() => (init_organization_scope(), exports_organization_scope));
|
|
22201
|
+
const tenancy = await resolveKeyTenancy2(key, { resolveOverride: false });
|
|
22202
|
+
return listKeyWorkspaces2(key, tenancy.endpoint, tenancy);
|
|
22203
|
+
});
|
|
22204
|
+
const found = (await list(apiKey)).find((w) => w.id === workspace);
|
|
22205
|
+
workspaceValidation = { id: workspace, name: found?.name, found: found !== undefined };
|
|
22040
22206
|
}
|
|
22041
22207
|
} catch {}
|
|
22042
22208
|
rcConfig = {
|
|
22043
22209
|
endpoint,
|
|
22044
|
-
...workspace ? { defaultWorkspace: workspace } : {}
|
|
22045
|
-
...resolvedOrgId ? { defaultOrganization: resolvedOrgId } : {}
|
|
22210
|
+
...workspace ? { defaultWorkspace: workspace } : {}
|
|
22046
22211
|
};
|
|
22047
22212
|
writeRcConfig(rcConfig, cwd);
|
|
22048
22213
|
if (!isJson) {
|
|
22049
22214
|
output.success("Created .mutagentrc.json");
|
|
22050
|
-
if (workspaceValidation) {
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
}
|
|
22054
|
-
|
|
22055
|
-
|
|
22215
|
+
if (workspaceValidation?.found) {
|
|
22216
|
+
output.info(`Workspace: ${workspaceValidation.name ?? workspaceValidation.id} (${workspaceValidation.id})`);
|
|
22217
|
+
} else if (workspaceValidation) {
|
|
22218
|
+
output.warn(`Your saved workspace ${workspaceValidation.id} is not among your workspaces. Run: mutagent workspaces use <name>`);
|
|
22219
|
+
} else if (!workspace) {
|
|
22220
|
+
output.warn("No workspace selected. Run: mutagent workspaces use <name>");
|
|
22056
22221
|
}
|
|
22057
22222
|
}
|
|
22058
22223
|
}
|
|
@@ -22236,22 +22401,8 @@ function parseTranscript(raw) {
|
|
|
22236
22401
|
// src/commands/hooks/api.ts
|
|
22237
22402
|
init_config();
|
|
22238
22403
|
init_known_secrets();
|
|
22404
|
+
init_organization_scope();
|
|
22239
22405
|
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
22406
|
async function sendBatchTrace(traces) {
|
|
22256
22407
|
const apiKey = getApiKey();
|
|
22257
22408
|
if (!apiKey) {
|
|
@@ -22265,12 +22416,7 @@ async function sendBatchTrace(traces) {
|
|
|
22265
22416
|
"x-api-key": apiKey,
|
|
22266
22417
|
"Content-Type": "application/json"
|
|
22267
22418
|
};
|
|
22268
|
-
|
|
22269
|
-
if (!tenancy.ok) {
|
|
22270
|
-
process.stderr.write(tenancy.warning);
|
|
22271
|
-
return;
|
|
22272
|
-
}
|
|
22273
|
-
Object.assign(headers, tenancy.headers);
|
|
22419
|
+
Object.assign(headers, configTenancyHeaders(config));
|
|
22274
22420
|
const controller = new AbortController;
|
|
22275
22421
|
const timeout = setTimeout(() => {
|
|
22276
22422
|
controller.abort();
|
|
@@ -23282,6 +23428,7 @@ import { join as join18, dirname as dirname5 } from "path";
|
|
|
23282
23428
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
23283
23429
|
init_errors();
|
|
23284
23430
|
init_config();
|
|
23431
|
+
init_organization_scope();
|
|
23285
23432
|
|
|
23286
23433
|
// src/lib/transcript.ts
|
|
23287
23434
|
init_errors();
|
|
@@ -23691,15 +23838,12 @@ function buildAutoContext(harness) {
|
|
|
23691
23838
|
nodeVersion: process.version
|
|
23692
23839
|
};
|
|
23693
23840
|
}
|
|
23694
|
-
async function postToServer(payload, endpoint, apiKey,
|
|
23841
|
+
async function postToServer(payload, endpoint, apiKey, tenancy) {
|
|
23695
23842
|
const headers = {
|
|
23696
23843
|
"Content-Type": "application/json",
|
|
23697
|
-
"x-api-key": apiKey
|
|
23844
|
+
"x-api-key": apiKey,
|
|
23845
|
+
...tenancy
|
|
23698
23846
|
};
|
|
23699
|
-
if (workspaceId)
|
|
23700
|
-
headers["x-workspace-id"] = workspaceId;
|
|
23701
|
-
if (organizationId)
|
|
23702
|
-
headers["x-organization-id"] = organizationId;
|
|
23703
23847
|
let response;
|
|
23704
23848
|
try {
|
|
23705
23849
|
response = await fetch(`${endpoint}/api/feedback`, {
|
|
@@ -23823,7 +23967,7 @@ Or set an API key: mutagent config set apiKey <key>`);
|
|
|
23823
23967
|
const payload = buildPayload(feedbackBody, category, options.title, options.session, transcript);
|
|
23824
23968
|
const config = loadConfig();
|
|
23825
23969
|
const endpoint = config.endpoint ?? "https://api.mutagent.io";
|
|
23826
|
-
const result = await postToServer(payload, endpoint, apiKey, config
|
|
23970
|
+
const result = await postToServer(payload, endpoint, apiKey, configTenancyHeaders(config));
|
|
23827
23971
|
if (isJson) {
|
|
23828
23972
|
output.output({
|
|
23829
23973
|
success: true,
|
|
@@ -24657,6 +24801,7 @@ init_errors();
|
|
|
24657
24801
|
|
|
24658
24802
|
// src/lib/sandbox-api.ts
|
|
24659
24803
|
init_config();
|
|
24804
|
+
init_organization_scope();
|
|
24660
24805
|
init_errors();
|
|
24661
24806
|
|
|
24662
24807
|
// src/lib/sandbox-token.ts
|
|
@@ -24748,7 +24893,7 @@ async function exchangeToken(input2, deps = {}) {
|
|
|
24748
24893
|
}
|
|
24749
24894
|
if (response.status === 403) {
|
|
24750
24895
|
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
|
|
24896
|
+
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
24897
|
}
|
|
24753
24898
|
if (!response.ok) {
|
|
24754
24899
|
const detail = scrubKnown(messageFrom(await response.text().catch(() => "")));
|
|
@@ -24821,22 +24966,25 @@ async function withStoppingRetry(send, sleep2 = (ms) => new Promise((resolve9) =
|
|
|
24821
24966
|
|
|
24822
24967
|
// src/lib/sandbox-api.ts
|
|
24823
24968
|
init_known_secrets();
|
|
24824
|
-
function resolveConnection() {
|
|
24969
|
+
async function resolveConnection() {
|
|
24825
24970
|
const apiKey = getApiKey();
|
|
24826
24971
|
if (!apiKey)
|
|
24827
24972
|
throw new AuthenticationError;
|
|
24828
|
-
const
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
const workspaceId = requireWorkspace(
|
|
24832
|
-
const headers = {
|
|
24833
|
-
|
|
24834
|
-
|
|
24835
|
-
|
|
24973
|
+
const tenancy = await resolveKeyTenancy(apiKey, { probe: envKeyReplacesFile() });
|
|
24974
|
+
if (tenancy.keyScope === "organization" && !tenancy.workspaceId)
|
|
24975
|
+
throw noWorkspaceSelected();
|
|
24976
|
+
const workspaceId = requireWorkspace(tenancy.workspaceId);
|
|
24977
|
+
const headers = tenancyHeaders({
|
|
24978
|
+
keyScope: tenancy.keyScope,
|
|
24979
|
+
workspaceId,
|
|
24980
|
+
workspaceOrigin: tenancy.workspaceOrigin,
|
|
24981
|
+
organizationId: tenancy.organization?.id
|
|
24982
|
+
});
|
|
24983
|
+
return { endpoint: tenancy.endpoint, apiKey, workspaceId, headers };
|
|
24836
24984
|
}
|
|
24837
24985
|
function requireWorkspace(workspaceId) {
|
|
24838
24986
|
if (workspaceId === undefined || workspaceId.trim() === "") {
|
|
24839
|
-
throw new WorkspaceContextError("
|
|
24987
|
+
throw new WorkspaceContextError("No workspace selected: sandbox access is minted per workspace, not per account.");
|
|
24840
24988
|
}
|
|
24841
24989
|
return workspaceId.trim();
|
|
24842
24990
|
}
|
|
@@ -24852,7 +25000,7 @@ async function fetchWithTokenRetry(url, init, baseHeaders, transport) {
|
|
|
24852
25000
|
return attempt(true);
|
|
24853
25001
|
}
|
|
24854
25002
|
async function sandboxFetch(path, init, deps = {}) {
|
|
24855
|
-
const connection = resolveConnection();
|
|
25003
|
+
const connection = await resolveConnection();
|
|
24856
25004
|
const { endpoint } = connection;
|
|
24857
25005
|
try {
|
|
24858
25006
|
const response = await fetchWithTokenRetry(`${endpoint}${path}`, init, { ...connection.headers, ...toHeaderRecord(init.headers) }, {
|
|
@@ -28091,7 +28239,7 @@ Examples:
|
|
|
28091
28239
|
${chalk34.dim("$")} mutagent sandbox ls --json
|
|
28092
28240
|
|
|
28093
28241
|
Lists the sandboxes in the configured workspace
|
|
28094
|
-
(mutagent
|
|
28242
|
+
(mutagent workspaces use <name>).
|
|
28095
28243
|
An empty list means none are running.
|
|
28096
28244
|
|
|
28097
28245
|
AI Agent Directive:
|
|
@@ -28323,7 +28471,7 @@ AI Agent Directive:
|
|
|
28323
28471
|
spawned sandbox runs until it is removed or stopped by the idle policy: confirm
|
|
28324
28472
|
with the user before 'spawn' and before 'rm', and tell them the sandbox ID.
|
|
28325
28473
|
|
|
28326
|
-
Requires a workspace: mutagent
|
|
28474
|
+
Requires a workspace: mutagent workspaces use <name> (or --workspace for one command)
|
|
28327
28475
|
`);
|
|
28328
28476
|
registerSpawnCommand(sandbox);
|
|
28329
28477
|
registerLsCommand(sandbox);
|
|
@@ -28459,7 +28607,7 @@ Values are write-only. \`show\` prints each entry's name and a sha256[:8] finger
|
|
|
28459
28607
|
Names: Environments use letters, digits, '.', '_' and '-' (up to 64); variables use
|
|
28460
28608
|
A-Z, 0-9 and _, not starting with a digit. An Environment holds up to 64 KiB.
|
|
28461
28609
|
|
|
28462
|
-
Requires a workspace: mutagent
|
|
28610
|
+
Requires a workspace: mutagent workspaces use <name> (or --workspace for one command)
|
|
28463
28611
|
|
|
28464
28612
|
AI Agent Directive:
|
|
28465
28613
|
Use 'set' to create or update — it creates the Environment when absent and
|
|
@@ -28694,6 +28842,7 @@ function printEntries(environment) {
|
|
|
28694
28842
|
|
|
28695
28843
|
// src/bin/cli.ts
|
|
28696
28844
|
init_config();
|
|
28845
|
+
init_organization_scope();
|
|
28697
28846
|
|
|
28698
28847
|
// src/lib/brand.ts
|
|
28699
28848
|
var BRAND = {
|
|
@@ -28762,7 +28911,7 @@ ${lines.join(`
|
|
|
28762
28911
|
}
|
|
28763
28912
|
|
|
28764
28913
|
// src/lib/global-version-flag.ts
|
|
28765
|
-
var GLOBAL_VALUE_OPTIONS = new Set(["--api-key", "--endpoint"]);
|
|
28914
|
+
var GLOBAL_VALUE_OPTIONS = new Set(["--api-key", "--endpoint", "--workspace", "--org"]);
|
|
28766
28915
|
function findSubcommandIndex(rawArgs) {
|
|
28767
28916
|
for (let i = 0;i < rawArgs.length; i++) {
|
|
28768
28917
|
const arg = rawArgs[i];
|
|
@@ -28862,7 +29011,7 @@ To pass the Mutagent platform key instead, put it before the command (mutagent -
|
|
|
28862
29011
|
function rootValueOptionRefusal(option, label) {
|
|
28863
29012
|
const command = label.replace(/^mutagent /, "");
|
|
28864
29013
|
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" : "";
|
|
29014
|
+
const env = option === "--endpoint" ? " or export MUTAGENT_ENDPOINT" : option === "--workspace" ? " or export MUTAGENT_WORKSPACE_ID" : "";
|
|
28866
29015
|
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
29016
|
For the forwarded command, put it after --: ${label} … -- … ${option} <value>.`, 2);
|
|
28868
29017
|
}
|
|
@@ -29254,7 +29403,7 @@ var program = new Command18;
|
|
|
29254
29403
|
program.name("mutagent").description(`Mutagent CLI - command-line client for the Mutagent platform
|
|
29255
29404
|
|
|
29256
29405
|
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({
|
|
29406
|
+
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; 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
29407
|
sortSubcommands: true,
|
|
29259
29408
|
showGlobalOptions: true
|
|
29260
29409
|
});
|
|
@@ -29272,9 +29421,11 @@ ${chalk37.yellow("Managed agents:")}
|
|
|
29272
29421
|
mutagent agent --help ${chalk37.dim("check, pack, run, deploy, activate, list, inspect, retire")}
|
|
29273
29422
|
|
|
29274
29423
|
${chalk37.yellow("Workspace:")}
|
|
29275
|
-
mutagent
|
|
29276
|
-
mutagent workspaces
|
|
29277
|
-
mutagent
|
|
29424
|
+
mutagent workspaces use <name> ${chalk37.dim("Choose the workspace every later command works in")}
|
|
29425
|
+
mutagent workspaces current ${chalk37.dim("The key's scope, organization and the verified workspace")}
|
|
29426
|
+
mutagent workspaces list ${chalk37.dim("Your workspaces in the key's organization")}
|
|
29427
|
+
--workspace <name|id> ${chalk37.dim("One command in another workspace (then MUTAGENT_WORKSPACE_ID, then the selection)")}
|
|
29428
|
+
mutagent login --org <slug> ${chalk37.dim("Sign in for another organization")}
|
|
29278
29429
|
mutagent providers list ${chalk37.dim("LLM providers in the workspace")}
|
|
29279
29430
|
mutagent helix models ${chalk37.dim("Models Helix can use in the cloud, and the workspace default model")}
|
|
29280
29431
|
mutagent env ls ${chalk37.dim("Environments: variables and secrets for tools")}
|
|
@@ -29337,7 +29488,7 @@ var rootOptionError = forwardedRootOptionRefusal(rawArgs);
|
|
|
29337
29488
|
if (rootOptionError !== undefined) {
|
|
29338
29489
|
handleError(rootOptionError, rawArgs.includes("--json"));
|
|
29339
29490
|
}
|
|
29340
|
-
program.hook("preAction", (thisCommand) => {
|
|
29491
|
+
program.hook("preAction", (thisCommand, actionCommand) => {
|
|
29341
29492
|
const globalOpts = thisCommand.optsWithGlobals();
|
|
29342
29493
|
if (globalOpts.apiKey && !process.env.MUTAGENT_API_KEY) {
|
|
29343
29494
|
process.env.MUTAGENT_API_KEY = globalOpts.apiKey;
|
|
@@ -29345,6 +29496,13 @@ program.hook("preAction", (thisCommand) => {
|
|
|
29345
29496
|
if (globalOpts.endpoint && !process.env.MUTAGENT_ENDPOINT) {
|
|
29346
29497
|
process.env.MUTAGENT_ENDPOINT = globalOpts.endpoint;
|
|
29347
29498
|
}
|
|
29499
|
+
if (globalOpts.workspace) {
|
|
29500
|
+
process.env.MUTAGENT_WORKSPACE_ID = globalOpts.workspace;
|
|
29501
|
+
setWorkspaceFlag(globalOpts.workspace);
|
|
29502
|
+
}
|
|
29503
|
+
if (actionCommand.name() !== "login") {
|
|
29504
|
+
setOrgAssertion(globalOpts.org);
|
|
29505
|
+
}
|
|
29348
29506
|
if (globalOpts.nonInteractive || process.env.CI === "true" || !process.stdin.isTTY) {
|
|
29349
29507
|
process.env.MUTAGENT_NON_INTERACTIVE = "true";
|
|
29350
29508
|
}
|
|
@@ -29388,4 +29546,4 @@ if (isAgentInvocation(rawArgs)) {
|
|
|
29388
29546
|
program.parse(argvForCommander, { from: "user" });
|
|
29389
29547
|
}
|
|
29390
29548
|
|
|
29391
|
-
//# debugId=
|
|
29549
|
+
//# debugId=005A5989779CDE4E64756E2164756E21
|