thinkncollab-cli 0.0.83 → 0.0.85
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 +46 -1
- package/commands/connect.js +2 -1
- package/commands/createTask.js +260 -0
- package/commands/myTask.js +85 -44
- package/commands/myTeam.js +44 -0
- package/commands/send.js +16 -0
- package/commands/task.js +190 -0
- package/package.json +8 -4
package/bin/index.js
CHANGED
|
@@ -17,6 +17,9 @@ import connect from '../commands/connect.js'
|
|
|
17
17
|
import machine from "node-machine-id";
|
|
18
18
|
import push from "../commands/push.js";
|
|
19
19
|
import taskCompletion from "../commands/taskCompletion.js";
|
|
20
|
+
import task from "../commands/task.js";
|
|
21
|
+
import createTask from "../commands/createTask.js";
|
|
22
|
+
import myTeam from "../commands/myTeam.js";
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
|
|
@@ -26,6 +29,18 @@ const VERSION_FILE = path.join(process.cwd(), ".tncversions");
|
|
|
26
29
|
const BASE_URL = "https://thinkncollab.com/rooms";
|
|
27
30
|
const CWD = process.cwd();
|
|
28
31
|
|
|
32
|
+
async function DAPP() {
|
|
33
|
+
try {
|
|
34
|
+
const res = await axios.get(`http://localhost:4545/logout`);
|
|
35
|
+
console.log("✅ Command sent to ThinkNCollab Agent");
|
|
36
|
+
console.log(res.data.message);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error("❌ ThinkNCollab Agent not running");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
29
44
|
/** ------------------ LOGIN ------------------ **/
|
|
30
45
|
async function login() {
|
|
31
46
|
const answers = await inquirer.prompt([
|
|
@@ -175,6 +190,17 @@ case "my-tasks": {
|
|
|
175
190
|
await myTask();
|
|
176
191
|
break;
|
|
177
192
|
}
|
|
193
|
+
case "create-task": {
|
|
194
|
+
const roomIdx = args.indexOf("create-task");
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
await createTask();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case "send": {
|
|
201
|
+
await send();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
178
204
|
case "invite": {
|
|
179
205
|
const roomIdx = args.indexOf("invite");
|
|
180
206
|
if (roomIdx === -1 || !args[roomIdx + 1]) {
|
|
@@ -185,10 +211,28 @@ case "invite": {
|
|
|
185
211
|
|
|
186
212
|
await sendInvite(email);
|
|
187
213
|
break;
|
|
214
|
+
}
|
|
215
|
+
case "task": {
|
|
216
|
+
task();
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "myteam": {
|
|
221
|
+
myTeam();
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
case "signout": {
|
|
226
|
+
await DAPP();
|
|
227
|
+
break;
|
|
228
|
+
|
|
188
229
|
}
|
|
189
230
|
|
|
190
231
|
default:
|
|
191
|
-
console.log(
|
|
232
|
+
console.log('================================');
|
|
233
|
+
console.log(' START REAL SYSTEM SHELL ');
|
|
234
|
+
console.log('================================ */ ');
|
|
235
|
+
console.log("✅ ThinkNCollab CLI ready!");
|
|
192
236
|
console.log("Commands:");
|
|
193
237
|
console.log(" tnc-cli login --> to login");
|
|
194
238
|
console.log(" tnc-cli init <roomId> --> to use CLI for a room");
|
|
@@ -200,6 +244,7 @@ case "invite": {
|
|
|
200
244
|
// console.log(" tnc-cli status");
|
|
201
245
|
console.log(" tnc-cli whoami --> to know by which Id you are loggedin");
|
|
202
246
|
console.log(" tnc-cli my-tasks --> to get ur tasks from the room ");
|
|
247
|
+
console.log(" tnc-cli tasks --> to know thetask details");
|
|
203
248
|
console.log(" tnc-cli task-complete --> to change the task status to complete")
|
|
204
249
|
console.log(" tnc-cli logout --> to logout");
|
|
205
250
|
console.log(" tnc-cli help --> to get help");
|
package/commands/connect.js
CHANGED
|
@@ -3,6 +3,7 @@ import machine from "node-machine-id";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import path from "path";
|
|
6
|
+
import inquirer from "inquirer";
|
|
6
7
|
|
|
7
8
|
const tncrcPath = path.join(os.homedir(), ".tncrc");
|
|
8
9
|
|
|
@@ -19,7 +20,7 @@ async function connect(roomId) {
|
|
|
19
20
|
const { email, token } = JSON.parse(data);
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
|
-
const response = await axios.post(`
|
|
23
|
+
const response = await axios.post(`http://localhost:3001/cli/connect/${roomId}`, {
|
|
23
24
|
email: email,
|
|
24
25
|
token: token,
|
|
25
26
|
machineId: await machine.machineIdSync(),
|
|
@@ -0,0 +1,260 @@
|
|
|
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 chalk from "chalk";
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import open from 'open';
|
|
9
|
+
import http from 'http';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const getVerifyModule = await import("../lib/getVerify.js");
|
|
13
|
+
const getVerify = getVerifyModule.default;
|
|
14
|
+
const { token } = await getVerify();
|
|
15
|
+
|
|
16
|
+
const CWD = process.cwd();
|
|
17
|
+
const tncmetaPath = path.join(CWD, ".tnc", ".tncmeta.json");
|
|
18
|
+
const fileData = fs.existsSync(tncmetaPath) ? JSON.parse(fs.readFileSync(tncmetaPath, "utf-8")) : null;
|
|
19
|
+
const roomId = fileData?.roomId || null;
|
|
20
|
+
|
|
21
|
+
function createSpinner(message) {
|
|
22
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
23
|
+
let i = 0;
|
|
24
|
+
|
|
25
|
+
const spinner = setInterval(() => {
|
|
26
|
+
process.stdout.write(`\r${chalk.cyan(frames[i])} ${message}`);
|
|
27
|
+
i = (i + 1) % frames.length;
|
|
28
|
+
}, 80);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
stop: (clearMessage = true) => {
|
|
32
|
+
clearInterval(spinner);
|
|
33
|
+
if (clearMessage) {
|
|
34
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 50) + '\r');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
update: (newMessage) => {
|
|
38
|
+
message = newMessage;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function createTask() {
|
|
44
|
+
try {
|
|
45
|
+
console.log(chalk.blue('\n🚀 Task Creation CLI\n'));
|
|
46
|
+
|
|
47
|
+
// Validate roomId and token
|
|
48
|
+
if (!roomId) {
|
|
49
|
+
console.error(chalk.red('❌ No roomId found in .tnc/.tncmeta.json'));
|
|
50
|
+
console.log(chalk.yellow('Please ensure you are in a ThinkNCollab project directory'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!token) {
|
|
55
|
+
console.error(chalk.red('❌ No token found'));
|
|
56
|
+
console.log(chalk.yellow('Please login first using: tnc login'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// console.log(chalk.green(`✅ Room ID: ${roomId}`));
|
|
61
|
+
// console.log(chalk.green(`✅ Token: ${token.substring(0, 10)}...${token.substring(token.length - 10)}\n`));
|
|
62
|
+
|
|
63
|
+
// Construct the URL with CLI flag
|
|
64
|
+
const redirectUrl = `https://thinkncollab.in/cli/tasks/${roomId}/${token}/create?cli=true`;
|
|
65
|
+
|
|
66
|
+
const spinner = createSpinner('Preparing task creation interface...');
|
|
67
|
+
|
|
68
|
+
console.log(chalk.blue('🔗 Opening task creation interface in your browser...'));
|
|
69
|
+
console.log(chalk.gray(`URL: ${redirectUrl}`));
|
|
70
|
+
|
|
71
|
+
// Find available port
|
|
72
|
+
const PORT = await findAvailablePort(3002);
|
|
73
|
+
spinner.stop();
|
|
74
|
+
|
|
75
|
+
// Create callback server
|
|
76
|
+
// Create callback server with better CORS and request handling
|
|
77
|
+
const server = http.createServer((req, res) => {
|
|
78
|
+
console.log(chalk.yellow(`\n📨 Callback received: ${req.url}`));
|
|
79
|
+
|
|
80
|
+
// CORS headers for all responses
|
|
81
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
82
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
83
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-CLI-Callback-URL');
|
|
84
|
+
res.setHeader('Access-Control-Max-Age', '86400'); // 24 hours
|
|
85
|
+
|
|
86
|
+
// Handle preflight
|
|
87
|
+
if (req.method === 'OPTIONS') {
|
|
88
|
+
res.writeHead(200);
|
|
89
|
+
res.end();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Parse URL
|
|
94
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
95
|
+
|
|
96
|
+
// Check for callback (handle both /callback and direct query params)
|
|
97
|
+
if (req.url.includes('/callback') || url.searchParams.has('taskId')) {
|
|
98
|
+
const taskId = url.searchParams.get('taskId');
|
|
99
|
+
const attachments = url.searchParams.get('attachments');
|
|
100
|
+
const status = url.searchParams.get('status');
|
|
101
|
+
|
|
102
|
+
// Log success
|
|
103
|
+
if (status === 'error') {
|
|
104
|
+
console.log(chalk.red('\n❌ Task creation failed!'));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(chalk.green('\n✅ Task created successfully!'));
|
|
107
|
+
if (taskId) {
|
|
108
|
+
console.log(chalk.blue(`📋 Task ID: ${taskId}`));
|
|
109
|
+
|
|
110
|
+
// Save task ID to file
|
|
111
|
+
const taskMetaPath = path.join(CWD, ".tnc", "last-task.json");
|
|
112
|
+
fs.writeFileSync(taskMetaPath, JSON.stringify({
|
|
113
|
+
taskId,
|
|
114
|
+
attachments: attachments ? attachments.length : 0,
|
|
115
|
+
timestamp: new Date().toISOString(),
|
|
116
|
+
roomId
|
|
117
|
+
}, null, 2));
|
|
118
|
+
console.log(chalk.gray(`📝 Task ID saved to .tnc/last-task.json`));
|
|
119
|
+
}
|
|
120
|
+
if (attachments) {
|
|
121
|
+
// console.log(chalk.blue(`📎 Attachments: ${attachments}`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Send response to close browser (HTML with auto-close)
|
|
126
|
+
res.writeHead(200, {
|
|
127
|
+
'Content-Type': 'text/html',
|
|
128
|
+
'Access-Control-Allow-Origin': '*'
|
|
129
|
+
});
|
|
130
|
+
res.end(`
|
|
131
|
+
<!DOCTYPE html>
|
|
132
|
+
<html>
|
|
133
|
+
<head>
|
|
134
|
+
<title>${status === 'error' ? 'Failed' : 'Success'}</title>
|
|
135
|
+
<style>
|
|
136
|
+
body {
|
|
137
|
+
font-family: Arial, sans-serif;
|
|
138
|
+
display: flex;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
align-items: center;
|
|
141
|
+
height: 100vh;
|
|
142
|
+
margin: 0;
|
|
143
|
+
background: ${status === 'error' ? '#f44336' : '#4CAF50'};
|
|
144
|
+
color: white;
|
|
145
|
+
}
|
|
146
|
+
.message {
|
|
147
|
+
text-align: center;
|
|
148
|
+
padding: 20px;
|
|
149
|
+
}
|
|
150
|
+
</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div class="message">
|
|
154
|
+
<h1>${status === 'error' ? '❌ Task Failed' : '✅ Task Created'}</h1>
|
|
155
|
+
${taskId ? `<p>Task ID: ${taskId}</p>` : ''}
|
|
156
|
+
<p>Closing in 3 seconds...</p>
|
|
157
|
+
</div>
|
|
158
|
+
<script>
|
|
159
|
+
// Send one more callback to ensure it was received
|
|
160
|
+
fetch(window.location.href, { method: 'GET', mode: 'no-cors' });
|
|
161
|
+
|
|
162
|
+
// Auto close
|
|
163
|
+
setTimeout(() => window.close(), 3000);
|
|
164
|
+
|
|
165
|
+
// Fallback
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
window.location.href = 'about:blank';
|
|
168
|
+
setTimeout(() => window.close(), 100);
|
|
169
|
+
}, 4000);
|
|
170
|
+
</script>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
|
173
|
+
`);
|
|
174
|
+
|
|
175
|
+
// Close server after response
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
server.close();
|
|
178
|
+
console.log(chalk.gray('\n📡 Callback server closed'));
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}, 2000);
|
|
181
|
+
} else {
|
|
182
|
+
// Handle other requests (like favicon)
|
|
183
|
+
res.writeHead(404);
|
|
184
|
+
res.end('Not found');
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Server error handling
|
|
189
|
+
server.on('error', (err) => {
|
|
190
|
+
if (err.code === 'EADDRINUSE') {
|
|
191
|
+
console.log(chalk.yellow(`⚠️ Port ${PORT} is already in use. Callback might not work.`));
|
|
192
|
+
console.log(chalk.yellow('The task will still be created, but you may need to check manually.'));
|
|
193
|
+
} else {
|
|
194
|
+
console.error(chalk.red('Server error:'), err.message);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Start server
|
|
199
|
+
server.listen(PORT, () => {
|
|
200
|
+
// console.log(chalk.gray(`📡 Waiting for callback on http://localhost:${PORT}/callback`));
|
|
201
|
+
// console.log(chalk.gray('The browser will automatically close after task creation\n'));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Open browser
|
|
205
|
+
setTimeout(async () => {
|
|
206
|
+
try {
|
|
207
|
+
await open(redirectUrl);
|
|
208
|
+
// console.log(chalk.green('✅ Browser opened successfully!'));
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (err.message.includes('No application')) {
|
|
211
|
+
console.error(chalk.red('❌ Could not open browser automatically.'));
|
|
212
|
+
console.log(chalk.yellow('Please manually open this URL in your browser:'));
|
|
213
|
+
console.log(chalk.cyan(redirectUrl));
|
|
214
|
+
} else {
|
|
215
|
+
console.error(chalk.red('Browser error:'), err.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}, 500);
|
|
219
|
+
|
|
220
|
+
// Wait for callback or timeout
|
|
221
|
+
await new Promise((resolve) => {
|
|
222
|
+
const timeout = setTimeout(() => {
|
|
223
|
+
console.log(chalk.yellow('\n⚠️ No callback received within 5 minutes.'));
|
|
224
|
+
console.log(chalk.gray('Your task may have been created successfully.'));
|
|
225
|
+
console.log(chalk.gray('Check your browser to confirm.'));
|
|
226
|
+
server.close();
|
|
227
|
+
resolve();
|
|
228
|
+
}, 300000); // 5 minutes
|
|
229
|
+
|
|
230
|
+
server.on('close', () => {
|
|
231
|
+
clearTimeout(timeout);
|
|
232
|
+
resolve();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(chalk.red("❌ An unexpected error occurred:"), err.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Helper function to find available port
|
|
242
|
+
async function findAvailablePort(startPort) {
|
|
243
|
+
const net = await import('net');
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const server = net.createServer();
|
|
246
|
+
server.listen(startPort, () => {
|
|
247
|
+
const port = server.address().port;
|
|
248
|
+
server.close(() => resolve(port));
|
|
249
|
+
});
|
|
250
|
+
server.on('error', (err) => {
|
|
251
|
+
if (err.code === 'EADDRINUSE') {
|
|
252
|
+
resolve(findAvailablePort(startPort + 1));
|
|
253
|
+
} else {
|
|
254
|
+
reject(err);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export default createTask;
|
package/commands/myTask.js
CHANGED
|
@@ -2,97 +2,138 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import axios from "axios";
|
|
4
4
|
import os from "os";
|
|
5
|
-
import machine from
|
|
6
|
-
|
|
5
|
+
import machine from "node-machine-id";
|
|
6
|
+
import CliTable3 from "cli-table3";
|
|
7
|
+
import chalk from "chalk";
|
|
7
8
|
|
|
8
9
|
const homeDir = os.homedir();
|
|
9
|
-
const url = "https://thinkncollab.com/cli/mytasks";
|
|
10
|
+
const url = "https://thinkncollab.com/cli/mytasks";
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
-
|
|
12
|
+
// -----------------------------
|
|
13
|
+
// Read ~/.tncrc
|
|
14
|
+
// -----------------------------
|
|
15
|
+
function getAuthData() {
|
|
13
16
|
const rcFile = path.join(homeDir, ".tncrc");
|
|
14
17
|
|
|
15
18
|
if (!fs.existsSync(rcFile)) {
|
|
16
|
-
console.log("⚠️ Please login first!");
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const content = fs.readFileSync(rcFile, "utf-8");
|
|
21
|
-
const email = JSON.parse(content).email;
|
|
22
|
-
return email;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Get saved token from ~/.tncrc
|
|
26
|
-
async function getToken() {
|
|
27
|
-
const rcFile = path.join(homeDir, ".tncrc");
|
|
28
|
-
if (!fs.existsSync(rcFile)) {
|
|
29
|
-
console.log("⚠️ Please login first!");
|
|
19
|
+
console.log(chalk.red("⚠️ Please login first!"));
|
|
30
20
|
process.exit(1);
|
|
31
21
|
}
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
const token = JSON.parse(content).token;
|
|
35
|
-
return token;
|
|
23
|
+
return JSON.parse(fs.readFileSync(rcFile, "utf-8"));
|
|
36
24
|
}
|
|
37
25
|
|
|
38
|
-
//
|
|
26
|
+
// -----------------------------
|
|
27
|
+
// Main Function
|
|
28
|
+
// -----------------------------
|
|
39
29
|
async function myTask() {
|
|
40
30
|
try {
|
|
41
|
-
const email =
|
|
42
|
-
const token = await getToken();
|
|
31
|
+
const { email, token } = getAuthData();
|
|
43
32
|
|
|
44
33
|
const CWD = process.cwd();
|
|
45
|
-
const metaDataPath = path.join(".tnc", ".tncmeta.json");
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
const metaDataPath = path.join(CWD, ".tnc", ".tncmeta.json");
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(metaDataPath)) {
|
|
37
|
+
console.log(chalk.yellow("⚠️ Room metadata not found in current directory!"));
|
|
48
38
|
return;
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
const metaData = JSON.parse(fs.readFileSync(
|
|
41
|
+
const metaData = JSON.parse(fs.readFileSync(metaDataPath, "utf-8"));
|
|
52
42
|
const roomId = metaData.roomId;
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
const cacheDir = path.join(homeDir, ".tnc");
|
|
44
|
+
const cacheDir = path.join(CWD, ".tnc");
|
|
56
45
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir);
|
|
57
|
-
|
|
46
|
+
|
|
47
|
+
const cacheFile = path.join(cacheDir, "tasks.json");
|
|
58
48
|
|
|
59
49
|
let tasks = [];
|
|
60
50
|
|
|
61
51
|
try {
|
|
62
|
-
// Try fetching tasks online
|
|
63
52
|
const res = await axios.get(`${url}/${roomId}`, {
|
|
64
|
-
params: {
|
|
53
|
+
params: {
|
|
54
|
+
email,
|
|
55
|
+
token,
|
|
56
|
+
machineId: machine.machineIdSync()
|
|
57
|
+
}
|
|
65
58
|
});
|
|
66
59
|
|
|
67
60
|
tasks = res.data.tasks || [];
|
|
68
61
|
|
|
69
|
-
// Save to cache
|
|
70
62
|
fs.writeFileSync(cacheFile, JSON.stringify(tasks, null, 2));
|
|
71
63
|
|
|
72
64
|
} catch (error) {
|
|
73
|
-
// If fetch fails, use offline cache
|
|
74
65
|
if (fs.existsSync(cacheFile)) {
|
|
75
|
-
console.log("⚠️ Offline mode: showing cached tasks");
|
|
66
|
+
console.log(chalk.yellow("⚠️ Offline mode: showing cached tasks"));
|
|
76
67
|
tasks = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
77
68
|
} else {
|
|
78
|
-
console.error(
|
|
69
|
+
console.error(
|
|
70
|
+
chalk.red("❌ Error fetching tasks and no cache available:"),
|
|
71
|
+
error.response?.data || error.message
|
|
72
|
+
);
|
|
79
73
|
return;
|
|
80
74
|
}
|
|
81
75
|
}
|
|
82
76
|
|
|
83
|
-
// Display tasks
|
|
84
77
|
if (!tasks.length) {
|
|
85
|
-
console.log("📭 No tasks assigned.");
|
|
78
|
+
console.log(chalk.gray("📭 No tasks assigned."));
|
|
86
79
|
return;
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
// -----------------------------
|
|
83
|
+
// Create Table
|
|
84
|
+
// -----------------------------
|
|
85
|
+
const table = new CliTable3({
|
|
86
|
+
head: [
|
|
87
|
+
chalk.cyan.bold("Title"),
|
|
88
|
+
chalk.green.bold("Status"),
|
|
89
|
+
chalk.magenta.bold("Category"),
|
|
90
|
+
chalk.red.bold("Priority"),
|
|
91
|
+
chalk.yellow.bold("ID"),
|
|
92
|
+
chalk.blue.bold("Att.")
|
|
93
|
+
],
|
|
94
|
+
colWidths: [30, 12, 18, 10, 26, 6], // adjusted
|
|
95
|
+
wordWrap: true,
|
|
96
|
+
style: {
|
|
97
|
+
head: [],
|
|
98
|
+
border: ["grey"]
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
console.log(chalk.bold.cyan("\n📋 YOUR TASKS\n"));
|
|
104
|
+
|
|
105
|
+
tasks.forEach(task => {
|
|
106
|
+
|
|
107
|
+
// Status color
|
|
108
|
+
let statusColor =
|
|
109
|
+
task.status === "completed"
|
|
110
|
+
? chalk.green
|
|
111
|
+
: task.status === "in-progress"
|
|
112
|
+
? chalk.yellow
|
|
113
|
+
: chalk.red;
|
|
114
|
+
|
|
115
|
+
// Priority color
|
|
116
|
+
let priorityColor =
|
|
117
|
+
task.priority === "high"
|
|
118
|
+
? chalk.red
|
|
119
|
+
: task.priority === "medium"
|
|
120
|
+
? chalk.yellow
|
|
121
|
+
: chalk.green;
|
|
122
|
+
|
|
123
|
+
table.push([
|
|
124
|
+
chalk.white(task.title),
|
|
125
|
+
statusColor(task.status),
|
|
126
|
+
chalk.magenta(task.category || "General"),
|
|
127
|
+
priorityColor(task.priority || "low"),
|
|
128
|
+
chalk.gray(task._id),
|
|
129
|
+
chalk.blue(task.attachments?.length || 0)
|
|
130
|
+
]);
|
|
92
131
|
});
|
|
93
132
|
|
|
133
|
+
console.log(table.toString());
|
|
134
|
+
|
|
94
135
|
} catch (error) {
|
|
95
|
-
console.error("❌ Error:", error.message);
|
|
136
|
+
console.error(chalk.red("❌ Error:"), error.message);
|
|
96
137
|
}
|
|
97
138
|
}
|
|
98
139
|
|
|
@@ -0,0 +1,44 @@
|
|
|
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 chalk from "chalk";
|
|
7
|
+
import FormData from "form-data";
|
|
8
|
+
import getVerify from "../lib/getVerify.js";
|
|
9
|
+
|
|
10
|
+
const CWD = process.cwd();
|
|
11
|
+
const TNC_API_URL = "http://localhost:3001/";
|
|
12
|
+
const metaDataFile = path.join(CWD, ".tnc", ".tncmeta.json");
|
|
13
|
+
const metaData = JSON.parse(fs.readFileSync(metaDataFile, "utf-8"));
|
|
14
|
+
const roomId = metaData.roomId;
|
|
15
|
+
|
|
16
|
+
async function myTeam() {
|
|
17
|
+
try {
|
|
18
|
+
const verifyData = await getVerify();
|
|
19
|
+
const { email, token } = verifyData;
|
|
20
|
+
const response = await axios.get(`${TNC_API_URL}cli/myTeam/${roomId}`, {
|
|
21
|
+
headers: {
|
|
22
|
+
email,
|
|
23
|
+
token,
|
|
24
|
+
machineId: machine.machineIdSync(),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const teamMembers = response.data.RoomMembers || [];
|
|
28
|
+
if (teamMembers.length === 0) {
|
|
29
|
+
console.log(chalk.yellow("⚠️ You are not part of any team yet."));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log(chalk.green("👥 Your Team Members:"))
|
|
33
|
+
teamMembers.forEach(member => {
|
|
34
|
+
console.log(chalk.blue(`- ${member.name} (${member.email})`));
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err.response) {
|
|
38
|
+
console.error(chalk.red("❌ Error:"), chalk.red(err.response.data.error));
|
|
39
|
+
} else {
|
|
40
|
+
console.error(chalk.red("❌ Error:"), chalk.red(err.message));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export default myTeam;
|
package/commands/send.js
ADDED
package/commands/task.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
import chalk from "chalk";
|
|
8
|
+
|
|
9
|
+
const CWD = process.cwd();
|
|
10
|
+
const tncrcPath = path.join(os.homedir(), ".tncrc");
|
|
11
|
+
const taskFilePath = path.join(CWD, ".tnc", "tasks.json");
|
|
12
|
+
|
|
13
|
+
async function task() {
|
|
14
|
+
try {
|
|
15
|
+
// Validate login
|
|
16
|
+
if (!fs.existsSync(tncrcPath)) {
|
|
17
|
+
console.error("❌ You are not logged in. Run 'tnc login' first.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validate project init
|
|
22
|
+
if (!fs.existsSync(taskFilePath)) {
|
|
23
|
+
console.error("❌ This directory is not initialized. Run 'tnc init' first.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Ask Task ID
|
|
28
|
+
const { taskId } = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: "input",
|
|
31
|
+
name: "taskId",
|
|
32
|
+
message: "Enter Task ID:",
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
if (!taskId?.trim()) {
|
|
37
|
+
console.error("❌ Task ID cannot be empty.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { email, token } = JSON.parse(fs.readFileSync(tncrcPath, "utf-8"));
|
|
42
|
+
const localData = JSON.parse(fs.readFileSync(taskFilePath, "utf-8"));
|
|
43
|
+
const localTasks = localData.tasks || [];
|
|
44
|
+
|
|
45
|
+
// API call (correct way using headers)
|
|
46
|
+
const response = await axios.get(
|
|
47
|
+
`https://thinkncollab.com/cli/task/${taskId.trim()}`,
|
|
48
|
+
{
|
|
49
|
+
headers: {
|
|
50
|
+
email,
|
|
51
|
+
token,
|
|
52
|
+
machineId: machine.machineIdSync(),
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const serverTasks = Array.isArray(response.data.task)
|
|
58
|
+
? response.data.task
|
|
59
|
+
: [response.data.task];
|
|
60
|
+
|
|
61
|
+
// Sync with local file
|
|
62
|
+
serverTasks.forEach((serverTask) => {
|
|
63
|
+
const index = localTasks.findIndex((t) => t.id === serverTask.id);
|
|
64
|
+
|
|
65
|
+
if (index !== -1) {
|
|
66
|
+
localTasks[index] = {
|
|
67
|
+
...localTasks[index],
|
|
68
|
+
...serverTask,
|
|
69
|
+
};
|
|
70
|
+
} else {
|
|
71
|
+
localTasks.push(serverTask);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(
|
|
76
|
+
taskFilePath,
|
|
77
|
+
JSON.stringify({ tasks: localTasks }, null, 2)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
console.log("✅ Task synced successfully");
|
|
81
|
+
|
|
82
|
+
serverTasks.forEach(task => {
|
|
83
|
+
console.log(chalk.gray("\n------------------------------------------"));
|
|
84
|
+
|
|
85
|
+
const statusColor =
|
|
86
|
+
task.status === "completed"
|
|
87
|
+
? chalk.green
|
|
88
|
+
: task.status === "in-progress"
|
|
89
|
+
? chalk.yellow
|
|
90
|
+
: chalk.red;
|
|
91
|
+
const priorityColor =
|
|
92
|
+
task.priority === "high"
|
|
93
|
+
? chalk.red
|
|
94
|
+
: task.priority === "medium"
|
|
95
|
+
? chalk.yellow
|
|
96
|
+
: chalk.green;
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
printField("Task ID", task._id, chalk.cyan.bold, chalk.white);
|
|
100
|
+
printField("Title", task.title, chalk.green.bold, chalk.white);
|
|
101
|
+
printField("Description", task.description || "N/A", chalk.yellow.bold, chalk.white);
|
|
102
|
+
printField("Status", task.status, chalk.blue.bold, statusColor);
|
|
103
|
+
printField("Priority", task.priority, chalk.magenta.bold, priorityColor);
|
|
104
|
+
printField(
|
|
105
|
+
"Due Date",
|
|
106
|
+
task.dueDate ? new Date(task.dueDate).toDateString() : "N/A",
|
|
107
|
+
chalk.cyan.bold,
|
|
108
|
+
chalk.white
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (task.attachments?.length > 0) {
|
|
112
|
+
printField("Attachments", `${task.attachments.length}`, chalk.blue.bold, chalk.white);
|
|
113
|
+
|
|
114
|
+
task.attachments.forEach((att, index) => {
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.gray(` ${index + 1}. ID: ${att._id}`)
|
|
117
|
+
);
|
|
118
|
+
console.log(
|
|
119
|
+
chalk.gray(` Name: ${att.name}`)
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
printField("Attachments", "None", chalk.blue.bold, chalk.gray);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(chalk.gray("------------------------------------------\n"));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(
|
|
134
|
+
"❌ Error:",
|
|
135
|
+
err.response?.data?.error || err.message
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function printField(label, value, labelColor, valueColor, width = 15) {
|
|
141
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
142
|
+
|
|
143
|
+
const plainLabel = label.padEnd(width);
|
|
144
|
+
const labelPart = `${plainLabel} : `;
|
|
145
|
+
const indent = " ".repeat(labelPart.length);
|
|
146
|
+
|
|
147
|
+
const availableWidth = terminalWidth - indent.length;
|
|
148
|
+
|
|
149
|
+
// Normalize multiline input
|
|
150
|
+
const rawLines = String(value).replace(/\r/g, "").split("\n");
|
|
151
|
+
|
|
152
|
+
rawLines.forEach((rawLine, lineIndex) => {
|
|
153
|
+
let words = rawLine.split(" ");
|
|
154
|
+
let currentLine = "";
|
|
155
|
+
|
|
156
|
+
words.forEach(word => {
|
|
157
|
+
if ((currentLine + word).length > availableWidth) {
|
|
158
|
+
if (lineIndex === 0 && currentLine === "") {
|
|
159
|
+
console.log(
|
|
160
|
+
labelColor(plainLabel) + " : " + valueColor(word)
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(indent + valueColor(currentLine.trim()));
|
|
164
|
+
currentLine = word + " ";
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
currentLine += word + " ";
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (currentLine.trim()) {
|
|
172
|
+
if (lineIndex === 0) {
|
|
173
|
+
console.log(
|
|
174
|
+
labelColor(plainLabel) +
|
|
175
|
+
" : " +
|
|
176
|
+
valueColor(currentLine.trim())
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
console.log(indent + valueColor(currentLine.trim()));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// After first printed line, ensure next raw lines are indented
|
|
184
|
+
lineIndex = 1;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
export default task;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkncollab-cli",
|
|
3
3
|
"author": "Raman Singh",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.85",
|
|
5
5
|
"description": "CLI tool for ThinkNCollab",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"axios": "^1.12.2",
|
|
16
16
|
"chalk": "^5.6.2",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"cli-table3": "^0.6.5",
|
|
18
|
+
"form-data": "^4.0.5",
|
|
19
|
+
"inquirer": "^8.2.5",
|
|
20
|
+
"inquirer-file-selector": "^1.0.1",
|
|
21
|
+
"node-machine-id": "^1.1.12",
|
|
22
|
+
"open": "^11.0.0"
|
|
19
23
|
}
|
|
20
|
-
}
|
|
24
|
+
}
|