@hapico/cli 0.0.14 → 0.0.15

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/bin/index.js CHANGED
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.compileES5 = void 0;
41
+ /* eslint-disable @typescript-eslint/no-explicit-any */
42
+ const lodash_1 = require("lodash");
41
43
  const commander_1 = require("commander");
42
44
  const axios_1 = __importDefault(require("axios"));
43
45
  const fs = __importStar(require("fs"));
@@ -226,7 +228,7 @@ class FileManager {
226
228
  }
227
229
  }
228
230
  class RoomState {
229
- constructor(roomId) {
231
+ constructor(roomId, onChangeListeners) {
230
232
  this.files = [];
231
233
  this.roomId = roomId;
232
234
  this.state = {};
@@ -234,6 +236,7 @@ class RoomState {
234
236
  this.ws = null;
235
237
  this.reconnectTimeout = null;
236
238
  this.reconnectAttempts = 0;
239
+ this.onChangeListeners = onChangeListeners;
237
240
  }
238
241
  connect(onConnected) {
239
242
  if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN)
@@ -242,25 +245,50 @@ class RoomState {
242
245
  clearTimeout(this.reconnectTimeout);
243
246
  }
244
247
  this.ws = new ws_1.WebSocket(`https://base.myworkbeast.com/ws?room=${this.roomId}`);
245
- this.ws.on("open", () => {
248
+ this.ws.onopen = () => {
249
+ console.log(`Connected to room: ${this.roomId}`);
246
250
  this.isConnected = true;
247
251
  this.reconnectAttempts = 0;
248
- onConnected === null || onConnected === void 0 ? void 0 : onConnected();
249
- connected.succeed(`Connected to WebSocket server`);
250
- });
251
- this.ws.on("close", () => {
252
+ onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
253
+ };
254
+ this.ws.onclose = () => {
255
+ console.log(`Disconnected from room: ${this.roomId}`);
252
256
  this.isConnected = false;
253
257
  this.reconnectAttempts++;
254
- connected.start(`Retry connection...`);
255
- this.connect();
256
- });
258
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
259
+ console.log(`Attempting to reconnect in ${delay / 1000}s...`);
260
+ this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
261
+ };
257
262
  this.ws.on("message", (data) => {
258
263
  try {
259
- const message = JSON.parse(data.toString());
260
- if (message.type === "state" && message.state) {
261
- this.state = message.state;
262
- const view = this.state.view;
263
- this.files = view;
264
+ const jsonStr = typeof data === "string" ? data : data.toString();
265
+ const parsedData = JSON.parse(jsonStr);
266
+ const includes = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
267
+ let newState;
268
+ let changedKeys;
269
+ if (parsedData.type === "state") {
270
+ // Full state (e.g., on initial connect)
271
+ newState = parsedData.state;
272
+ changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
273
+ }
274
+ else if (parsedData.type === "update") {
275
+ // Delta update
276
+ const delta = parsedData.state;
277
+ newState = { ...this.state, ...delta };
278
+ changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
279
+ }
280
+ else {
281
+ return;
282
+ }
283
+ const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
284
+ if (filteredChangedKeys.length > 0) {
285
+ filteredChangedKeys.forEach((key) => {
286
+ const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
287
+ if (listener) {
288
+ listener.callback(newState[key]);
289
+ }
290
+ });
291
+ this.state = newState;
264
292
  }
265
293
  }
266
294
  catch (e) {
@@ -280,16 +308,13 @@ class RoomState {
280
308
  state: { [key]: value },
281
309
  }));
282
310
  }
283
- else {
284
- console.log("Cannot update state: Not connected");
285
- }
286
311
  }
287
312
  disconnect() {
288
313
  if (this.reconnectTimeout) {
289
314
  clearTimeout(this.reconnectTimeout);
290
315
  }
291
316
  if (this.ws) {
292
- this.ws.removeAllListeners("close");
317
+ this.ws.onclose = null; // Prevent reconnect on intentional close
293
318
  this.ws.close();
294
319
  }
295
320
  }
@@ -300,7 +325,7 @@ class RoomState {
300
325
  return this.isConnected;
301
326
  }
302
327
  }
303
- commander_1.program.version("0.0.14").description("Hapico CLI for project management");
328
+ commander_1.program.version("0.0.15").description("Hapico CLI for project management");
304
329
  commander_1.program
305
330
  .command("clone <id>")
306
331
  .description("Clone a project by ID")
@@ -411,7 +436,7 @@ commander_1.program
411
436
  return;
412
437
  }
413
438
  console.log(`Connecting to WebSocket server`);
414
- const room = new RoomState(`view_${projectId}`);
439
+ const room = new RoomState(`view_${projectId}`, []);
415
440
  room.connect(async () => {
416
441
  devSpinner.succeed("Project started in development mode!");
417
442
  const fileManager = new FileManager(srcDir);
@@ -420,7 +445,7 @@ commander_1.program
420
445
  fileManager.setOnFileChange((filePath, content) => {
421
446
  const es5 = (0, exports.compileES5)(content, filePath);
422
447
  console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
423
- const updatedView = room.files.map((file) => {
448
+ const updatedView = initialFiles.map((file) => {
424
449
  if (path.join(srcDir, file.path) === filePath) {
425
450
  return { ...file, content, es5 };
426
451
  }
@@ -445,7 +470,6 @@ commander_1.program
445
470
  return;
446
471
  }
447
472
  const projectType = project.data.type || "view";
448
- console.log("Project type:", projectType);
449
473
  if (projectType === "zalominiapp") {
450
474
  qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
451
475
  console.log("Scan this QR code to connect to the project:");
package/bun.lock CHANGED
@@ -7,6 +7,7 @@
7
7
  "@babel/standalone": "^7.28.2",
8
8
  "axios": "^1.11.0",
9
9
  "commander": "^14.0.0",
10
+ "lodash": "^4.17.21",
10
11
  "open": "^10.2.0",
11
12
  "ora": "^8.2.0",
12
13
  "qrcode-terminal": "^0.12.0",
@@ -16,6 +17,7 @@
16
17
  "devDependencies": {
17
18
  "@types/babel__standalone": "^7.1.9",
18
19
  "@types/commander": "^2.12.5",
20
+ "@types/lodash": "^4.17.20",
19
21
  "@types/node": "^24.1.0",
20
22
  "@types/qrcode-terminal": "^0.12.2",
21
23
  "@types/unzipper": "^0.10.11",
@@ -47,6 +49,8 @@
47
49
 
48
50
  "@types/commander": ["@types/commander@2.12.5", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="],
49
51
 
52
+ "@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
53
+
50
54
  "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
51
55
 
52
56
  "@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="],
@@ -141,6 +145,8 @@
141
145
 
142
146
  "jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
143
147
 
148
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
149
+
144
150
  "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
145
151
 
146
152
  "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
package/dist/index.js CHANGED
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.compileES5 = void 0;
41
+ /* eslint-disable @typescript-eslint/no-explicit-any */
42
+ const lodash_1 = require("lodash");
41
43
  const commander_1 = require("commander");
42
44
  const axios_1 = __importDefault(require("axios"));
43
45
  const fs = __importStar(require("fs"));
@@ -226,7 +228,7 @@ class FileManager {
226
228
  }
227
229
  }
228
230
  class RoomState {
229
- constructor(roomId) {
231
+ constructor(roomId, onChangeListeners) {
230
232
  this.files = [];
231
233
  this.roomId = roomId;
232
234
  this.state = {};
@@ -234,6 +236,7 @@ class RoomState {
234
236
  this.ws = null;
235
237
  this.reconnectTimeout = null;
236
238
  this.reconnectAttempts = 0;
239
+ this.onChangeListeners = onChangeListeners;
237
240
  }
238
241
  connect(onConnected) {
239
242
  if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN)
@@ -242,25 +245,50 @@ class RoomState {
242
245
  clearTimeout(this.reconnectTimeout);
243
246
  }
244
247
  this.ws = new ws_1.WebSocket(`https://base.myworkbeast.com/ws?room=${this.roomId}`);
245
- this.ws.on("open", () => {
248
+ this.ws.onopen = () => {
249
+ console.log(`Connected to room: ${this.roomId}`);
246
250
  this.isConnected = true;
247
251
  this.reconnectAttempts = 0;
248
- onConnected === null || onConnected === void 0 ? void 0 : onConnected();
249
- connected.succeed(`Connected to WebSocket server`);
250
- });
251
- this.ws.on("close", () => {
252
+ onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
253
+ };
254
+ this.ws.onclose = () => {
255
+ console.log(`Disconnected from room: ${this.roomId}`);
252
256
  this.isConnected = false;
253
257
  this.reconnectAttempts++;
254
- connected.start(`Retry connection...`);
255
- this.connect();
256
- });
258
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
259
+ console.log(`Attempting to reconnect in ${delay / 1000}s...`);
260
+ this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
261
+ };
257
262
  this.ws.on("message", (data) => {
258
263
  try {
259
- const message = JSON.parse(data.toString());
260
- if (message.type === "state" && message.state) {
261
- this.state = message.state;
262
- const view = this.state.view;
263
- this.files = view;
264
+ const jsonStr = typeof data === "string" ? data : data.toString();
265
+ const parsedData = JSON.parse(jsonStr);
266
+ const includes = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
267
+ let newState;
268
+ let changedKeys;
269
+ if (parsedData.type === "state") {
270
+ // Full state (e.g., on initial connect)
271
+ newState = parsedData.state;
272
+ changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
273
+ }
274
+ else if (parsedData.type === "update") {
275
+ // Delta update
276
+ const delta = parsedData.state;
277
+ newState = { ...this.state, ...delta };
278
+ changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
279
+ }
280
+ else {
281
+ return;
282
+ }
283
+ const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
284
+ if (filteredChangedKeys.length > 0) {
285
+ filteredChangedKeys.forEach((key) => {
286
+ const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
287
+ if (listener) {
288
+ listener.callback(newState[key]);
289
+ }
290
+ });
291
+ this.state = newState;
264
292
  }
265
293
  }
266
294
  catch (e) {
@@ -280,16 +308,13 @@ class RoomState {
280
308
  state: { [key]: value },
281
309
  }));
282
310
  }
283
- else {
284
- console.log("Cannot update state: Not connected");
285
- }
286
311
  }
287
312
  disconnect() {
288
313
  if (this.reconnectTimeout) {
289
314
  clearTimeout(this.reconnectTimeout);
290
315
  }
291
316
  if (this.ws) {
292
- this.ws.removeAllListeners("close");
317
+ this.ws.onclose = null; // Prevent reconnect on intentional close
293
318
  this.ws.close();
294
319
  }
295
320
  }
@@ -300,7 +325,7 @@ class RoomState {
300
325
  return this.isConnected;
301
326
  }
302
327
  }
303
- commander_1.program.version("0.0.14").description("Hapico CLI for project management");
328
+ commander_1.program.version("0.0.15").description("Hapico CLI for project management");
304
329
  commander_1.program
305
330
  .command("clone <id>")
306
331
  .description("Clone a project by ID")
@@ -411,7 +436,7 @@ commander_1.program
411
436
  return;
412
437
  }
413
438
  console.log(`Connecting to WebSocket server`);
414
- const room = new RoomState(`view_${projectId}`);
439
+ const room = new RoomState(`view_${projectId}`, []);
415
440
  room.connect(async () => {
416
441
  devSpinner.succeed("Project started in development mode!");
417
442
  const fileManager = new FileManager(srcDir);
@@ -420,7 +445,7 @@ commander_1.program
420
445
  fileManager.setOnFileChange((filePath, content) => {
421
446
  const es5 = (0, exports.compileES5)(content, filePath);
422
447
  console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
423
- const updatedView = room.files.map((file) => {
448
+ const updatedView = initialFiles.map((file) => {
424
449
  if (path.join(srcDir, file.path) === filePath) {
425
450
  return { ...file, content, es5 };
426
451
  }
@@ -445,7 +470,6 @@ commander_1.program
445
470
  return;
446
471
  }
447
472
  const projectType = project.data.type || "view";
448
- console.log("Project type:", projectType);
449
473
  if (projectType === "zalominiapp") {
450
474
  qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
451
475
  console.log("Scan this QR code to connect to the project:");
package/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
-
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import { isEqual, find } from "lodash";
3
4
  import { program } from "commander";
4
5
  import axios from "axios";
5
6
  import * as fs from "fs";
@@ -249,6 +250,10 @@ class FileManager {
249
250
  }
250
251
  }
251
252
 
253
+ interface FileContent {
254
+ // Define the structure of FileContent if needed
255
+ }
256
+
252
257
  class RoomState {
253
258
  private roomId: string;
254
259
  private state: RoomStateData;
@@ -256,16 +261,21 @@ class RoomState {
256
261
  private ws: WebSocket | null;
257
262
  private reconnectTimeout: NodeJS.Timeout | null;
258
263
  private reconnectAttempts: number;
264
+ private onChangeListeners: Array<{ key: string; callback: (value: any) => void }>;
259
265
 
260
266
  public files: FileContent[] = [];
261
267
 
262
- constructor(roomId: string) {
268
+ constructor(
269
+ roomId: string,
270
+ onChangeListeners: Array<{ key: string; callback: (value: any) => void }>
271
+ ) {
263
272
  this.roomId = roomId;
264
273
  this.state = {};
265
274
  this.isConnected = false;
266
275
  this.ws = null;
267
276
  this.reconnectTimeout = null;
268
277
  this.reconnectAttempts = 0;
278
+ this.onChangeListeners = onChangeListeners;
269
279
  }
270
280
 
271
281
  connect(onConnected?: () => void): void {
@@ -279,36 +289,69 @@ class RoomState {
279
289
  `https://base.myworkbeast.com/ws?room=${this.roomId}`
280
290
  );
281
291
 
282
- this.ws.on("open", () => {
292
+ this.ws.onopen = () => {
293
+ console.log(`Connected to room: ${this.roomId}`);
283
294
  this.isConnected = true;
284
295
  this.reconnectAttempts = 0;
285
- onConnected?.();
286
- connected.succeed(`Connected to WebSocket server`);
287
- });
296
+ onConnected?.(); // Call the onConnected callback if provided
297
+ };
288
298
 
289
- this.ws.on("close", () => {
299
+ this.ws.onclose = () => {
300
+ console.log(`Disconnected from room: ${this.roomId}`);
290
301
  this.isConnected = false;
291
302
 
292
303
  this.reconnectAttempts++;
293
- connected.start(`Retry connection...`);
304
+ const delay = Math.min(
305
+ 1000 * Math.pow(2, this.reconnectAttempts),
306
+ 30000
307
+ );
308
+ console.log(`Attempting to reconnect in ${delay / 1000}s...`);
294
309
 
295
- this.connect();
296
- });
310
+ this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
311
+ };
297
312
 
298
- this.ws.on("message", (data: Buffer) => {
313
+ this.ws.on("message", (data: Buffer | string) => {
299
314
  try {
300
- const message: WebSocketMessage = JSON.parse(data.toString());
301
- if (message.type === "state" && message.state) {
302
- this.state = message.state;
303
- const view = this.state.view as Array<FileContent>;
304
- this.files = view;
315
+ const jsonStr = typeof data === "string" ? data : data.toString();
316
+ const parsedData = JSON.parse(jsonStr);
317
+ const includes = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
318
+
319
+ let newState: RoomStateData;
320
+ let changedKeys;
321
+
322
+ if (parsedData.type === "state") {
323
+ // Full state (e.g., on initial connect)
324
+ newState = parsedData.state;
325
+ changedKeys = Object.keys(newState).filter(
326
+ (key) => !isEqual(newState[key], this.state[key])
327
+ );
328
+ } else if (parsedData.type === "update") {
329
+ // Delta update
330
+ const delta = parsedData.state;
331
+ newState = { ...this.state, ...delta };
332
+ changedKeys = Object.keys(delta).filter(
333
+ (key) => !isEqual(delta[key], this.state[key])
334
+ );
335
+ } else {
336
+ return;
337
+ }
338
+
339
+ const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
340
+ if (filteredChangedKeys.length > 0) {
341
+ filteredChangedKeys.forEach((key) => {
342
+ const listener = find(this.onChangeListeners, (l) => l.key === key);
343
+ if (listener) {
344
+ listener.callback(newState[key]);
345
+ }
346
+ });
347
+ this.state = newState;
305
348
  }
306
349
  } catch (e) {
307
350
  console.error("Error processing message:", e);
308
351
  }
309
352
  });
310
353
 
311
- this.ws.on("error", (err: Error) => {
354
+ this.ws.on("error", (err) => {
312
355
  console.error("WebSocket error:", err);
313
356
  this.ws?.close();
314
357
  });
@@ -322,8 +365,6 @@ class RoomState {
322
365
  state: { [key]: value },
323
366
  })
324
367
  );
325
- } else {
326
- console.log("Cannot update state: Not connected");
327
368
  }
328
369
  }
329
370
 
@@ -332,7 +373,7 @@ class RoomState {
332
373
  clearTimeout(this.reconnectTimeout);
333
374
  }
334
375
  if (this.ws) {
335
- this.ws.removeAllListeners("close");
376
+ this.ws.onclose = null; // Prevent reconnect on intentional close
336
377
  this.ws.close();
337
378
  }
338
379
  }
@@ -346,7 +387,7 @@ class RoomState {
346
387
  }
347
388
  }
348
389
 
349
- program.version("0.0.14").description("Hapico CLI for project management");
390
+ program.version("0.0.15").description("Hapico CLI for project management");
350
391
 
351
392
  program
352
393
  .command("clone <id>")
@@ -431,7 +472,9 @@ program
431
472
  console.error("You need to login first. Use 'hapico login' command.");
432
473
  return;
433
474
  }
434
- const devSpinner = ora("Starting the project in development mode...").start();
475
+ const devSpinner = ora(
476
+ "Starting the project in development mode..."
477
+ ).start();
435
478
  const pwd = process.cwd();
436
479
  const srcDir = path.join(pwd, "src");
437
480
  if (!fs.existsSync(srcDir)) {
@@ -492,7 +535,7 @@ program
492
535
  }
493
536
 
494
537
  console.log(`Connecting to WebSocket server`);
495
- const room = new RoomState(`view_${projectId}`);
538
+ const room = new RoomState(`view_${projectId}`, []);
496
539
 
497
540
  room.connect(async () => {
498
541
  devSpinner.succeed("Project started in development mode!");
@@ -505,7 +548,7 @@ program
505
548
  fileManager.setOnFileChange((filePath, content) => {
506
549
  const es5 = compileES5(content, filePath);
507
550
  console.log(`File changed: ${filePath?.replace(srcDir, ".")}`);
508
- const updatedView = room.files.map((file) => {
551
+ const updatedView = initialFiles.map((file) => {
509
552
  if (path.join(srcDir, file.path) === filePath) {
510
553
  return { ...file, content, es5 };
511
554
  }
@@ -539,8 +582,6 @@ program
539
582
 
540
583
  const projectType = project.data.type || "view";
541
584
 
542
- console.log("Project type:", projectType);
543
-
544
585
  if (projectType === "zalominiapp") {
545
586
  QRCode.generate(
546
587
  `https://zalo.me/s/3218692650896662017/player/${projectId}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hapico/cli",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "A simple CLI tool for project management",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -23,6 +23,7 @@
23
23
  "@babel/standalone": "^7.28.2",
24
24
  "axios": "^1.11.0",
25
25
  "commander": "^14.0.0",
26
+ "lodash": "^4.17.21",
26
27
  "open": "^10.2.0",
27
28
  "ora": "^8.2.0",
28
29
  "qrcode-terminal": "^0.12.0",
@@ -32,6 +33,7 @@
32
33
  "devDependencies": {
33
34
  "@types/babel__standalone": "^7.1.9",
34
35
  "@types/commander": "^2.12.5",
36
+ "@types/lodash": "^4.17.20",
35
37
  "@types/node": "^24.1.0",
36
38
  "@types/qrcode-terminal": "^0.12.2",
37
39
  "@types/unzipper": "^0.10.11",