@getmagical/cli 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -10,6 +10,7 @@ Magical CLI for the shared MCP operation surface.
10
10
  ## Usage
11
11
 
12
12
  ```bash
13
+ mgcl --version
13
14
  mgcl update
14
15
  mgcl auth login
15
16
  mgcl settings env base-url http://main.localhost:1355
@@ -68,6 +69,10 @@ Once installed, users can update from the same channel with:
68
69
  mgcl --update
69
70
  ```
70
71
 
72
+ Installed CLIs also check the configured npm dist-tag before running
73
+ subcommands. If the local version is behind, `mgcl` blocks the command and
74
+ asks the user to run `mgcl update` before rerunning it.
75
+
71
76
  To remove the managed install:
72
77
 
73
78
  ```bash
package/dist/index.js CHANGED
@@ -2063,10 +2063,10 @@ function createMagicalApiClient({
2063
2063
  const resolveApiBaseUrl = typeof apiBaseUrl === "string" ? () => apiBaseUrl : apiBaseUrl;
2064
2064
  async function fetchJson({
2065
2065
  accessToken,
2066
- path: path5,
2066
+ path: path6,
2067
2067
  init
2068
2068
  }) {
2069
- const url = `${trimTrailingSlash(await resolveApiBaseUrl())}${path5}`;
2069
+ const url = `${trimTrailingSlash(await resolveApiBaseUrl())}${path6}`;
2070
2070
  const headers = new Headers(init?.headers);
2071
2071
  headers.set("authorization", `Bearer ${accessToken}`);
2072
2072
  const response = await fetchFn(url, {
@@ -2546,6 +2546,9 @@ function isAppErrorCategory(category) {
2546
2546
  return typeof category === "string" && // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2547
2547
  ORDERED_APP_ERROR_CATEGORIES.includes(category);
2548
2548
  }
2549
+ function isNonEmptyAppErrorCategoryArray(categories) {
2550
+ return Array.isArray(categories) && categories.length > 0 && categories.every(isAppErrorCategory);
2551
+ }
2549
2552
  var APP_ERROR_SEVERITIES = ["warn", "error", "fatal"];
2550
2553
  function isAppErrorCode(code) {
2551
2554
  return typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599;
@@ -2583,42 +2586,55 @@ var AppError = class _AppError extends Error {
2583
2586
  }
2584
2587
  return type;
2585
2588
  }
2586
- static extractCategoriesFromCause(cause) {
2587
- if (!cause || typeof cause !== "object" || !("categories" in cause)) {
2588
- return ["internal_platform"];
2589
+ static getInheritedCauseField(cause, extractor, seen = /* @__PURE__ */ new Set()) {
2590
+ if (!cause || typeof cause !== "object" || seen.has(cause)) {
2591
+ return void 0;
2589
2592
  }
2590
- if (Array.isArray(cause.categories) && cause.categories.length > 0 && cause.categories.every(isAppErrorCategory)) {
2591
- return cause.categories;
2593
+ const causeRecord = cause;
2594
+ const extractedValue = extractor(causeRecord);
2595
+ if (extractedValue !== void 0) {
2596
+ return extractedValue;
2592
2597
  }
2593
- return ["internal_platform"];
2598
+ return _AppError.getInheritedCauseField(causeRecord["cause"], extractor, new Set(seen).add(cause));
2599
+ }
2600
+ static extractCategoriesFromCause(cause) {
2601
+ return _AppError.getInheritedCauseField(cause, (causeRecord) => {
2602
+ const { categories } = causeRecord;
2603
+ if (isNonEmptyAppErrorCategoryArray(categories)) {
2604
+ return categories;
2605
+ }
2606
+ return void 0;
2607
+ }) ?? ["internal_platform"];
2594
2608
  }
2595
2609
  static extractSeverityFromCause(cause) {
2596
- if (cause && typeof cause === "object" && "severity" in cause && isAppErrorSeverity(cause.severity)) {
2597
- return cause.severity;
2598
- }
2599
- return "error";
2610
+ return _AppError.getInheritedCauseField(cause, (causeRecord) => {
2611
+ const { severity } = causeRecord;
2612
+ return isAppErrorSeverity(severity) ? severity : void 0;
2613
+ }) ?? "error";
2600
2614
  }
2601
2615
  static extractStatusCodeFromCause(cause) {
2602
- if (cause && typeof cause === "object" && "statusCode" in cause && isAppErrorCode(cause.statusCode)) {
2603
- return cause.statusCode;
2604
- }
2605
- if (cause && typeof cause === "object" && "code" in cause && isAppErrorCode(cause.code)) {
2606
- return cause.code;
2607
- }
2608
- return void 0;
2616
+ return _AppError.getInheritedCauseField(cause, (causeRecord) => {
2617
+ const { statusCode } = causeRecord;
2618
+ if (isAppErrorCode(statusCode)) {
2619
+ return statusCode;
2620
+ }
2621
+ const { code: legacyCode } = causeRecord;
2622
+ return isAppErrorCode(legacyCode) ? legacyCode : void 0;
2623
+ });
2609
2624
  }
2610
2625
  /** @deprecated Use statusCode instead. */
2611
2626
  get code() {
2612
2627
  return this.statusCode;
2613
2628
  }
2614
2629
  static extractSuggestedActionFromCause(cause) {
2615
- if (cause && typeof cause === "object" && "suggestedAction" in cause && typeof cause.suggestedAction === "string") {
2616
- const trimmedSuggestedAction = cause.suggestedAction.trim();
2617
- if (trimmedSuggestedAction.length > 0) {
2618
- return trimmedSuggestedAction;
2630
+ return _AppError.getInheritedCauseField(cause, (causeRecord) => {
2631
+ const { suggestedAction } = causeRecord;
2632
+ if (typeof suggestedAction !== "string") {
2633
+ return void 0;
2619
2634
  }
2620
- }
2621
- return void 0;
2635
+ const trimmedSuggestedAction = suggestedAction.trim();
2636
+ return trimmedSuggestedAction.length > 0 ? trimmedSuggestedAction : void 0;
2637
+ });
2622
2638
  }
2623
2639
  static applyStack(error, stack, internalFrames) {
2624
2640
  if (stack) {
@@ -3022,10 +3038,28 @@ import { spawn as spawn2 } from "child_process";
3022
3038
  import fs from "fs";
3023
3039
  import path3 from "path";
3024
3040
  import { fileURLToPath } from "url";
3041
+
3042
+ // ../common/dist/src/stdlib/url.utils.js
3043
+ function safeUrlConstructor(url) {
3044
+ try {
3045
+ return Ok(new URL(url));
3046
+ } catch {
3047
+ return Err({
3048
+ type: "invalid-url",
3049
+ message: `Invalid URL`,
3050
+ url
3051
+ });
3052
+ }
3053
+ }
3054
+
3055
+ // src/self-update.ts
3025
3056
  var CLI_PACKAGE_NAME = "@getmagical/cli";
3026
3057
  var DEFAULT_CLI_DIST_TAG2 = "latest";
3027
3058
  var DEFAULT_NPM_REGISTRY_URL = "https://registry.npmjs.org/";
3028
3059
  var MANAGED_NODE_SUBPATH = "runtime/node";
3060
+ function isRecord2(value) {
3061
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3062
+ }
3029
3063
  function resolveInstalledPackageRoot({
3030
3064
  entryFilePath,
3031
3065
  packageName
@@ -3066,6 +3100,154 @@ function resolveInstallPrefixFromPackageRoot(packageRootPath) {
3066
3100
  currentPath = parentPath;
3067
3101
  }
3068
3102
  }
3103
+ function readInstalledPackageVersion({
3104
+ packageName,
3105
+ packageRootPath
3106
+ }) {
3107
+ const packageManifestPath2 = path3.join(packageRootPath, "package.json");
3108
+ if (!fs.existsSync(packageManifestPath2)) {
3109
+ throw new CliError(
3110
+ `Installed package metadata was not found at ${packageManifestPath2}. Reinstall mgcl and try again.`
3111
+ );
3112
+ }
3113
+ const packageManifestResult2 = safeJsonParse(
3114
+ fs.readFileSync(packageManifestPath2, "utf8")
3115
+ );
3116
+ if (isErr(packageManifestResult2)) {
3117
+ throw new CliError(`Failed to parse ${packageManifestPath2}.`, {
3118
+ cause: packageManifestResult2.error.cause
3119
+ });
3120
+ }
3121
+ const packageManifest = packageManifestResult2.value;
3122
+ if (packageManifest.name !== packageName) {
3123
+ throw new CliError(
3124
+ `Expected installed package ${packageName} at ${packageManifestPath2}. Reinstall mgcl and try again.`
3125
+ );
3126
+ }
3127
+ if (typeof packageManifest.version !== "string" || !packageManifest.version) {
3128
+ throw new CliError(
3129
+ `Installed package version is missing from ${packageManifestPath2}. Reinstall mgcl and try again.`
3130
+ );
3131
+ }
3132
+ return packageManifest.version;
3133
+ }
3134
+ function parseVersionSegment(segment, version) {
3135
+ if (!/^\d+$/u.test(segment)) {
3136
+ throw new CliError(`Invalid CLI version "${version}".`);
3137
+ }
3138
+ return Number(segment);
3139
+ }
3140
+ function parseSemanticVersion(version) {
3141
+ const normalizedVersion = version.trim().replace(/^v/u, "");
3142
+ const [versionWithoutBuildMetadata = ""] = normalizedVersion.split("+");
3143
+ if (!versionWithoutBuildMetadata) {
3144
+ throw new CliError(`Invalid CLI version "${version}".`);
3145
+ }
3146
+ const [releasePart, prereleasePart] = versionWithoutBuildMetadata.split("-");
3147
+ if (!releasePart) {
3148
+ throw new CliError(`Invalid CLI version "${version}".`);
3149
+ }
3150
+ return {
3151
+ prerelease: prereleasePart ? prereleasePart.split(".") : null,
3152
+ release: releasePart.split(".").map((segment) => parseVersionSegment(segment, version))
3153
+ };
3154
+ }
3155
+ function comparePrereleaseIdentifiers(left, right) {
3156
+ const leftIsNumeric = /^\d+$/u.test(left);
3157
+ const rightIsNumeric = /^\d+$/u.test(right);
3158
+ if (leftIsNumeric && rightIsNumeric) {
3159
+ return Number(left) - Number(right);
3160
+ }
3161
+ if (leftIsNumeric) {
3162
+ return -1;
3163
+ }
3164
+ if (rightIsNumeric) {
3165
+ return 1;
3166
+ }
3167
+ return left.localeCompare(right);
3168
+ }
3169
+ function compareSemanticVersions(leftVersion, rightVersion) {
3170
+ const left = parseSemanticVersion(leftVersion);
3171
+ const right = parseSemanticVersion(rightVersion);
3172
+ const releaseLength = Math.max(left.release.length, right.release.length);
3173
+ for (let index = 0; index < releaseLength; index += 1) {
3174
+ const difference = (left.release[index] ?? 0) - (right.release[index] ?? 0);
3175
+ if (difference !== 0) {
3176
+ return difference;
3177
+ }
3178
+ }
3179
+ if (!left.prerelease && !right.prerelease) {
3180
+ return 0;
3181
+ }
3182
+ if (!left.prerelease) {
3183
+ return 1;
3184
+ }
3185
+ if (!right.prerelease) {
3186
+ return -1;
3187
+ }
3188
+ const prereleaseLength = Math.max(
3189
+ left.prerelease.length,
3190
+ right.prerelease.length
3191
+ );
3192
+ for (let index = 0; index < prereleaseLength; index += 1) {
3193
+ const leftIdentifier = left.prerelease[index];
3194
+ const rightIdentifier = right.prerelease[index];
3195
+ if (leftIdentifier === void 0) {
3196
+ return -1;
3197
+ }
3198
+ if (rightIdentifier === void 0) {
3199
+ return 1;
3200
+ }
3201
+ const difference = comparePrereleaseIdentifiers(
3202
+ leftIdentifier,
3203
+ rightIdentifier
3204
+ );
3205
+ if (difference !== 0) {
3206
+ return difference;
3207
+ }
3208
+ }
3209
+ return 0;
3210
+ }
3211
+ async function fetchPublishedPackageVersion({
3212
+ distTag,
3213
+ fetchFn = fetch,
3214
+ packageName,
3215
+ registryUrl
3216
+ }) {
3217
+ const distTagUrlResult = safeUrlConstructor(
3218
+ `${registryUrl.replace(/\/?$/u, "/")}-/package/${encodeURIComponent(packageName)}/dist-tags`
3219
+ );
3220
+ if (isErr(distTagUrlResult)) {
3221
+ throw new CliError(`Invalid npm registry URL "${registryUrl}".`);
3222
+ }
3223
+ const response = await fetchFn(distTagUrlResult.value, {
3224
+ headers: {
3225
+ accept: "application/json"
3226
+ }
3227
+ });
3228
+ if (!response.ok) {
3229
+ throw new CliError(
3230
+ `Could not check the latest published mgcl version (status ${response.status}).`
3231
+ );
3232
+ }
3233
+ const distTagsResult = safeJsonParse(await response.text());
3234
+ if (isErr(distTagsResult)) {
3235
+ throw new CliError("Could not parse the npm dist-tags response for mgcl.", {
3236
+ cause: distTagsResult.error.cause
3237
+ });
3238
+ }
3239
+ if (!isRecord2(distTagsResult.value)) {
3240
+ throw new CliError("Could not parse the npm dist-tags response for mgcl.");
3241
+ }
3242
+ const distTags = distTagsResult.value;
3243
+ const publishedVersion = distTags[distTag];
3244
+ if (typeof publishedVersion !== "string" || publishedVersion.length === 0) {
3245
+ throw new CliError(
3246
+ `Could not find npm dist-tag "${distTag}" for ${packageName}.`
3247
+ );
3248
+ }
3249
+ return publishedVersion;
3250
+ }
3069
3251
  function resolveNpmCommandFromPath() {
3070
3252
  return {
3071
3253
  argsPrefix: [],
@@ -3179,18 +3361,44 @@ async function selfUpdateInstalledCli({
3179
3361
  registryUrl
3180
3362
  });
3181
3363
  }
3182
-
3183
- // ../common/dist/src/stdlib/url.utils.js
3184
- function safeUrlConstructor(url) {
3185
- try {
3186
- return Ok(new URL(url));
3187
- } catch {
3188
- return Err({
3189
- type: "invalid-url",
3190
- message: `Invalid URL`,
3191
- url
3192
- });
3364
+ async function getInstalledCliUpdateRequirement({
3365
+ distTag = DEFAULT_CLI_DIST_TAG2,
3366
+ entryFilePath = fileURLToPath(import.meta.url),
3367
+ fetchFn = fetch,
3368
+ packageName = CLI_PACKAGE_NAME,
3369
+ registryUrl = DEFAULT_NPM_REGISTRY_URL
3370
+ } = {}) {
3371
+ const packageRootPath = resolveInstalledPackageRoot({
3372
+ entryFilePath,
3373
+ packageName
3374
+ });
3375
+ if (!packageRootPath) {
3376
+ return null;
3377
+ }
3378
+ const installedVersion = readInstalledPackageVersion({
3379
+ packageName,
3380
+ packageRootPath
3381
+ });
3382
+ const publishedVersionResult = await fromThrowable(
3383
+ async () => fetchPublishedPackageVersion({
3384
+ distTag,
3385
+ fetchFn,
3386
+ packageName,
3387
+ registryUrl
3388
+ }),
3389
+ "cli-update-check-failed"
3390
+ );
3391
+ if (isErr(publishedVersionResult)) {
3392
+ return null;
3193
3393
  }
3394
+ const publishedVersion = publishedVersionResult.value;
3395
+ if (compareSemanticVersions(installedVersion, publishedVersion) >= 0) {
3396
+ return null;
3397
+ }
3398
+ return {
3399
+ installedVersion,
3400
+ latestVersion: publishedVersion
3401
+ };
3194
3402
  }
3195
3403
 
3196
3404
  // src/settings-command.ts
@@ -3343,6 +3551,28 @@ function createFileStateStore(stateFilePath) {
3343
3551
  };
3344
3552
  }
3345
3553
 
3554
+ // src/version.ts
3555
+ import fs2 from "fs";
3556
+ import path5 from "path";
3557
+ import { fileURLToPath as fileURLToPath2 } from "url";
3558
+ var packageManifestPath = path5.resolve(
3559
+ path5.dirname(fileURLToPath2(import.meta.url)),
3560
+ "../package.json"
3561
+ );
3562
+ var packageManifestResult = safeJsonParse(
3563
+ fs2.readFileSync(packageManifestPath, "utf8")
3564
+ );
3565
+ if (isErr(packageManifestResult)) {
3566
+ throw new Error(`Failed to parse ${packageManifestPath}.`, {
3567
+ cause: packageManifestResult.error.cause
3568
+ });
3569
+ }
3570
+ var cliPackageVersion = packageManifestResult.value.version;
3571
+ if (typeof cliPackageVersion !== "string" || cliPackageVersion.length === 0) {
3572
+ throw new Error(`Expected a version in ${packageManifestPath}.`);
3573
+ }
3574
+ var CLI_VERSION = cliPackageVersion;
3575
+
3346
3576
  // src/workos-auth.ts
3347
3577
  import { z as z6 } from "zod";
3348
3578
  var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
@@ -3555,7 +3785,7 @@ function parseCommandOptionValue(rawValue, optionSpec) {
3555
3785
  }
3556
3786
  }
3557
3787
  }
3558
- function isRecord2(value) {
3788
+ function isRecord3(value) {
3559
3789
  return typeof value === "object" && value !== null && !Array.isArray(value);
3560
3790
  }
3561
3791
  function resolveOrganizationBySelector({
@@ -3615,7 +3845,7 @@ function collectOperationVariables(operationSpec, options) {
3615
3845
  parsedVariables.error.issues[0]?.message ?? "Invalid command input."
3616
3846
  );
3617
3847
  }
3618
- if (!isRecord2(parsedVariables.data)) {
3848
+ if (!isRecord3(parsedVariables.data)) {
3619
3849
  throw new CliError("Invalid command input.");
3620
3850
  }
3621
3851
  return parsedVariables.data;
@@ -3718,6 +3948,25 @@ async function handleSelfUpdate(dependencies, options) {
3718
3948
  `Updated mgcl via npm (${updateResult.packageSpecifier}) in ${updateResult.installPrefix}.`
3719
3949
  );
3720
3950
  }
3951
+ function isRootUpdateCommand(program2, actionCommand) {
3952
+ return actionCommand.parent === program2 && actionCommand.name() === "update";
3953
+ }
3954
+ async function enforceLatestCliVersionIfNeeded({
3955
+ dependencies,
3956
+ program: program2,
3957
+ actionCommand
3958
+ }) {
3959
+ if (actionCommand === program2 || isRootUpdateCommand(program2, actionCommand)) {
3960
+ return;
3961
+ }
3962
+ const updateRequirement = await dependencies.ensureLatestCliVersion?.();
3963
+ if (!updateRequirement) {
3964
+ return;
3965
+ }
3966
+ throw new CliError(
3967
+ `mgcl ${updateRequirement.installedVersion} is outdated. Upgrade to ${updateRequirement.latestVersion} with "mgcl update", then rerun your command.`
3968
+ );
3969
+ }
3721
3970
  async function handleOperationCommand(dependencies, operationSpec, commandOptions) {
3722
3971
  const state = requireLoggedInState(await dependencies.stateStore.load());
3723
3972
  const organization = resolveSelectedOrganization({
@@ -3862,6 +4111,7 @@ function createDefaultDependencies() {
3862
4111
  refreshTokenStore: createKeytarRefreshTokenStore(
3863
4112
  runtimeConfig.keychainServiceName
3864
4113
  ),
4114
+ ensureLatestCliVersion: async () => getInstalledCliUpdateRequirement(selfUpdateOptions),
3865
4115
  runtimeConfig,
3866
4116
  settingsStore,
3867
4117
  selfUpdateCli: async () => selfUpdateInstalledCli(selfUpdateOptions),
@@ -3871,7 +4121,7 @@ function createDefaultDependencies() {
3871
4121
  function buildProgram(dependencies) {
3872
4122
  const program2 = configureCommandPresentation(
3873
4123
  new Command2().name("mgcl").description("Run Magical operations from the command line.")
3874
- ).addOption(new Option2("--json", "Render JSON output.")).addOption(
4124
+ ).addOption(new Option2("--json", "Render JSON output.")).version(CLI_VERSION, "--version", `Display mgcl version (${CLI_VERSION}).`).addOption(
3875
4125
  new Option2(
3876
4126
  "--update",
3877
4127
  "Update mgcl to the latest published npm release."
@@ -3897,6 +4147,13 @@ ${formatOutputModesNote()}`
3897
4147
  }
3898
4148
  this.outputHelp();
3899
4149
  });
4150
+ program2.hook("preAction", async (_command, actionCommand) => {
4151
+ await enforceLatestCliVersionIfNeeded({
4152
+ actionCommand,
4153
+ dependencies,
4154
+ program: program2
4155
+ });
4156
+ });
3900
4157
  const authCommand = configureCommandPresentation(
3901
4158
  new Command2("auth").description("Manage CLI authentication.")
3902
4159
  ).helpGroup("Workspace:").addHelpText(