@apso/cli 0.47.0 → 0.47.2

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.
@@ -100,7 +100,7 @@ class Deploy extends base_command_1.default {
100
100
  }
101
101
  }
102
102
  // Trigger build
103
- const build = await (0, upgrade_1.withUpgradeRetry)(() => services_1.buildApi.trigger(link.workspaceSlug, link.serviceSlug, env));
103
+ const build = await (0, upgrade_1.withUpgradeRetry)(() => services_1.buildApi.trigger(link.workspaceSlug, link.serviceSlug, env), { workspaceSlug: link.workspaceSlug, serviceId: service.id });
104
104
  this.log(`Build started: ${build.id}`);
105
105
  if (flags["no-wait"]) {
106
106
  this.log(`Deploy triggered. Run 'apso status --env ${env}' to check progress.`);
@@ -5,6 +5,7 @@ const core_1 = require("@oclif/core");
5
5
  const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
6
6
  const services_1 = require("../../lib/api/services");
7
7
  const environments_1 = require("../../lib/environments");
8
+ const upgrade_1 = require("../../lib/upgrade");
8
9
  class EnvAdd extends base_command_1.default {
9
10
  async run() {
10
11
  const { flags } = await this.parse(EnvAdd);
@@ -16,7 +17,7 @@ class EnvAdd extends base_command_1.default {
16
17
  const ok = await (0, environments_1.confirmOrExit)(this, flags.yes, `Add staging to "${service.name}"? This starts the paid staging add-on.`);
17
18
  if (!ok)
18
19
  return this.log("Cancelled.");
19
- await services_1.envApi.addStaging(service.id);
20
+ await (0, upgrade_1.withUpgradeRetry)(() => services_1.envApi.addStaging(service.id), (0, environments_1.pageContext)(service));
20
21
  this.log(`Staging is being created for "${service.name}". Deploy to it with 'apso deploy --env staging'.`);
21
22
  }
22
23
  }
@@ -15,6 +15,10 @@ class EnvList extends base_command_1.default {
15
15
  this.log(JSON.stringify(envs, null, 2));
16
16
  return;
17
17
  }
18
+ // Production's row appears with the first deploy; until then say so rather than print nothing.
19
+ if (!envs.some((e) => e.environment === "production")) {
20
+ this.log(`${"production".padEnd(12)}${"not deployed yet".padEnd(22)}-`);
21
+ }
18
22
  for (const e of envs) {
19
23
  this.log(`${e.environment.padEnd(12)}${(e.buildStatus || e.status || "-").padEnd(22)}${(_a = e.url) !== null && _a !== void 0 ? _a : "-"}`);
20
24
  }
@@ -152,6 +152,7 @@ class Generate extends base_command_1.default {
152
152
  relationships: entityRelationships,
153
153
  allEntities: entities,
154
154
  apiType: lowerCaseApiType,
155
+ auth,
155
156
  }),
156
157
  generator.generateService({
157
158
  entity,
@@ -368,6 +368,7 @@ class McpServe extends base_command_1.default {
368
368
  relationships: entityRelationships,
369
369
  allEntities: entities,
370
370
  apiType: lowerCaseApiType,
371
+ auth,
371
372
  }),
372
373
  generator.generateService({
373
374
  entity,
@@ -36,7 +36,7 @@ class Open extends base_command_1.default {
36
36
  }
37
37
  else {
38
38
  // Open the dashboard (default)
39
- const dashboardUrl = `${config.webUrl}/workspaces/${link.workspaceSlug}/services/${link.serviceSlug}`;
39
+ const dashboardUrl = `${config.webUrl}/${link.workspaceSlug}/services/${link.serviceId}/dashboard`;
40
40
  this.log(`Opening dashboard: ${dashboardUrl}`);
41
41
  openUrl(dashboardUrl);
42
42
  }
@@ -6,6 +6,7 @@ const core_1 = require("@oclif/core");
6
6
  const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
7
7
  const services_1 = require("../lib/api/services");
8
8
  const environments_1 = require("../lib/environments");
9
+ const upgrade_1 = require("../lib/upgrade");
9
10
  const spinner_1 = require("../lib/utils/spinner");
10
11
  class Promote extends base_command_1.default {
11
12
  async run() {
@@ -23,7 +24,7 @@ class Promote extends base_command_1.default {
23
24
  const ok = await (0, environments_1.confirmOrExit)(this, flags.yes, `Promote staging to production for "${service.name}"?`);
24
25
  if (!ok)
25
26
  return this.log("Cancelled.");
26
- await services_1.envApi.promote(service.id);
27
+ await (0, upgrade_1.withUpgradeRetry)(() => services_1.envApi.promote(service.id), (0, environments_1.pageContext)(service));
27
28
  if (flags["no-wait"]) {
28
29
  this.log("Promote started. Run 'apso status' to check progress.");
29
30
  return;
@@ -9,6 +9,12 @@
9
9
  * - Timeout handling
10
10
  */
11
11
  import { ApiRequestOptions } from "./types";
12
+ /**
13
+ * Refusals the user resolves in the web app, and where: "service" is the
14
+ * service's Deployments page (size and staging), "billing" the workspace's
15
+ * Billing page.
16
+ */
17
+ export declare const APP_FIX_PAGES: Record<string, "service" | "billing">;
12
18
  /**
13
19
  * Custom error class for API errors
14
20
  */
@@ -10,7 +10,7 @@
10
10
  * - Timeout handling
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.api = exports.apiRequest = exports.checkNetworkConnectivity = exports.ApiClientError = void 0;
13
+ exports.api = exports.apiRequest = exports.checkNetworkConnectivity = exports.ApiClientError = exports.APP_FIX_PAGES = void 0;
14
14
  const config_1 = require("../config");
15
15
  /**
16
16
  * Default request timeout in milliseconds
@@ -24,6 +24,17 @@ const MAX_RETRIES = 3;
24
24
  * Base delay for exponential backoff in milliseconds
25
25
  */
26
26
  const BACKOFF_BASE_DELAY = 1000;
27
+ /**
28
+ * Refusals the user resolves in the web app, and where: "service" is the
29
+ * service's Deployments page (size and staging), "billing" the workspace's
30
+ * Billing page.
31
+ */
32
+ exports.APP_FIX_PAGES = {
33
+ STAGING_REQUIRES_PAID_SERVICE: "service",
34
+ STAGING_ADDON_REQUIRED: "service",
35
+ STAGING_ADDON_PURCHASE_FAILED: "service",
36
+ SERVICE_SUSPENDED: "billing",
37
+ };
27
38
  /**
28
39
  * Custom error class for API errors
29
40
  */
@@ -45,7 +56,9 @@ class ApiClientError extends Error {
45
56
  * upgrading (e.g. the free-tier service or deploy limit).
46
57
  */
47
58
  isLimitError() {
48
- return (this.statusCode === 403 &&
59
+ if (this.code && this.code in exports.APP_FIX_PAGES)
60
+ return true;
61
+ return ((this.statusCode === 403 || this.statusCode === 402) &&
49
62
  (this.code === "SERVICE_LIMIT_REACHED" ||
50
63
  this.code === "LIMIT_REACHED" ||
51
64
  Boolean(this.upgradeUrl)));
@@ -306,7 +319,12 @@ async function apiRequest(path, options = {}) {
306
319
  }
307
320
  // Client errors - don't retry
308
321
  const limit = extractLimitInfo(errorData);
309
- const clientError = new ApiClientError(limit.message || errorData.message || "Request failed", response.status, errorData.details);
322
+ // The app's API routes put their sentence in `.error`; the backend uses `.message`.
323
+ const appError = errorData.error;
324
+ const clientError = new ApiClientError(limit.message ||
325
+ errorData.message ||
326
+ (typeof appError === "string" ? appError : undefined) ||
327
+ "Request failed", response.status, errorData.details);
310
328
  clientError.code = limit.code;
311
329
  clientError.upgradeUrl = limit.upgradeUrl;
312
330
  throw clientError;
@@ -1,7 +1,9 @@
1
1
  import type { Command } from "@oclif/core";
2
2
  import { type ServiceEnv } from "./api/services";
3
3
  import type { Service } from "./api/types";
4
+ import type { PageContext } from "./upgrade";
4
5
  export declare const envFlag: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
5
6
  export declare const asEnv: (value: string | undefined) => ServiceEnv;
6
7
  export declare function linkedService(cmd: Command): Promise<Service>;
8
+ export declare function pageContext(service: Service): PageContext;
7
9
  export declare function confirmOrExit(cmd: Command, yes: boolean, question: string): Promise<boolean>;
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.confirmOrExit = exports.linkedService = exports.asEnv = exports.envFlag = void 0;
26
+ exports.confirmOrExit = exports.pageContext = exports.linkedService = exports.asEnv = exports.envFlag = void 0;
27
27
  const core_1 = require("@oclif/core");
28
28
  const config_1 = require("./config");
29
29
  const services_1 = require("./api/services");
@@ -46,6 +46,12 @@ async function linkedService(cmd) {
46
46
  return services_1.servicesApi.get(link.workspaceId, link.serviceSlug);
47
47
  }
48
48
  exports.linkedService = linkedService;
49
+ // Where the linked service lives in the web app, for links to the page that fixes a refusal.
50
+ function pageContext(service) {
51
+ var _a;
52
+ return { workspaceSlug: (_a = config_1.projectLink.read()) === null || _a === void 0 ? void 0 : _a.workspaceSlug, serviceId: service.id };
53
+ }
54
+ exports.pageContext = pageContext;
49
55
  // Asks before a step that costs money or replaces something; headless runs must pass --yes.
50
56
  async function confirmOrExit(cmd, yes, question) {
51
57
  if (yes)
@@ -244,8 +244,10 @@ class GoGenerator extends base_1.BaseGenerator {
244
244
  ];
245
245
  }
246
246
  async generateDto(options) {
247
+ var _a;
247
248
  const { entity, relationships, allEntities, apiType } = options;
248
249
  const { name: entityName, fields = [] } = entity;
250
+ const scopeFields = new Set((_a = (0, guards_1.entityScope)(entity, Boolean(options.auth))) === null || _a === void 0 ? void 0 : _a.fields.map((f) => f.field));
249
251
  const columns = this.getGoFieldsForTemplate(fields, entityName);
250
252
  const relationshipsTemplate = (0, relationships_1.getRelationshipForTemplate)(entityName, relationships, allEntities);
251
253
  // Add Go-specific properties to relationships
@@ -258,6 +260,8 @@ class GoGenerator extends base_1.BaseGenerator {
258
260
  camelReferenceName: (0, casing_1.camelCase)(rel.referenceName || rel.name),
259
261
  pluralName: (0, casing_1.pascalCase)((0, pluralize_1.default)(rel.name)),
260
262
  camelPluralName: (0, casing_1.camelCase)((0, pluralize_1.default)(rel.name)),
263
+ // The service sets a scopeBy foreign key from the caller on create.
264
+ scoped: scopeFields.has(`${(0, casing_1.camelCase)(rel.referenceName || rel.name)}Id`),
261
265
  };
262
266
  });
263
267
  const primaryKeyColumns = columns.filter((column) => column.primary === true);
@@ -109,7 +109,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
109
109
  ];
110
110
  }
111
111
  async generateController(options) {
112
- var _a, _b;
112
+ var _a;
113
113
  const { entity, relationships, relationshipMap, auth } = options;
114
114
  const { name: entityName } = entity;
115
115
  const relationshipsForTemplate = (0, relationships_1.getRelationshipForTemplate)(entityName, relationships);
@@ -128,9 +128,22 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
128
128
  const ctrlName = `${entityName}Controller`;
129
129
  const pluralEntityName = (0, pluralize_1.default)(entityName);
130
130
  const primaryKeyType = entity.primaryKeyType || "serial";
131
- const scopes = auth
132
- ? (_b = (_a = (0, guards_1.getScopedEntities)([entity], relationshipMap)[0]) === null || _a === void 0 ? void 0 : _a.scopes) !== null && _b !== void 0 ? _b : []
133
- : [];
131
+ const scoped = auth
132
+ ? (0, guards_1.getScopedEntities)([entity], relationshipMap)[0]
133
+ : undefined;
134
+ const scopes = (_a = scoped === null || scoped === void 0 ? void 0 : scoped.scopes) !== null && _a !== void 0 ? _a : [];
135
+ // Only emitted when scopeOptions differ from the defaults, so services
136
+ // without them keep the same generated controller.
137
+ const scopeOptions = scoped &&
138
+ (scoped.bypassRoles.length > 0 ||
139
+ !scoped.injectOnCreate ||
140
+ ["find", "get", "create", "update", "delete"].some((op) => !scoped.enforceOn.includes(op)))
141
+ ? {
142
+ enforceOn: scoped.enforceOn,
143
+ bypassRoles: scoped.bypassRoles,
144
+ injectOnCreate: scoped.injectOnCreate,
145
+ }
146
+ : undefined;
134
147
  const data = {
135
148
  svcName,
136
149
  ctrlName,
@@ -145,6 +158,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
145
158
  // ids ScopeGuard loaded for the caller into req.apsoScope.
146
159
  crudAuth: scopes.filter((s) => s.direct),
147
160
  crudAuthNested: scopes.filter((s) => s.fkField),
161
+ scopeOptions,
148
162
  };
149
163
  const controllerContent = await this.renderTemplate("./rest/controller-rest", data);
150
164
  const specContent = await this.renderTemplate("./rest/controller-rest-spec", data);
@@ -51,7 +51,7 @@ type <%= it.entityName %>Create struct {
51
51
  <% }); -%>
52
52
  <% it.associations.forEach((assoc) => { -%>
53
53
  <% if (assoc.type === 'ManyToOne' || (assoc.type === 'OneToOne' && assoc.isOwner)) { -%>
54
- <%= assoc.referenceName || assoc.name %>ID <%= assoc.primaryKeyGoType || 'uint' %> `json:"<%= assoc.camelReferenceName || assoc.camelName %>Id" validate:"required"`
54
+ <%= assoc.referenceName || assoc.name %>ID <%= assoc.primaryKeyGoType || 'uint' %> `json:"<%= assoc.camelReferenceName || assoc.camelName %>Id"<% if (!assoc.scoped) { %> validate:"required"<% } %>`
55
55
  <% } -%>
56
56
  <% }) -%>
57
57
  }
@@ -168,7 +168,7 @@ export class ScopeGuard implements CanActivate {
168
168
  }
169
169
 
170
170
  // Check if user has bypass role
171
- const userRoles: string[] = request.user?.roles || request.roles || [];
171
+ const userRoles: string[] = request.auth?.roles ?? request.user?.roles ?? request.roles ?? [];
172
172
  if (scopeConfig.bypassRoles.some(role => userRoles.includes(role))) {
173
173
  return true;
174
174
  }
@@ -15,6 +15,26 @@ const nestedScope = (req: any, fk: string) => {
15
15
  return { [fk]: ids.length ? { $in: ids } : { $isnull: true, $notnull: true } };
16
16
  };
17
17
  <% } %>
18
+ <% if (it.scopeOptions && (it.crudAuth.length || it.crudAuthNested.length)) { %>
19
+ // .apsorc scopeOptions: which requests the tenant filter covers and which
20
+ // roles skip it.
21
+ const ENFORCE_ON = [<%~ it.scopeOptions.enforceOn.map((op) => `'${op}'`).join(', ') %>];
22
+ const BYPASS_ROLES: string[] = [<%~ it.scopeOptions.bypassRoles.map((r) => `'${r}'`).join(', ') %>];
23
+ const scopeOperation = (req: any) => {
24
+ switch (req.method) {
25
+ case 'GET': return req.params?.id ? 'get' : 'find';
26
+ case 'POST': return 'create';
27
+ case 'PUT':
28
+ case 'PATCH': return 'update';
29
+ case 'DELETE': return 'delete';
30
+ default: return 'find';
31
+ }
32
+ };
33
+ const skipScope = (req: any) => {
34
+ const roles: string[] = req.auth?.roles ?? req.user?.roles ?? [];
35
+ return roles.some((r) => BYPASS_ROLES.includes(r)) || !ENFORCE_ON.includes(scopeOperation(req));
36
+ };
37
+ <% } %>
18
38
  @Crud({
19
39
  model: {
20
40
  type: <%= it.entityName %>
@@ -63,7 +83,7 @@ const nestedScope = (req: any, fk: string) => {
63
83
  <% if (it.crudAuth.length || it.crudAuthNested.length) { %>
64
84
  // Every query and write is limited to the caller's tenant (.apsorc scopeBy).
65
85
  @CrudAuth({
66
- filter: (req: any) => ({
86
+ filter: (req: any) => <% if (it.scopeOptions) { %>skipScope(req) ? {} : <% } %>({
67
87
  <% it.crudAuth.forEach((s) => { %>
68
88
  <%= s.field %>: req.<%= s.contextKey %> ?? req.user?.<%= s.contextKey %> ?? req.auth?.<%= s.contextKey %> ?? req.auth?.workspaceId,
69
89
  <% }) %>
@@ -71,7 +91,7 @@ const nestedScope = (req: any, fk: string) => {
71
91
  ...nestedScope(req, '<%= s.fkField %>'),
72
92
  <% }) %>
73
93
  }),
74
- persist: (req: any) => ({
94
+ persist: (req: any) => <% if (it.scopeOptions) { %>skipScope(req)<% if (!it.scopeOptions.injectOnCreate) { %> || scopeOperation(req) === 'create'<% } %> ? {} : <% } %>({
75
95
  <% it.crudAuth.forEach((s) => { %>
76
96
  <%= s.field %>: req.<%= s.contextKey %> ?? req.user?.<%= s.contextKey %> ?? req.auth?.<%= s.contextKey %> ?? req.auth?.workspaceId,
77
97
  <% }) %>
@@ -181,6 +181,8 @@ export interface DtoGenerationOptions extends EntityGenerationOptions {
181
181
  * Include validation decorators/rules
182
182
  */
183
183
  includeValidation?: boolean;
184
+ /** With scopeBy, the service sets the tenant field, so create doesn't require it. */
185
+ auth?: AuthConfig;
184
186
  }
185
187
  /**
186
188
  * Options for generating migrations
@@ -7,13 +7,23 @@
7
7
  * "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
8
8
  */
9
9
  import { ApiClientError } from "./api/client";
10
+ /** Where the user is when the refusal happened, so the link can go to the exact page. */
11
+ export interface PageContext {
12
+ workspaceSlug?: string;
13
+ serviceId?: string;
14
+ }
15
+ /** The web app page that resolves this refusal, and how to name it in a prompt. */
16
+ export declare function fixPage(error: ApiClientError, ctx?: PageContext): {
17
+ url: string;
18
+ label: string;
19
+ };
10
20
  /**
11
- * Prompt the user to upgrade after a limit error, open the upgrade page, and
12
- * wait for them to finish. Returns true if the caller should retry the action.
21
+ * Prompt the user to resolve a plan or billing refusal in the web app, open the
22
+ * page, and wait for them to finish. Returns true if the caller should retry.
13
23
  */
14
- export declare function promptUpgrade(error: ApiClientError): Promise<boolean>;
24
+ export declare function promptUpgrade(error: ApiClientError, ctx?: PageContext): Promise<boolean>;
15
25
  /**
16
26
  * Run an action; if it fails with an upgradeable plan-limit error, offer to
17
27
  * upgrade and retry once.
18
28
  */
19
- export declare function withUpgradeRetry<T>(action: () => Promise<T>): Promise<T>;
29
+ export declare function withUpgradeRetry<T>(action: () => Promise<T>, ctx?: PageContext): Promise<T>;
@@ -8,7 +8,7 @@
8
8
  * "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.withUpgradeRetry = exports.promptUpgrade = void 0;
11
+ exports.withUpgradeRetry = exports.promptUpgrade = exports.fixPage = void 0;
12
12
  const tslib_1 = require("tslib");
13
13
  const child_process_1 = require("child_process");
14
14
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
@@ -29,13 +29,31 @@ function openBrowser(url) {
29
29
  (0, child_process_1.exec)(command, () => resolve());
30
30
  });
31
31
  }
32
+ /** The web app page that resolves this refusal, and how to name it in a prompt. */
33
+ function fixPage(error, ctx = {}) {
34
+ const web = config_1.globalConfig.read().webUrl;
35
+ const where = error.code ? client_1.APP_FIX_PAGES[error.code] : undefined;
36
+ if (where === "service" && ctx.workspaceSlug && ctx.serviceId) {
37
+ return {
38
+ url: `${web}/${ctx.workspaceSlug}/services/${ctx.serviceId}/deployments`,
39
+ label: "the service's Deployments page",
40
+ };
41
+ }
42
+ if (where === "billing" && ctx.workspaceSlug) {
43
+ return { url: `${web}/${ctx.workspaceSlug}/billing`, label: "Billing" };
44
+ }
45
+ return {
46
+ url: `${web}${error.upgradeUrl || "/billing/upgrade"}`,
47
+ label: "the upgrade page",
48
+ };
49
+ }
50
+ exports.fixPage = fixPage;
32
51
  /**
33
- * Prompt the user to upgrade after a limit error, open the upgrade page, and
34
- * wait for them to finish. Returns true if the caller should retry the action.
52
+ * Prompt the user to resolve a plan or billing refusal in the web app, open the
53
+ * page, and wait for them to finish. Returns true if the caller should retry.
35
54
  */
36
- async function promptUpgrade(error) {
37
- const config = config_1.globalConfig.read();
38
- const upgradeUrl = `${config.webUrl}${error.upgradeUrl || "/billing/upgrade"}`;
55
+ async function promptUpgrade(error, ctx = {}) {
56
+ const { url: upgradeUrl, label } = fixPage(error, ctx);
39
57
  console.log("");
40
58
  console.log(` ⚠ ${error.message || "You've reached a plan limit."}`);
41
59
  console.log("");
@@ -43,12 +61,12 @@ async function promptUpgrade(error) {
43
61
  {
44
62
  type: "confirm",
45
63
  name: "proceed",
46
- message: "Open the upgrade page in your browser?",
64
+ message: `Open ${label} in your browser?`,
47
65
  default: true,
48
66
  },
49
67
  ]);
50
68
  if (!proceed) {
51
- console.log(` You can upgrade any time at: ${upgradeUrl}`);
69
+ console.log(` You can do this any time at: ${upgradeUrl}`);
52
70
  return false;
53
71
  }
54
72
  console.log("");
@@ -59,7 +77,7 @@ async function promptUpgrade(error) {
59
77
  {
60
78
  type: "confirm",
61
79
  name: "done",
62
- message: "Press enter once your upgrade is complete to retry",
80
+ message: "Press enter once you are done there to retry",
63
81
  default: true,
64
82
  },
65
83
  ]);
@@ -70,7 +88,7 @@ exports.promptUpgrade = promptUpgrade;
70
88
  * Run an action; if it fails with an upgradeable plan-limit error, offer to
71
89
  * upgrade and retry once.
72
90
  */
73
- async function withUpgradeRetry(action) {
91
+ async function withUpgradeRetry(action, ctx = {}) {
74
92
  try {
75
93
  return await action();
76
94
  }
@@ -79,10 +97,10 @@ async function withUpgradeRetry(action) {
79
97
  // Headless (agent/CI): never open a browser or prompt — surface the limit
80
98
  // as a clear error with the upgrade URL so the caller can act on it.
81
99
  if (!(0, interactive_1.isInteractive)()) {
82
- const upgradeUrl = `${config_1.globalConfig.read().webUrl}${error.upgradeUrl || "/billing/upgrade"}`;
83
- throw new Error(`${error.message || "Plan limit reached."} Upgrade at ${upgradeUrl}`);
100
+ const { url } = fixPage(error, ctx);
101
+ throw new Error(`${(error.message || "Plan limit reached").replace(/\.?$/, ".")} Resolve it at ${url}`);
84
102
  }
85
- const retry = await promptUpgrade(error);
103
+ const retry = await promptUpgrade(error, ctx);
86
104
  if (retry) {
87
105
  return action();
88
106
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.47.0",
3
+ "version": "0.47.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.47.0",
9
+ "version": "0.47.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@apso/schema-tools": "^0.2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.47.0",
2
+ "version": "0.47.2",
3
3
  "commands": {
4
4
  "config": {
5
5
  "id": "config",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.47.0",
3
+ "version": "0.47.2",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",