@apso/cli 0.26.2 → 0.28.0

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.
@@ -9,6 +9,7 @@ export default class Deploy extends BaseCommand {
9
9
  "skip-push": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
10
  repo: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
11
11
  branch: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
12
+ "local-git": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
13
  };
13
14
  run(): Promise<void>;
14
15
  /**
@@ -18,6 +19,11 @@ export default class Deploy extends BaseCommand {
18
19
  * git and push straight to GitHub.
19
20
  */
20
21
  private syncCodeToGithub;
21
- /** Let the user pick one of their GitHub repos to deploy from. */
22
- private promptRepoSelection;
22
+ /**
23
+ * Decide which GitHub repo to deploy to. No local git or `gh` needed to make
24
+ * one — we create it through the user's Apso GitHub connection. Headless (or
25
+ * when the account has no repos) auto-creates a repo named after the service;
26
+ * interactive offers "create new" first, then existing repos.
27
+ */
28
+ private resolveRepo;
23
29
  }
@@ -11,6 +11,7 @@ const apsorc_parser_1 = require("../lib/apsorc-parser");
11
11
  const sandbox_1 = require("../lib/migrate/sandbox");
12
12
  const spinner_1 = require("../lib/utils/spinner");
13
13
  const interactive_1 = require("../lib/utils/interactive");
14
+ const server_push_1 = require("../lib/deploy/server-push");
14
15
  class Deploy extends base_command_1.default {
15
16
  async run() {
16
17
  const { flags } = await this.parse(Deploy);
@@ -68,7 +69,7 @@ class Deploy extends base_command_1.default {
68
69
  // Sync local code to the connected GitHub repo (the deploy builds from it).
69
70
  // Skip for redeploys of already-synced code.
70
71
  if (!flags["skip-push"]) {
71
- await this.syncCodeToGithub(service, flags.repo, flags.branch);
72
+ await this.syncCodeToGithub(service, flags.repo, flags.branch, flags["local-git"]);
72
73
  }
73
74
  // Determine source
74
75
  if (service.githubRepo || flags.repo) {
@@ -132,12 +133,16 @@ class Deploy extends base_command_1.default {
132
133
  * can clone it at deploy time. No code passes through Apso — we read the local
133
134
  * git and push straight to GitHub.
134
135
  */
135
- async syncCodeToGithub(service, repoFlag, branch) {
136
+ async syncCodeToGithub(service, repoFlag, branch, useLocalGit) {
136
137
  const cwd = process.cwd();
137
- if (!git.gitAvailable()) {
138
- this.error("git is required to deploy. Install git and try again.");
138
+ // --local-git needs git + a local repo. The default server-side push reads
139
+ // the files directly and pushes through the platform, so neither is needed.
140
+ if (useLocalGit) {
141
+ if (!git.gitAvailable()) {
142
+ this.error("git is required for --local-git. Install git, or drop --local-git.");
143
+ }
144
+ git.initRepo(cwd);
139
145
  }
140
- git.initRepo(cwd);
141
146
  // Pick the repo: prefer what the local project is already wired to, then the
142
147
  // service's connected repo, then --repo, then ask.
143
148
  let conn = await services_1.githubApi.getConnection();
@@ -166,9 +171,12 @@ class Deploy extends base_command_1.default {
166
171
  this.error("GitHub still isn't connected. Complete 'apso github connect' and re-run deploy.");
167
172
  }
168
173
  }
169
- let repoFullName = git.localRepoFullName(cwd) || service.githubRepo || repoFlag;
174
+ // Only read the local git remote when git is present (a bare machine using
175
+ // the server-side push may not have git at all).
176
+ const localRepo = git.gitAvailable() ? git.localRepoFullName(cwd) : "";
177
+ let repoFullName = localRepo || service.githubRepo || repoFlag;
170
178
  if (!repoFullName) {
171
- repoFullName = await this.promptRepoSelection(conn.connectionId);
179
+ repoFullName = await this.resolveRepo(conn.connectionId, service);
172
180
  }
173
181
  // Connect the repo to the service (records ServiceRepository) unless the
174
182
  // service is already linked to this exact repo.
@@ -176,35 +184,84 @@ class Deploy extends base_command_1.default {
176
184
  this.log(`Connecting repository ${repoFullName}...`);
177
185
  await services_1.githubApi.connectRepo(service.id, conn.connectionId, repoFullName);
178
186
  }
179
- // Point the local repo at it and push with the user's git credentials.
180
- git.setOrigin(cwd, git.httpsUrlFor(repoFullName));
181
- const committed = git.commitAll(cwd, "Deploy via Apso CLI");
182
- this.log(committed
183
- ? `Committed local changes; pushing to ${repoFullName}#${branch}...`
184
- : `Pushing to ${repoFullName}#${branch}...`);
185
- try {
186
- git.pushHead(cwd, branch);
187
+ if (useLocalGit) {
188
+ // Push with the user's local git credentials.
189
+ git.setOrigin(cwd, git.httpsUrlFor(repoFullName));
190
+ const committed = git.commitAll(cwd, "Deploy via Apso CLI");
191
+ this.log(committed
192
+ ? `Committed local changes; pushing to ${repoFullName}#${branch}...`
193
+ : `Pushing to ${repoFullName}#${branch}...`);
194
+ try {
195
+ git.pushHead(cwd, branch);
196
+ }
197
+ catch (error) {
198
+ const msg = error instanceof Error ? error.message : String(error);
199
+ this.error(`git push to ${repoFullName} failed. Ensure you have push access and your git credentials are set up.\n${msg}`);
200
+ }
187
201
  }
188
- catch (error) {
189
- const msg = error instanceof Error ? error.message : String(error);
190
- this.error(`git push to ${repoFullName} failed. Ensure you have push access and your git credentials are set up.\n${msg}`);
202
+ else {
203
+ // Default: upload the project and push through the Apso GitHub connection.
204
+ // No local git or gh required.
205
+ this.log(`Uploading code and pushing to ${repoFullName}#${branch} via your Apso GitHub connection...`);
206
+ try {
207
+ await (0, server_push_1.serverSidePush)({
208
+ cwd,
209
+ serviceId: service.id,
210
+ connectionId: conn.connectionId,
211
+ branch,
212
+ message: "Deploy via Apso CLI",
213
+ });
214
+ this.log(`Pushed to ${repoFullName}#${branch}.`);
215
+ }
216
+ catch (error) {
217
+ const msg = error instanceof Error ? error.message : String(error);
218
+ this.error(`Push failed: ${msg}\nRetry, or use --local-git to push with your local git instead.`);
219
+ }
191
220
  }
192
221
  }
193
- /** Let the user pick one of their GitHub repos to deploy from. */
194
- async promptRepoSelection(connectionId) {
195
- const repos = await services_1.githubApi.listRepos("", { connectionId, pageSize: 100 });
196
- if (!repos.data.length) {
197
- this.error("No GitHub repositories available. Create one on GitHub (or pass --repo owner/name).");
222
+ /**
223
+ * Decide which GitHub repo to deploy to. No local git or `gh` needed to make
224
+ * one — we create it through the user's Apso GitHub connection. Headless (or
225
+ * when the account has no repos) auto-creates a repo named after the service;
226
+ * interactive offers "create new" first, then existing repos.
227
+ */
228
+ async resolveRepo(connectionId, service) {
229
+ const repoName = (service.name || service.slug).trim();
230
+ const repos = await services_1.githubApi
231
+ .listRepos("", { connectionId, pageSize: 100 })
232
+ .catch(() => ({ data: [] }));
233
+ // Headless, or nothing to pick from: create a repo for this service.
234
+ if (!(0, interactive_1.isInteractive)() || repos.data.length === 0) {
235
+ this.log(`Creating GitHub repository "${repoName}"...`);
236
+ const created = await services_1.githubApi.createRepo(connectionId, repoName, {
237
+ autoInit: false,
238
+ });
239
+ this.log(`Created ${created.fullName}`);
240
+ return created.fullName;
198
241
  }
242
+ // Interactive with existing repos: "create new" first, then the list.
199
243
  const inquirer = await Promise.resolve().then(() => tslib_1.__importStar(require("inquirer")));
244
+ const CREATE = "create";
200
245
  const { repo } = await inquirer.default.prompt([
201
246
  {
202
247
  type: "list",
203
248
  name: "repo",
204
- message: "Select a GitHub repository to deploy from:",
205
- choices: repos.data.map((r) => ({ name: r.fullName, value: r.fullName })),
249
+ message: "Deploy to which GitHub repository?",
250
+ choices: [
251
+ { name: `+ Create new repo "${repoName}"`, value: CREATE },
252
+ new inquirer.default.Separator(),
253
+ ...repos.data.map((r) => ({ name: r.fullName, value: r.fullName })),
254
+ ],
206
255
  },
207
256
  ]);
257
+ if (repo === CREATE) {
258
+ this.log(`Creating GitHub repository "${repoName}"...`);
259
+ const created = await services_1.githubApi.createRepo(connectionId, repoName, {
260
+ autoInit: false,
261
+ });
262
+ this.log(`Created ${created.fullName}`);
263
+ return created.fullName;
264
+ }
208
265
  return repo;
209
266
  }
210
267
  }
@@ -240,5 +297,9 @@ Deploy.flags = {
240
297
  description: "Branch to push to",
241
298
  default: "main",
242
299
  }),
300
+ "local-git": core_1.Flags.boolean({
301
+ description: "Push using your local git instead of the platform (needs local GitHub auth)",
302
+ default: false,
303
+ }),
243
304
  };
244
305
  exports.default = Deploy;
@@ -6,6 +6,8 @@ export default class McpServe extends BaseCommand {
6
6
  help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
7
7
  };
8
8
  run(): Promise<void>;
9
+ /** Wrap every registered tool handler with usage telemetry. */
10
+ private instrumentTools;
9
11
  private registerTools;
10
12
  private registerResources;
11
13
  }
@@ -10,6 +10,7 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
10
10
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
11
11
  const zod_1 = require("zod");
12
12
  const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
13
+ const telemetry_1 = require("../../lib/telemetry/telemetry");
13
14
  const apsorc_parser_1 = require("../../lib/apsorc-parser");
14
15
  const lib_1 = require("../../lib");
15
16
  const runner_1 = require("../../lib/doctor/runner");
@@ -23,11 +24,50 @@ class McpServe extends base_command_1.default {
23
24
  name: "apso",
24
25
  version: "0.10.2",
25
26
  });
27
+ // Emit a PostHog event per MCP tool call so agent usage of the server shows
28
+ // up in the same analytics as the CLI. Wrapping server.tool once covers all
29
+ // tools without touching each handler.
30
+ this.instrumentTools(server);
26
31
  this.registerTools(server);
27
32
  this.registerResources(server);
28
33
  const transport = new stdio_js_1.StdioServerTransport();
29
34
  await server.connect(transport);
30
35
  }
36
+ /** Wrap every registered tool handler with usage telemetry. */
37
+ instrumentTools(server) {
38
+ const original = server.tool.bind(server);
39
+ server.tool = (...args) => {
40
+ const name = String(args[0]);
41
+ const lastIndex = args.length - 1;
42
+ const handler = args[lastIndex];
43
+ if (typeof handler === "function") {
44
+ const orig = handler;
45
+ args[lastIndex] = async (...hArgs) => {
46
+ const start = Date.now();
47
+ try {
48
+ const result = await orig(...hArgs);
49
+ (0, telemetry_1.track)("mcp_tool_called", {
50
+ tool: name,
51
+ success: true,
52
+ duration_ms: Date.now() - start,
53
+ });
54
+ return result;
55
+ }
56
+ catch (error) {
57
+ (0, telemetry_1.track)("mcp_tool_called", {
58
+ tool: name,
59
+ success: false,
60
+ duration_ms: Date.now() - start,
61
+ error: error instanceof Error ? error.message : String(error),
62
+ });
63
+ (0, telemetry_1.captureException)(error, { mcp_tool: name });
64
+ throw error;
65
+ }
66
+ };
67
+ }
68
+ return original(...args);
69
+ };
70
+ }
31
71
  registerTools(server) {
32
72
  // ── design_schema ──────────────────────────────────────────────
33
73
  server.tool("design_schema", "Design a database schema from application requirements. Takes a description of entities, relationships, and business rules. Returns a validated .apsorc schema definition ready for code generation.", {
@@ -110,6 +110,15 @@ export declare const githubApi: {
110
110
  getConnectUrl(_workspaceSlug: string): Promise<{
111
111
  url: string;
112
112
  }>;
113
+ /**
114
+ * Create a new GitHub repo for a service via the user's connection (no local
115
+ * git or gh required). autoInit=false leaves it empty so the deploy's first
116
+ * push becomes the initial commit.
117
+ */
118
+ createRepo(connectionId: string, serviceName: string, options?: {
119
+ private?: boolean;
120
+ autoInit?: boolean;
121
+ }): Promise<GitHubRepo>;
113
122
  /** Link an existing GitHub repo to a service. */
114
123
  connectRepo(serviceId: string, connectionId: string, repoFullName: string): Promise<Raw>;
115
124
  /** Push the service's saved (S3) code to its connected GitHub repo. */
@@ -118,4 +127,16 @@ export declare const githubApi: {
118
127
  message?: string;
119
128
  }): Promise<Raw>;
120
129
  };
130
+ /**
131
+ * Service code API — used for the server-side deploy (upload the project zip to
132
+ * S3; the backend then pushes it to the repo, so no local git is required).
133
+ */
134
+ export declare const codeApi: {
135
+ /** Presigned S3 PUT URL to upload the service's code zip. */
136
+ getUploadUrl(serviceId: string): Promise<{
137
+ url: string;
138
+ bucket?: string;
139
+ key?: string;
140
+ }>;
141
+ };
121
142
  export {};
@@ -12,7 +12,7 @@
12
12
  * below.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.githubApi = exports.buildApi = exports.schemaApi = exports.servicesApi = exports.workspacesApi = exports.authApi = void 0;
15
+ exports.codeApi = exports.githubApi = exports.buildApi = exports.schemaApi = exports.servicesApi = exports.workspacesApi = exports.authApi = void 0;
16
16
  const client_1 = require("./client");
17
17
  function adaptWorkspace(raw) {
18
18
  var _a, _b, _c, _d;
@@ -323,6 +323,30 @@ exports.githubApi = {
323
323
  async getConnectUrl(_workspaceSlug) {
324
324
  return client_1.api.get("/api/github/authorize");
325
325
  },
326
+ /**
327
+ * Create a new GitHub repo for a service via the user's connection (no local
328
+ * git or gh required). autoInit=false leaves it empty so the deploy's first
329
+ * push becomes the initial commit.
330
+ */
331
+ async createRepo(connectionId, serviceName, options) {
332
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
333
+ const raw = await client_1.api.post("/api/github/repos", {
334
+ connectionId,
335
+ serviceName,
336
+ isPrivate: (_a = options === null || options === void 0 ? void 0 : options.private) !== null && _a !== void 0 ? _a : true,
337
+ autoInit: (_b = options === null || options === void 0 ? void 0 : options.autoInit) !== null && _b !== void 0 ? _b : false,
338
+ });
339
+ const r = (_d = (_c = raw === null || raw === void 0 ? void 0 : raw.repository) !== null && _c !== void 0 ? _c : raw === null || raw === void 0 ? void 0 : raw.data) !== null && _d !== void 0 ? _d : raw;
340
+ return {
341
+ id: r.id,
342
+ fullName: (_f = (_e = r.full_name) !== null && _e !== void 0 ? _e : r.fullName) !== null && _f !== void 0 ? _f : r.name,
343
+ name: r.name,
344
+ owner: (_h = (_g = r.owner) === null || _g === void 0 ? void 0 : _g.login) !== null && _h !== void 0 ? _h : r.owner,
345
+ private: Boolean(r.private),
346
+ defaultBranch: (_k = (_j = r.default_branch) !== null && _j !== void 0 ? _j : r.defaultBranch) !== null && _k !== void 0 ? _k : "main",
347
+ url: (_l = r.html_url) !== null && _l !== void 0 ? _l : r.url,
348
+ };
349
+ },
326
350
  /** Link an existing GitHub repo to a service. */
327
351
  async connectRepo(serviceId, connectionId, repoFullName) {
328
352
  return client_1.api.post("/api/github/connect-repo", {
@@ -342,3 +366,13 @@ exports.githubApi = {
342
366
  });
343
367
  },
344
368
  };
369
+ /**
370
+ * Service code API — used for the server-side deploy (upload the project zip to
371
+ * S3; the backend then pushes it to the repo, so no local git is required).
372
+ */
373
+ exports.codeApi = {
374
+ /** Presigned S3 PUT URL to upload the service's code zip. */
375
+ async getUploadUrl(serviceId) {
376
+ return client_1.api.get(`/api/services/${serviceId}/code/upload-url`);
377
+ },
378
+ };
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ /** Zip the project source into a buffer, skipping build/vcs/dependency dirs. */
3
+ export declare function zipProject(cwd: string): Buffer;
4
+ /**
5
+ * Server-side push: upload the project zip to S3 and have the platform push it
6
+ * to the connected repo through the user's GitHub connection. No local git or
7
+ * `gh` required — works on a bare machine that has only `apso login`.
8
+ */
9
+ export declare function serverSidePush(opts: {
10
+ cwd: string;
11
+ serviceId: string;
12
+ connectionId: string;
13
+ branch: string;
14
+ message: string;
15
+ }): Promise<void>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serverSidePush = exports.zipProject = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const adm_zip_1 = tslib_1.__importDefault(require("adm-zip"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const services_1 = require("../api/services");
8
+ // Never ship these into the code zip — the backend reconstitutes the runnable
9
+ // base template and only needs the source + schema + user edits.
10
+ const EXCLUDE_DIRS = new Set([
11
+ "node_modules",
12
+ ".git",
13
+ "dist",
14
+ ".next",
15
+ ".apso",
16
+ "coverage",
17
+ ]);
18
+ /** Zip the project source into a buffer, skipping build/vcs/dependency dirs. */
19
+ function zipProject(cwd) {
20
+ const zip = new adm_zip_1.default();
21
+ zip.addLocalFolder(cwd, "", (filename) => {
22
+ const parts = filename.split(path.sep);
23
+ return !parts.some((p) => EXCLUDE_DIRS.has(p));
24
+ });
25
+ return zip.toBuffer();
26
+ }
27
+ exports.zipProject = zipProject;
28
+ /**
29
+ * Server-side push: upload the project zip to S3 and have the platform push it
30
+ * to the connected repo through the user's GitHub connection. No local git or
31
+ * `gh` required — works on a bare machine that has only `apso login`.
32
+ */
33
+ async function serverSidePush(opts) {
34
+ const buffer = zipProject(opts.cwd);
35
+ const { url } = await services_1.codeApi.getUploadUrl(opts.serviceId);
36
+ const res = await fetch(url, {
37
+ method: "PUT",
38
+ headers: { "Content-Type": "application/zip" },
39
+ body: buffer,
40
+ });
41
+ if (!res.ok) {
42
+ throw new Error(`Code upload failed (${res.status}). ${await res.text().catch(() => "")}`);
43
+ }
44
+ await services_1.githubApi.push(opts.serviceId, opts.connectionId, {
45
+ branch: opts.branch,
46
+ message: opts.message,
47
+ });
48
+ }
49
+ exports.serverSidePush = serverSidePush;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.26.2",
3
+ "version": "0.28.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.26.2",
9
+ "version": "0.28.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@biomejs/biome": "^1.9.4",
@@ -17,6 +17,7 @@
17
17
  "@oclif/plugin-plugins": "^3",
18
18
  "@oclif/plugin-warn-if-update-available": "^2.1.1",
19
19
  "@sentry/node": "^10.68.0",
20
+ "adm-zip": "^0.6.0",
20
21
  "debug": "^4.3.4",
21
22
  "eta": "^2.0.0",
22
23
  "inquirer": "^8.2.7",
@@ -35,6 +36,7 @@
35
36
  "devDependencies": {
36
37
  "@jest/globals": "^29.5.0",
37
38
  "@oclif/test": "^2.4.7",
39
+ "@types/adm-zip": "^0.5.8",
38
40
  "@types/chai": "^4",
39
41
  "@types/inquirer": "^8.2.12",
40
42
  "@types/mocha": "^10",
@@ -2659,6 +2661,16 @@
2659
2661
  "url": "https://github.com/sponsors/isaacs"
2660
2662
  }
2661
2663
  },
2664
+ "node_modules/@types/adm-zip": {
2665
+ "version": "0.5.8",
2666
+ "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.8.tgz",
2667
+ "integrity": "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==",
2668
+ "dev": true,
2669
+ "license": "MIT",
2670
+ "dependencies": {
2671
+ "@types/node": "*"
2672
+ }
2673
+ },
2662
2674
  "node_modules/@types/babel__core": {
2663
2675
  "version": "7.20.1",
2664
2676
  "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz",
@@ -3556,6 +3568,15 @@
3556
3568
  "node": ">=0.4.0"
3557
3569
  }
3558
3570
  },
3571
+ "node_modules/adm-zip": {
3572
+ "version": "0.6.0",
3573
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
3574
+ "integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==",
3575
+ "license": "MIT",
3576
+ "engines": {
3577
+ "node": ">=14.0"
3578
+ }
3579
+ },
3559
3580
  "node_modules/agent-base": {
3560
3581
  "version": "6.0.2",
3561
3582
  "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.26.2",
2
+ "version": "0.28.0",
3
3
  "commands": {
4
4
  "config": {
5
5
  "id": "config",
@@ -88,6 +88,12 @@
88
88
  "description": "Branch to push to",
89
89
  "multiple": false,
90
90
  "default": "main"
91
+ },
92
+ "local-git": {
93
+ "name": "local-git",
94
+ "type": "boolean",
95
+ "description": "Push using your local git instead of the platform (needs local GitHub auth)",
96
+ "allowNo": false
91
97
  }
92
98
  },
93
99
  "args": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.26.2",
3
+ "version": "0.28.0",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",
@@ -32,6 +32,7 @@
32
32
  "@oclif/plugin-plugins": "^3",
33
33
  "@oclif/plugin-warn-if-update-available": "^2.1.1",
34
34
  "@sentry/node": "^10.68.0",
35
+ "adm-zip": "^0.6.0",
35
36
  "debug": "^4.3.4",
36
37
  "eta": "^2.0.0",
37
38
  "inquirer": "^8.2.7",
@@ -47,6 +48,7 @@
47
48
  "devDependencies": {
48
49
  "@jest/globals": "^29.5.0",
49
50
  "@oclif/test": "^2.4.7",
51
+ "@types/adm-zip": "^0.5.8",
50
52
  "@types/chai": "^4",
51
53
  "@types/inquirer": "^8.2.12",
52
54
  "@types/mocha": "^10",