@hapico/cli 0.0.28 → 0.0.29

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
@@ -411,7 +411,7 @@ class RoomState {
411
411
  return this.isConnected;
412
412
  }
413
413
  }
414
- commander_1.program.version("0.0.28").description("Hapico CLI for project management");
414
+ commander_1.program.version("0.0.29").description("Hapico CLI for project management");
415
415
  commander_1.program
416
416
  .command("clone <id>")
417
417
  .description("Clone a project by ID")
@@ -3,16 +3,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.vibe = void 0;
6
+ exports.vibe = exports.applyPatch = void 0;
7
7
  /* eslint-disable @typescript-eslint/no-explicit-any */
8
8
  const promises_1 = __importDefault(require("node:fs/promises"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
- const node_child_process_1 = require("node:child_process");
11
- const node_util_1 = __importDefault(require("node:util"));
10
+ const diff_match_patch_1 = require("diff-match-patch");
12
11
  const express_1 = __importDefault(require("express"));
13
12
  const open_1 = __importDefault(require("open"));
14
- const execAsync = node_util_1.default.promisify(node_child_process_1.exec);
15
13
  const node_net_1 = __importDefault(require("node:net"));
14
+ const scanRecursively = async (dir, baseDir) => {
15
+ let results = [];
16
+ try {
17
+ const list = await promises_1.default.readdir(dir, { withFileTypes: true });
18
+ for (const entry of list) {
19
+ const fullPath = node_path_1.default.join(dir, entry.name);
20
+ const relativePath = node_path_1.default.relative(baseDir, fullPath);
21
+ if (shouldIgnorePath(relativePath))
22
+ continue;
23
+ if (entry.isDirectory()) {
24
+ const subResults = await scanRecursively(fullPath, baseDir);
25
+ results = results.concat(subResults);
26
+ }
27
+ else if (entry.isFile()) {
28
+ try {
29
+ const content = await promises_1.default.readFile(fullPath, "utf-8");
30
+ results.push({
31
+ path: relativePath,
32
+ fullPath: fullPath,
33
+ content: content,
34
+ extension: entry.name.split(".").pop() || "txt",
35
+ });
36
+ }
37
+ catch (err) {
38
+ // Bỏ qua file nhị phân hoặc không đọc được
39
+ }
40
+ }
41
+ }
42
+ }
43
+ catch (error) {
44
+ // Bỏ qua nếu lỗi permission hoặc path không tồn tại
45
+ }
46
+ return results;
47
+ };
16
48
  // random port from 3000-3999
17
49
  // Check if port is available would be better in real-world usage
18
50
  const isPortInUse = (port) => {
@@ -81,98 +113,226 @@ const shouldIgnorePath = (filePath) => {
81
113
  }
82
114
  return false;
83
115
  };
84
- const scanRecursively = async (dir, baseDir) => {
85
- let results = [];
86
- try {
87
- const list = await promises_1.default.readdir(dir, { withFileTypes: true });
88
- for (const entry of list) {
89
- const fullPath = node_path_1.default.join(dir, entry.name);
90
- const relativePath = node_path_1.default.relative(baseDir, fullPath);
91
- if (shouldIgnorePath(relativePath))
92
- continue;
93
- if (entry.isDirectory()) {
94
- const subResults = await scanRecursively(fullPath, baseDir);
95
- results = results.concat(subResults);
96
- }
97
- else if (entry.isFile()) {
98
- try {
99
- const content = await promises_1.default.readFile(fullPath, "utf-8");
100
- results.push({
101
- path: relativePath,
102
- fullPath: fullPath,
103
- content: content,
104
- extension: entry.name.split(".").pop() || "txt",
105
- });
106
- }
107
- catch (err) {
108
- // Bỏ qua file nhị phân hoặc không đọc được
109
- }
110
- }
111
- }
112
- }
113
- catch (error) {
114
- // Bỏ qua nếu lỗi permission hoặc path không tồn tại
116
+ /**
117
+ * ------------------------------------------------------------------
118
+ * HELPER: STRING SIMILARITY (Levenshtein based)
119
+ * Tính độ giống nhau giữa 2 chuỗi (0 -> 1)
120
+ * ------------------------------------------------------------------
121
+ */
122
+ const calculateSimilarity = (s1, s2) => {
123
+ const longer = s1.length > s2.length ? s1 : s2;
124
+ const shorter = s1.length > s2.length ? s2 : s1;
125
+ const longerLength = longer.length;
126
+ if (longerLength === 0) {
127
+ return 1.0;
115
128
  }
116
- return results;
117
- };
118
- const areLinesFuzzyEqual = (line1, line2) => {
119
- return line1.trim() === line2.trim();
129
+ // Sử dụng dmp để tính Levenshtein distance cho chính xác và nhanh
130
+ const dmp = new diff_match_patch_1.diff_match_patch();
131
+ const diffs = dmp.diff_main(longer, shorter);
132
+ const levenshtein = dmp.diff_levenshtein(diffs);
133
+ return (longerLength - levenshtein) / longerLength;
120
134
  };
121
- const performFuzzyReplace = (originalContent, searchBlock, replaceBlock) => {
122
- const originalLines = originalContent.split(/\r?\n/);
123
- const searchLines = searchBlock.split(/\r?\n/);
124
- while (searchLines.length > 0 && searchLines[0].trim() === "") {
135
+ /**
136
+ * ------------------------------------------------------------------
137
+ * HELPER: FUZZY LINE MATCHING
138
+ * Tìm đoạn code khớp nhất trong file gốc dựa trên điểm số (Confidence)
139
+ * ------------------------------------------------------------------
140
+ */
141
+ const applyFuzzyLineMatch = (original, search, replace, threshold = 0.85 // Ngưỡng chấp nhận (85% giống nhau)
142
+ ) => {
143
+ const originalLines = original.split(/\r?\n/);
144
+ const searchLines = search.split(/\r?\n/);
145
+ // Lọc bỏ dòng trống ở đầu/cuối search block để match chính xác hơn
146
+ while (searchLines.length > 0 && searchLines[0].trim() === "")
125
147
  searchLines.shift();
126
- }
127
- while (searchLines.length > 0 &&
128
- searchLines[searchLines.length - 1].trim() === "") {
148
+ while (searchLines.length > 0 && searchLines[searchLines.length - 1].trim() === "")
129
149
  searchLines.pop();
130
- }
131
150
  if (searchLines.length === 0)
132
- return null;
151
+ return { result: null, confidence: 0 };
152
+ let bestMatchIndex = -1;
153
+ let bestMatchScore = 0;
154
+ // Duyệt qua file gốc (Sliding Window)
155
+ // Chỉ cần duyệt đến vị trí mà số dòng còn lại đủ để chứa searchLines
133
156
  for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
134
- let match = true;
157
+ let currentScoreTotal = 0;
158
+ let possible = true;
135
159
  for (let j = 0; j < searchLines.length; j++) {
136
- if (!areLinesFuzzyEqual(originalLines[i + j], searchLines[j])) {
137
- match = false;
160
+ const originalLine = originalLines[i + j].trim();
161
+ const searchLine = searchLines[j].trim();
162
+ // So sánh nhanh: Nếu giống hệt nhau
163
+ if (originalLine === searchLine) {
164
+ currentScoreTotal += 1;
165
+ continue;
166
+ }
167
+ // So sánh chậm: Tính độ tương đồng
168
+ const similarity = calculateSimilarity(originalLine, searchLine);
169
+ // Nếu một dòng quá khác biệt (< 60%), coi như block này không khớp
170
+ // (Giúp tối ưu hiệu năng, cắt nhánh sớm)
171
+ if (similarity < 0.6) {
172
+ possible = false;
138
173
  break;
139
174
  }
175
+ currentScoreTotal += similarity;
140
176
  }
141
- if (match) {
142
- const before = originalLines.slice(0, i).join("\n");
143
- const after = originalLines.slice(i + searchLines.length).join("\n");
144
- return `${before}\n${replaceBlock}\n${after}`;
177
+ if (possible) {
178
+ const avgScore = currentScoreTotal / searchLines.length;
179
+ if (avgScore > bestMatchScore) {
180
+ bestMatchScore = avgScore;
181
+ bestMatchIndex = i;
182
+ }
145
183
  }
146
184
  }
147
- return null;
185
+ // LOG CONFIDENCE ĐỂ DEBUG
186
+ if (bestMatchScore > 0) {
187
+ console.log(`[FuzzyMatch] Best match score: ${(bestMatchScore * 100).toFixed(2)}% at line ${bestMatchIndex + 1}`);
188
+ }
189
+ // Nếu tìm thấy vị trí có độ tin cậy cao hơn ngưỡng
190
+ if (bestMatchIndex !== -1 && bestMatchScore >= threshold) {
191
+ const before = originalLines.slice(0, bestMatchIndex);
192
+ // Lưu ý: Đoạn replace thay thế đúng số lượng dòng của searchLines
193
+ // (Giả định search và match trong original có cùng số dòng hiển thị)
194
+ const after = originalLines.slice(bestMatchIndex + searchLines.length);
195
+ return {
196
+ result: [...before, replace, ...after].join("\n"),
197
+ confidence: bestMatchScore
198
+ };
199
+ }
200
+ return { result: null, confidence: bestMatchScore };
148
201
  };
149
- const applyPatch = (originalContent, patchContent) => {
150
- let result = originalContent;
151
- const patchRegex = /<<<<<<< SEARCH\s*\n([\s\S]*?)\n?=======\s*\n([\s\S]*?)\n?>>>>>>> REPLACE/g;
152
- const matches = [...patchContent.matchAll(patchRegex)];
153
- if (matches.length === 0) {
154
- return originalContent;
202
+ /**
203
+ * ------------------------------------------------------------------
204
+ * 1. STATE MACHINE PARSER (FILE-AWARE)
205
+ * ------------------------------------------------------------------
206
+ */
207
+ const parseLLMPatch = (patchContent) => {
208
+ const lines = patchContent.split(/\r?\n/);
209
+ const filePatches = {};
210
+ let currentFile = "unknown";
211
+ let inSearch = false;
212
+ let inReplace = false;
213
+ let searchLines = [];
214
+ let replaceLines = [];
215
+ const isFileMarker = (line) => {
216
+ const match = line.match(/^### File:\s*[`']?([^`'\s]+)[`']?/i);
217
+ return match ? match[1] : null;
218
+ };
219
+ const isSearchMarker = (line) => /^\s*<{7,}\s*SEARCH\s*$/.test(line);
220
+ const isDividerMarker = (line) => /^\s*={7,}\s*$/.test(line);
221
+ const isEndMarker = (line) => /^\s*>{7,}\s*REPLACE\s*$/.test(line);
222
+ for (const line of lines) {
223
+ const fileName = isFileMarker(line);
224
+ if (fileName) {
225
+ currentFile = fileName;
226
+ continue;
227
+ }
228
+ if (isSearchMarker(line)) {
229
+ inSearch = true;
230
+ inReplace = false;
231
+ searchLines = [];
232
+ replaceLines = [];
233
+ continue;
234
+ }
235
+ if (isDividerMarker(line)) {
236
+ if (inSearch) {
237
+ inSearch = false;
238
+ inReplace = true;
239
+ }
240
+ continue;
241
+ }
242
+ if (isEndMarker(line)) {
243
+ if (inReplace || inSearch) {
244
+ if (searchLines.length > 0 || replaceLines.length > 0) {
245
+ if (!filePatches[currentFile])
246
+ filePatches[currentFile] = [];
247
+ filePatches[currentFile].push({
248
+ search: searchLines.join("\n"),
249
+ replace: replaceLines.join("\n")
250
+ });
251
+ }
252
+ }
253
+ inSearch = false;
254
+ inReplace = false;
255
+ searchLines = [];
256
+ replaceLines = [];
257
+ continue;
258
+ }
259
+ if (inSearch)
260
+ searchLines.push(line);
261
+ else if (inReplace)
262
+ replaceLines.push(line);
155
263
  }
156
- for (const match of matches) {
157
- const [_, searchBlock, replaceBlock] = match;
158
- if (result.includes(searchBlock)) {
159
- result = result.replace(searchBlock, replaceBlock);
264
+ return filePatches;
265
+ };
266
+ /**
267
+ * ------------------------------------------------------------------
268
+ * 2. APPLY PATCH (UPDATED)
269
+ * ------------------------------------------------------------------
270
+ */
271
+ const applyPatch = (originalContent, patches) => {
272
+ if (!patches || patches.length === 0)
273
+ return originalContent;
274
+ let result = originalContent;
275
+ for (const patch of patches) {
276
+ const { search, replace } = patch;
277
+ // NẾU SEARCH RỖNG (INSERT/APPEND)
278
+ if (search.trim() === "") {
279
+ console.log(`[ApplyPatch] Inserting/appending block.`);
280
+ result += `\n${replace}`;
160
281
  continue;
161
282
  }
162
- const trimmedSearch = searchBlock.trim();
163
- const trimmedReplace = replaceBlock.trim();
283
+ // --- STRATEGY 1: EXACT MATCH ---
284
+ if (result.includes(search)) {
285
+ console.log(`[ApplyPatch] Exact match found.`);
286
+ result = result.replace(search, replace);
287
+ continue;
288
+ }
289
+ // --- STRATEGY 2: NORMALIZED MATCH (Trim) ---
290
+ const trimmedSearch = search.trim();
164
291
  if (result.includes(trimmedSearch)) {
165
- result = result.replace(trimmedSearch, trimmedReplace);
292
+ console.log(`[ApplyPatch] Trimmed match found.`);
293
+ result = result.replace(trimmedSearch, replace.trim());
166
294
  continue;
167
295
  }
168
- const fuzzyResult = performFuzzyReplace(result, searchBlock, replaceBlock);
169
- if (fuzzyResult) {
170
- result = fuzzyResult;
296
+ // --- STRATEGY 3: FUZZY LINE MATCH (BEST FOR CODE) ---
297
+ // Tìm vị trí tốt nhất dựa trên similarity score của từng dòng
298
+ const fuzzyMatch = applyFuzzyLineMatch(result, search, replace, 0.80); // 80% confidence
299
+ if (fuzzyMatch.result !== null) {
300
+ console.log(`[ApplyPatch] Fuzzy line match applied (Confidence: ${(fuzzyMatch.confidence * 100).toFixed(0)}%)`);
301
+ result = fuzzyMatch.result;
171
302
  continue;
172
303
  }
304
+ else {
305
+ console.warn(`[ApplyPatch] Failed to match block. Max confidence was: ${(fuzzyMatch.confidence * 100).toFixed(0)}%`);
306
+ }
307
+ // --- STRATEGY 4: DMP FALLBACK (LAST RESORT) ---
308
+ // Nếu cả fuzzy line match cũng tạch (ví dụ do search block quá ngắn hoặc cấu trúc quá nát)
309
+ // thì mới dùng DMP patch_apply.
310
+ try {
311
+ const dmp = new diff_match_patch_1.diff_match_patch();
312
+ dmp.Match_Threshold = 0.5;
313
+ dmp.Match_Distance = 1000;
314
+ const dmpPatches = dmp.patch_make(search, replace);
315
+ const [newText, applyResults] = dmp.patch_apply(dmpPatches, result);
316
+ // Kiểm tra xem có apply được hết các hunk không
317
+ const successCount = applyResults.filter(Boolean).length;
318
+ const successRate = successCount / applyResults.length;
319
+ if (successRate === 1 && newText !== result) {
320
+ console.log(`[ApplyPatch] DMP Patch applied via fuzzy logic.`);
321
+ result = newText;
322
+ }
323
+ else {
324
+ console.error(`[ApplyPatch] All strategies failed for block starting with: ${search.substring(0, 30)}...`);
325
+ // Tuỳ chọn: Throw error để báo cho LLM biết là apply thất bại
326
+ // throw new Error("Could not apply patch block");
327
+ }
328
+ }
329
+ catch (e) {
330
+ console.error(`[ApplyPatch] DMP error:`, e);
331
+ }
173
332
  }
174
333
  return result;
175
334
  };
335
+ exports.applyPatch = applyPatch;
176
336
  // ==========================================
177
337
  // 3. WEB UI SERVER (UPDATED UI)
178
338
  // ==========================================
@@ -199,15 +359,42 @@ const startServer = async (rootDir) => {
199
359
  });
200
360
  // API 3: Apply Patch
201
361
  app.post("/api/apply", async (req, res) => {
202
- const { llmResponse, filePaths } = req.body;
362
+ var _a;
363
+ const { llmResponse, filePaths: selectedPaths } = req.body;
364
+ // 1. Parse all patches from LLM response
365
+ const filePatches = parseLLMPatch(llmResponse);
366
+ // 2. Scan project files
203
367
  const allFiles = await scanRecursively(rootDir, rootDir);
204
- const selectedFiles = allFiles.filter((f) => filePaths.includes(f.path));
205
368
  const modifiedFiles = [];
206
- for (const file of selectedFiles) {
207
- const newContent = applyPatch(file.content, llmResponse);
208
- if (newContent !== file.content) {
209
- await promises_1.default.writeFile(file.fullPath, newContent, "utf-8");
210
- modifiedFiles.push(file.path);
369
+ // 3. Match patches to files and apply
370
+ for (const file of allFiles) {
371
+ // Find patches meant for this specific file path
372
+ // Note: LLM might use different path separators, so we normalize
373
+ const normalizedPath = file.path.replace(/\\/g, '/');
374
+ const patchesForFile = (_a = Object.entries(filePatches).find(([path]) => {
375
+ const normalizedKey = path.replace(/\\/g, '/');
376
+ return normalizedKey === normalizedPath || normalizedPath.endsWith('/' + normalizedKey);
377
+ })) === null || _a === void 0 ? void 0 : _a[1];
378
+ // Only apply if the file was selected in UI AND has patches in the response
379
+ if (selectedPaths.includes(file.path) && patchesForFile) {
380
+ console.log(`[Apply] Processing ${patchesForFile.length} blocks for: ${file.path}`);
381
+ const newContent = (0, exports.applyPatch)(file.content, patchesForFile);
382
+ if (newContent !== file.content) {
383
+ await promises_1.default.writeFile(file.fullPath, newContent, "utf-8");
384
+ modifiedFiles.push(file.path);
385
+ }
386
+ }
387
+ }
388
+ // Fallback: If no matched files but only one file was selected, try applying "unknown" patches
389
+ if (modifiedFiles.length === 0 && selectedPaths.length === 1 && filePatches["unknown"]) {
390
+ const file = allFiles.find(f => f.path === selectedPaths[0]);
391
+ if (file) {
392
+ console.log(`[Apply] Fallback: Applying unknown blocks to single selected file: ${file.path}`);
393
+ const newContent = (0, exports.applyPatch)(file.content, filePatches["unknown"]);
394
+ if (newContent !== file.content) {
395
+ await promises_1.default.writeFile(file.fullPath, newContent, "utf-8");
396
+ modifiedFiles.push(file.path);
397
+ }
211
398
  }
212
399
  }
213
400
  res.json({ modified: modifiedFiles });
@@ -335,7 +522,8 @@ const startServer = async (rootDir) => {
335
522
 
336
523
  <div class="flex-1 flex flex-col bg-gray-850 min-w-0">
337
524
 
338
- <div class="flex-shrink-0 p-6 border-b border-apple-border">
525
+ <!-- 1. Instruction (Takes more space and resizable) -->
526
+ <div class="flex-1 p-6 flex flex-col min-h-0 border-b border-apple-border">
339
527
  <div class="flex justify-between items-end mb-3">
340
528
  <div>
341
529
  <h2 class="text-sm font-semibold text-gray-200">1. Instruction</h2>
@@ -351,12 +539,12 @@ const startServer = async (rootDir) => {
351
539
  </button>
352
540
  </div>
353
541
  <textarea x-model="instruction"
354
- class="w-full bg-apple-panel border border-apple-border rounded p-3 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-apple-blue/50 focus:ring-1 focus:ring-apple-blue/50 transition resize-none font-mono"
355
- rows="3"
542
+ class="w-full flex-1 bg-apple-panel border border-apple-border rounded p-3 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-apple-blue/50 focus:ring-1 focus:ring-apple-blue/50 transition resize-y font-mono min-h-[120px]"
356
543
  placeholder="e.g. Refactor the Button component to use TypeScript interfaces..."></textarea>
357
544
  </div>
358
545
 
359
- <div class="flex-1 p-6 flex flex-col min-h-0">
546
+ <!-- 2. LLM Patch (Fixed smaller size) -->
547
+ <div class="h-64 p-6 flex flex-col flex-shrink-0">
360
548
  <div class="flex justify-between items-end mb-3">
361
549
  <div>
362
550
  <h2 class="text-sm font-semibold text-gray-200">2. LLM Patch</h2>
@@ -369,11 +557,9 @@ const startServer = async (rootDir) => {
369
557
  <span x-show="applying">Applying...</span>
370
558
  </button>
371
559
  </div>
372
- <div class="flex-1 relative">
373
- <textarea x-model="llmResponse"
374
- class="absolute inset-0 w-full h-full bg-apple-panel border border-apple-border rounded p-3 text-sm text-gray-300 font-mono placeholder-gray-600 focus:outline-none focus:border-emerald-500/50 focus:ring-1 focus:ring-emerald-500/50 transition resize-none leading-relaxed"
375
- placeholder="<<<<<<< SEARCH..."></textarea>
376
- </div>
560
+ <textarea x-model="llmResponse"
561
+ class="w-full h-40 bg-apple-panel border border-apple-border rounded p-3 text-sm text-gray-300 font-mono placeholder-gray-600 focus:outline-none focus:border-emerald-500/50 focus:ring-1 focus:ring-emerald-500/50 transition resize-none leading-relaxed"
562
+ placeholder="<<<<<<< SEARCH..."></textarea>
377
563
  </div>
378
564
 
379
565
  </div>
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
+ "diff-match-patch": "^1.0.5",
10
11
  "express": "^5.2.1",
11
12
  "inquirer": "^12.9.6",
12
13
  "lodash": "^4.17.21",
@@ -22,6 +23,7 @@
22
23
  "devDependencies": {
23
24
  "@types/babel__standalone": "^7.1.9",
24
25
  "@types/commander": "^2.12.5",
26
+ "@types/diff-match-patch": "^1.0.36",
25
27
  "@types/express": "^5.0.6",
26
28
  "@types/lodash": "^4.17.20",
27
29
  "@types/node": "^24.1.0",
@@ -92,6 +94,8 @@
92
94
 
93
95
  "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
94
96
 
97
+ "@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
98
+
95
99
  "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
96
100
 
97
101
  "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="],
@@ -180,6 +184,8 @@
180
184
 
181
185
  "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
182
186
 
187
+ "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
188
+
183
189
  "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
184
190
 
185
191
  "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="],
package/dist/index.js CHANGED
@@ -411,7 +411,7 @@ class RoomState {
411
411
  return this.isConnected;
412
412
  }
413
413
  }
414
- commander_1.program.version("0.0.28").description("Hapico CLI for project management");
414
+ commander_1.program.version("0.0.29").description("Hapico CLI for project management");
415
415
  commander_1.program
416
416
  .command("clone <id>")
417
417
  .description("Clone a project by ID")