@getmagical/cli 0.1.5 → 0.1.6

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/index.js CHANGED
@@ -1276,7 +1276,7 @@ mutation UpdateAutomationById($id: ID!, $input: UpdateAutomationInput!) {
1276
1276
  ];
1277
1277
 
1278
1278
  // src/program.ts
1279
- import { Command, InvalidArgumentError, Option } from "commander";
1279
+ import { Command as Command2, InvalidArgumentError as InvalidArgumentError2, Option as Option2 } from "commander";
1280
1280
 
1281
1281
  // src/api.ts
1282
1282
  import { z as z2 } from "zod";
@@ -1323,15 +1323,20 @@ function getApiErrorDetail(responseBody) {
1323
1323
  }
1324
1324
  return parsedResponse.data.errors.map((error) => error.message).join("\n");
1325
1325
  }
1326
+ function trimTrailingSlash(value) {
1327
+ return value.replace(/\/+$/u, "");
1328
+ }
1326
1329
  function createMagicalApiClient({
1327
1330
  apiBaseUrl,
1328
1331
  fetchFn = fetch
1329
1332
  }) {
1333
+ const resolveApiBaseUrl = typeof apiBaseUrl === "string" ? () => apiBaseUrl : apiBaseUrl;
1330
1334
  async function fetchJson({
1331
1335
  accessToken,
1332
- url,
1336
+ path: path5,
1333
1337
  init
1334
1338
  }) {
1339
+ const url = `${trimTrailingSlash(await resolveApiBaseUrl())}${path5}`;
1335
1340
  const headers = new Headers(init?.headers);
1336
1341
  headers.set("authorization", `Bearer ${accessToken}`);
1337
1342
  const response = await fetchFn(url, {
@@ -1339,7 +1344,7 @@ function createMagicalApiClient({
1339
1344
  headers
1340
1345
  }).catch((error) => {
1341
1346
  throw new CliError(
1342
- `Failed to reach Magical API at ${url}. Check MAGICAL_API_URL and make sure service-main is running.`,
1347
+ `Failed to reach Magical API at ${url}. Check your configured base URL and make sure service-main is running.`,
1343
1348
  { cause: error }
1344
1349
  );
1345
1350
  });
@@ -1379,7 +1384,7 @@ function createMagicalApiClient({
1379
1384
  const parsedResponse = graphQlResponseSchema.safeParse(
1380
1385
  await fetchJson({
1381
1386
  accessToken,
1382
- url: `${apiBaseUrl}/graphql`,
1387
+ path: "/graphql",
1383
1388
  init: {
1384
1389
  method: "POST",
1385
1390
  headers,
@@ -1404,7 +1409,7 @@ function createMagicalApiClient({
1404
1409
  const parsedResponse = workosOrganizationsResponseSchema.safeParse(
1405
1410
  await fetchJson({
1406
1411
  accessToken,
1407
- url: `${apiBaseUrl}/workos/organizations`
1412
+ path: "/workos/organizations"
1408
1413
  })
1409
1414
  );
1410
1415
  if (!parsedResponse.success) {
@@ -1423,7 +1428,7 @@ function createMagicalApiClient({
1423
1428
  const parsedResponse = workosUserSchema.safeParse(
1424
1429
  await fetchJson({
1425
1430
  accessToken,
1426
- url: `${apiBaseUrl}/workos/user/${userId}`
1431
+ path: `/workos/user/${userId}`
1427
1432
  })
1428
1433
  );
1429
1434
  if (!parsedResponse.success) {
@@ -1434,57 +1439,19 @@ function createMagicalApiClient({
1434
1439
  };
1435
1440
  }
1436
1441
 
1437
- // src/command-presentation.ts
1438
- var rootOperationalGroupDescriptions = /* @__PURE__ */ new Map([
1439
- ["agents", "Query agents."],
1440
- ["agent-runs", "Query agent runs."],
1441
- ["automations", "Manage automations."],
1442
- ["automation-runs", "Inspect and control automation runs."],
1443
- ["catalog", "Inspect available commands, models, and tools."],
1444
- ["graphql", "Inspect GraphQL metadata."]
1445
- ]);
1446
- function configureCommandPresentation(command) {
1447
- const originalCreateCommand = command.createCommand.bind(command);
1448
- command.createCommand = (name) => configureCommandPresentation(originalCreateCommand(name));
1449
- command.createHelp = () => new MagicalHelp();
1450
- return command;
1451
- }
1452
- function createOperationHelpExamples(operationSpec) {
1453
- const baseCommand = `mgcl ${operationSpec.cli.path.join(" ")}`;
1454
- const requiredOptions = operationSpec.cli.options.filter((optionSpec) => optionSpec.required).map((optionSpec) => {
1455
- if (optionSpec.valueType === "boolean") {
1456
- return `--${optionSpec.flagName}`;
1457
- }
1458
- if (optionSpec.valueType === "number") {
1459
- return `--${optionSpec.flagName} 10`;
1460
- }
1461
- if (optionSpec.valueType === "json") {
1462
- return `--${optionSpec.flagName} '{...}'`;
1463
- }
1464
- if (optionSpec.valueType === "string-array") {
1465
- return `--${optionSpec.flagName} value-a,value-b`;
1466
- }
1467
- return `--${optionSpec.flagName} <${optionSpec.flagName}>`;
1468
- });
1469
- const baseInvocation = [baseCommand, ...requiredOptions].join(" ");
1470
- return [
1471
- baseInvocation,
1472
- `${baseInvocation} --org <org>`,
1473
- `${baseInvocation} --json`
1474
- ];
1475
- }
1476
-
1477
1442
  // src/config.ts
1478
1443
  import os from "os";
1479
1444
  import path from "path";
1480
1445
  var DEFAULT_MAGICAL_API_URL = "https://api-agt.getmagical.io";
1481
1446
  var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
1482
1447
  var DEFAULT_WORKOS_CLIENT_ID = "client_01JJZ6XJ1RF248P20WF63M4VAM";
1448
+ var DEFAULT_STAGING_WORKOS_CLIENT_ID = "client_01JJZ6X26BGFBT8AC303XPQ9EQ";
1483
1449
  var DEFAULT_KEYCHAIN_SERVICE_NAME = "@getmagical/mcp-cli";
1484
1450
  var DEFAULT_CLI_DIST_TAG = "latest";
1485
1451
  var DEFAULT_CLI_NPM_REGISTRY_URL = "https://registry.npmjs.org/";
1486
1452
  var CLI_CONFIG_DIRECTORY_NAME = "magical-mcp-cli";
1487
1453
  var CLI_STATE_FILE_NAME = "state.json";
1454
+ var CLI_SETTINGS_FILE_NAME = "settings.json";
1488
1455
  function resolvePlatformConfigDirectory() {
1489
1456
  const customConfigDirectory = process.env["MAGICAL_CLI_CONFIG_DIR"];
1490
1457
  if (customConfigDirectory) {
@@ -1506,19 +1473,202 @@ function getRuntimeConfig() {
1506
1473
  );
1507
1474
  return {
1508
1475
  cliDistTag: process.env["MAGICAL_CLI_DIST_TAG"] ?? DEFAULT_CLI_DIST_TAG,
1509
- magicalApiUrl: process.env["MAGICAL_API_URL"] ?? DEFAULT_MAGICAL_API_URL,
1476
+ magicalApiUrl: DEFAULT_MAGICAL_API_URL,
1510
1477
  npmRegistryUrl: process.env["MAGICAL_CLI_NPM_REGISTRY_URL"] ?? DEFAULT_CLI_NPM_REGISTRY_URL,
1511
1478
  stateFilePath: path.join(configDirectory, CLI_STATE_FILE_NAME),
1512
1479
  workosApiUrl: DEFAULT_WORKOS_API_URL,
1513
- workosClientId: process.env["WORKOS_CLIENT_ID"] ?? DEFAULT_WORKOS_CLIENT_ID,
1514
- keychainServiceName: DEFAULT_KEYCHAIN_SERVICE_NAME,
1515
- ...process.env["WORKOS_AUTHKIT_DOMAIN"] ? { workosAuthkitDomain: process.env["WORKOS_AUTHKIT_DOMAIN"] } : {}
1480
+ workosClientId: DEFAULT_WORKOS_CLIENT_ID,
1481
+ keychainServiceName: DEFAULT_KEYCHAIN_SERVICE_NAME
1516
1482
  };
1517
1483
  }
1518
1484
  function requireWorkosClientId(config) {
1519
1485
  return config.workosClientId;
1520
1486
  }
1521
1487
 
1488
+ // src/settings.ts
1489
+ import { mkdir, readFile, rm, writeFile } from "fs/promises";
1490
+ import path2 from "path";
1491
+ import { safeJsonParse } from "@magical/common/stdlib/json.util";
1492
+ import { isErr } from "@magical/common/stdlib/result.util";
1493
+ import { z as z3 } from "zod";
1494
+ var cliSettingsSchema = z3.object({
1495
+ env: z3.object({
1496
+ baseUrl: z3.url().optional()
1497
+ }).default({})
1498
+ });
1499
+ function createEmptyCliSettings() {
1500
+ return {
1501
+ env: {}
1502
+ };
1503
+ }
1504
+ function hasCliSettingsOverrides(settings) {
1505
+ return settings.env.baseUrl !== void 0;
1506
+ }
1507
+ function resolveSettingsFilePath(stateFilePath) {
1508
+ return path2.join(path2.dirname(stateFilePath), CLI_SETTINGS_FILE_NAME);
1509
+ }
1510
+ function resolveConfiguredApiBaseUrl({
1511
+ defaultApiBaseUrl,
1512
+ settings
1513
+ }) {
1514
+ return settings.env.baseUrl ?? defaultApiBaseUrl;
1515
+ }
1516
+ function shouldUseStagingWorkosClientId(settings) {
1517
+ return settings.env.baseUrl !== void 0;
1518
+ }
1519
+ function createFileSettingsStore(stateFilePath) {
1520
+ const settingsFilePath = resolveSettingsFilePath(stateFilePath);
1521
+ return {
1522
+ async load() {
1523
+ const fileContents = await readFile(settingsFilePath, "utf8").catch(
1524
+ (error) => {
1525
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1526
+ return null;
1527
+ }
1528
+ throw new CliError("Failed to load CLI settings.", { cause: error });
1529
+ }
1530
+ );
1531
+ if (fileContents === null) {
1532
+ return createEmptyCliSettings();
1533
+ }
1534
+ const parsedJson = safeJsonParse(fileContents);
1535
+ if (isErr(parsedJson)) {
1536
+ throw new CliError("Stored CLI settings are invalid.", {
1537
+ cause: parsedJson.error.cause
1538
+ });
1539
+ }
1540
+ const parsed = cliSettingsSchema.safeParse(parsedJson.value);
1541
+ if (!parsed.success) {
1542
+ throw new CliError("Stored CLI settings are invalid.", {
1543
+ cause: parsed.error
1544
+ });
1545
+ }
1546
+ return parsed.data;
1547
+ },
1548
+ async save(settings) {
1549
+ if (!hasCliSettingsOverrides(settings)) {
1550
+ await rm(settingsFilePath, { force: true });
1551
+ return;
1552
+ }
1553
+ await mkdir(path2.dirname(settingsFilePath), { recursive: true });
1554
+ await writeFile(
1555
+ settingsFilePath,
1556
+ `${JSON.stringify(settings, null, 2)}
1557
+ `
1558
+ );
1559
+ }
1560
+ };
1561
+ }
1562
+
1563
+ // src/auth-login.ts
1564
+ function writeJsonOutput(io, value) {
1565
+ io.info(JSON.stringify(value, null, 2));
1566
+ }
1567
+ async function handleAuthLogin(dependencies, options) {
1568
+ const settings = await dependencies.settingsStore?.load() ?? createEmptyCliSettings();
1569
+ const clientId = shouldUseStagingWorkosClientId(settings) ? DEFAULT_STAGING_WORKOS_CLIENT_ID : requireWorkosClientId(dependencies.runtimeConfig);
1570
+ const deviceAuthorization = await dependencies.authClient.startDeviceAuthorization({ clientId });
1571
+ const verificationUrl = deviceAuthorization.verification_uri_complete ?? deviceAuthorization.verification_uri;
1572
+ const promptWriter = options.json ? dependencies.io.error : dependencies.io.info;
1573
+ promptWriter(
1574
+ formatAuthLoginPrompt({
1575
+ userCode: deviceAuthorization.user_code,
1576
+ verificationUrl
1577
+ })
1578
+ );
1579
+ await dependencies.openExternalUrl(verificationUrl).catch(() => {
1580
+ promptWriter(formatBrowserOpenFallback());
1581
+ });
1582
+ const tokenResponse = await dependencies.authClient.pollForDeviceTokens({
1583
+ clientId,
1584
+ deviceCode: deviceAuthorization.device_code,
1585
+ ...deviceAuthorization.expires_in ? { expiresInSeconds: deviceAuthorization.expires_in } : {},
1586
+ ...deviceAuthorization.interval ? { intervalSeconds: deviceAuthorization.interval } : {}
1587
+ });
1588
+ const accessTokenClaims = dependencies.authClient.decodeAccessTokenClaims(
1589
+ tokenResponse.access_token
1590
+ );
1591
+ const [user, organizations] = await Promise.all([
1592
+ dependencies.apiClient.fetchUser({
1593
+ accessToken: tokenResponse.access_token,
1594
+ userId: accessTokenClaims.sub
1595
+ }),
1596
+ dependencies.apiClient.fetchOrganizations({
1597
+ accessToken: tokenResponse.access_token
1598
+ })
1599
+ ]);
1600
+ const defaultOrgId = accessTokenClaims.org_id ?? organizations[0]?.id ?? null;
1601
+ const nextState = {
1602
+ account: {
1603
+ clientId,
1604
+ email: user.email,
1605
+ userId: user.id
1606
+ },
1607
+ defaultOrgId,
1608
+ lastUsedOrgId: defaultOrgId,
1609
+ organizations
1610
+ };
1611
+ await Promise.all([
1612
+ dependencies.refreshTokenStore.save(
1613
+ nextState.account,
1614
+ tokenResponse.refresh_token
1615
+ ),
1616
+ dependencies.stateStore.save(nextState)
1617
+ ]);
1618
+ const defaultOrganization = organizations.find((organization) => organization.id === defaultOrgId) ?? null;
1619
+ if (options.json) {
1620
+ writeJsonOutput(dependencies.io, nextState);
1621
+ return;
1622
+ }
1623
+ dependencies.io.info(
1624
+ formatAuthLoginSuccess({
1625
+ account: nextState.account,
1626
+ defaultOrganization,
1627
+ organizationCount: organizations.length
1628
+ })
1629
+ );
1630
+ }
1631
+
1632
+ // src/command-presentation.ts
1633
+ var rootOperationalGroupDescriptions = /* @__PURE__ */ new Map([
1634
+ ["agents", "Query agents."],
1635
+ ["agent-runs", "Query agent runs."],
1636
+ ["automations", "Manage automations."],
1637
+ ["automation-runs", "Inspect and control automation runs."],
1638
+ ["catalog", "Inspect available commands, models, and tools."],
1639
+ ["graphql", "Inspect GraphQL metadata."]
1640
+ ]);
1641
+ function configureCommandPresentation(command) {
1642
+ const originalCreateCommand = command.createCommand.bind(command);
1643
+ command.createCommand = (name) => configureCommandPresentation(originalCreateCommand(name));
1644
+ command.createHelp = () => new MagicalHelp();
1645
+ return command;
1646
+ }
1647
+ function createOperationHelpExamples(operationSpec) {
1648
+ const baseCommand = `mgcl ${operationSpec.cli.path.join(" ")}`;
1649
+ const requiredOptions = operationSpec.cli.options.filter((optionSpec) => optionSpec.required).map((optionSpec) => {
1650
+ if (optionSpec.valueType === "boolean") {
1651
+ return `--${optionSpec.flagName}`;
1652
+ }
1653
+ if (optionSpec.valueType === "number") {
1654
+ return `--${optionSpec.flagName} 10`;
1655
+ }
1656
+ if (optionSpec.valueType === "json") {
1657
+ return `--${optionSpec.flagName} '{...}'`;
1658
+ }
1659
+ if (optionSpec.valueType === "string-array") {
1660
+ return `--${optionSpec.flagName} value-a,value-b`;
1661
+ }
1662
+ return `--${optionSpec.flagName} <${optionSpec.flagName}>`;
1663
+ });
1664
+ const baseInvocation = [baseCommand, ...requiredOptions].join(" ");
1665
+ return [
1666
+ baseInvocation,
1667
+ `${baseInvocation} --org <org>`,
1668
+ `${baseInvocation} --json`
1669
+ ];
1670
+ }
1671
+
1522
1672
  // src/keychain.ts
1523
1673
  import keytar from "keytar";
1524
1674
  function buildKeychainAccountName(account) {
@@ -1574,7 +1724,7 @@ function resolveOpenCommand(url) {
1574
1724
  // src/self-update.ts
1575
1725
  import { spawn as spawn2 } from "child_process";
1576
1726
  import fs from "fs";
1577
- import path2 from "path";
1727
+ import path3 from "path";
1578
1728
  import { fileURLToPath } from "url";
1579
1729
  var CLI_PACKAGE_NAME = "@getmagical/cli";
1580
1730
  var DEFAULT_CLI_DIST_TAG2 = "latest";
@@ -1585,9 +1735,9 @@ function resolveInstalledPackageRoot({
1585
1735
  packageName
1586
1736
  }) {
1587
1737
  const packagePathSegments = packageName.split("/");
1588
- let currentPath = path2.dirname(entryFilePath);
1738
+ let currentPath = path3.dirname(entryFilePath);
1589
1739
  for (; ; ) {
1590
- const currentPathSegments = currentPath.split(path2.sep).filter(Boolean);
1740
+ const currentPathSegments = currentPath.split(path3.sep).filter(Boolean);
1591
1741
  const currentPackageSegments = currentPathSegments.slice(
1592
1742
  -packagePathSegments.length
1593
1743
  );
@@ -1596,7 +1746,7 @@ function resolveInstalledPackageRoot({
1596
1746
  )) {
1597
1747
  return currentPath;
1598
1748
  }
1599
- const parentPath = path2.dirname(currentPath);
1749
+ const parentPath = path3.dirname(currentPath);
1600
1750
  if (parentPath === currentPath) {
1601
1751
  return null;
1602
1752
  }
@@ -1606,14 +1756,14 @@ function resolveInstalledPackageRoot({
1606
1756
  function resolveInstallPrefixFromPackageRoot(packageRootPath) {
1607
1757
  let currentPath = packageRootPath;
1608
1758
  for (; ; ) {
1609
- if (path2.basename(currentPath) === "node_modules") {
1610
- const nodeModulesParentPath = path2.dirname(currentPath);
1611
- if (path2.basename(nodeModulesParentPath) === "lib") {
1612
- return path2.dirname(nodeModulesParentPath);
1759
+ if (path3.basename(currentPath) === "node_modules") {
1760
+ const nodeModulesParentPath = path3.dirname(currentPath);
1761
+ if (path3.basename(nodeModulesParentPath) === "lib") {
1762
+ return path3.dirname(nodeModulesParentPath);
1613
1763
  }
1614
1764
  return nodeModulesParentPath;
1615
1765
  }
1616
- const parentPath = path2.dirname(currentPath);
1766
+ const parentPath = path3.dirname(currentPath);
1617
1767
  if (parentPath === currentPath) {
1618
1768
  return null;
1619
1769
  }
@@ -1630,13 +1780,13 @@ function resolveManagedNpmCommand(installPrefix) {
1630
1780
  if (process.platform === "win32") {
1631
1781
  return null;
1632
1782
  }
1633
- const managedNodeExecutable = path2.join(
1783
+ const managedNodeExecutable = path3.join(
1634
1784
  installPrefix,
1635
1785
  MANAGED_NODE_SUBPATH,
1636
1786
  "bin",
1637
1787
  "node"
1638
1788
  );
1639
- const managedNpmCli = path2.join(
1789
+ const managedNpmCli = path3.join(
1640
1790
  installPrefix,
1641
1791
  MANAGED_NODE_SUBPATH,
1642
1792
  "lib",
@@ -1734,34 +1884,135 @@ async function selfUpdateInstalledCli({
1734
1884
  });
1735
1885
  }
1736
1886
 
1887
+ // src/settings-command.ts
1888
+ import { isErr as isErr2 } from "@magical/common/stdlib/result.util";
1889
+ import { safeUrlConstructor } from "@magical/common/stdlib/url.utils";
1890
+ import { Command, InvalidArgumentError, Option } from "commander";
1891
+ function writeJsonOutput2(io, value) {
1892
+ io.info(JSON.stringify(value, null, 2));
1893
+ }
1894
+ function requireSettingsStore(settingsStore) {
1895
+ if (!settingsStore) {
1896
+ throw new CliError("CLI settings are not configured for this instance.");
1897
+ }
1898
+ return settingsStore;
1899
+ }
1900
+ function parseBaseUrl(value) {
1901
+ const parsedUrl = safeUrlConstructor(value);
1902
+ if (isErr2(parsedUrl)) {
1903
+ throw new InvalidArgumentError("Expected a valid URL for base-url.");
1904
+ }
1905
+ return parsedUrl.value.toString().replace(/\/$/u, "");
1906
+ }
1907
+ async function clearSavedAuth(dependencies) {
1908
+ const state = await dependencies.stateStore.load();
1909
+ if (!state) {
1910
+ return false;
1911
+ }
1912
+ await dependencies.refreshTokenStore.clear(state.account);
1913
+ await dependencies.stateStore.clear();
1914
+ return true;
1915
+ }
1916
+ async function handleSettingsEnvBaseUrl(dependencies, rawBaseUrl, options) {
1917
+ if (options.reset && rawBaseUrl) {
1918
+ throw new CliError("Pass either a base URL or --reset, not both.");
1919
+ }
1920
+ if (!options.reset && !rawBaseUrl) {
1921
+ throw new CliError("Provide a base URL or pass --reset.");
1922
+ }
1923
+ const settingsStore = requireSettingsStore(dependencies.settingsStore);
1924
+ const currentSettings = await settingsStore.load();
1925
+ const { baseUrl: _baseUrl, ...remainingEnvSettings } = currentSettings.env;
1926
+ if (options.reset) {
1927
+ const nextSettings2 = {
1928
+ ...currentSettings,
1929
+ env: remainingEnvSettings
1930
+ };
1931
+ await settingsStore.save(nextSettings2);
1932
+ const authCleared2 = await clearSavedAuth(dependencies);
1933
+ if (options.json) {
1934
+ writeJsonOutput2(dependencies.io, {
1935
+ authCleared: authCleared2,
1936
+ settings: nextSettings2
1937
+ });
1938
+ return;
1939
+ }
1940
+ dependencies.io.info(
1941
+ authCleared2 ? 'Cleared the persisted base-url override and removed saved auth. Run "mgcl auth login" again.' : "Cleared the persisted base-url override."
1942
+ );
1943
+ return;
1944
+ }
1945
+ const nextBaseUrl = parseBaseUrl(rawBaseUrl ?? "");
1946
+ const nextSettings = {
1947
+ ...currentSettings,
1948
+ env: {
1949
+ ...currentSettings.env,
1950
+ baseUrl: nextBaseUrl
1951
+ }
1952
+ };
1953
+ await settingsStore.save(nextSettings);
1954
+ const authCleared = await clearSavedAuth(dependencies);
1955
+ if (options.json) {
1956
+ writeJsonOutput2(dependencies.io, {
1957
+ authCleared,
1958
+ settings: nextSettings
1959
+ });
1960
+ return;
1961
+ }
1962
+ dependencies.io.info(
1963
+ authCleared ? `Saved base-url override ${nextBaseUrl} and removed saved auth. Run "mgcl auth login" again.` : `Saved base-url override ${nextBaseUrl}.`
1964
+ );
1965
+ }
1966
+ function createSettingsCommand(dependencies) {
1967
+ const settingsCommand = configureCommandPresentation(
1968
+ new Command("settings").description("Manage persisted CLI settings.")
1969
+ ).helpGroup("Workspace:").addHelpText(
1970
+ "after",
1971
+ `
1972
+ ${formatExamples("Examples", [
1973
+ "mgcl settings env base-url http://main.localhost:1355",
1974
+ "mgcl settings env base-url --reset"
1975
+ ])}`
1976
+ );
1977
+ const settingsEnvCommand = settingsCommand.command("env").description("Manage persisted environment overrides.");
1978
+ settingsEnvCommand.command("base-url [url]").description("Set or clear the persisted Magical API base URL override.").addOption(new Option("--reset", "Clear the persisted base URL override.")).action(async function(baseUrl) {
1979
+ await handleSettingsEnvBaseUrl(
1980
+ dependencies,
1981
+ baseUrl,
1982
+ this.optsWithGlobals()
1983
+ );
1984
+ });
1985
+ return settingsCommand;
1986
+ }
1987
+
1737
1988
  // src/state.ts
1738
- import { mkdir, readFile, rm, writeFile } from "fs/promises";
1739
- import path3 from "path";
1740
- import { z as z3 } from "zod";
1741
- var cliAccountSchema = z3.object({
1742
- clientId: z3.string().min(1),
1743
- email: z3.string().nullable(),
1744
- userId: z3.string().min(1)
1989
+ import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1990
+ import path4 from "path";
1991
+ import { z as z4 } from "zod";
1992
+ var cliAccountSchema = z4.object({
1993
+ clientId: z4.string().min(1),
1994
+ email: z4.string().nullable(),
1995
+ userId: z4.string().min(1)
1745
1996
  });
1746
- var cliOrganizationSchema = z3.object({
1747
- id: z3.string().min(1),
1748
- name: z3.string().min(1),
1749
- primaryDomain: z3.string().nullable().default(null)
1997
+ var cliOrganizationSchema = z4.object({
1998
+ id: z4.string().min(1),
1999
+ name: z4.string().min(1),
2000
+ primaryDomain: z4.string().nullable().default(null)
1750
2001
  });
1751
- var cliStateSchema = z3.object({
2002
+ var cliStateSchema = z4.object({
1752
2003
  account: cliAccountSchema,
1753
- defaultOrgId: z3.string().nullable(),
1754
- lastUsedOrgId: z3.string().nullable(),
1755
- organizations: z3.array(cliOrganizationSchema)
2004
+ defaultOrgId: z4.string().nullable(),
2005
+ lastUsedOrgId: z4.string().nullable(),
2006
+ organizations: z4.array(cliOrganizationSchema)
1756
2007
  });
1757
2008
  function createFileStateStore(stateFilePath) {
1758
2009
  return {
1759
2010
  async clear() {
1760
- await rm(stateFilePath, { force: true });
2011
+ await rm2(stateFilePath, { force: true });
1761
2012
  },
1762
2013
  async load() {
1763
2014
  try {
1764
- const fileContents = await readFile(stateFilePath, "utf8");
2015
+ const fileContents = await readFile2(stateFilePath, "utf8");
1765
2016
  const parsed = cliStateSchema.safeParse(JSON.parse(fileContents));
1766
2017
  if (!parsed.success) {
1767
2018
  throw new CliError("Stored CLI state is invalid.");
@@ -1778,43 +2029,43 @@ function createFileStateStore(stateFilePath) {
1778
2029
  }
1779
2030
  },
1780
2031
  async save(state) {
1781
- await mkdir(path3.dirname(stateFilePath), { recursive: true });
1782
- await writeFile(stateFilePath, `${JSON.stringify(state, null, 2)}
2032
+ await mkdir2(path4.dirname(stateFilePath), { recursive: true });
2033
+ await writeFile2(stateFilePath, `${JSON.stringify(state, null, 2)}
1783
2034
  `);
1784
2035
  }
1785
2036
  };
1786
2037
  }
1787
2038
 
1788
2039
  // src/workos-auth.ts
1789
- import { z as z4 } from "zod";
2040
+ import { z as z5 } from "zod";
1790
2041
  var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
1791
2042
  function buildFormHeaders() {
1792
2043
  const headers = new Headers();
1793
2044
  headers.set("content-type", "application/x-www-form-urlencoded");
1794
2045
  return headers;
1795
2046
  }
1796
- var deviceAuthorizationSchema = z4.object({
1797
- device_code: z4.string().min(1),
1798
- expires_in: z4.number().int().positive().optional(),
1799
- interval: z4.number().int().positive().optional(),
1800
- user_code: z4.string().min(1),
1801
- verification_uri: z4.url(),
1802
- verification_uri_complete: z4.url().optional()
2047
+ var deviceAuthorizationSchema = z5.object({
2048
+ device_code: z5.string().min(1),
2049
+ expires_in: z5.number().int().positive().optional(),
2050
+ interval: z5.number().int().positive().optional(),
2051
+ user_code: z5.string().min(1),
2052
+ verification_uri: z5.url(),
2053
+ verification_uri_complete: z5.url().optional()
1803
2054
  });
1804
- var tokenResponseSchema = z4.object({
1805
- access_token: z4.string().min(1),
1806
- expires_in: z4.number().int().positive().optional(),
1807
- refresh_token: z4.string().min(1),
1808
- token_type: z4.string().optional()
2055
+ var tokenResponseSchema = z5.object({
2056
+ access_token: z5.string().min(1),
2057
+ expires_in: z5.number().int().positive().optional(),
2058
+ refresh_token: z5.string().min(1),
2059
+ token_type: z5.string().optional()
1809
2060
  });
1810
- var tokenErrorSchema = z4.object({
1811
- error: z4.string().min(1),
1812
- error_description: z4.string().optional()
2061
+ var tokenErrorSchema = z5.object({
2062
+ error: z5.string().min(1),
2063
+ error_description: z5.string().optional()
1813
2064
  });
1814
- var accessTokenClaimsSchema = z4.object({
1815
- org_id: z4.string().min(1).optional(),
1816
- sid: z4.string().min(1).optional(),
1817
- sub: z4.string().min(1)
2065
+ var accessTokenClaimsSchema = z5.object({
2066
+ org_id: z5.string().min(1).optional(),
2067
+ sid: z5.string().min(1).optional(),
2068
+ sub: z5.string().min(1)
1818
2069
  });
1819
2070
  function createWorkosAuthClient({
1820
2071
  fetchFn = fetch,
@@ -1971,7 +2222,7 @@ function parseCommandOptionValue(rawValue, optionSpec) {
1971
2222
  case "number": {
1972
2223
  const parsedNumber = Number(rawValue);
1973
2224
  if (Number.isNaN(parsedNumber)) {
1974
- throw new InvalidArgumentError(
2225
+ throw new InvalidArgumentError2(
1975
2226
  `Expected a number for --${optionSpec.flagName}.`
1976
2227
  );
1977
2228
  }
@@ -1984,7 +2235,7 @@ function parseCommandOptionValue(rawValue, optionSpec) {
1984
2235
  try {
1985
2236
  return JSON.parse(rawValue);
1986
2237
  } catch {
1987
- throw new InvalidArgumentError(
2238
+ throw new InvalidArgumentError2(
1988
2239
  `Expected valid JSON for --${optionSpec.flagName}.`
1989
2240
  );
1990
2241
  }
@@ -2038,7 +2289,7 @@ function requireLoggedInState(state) {
2038
2289
  }
2039
2290
  return state;
2040
2291
  }
2041
- function writeJsonOutput(io, value) {
2292
+ function writeJsonOutput3(io, value) {
2042
2293
  io.info(JSON.stringify(value, null, 2));
2043
2294
  }
2044
2295
  function collectOperationVariables(operationSpec, options) {
@@ -2094,69 +2345,6 @@ async function withOrgScopedAccessToken({
2094
2345
  accessToken: tokenResponse.access_token
2095
2346
  };
2096
2347
  }
2097
- async function handleAuthLogin(dependencies, options) {
2098
- const clientId = requireWorkosClientId(dependencies.runtimeConfig);
2099
- const deviceAuthorization = await dependencies.authClient.startDeviceAuthorization({ clientId });
2100
- const verificationUrl = deviceAuthorization.verification_uri_complete ?? deviceAuthorization.verification_uri;
2101
- const promptWriter = options.json ? dependencies.io.error : dependencies.io.info;
2102
- promptWriter(
2103
- formatAuthLoginPrompt({
2104
- userCode: deviceAuthorization.user_code,
2105
- verificationUrl
2106
- })
2107
- );
2108
- await dependencies.openExternalUrl(verificationUrl).catch(() => {
2109
- promptWriter(formatBrowserOpenFallback());
2110
- });
2111
- const tokenResponse = await dependencies.authClient.pollForDeviceTokens({
2112
- clientId,
2113
- deviceCode: deviceAuthorization.device_code,
2114
- ...deviceAuthorization.expires_in ? { expiresInSeconds: deviceAuthorization.expires_in } : {},
2115
- ...deviceAuthorization.interval ? { intervalSeconds: deviceAuthorization.interval } : {}
2116
- });
2117
- const accessTokenClaims = dependencies.authClient.decodeAccessTokenClaims(
2118
- tokenResponse.access_token
2119
- );
2120
- const [user, organizations] = await Promise.all([
2121
- dependencies.apiClient.fetchUser({
2122
- accessToken: tokenResponse.access_token,
2123
- userId: accessTokenClaims.sub
2124
- }),
2125
- dependencies.apiClient.fetchOrganizations({
2126
- accessToken: tokenResponse.access_token
2127
- })
2128
- ]);
2129
- const defaultOrgId = accessTokenClaims.org_id ?? organizations[0]?.id ?? null;
2130
- const nextState = {
2131
- account: {
2132
- clientId,
2133
- email: user.email,
2134
- userId: user.id
2135
- },
2136
- defaultOrgId,
2137
- lastUsedOrgId: defaultOrgId,
2138
- organizations
2139
- };
2140
- await Promise.all([
2141
- dependencies.refreshTokenStore.save(
2142
- nextState.account,
2143
- tokenResponse.refresh_token
2144
- ),
2145
- dependencies.stateStore.save(nextState)
2146
- ]);
2147
- const defaultOrganization = organizations.find((organization) => organization.id === defaultOrgId) ?? null;
2148
- if (options.json) {
2149
- writeJsonOutput(dependencies.io, nextState);
2150
- return;
2151
- }
2152
- dependencies.io.info(
2153
- formatAuthLoginSuccess({
2154
- account: nextState.account,
2155
- defaultOrganization,
2156
- organizationCount: organizations.length
2157
- })
2158
- );
2159
- }
2160
2348
  async function handleAuthLogout(dependencies, options) {
2161
2349
  const state = await dependencies.stateStore.load();
2162
2350
  if (state) {
@@ -2164,7 +2352,7 @@ async function handleAuthLogout(dependencies, options) {
2164
2352
  }
2165
2353
  await dependencies.stateStore.clear();
2166
2354
  if (options.json) {
2167
- writeJsonOutput(dependencies.io, { loggedOut: true });
2355
+ writeJsonOutput3(dependencies.io, { loggedOut: true });
2168
2356
  return;
2169
2357
  }
2170
2358
  dependencies.io.info(formatLogoutSuccess());
@@ -2172,7 +2360,7 @@ async function handleAuthLogout(dependencies, options) {
2172
2360
  async function handleOrgList(dependencies, options) {
2173
2361
  const state = requireLoggedInState(await dependencies.stateStore.load());
2174
2362
  if (options.json) {
2175
- writeJsonOutput(dependencies.io, state.organizations);
2363
+ writeJsonOutput3(dependencies.io, state.organizations);
2176
2364
  return;
2177
2365
  }
2178
2366
  dependencies.io.info(
@@ -2196,7 +2384,7 @@ async function handleOrgUse(dependencies, organizationSelector, options) {
2196
2384
  };
2197
2385
  await dependencies.stateStore.save(nextState);
2198
2386
  if (options.json) {
2199
- writeJsonOutput(dependencies.io, {
2387
+ writeJsonOutput3(dependencies.io, {
2200
2388
  defaultOrgId: organization.id,
2201
2389
  organization
2202
2390
  });
@@ -2211,7 +2399,7 @@ async function handleSelfUpdate(dependencies, options) {
2211
2399
  }
2212
2400
  const updateResult = await selfUpdateCli();
2213
2401
  if (options.json) {
2214
- writeJsonOutput(dependencies.io, {
2402
+ writeJsonOutput3(dependencies.io, {
2215
2403
  installPrefix: updateResult.installPrefix,
2216
2404
  packageSpecifier: updateResult.packageSpecifier,
2217
2405
  registryUrl: updateResult.registryUrl,
@@ -2246,7 +2434,7 @@ async function handleOperationCommand(dependencies, operationSpec, commandOption
2246
2434
  lastUsedOrgId: organization.id
2247
2435
  });
2248
2436
  if (commandOptions.json) {
2249
- writeJsonOutput(dependencies.io, result);
2437
+ writeJsonOutput3(dependencies.io, result);
2250
2438
  return;
2251
2439
  }
2252
2440
  dependencies.io.info(formatHumanReadableOutput(result));
@@ -2294,7 +2482,7 @@ function addRegistryOperationCommand({
2294
2482
  }
2295
2483
  function configureOperationalCommand(command, dependencies, operationSpec) {
2296
2484
  command.description(operationSpec.cli.description).addOption(
2297
- new Option(
2485
+ new Option2(
2298
2486
  "--org <org>",
2299
2487
  "Organization ID or exact organization name to use."
2300
2488
  ).helpGroup("Shared options:")
@@ -2309,7 +2497,7 @@ ${formatExamples(
2309
2497
  for (const optionSpec of operationSpec.cli.options) {
2310
2498
  const optionFlags = optionSpec.valueType === "boolean" ? `--${optionSpec.flagName}` : `--${optionSpec.flagName} <value>`;
2311
2499
  const optionDescription = optionSpec.valueHint ? `${optionSpec.description} (${optionSpec.valueHint})` : optionSpec.description;
2312
- const nextOption = new Option(optionFlags, optionDescription).helpGroup(
2500
+ const nextOption = new Option2(optionFlags, optionDescription).helpGroup(
2313
2501
  operationSpec.kind === "query" ? "Query options:" : "Mutation options:"
2314
2502
  );
2315
2503
  if (optionSpec.valueType === "boolean") {
@@ -2347,13 +2535,17 @@ function createConsoleIo() {
2347
2535
  }
2348
2536
  function createDefaultDependencies() {
2349
2537
  const runtimeConfig = getRuntimeConfig();
2538
+ const settingsStore = createFileSettingsStore(runtimeConfig.stateFilePath);
2350
2539
  const selfUpdateOptions = {
2351
2540
  ...runtimeConfig.cliDistTag ? { distTag: runtimeConfig.cliDistTag } : {},
2352
2541
  ...runtimeConfig.npmRegistryUrl ? { registryUrl: runtimeConfig.npmRegistryUrl } : {}
2353
2542
  };
2354
2543
  return {
2355
2544
  apiClient: createMagicalApiClient({
2356
- apiBaseUrl: runtimeConfig.magicalApiUrl
2545
+ apiBaseUrl: async () => resolveConfiguredApiBaseUrl({
2546
+ defaultApiBaseUrl: runtimeConfig.magicalApiUrl,
2547
+ settings: await settingsStore.load()
2548
+ })
2357
2549
  }),
2358
2550
  authClient: createWorkosAuthClient({
2359
2551
  workosApiUrl: runtimeConfig.workosApiUrl
@@ -2364,15 +2556,16 @@ function createDefaultDependencies() {
2364
2556
  runtimeConfig.keychainServiceName
2365
2557
  ),
2366
2558
  runtimeConfig,
2559
+ settingsStore,
2367
2560
  selfUpdateCli: async () => selfUpdateInstalledCli(selfUpdateOptions),
2368
2561
  stateStore: createFileStateStore(runtimeConfig.stateFilePath)
2369
2562
  };
2370
2563
  }
2371
2564
  function buildProgram(dependencies) {
2372
2565
  const program2 = configureCommandPresentation(
2373
- new Command().name("mgcl").description("Run Magical operations from the command line.")
2374
- ).addOption(new Option("--json", "Render JSON output.")).addOption(
2375
- new Option(
2566
+ new Command2().name("mgcl").description("Run Magical operations from the command line.")
2567
+ ).addOption(new Option2("--json", "Render JSON output.")).addOption(
2568
+ new Option2(
2376
2569
  "--update",
2377
2570
  "Update mgcl to the latest published npm release."
2378
2571
  )
@@ -2398,14 +2591,11 @@ ${formatOutputModesNote()}`
2398
2591
  this.outputHelp();
2399
2592
  });
2400
2593
  const authCommand = configureCommandPresentation(
2401
- new Command("auth").description("Manage CLI authentication.")
2594
+ new Command2("auth").description("Manage CLI authentication.")
2402
2595
  ).helpGroup("Workspace:").addHelpText(
2403
2596
  "after",
2404
2597
  `
2405
- ${formatExamples("Examples", [
2406
- "mgcl auth login",
2407
- "mgcl auth logout"
2408
- ])}`
2598
+ ${formatExamples("Examples", ["mgcl auth login", "mgcl auth logout"])}`
2409
2599
  );
2410
2600
  authCommand.command("login").description("Authenticate with Magical and load organization memberships.").action(async function() {
2411
2601
  await handleAuthLogin(dependencies, this.optsWithGlobals());
@@ -2414,8 +2604,9 @@ ${formatExamples("Examples", [
2414
2604
  await handleAuthLogout(dependencies, this.optsWithGlobals());
2415
2605
  });
2416
2606
  program2.addCommand(authCommand);
2607
+ program2.addCommand(createSettingsCommand(dependencies));
2417
2608
  const orgCommand = configureCommandPresentation(
2418
- new Command("org").description("Manage organization selection.")
2609
+ new Command2("org").description("Manage organization selection.")
2419
2610
  ).helpGroup("Workspace:").addHelpText(
2420
2611
  "after",
2421
2612
  `
@@ -2436,7 +2627,7 @@ ${formatExamples("Examples", [
2436
2627
  });
2437
2628
  program2.addCommand(orgCommand);
2438
2629
  const updateCommand = configureCommandPresentation(
2439
- new Command("update").description(
2630
+ new Command2("update").description(
2440
2631
  "Update mgcl to the latest published npm release."
2441
2632
  )
2442
2633
  ).helpGroup("Workspace:").addHelpText(