thinkncollab-cli 0.0.76 → 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/bin/index.js +31 -384
- package/commands/branch.js +17 -14
- package/commands/connect.js +60 -7
- package/commands/init.js +8 -2
- package/commands/merge.js +0 -0
- package/commands/myTask.js +7 -3
- package/commands/pull.js +3 -5
- package/commands/push.js +465 -0
- package/commands/sendInvite.js +21 -30
- package/commands/taskCompletion.js +67 -0
- package/lib/getVerify.js +23 -0
- package/package.json +4 -3
- package/.vscode/geekload-types.d.ts +0 -455
- package/bin/tnc.js +0 -21
- package/lib/api.js +0 -28
- package/lib/scanner.js +0 -27
- package/lib/uploader.js +0 -5
package/bin/index.js
CHANGED
|
@@ -4,8 +4,6 @@ import axios from "axios";
|
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import os from "os";
|
|
6
6
|
import path from "path";
|
|
7
|
-
import crypto from "crypto";
|
|
8
|
-
import FormData from "form-data";
|
|
9
7
|
import projectInit from "../commands/init.js";
|
|
10
8
|
import Status from "../commands/status.js";
|
|
11
9
|
import pull from "../commands/pull.js";
|
|
@@ -16,12 +14,16 @@ import createBranch from "../commands/branch.js"
|
|
|
16
14
|
import myTask from "../commands/myTask.js"
|
|
17
15
|
import sendInvite from '../commands/sendInvite.js'
|
|
18
16
|
import connect from '../commands/connect.js'
|
|
17
|
+
import machine from "node-machine-id";
|
|
18
|
+
import push from "../commands/push.js";
|
|
19
|
+
import taskCompletion from "../commands/taskCompletion.js";
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
const RC_FILE = path.join(os.homedir(), ".tncrc");
|
|
23
25
|
const VERSION_FILE = path.join(process.cwd(), ".tncversions");
|
|
24
|
-
const BASE_URL = "
|
|
26
|
+
const BASE_URL = "https://thinkncollab.com/rooms";
|
|
25
27
|
const CWD = process.cwd();
|
|
26
28
|
|
|
27
29
|
/** ------------------ LOGIN ------------------ **/
|
|
@@ -29,13 +31,15 @@ async function login() {
|
|
|
29
31
|
const answers = await inquirer.prompt([
|
|
30
32
|
{ type: "input", name: "email", message: "Email:" },
|
|
31
33
|
{ type: "password", name: "password", message: "Password:" }
|
|
34
|
+
|
|
32
35
|
]);
|
|
33
36
|
|
|
34
37
|
try {
|
|
35
38
|
console.log("🔐 Logging in...");
|
|
36
39
|
const res = await axios.post("https://thinkncollab.com/login", {
|
|
37
40
|
email: answers.email,
|
|
38
|
-
password: answers.password
|
|
41
|
+
password: answers.password,
|
|
42
|
+
machineId: await machine.machineIdSync()
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
const { token, email } = res.data;
|
|
@@ -60,377 +64,6 @@ async function logout() {
|
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
/** ------------------ TOKEN UTILS ------------------ **/
|
|
64
|
-
function readToken() {
|
|
65
|
-
if (!fs.existsSync(RC_FILE)) {
|
|
66
|
-
console.error("❌ Not logged in. Run 'tnc login' first.");
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
const data = JSON.parse(fs.readFileSync(RC_FILE));
|
|
70
|
-
return { token: data.token, email: data.email };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** ------------------ IGNORE HANDLING ------------------ **/
|
|
74
|
-
function loadIgnore(folderPath) {
|
|
75
|
-
const ignoreFile = path.join(folderPath, ".ignoretnc");
|
|
76
|
-
if (!fs.existsSync(ignoreFile)) return [];
|
|
77
|
-
return fs
|
|
78
|
-
.readFileSync(ignoreFile, "utf-8")
|
|
79
|
-
.split("\n")
|
|
80
|
-
.map(line => line.trim())
|
|
81
|
-
.filter(line => line && !line.startsWith("#"));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function shouldIgnore(relativePath, ignoreList) {
|
|
85
|
-
return ignoreList.some(pattern => {
|
|
86
|
-
if (pattern.endsWith("/**")) {
|
|
87
|
-
const folder = pattern.slice(0, -3);
|
|
88
|
-
return relativePath === folder || relativePath.startsWith(folder + path.sep);
|
|
89
|
-
}
|
|
90
|
-
if (pattern.startsWith("*.")) {
|
|
91
|
-
return relativePath.endsWith(pattern.slice(1));
|
|
92
|
-
}
|
|
93
|
-
return relativePath === pattern;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** ------------------ SCAN FOLDER - FIXED ------------------ **/
|
|
98
|
-
function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
|
|
99
|
-
const items = fs.readdirSync(folderPath, { withFileTypes: true });
|
|
100
|
-
const result = [];
|
|
101
|
-
|
|
102
|
-
for (const item of items) {
|
|
103
|
-
const fullPath = path.join(folderPath, item.name);
|
|
104
|
-
const relativePath = path.relative(rootPath, fullPath);
|
|
105
|
-
|
|
106
|
-
if (shouldIgnore(relativePath, ignoreList)) {
|
|
107
|
-
console.log("⚠️ Ignored:", relativePath);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (item.isDirectory()) {
|
|
112
|
-
result.push({
|
|
113
|
-
name: item.name,
|
|
114
|
-
type: "folder",
|
|
115
|
-
children: scanFolder(fullPath, ignoreList, rootPath), // ✅ Keep same rootPath
|
|
116
|
-
path: item.name // ✅ Only store folder name, not full path
|
|
117
|
-
});
|
|
118
|
-
} else {
|
|
119
|
-
const stats = fs.statSync(fullPath);
|
|
120
|
-
result.push({
|
|
121
|
-
name: item.name,
|
|
122
|
-
type: "file",
|
|
123
|
-
path: relativePath, // ✅ Keep relative path for files
|
|
124
|
-
size: stats.size
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** ------------------ VERSIONING ------------------ **/
|
|
132
|
-
function loadVersions() {
|
|
133
|
-
if (!fs.existsSync(VERSION_FILE)) return {};
|
|
134
|
-
const data = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
|
|
135
|
-
|
|
136
|
-
// Backward compatibility: if old format (just strings), convert to new format
|
|
137
|
-
const converted = {};
|
|
138
|
-
for (const [path, value] of Object.entries(data)) {
|
|
139
|
-
if (typeof value === 'string') {
|
|
140
|
-
converted[path] = { hash: value, url: '', version: 1 };
|
|
141
|
-
} else {
|
|
142
|
-
converted[path] = value;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return converted;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function saveVersions(versionMap) {
|
|
149
|
-
fs.writeFileSync(VERSION_FILE, JSON.stringify(versionMap, null, 2));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function computeFileHash(filePath) {
|
|
153
|
-
try {
|
|
154
|
-
if (!fs.existsSync(filePath)) return null;
|
|
155
|
-
|
|
156
|
-
const stats = fs.statSync(filePath);
|
|
157
|
-
if (stats.isDirectory()) {
|
|
158
|
-
// For folders: hash of sorted file names + their hashes
|
|
159
|
-
const items = fs.readdirSync(filePath).sort();
|
|
160
|
-
const childrenHashes = items.map(name =>
|
|
161
|
-
computeFileHash(path.join(filePath, name))
|
|
162
|
-
).filter(Boolean).join('|');
|
|
163
|
-
|
|
164
|
-
return crypto.createHash("sha256")
|
|
165
|
-
.update(`folder:${filePath}|${childrenHashes}`)
|
|
166
|
-
.digest("hex");
|
|
167
|
-
} else {
|
|
168
|
-
// For files: hash of content + file metadata
|
|
169
|
-
const content = fs.readFileSync(filePath);
|
|
170
|
-
const stats = fs.statSync(filePath);
|
|
171
|
-
|
|
172
|
-
return crypto.createHash("sha256")
|
|
173
|
-
.update(content)
|
|
174
|
-
.update(`size:${stats.size}|mtime:${stats.mtimeMs}`)
|
|
175
|
-
.digest("hex");
|
|
176
|
-
}
|
|
177
|
-
} catch (err) {
|
|
178
|
-
console.error(`❌ Error computing hash for ${filePath}:`, err.message);
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function checkChanges(fileTree, versionMap, rootPath = CWD) {
|
|
184
|
-
return fileTree.map(item => {
|
|
185
|
-
const fullPath = path.join(rootPath, item.path || item.name);
|
|
186
|
-
const relativePath = item.path || item.name;
|
|
187
|
-
|
|
188
|
-
let hash = null;
|
|
189
|
-
let changed = true;
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
if (fs.existsSync(fullPath)) {
|
|
193
|
-
hash = computeFileHash(fullPath);
|
|
194
|
-
|
|
195
|
-
// Check against previous version
|
|
196
|
-
const prevVersion = versionMap[relativePath];
|
|
197
|
-
if (prevVersion && prevVersion.hash === hash) {
|
|
198
|
-
changed = false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
} catch (err) {
|
|
202
|
-
console.error(`❌ Error checking changes for ${relativePath}:`, err.message);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const newItem = {
|
|
206
|
-
...item,
|
|
207
|
-
changed,
|
|
208
|
-
hash,
|
|
209
|
-
path: relativePath // Ensure consistent path
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// Recursively check children for folders
|
|
213
|
-
if (item.type === "folder" && item.children && item.children.length > 0) {
|
|
214
|
-
newItem.children = checkChanges(item.children, versionMap, rootPath);
|
|
215
|
-
newItem.changed = newItem.changed || newItem.children.some(c => c.changed);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return newItem;
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** ------------------ CLOUDINARY UPLOAD ------------------ **/
|
|
223
|
-
async function uploadFileSigned(filePath, folder, roomId, token, email) {
|
|
224
|
-
const filename = path.basename(filePath);
|
|
225
|
-
|
|
226
|
-
const sigRes = await axios.post(
|
|
227
|
-
`${BASE_URL}/${roomId}/get-upload-signature`,
|
|
228
|
-
{ filename, folder, roomId },
|
|
229
|
-
{ headers: { authorization: `Bearer ${token}`, email } }
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
const { signature, timestamp, api_key, cloud_name } = sigRes.data;
|
|
233
|
-
|
|
234
|
-
const formData = new FormData();
|
|
235
|
-
formData.append("file", fs.createReadStream(filePath));
|
|
236
|
-
formData.append("folder", folder);
|
|
237
|
-
formData.append("public_id", filename);
|
|
238
|
-
formData.append("timestamp", timestamp);
|
|
239
|
-
formData.append("signature", signature);
|
|
240
|
-
formData.append("api_key", api_key);
|
|
241
|
-
|
|
242
|
-
const cloudRes = await axios.post(
|
|
243
|
-
`https://api.cloudinary.com/v1_1/${cloud_name}/auto/upload`,
|
|
244
|
-
formData,
|
|
245
|
-
{ headers: formData.getHeaders() }
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
return cloudRes.data.secure_url;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** ------------------ UPLOAD TREE - FIXED PATH CONSTRUCTION ------------------ **/
|
|
252
|
-
async function uploadTree(fileTree, folderHex, roomId, token, email, previousVersions, parentPath = "") {
|
|
253
|
-
const uploaded = [];
|
|
254
|
-
|
|
255
|
-
for (const node of fileTree) {
|
|
256
|
-
// ✅ FIXED: Correct path construction without duplication
|
|
257
|
-
let relativePath;
|
|
258
|
-
if (parentPath) {
|
|
259
|
-
relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
|
|
260
|
-
} else {
|
|
261
|
-
relativePath = node.path || node.name;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (node.type === "folder") {
|
|
265
|
-
const children = await uploadTree(node.children, folderHex, roomId, token, email, previousVersions, relativePath);
|
|
266
|
-
|
|
267
|
-
uploaded.push({
|
|
268
|
-
...node,
|
|
269
|
-
children,
|
|
270
|
-
hash: node.hash,
|
|
271
|
-
path: relativePath
|
|
272
|
-
});
|
|
273
|
-
} else {
|
|
274
|
-
const prevFile = previousVersions[relativePath];
|
|
275
|
-
|
|
276
|
-
if (node.changed) {
|
|
277
|
-
try {
|
|
278
|
-
console.log(`📤 Uploading changed file: ${relativePath}`);
|
|
279
|
-
const url = await uploadFileSigned(path.join(CWD, node.path), `tnc_uploads/${folderHex}`, roomId, token, email);
|
|
280
|
-
console.log(`✅ Uploaded: ${relativePath}`);
|
|
281
|
-
|
|
282
|
-
uploaded.push({
|
|
283
|
-
...node,
|
|
284
|
-
url,
|
|
285
|
-
hash: node.hash,
|
|
286
|
-
path: relativePath,
|
|
287
|
-
version: prevFile ? prevFile.version + 1 : 1
|
|
288
|
-
});
|
|
289
|
-
} catch (err) {
|
|
290
|
-
console.error(`❌ Failed to upload ${relativePath}:`, err.message);
|
|
291
|
-
// Fallback to previous version if upload fails
|
|
292
|
-
uploaded.push({
|
|
293
|
-
...node,
|
|
294
|
-
url: prevFile?.url,
|
|
295
|
-
hash: node.hash,
|
|
296
|
-
path: relativePath,
|
|
297
|
-
version: prevFile?.version || 1
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
} else {
|
|
301
|
-
// Unchanged file - use previous URL and version
|
|
302
|
-
console.log(`📋 Using previous version for: ${relativePath}`);
|
|
303
|
-
uploaded.push({
|
|
304
|
-
...node,
|
|
305
|
-
url: prevFile?.url,
|
|
306
|
-
hash: node.hash,
|
|
307
|
-
path: relativePath,
|
|
308
|
-
version: prevFile?.version || 1
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return uploaded;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/** ------------------ PUSH FUNCTION ------------------ **/
|
|
318
|
-
async function push(roomId, targetPath) {
|
|
319
|
-
const { token, email } = readToken();
|
|
320
|
-
const tncMetaPath = path.join(process.cwd(), ".tnc", ".tncmeta.json");
|
|
321
|
-
if (!fs.existsSync(tncMetaPath)) {
|
|
322
|
-
console.error("❌ Project not initialized. Run 'tnc init' first.");
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
|
|
326
|
-
const projectId = meta.projectId;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const tncPushInfo = path.join(process.cwd(), ".tnc", ".tncpush.json");
|
|
330
|
-
if (!fs.existsSync(tncMetaPath)) {
|
|
331
|
-
console.error("❌ Project not initialized. Run 'tnc init' first.");
|
|
332
|
-
process.exit(1);
|
|
333
|
-
}
|
|
334
|
-
const lastFolderId = JSON.parse(fs.readFileSync(tncPushInfo, "utf-8")).folderId;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const stats = fs.statSync(targetPath);
|
|
339
|
-
const rootFolder = stats.isDirectory() ? targetPath : path.dirname(targetPath);
|
|
340
|
-
const ignoreList = loadIgnore(rootFolder);
|
|
341
|
-
|
|
342
|
-
let content;
|
|
343
|
-
if (stats.isDirectory()) {
|
|
344
|
-
content = scanFolder(targetPath, ignoreList);
|
|
345
|
-
} else {
|
|
346
|
-
const relativePath = path.basename(targetPath);
|
|
347
|
-
content = shouldIgnore(relativePath, ignoreList)
|
|
348
|
-
? []
|
|
349
|
-
: [{ name: relativePath, type: "file", path: relativePath, size: stats.size }];
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!content.length) {
|
|
353
|
-
console.log("⚠️ Nothing to upload (all ignored).");
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const previousVersions = loadVersions();
|
|
358
|
-
console.log('📁 Previous versions:', Object.keys(previousVersions).length);
|
|
359
|
-
|
|
360
|
-
const contentWithChanges = checkChanges(content, previousVersions);
|
|
361
|
-
|
|
362
|
-
// Debug: Show what changed
|
|
363
|
-
const changedFiles = contentWithChanges.flatMap(item =>
|
|
364
|
-
item.changed ? [item.path] : []
|
|
365
|
-
);
|
|
366
|
-
console.log('🔄 Changed files:', changedFiles.length, changedFiles);
|
|
367
|
-
|
|
368
|
-
const hasChanges = contentWithChanges.some(item => item.changed || (item.children && item.children.some(c => c.changed)));
|
|
369
|
-
|
|
370
|
-
if (!hasChanges) {
|
|
371
|
-
console.log("ℹ️ No changes detected since last push.");
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const folderHex = crypto.createHash("md5").update(path.basename(targetPath) + Date.now()).digest("hex");
|
|
377
|
-
|
|
378
|
-
console.log("🚀 Uploading to Cloudinary...");
|
|
379
|
-
const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email, previousVersions);
|
|
380
|
-
|
|
381
|
-
console.log("🗂️ Sending metadata...");
|
|
382
|
-
const res = await axios.post(
|
|
383
|
-
`${BASE_URL}/${roomId}/upload`,
|
|
384
|
-
{ folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId, latestFolderId: lastFolderId},
|
|
385
|
-
{ headers: { authorization: `Bearer ${token}`, email } }
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
console.log("✅ Upload complete! Metadata stored successfully.");
|
|
389
|
-
|
|
390
|
-
// Save version hashes WITH URLs
|
|
391
|
-
const newVersionMap = {};
|
|
392
|
-
const flattenAndStore = (items) => {
|
|
393
|
-
for (const item of items) {
|
|
394
|
-
if (item.type === "file") {
|
|
395
|
-
newVersionMap[item.path] = {
|
|
396
|
-
hash: item.hash,
|
|
397
|
-
url: item.url, // ✅ Store URL locally
|
|
398
|
-
version: item.version || 1
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
if (item.children) flattenAndStore(item.children);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
flattenAndStore(uploadedTree);
|
|
405
|
-
saveVersions(newVersionMap);
|
|
406
|
-
|
|
407
|
-
// Determine latest version number from uploaded files
|
|
408
|
-
const versionNumbers = Object.values(newVersionMap).map(f => f.version || 1);
|
|
409
|
-
const latestVersion = versionNumbers.length > 0 ? Math.max(...versionNumbers) : 1;
|
|
410
|
-
|
|
411
|
-
const newPushRecord = {
|
|
412
|
-
version: latestVersion,
|
|
413
|
-
pushedAt: new Date().toISOString(),
|
|
414
|
-
roomId: roomId,
|
|
415
|
-
pushedBy: email,
|
|
416
|
-
projectId: projectId,
|
|
417
|
-
folderId: res.data.folderId
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const pushFilePath = path.join(CWD, ".tnc", ".tncpush.json");
|
|
423
|
-
fs.writeFileSync(
|
|
424
|
-
pushFilePath,
|
|
425
|
-
JSON.stringify(newPushRecord, null, 2)
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
console.log(`💾 Saved ${Object.keys(newVersionMap).length} files to .tncversions`);
|
|
429
|
-
|
|
430
|
-
} catch (err) {
|
|
431
|
-
console.error("❌ Upload failed:", err.response?.data || err.message);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
67
|
|
|
435
68
|
/** ------------------ CLI HANDLER ------------------ **/
|
|
436
69
|
async function main() {
|
|
@@ -443,7 +76,7 @@ async function main() {
|
|
|
443
76
|
|
|
444
77
|
case "logout":
|
|
445
78
|
await logout();
|
|
446
|
-
break;
|
|
79
|
+
break;
|
|
447
80
|
|
|
448
81
|
case "push": {
|
|
449
82
|
const roomIndex = args.indexOf("--room");
|
|
@@ -452,7 +85,7 @@ async function main() {
|
|
|
452
85
|
process.exit(1);
|
|
453
86
|
}
|
|
454
87
|
const roomId = args[roomIndex + 1];
|
|
455
|
-
const targetPath = args[roomIndex +
|
|
88
|
+
const targetPath = args[roomIndex + 2];
|
|
456
89
|
await push(roomId, targetPath);
|
|
457
90
|
break;
|
|
458
91
|
}
|
|
@@ -461,7 +94,10 @@ async function main() {
|
|
|
461
94
|
break;
|
|
462
95
|
|
|
463
96
|
case "init":
|
|
464
|
-
|
|
97
|
+
const roomIndex = args.indexOf("init");
|
|
98
|
+
const roomid = args[roomIndex + 1];
|
|
99
|
+
if (!roomid) console.log("Usage: tnc-cli init <roomId>");
|
|
100
|
+
await projectInit(roomid);
|
|
465
101
|
break;
|
|
466
102
|
|
|
467
103
|
case "create":
|
|
@@ -485,13 +121,19 @@ async function main() {
|
|
|
485
121
|
|
|
486
122
|
case "connect": {
|
|
487
123
|
const idx = args.indexOf("connect");
|
|
124
|
+
if(idx === -1 || !args[idx+1]){
|
|
125
|
+
console.log(" Usage: tnc-cli connect <roomId> ");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
488
128
|
const link = args[idx+1];
|
|
489
|
-
|
|
490
129
|
await connect(link);
|
|
491
130
|
break;
|
|
131
|
+
}
|
|
492
132
|
|
|
493
|
-
|
|
494
|
-
|
|
133
|
+
case "task-complete": {
|
|
134
|
+
const idx = args.indexOf("task-complete");
|
|
135
|
+
await taskCompletion();
|
|
136
|
+
break;
|
|
495
137
|
}
|
|
496
138
|
|
|
497
139
|
case "pull": {
|
|
@@ -528,14 +170,18 @@ async function main() {
|
|
|
528
170
|
|
|
529
171
|
case "my-tasks": {
|
|
530
172
|
const roomIdx = args.indexOf("my-tasks");
|
|
531
|
-
const roomId = args[roomIdx + 1];
|
|
173
|
+
const roomId = args[roomIdx + 1];
|
|
532
174
|
|
|
533
|
-
await myTask(
|
|
175
|
+
await myTask();
|
|
534
176
|
break;
|
|
535
177
|
}
|
|
536
178
|
case "invite": {
|
|
537
179
|
const roomIdx = args.indexOf("invite");
|
|
538
|
-
|
|
180
|
+
if (roomIdx === -1 || !args[roomIdx + 1]) {
|
|
181
|
+
console.log("Usage: tnc-cli invite <invitee-email>");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const email = args[roomIdx + 1];
|
|
539
185
|
|
|
540
186
|
await sendInvite(email);
|
|
541
187
|
break;
|
|
@@ -557,6 +203,7 @@ case "invite": {
|
|
|
557
203
|
console.log(" tnc-cli logout");
|
|
558
204
|
console.log(" tnc-cli help");
|
|
559
205
|
console.log(" tnc-cli version");
|
|
206
|
+
console.log(" tnc-cli task-complete <task-Id>")
|
|
560
207
|
|
|
561
208
|
}
|
|
562
209
|
}
|
package/commands/branch.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "path";
|
|
|
3
3
|
import axios from "axios";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
|
+
import getUserInfo from "../lib/getVerify.js";
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
async function updateBranch(branchName, branchId) {
|
|
@@ -29,7 +30,7 @@ async function updateBranch(branchName, branchId) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// Update the current branch
|
|
32
|
-
meta.
|
|
33
|
+
meta.branch = branchName;
|
|
33
34
|
Object.assign(meta, {branchId: `${branchId}`});
|
|
34
35
|
|
|
35
36
|
// Write updated data
|
|
@@ -58,25 +59,27 @@ async function createBranch(roomId) {
|
|
|
58
59
|
},
|
|
59
60
|
]);
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
const tncfilepath = path.join(homeDir, ".tncrc");
|
|
62
|
+
const userInfo = await getUserInfo(); // FIXED
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
if (!userInfo) {
|
|
65
|
+
throw new Error("User info could not be loaded. Please login first.");
|
|
66
|
+
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const email = userInfo.email;
|
|
69
|
+
const token = userInfo.token;
|
|
70
|
+
|
|
71
|
+
if (!email || !token) {
|
|
72
|
+
throw new Error("User email or token missing. Please login again.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(email, token);
|
|
71
76
|
|
|
72
|
-
if (!email) {
|
|
73
|
-
throw new Error("User email not found in configuration. Please login again.");
|
|
74
|
-
}
|
|
75
77
|
|
|
76
78
|
const res = await axios.post("https://thinkncollab.com/cli/createBranch", {
|
|
77
79
|
branchName: branchName.trim(),
|
|
78
|
-
roomId,
|
|
79
|
-
email,
|
|
80
|
+
roomId : roomId,
|
|
81
|
+
email: email,
|
|
82
|
+
token:token
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
const {
|
package/commands/connect.js
CHANGED
|
@@ -1,13 +1,66 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
1
|
import axios from "axios";
|
|
2
|
+
import machine from "node-machine-id";
|
|
3
|
+
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
5
|
-
import
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
const tncrcPath = path.join(os.homedir(), ".tncrc");
|
|
8
|
+
|
|
9
|
+
async function connect(roomId) {
|
|
10
|
+
if (!fs.existsSync(tncrcPath)) {
|
|
11
|
+
console.error("❌ You are not logged in. Run 'tnc login' first.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const answer = await inquirer.prompt([
|
|
15
|
+
{ type: "input", name: "BranchName", message: "Enter Branch Name to create a branch:" }
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const data = fs.readFileSync(tncrcPath, "utf-8");
|
|
19
|
+
const { email, token } = JSON.parse(data);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await axios.post(`https://thinkncollab.com/cli/connect/${roomId}`, {
|
|
23
|
+
email: email,
|
|
24
|
+
token: token,
|
|
25
|
+
machineId: await machine.machineIdSync(),
|
|
26
|
+
branchName: answer.BranchName,
|
|
27
|
+
});
|
|
28
|
+
const CWD= process.cwd();
|
|
29
|
+
const tncFolderPath = path.join(CWD, ".tnc");
|
|
30
|
+
if (!fs.existsSync(tncFolderPath)) fs.mkdirSync(tncFolderPath, { recursive: true });
|
|
31
|
+
|
|
32
|
+
// Write metadata file
|
|
33
|
+
const metaFilePath = path.join(tncFolderPath, ".tncmeta.json");
|
|
34
|
+
const pushFilePath = path.join(tncFolderPath, ".tncpush.json");
|
|
35
|
+
|
|
36
|
+
const metaFileInfo = JSON.stringify({
|
|
37
|
+
"projectId": response.data.project._id,
|
|
38
|
+
"projectName": response.data.project.name,
|
|
39
|
+
"roomId": response.data.project.roomId,
|
|
40
|
+
"branch": null
|
|
41
|
+
});
|
|
42
|
+
fs.writeFileSync(metaFilePath, metaFileInfo);
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
pushFilePath,
|
|
45
|
+
" {} "
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
console.log(response.data);
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
6
52
|
|
|
7
53
|
|
|
8
|
-
|
|
54
|
+
// console.log("✅ Connected to project:", response.data.project.name);
|
|
55
|
+
// console.log("Members connected:", response.data.project.membersConnected);
|
|
9
56
|
|
|
10
|
-
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err.response) {
|
|
59
|
+
console.error(" Error:", err.response.data.error);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(" Error:", err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
11
65
|
|
|
12
|
-
|
|
13
|
-
}
|
|
66
|
+
export default connect;
|
package/commands/init.js
CHANGED
|
@@ -3,10 +3,11 @@ import os from "os";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
5
|
import axios from "axios";
|
|
6
|
+
import machine from "node-machine-id";
|
|
6
7
|
|
|
7
8
|
const CWD = process.cwd();
|
|
8
9
|
|
|
9
|
-
async function projectInit() {
|
|
10
|
+
async function projectInit(roomId) {
|
|
10
11
|
const answer = await inquirer.prompt([
|
|
11
12
|
{ type: "input", name: "projectName", message: "Enter Project Name:" }
|
|
12
13
|
]);
|
|
@@ -21,11 +22,15 @@ async function projectInit() {
|
|
|
21
22
|
|
|
22
23
|
const data = fs.readFileSync(tncrcPath, "utf-8");
|
|
23
24
|
const currentUser = JSON.parse(data).email;
|
|
25
|
+
const userToken = JSON.parse(data).token;
|
|
24
26
|
|
|
25
27
|
// Initialize project via backend
|
|
26
28
|
const response = await axios.post("https://thinkncollab.com/cli/init", {
|
|
27
29
|
projectName: answer.projectName,
|
|
28
|
-
owner: currentUser
|
|
30
|
+
owner: currentUser,
|
|
31
|
+
token: userToken,
|
|
32
|
+
machineId: await machine.machineIdSync(),
|
|
33
|
+
roomId: roomId
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
const projectId = response.data.project._id;
|
|
@@ -44,6 +49,7 @@ async function projectInit() {
|
|
|
44
49
|
projectId,
|
|
45
50
|
projectName: answer.projectName,
|
|
46
51
|
currentBranch: "main",
|
|
52
|
+
roomId: roomId,
|
|
47
53
|
lastCommit: null,
|
|
48
54
|
files: {}
|
|
49
55
|
},
|
|
File without changes
|
package/commands/myTask.js
CHANGED
|
@@ -4,7 +4,7 @@ import axios from "axios";
|
|
|
4
4
|
import os from "os";
|
|
5
5
|
|
|
6
6
|
const homeDir = os.homedir();
|
|
7
|
-
const url = "https://thinkncollab.
|
|
7
|
+
const url = "https://thinkncollab.com/cli/mytasks"; // backend endpoint
|
|
8
8
|
|
|
9
9
|
// Get saved email from ~/.tncrc
|
|
10
10
|
async function getEmail() {
|
|
@@ -32,10 +32,14 @@ async function getToken() {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Fetch tasks for a given room
|
|
35
|
-
async function myTask(
|
|
35
|
+
async function myTask() {
|
|
36
36
|
try {
|
|
37
37
|
const email = await getEmail();
|
|
38
38
|
const token = await getToken();
|
|
39
|
+
const CWD = process.cwd();
|
|
40
|
+
const metaDataPath = path.join(".tnc", '.tncmeta.json'); // assuming room ID is the current directory name
|
|
41
|
+
const metaData = JSON.parse(fs.readFileSync(path.join(CWD, metaDataPath), 'utf-8'));
|
|
42
|
+
const roomId = metaData.roomId;
|
|
39
43
|
|
|
40
44
|
const res = await axios.get(`${url}/${roomId}`, {
|
|
41
45
|
params: { email, token } // since backend uses req.query
|
|
@@ -50,7 +54,7 @@ async function myTask(roomId) {
|
|
|
50
54
|
|
|
51
55
|
console.log("📋 Your Tasks:");
|
|
52
56
|
tasks.forEach((task, i) => {
|
|
53
|
-
console.log(`${i + 1}. ${task.title} — ${task.status}`);
|
|
57
|
+
console.log(`${i + 1}. ${task.title} — ${task.status} ${task._id}`);
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
} catch (error) {
|