thinkncollab-cli 0.0.77 → 0.0.78

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/commands/pull.js CHANGED
@@ -19,17 +19,15 @@ function readToken() {
19
19
  return { token: data.token, email: data.email };
20
20
  }
21
21
 
22
- /** ------------------ DOWNLOAD FILE ------------------ **/ await axios
22
+ /** ------------------ DOWNLOAD FILE ------------------ **/
23
23
  async function downloadFile(url, filePath) {
24
24
  return new Promise((resolve, reject) => {
25
25
  const fileDir = path.dirname(filePath);
26
26
  if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir, { recursive: true });
27
-
28
27
  const file = fs.createWriteStream(filePath);
29
28
  const client = url.startsWith("https") ? https : http;
30
29
 
31
- client
32
- .get(url, (response) => {
30
+ client.get(url, (response) => {
33
31
  if (response.statusCode !== 200) {
34
32
  reject(new Error(`Failed to download: ${response.statusCode}`));
35
33
  return;
@@ -47,7 +45,7 @@ async function downloadFile(url, filePath) {
47
45
  });
48
46
  }
49
47
 
50
- /** ------------------ PROCESS FOLDER CONTENT ------------------ **/
48
+ /**------------------PROCESS FOLDER CONTENT------------------**/
51
49
  async function processFolderContent(content, basePath = "") {
52
50
  let downloadedCount = 0;
53
51
  let skippedCount = 0;
@@ -0,0 +1,465 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import axios from 'axios';
5
+ import inquirer from 'inquirer';
6
+ import crypto from 'crypto';
7
+ import FormData from 'form-data';
8
+ import machine from 'node-machine-id';
9
+ const RC_FILE = path.join(os.homedir(), '.tncrc');
10
+ const VERSION_FILE = path.join(process.cwd(), '.tncversions');
11
+ const BASE_URL = 'http://localhost:3001/rooms';
12
+ const CWD = process.cwd();
13
+ /** ------------------ READ TOKEN ------------------ **/
14
+
15
+ function readToken() {
16
+ if (!fs.existsSync(RC_FILE)) {
17
+ console.error("❌ Not logged in. Run 'tnc login' first.");
18
+ process.exit(1);
19
+ }
20
+ const data = JSON.parse(fs.readFileSync(RC_FILE));
21
+ return { token: data.token, email: data.email };
22
+ }
23
+
24
+ /** ------------------ IGNORE HANDLING ------------------ **/
25
+ function loadIgnore(folderPath) {
26
+ const ignoreFile = path.join(folderPath, ".ignoretnc");
27
+ if (!fs.existsSync(ignoreFile)) return [];
28
+ return fs
29
+ .readFileSync(ignoreFile, "utf-8")
30
+ .split("\n")
31
+ .map(line => line.trim())
32
+ .filter(line => line && !line.startsWith("#"));
33
+ }
34
+
35
+ function shouldIgnore(relativePath, ignoreList) {
36
+ return ignoreList.some(pattern => {
37
+ if (pattern.endsWith("/**")) {
38
+ const folder = pattern.slice(0, -3);
39
+ return relativePath === folder || relativePath.startsWith(folder + path.sep);
40
+ }
41
+ if (pattern.startsWith("*.")) {
42
+ return relativePath.endsWith(pattern.slice(1));
43
+ }
44
+ return relativePath === pattern;
45
+ });
46
+ }
47
+
48
+ /** ------------------ SCAN FOLDER ------------------ **/
49
+ function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
50
+ const items = fs.readdirSync(folderPath, { withFileTypes: true });
51
+ const result = [];
52
+
53
+ for (const item of items) {
54
+ const fullPath = path.join(folderPath, item.name);
55
+ const relativePath = path.relative(rootPath, fullPath);
56
+
57
+ if (shouldIgnore(relativePath, ignoreList)) {
58
+ console.log("⚠️ Ignored:", relativePath);
59
+ continue;
60
+ }
61
+
62
+ if (item.isDirectory()) {
63
+ result.push({
64
+ name: item.name,
65
+ type: "folder",
66
+ children: scanFolder(fullPath, ignoreList, rootPath),
67
+ path: item.name
68
+ });
69
+ } else {
70
+ const stats = fs.statSync(fullPath);
71
+ result.push({
72
+ name: item.name,
73
+ type: "file",
74
+ path: relativePath,
75
+ size: stats.size
76
+ });
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+
82
+ /** ------------------ VERSIONING ------------------ **/
83
+ function loadVersions() {
84
+ if (!fs.existsSync(VERSION_FILE)) return {};
85
+ const data = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
86
+
87
+ // Backward compatibility: if old format (just strings), convert to new format
88
+ const converted = {};
89
+ for (const [path, value] of Object.entries(data)) {
90
+ if (typeof value === 'string') {
91
+ converted[path] = { hash: value, url: '', version: 1 };
92
+ } else {
93
+ converted[path] = value;
94
+ }
95
+ }
96
+ return converted;
97
+ }
98
+
99
+ function saveVersions(versionMap) {
100
+ fs.writeFileSync(VERSION_FILE, JSON.stringify(versionMap, null, 2));
101
+ }
102
+
103
+ function computeFileHash(filePath) {
104
+ try {
105
+ if (!fs.existsSync(filePath)) return null;
106
+
107
+ const stats = fs.statSync(filePath);
108
+ if (stats.isDirectory()) {
109
+ // For folders: hash of sorted file names + their hashes
110
+ const items = fs.readdirSync(filePath).sort();
111
+ const childrenHashes = items.map(name =>
112
+ computeFileHash(path.join(filePath, name))
113
+ ).filter(Boolean).join('|');
114
+
115
+ return crypto.createHash("sha256")
116
+ .update(`folder:${filePath}|${childrenHashes}`)
117
+ .digest("hex");
118
+ } else {
119
+ // For files: hash of content + file metadata
120
+ const content = fs.readFileSync(filePath);
121
+ const stats = fs.statSync(filePath);
122
+
123
+ return crypto.createHash("sha256")
124
+ .update(content)
125
+ .update(`size:${stats.size}|mtime:${stats.mtimeMs}`)
126
+ .digest("hex");
127
+ }
128
+ } catch (err) {
129
+ console.error(`❌ Error computing hash for ${filePath}:`, err.message);
130
+ return null;
131
+ }
132
+ }
133
+
134
+ function checkChanges(fileTree, versionMap, rootPath = CWD) {
135
+ return fileTree.map(item => {
136
+ const fullPath = path.join(rootPath, item.path || item.name);
137
+ const relativePath = item.path || item.name;
138
+
139
+ let hash = null;
140
+ let changed = true;
141
+
142
+ try {
143
+ if (fs.existsSync(fullPath)) {
144
+ hash = computeFileHash(fullPath);
145
+
146
+ // Check against previous version
147
+ const prevVersion = versionMap[relativePath];
148
+ if (prevVersion && prevVersion.hash === hash) {
149
+ changed = false;
150
+ }
151
+ }
152
+ } catch (err) {
153
+ console.error(` Error checking changes for ${relativePath}:`, err.message);
154
+ }
155
+
156
+ const newItem = {
157
+ ...item,
158
+ changed,
159
+ hash,
160
+ path: relativePath // Ensure consistent path
161
+ };
162
+
163
+ // Recursively check children for folders
164
+ if (item.type === "folder" && item.children && item.children.length > 0) {
165
+ newItem.children = checkChanges(item.children, versionMap, rootPath);
166
+ newItem.changed = newItem.changed || newItem.children.some(c => c.changed);
167
+ }
168
+
169
+ return newItem;
170
+ });
171
+ }
172
+
173
+ /** ------------------ MERGE CONFLICT HANDLING ------------------ **/
174
+ async function checkMergeConflicts(roomId, branch, localFiles) {
175
+ try {
176
+ console.log('🔍 Checking for merge conflicts...');
177
+
178
+
179
+ const cloudFiles = await getCloudFiles(roomId, branch);
180
+ const conflicts = [];
181
+
182
+ for (const localFile of localFiles) {
183
+ const cloudFile = cloudFiles.find(f => f.path === localFile.path);
184
+
185
+ if (cloudFile && localFile.hash !== cloudFile.hash) {
186
+
187
+ conflicts.push({
188
+ file: localFile.path,
189
+ message: `File changed by another user in cloud`
190
+ });
191
+ }
192
+ }
193
+
194
+ return conflicts;
195
+ } catch (error) {
196
+ console.log(' Could not check conflicts:', error.message);
197
+ return [];
198
+ }
199
+ }
200
+
201
+ async function getCloudFiles(roomId, branch) {
202
+
203
+ const { token, email } = readToken();
204
+
205
+ try {
206
+ const response = await axios.get(
207
+ `${BASE_URL}/${roomId}/files`,
208
+ {
209
+ params: { branch },
210
+ headers: { authorization: `Bearer ${token}`, email }
211
+ }
212
+ );
213
+ return response.data.files || [];
214
+ } catch (error) {
215
+ console.log('❌ Could not fetch cloud files:', error.message);
216
+ return [];
217
+ }
218
+ }
219
+
220
+ async function handleConflicts(conflicts) {
221
+ if (conflicts.length === 0) return true;
222
+
223
+ console.log('\n MERGE CONFLICTS DETECTED:');
224
+ conflicts.forEach(conflict => {
225
+ console.log(`📄 ${conflict.file} - ${conflict.message}`);
226
+ });
227
+
228
+ console.log('\n✅ Auto-resolving: Using your local version for all conflicts');
229
+ console.log('💡 Note: Other users\' changes will be overwritten');
230
+
231
+ return true;
232
+ }
233
+
234
+ /** ------------------ CLOUDINARY UPLOAD ------------------ **/
235
+ async function uploadFileSigned(filePath, folder, roomId, token, email) {
236
+ const filename = path.basename(filePath);
237
+
238
+ const sigRes = await axios.post(
239
+ `${BASE_URL}/${roomId}/get-upload-signature`,
240
+ { filename, folder, roomId },
241
+ { headers: { authorization: `Bearer ${token}`, email } }
242
+ );
243
+
244
+ const { signature, timestamp, api_key, cloud_name } = sigRes.data;
245
+
246
+ const formData = new FormData();
247
+ formData.append("file", fs.createReadStream(filePath));
248
+ formData.append("folder", folder);
249
+ formData.append("public_id", filename);
250
+ formData.append("timestamp", timestamp);
251
+ formData.append("signature", signature);
252
+ formData.append("api_key", api_key);
253
+
254
+ const cloudRes = await axios.post(
255
+ `https://api.cloudinary.com/v1_1/${cloud_name}/auto/upload`,
256
+ formData,
257
+ { headers: formData.getHeaders() }
258
+ );
259
+
260
+ return cloudRes.data.secure_url;
261
+ }
262
+
263
+ /** ------------------ UPLOAD TREE - FIXED PATH CONSTRUCTION ------------------ **/
264
+ async function uploadTree(fileTree, folderHex, roomId, token, email, previousVersions, parentPath = "") {
265
+ const uploaded = [];
266
+
267
+ for (const node of fileTree) {
268
+
269
+ let relativePath;
270
+ if (parentPath) {
271
+ relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
272
+ } else {
273
+ relativePath = node.path || node.name;
274
+ }
275
+
276
+ if (node.type === "folder") {
277
+ const children = await uploadTree(node.children, folderHex, roomId, token, email, previousVersions, relativePath);
278
+
279
+ uploaded.push({
280
+ ...node,
281
+ children,
282
+ hash: node.hash,
283
+ path: relativePath
284
+ });
285
+ } else {
286
+ const prevFile = previousVersions[relativePath];
287
+
288
+ if (node.changed) {
289
+ try {
290
+ console.log(` Uploading changed file: ${relativePath}`);
291
+ const url = await uploadFileSigned(path.join(CWD, node.path), `tnc_uploads/${folderHex}`, roomId, token, email);
292
+ console.log(` Uploaded: ${relativePath}`);
293
+
294
+ uploaded.push({
295
+ ...node,
296
+ url,
297
+ hash: node.hash,
298
+ path: relativePath,
299
+ version: prevFile ? prevFile.version + 1 : 1
300
+ });
301
+ } catch (err) {
302
+ console.error(` Failed to upload ${relativePath}:`, err.message);
303
+
304
+ uploaded.push({
305
+ ...node,
306
+ url: prevFile?.url,
307
+ hash: node.hash,
308
+ path: relativePath,
309
+ version: prevFile?.version || 1
310
+ });
311
+ }
312
+ } else {
313
+
314
+ console.log(` Using previous version for: ${relativePath}`);
315
+ uploaded.push({
316
+ ...node,
317
+ url: prevFile?.url,
318
+ hash: node.hash,
319
+ path: relativePath,
320
+ version: prevFile?.version || 1
321
+ });
322
+ }
323
+ }
324
+ }
325
+
326
+ return uploaded;
327
+ }
328
+
329
+ /** ------------------ PUSH FUNCTION WITH MERGE CONFLICT CHECK ------------------ **/
330
+ async function push(roomId, targetPath) {
331
+ const { token, email } = readToken();
332
+ const tncMetaPath = path.join(process.cwd(), ".tnc", ".tncmeta.json");
333
+ if (!fs.existsSync(tncMetaPath)) {
334
+ console.error(" Project not initialized. Run 'tnc-cli init' first.");
335
+ process.exit(1);
336
+ }
337
+ const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
338
+ const projectId = meta.projectId;
339
+ const branch = meta.branch;
340
+ const branchId = meta.branchId;
341
+ const tncPushInfo = path.join(process.cwd(), ".tnc", ".tncpush.json");
342
+
343
+ const lastFolderId = JSON.parse(fs.readFileSync(tncPushInfo, "utf-8")).folderId;
344
+ const stats = fs.statSync(targetPath);
345
+ const rootFolder = stats.isDirectory() ? targetPath : path.dirname(targetPath);
346
+ const ignoreList = loadIgnore(rootFolder);
347
+ let content;
348
+ if (stats.isDirectory()) {
349
+ content = scanFolder(targetPath, ignoreList);
350
+ } else {
351
+ const relativePath = path.basename(targetPath);
352
+ content = shouldIgnore(relativePath, ignoreList)
353
+ ? []
354
+ : [{ name: relativePath, type: "file", path: relativePath, size: stats.size }];
355
+ }
356
+ if (!content.length) {
357
+ console.log(" Nothing to upload (all ignored).");
358
+ return;
359
+ }
360
+ const previousVersions = loadVersions();
361
+ console.log(' Previous versions:', Object.keys(previousVersions).length);
362
+
363
+ const contentWithChanges = checkChanges(content, previousVersions);
364
+
365
+
366
+ const changedFiles = contentWithChanges.flatMap(item =>
367
+ item.changed ? [item.path] : []
368
+ );
369
+ console.log('Changed files:', changedFiles.length, changedFiles);
370
+
371
+ const hasChanges = contentWithChanges.some(item => item.changed || (item.children && item.children.some(c => c.changed)));
372
+
373
+ if (!hasChanges) {
374
+ console.log(" No changes detected since last push.");
375
+ return;
376
+ }
377
+
378
+
379
+ const localFiles = getLocalFilesList(contentWithChanges);
380
+ const conflicts = await checkMergeConflicts(roomId, branch, localFiles);
381
+
382
+ if (conflicts.length > 0) {
383
+ const shouldContinue = await handleConflicts(conflicts);
384
+ if (!shouldContinue) {
385
+ console.error('Push cancelled due to conflicts');
386
+ return;
387
+ }
388
+ }
389
+
390
+ try {
391
+ const folderHex = crypto.createHash("md5").update(path.basename(targetPath) + Date.now()).digest("hex");
392
+ console.log(" Uploading to Cloudinary...");
393
+ const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email, previousVersions);
394
+ console.log(" Sending metadata...");
395
+ const res = await axios.post(
396
+ `${BASE_URL}/${roomId}/upload`,
397
+ { folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId, latestFolderId: lastFolderId, branch: branch, branchId: branchId },
398
+ { headers: { authorization: `Bearer ${token}`, email } }
399
+ );
400
+ console.log(" Upload complete! Metadata stored successfully.");
401
+ const newVersionMap = {};
402
+ const flattenAndStore = (items) => {
403
+ for (const item of items) {
404
+ if (item.type === "file") {
405
+ newVersionMap[item.path] = {
406
+ hash: item.hash,
407
+ url: item.url,
408
+ version: item.version || 1
409
+ };
410
+ }
411
+ if (item.children) flattenAndStore(item.children);
412
+ }
413
+ };
414
+ flattenAndStore(uploadedTree);
415
+ saveVersions(newVersionMap);
416
+
417
+ const versionNumbers = Object.values(newVersionMap).map(f => f.version || 1);
418
+ const latestVersion = versionNumbers.length > 0 ? Math.max(...versionNumbers) : 1;
419
+ const newPushRecord = {
420
+ version: latestVersion,
421
+ pushedAt: new Date().toISOString(),
422
+ roomId: roomId,
423
+ pushedBy: email,
424
+ projectId: projectId,
425
+ folderId: res.data.folderId,
426
+ branch: res.data.branch,
427
+ branchId: res.data.branchId
428
+ };
429
+
430
+ const pushFilePath = path.join(CWD, ".tnc", ".tncpush.json");
431
+ fs.writeFileSync(
432
+ pushFilePath,
433
+ JSON.stringify(newPushRecord, null, 2)
434
+ );
435
+
436
+ console.log(` Saved ${Object.keys(newVersionMap).length} files to .tncversions`);
437
+
438
+ } catch (err) {
439
+ console.error(" Upload failed:", err.response?.data || err.message);
440
+ }
441
+ }
442
+
443
+ function getLocalFilesList(contentWithChanges) {
444
+ const files = [];
445
+
446
+ function extractFiles(items) {
447
+ for (const item of items) {
448
+ if (item.type === "file") {
449
+ files.push({
450
+ path: item.path,
451
+ hash: item.hash,
452
+ content: fs.readFileSync(path.join(CWD, item.path), 'utf-8')
453
+ });
454
+ }
455
+ if (item.children) {
456
+ extractFiles(item.children);
457
+ }
458
+ }
459
+ }
460
+
461
+ extractFiles(contentWithChanges);
462
+ return files;
463
+ }
464
+
465
+ export default push;
@@ -4,17 +4,13 @@ import axios from "axios";
4
4
  import os from "os";
5
5
 
6
6
  const homeDir = os.homedir();
7
- const url = "http://localhost:3001/cli/invite"; // backend endpoint
8
-
9
- // Get saved email from ~/.tncrc
7
+ const url = "https://thinkncollab.com/cli/invite";
10
8
  async function getEmail() {
11
9
  const rcFile = path.join(homeDir, ".tncrc");
12
-
13
10
  if (!fs.existsSync(rcFile)) {
14
- console.log("⚠️ Please login first!");
11
+ console.log(" Please login first!");
15
12
  process.exit(1);
16
13
  }
17
-
18
14
  const content = fs.readFileSync(rcFile, "utf-8");
19
15
  const email = JSON.parse(content).email;
20
16
  return email;
@@ -22,40 +18,35 @@ async function getEmail() {
22
18
  async function getToken() {
23
19
  const rcFile = path.join(homeDir, '.tncrc');
24
20
  if(!fs.readFileSync(rcFile)){
25
- console.log("⚠️ Please login first! ")
21
+ console.log(" Please login first! ")
26
22
  }
27
23
  const content = fs.readFileSync(rcFile, 'utf-8');
28
24
  const token = JSON.parse(content).token;
29
25
  return token;
30
-
31
-
32
26
  }
33
-
34
- // Fetch tasks for a given room
35
27
  async function sendInvite(inviteeEmail) {
36
28
  try {
37
29
  const email = await getEmail();
38
30
  const token = await getToken();
39
-
40
- const res = await axios.get(`${url}/${roomId}`, {
41
- params: { email, token,inviteeEmail }
42
- });
43
-
44
- const tasks = res.data.tasks;
45
-
46
- if (!tasks.length) {
47
- console.log("📭 No tasks assigned.");
48
- return;
49
- }
50
-
51
- console.log("📋 Your Tasks:");
52
- tasks.forEach((task, i) => {
53
- console.log(`${i + 1}. ${task.title} — ${task.status}`);
54
- });
55
-
31
+ const CWD = process.cwd();
32
+ const metaDataPath = path.join(".tnc", '.tncmeta.json');
33
+ const metaData = JSON.parse(fs.readFileSync(path.join(CWD, metaDataPath), 'utf-8'));
34
+ const roomId = metaData.roomId;
35
+ console.log(email, token, roomId, inviteeEmail);
36
+ const res = await axios({
37
+ method: 'post',
38
+ url: `${url}/${roomId}`,
39
+ params: {
40
+ email,
41
+ token,
42
+ inviteeEmail,
43
+ roomId
44
+ }
45
+ });
46
+ console.log(" Invitation sent successfully to", inviteeEmail);
47
+ console.log("Invite Link:", res.data.invitationLink);
56
48
  } catch (error) {
57
- console.error(" Error fetching tasks:", error.response?.data || error.message);
49
+ console.error(" Error while generating the invitation link:", error.response?.data || error.message);
58
50
  }
59
51
  }
60
-
61
52
  export default sendInvite;
@@ -0,0 +1,67 @@
1
+ import axios from 'axios';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import machine from 'node-machine-id';
6
+ import inquirer from "inquirer";
7
+
8
+
9
+
10
+ const CWD = process.cwd();
11
+
12
+ const tncrcPath = path.join(os.homedir(), '.tncrc');
13
+ const tncMetaPath = path.join(CWD, '.tnc', '.tncmeta.json');
14
+
15
+ async function taskCompletion() {
16
+ try {
17
+ if (!fs.existsSync(tncrcPath)) {
18
+ console.error("❌ You are not logged in. Run 'tnc login' first.");
19
+ process.exit(1);
20
+ }
21
+
22
+ if (!fs.existsSync(tncMetaPath)) {
23
+ console.error("❌ This directory is not initialized. Run 'tnc init' first.");
24
+ process.exit(1);
25
+ }
26
+
27
+ const tncrcData = fs.readFileSync(tncrcPath, "utf-8");
28
+ const { email, token } = JSON.parse(tncrcData);
29
+
30
+ const tncMetaData = fs.readFileSync(tncMetaPath, "utf-8");
31
+ const { projectId } = JSON.parse(tncMetaData);
32
+
33
+ const { taskId } = await inquirer.prompt([
34
+ {
35
+ type: "input",
36
+ name: "taskId",
37
+ message: "Enter Task ID to mark as complete:",
38
+ },
39
+ ]);
40
+
41
+ const trimmedTaskId = taskId.trim();
42
+
43
+ if (!trimmedTaskId) {
44
+ console.error("❌ Task ID cannot be empty.");
45
+ process.exit(1);
46
+ }
47
+
48
+ const response = await axios.post(
49
+ `https://thinkncollab.com/cli/taskcomplete/${trimmedTaskId}`,
50
+ {
51
+ email,
52
+ token,
53
+ machineId: machine.machineIdSync(),
54
+ }
55
+ );
56
+
57
+ console.log("✅ Task marked as complete:", response.data.message);
58
+ } catch (err) {
59
+ if (err.response) {
60
+ console.error("❌ Error:", err.response.data.error);
61
+ } else {
62
+ console.error("❌ An unexpected error occurred:", err.message);
63
+ }
64
+ process.exit(1);
65
+ }
66
+ }
67
+ export default taskCompletion;
@@ -0,0 +1,23 @@
1
+ import os from 'os'
2
+ import fs from 'fs'
3
+ import path from 'path';
4
+
5
+ const RC_FILE = os.homedir();
6
+
7
+ async function getVerify() {
8
+ const tncfilepath = path.join(RC_FILE, ".tncrc");
9
+
10
+ if (!fs.existsSync(tncfilepath)) {
11
+ throw new Error("Configuration file not found. Please login first.");
12
+ }
13
+
14
+ const doc = await fs.readFileSync(tncfilepath, "utf-8");
15
+ const obj = JSON.parse(doc);
16
+
17
+
18
+
19
+ return obj;
20
+
21
+ }
22
+
23
+ export default getVerify;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thinkncollab-cli",
3
3
  "author": "Raman Singh",
4
- "version": "0.0.77",
4
+ "version": "0.0.78",
5
5
  "description": "CLI tool for ThinkNCollab",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "dependencies": {
15
15
  "axios": "^1.12.2",
16
16
  "chalk": "^5.6.2",
17
- "inquirer": "^9.3.8"
17
+ "inquirer": "^9.3.8",
18
+ "node-machine-id": "^1.1.12"
18
19
  }
19
- }
20
+ }