@vibetasks/cli 0.1.0 → 0.3.0
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/dist/bin/vibetasks.js +2654 -0
- package/dist/chunk-2KRLRG4G.js +161 -0
- package/dist/chunk-PZF4VRDG.js +430 -0
- package/dist/daemon-config-EUSBQA4E.js +10 -0
- package/dist/src/daemon-worker.js +198 -0
- package/hooks/sync-todos.js +333 -0
- package/package.json +10 -6
- package/dist/vibetasks.js +0 -1126
package/dist/vibetasks.js
DELETED
|
@@ -1,1126 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// bin/vibetasks.ts
|
|
4
|
-
import { Command as Command11 } from "commander";
|
|
5
|
-
|
|
6
|
-
// src/commands/login.ts
|
|
7
|
-
import { Command } from "commander";
|
|
8
|
-
import inquirer from "inquirer";
|
|
9
|
-
import ora2 from "ora";
|
|
10
|
-
import chalk2 from "chalk";
|
|
11
|
-
import { AuthManager as AuthManager2, createSupabaseClient } from "@vibetasks/core";
|
|
12
|
-
|
|
13
|
-
// src/utils/browser-auth.ts
|
|
14
|
-
import { createServer } from "http";
|
|
15
|
-
import { exec } from "child_process";
|
|
16
|
-
import { promisify } from "util";
|
|
17
|
-
import ora from "ora";
|
|
18
|
-
import chalk from "chalk";
|
|
19
|
-
import { AuthManager } from "@vibetasks/core";
|
|
20
|
-
var execAsync = promisify(exec);
|
|
21
|
-
async function openBrowser(url) {
|
|
22
|
-
const platform = process.platform;
|
|
23
|
-
let command;
|
|
24
|
-
if (platform === "win32") {
|
|
25
|
-
command = `start "" "${url}"`;
|
|
26
|
-
} else if (platform === "darwin") {
|
|
27
|
-
command = `open "${url}"`;
|
|
28
|
-
} else {
|
|
29
|
-
command = `xdg-open "${url}"`;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
await execAsync(command);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.log(chalk.yellow(`
|
|
35
|
-
Couldn't open browser automatically. Please visit:
|
|
36
|
-
${url}
|
|
37
|
-
`));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function loginWithBrowser() {
|
|
41
|
-
console.log(chalk.blue.bold("\n\u{1F510} TaskFlow Browser Login\n"));
|
|
42
|
-
const spinner = ora("Starting local server...").start();
|
|
43
|
-
const port = await getAvailablePort();
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
let timeoutId = null;
|
|
46
|
-
const server = createServer(async (req, res) => {
|
|
47
|
-
if (req.method === "OPTIONS") {
|
|
48
|
-
res.writeHead(200, {
|
|
49
|
-
"Access-Control-Allow-Origin": "*",
|
|
50
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
51
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
52
|
-
});
|
|
53
|
-
res.end();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (req.url?.startsWith("/callback")) {
|
|
57
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
58
|
-
const accessToken = url.searchParams.get("access_token");
|
|
59
|
-
const refreshToken = url.searchParams.get("refresh_token");
|
|
60
|
-
const email = url.searchParams.get("email");
|
|
61
|
-
const supabaseUrl = url.searchParams.get("supabase_url");
|
|
62
|
-
const supabaseKey = url.searchParams.get("supabase_key");
|
|
63
|
-
if (!accessToken || !refreshToken || !email) {
|
|
64
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
65
|
-
res.end(`
|
|
66
|
-
<!DOCTYPE html>
|
|
67
|
-
<html>
|
|
68
|
-
<head>
|
|
69
|
-
<title>Authentication Failed</title>
|
|
70
|
-
<style>
|
|
71
|
-
body { font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
|
|
72
|
-
.container { text-align: center; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
73
|
-
h1 { color: #e53e3e; margin: 0 0 16px 0; }
|
|
74
|
-
p { color: #666; margin: 0; }
|
|
75
|
-
</style>
|
|
76
|
-
</head>
|
|
77
|
-
<body>
|
|
78
|
-
<div class="container">
|
|
79
|
-
<h1>Authentication Failed</h1>
|
|
80
|
-
<p>Missing required authentication data.</p>
|
|
81
|
-
<p style="margin-top: 16px;">You can close this window.</p>
|
|
82
|
-
</div>
|
|
83
|
-
</body>
|
|
84
|
-
</html>
|
|
85
|
-
`);
|
|
86
|
-
spinner.fail(chalk.red("Authentication failed - missing data"));
|
|
87
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
88
|
-
server.close(() => {
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
|
91
|
-
reject(new Error("Authentication failed"));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
const authManager = new AuthManager();
|
|
96
|
-
await authManager.setAccessToken(accessToken);
|
|
97
|
-
await authManager.setRefreshToken(refreshToken);
|
|
98
|
-
if (supabaseUrl) {
|
|
99
|
-
await authManager.setConfig("supabase_url", supabaseUrl);
|
|
100
|
-
}
|
|
101
|
-
if (supabaseKey) {
|
|
102
|
-
await authManager.setConfig("supabase_key", supabaseKey);
|
|
103
|
-
}
|
|
104
|
-
const storageMethod = authManager.getStorageMethod();
|
|
105
|
-
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
106
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
107
|
-
res.end(`
|
|
108
|
-
<!DOCTYPE html>
|
|
109
|
-
<html>
|
|
110
|
-
<head>
|
|
111
|
-
<title>Authentication Successful</title>
|
|
112
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
113
|
-
<style>
|
|
114
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
115
|
-
body {
|
|
116
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: center;
|
|
120
|
-
min-height: 100vh;
|
|
121
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
122
|
-
padding: 20px;
|
|
123
|
-
}
|
|
124
|
-
.container {
|
|
125
|
-
text-align: center;
|
|
126
|
-
padding: 60px 40px;
|
|
127
|
-
background: white;
|
|
128
|
-
border-radius: 20px;
|
|
129
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
130
|
-
max-width: 450px;
|
|
131
|
-
width: 100%;
|
|
132
|
-
}
|
|
133
|
-
.checkmark {
|
|
134
|
-
width: 80px;
|
|
135
|
-
height: 80px;
|
|
136
|
-
border-radius: 50%;
|
|
137
|
-
display: block;
|
|
138
|
-
stroke-width: 3;
|
|
139
|
-
stroke: #4CAF50;
|
|
140
|
-
stroke-miterlimit: 10;
|
|
141
|
-
margin: 0 auto 24px;
|
|
142
|
-
animation: fill 0.4s ease-in-out 0.4s forwards, scale 0.3s ease-in-out 0.9s both;
|
|
143
|
-
}
|
|
144
|
-
.checkmark-circle {
|
|
145
|
-
stroke-dasharray: 166;
|
|
146
|
-
stroke-dashoffset: 166;
|
|
147
|
-
stroke-width: 3;
|
|
148
|
-
stroke-miterlimit: 10;
|
|
149
|
-
stroke: #4CAF50;
|
|
150
|
-
fill: none;
|
|
151
|
-
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
152
|
-
}
|
|
153
|
-
.checkmark-check {
|
|
154
|
-
transform-origin: 50% 50%;
|
|
155
|
-
stroke-dasharray: 48;
|
|
156
|
-
stroke-dashoffset: 48;
|
|
157
|
-
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
|
158
|
-
}
|
|
159
|
-
@keyframes stroke {
|
|
160
|
-
100% { stroke-dashoffset: 0; }
|
|
161
|
-
}
|
|
162
|
-
@keyframes scale {
|
|
163
|
-
0%, 100% { transform: none; }
|
|
164
|
-
50% { transform: scale3d(1.1, 1.1, 1); }
|
|
165
|
-
}
|
|
166
|
-
h1 {
|
|
167
|
-
color: #2d3748;
|
|
168
|
-
font-size: 28px;
|
|
169
|
-
font-weight: 700;
|
|
170
|
-
margin-bottom: 12px;
|
|
171
|
-
}
|
|
172
|
-
.email {
|
|
173
|
-
font-weight: 600;
|
|
174
|
-
color: #667eea;
|
|
175
|
-
font-size: 16px;
|
|
176
|
-
margin-bottom: 24px;
|
|
177
|
-
}
|
|
178
|
-
.message {
|
|
179
|
-
color: #718096;
|
|
180
|
-
font-size: 15px;
|
|
181
|
-
line-height: 1.6;
|
|
182
|
-
}
|
|
183
|
-
.success-badge {
|
|
184
|
-
display: inline-block;
|
|
185
|
-
background: #f0fdf4;
|
|
186
|
-
color: #15803d;
|
|
187
|
-
padding: 8px 16px;
|
|
188
|
-
border-radius: 20px;
|
|
189
|
-
font-size: 14px;
|
|
190
|
-
font-weight: 600;
|
|
191
|
-
margin-top: 20px;
|
|
192
|
-
}
|
|
193
|
-
.button-group {
|
|
194
|
-
margin-top: 32px;
|
|
195
|
-
display: flex;
|
|
196
|
-
gap: 12px;
|
|
197
|
-
justify-content: center;
|
|
198
|
-
}
|
|
199
|
-
.button {
|
|
200
|
-
padding: 12px 24px;
|
|
201
|
-
border-radius: 8px;
|
|
202
|
-
font-weight: 600;
|
|
203
|
-
font-size: 14px;
|
|
204
|
-
cursor: pointer;
|
|
205
|
-
transition: all 0.2s;
|
|
206
|
-
text-decoration: none;
|
|
207
|
-
display: inline-block;
|
|
208
|
-
}
|
|
209
|
-
.button-primary {
|
|
210
|
-
background: #667eea;
|
|
211
|
-
color: white;
|
|
212
|
-
border: none;
|
|
213
|
-
}
|
|
214
|
-
.button-primary:hover {
|
|
215
|
-
background: #5568d3;
|
|
216
|
-
transform: translateY(-1px);
|
|
217
|
-
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
218
|
-
}
|
|
219
|
-
.button-secondary {
|
|
220
|
-
background: #f7fafc;
|
|
221
|
-
color: #4a5568;
|
|
222
|
-
border: 2px solid #e2e8f0;
|
|
223
|
-
}
|
|
224
|
-
.button-secondary:hover {
|
|
225
|
-
background: #edf2f7;
|
|
226
|
-
border-color: #cbd5e0;
|
|
227
|
-
}
|
|
228
|
-
</style>
|
|
229
|
-
</head>
|
|
230
|
-
<body>
|
|
231
|
-
<div class="container">
|
|
232
|
-
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
|
233
|
-
<circle class="checkmark-circle" cx="26" cy="26" r="25" fill="none"/>
|
|
234
|
-
<path class="checkmark-check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
|
235
|
-
</svg>
|
|
236
|
-
<h1>Authentication Successful!</h1>
|
|
237
|
-
<p class="email">${email}</p>
|
|
238
|
-
<p class="message">
|
|
239
|
-
Your TaskFlow CLI is now connected to your account.<br>
|
|
240
|
-
You can close this window and return to your terminal.
|
|
241
|
-
</p>
|
|
242
|
-
<div class="success-badge">Ready to use</div>
|
|
243
|
-
<div class="button-group">
|
|
244
|
-
<a href="http://localhost:2843/dashboard" class="button button-primary">Go to Dashboard</a>
|
|
245
|
-
<button onclick="window.close()" class="button button-secondary">Close Window</button>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
</body>
|
|
249
|
-
</html>
|
|
250
|
-
`);
|
|
251
|
-
spinner.succeed(chalk.green("Successfully authenticated!"));
|
|
252
|
-
console.log(chalk.gray(`
|
|
253
|
-
Logged in as: ${email}`));
|
|
254
|
-
console.log(chalk.gray(`Tokens stored in: ${storageLocation}`));
|
|
255
|
-
console.log(chalk.blue.bold("\n\u2728 You can now use TaskFlow CLI commands\n"));
|
|
256
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
257
|
-
resolve();
|
|
258
|
-
server.close(() => {
|
|
259
|
-
process.exit(0);
|
|
260
|
-
});
|
|
261
|
-
} catch (error) {
|
|
262
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
263
|
-
res.end(`
|
|
264
|
-
<!DOCTYPE html>
|
|
265
|
-
<html>
|
|
266
|
-
<head>
|
|
267
|
-
<title>Storage Error</title>
|
|
268
|
-
<style>
|
|
269
|
-
body { font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
|
|
270
|
-
.container { text-align: center; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
271
|
-
h1 { color: #e53e3e; margin: 0 0 16px 0; }
|
|
272
|
-
p { color: #666; margin: 0; }
|
|
273
|
-
</style>
|
|
274
|
-
</head>
|
|
275
|
-
<body>
|
|
276
|
-
<div class="container">
|
|
277
|
-
<h1>Storage Error</h1>
|
|
278
|
-
<p>Failed to store authentication tokens.</p>
|
|
279
|
-
<p style="margin-top: 16px;">You can close this window.</p>
|
|
280
|
-
</div>
|
|
281
|
-
</body>
|
|
282
|
-
</html>
|
|
283
|
-
`);
|
|
284
|
-
spinner.fail(chalk.red("Failed to store tokens"));
|
|
285
|
-
console.error(chalk.red(`
|
|
286
|
-
Error: ${error.message}`));
|
|
287
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
288
|
-
server.close(() => {
|
|
289
|
-
process.exit(1);
|
|
290
|
-
});
|
|
291
|
-
reject(error);
|
|
292
|
-
}
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
res.writeHead(404);
|
|
296
|
-
res.end();
|
|
297
|
-
});
|
|
298
|
-
server.listen(port, async () => {
|
|
299
|
-
spinner.text = "Opening browser...";
|
|
300
|
-
const taskflowPort = process.env.TASKFLOW_WEB_PORT || "2843";
|
|
301
|
-
const authUrl = `http://localhost:${taskflowPort}/cli-auth?port=${port}`;
|
|
302
|
-
spinner.text = "Waiting for authentication in browser...";
|
|
303
|
-
console.log(chalk.gray(`
|
|
304
|
-
If browser doesn't open, visit: ${authUrl}
|
|
305
|
-
`));
|
|
306
|
-
await openBrowser(authUrl);
|
|
307
|
-
});
|
|
308
|
-
server.on("error", (error) => {
|
|
309
|
-
spinner.fail(chalk.red("Server error"));
|
|
310
|
-
console.error(chalk.red(`
|
|
311
|
-
Error: ${error.message}`));
|
|
312
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
313
|
-
reject(error);
|
|
314
|
-
process.exit(1);
|
|
315
|
-
});
|
|
316
|
-
timeoutId = setTimeout(() => {
|
|
317
|
-
spinner.fail(chalk.red("Authentication timeout"));
|
|
318
|
-
console.log(chalk.yellow("\nAuthentication timed out after 5 minutes"));
|
|
319
|
-
server.close(() => {
|
|
320
|
-
process.exit(1);
|
|
321
|
-
});
|
|
322
|
-
reject(new Error("Authentication timeout"));
|
|
323
|
-
}, 5 * 60 * 1e3);
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
async function getAvailablePort(startPort = 3737) {
|
|
327
|
-
return new Promise((resolve, reject) => {
|
|
328
|
-
const server = createServer();
|
|
329
|
-
server.listen(startPort, () => {
|
|
330
|
-
const port = server.address().port;
|
|
331
|
-
server.close(() => resolve(port));
|
|
332
|
-
});
|
|
333
|
-
server.on("error", (error) => {
|
|
334
|
-
if (error.code === "EADDRINUSE") {
|
|
335
|
-
resolve(getAvailablePort(startPort + 1));
|
|
336
|
-
} else {
|
|
337
|
-
reject(error);
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/commands/login.ts
|
|
344
|
-
var loginCommand = new Command("login").description("Authenticate with TaskFlow").option("-b, --browser", "Login via browser (recommended)").action(async (options) => {
|
|
345
|
-
if (options.browser) {
|
|
346
|
-
await loginWithBrowser();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
console.log(chalk2.blue.bold("\n\u{1F510} TaskFlow Login\n"));
|
|
350
|
-
const answers = await inquirer.prompt([
|
|
351
|
-
{
|
|
352
|
-
type: "input",
|
|
353
|
-
name: "email",
|
|
354
|
-
message: "Email:",
|
|
355
|
-
validate: (input) => {
|
|
356
|
-
if (!input.includes("@")) return "Please enter a valid email address";
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
type: "password",
|
|
362
|
-
name: "password",
|
|
363
|
-
message: "Password:",
|
|
364
|
-
mask: "*",
|
|
365
|
-
validate: (input) => {
|
|
366
|
-
if (input.length < 6) return "Password must be at least 6 characters";
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
]);
|
|
371
|
-
const spinner = ora2("Authenticating...").start();
|
|
372
|
-
try {
|
|
373
|
-
const authManager = new AuthManager2();
|
|
374
|
-
const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
375
|
-
const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || process.env.SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
376
|
-
await authManager.setConfig("supabase_url", supabaseUrl);
|
|
377
|
-
await authManager.setConfig("supabase_key", supabaseKey);
|
|
378
|
-
const supabase = createSupabaseClient({
|
|
379
|
-
supabaseUrl,
|
|
380
|
-
supabaseKey
|
|
381
|
-
});
|
|
382
|
-
const { data, error } = await supabase.auth.signInWithPassword({
|
|
383
|
-
email: answers.email,
|
|
384
|
-
password: answers.password
|
|
385
|
-
});
|
|
386
|
-
if (error) throw error;
|
|
387
|
-
if (!data.session) {
|
|
388
|
-
throw new Error("Authentication failed - no session returned");
|
|
389
|
-
}
|
|
390
|
-
await authManager.setAccessToken(data.session.access_token);
|
|
391
|
-
await authManager.setRefreshToken(data.session.refresh_token);
|
|
392
|
-
const storageMethod = authManager.getStorageMethod();
|
|
393
|
-
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
394
|
-
spinner.succeed(chalk2.green("Successfully authenticated!"));
|
|
395
|
-
console.log(chalk2.gray(`
|
|
396
|
-
Logged in as: ${data.user.email}`));
|
|
397
|
-
console.log(chalk2.gray(`Tokens stored in: ${storageLocation}`));
|
|
398
|
-
console.log(chalk2.blue.bold("\n\u2728 You can now use TaskFlow CLI commands\n"));
|
|
399
|
-
process.exit(0);
|
|
400
|
-
} catch (error) {
|
|
401
|
-
spinner.fail(chalk2.red("Authentication failed"));
|
|
402
|
-
console.error(chalk2.red(`
|
|
403
|
-
Error: ${error.message}`));
|
|
404
|
-
process.exit(1);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// src/commands/add.ts
|
|
409
|
-
import { Command as Command2 } from "commander";
|
|
410
|
-
import ora3 from "ora";
|
|
411
|
-
import chalk3 from "chalk";
|
|
412
|
-
import { AuthManager as AuthManager3, TaskOperations } from "@vibetasks/core";
|
|
413
|
-
|
|
414
|
-
// src/utils/date-parser.ts
|
|
415
|
-
import { addDays, addWeeks, addMonths, format, parse, isValid } from "date-fns";
|
|
416
|
-
function parseDate(dateStr) {
|
|
417
|
-
const today = /* @__PURE__ */ new Date();
|
|
418
|
-
today.setHours(0, 0, 0, 0);
|
|
419
|
-
const lowerStr = dateStr.toLowerCase().trim();
|
|
420
|
-
if (lowerStr === "today") {
|
|
421
|
-
return today.toISOString();
|
|
422
|
-
}
|
|
423
|
-
if (lowerStr === "tomorrow") {
|
|
424
|
-
return addDays(today, 1).toISOString();
|
|
425
|
-
}
|
|
426
|
-
const relativeMatch = lowerStr.match(/^\+(\d+)([dwm])$/);
|
|
427
|
-
if (relativeMatch) {
|
|
428
|
-
const amount = parseInt(relativeMatch[1], 10);
|
|
429
|
-
const unit = relativeMatch[2];
|
|
430
|
-
if (unit === "d") {
|
|
431
|
-
return addDays(today, amount).toISOString();
|
|
432
|
-
} else if (unit === "w") {
|
|
433
|
-
return addWeeks(today, amount).toISOString();
|
|
434
|
-
} else if (unit === "m") {
|
|
435
|
-
return addMonths(today, amount).toISOString();
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const parsedDate = parse(dateStr, "yyyy-MM-dd", /* @__PURE__ */ new Date());
|
|
439
|
-
if (isValid(parsedDate)) {
|
|
440
|
-
return parsedDate.toISOString();
|
|
441
|
-
}
|
|
442
|
-
const usDate = parse(dateStr, "MM/dd/yyyy", /* @__PURE__ */ new Date());
|
|
443
|
-
if (isValid(usDate)) {
|
|
444
|
-
return usDate.toISOString();
|
|
445
|
-
}
|
|
446
|
-
throw new Error(
|
|
447
|
-
`Invalid date format: "${dateStr}". Use: "today", "tomorrow", "+3d", "+2w", "+1m", or "YYYY-MM-DD"`
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// src/commands/add.ts
|
|
452
|
-
import { detectProject } from "@vibetasks/shared/utils/project-detector";
|
|
453
|
-
var addCommand = new Command2("add").description("Add a new task").argument("<title>", "Task title").option("-n, --notes <notes>", "Task notes (markdown supported)").option("-d, --due <date>", 'Due date (YYYY-MM-DD, "today", "tomorrow", "+3d")').option("-p, --priority <level>", "Priority: low, medium, high", "none").option("-t, --tags <tags...>", "Tags (space-separated)").option("--project <name>", "Override auto-detected project").option("-e, --energy <level>", "Energy required: low, medium, high").action(async (title, options) => {
|
|
454
|
-
const spinner = ora3("Creating task...").start();
|
|
455
|
-
try {
|
|
456
|
-
const authManager = new AuthManager3();
|
|
457
|
-
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
458
|
-
let projectTag;
|
|
459
|
-
if (options.project) {
|
|
460
|
-
projectTag = options.project;
|
|
461
|
-
} else {
|
|
462
|
-
try {
|
|
463
|
-
const detected = await detectProject(process.cwd());
|
|
464
|
-
projectTag = detected.name;
|
|
465
|
-
if (process.env.TASKFLOW_VERBOSE) {
|
|
466
|
-
console.log(chalk3.gray(`Detected project: ${projectTag} (${detected.source})`));
|
|
467
|
-
}
|
|
468
|
-
} catch (error) {
|
|
469
|
-
if (process.env.TASKFLOW_VERBOSE) {
|
|
470
|
-
console.log(chalk3.gray("Could not detect project"));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
const createdBy = isRunningInClaudeCode() ? "ai" : "human";
|
|
475
|
-
let dueDate;
|
|
476
|
-
if (options.due) {
|
|
477
|
-
dueDate = parseDate(options.due);
|
|
478
|
-
} else {
|
|
479
|
-
dueDate = parseDate("today");
|
|
480
|
-
}
|
|
481
|
-
const validPriorities = ["none", "low", "medium", "high"];
|
|
482
|
-
if (!validPriorities.includes(options.priority)) {
|
|
483
|
-
throw new Error(`Invalid priority. Must be one of: ${validPriorities.join(", ")}`);
|
|
484
|
-
}
|
|
485
|
-
if (options.energy) {
|
|
486
|
-
const validEnergy = ["low", "medium", "high"];
|
|
487
|
-
if (!validEnergy.includes(options.energy)) {
|
|
488
|
-
throw new Error(`Invalid energy level. Must be one of: ${validEnergy.join(", ")}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
const task = await taskOps.createTask({
|
|
492
|
-
title,
|
|
493
|
-
notes: options.notes,
|
|
494
|
-
notes_format: "markdown",
|
|
495
|
-
due_date: dueDate,
|
|
496
|
-
priority: options.priority,
|
|
497
|
-
project_tag: projectTag,
|
|
498
|
-
// AUTO-TAGGED!
|
|
499
|
-
created_by: createdBy,
|
|
500
|
-
// ai or human
|
|
501
|
-
status: createdBy === "ai" ? "vibing" : "todo",
|
|
502
|
-
// AI tasks start vibing
|
|
503
|
-
energy_required: options.energy
|
|
504
|
-
});
|
|
505
|
-
if (options.tags && options.tags.length > 0) {
|
|
506
|
-
const tagIds = [];
|
|
507
|
-
for (const tagName of options.tags) {
|
|
508
|
-
const tag = await taskOps.findOrCreateTag(tagName);
|
|
509
|
-
tagIds.push(tag.id);
|
|
510
|
-
}
|
|
511
|
-
await taskOps.linkTaskTags(task.id, tagIds);
|
|
512
|
-
}
|
|
513
|
-
spinner.succeed(chalk3.green("Task created!"));
|
|
514
|
-
console.log();
|
|
515
|
-
if (projectTag) {
|
|
516
|
-
console.log(chalk3.gray(`\u{1F4C1} Project: ${chalk3.white(projectTag)}`));
|
|
517
|
-
}
|
|
518
|
-
if (createdBy === "ai") {
|
|
519
|
-
console.log(chalk3.gray(`\u{1F916} Created by: ${chalk3.cyan("AI")}`));
|
|
520
|
-
console.log(chalk3.gray(`\u26A1 Status: ${chalk3.magenta("vibing")}`));
|
|
521
|
-
}
|
|
522
|
-
console.log(chalk3.gray(`ID: ${task.id.substring(0, 8)}...`));
|
|
523
|
-
console.log(chalk3.white.bold(task.title));
|
|
524
|
-
if (task.notes) {
|
|
525
|
-
console.log(chalk3.gray(`Notes: ${task.notes.substring(0, 50)}${task.notes.length > 50 ? "..." : ""}`));
|
|
526
|
-
}
|
|
527
|
-
if (task.priority && task.priority !== "none") {
|
|
528
|
-
const priorityColors = {
|
|
529
|
-
high: chalk3.red,
|
|
530
|
-
medium: chalk3.yellow,
|
|
531
|
-
low: chalk3.blue
|
|
532
|
-
};
|
|
533
|
-
console.log(priorityColors[task.priority](`Priority: ${task.priority.toUpperCase()}`));
|
|
534
|
-
}
|
|
535
|
-
if (task.energy_required) {
|
|
536
|
-
const energyEmoji = { low: "\u{1F50B}", medium: "\u26A1", high: "\u{1F525}" };
|
|
537
|
-
console.log(chalk3.gray(`Energy: ${energyEmoji[task.energy_required]} ${task.energy_required}`));
|
|
538
|
-
}
|
|
539
|
-
if (task.due_date) {
|
|
540
|
-
console.log(chalk3.blue(`Due: ${task.due_date.split("T")[0]}`));
|
|
541
|
-
}
|
|
542
|
-
if (options.tags && options.tags.length > 0) {
|
|
543
|
-
console.log(chalk3.magenta(`Tags: ${options.tags.join(", ")}`));
|
|
544
|
-
}
|
|
545
|
-
console.log();
|
|
546
|
-
process.exit(0);
|
|
547
|
-
} catch (error) {
|
|
548
|
-
spinner.fail(chalk3.red("Failed to create task"));
|
|
549
|
-
if (error.message.includes("Not authenticated")) {
|
|
550
|
-
console.error(chalk3.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
551
|
-
console.error(chalk3.gray("Run: taskflow login\n"));
|
|
552
|
-
} else {
|
|
553
|
-
console.error(chalk3.red(`
|
|
554
|
-
Error: ${error.message}
|
|
555
|
-
`));
|
|
556
|
-
}
|
|
557
|
-
process.exit(1);
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
function isRunningInClaudeCode() {
|
|
561
|
-
return !!(process.env.CLAUDE_CODE_SESSION || process.env.ANTHROPIC_CLI_SESSION || process.env.TERM_PROGRAM?.includes("claude") || process.env.TERM_PROGRAM?.includes("anthropic"));
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// src/commands/list.ts
|
|
565
|
-
import { Command as Command3 } from "commander";
|
|
566
|
-
import chalk4 from "chalk";
|
|
567
|
-
import Table from "cli-table3";
|
|
568
|
-
import { AuthManager as AuthManager4, TaskOperations as TaskOperations2 } from "@vibetasks/core";
|
|
569
|
-
var listCommand = new Command3("list").description("List tasks").argument("[filter]", "Filter: all, today, upcoming, completed", "all").option("-l, --limit <number>", "Maximum number of tasks to show", "50").option("--project <name>", "Filter by project tag").option("--created-by <source>", "Filter by creator: ai or human").option("--status <status>", "Filter by status: todo, vibing, done").action(async (filter, options) => {
|
|
570
|
-
try {
|
|
571
|
-
const validFilters = ["all", "today", "upcoming", "completed"];
|
|
572
|
-
if (!validFilters.includes(filter)) {
|
|
573
|
-
console.error(chalk4.red(`Invalid filter: ${filter}`));
|
|
574
|
-
console.error(chalk4.gray(`Valid filters: ${validFilters.join(", ")}`));
|
|
575
|
-
process.exit(1);
|
|
576
|
-
}
|
|
577
|
-
const authManager = new AuthManager4();
|
|
578
|
-
const taskOps = await TaskOperations2.fromAuthManager(authManager);
|
|
579
|
-
let tasks = await taskOps.getTasks(filter);
|
|
580
|
-
if (options.project) {
|
|
581
|
-
tasks = tasks.filter((t) => t.project_tag === options.project);
|
|
582
|
-
}
|
|
583
|
-
if (options.createdBy) {
|
|
584
|
-
const validSources = ["ai", "human"];
|
|
585
|
-
if (!validSources.includes(options.createdBy)) {
|
|
586
|
-
console.error(chalk4.red(`Invalid created-by filter. Must be: ai or human`));
|
|
587
|
-
process.exit(1);
|
|
588
|
-
}
|
|
589
|
-
tasks = tasks.filter((t) => t.created_by === options.createdBy);
|
|
590
|
-
}
|
|
591
|
-
if (options.status) {
|
|
592
|
-
const validStatuses = ["todo", "vibing", "done"];
|
|
593
|
-
if (!validStatuses.includes(options.status)) {
|
|
594
|
-
console.error(chalk4.red(`Invalid status filter. Must be: todo, vibing, or done`));
|
|
595
|
-
process.exit(1);
|
|
596
|
-
}
|
|
597
|
-
tasks = tasks.filter((t) => t.status === options.status);
|
|
598
|
-
}
|
|
599
|
-
if (tasks.length === 0) {
|
|
600
|
-
const filterMessages = {
|
|
601
|
-
all: "No active tasks found",
|
|
602
|
-
today: "No tasks due today",
|
|
603
|
-
upcoming: "No upcoming tasks",
|
|
604
|
-
completed: "No completed tasks"
|
|
605
|
-
};
|
|
606
|
-
console.log(chalk4.gray(`
|
|
607
|
-
${filterMessages[filter] || "No tasks found"}.
|
|
608
|
-
`));
|
|
609
|
-
process.exit(0);
|
|
610
|
-
}
|
|
611
|
-
const limit = parseInt(options.limit, 10);
|
|
612
|
-
const displayTasks = tasks.slice(0, limit);
|
|
613
|
-
const table = new Table({
|
|
614
|
-
head: [
|
|
615
|
-
chalk4.cyan("ID"),
|
|
616
|
-
chalk4.cyan("Status"),
|
|
617
|
-
chalk4.cyan("Title"),
|
|
618
|
-
chalk4.cyan("Project"),
|
|
619
|
-
chalk4.cyan("Priority"),
|
|
620
|
-
chalk4.cyan("Due")
|
|
621
|
-
],
|
|
622
|
-
colWidths: [10, 10, 35, 15, 10, 12],
|
|
623
|
-
wordWrap: true
|
|
624
|
-
});
|
|
625
|
-
displayTasks.forEach((task) => {
|
|
626
|
-
const priorityColors = {
|
|
627
|
-
high: chalk4.red,
|
|
628
|
-
medium: chalk4.yellow,
|
|
629
|
-
low: chalk4.blue,
|
|
630
|
-
none: chalk4.gray
|
|
631
|
-
};
|
|
632
|
-
const statusColors = {
|
|
633
|
-
todo: chalk4.gray,
|
|
634
|
-
vibing: chalk4.magenta,
|
|
635
|
-
done: chalk4.green
|
|
636
|
-
};
|
|
637
|
-
const statusEmojis = {
|
|
638
|
-
todo: "\u{1F4CB}",
|
|
639
|
-
vibing: "\u26A1",
|
|
640
|
-
done: "\u2713"
|
|
641
|
-
};
|
|
642
|
-
const priorityColor = priorityColors[task.priority || "none"];
|
|
643
|
-
const statusColor = statusColors[task.status || "todo"];
|
|
644
|
-
const id = task.id.substring(0, 8);
|
|
645
|
-
const status = statusColor(`${statusEmojis[task.status || "todo"]} ${task.status || "todo"}`);
|
|
646
|
-
const title = task.status === "done" ? chalk4.strikethrough(task.title) : task.title;
|
|
647
|
-
const titleWithAI = task.created_by === "ai" ? `\u{1F916} ${title}` : title;
|
|
648
|
-
const project = task.project_tag ? chalk4.blue(task.project_tag) : chalk4.gray("-");
|
|
649
|
-
const priority = priorityColor(task.priority || "none");
|
|
650
|
-
const dueDate = task.due_date ? task.due_date.split("T")[0] : chalk4.gray("-");
|
|
651
|
-
table.push([id, status, titleWithAI, project, priority, dueDate]);
|
|
652
|
-
});
|
|
653
|
-
console.log("\n" + table.toString());
|
|
654
|
-
const filterLabels = {
|
|
655
|
-
all: "active",
|
|
656
|
-
today: "today",
|
|
657
|
-
upcoming: "upcoming",
|
|
658
|
-
completed: "completed"
|
|
659
|
-
};
|
|
660
|
-
console.log(chalk4.gray(`
|
|
661
|
-
Total: ${tasks.length} ${filterLabels[filter]} task${tasks.length === 1 ? "" : "s"}`));
|
|
662
|
-
if (tasks.length > limit) {
|
|
663
|
-
console.log(chalk4.yellow(`Showing ${limit} of ${tasks.length} tasks. Use --limit to show more.`));
|
|
664
|
-
}
|
|
665
|
-
console.log();
|
|
666
|
-
process.exit(0);
|
|
667
|
-
} catch (error) {
|
|
668
|
-
if (error.message.includes("Not authenticated")) {
|
|
669
|
-
console.error(chalk4.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
670
|
-
console.error(chalk4.gray("Run: taskflow login\n"));
|
|
671
|
-
} else {
|
|
672
|
-
console.error(chalk4.red(`
|
|
673
|
-
Error: ${error.message}
|
|
674
|
-
`));
|
|
675
|
-
}
|
|
676
|
-
process.exit(1);
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
// src/commands/done.ts
|
|
681
|
-
import { Command as Command4 } from "commander";
|
|
682
|
-
import ora4 from "ora";
|
|
683
|
-
import chalk5 from "chalk";
|
|
684
|
-
import { AuthManager as AuthManager5, TaskOperations as TaskOperations3 } from "@vibetasks/core";
|
|
685
|
-
var doneCommand = new Command4("done").description("Mark a task as complete").argument("<id>", "Task ID (full or first 8 characters)").action(async (id) => {
|
|
686
|
-
const spinner = ora4("Completing task...").start();
|
|
687
|
-
try {
|
|
688
|
-
const authManager = new AuthManager5();
|
|
689
|
-
const taskOps = await TaskOperations3.fromAuthManager(authManager);
|
|
690
|
-
let taskId = id;
|
|
691
|
-
if (id.length < 32) {
|
|
692
|
-
const allTasks = await taskOps.getTasks("all");
|
|
693
|
-
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
694
|
-
if (!matchingTask) {
|
|
695
|
-
spinner.fail(chalk5.red("Task not found"));
|
|
696
|
-
console.error(chalk5.gray(`
|
|
697
|
-
No task found with ID starting with: ${id}
|
|
698
|
-
`));
|
|
699
|
-
process.exit(1);
|
|
700
|
-
}
|
|
701
|
-
taskId = matchingTask.id;
|
|
702
|
-
}
|
|
703
|
-
const task = await taskOps.completeTask(taskId);
|
|
704
|
-
spinner.succeed(chalk5.green("Task completed!"));
|
|
705
|
-
console.log(chalk5.gray(`
|
|
706
|
-
\u2713 ${task.title}`));
|
|
707
|
-
console.log(chalk5.gray(`Completed at: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
708
|
-
`));
|
|
709
|
-
process.exit(0);
|
|
710
|
-
} catch (error) {
|
|
711
|
-
spinner.fail(chalk5.red("Failed to complete task"));
|
|
712
|
-
if (error.message.includes("Not authenticated")) {
|
|
713
|
-
console.error(chalk5.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
714
|
-
console.error(chalk5.gray("Run: taskflow login\n"));
|
|
715
|
-
} else {
|
|
716
|
-
console.error(chalk5.red(`
|
|
717
|
-
Error: ${error.message}
|
|
718
|
-
`));
|
|
719
|
-
}
|
|
720
|
-
process.exit(1);
|
|
721
|
-
}
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
// src/commands/search.ts
|
|
725
|
-
import { Command as Command5 } from "commander";
|
|
726
|
-
import chalk6 from "chalk";
|
|
727
|
-
import Table2 from "cli-table3";
|
|
728
|
-
import { AuthManager as AuthManager6, TaskOperations as TaskOperations4 } from "@vibetasks/core";
|
|
729
|
-
var searchCommand = new Command5("search").description("Search tasks by title").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "20").action(async (query, options) => {
|
|
730
|
-
try {
|
|
731
|
-
const authManager = new AuthManager6();
|
|
732
|
-
const taskOps = await TaskOperations4.fromAuthManager(authManager);
|
|
733
|
-
const limit = parseInt(options.limit, 10);
|
|
734
|
-
const tasks = await taskOps.searchTasks(query, limit);
|
|
735
|
-
if (tasks.length === 0) {
|
|
736
|
-
console.log(chalk6.gray(`
|
|
737
|
-
No tasks found matching: "${query}"
|
|
738
|
-
`));
|
|
739
|
-
process.exit(0);
|
|
740
|
-
}
|
|
741
|
-
const table = new Table2({
|
|
742
|
-
head: [
|
|
743
|
-
chalk6.cyan("ID"),
|
|
744
|
-
chalk6.cyan("Title"),
|
|
745
|
-
chalk6.cyan("Priority"),
|
|
746
|
-
chalk6.cyan("Due Date")
|
|
747
|
-
],
|
|
748
|
-
colWidths: [12, 50, 10, 12],
|
|
749
|
-
wordWrap: true
|
|
750
|
-
});
|
|
751
|
-
tasks.forEach((task) => {
|
|
752
|
-
const priorityColors = {
|
|
753
|
-
high: chalk6.red,
|
|
754
|
-
medium: chalk6.yellow,
|
|
755
|
-
low: chalk6.blue,
|
|
756
|
-
none: chalk6.gray
|
|
757
|
-
};
|
|
758
|
-
const priorityColor = priorityColors[task.priority || "none"];
|
|
759
|
-
table.push([
|
|
760
|
-
task.id.substring(0, 8),
|
|
761
|
-
task.title,
|
|
762
|
-
priorityColor(task.priority || "none"),
|
|
763
|
-
task.due_date ? task.due_date.split("T")[0] : chalk6.gray("-")
|
|
764
|
-
]);
|
|
765
|
-
});
|
|
766
|
-
console.log("\n" + table.toString());
|
|
767
|
-
console.log(chalk6.gray(`
|
|
768
|
-
Found ${tasks.length} task${tasks.length === 1 ? "" : "s"} matching "${query}"
|
|
769
|
-
`));
|
|
770
|
-
process.exit(0);
|
|
771
|
-
} catch (error) {
|
|
772
|
-
if (error.message.includes("Not authenticated")) {
|
|
773
|
-
console.error(chalk6.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
774
|
-
console.error(chalk6.gray("Run: taskflow login\n"));
|
|
775
|
-
} else {
|
|
776
|
-
console.error(chalk6.red(`
|
|
777
|
-
Error: ${error.message}
|
|
778
|
-
`));
|
|
779
|
-
}
|
|
780
|
-
process.exit(1);
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
// src/commands/update.ts
|
|
785
|
-
import { Command as Command6 } from "commander";
|
|
786
|
-
import ora5 from "ora";
|
|
787
|
-
import chalk7 from "chalk";
|
|
788
|
-
import { AuthManager as AuthManager7, TaskOperations as TaskOperations5 } from "@vibetasks/core";
|
|
789
|
-
var updateCommand = new Command6("update").description("Update a task").argument("<id>", "Task ID (full or first 8 characters)").option("-t, --title <title>", "New title").option("-n, --notes <notes>", "New notes").option("-d, --due <date>", "New due date").option("-p, --priority <level>", "New priority: low, medium, high, none").option("-s, --status <status>", "New status: todo, vibing, done").option("--project <name>", "New project tag").option("-e, --energy <level>", "Energy required: low, medium, high").option("-c, --context <notes>", "Update context notes").action(async (id, options) => {
|
|
790
|
-
if (!options.title && !options.notes && !options.due && !options.priority && !options.status && !options.project && !options.energy && !options.context) {
|
|
791
|
-
console.error(chalk7.red("\nError: No updates specified"));
|
|
792
|
-
console.error(chalk7.gray("Provide at least one option: --title, --notes, --due, --priority, --status, --project, --energy, or --context\n"));
|
|
793
|
-
process.exit(1);
|
|
794
|
-
}
|
|
795
|
-
const spinner = ora5("Updating task...").start();
|
|
796
|
-
try {
|
|
797
|
-
const authManager = new AuthManager7();
|
|
798
|
-
const taskOps = await TaskOperations5.fromAuthManager(authManager);
|
|
799
|
-
let taskId = id;
|
|
800
|
-
if (id.length < 32) {
|
|
801
|
-
const allTasks = await taskOps.getTasks("all");
|
|
802
|
-
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
803
|
-
if (!matchingTask) {
|
|
804
|
-
spinner.fail(chalk7.red("Task not found"));
|
|
805
|
-
console.error(chalk7.gray(`
|
|
806
|
-
No task found with ID starting with: ${id}
|
|
807
|
-
`));
|
|
808
|
-
process.exit(1);
|
|
809
|
-
}
|
|
810
|
-
taskId = matchingTask.id;
|
|
811
|
-
}
|
|
812
|
-
const updates = {};
|
|
813
|
-
if (options.title) updates.title = options.title;
|
|
814
|
-
if (options.notes) updates.notes = options.notes;
|
|
815
|
-
if (options.due) updates.due_date = parseDate(options.due);
|
|
816
|
-
if (options.context) updates.context_notes = options.context;
|
|
817
|
-
if (options.project) updates.project_tag = options.project;
|
|
818
|
-
if (options.priority) {
|
|
819
|
-
const validPriorities = ["none", "low", "medium", "high"];
|
|
820
|
-
if (!validPriorities.includes(options.priority)) {
|
|
821
|
-
throw new Error(`Invalid priority. Must be one of: ${validPriorities.join(", ")}`);
|
|
822
|
-
}
|
|
823
|
-
updates.priority = options.priority;
|
|
824
|
-
}
|
|
825
|
-
if (options.status) {
|
|
826
|
-
const validStatuses = ["todo", "vibing", "done"];
|
|
827
|
-
if (!validStatuses.includes(options.status)) {
|
|
828
|
-
throw new Error(`Invalid status. Must be one of: ${validStatuses.join(", ")}`);
|
|
829
|
-
}
|
|
830
|
-
updates.status = options.status;
|
|
831
|
-
}
|
|
832
|
-
if (options.energy) {
|
|
833
|
-
const validEnergy = ["low", "medium", "high"];
|
|
834
|
-
if (!validEnergy.includes(options.energy)) {
|
|
835
|
-
throw new Error(`Invalid energy level. Must be one of: ${validEnergy.join(", ")}`);
|
|
836
|
-
}
|
|
837
|
-
updates.energy_required = options.energy;
|
|
838
|
-
}
|
|
839
|
-
const task = await taskOps.updateTask(taskId, updates);
|
|
840
|
-
spinner.succeed(chalk7.green("Task updated!"));
|
|
841
|
-
console.log(chalk7.gray(`
|
|
842
|
-
\u2713 ${task.title}
|
|
843
|
-
`));
|
|
844
|
-
process.exit(0);
|
|
845
|
-
} catch (error) {
|
|
846
|
-
spinner.fail(chalk7.red("Failed to update task"));
|
|
847
|
-
if (error.message.includes("Not authenticated")) {
|
|
848
|
-
console.error(chalk7.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
849
|
-
console.error(chalk7.gray("Run: taskflow login\n"));
|
|
850
|
-
} else {
|
|
851
|
-
console.error(chalk7.red(`
|
|
852
|
-
Error: ${error.message}
|
|
853
|
-
`));
|
|
854
|
-
}
|
|
855
|
-
process.exit(1);
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
// src/commands/delete.ts
|
|
860
|
-
import { Command as Command7 } from "commander";
|
|
861
|
-
import ora6 from "ora";
|
|
862
|
-
import chalk8 from "chalk";
|
|
863
|
-
import inquirer2 from "inquirer";
|
|
864
|
-
import { AuthManager as AuthManager8, TaskOperations as TaskOperations6 } from "@vibetasks/core";
|
|
865
|
-
var deleteCommand = new Command7("delete").description("Delete a task").argument("<id>", "Task ID (full or first 8 characters)").option("-y, --yes", "Skip confirmation").action(async (id, options) => {
|
|
866
|
-
try {
|
|
867
|
-
const authManager = new AuthManager8();
|
|
868
|
-
const taskOps = await TaskOperations6.fromAuthManager(authManager);
|
|
869
|
-
let taskId = id;
|
|
870
|
-
let taskTitle = "";
|
|
871
|
-
if (id.length < 32) {
|
|
872
|
-
const allTasks = await taskOps.getTasks("all");
|
|
873
|
-
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
874
|
-
if (!matchingTask) {
|
|
875
|
-
console.error(chalk8.red("\nTask not found"));
|
|
876
|
-
console.error(chalk8.gray(`No task found with ID starting with: ${id}
|
|
877
|
-
`));
|
|
878
|
-
process.exit(1);
|
|
879
|
-
}
|
|
880
|
-
taskId = matchingTask.id;
|
|
881
|
-
taskTitle = matchingTask.title;
|
|
882
|
-
} else {
|
|
883
|
-
const task = await taskOps.getTask(taskId);
|
|
884
|
-
taskTitle = task.title;
|
|
885
|
-
}
|
|
886
|
-
if (!options.yes) {
|
|
887
|
-
const answers = await inquirer2.prompt([
|
|
888
|
-
{
|
|
889
|
-
type: "confirm",
|
|
890
|
-
name: "confirm",
|
|
891
|
-
message: `Delete task: "${taskTitle}"?`,
|
|
892
|
-
default: false
|
|
893
|
-
}
|
|
894
|
-
]);
|
|
895
|
-
if (!answers.confirm) {
|
|
896
|
-
console.log(chalk8.gray("\nDeletion cancelled.\n"));
|
|
897
|
-
process.exit(0);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
const spinner = ora6("Deleting task...").start();
|
|
901
|
-
await taskOps.deleteTask(taskId);
|
|
902
|
-
spinner.succeed(chalk8.green("Task deleted!"));
|
|
903
|
-
console.log(chalk8.gray(`
|
|
904
|
-
\u2717 ${taskTitle}
|
|
905
|
-
`));
|
|
906
|
-
process.exit(0);
|
|
907
|
-
} catch (error) {
|
|
908
|
-
console.error(chalk8.red("\nFailed to delete task"));
|
|
909
|
-
if (error.message.includes("Not authenticated")) {
|
|
910
|
-
console.error(chalk8.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
911
|
-
console.error(chalk8.gray("Run: taskflow login\n"));
|
|
912
|
-
} else {
|
|
913
|
-
console.error(chalk8.red(`Error: ${error.message}
|
|
914
|
-
`));
|
|
915
|
-
}
|
|
916
|
-
process.exit(1);
|
|
917
|
-
}
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
// src/commands/config.ts
|
|
921
|
-
import { Command as Command8 } from "commander";
|
|
922
|
-
import chalk9 from "chalk";
|
|
923
|
-
import { AuthManager as AuthManager9 } from "@vibetasks/core";
|
|
924
|
-
var configCommand = new Command8("config").description("View or set configuration").argument("[key]", "Configuration key").argument("[value]", "Configuration value").action(async (key, value) => {
|
|
925
|
-
try {
|
|
926
|
-
const authManager = new AuthManager9();
|
|
927
|
-
if (!key) {
|
|
928
|
-
const configManager = authManager["configManager"];
|
|
929
|
-
const config = await configManager.getConfig();
|
|
930
|
-
const configPath = configManager.getConfigPath();
|
|
931
|
-
console.log(chalk9.blue.bold("\n\u{1F4CB} TaskFlow Configuration\n"));
|
|
932
|
-
console.log(chalk9.gray(`Location: ${configPath}
|
|
933
|
-
`));
|
|
934
|
-
if (Object.keys(config).length === 0) {
|
|
935
|
-
console.log(chalk9.gray("No configuration set.\n"));
|
|
936
|
-
process.exit(0);
|
|
937
|
-
}
|
|
938
|
-
Object.entries(config).forEach(([k, v]) => {
|
|
939
|
-
if (k.includes("token") || k.includes("key")) {
|
|
940
|
-
console.log(`${chalk9.cyan(k)}: ${chalk9.gray("[hidden]")}`);
|
|
941
|
-
} else {
|
|
942
|
-
console.log(`${chalk9.cyan(k)}: ${chalk9.white(String(v))}`);
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
console.log();
|
|
946
|
-
process.exit(0);
|
|
947
|
-
}
|
|
948
|
-
if (!value) {
|
|
949
|
-
const val = await authManager.getConfig(key);
|
|
950
|
-
if (val === void 0) {
|
|
951
|
-
console.log(chalk9.gray(`
|
|
952
|
-
Configuration key "${key}" not set.
|
|
953
|
-
`));
|
|
954
|
-
} else {
|
|
955
|
-
if (key.includes("token") || key.includes("key")) {
|
|
956
|
-
console.log(chalk9.gray(`
|
|
957
|
-
${key}: [hidden]
|
|
958
|
-
`));
|
|
959
|
-
} else {
|
|
960
|
-
console.log(chalk9.white(`
|
|
961
|
-
${key}: ${val}
|
|
962
|
-
`));
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
process.exit(0);
|
|
966
|
-
}
|
|
967
|
-
await authManager.setConfig(key, value);
|
|
968
|
-
console.log(chalk9.green(`
|
|
969
|
-
\u2713 Configuration updated: ${key} = ${value}
|
|
970
|
-
`));
|
|
971
|
-
process.exit(0);
|
|
972
|
-
} catch (error) {
|
|
973
|
-
console.error(chalk9.red(`
|
|
974
|
-
Error: ${error.message}
|
|
975
|
-
`));
|
|
976
|
-
process.exit(1);
|
|
977
|
-
}
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
// src/commands/hooks.ts
|
|
981
|
-
import { Command as Command9 } from "commander";
|
|
982
|
-
import chalk10 from "chalk";
|
|
983
|
-
import ora7 from "ora";
|
|
984
|
-
import fs from "fs/promises";
|
|
985
|
-
import path from "path";
|
|
986
|
-
var hooksCommand = new Command9("hooks").description("Manage git hooks integration").command("install").description("Install git hooks in current repository").action(async () => {
|
|
987
|
-
const spinner = ora7("Installing git hooks...").start();
|
|
988
|
-
try {
|
|
989
|
-
const gitDir = path.join(process.cwd(), ".git");
|
|
990
|
-
try {
|
|
991
|
-
await fs.access(gitDir);
|
|
992
|
-
} catch {
|
|
993
|
-
spinner.fail(chalk10.red("Not a git repository"));
|
|
994
|
-
console.error(chalk10.gray("\nRun this command from the root of a git repository.\n"));
|
|
995
|
-
process.exit(1);
|
|
996
|
-
}
|
|
997
|
-
const hooksDir = path.join(gitDir, "hooks");
|
|
998
|
-
await fs.mkdir(hooksDir, { recursive: true });
|
|
999
|
-
const postCommitPath = path.join(hooksDir, "post-commit");
|
|
1000
|
-
const hookScript = `#!/bin/sh
|
|
1001
|
-
# TaskFlow post-commit hook
|
|
1002
|
-
# Automatically updates tasks based on commit messages
|
|
1003
|
-
|
|
1004
|
-
# Get the commit message
|
|
1005
|
-
COMMIT_MSG=$(git log -1 --pretty=%B)
|
|
1006
|
-
|
|
1007
|
-
# Parse task ID from commit message [TASK-xxx]
|
|
1008
|
-
TASK_ID=$(echo "$COMMIT_MSG" | grep -oP '\\[TASK-\\K[a-zA-Z0-9-]+(?=\\])' || true)
|
|
1009
|
-
|
|
1010
|
-
if [ -n "$TASK_ID" ]; then
|
|
1011
|
-
# Check if commit marks task as complete
|
|
1012
|
-
IS_COMPLETE=$(echo "$COMMIT_MSG" | grep -i '\\[COMPLETE\\]\\|\\[DONE\\]' || true)
|
|
1013
|
-
|
|
1014
|
-
if [ -n "$IS_COMPLETE" ]; then
|
|
1015
|
-
# Mark task as complete
|
|
1016
|
-
taskflow done "$TASK_ID" 2>/dev/null || true
|
|
1017
|
-
else
|
|
1018
|
-
# Add commit info to task notes
|
|
1019
|
-
COMMIT_HASH=$(git log -1 --pretty=%h)
|
|
1020
|
-
COMMIT_TYPE=$(echo "$COMMIT_MSG" | grep -oP '^\\w+(?=:)' || echo "update")
|
|
1021
|
-
|
|
1022
|
-
# Update task with commit reference (silently, don't block commit)
|
|
1023
|
-
taskflow update "$TASK_ID" --notes "Commit $COMMIT_HASH: $COMMIT_TYPE" 2>/dev/null || true
|
|
1024
|
-
fi
|
|
1025
|
-
fi
|
|
1026
|
-
|
|
1027
|
-
exit 0
|
|
1028
|
-
`;
|
|
1029
|
-
await fs.writeFile(postCommitPath, hookScript, { mode: 493 });
|
|
1030
|
-
spinner.succeed(chalk10.green("Git hooks installed!"));
|
|
1031
|
-
console.log(chalk10.gray("\n\u{1F4CC} Post-commit hook created"));
|
|
1032
|
-
console.log(chalk10.gray("Location:"), postCommitPath);
|
|
1033
|
-
console.log(chalk10.blue.bold("\n\u2728 How to use:\n"));
|
|
1034
|
-
console.log(chalk10.white("1. Link commits to tasks:"));
|
|
1035
|
-
console.log(chalk10.gray(' git commit -m "feat: Add login [TASK-abc123]"'));
|
|
1036
|
-
console.log(chalk10.white("\n2. Auto-complete tasks:"));
|
|
1037
|
-
console.log(chalk10.gray(' git commit -m "feat: Complete feature [TASK-abc123] [COMPLETE]"'));
|
|
1038
|
-
console.log();
|
|
1039
|
-
process.exit(0);
|
|
1040
|
-
} catch (error) {
|
|
1041
|
-
spinner.fail(chalk10.red("Failed to install hooks"));
|
|
1042
|
-
console.error(chalk10.red(`
|
|
1043
|
-
Error: ${error.message}
|
|
1044
|
-
`));
|
|
1045
|
-
process.exit(1);
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
|
-
// src/commands/init.ts
|
|
1050
|
-
import { Command as Command10 } from "commander";
|
|
1051
|
-
import inquirer3 from "inquirer";
|
|
1052
|
-
import ora8 from "ora";
|
|
1053
|
-
import chalk11 from "chalk";
|
|
1054
|
-
import { AuthManager as AuthManager10 } from "@vibetasks/core";
|
|
1055
|
-
import { detectProject as detectProject2 } from "@vibetasks/shared/utils/project-detector";
|
|
1056
|
-
var initCommand = new Command10("init").description("Initialize TaskFlow for current project (auto-detect from git)").option("--name <name>", "Manually specify project name").action(async (options) => {
|
|
1057
|
-
const spinner = ora8("Detecting project...").start();
|
|
1058
|
-
try {
|
|
1059
|
-
const authManager = new AuthManager10();
|
|
1060
|
-
const cwd = process.cwd();
|
|
1061
|
-
let project;
|
|
1062
|
-
if (options.name) {
|
|
1063
|
-
project = {
|
|
1064
|
-
name: options.name,
|
|
1065
|
-
source: "manual",
|
|
1066
|
-
path: cwd
|
|
1067
|
-
};
|
|
1068
|
-
spinner.succeed(chalk11.green("Using manual project name"));
|
|
1069
|
-
} else {
|
|
1070
|
-
project = await detectProject2(cwd);
|
|
1071
|
-
spinner.succeed(chalk11.green("Project detected!"));
|
|
1072
|
-
}
|
|
1073
|
-
console.log(chalk11.white("\n\u{1F4C1} Project Info:"));
|
|
1074
|
-
console.log(chalk11.gray(` Name: ${chalk11.white(project.name)}`));
|
|
1075
|
-
console.log(chalk11.gray(` Source: ${chalk11.white(project.source)}`));
|
|
1076
|
-
console.log(chalk11.gray(` Path: ${chalk11.white(project.path)}
|
|
1077
|
-
`));
|
|
1078
|
-
const answers = await inquirer3.prompt([
|
|
1079
|
-
{
|
|
1080
|
-
type: "confirm",
|
|
1081
|
-
name: "confirm",
|
|
1082
|
-
message: `Tag all tasks created from this directory as "${project.name}"?`,
|
|
1083
|
-
default: true
|
|
1084
|
-
}
|
|
1085
|
-
]);
|
|
1086
|
-
if (!answers.confirm) {
|
|
1087
|
-
console.log(chalk11.gray("\nCancelled. Tasks will use default project.\n"));
|
|
1088
|
-
process.exit(0);
|
|
1089
|
-
}
|
|
1090
|
-
await authManager.setConfig(`project_${cwd}`, project.name);
|
|
1091
|
-
console.log(chalk11.green("\n\u2713 Project initialized!"));
|
|
1092
|
-
console.log(
|
|
1093
|
-
chalk11.gray(
|
|
1094
|
-
`
|
|
1095
|
-
All tasks created from ${chalk11.white(cwd)} will be tagged as ${chalk11.white(project.name)}
|
|
1096
|
-
`
|
|
1097
|
-
)
|
|
1098
|
-
);
|
|
1099
|
-
console.log(chalk11.blue("Next steps:"));
|
|
1100
|
-
console.log(chalk11.gray(' \u2022 Run taskflow add "Your task" to create tasks'));
|
|
1101
|
-
console.log(chalk11.gray(" \u2022 Tasks will automatically be tagged with this project"));
|
|
1102
|
-
console.log(chalk11.gray(" \u2022 Use taskflow list --project to filter by project\n"));
|
|
1103
|
-
process.exit(0);
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
spinner.fail(chalk11.red("Failed to initialize project"));
|
|
1106
|
-
console.error(chalk11.red(`
|
|
1107
|
-
Error: ${error.message}
|
|
1108
|
-
`));
|
|
1109
|
-
process.exit(1);
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
// bin/vibetasks.ts
|
|
1114
|
-
var program = new Command11();
|
|
1115
|
-
program.name("vibetasks").description("VibeTasks CLI - Fast task management for developers").version("0.1.0");
|
|
1116
|
-
program.addCommand(loginCommand);
|
|
1117
|
-
program.addCommand(addCommand);
|
|
1118
|
-
program.addCommand(listCommand);
|
|
1119
|
-
program.addCommand(doneCommand);
|
|
1120
|
-
program.addCommand(searchCommand);
|
|
1121
|
-
program.addCommand(updateCommand);
|
|
1122
|
-
program.addCommand(deleteCommand);
|
|
1123
|
-
program.addCommand(configCommand);
|
|
1124
|
-
program.addCommand(hooksCommand);
|
|
1125
|
-
program.addCommand(initCommand);
|
|
1126
|
-
program.parse();
|