@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
|
@@ -0,0 +1,2654 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkClipboardForError,
|
|
4
|
+
createClipboardMonitor,
|
|
5
|
+
detectError,
|
|
6
|
+
formatErrorForNotes,
|
|
7
|
+
getErrorSummary
|
|
8
|
+
} from "../chunk-PZF4VRDG.js";
|
|
9
|
+
import {
|
|
10
|
+
daemonConfigManager
|
|
11
|
+
} from "../chunk-2KRLRG4G.js";
|
|
12
|
+
|
|
13
|
+
// bin/vibetasks.ts
|
|
14
|
+
import { Command as Command16 } from "commander";
|
|
15
|
+
|
|
16
|
+
// src/commands/login.ts
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
import inquirer from "inquirer";
|
|
19
|
+
import ora2 from "ora";
|
|
20
|
+
import chalk2 from "chalk";
|
|
21
|
+
import { AuthManager as AuthManager2, createSupabaseClient } from "@vibetasks/core";
|
|
22
|
+
|
|
23
|
+
// src/utils/browser-auth.ts
|
|
24
|
+
import { createServer } from "http";
|
|
25
|
+
import { exec } from "child_process";
|
|
26
|
+
import { promisify } from "util";
|
|
27
|
+
import ora from "ora";
|
|
28
|
+
import chalk from "chalk";
|
|
29
|
+
import { AuthManager } from "@vibetasks/core";
|
|
30
|
+
var execAsync = promisify(exec);
|
|
31
|
+
async function openBrowser(url) {
|
|
32
|
+
const platform = process.platform;
|
|
33
|
+
let command;
|
|
34
|
+
if (platform === "win32") {
|
|
35
|
+
command = `start "" "${url}"`;
|
|
36
|
+
} else if (platform === "darwin") {
|
|
37
|
+
command = `open "${url}"`;
|
|
38
|
+
} else {
|
|
39
|
+
command = `xdg-open "${url}"`;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await execAsync(command);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.log(chalk.yellow(`
|
|
45
|
+
Couldn't open browser automatically. Please visit:
|
|
46
|
+
${url}
|
|
47
|
+
`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function loginWithBrowser() {
|
|
51
|
+
console.log(chalk.blue.bold("\n\u{1F510} TaskFlow Browser Login\n"));
|
|
52
|
+
const spinner = ora("Starting local server...").start();
|
|
53
|
+
const port = await getAvailablePort();
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
let timeoutId = null;
|
|
56
|
+
const server = createServer(async (req, res) => {
|
|
57
|
+
if (req.method === "OPTIONS") {
|
|
58
|
+
res.writeHead(200, {
|
|
59
|
+
"Access-Control-Allow-Origin": "*",
|
|
60
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
61
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
62
|
+
});
|
|
63
|
+
res.end();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (req.url?.startsWith("/callback")) {
|
|
67
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
68
|
+
const accessToken = url.searchParams.get("access_token");
|
|
69
|
+
const refreshToken = url.searchParams.get("refresh_token");
|
|
70
|
+
const email = url.searchParams.get("email");
|
|
71
|
+
const supabaseUrl = url.searchParams.get("supabase_url");
|
|
72
|
+
const supabaseKey = url.searchParams.get("supabase_key");
|
|
73
|
+
if (!accessToken || !refreshToken || !email) {
|
|
74
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
75
|
+
res.end(`
|
|
76
|
+
<!DOCTYPE html>
|
|
77
|
+
<html>
|
|
78
|
+
<head>
|
|
79
|
+
<title>Authentication Failed</title>
|
|
80
|
+
<style>
|
|
81
|
+
body { font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
|
|
82
|
+
.container { text-align: center; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
83
|
+
h1 { color: #e53e3e; margin: 0 0 16px 0; }
|
|
84
|
+
p { color: #666; margin: 0; }
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body>
|
|
88
|
+
<div class="container">
|
|
89
|
+
<h1>Authentication Failed</h1>
|
|
90
|
+
<p>Missing required authentication data.</p>
|
|
91
|
+
<p style="margin-top: 16px;">You can close this window.</p>
|
|
92
|
+
</div>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
`);
|
|
96
|
+
spinner.fail(chalk.red("Authentication failed - missing data"));
|
|
97
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
98
|
+
server.close(() => {
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
reject(new Error("Authentication failed"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const authManager = new AuthManager();
|
|
106
|
+
await authManager.setAccessToken(accessToken);
|
|
107
|
+
await authManager.setRefreshToken(refreshToken);
|
|
108
|
+
if (supabaseUrl) {
|
|
109
|
+
await authManager.setConfig("supabase_url", supabaseUrl);
|
|
110
|
+
}
|
|
111
|
+
if (supabaseKey) {
|
|
112
|
+
await authManager.setConfig("supabase_key", supabaseKey);
|
|
113
|
+
}
|
|
114
|
+
const storageMethod = authManager.getStorageMethod();
|
|
115
|
+
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
116
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
117
|
+
res.end(`
|
|
118
|
+
<!DOCTYPE html>
|
|
119
|
+
<html>
|
|
120
|
+
<head>
|
|
121
|
+
<title>Authentication Successful</title>
|
|
122
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
123
|
+
<style>
|
|
124
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
125
|
+
body {
|
|
126
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
min-height: 100vh;
|
|
131
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
132
|
+
padding: 20px;
|
|
133
|
+
}
|
|
134
|
+
.container {
|
|
135
|
+
text-align: center;
|
|
136
|
+
padding: 60px 40px;
|
|
137
|
+
background: white;
|
|
138
|
+
border-radius: 20px;
|
|
139
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
140
|
+
max-width: 450px;
|
|
141
|
+
width: 100%;
|
|
142
|
+
}
|
|
143
|
+
.checkmark {
|
|
144
|
+
width: 80px;
|
|
145
|
+
height: 80px;
|
|
146
|
+
border-radius: 50%;
|
|
147
|
+
display: block;
|
|
148
|
+
stroke-width: 3;
|
|
149
|
+
stroke: #4CAF50;
|
|
150
|
+
stroke-miterlimit: 10;
|
|
151
|
+
margin: 0 auto 24px;
|
|
152
|
+
animation: fill 0.4s ease-in-out 0.4s forwards, scale 0.3s ease-in-out 0.9s both;
|
|
153
|
+
}
|
|
154
|
+
.checkmark-circle {
|
|
155
|
+
stroke-dasharray: 166;
|
|
156
|
+
stroke-dashoffset: 166;
|
|
157
|
+
stroke-width: 3;
|
|
158
|
+
stroke-miterlimit: 10;
|
|
159
|
+
stroke: #4CAF50;
|
|
160
|
+
fill: none;
|
|
161
|
+
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
162
|
+
}
|
|
163
|
+
.checkmark-check {
|
|
164
|
+
transform-origin: 50% 50%;
|
|
165
|
+
stroke-dasharray: 48;
|
|
166
|
+
stroke-dashoffset: 48;
|
|
167
|
+
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
|
168
|
+
}
|
|
169
|
+
@keyframes stroke {
|
|
170
|
+
100% { stroke-dashoffset: 0; }
|
|
171
|
+
}
|
|
172
|
+
@keyframes scale {
|
|
173
|
+
0%, 100% { transform: none; }
|
|
174
|
+
50% { transform: scale3d(1.1, 1.1, 1); }
|
|
175
|
+
}
|
|
176
|
+
h1 {
|
|
177
|
+
color: #2d3748;
|
|
178
|
+
font-size: 28px;
|
|
179
|
+
font-weight: 700;
|
|
180
|
+
margin-bottom: 12px;
|
|
181
|
+
}
|
|
182
|
+
.email {
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
color: #667eea;
|
|
185
|
+
font-size: 16px;
|
|
186
|
+
margin-bottom: 24px;
|
|
187
|
+
}
|
|
188
|
+
.message {
|
|
189
|
+
color: #718096;
|
|
190
|
+
font-size: 15px;
|
|
191
|
+
line-height: 1.6;
|
|
192
|
+
}
|
|
193
|
+
.success-badge {
|
|
194
|
+
display: inline-block;
|
|
195
|
+
background: #f0fdf4;
|
|
196
|
+
color: #15803d;
|
|
197
|
+
padding: 8px 16px;
|
|
198
|
+
border-radius: 20px;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
font-weight: 600;
|
|
201
|
+
margin-top: 20px;
|
|
202
|
+
}
|
|
203
|
+
.button-group {
|
|
204
|
+
margin-top: 32px;
|
|
205
|
+
display: flex;
|
|
206
|
+
gap: 12px;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
}
|
|
209
|
+
.button {
|
|
210
|
+
padding: 12px 24px;
|
|
211
|
+
border-radius: 8px;
|
|
212
|
+
font-weight: 600;
|
|
213
|
+
font-size: 14px;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
transition: all 0.2s;
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
display: inline-block;
|
|
218
|
+
}
|
|
219
|
+
.button-primary {
|
|
220
|
+
background: #667eea;
|
|
221
|
+
color: white;
|
|
222
|
+
border: none;
|
|
223
|
+
}
|
|
224
|
+
.button-primary:hover {
|
|
225
|
+
background: #5568d3;
|
|
226
|
+
transform: translateY(-1px);
|
|
227
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
228
|
+
}
|
|
229
|
+
.button-secondary {
|
|
230
|
+
background: #f7fafc;
|
|
231
|
+
color: #4a5568;
|
|
232
|
+
border: 2px solid #e2e8f0;
|
|
233
|
+
}
|
|
234
|
+
.button-secondary:hover {
|
|
235
|
+
background: #edf2f7;
|
|
236
|
+
border-color: #cbd5e0;
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
239
|
+
</head>
|
|
240
|
+
<body>
|
|
241
|
+
<div class="container">
|
|
242
|
+
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
|
243
|
+
<circle class="checkmark-circle" cx="26" cy="26" r="25" fill="none"/>
|
|
244
|
+
<path class="checkmark-check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
|
245
|
+
</svg>
|
|
246
|
+
<h1>Authentication Successful!</h1>
|
|
247
|
+
<p class="email">${email}</p>
|
|
248
|
+
<p class="message">
|
|
249
|
+
Your TaskFlow CLI is now connected to your account.<br>
|
|
250
|
+
You can close this window and return to your terminal.
|
|
251
|
+
</p>
|
|
252
|
+
<div class="success-badge">Ready to use</div>
|
|
253
|
+
<div class="button-group">
|
|
254
|
+
<a href="http://localhost:2843/dashboard" class="button button-primary">Go to Dashboard</a>
|
|
255
|
+
<button onclick="window.close()" class="button button-secondary">Close Window</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</body>
|
|
259
|
+
</html>
|
|
260
|
+
`);
|
|
261
|
+
spinner.succeed(chalk.green("Successfully authenticated!"));
|
|
262
|
+
console.log(chalk.gray(`
|
|
263
|
+
Logged in as: ${email}`));
|
|
264
|
+
console.log(chalk.gray(`Tokens stored in: ${storageLocation}`));
|
|
265
|
+
console.log(chalk.blue.bold("\n\u2728 You can now use TaskFlow CLI commands\n"));
|
|
266
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
267
|
+
resolve();
|
|
268
|
+
server.close(() => {
|
|
269
|
+
process.exit(0);
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
273
|
+
res.end(`
|
|
274
|
+
<!DOCTYPE html>
|
|
275
|
+
<html>
|
|
276
|
+
<head>
|
|
277
|
+
<title>Storage Error</title>
|
|
278
|
+
<style>
|
|
279
|
+
body { font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
|
|
280
|
+
.container { text-align: center; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
281
|
+
h1 { color: #e53e3e; margin: 0 0 16px 0; }
|
|
282
|
+
p { color: #666; margin: 0; }
|
|
283
|
+
</style>
|
|
284
|
+
</head>
|
|
285
|
+
<body>
|
|
286
|
+
<div class="container">
|
|
287
|
+
<h1>Storage Error</h1>
|
|
288
|
+
<p>Failed to store authentication tokens.</p>
|
|
289
|
+
<p style="margin-top: 16px;">You can close this window.</p>
|
|
290
|
+
</div>
|
|
291
|
+
</body>
|
|
292
|
+
</html>
|
|
293
|
+
`);
|
|
294
|
+
spinner.fail(chalk.red("Failed to store tokens"));
|
|
295
|
+
console.error(chalk.red(`
|
|
296
|
+
Error: ${error.message}`));
|
|
297
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
298
|
+
server.close(() => {
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
|
301
|
+
reject(error);
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
res.writeHead(404);
|
|
306
|
+
res.end();
|
|
307
|
+
});
|
|
308
|
+
server.listen(port, async () => {
|
|
309
|
+
spinner.text = "Opening browser...";
|
|
310
|
+
const taskflowPort = process.env.TASKFLOW_WEB_PORT || "2843";
|
|
311
|
+
const authUrl = `http://localhost:${taskflowPort}/cli-auth?port=${port}`;
|
|
312
|
+
spinner.text = "Waiting for authentication in browser...";
|
|
313
|
+
console.log(chalk.gray(`
|
|
314
|
+
If browser doesn't open, visit: ${authUrl}
|
|
315
|
+
`));
|
|
316
|
+
await openBrowser(authUrl);
|
|
317
|
+
});
|
|
318
|
+
server.on("error", (error) => {
|
|
319
|
+
spinner.fail(chalk.red("Server error"));
|
|
320
|
+
console.error(chalk.red(`
|
|
321
|
+
Error: ${error.message}`));
|
|
322
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
323
|
+
reject(error);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
|
326
|
+
timeoutId = setTimeout(() => {
|
|
327
|
+
spinner.fail(chalk.red("Authentication timeout"));
|
|
328
|
+
console.log(chalk.yellow("\nAuthentication timed out after 5 minutes"));
|
|
329
|
+
server.close(() => {
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
reject(new Error("Authentication timeout"));
|
|
333
|
+
}, 5 * 60 * 1e3);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async function getAvailablePort(startPort = 3737) {
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
const server = createServer();
|
|
339
|
+
server.listen(startPort, () => {
|
|
340
|
+
const port = server.address().port;
|
|
341
|
+
server.close(() => resolve(port));
|
|
342
|
+
});
|
|
343
|
+
server.on("error", (error) => {
|
|
344
|
+
if (error.code === "EADDRINUSE") {
|
|
345
|
+
resolve(getAvailablePort(startPort + 1));
|
|
346
|
+
} else {
|
|
347
|
+
reject(error);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/commands/login.ts
|
|
354
|
+
var loginCommand = new Command("login").description("Authenticate with TaskFlow").option("-b, --browser", "Login via browser (recommended)").action(async (options) => {
|
|
355
|
+
if (options.browser) {
|
|
356
|
+
await loginWithBrowser();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
console.log(chalk2.blue.bold("\n\u{1F510} TaskFlow Login\n"));
|
|
360
|
+
const answers = await inquirer.prompt([
|
|
361
|
+
{
|
|
362
|
+
type: "input",
|
|
363
|
+
name: "email",
|
|
364
|
+
message: "Email:",
|
|
365
|
+
validate: (input) => {
|
|
366
|
+
if (!input.includes("@")) return "Please enter a valid email address";
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
type: "password",
|
|
372
|
+
name: "password",
|
|
373
|
+
message: "Password:",
|
|
374
|
+
mask: "*",
|
|
375
|
+
validate: (input) => {
|
|
376
|
+
if (input.length < 6) return "Password must be at least 6 characters";
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
]);
|
|
381
|
+
const spinner = ora2("Authenticating...").start();
|
|
382
|
+
try {
|
|
383
|
+
const authManager = new AuthManager2();
|
|
384
|
+
const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
385
|
+
const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || process.env.SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
386
|
+
await authManager.setConfig("supabase_url", supabaseUrl);
|
|
387
|
+
await authManager.setConfig("supabase_key", supabaseKey);
|
|
388
|
+
const supabase = createSupabaseClient({
|
|
389
|
+
supabaseUrl,
|
|
390
|
+
supabaseKey
|
|
391
|
+
});
|
|
392
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
393
|
+
email: answers.email,
|
|
394
|
+
password: answers.password
|
|
395
|
+
});
|
|
396
|
+
if (error) throw error;
|
|
397
|
+
if (!data.session) {
|
|
398
|
+
throw new Error("Authentication failed - no session returned");
|
|
399
|
+
}
|
|
400
|
+
await authManager.setAccessToken(data.session.access_token);
|
|
401
|
+
await authManager.setRefreshToken(data.session.refresh_token);
|
|
402
|
+
const storageMethod = authManager.getStorageMethod();
|
|
403
|
+
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
404
|
+
spinner.succeed(chalk2.green("Successfully authenticated!"));
|
|
405
|
+
console.log(chalk2.gray(`
|
|
406
|
+
Logged in as: ${data.user.email}`));
|
|
407
|
+
console.log(chalk2.gray(`Tokens stored in: ${storageLocation}`));
|
|
408
|
+
console.log(chalk2.blue.bold("\n\u2728 You can now use TaskFlow CLI commands\n"));
|
|
409
|
+
process.exit(0);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
spinner.fail(chalk2.red("Authentication failed"));
|
|
412
|
+
console.error(chalk2.red(`
|
|
413
|
+
Error: ${error.message}`));
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// src/commands/add.ts
|
|
419
|
+
import { Command as Command2 } from "commander";
|
|
420
|
+
import ora3 from "ora";
|
|
421
|
+
import chalk3 from "chalk";
|
|
422
|
+
import { AuthManager as AuthManager3, TaskOperations } from "@vibetasks/core";
|
|
423
|
+
|
|
424
|
+
// src/utils/date-parser.ts
|
|
425
|
+
import { addDays, addWeeks, addMonths, format, parse, isValid } from "date-fns";
|
|
426
|
+
function parseDate(dateStr) {
|
|
427
|
+
const today = /* @__PURE__ */ new Date();
|
|
428
|
+
today.setHours(0, 0, 0, 0);
|
|
429
|
+
const lowerStr = dateStr.toLowerCase().trim();
|
|
430
|
+
if (lowerStr === "today") {
|
|
431
|
+
return today.toISOString();
|
|
432
|
+
}
|
|
433
|
+
if (lowerStr === "tomorrow") {
|
|
434
|
+
return addDays(today, 1).toISOString();
|
|
435
|
+
}
|
|
436
|
+
const relativeMatch = lowerStr.match(/^\+(\d+)([dwm])$/);
|
|
437
|
+
if (relativeMatch) {
|
|
438
|
+
const amount = parseInt(relativeMatch[1], 10);
|
|
439
|
+
const unit = relativeMatch[2];
|
|
440
|
+
if (unit === "d") {
|
|
441
|
+
return addDays(today, amount).toISOString();
|
|
442
|
+
} else if (unit === "w") {
|
|
443
|
+
return addWeeks(today, amount).toISOString();
|
|
444
|
+
} else if (unit === "m") {
|
|
445
|
+
return addMonths(today, amount).toISOString();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const parsedDate = parse(dateStr, "yyyy-MM-dd", /* @__PURE__ */ new Date());
|
|
449
|
+
if (isValid(parsedDate)) {
|
|
450
|
+
return parsedDate.toISOString();
|
|
451
|
+
}
|
|
452
|
+
const usDate = parse(dateStr, "MM/dd/yyyy", /* @__PURE__ */ new Date());
|
|
453
|
+
if (isValid(usDate)) {
|
|
454
|
+
return usDate.toISOString();
|
|
455
|
+
}
|
|
456
|
+
throw new Error(
|
|
457
|
+
`Invalid date format: "${dateStr}". Use: "today", "tomorrow", "+3d", "+2w", "+1m", or "YYYY-MM-DD"`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/commands/add.ts
|
|
462
|
+
import { detectProject } from "@vibetasks/shared/utils/project-detector";
|
|
463
|
+
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) => {
|
|
464
|
+
const spinner = ora3("Creating task...").start();
|
|
465
|
+
try {
|
|
466
|
+
const authManager = new AuthManager3();
|
|
467
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
468
|
+
let projectTag;
|
|
469
|
+
if (options.project) {
|
|
470
|
+
projectTag = options.project;
|
|
471
|
+
} else {
|
|
472
|
+
try {
|
|
473
|
+
const detected = await detectProject(process.cwd());
|
|
474
|
+
projectTag = detected.name;
|
|
475
|
+
if (process.env.TASKFLOW_VERBOSE) {
|
|
476
|
+
console.log(chalk3.gray(`Detected project: ${projectTag} (${detected.source})`));
|
|
477
|
+
}
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (process.env.TASKFLOW_VERBOSE) {
|
|
480
|
+
console.log(chalk3.gray("Could not detect project"));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const createdBy = isRunningInClaudeCode() ? "ai" : "human";
|
|
485
|
+
let dueDate;
|
|
486
|
+
if (options.due) {
|
|
487
|
+
dueDate = parseDate(options.due);
|
|
488
|
+
} else {
|
|
489
|
+
dueDate = parseDate("today");
|
|
490
|
+
}
|
|
491
|
+
const validPriorities = ["none", "low", "medium", "high"];
|
|
492
|
+
if (!validPriorities.includes(options.priority)) {
|
|
493
|
+
throw new Error(`Invalid priority. Must be one of: ${validPriorities.join(", ")}`);
|
|
494
|
+
}
|
|
495
|
+
if (options.energy) {
|
|
496
|
+
const validEnergy = ["low", "medium", "high"];
|
|
497
|
+
if (!validEnergy.includes(options.energy)) {
|
|
498
|
+
throw new Error(`Invalid energy level. Must be one of: ${validEnergy.join(", ")}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const task = await taskOps.createTask({
|
|
502
|
+
title,
|
|
503
|
+
notes: options.notes,
|
|
504
|
+
notes_format: "markdown",
|
|
505
|
+
due_date: dueDate,
|
|
506
|
+
priority: options.priority,
|
|
507
|
+
project_tag: projectTag,
|
|
508
|
+
// AUTO-TAGGED!
|
|
509
|
+
created_by: createdBy,
|
|
510
|
+
// ai or human
|
|
511
|
+
status: createdBy === "ai" ? "vibing" : "todo",
|
|
512
|
+
// AI tasks start vibing
|
|
513
|
+
energy_required: options.energy
|
|
514
|
+
});
|
|
515
|
+
if (options.tags && options.tags.length > 0) {
|
|
516
|
+
const tagIds = [];
|
|
517
|
+
for (const tagName of options.tags) {
|
|
518
|
+
const tag = await taskOps.findOrCreateTag(tagName);
|
|
519
|
+
tagIds.push(tag.id);
|
|
520
|
+
}
|
|
521
|
+
await taskOps.linkTaskTags(task.id, tagIds);
|
|
522
|
+
}
|
|
523
|
+
spinner.succeed(chalk3.green("Task created!"));
|
|
524
|
+
console.log();
|
|
525
|
+
if (projectTag) {
|
|
526
|
+
console.log(chalk3.gray(`\u{1F4C1} Project: ${chalk3.white(projectTag)}`));
|
|
527
|
+
}
|
|
528
|
+
if (createdBy === "ai") {
|
|
529
|
+
console.log(chalk3.gray(`\u{1F916} Created by: ${chalk3.cyan("AI")}`));
|
|
530
|
+
console.log(chalk3.gray(`\u26A1 Status: ${chalk3.magenta("vibing")}`));
|
|
531
|
+
}
|
|
532
|
+
console.log(chalk3.gray(`ID: ${task.id.substring(0, 8)}...`));
|
|
533
|
+
console.log(chalk3.white.bold(task.title));
|
|
534
|
+
if (task.notes) {
|
|
535
|
+
console.log(chalk3.gray(`Notes: ${task.notes.substring(0, 50)}${task.notes.length > 50 ? "..." : ""}`));
|
|
536
|
+
}
|
|
537
|
+
if (task.priority && task.priority !== "none") {
|
|
538
|
+
const priorityColors = {
|
|
539
|
+
high: chalk3.red,
|
|
540
|
+
medium: chalk3.yellow,
|
|
541
|
+
low: chalk3.blue
|
|
542
|
+
};
|
|
543
|
+
console.log(priorityColors[task.priority](`Priority: ${task.priority.toUpperCase()}`));
|
|
544
|
+
}
|
|
545
|
+
if (task.energy_required) {
|
|
546
|
+
const energyEmoji = { low: "\u{1F50B}", medium: "\u26A1", high: "\u{1F525}" };
|
|
547
|
+
console.log(chalk3.gray(`Energy: ${energyEmoji[task.energy_required]} ${task.energy_required}`));
|
|
548
|
+
}
|
|
549
|
+
if (task.due_date) {
|
|
550
|
+
console.log(chalk3.blue(`Due: ${task.due_date.split("T")[0]}`));
|
|
551
|
+
}
|
|
552
|
+
if (options.tags && options.tags.length > 0) {
|
|
553
|
+
console.log(chalk3.magenta(`Tags: ${options.tags.join(", ")}`));
|
|
554
|
+
}
|
|
555
|
+
console.log();
|
|
556
|
+
process.exit(0);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
spinner.fail(chalk3.red("Failed to create task"));
|
|
559
|
+
if (error.message.includes("Not authenticated")) {
|
|
560
|
+
console.error(chalk3.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
561
|
+
console.error(chalk3.gray("Run: taskflow login\n"));
|
|
562
|
+
} else {
|
|
563
|
+
console.error(chalk3.red(`
|
|
564
|
+
Error: ${error.message}
|
|
565
|
+
`));
|
|
566
|
+
}
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
function isRunningInClaudeCode() {
|
|
571
|
+
return !!(process.env.CLAUDE_CODE_SESSION || process.env.ANTHROPIC_CLI_SESSION || process.env.TERM_PROGRAM?.includes("claude") || process.env.TERM_PROGRAM?.includes("anthropic"));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/commands/list.ts
|
|
575
|
+
import { Command as Command3 } from "commander";
|
|
576
|
+
import chalk4 from "chalk";
|
|
577
|
+
import Table from "cli-table3";
|
|
578
|
+
import { AuthManager as AuthManager4, TaskOperations as TaskOperations2 } from "@vibetasks/core";
|
|
579
|
+
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) => {
|
|
580
|
+
try {
|
|
581
|
+
const validFilters = ["all", "today", "upcoming", "completed"];
|
|
582
|
+
if (!validFilters.includes(filter)) {
|
|
583
|
+
console.error(chalk4.red(`Invalid filter: ${filter}`));
|
|
584
|
+
console.error(chalk4.gray(`Valid filters: ${validFilters.join(", ")}`));
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
const authManager = new AuthManager4();
|
|
588
|
+
const taskOps = await TaskOperations2.fromAuthManager(authManager);
|
|
589
|
+
let tasks = await taskOps.getTasks(filter);
|
|
590
|
+
if (options.project) {
|
|
591
|
+
tasks = tasks.filter((t) => t.project_tag === options.project);
|
|
592
|
+
}
|
|
593
|
+
if (options.createdBy) {
|
|
594
|
+
const validSources = ["ai", "human"];
|
|
595
|
+
if (!validSources.includes(options.createdBy)) {
|
|
596
|
+
console.error(chalk4.red(`Invalid created-by filter. Must be: ai or human`));
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
tasks = tasks.filter((t) => t.created_by === options.createdBy);
|
|
600
|
+
}
|
|
601
|
+
if (options.status) {
|
|
602
|
+
const validStatuses = ["todo", "vibing", "done"];
|
|
603
|
+
if (!validStatuses.includes(options.status)) {
|
|
604
|
+
console.error(chalk4.red(`Invalid status filter. Must be: todo, vibing, or done`));
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
tasks = tasks.filter((t) => t.status === options.status);
|
|
608
|
+
}
|
|
609
|
+
if (tasks.length === 0) {
|
|
610
|
+
const filterMessages = {
|
|
611
|
+
all: "No active tasks found",
|
|
612
|
+
today: "No tasks due today",
|
|
613
|
+
upcoming: "No upcoming tasks",
|
|
614
|
+
completed: "No completed tasks"
|
|
615
|
+
};
|
|
616
|
+
console.log(chalk4.gray(`
|
|
617
|
+
${filterMessages[filter] || "No tasks found"}.
|
|
618
|
+
`));
|
|
619
|
+
process.exit(0);
|
|
620
|
+
}
|
|
621
|
+
const limit = parseInt(options.limit, 10);
|
|
622
|
+
const displayTasks = tasks.slice(0, limit);
|
|
623
|
+
const table = new Table({
|
|
624
|
+
head: [
|
|
625
|
+
chalk4.cyan("ID"),
|
|
626
|
+
chalk4.cyan("Status"),
|
|
627
|
+
chalk4.cyan("Title"),
|
|
628
|
+
chalk4.cyan("Project"),
|
|
629
|
+
chalk4.cyan("Priority"),
|
|
630
|
+
chalk4.cyan("Due")
|
|
631
|
+
],
|
|
632
|
+
colWidths: [10, 10, 35, 15, 10, 12],
|
|
633
|
+
wordWrap: true
|
|
634
|
+
});
|
|
635
|
+
displayTasks.forEach((task) => {
|
|
636
|
+
const priorityColors = {
|
|
637
|
+
high: chalk4.red,
|
|
638
|
+
medium: chalk4.yellow,
|
|
639
|
+
low: chalk4.blue,
|
|
640
|
+
none: chalk4.gray
|
|
641
|
+
};
|
|
642
|
+
const statusColors = {
|
|
643
|
+
todo: chalk4.gray,
|
|
644
|
+
vibing: chalk4.magenta,
|
|
645
|
+
done: chalk4.green
|
|
646
|
+
};
|
|
647
|
+
const statusEmojis = {
|
|
648
|
+
todo: "\u{1F4CB}",
|
|
649
|
+
vibing: "\u26A1",
|
|
650
|
+
done: "\u2713"
|
|
651
|
+
};
|
|
652
|
+
const priorityColor = priorityColors[task.priority || "none"];
|
|
653
|
+
const statusColor = statusColors[task.status || "todo"];
|
|
654
|
+
const id = task.id.substring(0, 8);
|
|
655
|
+
const status = statusColor(`${statusEmojis[task.status || "todo"]} ${task.status || "todo"}`);
|
|
656
|
+
const title = task.status === "done" ? chalk4.strikethrough(task.title) : task.title;
|
|
657
|
+
const titleWithAI = task.created_by === "ai" ? `\u{1F916} ${title}` : title;
|
|
658
|
+
const project = task.project_tag ? chalk4.blue(task.project_tag) : chalk4.gray("-");
|
|
659
|
+
const priority = priorityColor(task.priority || "none");
|
|
660
|
+
const dueDate = task.due_date ? task.due_date.split("T")[0] : chalk4.gray("-");
|
|
661
|
+
table.push([id, status, titleWithAI, project, priority, dueDate]);
|
|
662
|
+
});
|
|
663
|
+
console.log("\n" + table.toString());
|
|
664
|
+
const filterLabels = {
|
|
665
|
+
all: "active",
|
|
666
|
+
today: "today",
|
|
667
|
+
upcoming: "upcoming",
|
|
668
|
+
completed: "completed"
|
|
669
|
+
};
|
|
670
|
+
console.log(chalk4.gray(`
|
|
671
|
+
Total: ${tasks.length} ${filterLabels[filter]} task${tasks.length === 1 ? "" : "s"}`));
|
|
672
|
+
if (tasks.length > limit) {
|
|
673
|
+
console.log(chalk4.yellow(`Showing ${limit} of ${tasks.length} tasks. Use --limit to show more.`));
|
|
674
|
+
}
|
|
675
|
+
console.log();
|
|
676
|
+
process.exit(0);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
if (error.message.includes("Not authenticated")) {
|
|
679
|
+
console.error(chalk4.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
680
|
+
console.error(chalk4.gray("Run: taskflow login\n"));
|
|
681
|
+
} else {
|
|
682
|
+
console.error(chalk4.red(`
|
|
683
|
+
Error: ${error.message}
|
|
684
|
+
`));
|
|
685
|
+
}
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// src/commands/done.ts
|
|
691
|
+
import { Command as Command4 } from "commander";
|
|
692
|
+
import ora4 from "ora";
|
|
693
|
+
import chalk5 from "chalk";
|
|
694
|
+
import { AuthManager as AuthManager5, TaskOperations as TaskOperations3 } from "@vibetasks/core";
|
|
695
|
+
var doneCommand = new Command4("done").description("Mark a task as complete").argument("<id>", "Task ID (full or first 8 characters)").action(async (id) => {
|
|
696
|
+
const spinner = ora4("Completing task...").start();
|
|
697
|
+
try {
|
|
698
|
+
const authManager = new AuthManager5();
|
|
699
|
+
const taskOps = await TaskOperations3.fromAuthManager(authManager);
|
|
700
|
+
let taskId = id;
|
|
701
|
+
if (id.length < 32) {
|
|
702
|
+
const allTasks = await taskOps.getTasks("all");
|
|
703
|
+
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
704
|
+
if (!matchingTask) {
|
|
705
|
+
spinner.fail(chalk5.red("Task not found"));
|
|
706
|
+
console.error(chalk5.gray(`
|
|
707
|
+
No task found with ID starting with: ${id}
|
|
708
|
+
`));
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
taskId = matchingTask.id;
|
|
712
|
+
}
|
|
713
|
+
const task = await taskOps.completeTask(taskId);
|
|
714
|
+
spinner.succeed(chalk5.green("Task completed!"));
|
|
715
|
+
console.log(chalk5.gray(`
|
|
716
|
+
\u2713 ${task.title}`));
|
|
717
|
+
console.log(chalk5.gray(`Completed at: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
718
|
+
`));
|
|
719
|
+
process.exit(0);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
spinner.fail(chalk5.red("Failed to complete task"));
|
|
722
|
+
if (error.message.includes("Not authenticated")) {
|
|
723
|
+
console.error(chalk5.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
724
|
+
console.error(chalk5.gray("Run: taskflow login\n"));
|
|
725
|
+
} else {
|
|
726
|
+
console.error(chalk5.red(`
|
|
727
|
+
Error: ${error.message}
|
|
728
|
+
`));
|
|
729
|
+
}
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// src/commands/vibing.ts
|
|
735
|
+
import { Command as Command5 } from "commander";
|
|
736
|
+
import chalk6 from "chalk";
|
|
737
|
+
import ora5 from "ora";
|
|
738
|
+
import inquirer2 from "inquirer";
|
|
739
|
+
import { AuthManager as AuthManager6, TaskOperations as TaskOperations4 } from "@vibetasks/core";
|
|
740
|
+
var WIP_LIMIT = 3;
|
|
741
|
+
var vibingCommand = new Command5("vibing").alias("start").alias("v").description("Start working on a task (move to vibing status)").argument("[task-id]", "Task ID to start vibing on").option("-p, --pick", "Pick from todo tasks interactively").action(async (taskId, options) => {
|
|
742
|
+
const spinner = ora5();
|
|
743
|
+
try {
|
|
744
|
+
const authManager = new AuthManager6();
|
|
745
|
+
const taskOps = await TaskOperations4.fromAuthManager(authManager);
|
|
746
|
+
const allTasks = await taskOps.getTasks("all");
|
|
747
|
+
const vibingTasks = allTasks.filter((t) => t.status === "vibing" && !t.completed);
|
|
748
|
+
if (vibingTasks.length >= WIP_LIMIT) {
|
|
749
|
+
console.log(chalk6.yellow("\n\u26A0 WIP Limit Warning"));
|
|
750
|
+
console.log(chalk6.gray(` You already have ${vibingTasks.length} tasks in progress.`));
|
|
751
|
+
console.log(chalk6.gray(" Research shows 3+ concurrent tasks = 40% productivity loss.\n"));
|
|
752
|
+
console.log(chalk6.white("Currently vibing:"));
|
|
753
|
+
vibingTasks.forEach((t, i) => {
|
|
754
|
+
console.log(chalk6.magenta(` ${i + 1}. ${t.title}`));
|
|
755
|
+
});
|
|
756
|
+
console.log("");
|
|
757
|
+
const { proceed } = await inquirer2.prompt([{
|
|
758
|
+
type: "confirm",
|
|
759
|
+
name: "proceed",
|
|
760
|
+
message: "Start another task anyway?",
|
|
761
|
+
default: false
|
|
762
|
+
}]);
|
|
763
|
+
if (!proceed) {
|
|
764
|
+
console.log(chalk6.gray("\nFocus on finishing what you started! Run `vibetasks done <id>` when ready.\n"));
|
|
765
|
+
process.exit(0);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!taskId || options.pick) {
|
|
769
|
+
const todoTasks = allTasks.filter((t) => t.status === "todo" && !t.completed);
|
|
770
|
+
if (todoTasks.length === 0) {
|
|
771
|
+
console.log(chalk6.yellow('\nNo todo tasks found. Add one with `vibetasks add "task title"`\n'));
|
|
772
|
+
process.exit(0);
|
|
773
|
+
}
|
|
774
|
+
const { selectedTask } = await inquirer2.prompt([{
|
|
775
|
+
type: "list",
|
|
776
|
+
name: "selectedTask",
|
|
777
|
+
message: "Pick a task to start:",
|
|
778
|
+
choices: todoTasks.map((t) => ({
|
|
779
|
+
name: `${t.title}${t.project_tag ? chalk6.gray(` [${t.project_tag}]`) : ""}`,
|
|
780
|
+
value: t.id
|
|
781
|
+
}))
|
|
782
|
+
}]);
|
|
783
|
+
taskId = selectedTask;
|
|
784
|
+
}
|
|
785
|
+
spinner.start("Starting task...");
|
|
786
|
+
await taskOps.updateTask(taskId, {
|
|
787
|
+
status: "vibing",
|
|
788
|
+
completed: false
|
|
789
|
+
});
|
|
790
|
+
const task = allTasks.find((t) => t.id === taskId);
|
|
791
|
+
spinner.succeed(chalk6.green("Now vibing on:"));
|
|
792
|
+
console.log(chalk6.white(`
|
|
793
|
+
${task?.title || taskId}
|
|
794
|
+
`));
|
|
795
|
+
console.log(chalk6.gray(" Tips:"));
|
|
796
|
+
console.log(chalk6.gray(" \u2022 Update context notes as you work"));
|
|
797
|
+
console.log(chalk6.gray(" \u2022 Run `vibetasks done " + taskId.slice(0, 8) + "` when finished"));
|
|
798
|
+
console.log(chalk6.gray(' \u2022 In Claude Code, say "show my vibing tasks"\n'));
|
|
799
|
+
} catch (error) {
|
|
800
|
+
spinner.fail(chalk6.red("Failed to start task"));
|
|
801
|
+
console.error(chalk6.red(error.message));
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// src/commands/search.ts
|
|
807
|
+
import { Command as Command6 } from "commander";
|
|
808
|
+
import chalk7 from "chalk";
|
|
809
|
+
import Table2 from "cli-table3";
|
|
810
|
+
import { AuthManager as AuthManager7, TaskOperations as TaskOperations5 } from "@vibetasks/core";
|
|
811
|
+
var searchCommand = new Command6("search").description("Search tasks by title").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "20").action(async (query, options) => {
|
|
812
|
+
try {
|
|
813
|
+
const authManager = new AuthManager7();
|
|
814
|
+
const taskOps = await TaskOperations5.fromAuthManager(authManager);
|
|
815
|
+
const limit = parseInt(options.limit, 10);
|
|
816
|
+
const tasks = await taskOps.searchTasks(query, limit);
|
|
817
|
+
if (tasks.length === 0) {
|
|
818
|
+
console.log(chalk7.gray(`
|
|
819
|
+
No tasks found matching: "${query}"
|
|
820
|
+
`));
|
|
821
|
+
process.exit(0);
|
|
822
|
+
}
|
|
823
|
+
const table = new Table2({
|
|
824
|
+
head: [
|
|
825
|
+
chalk7.cyan("ID"),
|
|
826
|
+
chalk7.cyan("Title"),
|
|
827
|
+
chalk7.cyan("Priority"),
|
|
828
|
+
chalk7.cyan("Due Date")
|
|
829
|
+
],
|
|
830
|
+
colWidths: [12, 50, 10, 12],
|
|
831
|
+
wordWrap: true
|
|
832
|
+
});
|
|
833
|
+
tasks.forEach((task) => {
|
|
834
|
+
const priorityColors = {
|
|
835
|
+
high: chalk7.red,
|
|
836
|
+
medium: chalk7.yellow,
|
|
837
|
+
low: chalk7.blue,
|
|
838
|
+
none: chalk7.gray
|
|
839
|
+
};
|
|
840
|
+
const priorityColor = priorityColors[task.priority || "none"];
|
|
841
|
+
table.push([
|
|
842
|
+
task.id.substring(0, 8),
|
|
843
|
+
task.title,
|
|
844
|
+
priorityColor(task.priority || "none"),
|
|
845
|
+
task.due_date ? task.due_date.split("T")[0] : chalk7.gray("-")
|
|
846
|
+
]);
|
|
847
|
+
});
|
|
848
|
+
console.log("\n" + table.toString());
|
|
849
|
+
console.log(chalk7.gray(`
|
|
850
|
+
Found ${tasks.length} task${tasks.length === 1 ? "" : "s"} matching "${query}"
|
|
851
|
+
`));
|
|
852
|
+
process.exit(0);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
if (error.message.includes("Not authenticated")) {
|
|
855
|
+
console.error(chalk7.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
856
|
+
console.error(chalk7.gray("Run: taskflow login\n"));
|
|
857
|
+
} else {
|
|
858
|
+
console.error(chalk7.red(`
|
|
859
|
+
Error: ${error.message}
|
|
860
|
+
`));
|
|
861
|
+
}
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
// src/commands/update.ts
|
|
867
|
+
import { Command as Command7 } from "commander";
|
|
868
|
+
import ora6 from "ora";
|
|
869
|
+
import chalk8 from "chalk";
|
|
870
|
+
import { AuthManager as AuthManager8, TaskOperations as TaskOperations6 } from "@vibetasks/core";
|
|
871
|
+
var updateCommand = new Command7("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) => {
|
|
872
|
+
if (!options.title && !options.notes && !options.due && !options.priority && !options.status && !options.project && !options.energy && !options.context) {
|
|
873
|
+
console.error(chalk8.red("\nError: No updates specified"));
|
|
874
|
+
console.error(chalk8.gray("Provide at least one option: --title, --notes, --due, --priority, --status, --project, --energy, or --context\n"));
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
const spinner = ora6("Updating task...").start();
|
|
878
|
+
try {
|
|
879
|
+
const authManager = new AuthManager8();
|
|
880
|
+
const taskOps = await TaskOperations6.fromAuthManager(authManager);
|
|
881
|
+
let taskId = id;
|
|
882
|
+
if (id.length < 32) {
|
|
883
|
+
const allTasks = await taskOps.getTasks("all");
|
|
884
|
+
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
885
|
+
if (!matchingTask) {
|
|
886
|
+
spinner.fail(chalk8.red("Task not found"));
|
|
887
|
+
console.error(chalk8.gray(`
|
|
888
|
+
No task found with ID starting with: ${id}
|
|
889
|
+
`));
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
taskId = matchingTask.id;
|
|
893
|
+
}
|
|
894
|
+
const updates = {};
|
|
895
|
+
if (options.title) updates.title = options.title;
|
|
896
|
+
if (options.notes) updates.notes = options.notes;
|
|
897
|
+
if (options.due) updates.due_date = parseDate(options.due);
|
|
898
|
+
if (options.context) updates.context_notes = options.context;
|
|
899
|
+
if (options.project) updates.project_tag = options.project;
|
|
900
|
+
if (options.priority) {
|
|
901
|
+
const validPriorities = ["none", "low", "medium", "high"];
|
|
902
|
+
if (!validPriorities.includes(options.priority)) {
|
|
903
|
+
throw new Error(`Invalid priority. Must be one of: ${validPriorities.join(", ")}`);
|
|
904
|
+
}
|
|
905
|
+
updates.priority = options.priority;
|
|
906
|
+
}
|
|
907
|
+
if (options.status) {
|
|
908
|
+
const validStatuses = ["todo", "vibing", "done"];
|
|
909
|
+
if (!validStatuses.includes(options.status)) {
|
|
910
|
+
throw new Error(`Invalid status. Must be one of: ${validStatuses.join(", ")}`);
|
|
911
|
+
}
|
|
912
|
+
updates.status = options.status;
|
|
913
|
+
}
|
|
914
|
+
if (options.energy) {
|
|
915
|
+
const validEnergy = ["low", "medium", "high"];
|
|
916
|
+
if (!validEnergy.includes(options.energy)) {
|
|
917
|
+
throw new Error(`Invalid energy level. Must be one of: ${validEnergy.join(", ")}`);
|
|
918
|
+
}
|
|
919
|
+
updates.energy_required = options.energy;
|
|
920
|
+
}
|
|
921
|
+
const task = await taskOps.updateTask(taskId, updates);
|
|
922
|
+
spinner.succeed(chalk8.green("Task updated!"));
|
|
923
|
+
console.log(chalk8.gray(`
|
|
924
|
+
\u2713 ${task.title}
|
|
925
|
+
`));
|
|
926
|
+
process.exit(0);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
spinner.fail(chalk8.red("Failed to update task"));
|
|
929
|
+
if (error.message.includes("Not authenticated")) {
|
|
930
|
+
console.error(chalk8.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
931
|
+
console.error(chalk8.gray("Run: taskflow login\n"));
|
|
932
|
+
} else {
|
|
933
|
+
console.error(chalk8.red(`
|
|
934
|
+
Error: ${error.message}
|
|
935
|
+
`));
|
|
936
|
+
}
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// src/commands/delete.ts
|
|
942
|
+
import { Command as Command8 } from "commander";
|
|
943
|
+
import ora7 from "ora";
|
|
944
|
+
import chalk9 from "chalk";
|
|
945
|
+
import inquirer3 from "inquirer";
|
|
946
|
+
import { AuthManager as AuthManager9, TaskOperations as TaskOperations7 } from "@vibetasks/core";
|
|
947
|
+
var deleteCommand = new Command8("delete").description("Delete a task").argument("<id>", "Task ID (full or first 8 characters)").option("-y, --yes", "Skip confirmation").action(async (id, options) => {
|
|
948
|
+
try {
|
|
949
|
+
const authManager = new AuthManager9();
|
|
950
|
+
const taskOps = await TaskOperations7.fromAuthManager(authManager);
|
|
951
|
+
let taskId = id;
|
|
952
|
+
let taskTitle = "";
|
|
953
|
+
if (id.length < 32) {
|
|
954
|
+
const allTasks = await taskOps.getTasks("all");
|
|
955
|
+
const matchingTask = allTasks.find((t) => t.id.startsWith(id));
|
|
956
|
+
if (!matchingTask) {
|
|
957
|
+
console.error(chalk9.red("\nTask not found"));
|
|
958
|
+
console.error(chalk9.gray(`No task found with ID starting with: ${id}
|
|
959
|
+
`));
|
|
960
|
+
process.exit(1);
|
|
961
|
+
}
|
|
962
|
+
taskId = matchingTask.id;
|
|
963
|
+
taskTitle = matchingTask.title;
|
|
964
|
+
} else {
|
|
965
|
+
const task = await taskOps.getTask(taskId);
|
|
966
|
+
taskTitle = task.title;
|
|
967
|
+
}
|
|
968
|
+
if (!options.yes) {
|
|
969
|
+
const answers = await inquirer3.prompt([
|
|
970
|
+
{
|
|
971
|
+
type: "confirm",
|
|
972
|
+
name: "confirm",
|
|
973
|
+
message: `Delete task: "${taskTitle}"?`,
|
|
974
|
+
default: false
|
|
975
|
+
}
|
|
976
|
+
]);
|
|
977
|
+
if (!answers.confirm) {
|
|
978
|
+
console.log(chalk9.gray("\nDeletion cancelled.\n"));
|
|
979
|
+
process.exit(0);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const spinner = ora7("Deleting task...").start();
|
|
983
|
+
await taskOps.deleteTask(taskId);
|
|
984
|
+
spinner.succeed(chalk9.green("Task deleted!"));
|
|
985
|
+
console.log(chalk9.gray(`
|
|
986
|
+
\u2717 ${taskTitle}
|
|
987
|
+
`));
|
|
988
|
+
process.exit(0);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
console.error(chalk9.red("\nFailed to delete task"));
|
|
991
|
+
if (error.message.includes("Not authenticated")) {
|
|
992
|
+
console.error(chalk9.yellow("\n\u26A0\uFE0F You need to login first"));
|
|
993
|
+
console.error(chalk9.gray("Run: taskflow login\n"));
|
|
994
|
+
} else {
|
|
995
|
+
console.error(chalk9.red(`Error: ${error.message}
|
|
996
|
+
`));
|
|
997
|
+
}
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// src/commands/config.ts
|
|
1003
|
+
import { Command as Command9 } from "commander";
|
|
1004
|
+
import chalk10 from "chalk";
|
|
1005
|
+
import { AuthManager as AuthManager10 } from "@vibetasks/core";
|
|
1006
|
+
var configCommand = new Command9("config").description("View or set configuration").argument("[key]", "Configuration key").argument("[value]", "Configuration value").action(async (key, value) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const authManager = new AuthManager10();
|
|
1009
|
+
if (!key) {
|
|
1010
|
+
const configManager = authManager["configManager"];
|
|
1011
|
+
const config = await configManager.getConfig();
|
|
1012
|
+
const configPath = configManager.getConfigPath();
|
|
1013
|
+
console.log(chalk10.blue.bold("\n\u{1F4CB} TaskFlow Configuration\n"));
|
|
1014
|
+
console.log(chalk10.gray(`Location: ${configPath}
|
|
1015
|
+
`));
|
|
1016
|
+
if (Object.keys(config).length === 0) {
|
|
1017
|
+
console.log(chalk10.gray("No configuration set.\n"));
|
|
1018
|
+
process.exit(0);
|
|
1019
|
+
}
|
|
1020
|
+
Object.entries(config).forEach(([k, v]) => {
|
|
1021
|
+
if (k.includes("token") || k.includes("key")) {
|
|
1022
|
+
console.log(`${chalk10.cyan(k)}: ${chalk10.gray("[hidden]")}`);
|
|
1023
|
+
} else {
|
|
1024
|
+
console.log(`${chalk10.cyan(k)}: ${chalk10.white(String(v))}`);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
console.log();
|
|
1028
|
+
process.exit(0);
|
|
1029
|
+
}
|
|
1030
|
+
if (!value) {
|
|
1031
|
+
const val = await authManager.getConfig(key);
|
|
1032
|
+
if (val === void 0) {
|
|
1033
|
+
console.log(chalk10.gray(`
|
|
1034
|
+
Configuration key "${key}" not set.
|
|
1035
|
+
`));
|
|
1036
|
+
} else {
|
|
1037
|
+
if (key.includes("token") || key.includes("key")) {
|
|
1038
|
+
console.log(chalk10.gray(`
|
|
1039
|
+
${key}: [hidden]
|
|
1040
|
+
`));
|
|
1041
|
+
} else {
|
|
1042
|
+
console.log(chalk10.white(`
|
|
1043
|
+
${key}: ${val}
|
|
1044
|
+
`));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
process.exit(0);
|
|
1048
|
+
}
|
|
1049
|
+
await authManager.setConfig(key, value);
|
|
1050
|
+
console.log(chalk10.green(`
|
|
1051
|
+
\u2713 Configuration updated: ${key} = ${value}
|
|
1052
|
+
`));
|
|
1053
|
+
process.exit(0);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(chalk10.red(`
|
|
1056
|
+
Error: ${error.message}
|
|
1057
|
+
`));
|
|
1058
|
+
process.exit(1);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// src/commands/hooks.ts
|
|
1063
|
+
import { Command as Command10 } from "commander";
|
|
1064
|
+
import chalk11 from "chalk";
|
|
1065
|
+
import ora8 from "ora";
|
|
1066
|
+
import fs from "fs/promises";
|
|
1067
|
+
import path from "path";
|
|
1068
|
+
function getVibetasksHooksPath() {
|
|
1069
|
+
const cliRoot = path.dirname(path.dirname(path.dirname(new URL(import.meta.url).pathname)));
|
|
1070
|
+
const normalizedPath = process.platform === "win32" && cliRoot.startsWith("/") ? cliRoot.slice(1) : cliRoot;
|
|
1071
|
+
return path.join(normalizedPath, "hooks");
|
|
1072
|
+
}
|
|
1073
|
+
var hooksCommand = new Command10("hooks").description("Manage hooks integration (git and Claude Code)");
|
|
1074
|
+
hooksCommand.command("install").description("Install git hooks in current repository").action(async () => {
|
|
1075
|
+
const spinner = ora8("Installing git hooks...").start();
|
|
1076
|
+
try {
|
|
1077
|
+
const gitDir = path.join(process.cwd(), ".git");
|
|
1078
|
+
try {
|
|
1079
|
+
await fs.access(gitDir);
|
|
1080
|
+
} catch {
|
|
1081
|
+
spinner.fail(chalk11.red("Not a git repository"));
|
|
1082
|
+
console.error(chalk11.gray("\nRun this command from the root of a git repository.\n"));
|
|
1083
|
+
process.exit(1);
|
|
1084
|
+
}
|
|
1085
|
+
const hooksDir = path.join(gitDir, "hooks");
|
|
1086
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
1087
|
+
const postCommitPath = path.join(hooksDir, "post-commit");
|
|
1088
|
+
const hookScript = `#!/bin/sh
|
|
1089
|
+
# VibeTasks post-commit hook
|
|
1090
|
+
# Automatically updates tasks based on commit messages
|
|
1091
|
+
|
|
1092
|
+
# Get the commit message
|
|
1093
|
+
COMMIT_MSG=$(git log -1 --pretty=%B)
|
|
1094
|
+
|
|
1095
|
+
# Parse task ID from commit message [TASK-xxx]
|
|
1096
|
+
TASK_ID=$(echo "$COMMIT_MSG" | grep -oP '\\[TASK-\\K[a-zA-Z0-9-]+(?=\\])' || true)
|
|
1097
|
+
|
|
1098
|
+
if [ -n "$TASK_ID" ]; then
|
|
1099
|
+
# Check if commit marks task as complete
|
|
1100
|
+
IS_COMPLETE=$(echo "$COMMIT_MSG" | grep -i '\\[COMPLETE\\]\\|\\[DONE\\]' || true)
|
|
1101
|
+
|
|
1102
|
+
if [ -n "$IS_COMPLETE" ]; then
|
|
1103
|
+
# Mark task as complete
|
|
1104
|
+
vibetasks done "$TASK_ID" 2>/dev/null || true
|
|
1105
|
+
else
|
|
1106
|
+
# Add commit info to task notes
|
|
1107
|
+
COMMIT_HASH=$(git log -1 --pretty=%h)
|
|
1108
|
+
COMMIT_TYPE=$(echo "$COMMIT_MSG" | grep -oP '^\\w+(?=:)' || echo "update")
|
|
1109
|
+
|
|
1110
|
+
# Update task with commit reference (silently, don't block commit)
|
|
1111
|
+
vibetasks update "$TASK_ID" --notes "Commit $COMMIT_HASH: $COMMIT_TYPE" 2>/dev/null || true
|
|
1112
|
+
fi
|
|
1113
|
+
fi
|
|
1114
|
+
|
|
1115
|
+
exit 0
|
|
1116
|
+
`;
|
|
1117
|
+
await fs.writeFile(postCommitPath, hookScript, { mode: 493 });
|
|
1118
|
+
spinner.succeed(chalk11.green("Git hooks installed!"));
|
|
1119
|
+
console.log(chalk11.gray("\n Post-commit hook created"));
|
|
1120
|
+
console.log(chalk11.gray(" Location:"), postCommitPath);
|
|
1121
|
+
console.log(chalk11.blue.bold("\n How to use:\n"));
|
|
1122
|
+
console.log(chalk11.white(" 1. Link commits to tasks:"));
|
|
1123
|
+
console.log(chalk11.gray(' git commit -m "feat: Add login [TASK-abc123]"'));
|
|
1124
|
+
console.log(chalk11.white("\n 2. Auto-complete tasks:"));
|
|
1125
|
+
console.log(chalk11.gray(' git commit -m "feat: Complete feature [TASK-abc123] [COMPLETE]"'));
|
|
1126
|
+
console.log();
|
|
1127
|
+
process.exit(0);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
spinner.fail(chalk11.red("Failed to install hooks"));
|
|
1130
|
+
console.error(chalk11.red(`
|
|
1131
|
+
Error: ${error.message}
|
|
1132
|
+
`));
|
|
1133
|
+
process.exit(1);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
hooksCommand.command("claude").description("Generate Claude Code hook configuration for TodoWrite sync").option("--project <path>", "Project path (defaults to current directory)").option("--output", "Output config to stdout instead of installing").action(async (options) => {
|
|
1137
|
+
const spinner = ora8("Generating Claude Code hook configuration...").start();
|
|
1138
|
+
try {
|
|
1139
|
+
const projectPath = options.project || process.cwd();
|
|
1140
|
+
const hooksPath = getVibetasksHooksPath();
|
|
1141
|
+
const syncScriptPath = path.join(hooksPath, "sync-todos.js");
|
|
1142
|
+
const hookConfig = {
|
|
1143
|
+
hooks: {
|
|
1144
|
+
SubagentStop: [
|
|
1145
|
+
{
|
|
1146
|
+
command: `node "${syncScriptPath.replace(/\\/g, "/")}"`,
|
|
1147
|
+
timeout: 1e4,
|
|
1148
|
+
input: {
|
|
1149
|
+
todoState: true
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
]
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
if (options.output) {
|
|
1156
|
+
spinner.stop();
|
|
1157
|
+
console.log(JSON.stringify(hookConfig, null, 2));
|
|
1158
|
+
process.exit(0);
|
|
1159
|
+
}
|
|
1160
|
+
const claudeDir = path.join(projectPath, ".claude");
|
|
1161
|
+
await fs.mkdir(claudeDir, { recursive: true });
|
|
1162
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
1163
|
+
let existingSettings = {};
|
|
1164
|
+
try {
|
|
1165
|
+
const data = await fs.readFile(settingsPath, "utf-8");
|
|
1166
|
+
existingSettings = JSON.parse(data);
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
existingSettings.hooks = existingSettings.hooks || {};
|
|
1170
|
+
existingSettings.hooks.SubagentStop = existingSettings.hooks.SubagentStop || [];
|
|
1171
|
+
const existingHookIndex = existingSettings.hooks.SubagentStop.findIndex(
|
|
1172
|
+
(h) => h.command?.includes("sync-todos.js")
|
|
1173
|
+
);
|
|
1174
|
+
if (existingHookIndex >= 0) {
|
|
1175
|
+
existingSettings.hooks.SubagentStop[existingHookIndex] = hookConfig.hooks.SubagentStop[0];
|
|
1176
|
+
spinner.text = "Updating existing Claude Code hook...";
|
|
1177
|
+
} else {
|
|
1178
|
+
existingSettings.hooks.SubagentStop.push(hookConfig.hooks.SubagentStop[0]);
|
|
1179
|
+
}
|
|
1180
|
+
await fs.writeFile(settingsPath, JSON.stringify(existingSettings, null, 2), "utf-8");
|
|
1181
|
+
spinner.succeed(chalk11.green("Claude Code hooks configured!"));
|
|
1182
|
+
console.log(chalk11.gray("\n Configuration written to:"));
|
|
1183
|
+
console.log(chalk11.white(` ${settingsPath}`));
|
|
1184
|
+
console.log(chalk11.gray("\n Hook script location:"));
|
|
1185
|
+
console.log(chalk11.white(` ${syncScriptPath}`));
|
|
1186
|
+
console.log(chalk11.blue.bold("\n How it works:\n"));
|
|
1187
|
+
console.log(chalk11.white(" 1. When Claude marks a TodoWrite item as completed"));
|
|
1188
|
+
console.log(chalk11.white(" 2. The SubagentStop hook triggers"));
|
|
1189
|
+
console.log(chalk11.white(" 3. sync-todos.js syncs status to vibetasks"));
|
|
1190
|
+
console.log(chalk11.gray("\n Status mapping:"));
|
|
1191
|
+
console.log(chalk11.gray(" pending -> todo"));
|
|
1192
|
+
console.log(chalk11.gray(" in_progress -> vibing"));
|
|
1193
|
+
console.log(chalk11.gray(" completed -> done"));
|
|
1194
|
+
console.log(chalk11.yellow.bold("\n Note: Ensure vibetasks MCP is configured in Claude Code."));
|
|
1195
|
+
console.log(chalk11.gray(" Run: vibetasks setup claude\n"));
|
|
1196
|
+
process.exit(0);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
spinner.fail(chalk11.red("Failed to configure Claude Code hooks"));
|
|
1199
|
+
console.error(chalk11.red(`
|
|
1200
|
+
Error: ${error.message}
|
|
1201
|
+
`));
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
hooksCommand.command("status").description("Show status of installed hooks").action(async () => {
|
|
1206
|
+
console.log(chalk11.blue.bold("\n VibeTasks Hooks Status\n"));
|
|
1207
|
+
const gitHooksDir = path.join(process.cwd(), ".git", "hooks");
|
|
1208
|
+
const postCommitPath = path.join(gitHooksDir, "post-commit");
|
|
1209
|
+
try {
|
|
1210
|
+
const content = await fs.readFile(postCommitPath, "utf-8");
|
|
1211
|
+
if (content.includes("vibetasks") || content.includes("VibeTasks")) {
|
|
1212
|
+
console.log(chalk11.green(" [x] Git post-commit hook installed"));
|
|
1213
|
+
} else {
|
|
1214
|
+
console.log(chalk11.yellow(" [ ] Git post-commit hook (exists but not vibetasks)"));
|
|
1215
|
+
}
|
|
1216
|
+
} catch {
|
|
1217
|
+
console.log(chalk11.gray(" [ ] Git post-commit hook not installed"));
|
|
1218
|
+
}
|
|
1219
|
+
const claudeSettingsPath = path.join(process.cwd(), ".claude", "settings.json");
|
|
1220
|
+
try {
|
|
1221
|
+
const content = await fs.readFile(claudeSettingsPath, "utf-8");
|
|
1222
|
+
const settings = JSON.parse(content);
|
|
1223
|
+
if (settings.hooks?.SubagentStop?.some((h) => h.command?.includes("sync-todos"))) {
|
|
1224
|
+
console.log(chalk11.green(" [x] Claude Code SubagentStop hook configured"));
|
|
1225
|
+
} else {
|
|
1226
|
+
console.log(chalk11.gray(" [ ] Claude Code SubagentStop hook not configured"));
|
|
1227
|
+
}
|
|
1228
|
+
} catch {
|
|
1229
|
+
console.log(chalk11.gray(" [ ] Claude Code hooks not configured"));
|
|
1230
|
+
}
|
|
1231
|
+
console.log(chalk11.gray("\n To install hooks:"));
|
|
1232
|
+
console.log(chalk11.gray(" Git: vibetasks hooks install"));
|
|
1233
|
+
console.log(chalk11.gray(" Claude: vibetasks hooks claude\n"));
|
|
1234
|
+
process.exit(0);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
// src/commands/init.ts
|
|
1238
|
+
import { Command as Command11 } from "commander";
|
|
1239
|
+
import inquirer4 from "inquirer";
|
|
1240
|
+
import ora9 from "ora";
|
|
1241
|
+
import chalk12 from "chalk";
|
|
1242
|
+
import { AuthManager as AuthManager11 } from "@vibetasks/core";
|
|
1243
|
+
import { detectProject as detectProject2 } from "@vibetasks/shared/utils/project-detector";
|
|
1244
|
+
var initCommand = new Command11("init").description("Initialize TaskFlow for current project (auto-detect from git)").option("--name <name>", "Manually specify project name").action(async (options) => {
|
|
1245
|
+
const spinner = ora9("Detecting project...").start();
|
|
1246
|
+
try {
|
|
1247
|
+
const authManager = new AuthManager11();
|
|
1248
|
+
const cwd = process.cwd();
|
|
1249
|
+
let project;
|
|
1250
|
+
if (options.name) {
|
|
1251
|
+
project = {
|
|
1252
|
+
name: options.name,
|
|
1253
|
+
source: "manual",
|
|
1254
|
+
path: cwd
|
|
1255
|
+
};
|
|
1256
|
+
spinner.succeed(chalk12.green("Using manual project name"));
|
|
1257
|
+
} else {
|
|
1258
|
+
project = await detectProject2(cwd);
|
|
1259
|
+
spinner.succeed(chalk12.green("Project detected!"));
|
|
1260
|
+
}
|
|
1261
|
+
console.log(chalk12.white("\n\u{1F4C1} Project Info:"));
|
|
1262
|
+
console.log(chalk12.gray(` Name: ${chalk12.white(project.name)}`));
|
|
1263
|
+
console.log(chalk12.gray(` Source: ${chalk12.white(project.source)}`));
|
|
1264
|
+
console.log(chalk12.gray(` Path: ${chalk12.white(project.path)}
|
|
1265
|
+
`));
|
|
1266
|
+
const answers = await inquirer4.prompt([
|
|
1267
|
+
{
|
|
1268
|
+
type: "confirm",
|
|
1269
|
+
name: "confirm",
|
|
1270
|
+
message: `Tag all tasks created from this directory as "${project.name}"?`,
|
|
1271
|
+
default: true
|
|
1272
|
+
}
|
|
1273
|
+
]);
|
|
1274
|
+
if (!answers.confirm) {
|
|
1275
|
+
console.log(chalk12.gray("\nCancelled. Tasks will use default project.\n"));
|
|
1276
|
+
process.exit(0);
|
|
1277
|
+
}
|
|
1278
|
+
await authManager.setConfig(`project_${cwd}`, project.name);
|
|
1279
|
+
console.log(chalk12.green("\n\u2713 Project initialized!"));
|
|
1280
|
+
console.log(
|
|
1281
|
+
chalk12.gray(
|
|
1282
|
+
`
|
|
1283
|
+
All tasks created from ${chalk12.white(cwd)} will be tagged as ${chalk12.white(project.name)}
|
|
1284
|
+
`
|
|
1285
|
+
)
|
|
1286
|
+
);
|
|
1287
|
+
console.log(chalk12.blue("Next steps:"));
|
|
1288
|
+
console.log(chalk12.gray(' \u2022 Run taskflow add "Your task" to create tasks'));
|
|
1289
|
+
console.log(chalk12.gray(" \u2022 Tasks will automatically be tagged with this project"));
|
|
1290
|
+
console.log(chalk12.gray(" \u2022 Use taskflow list --project to filter by project\n"));
|
|
1291
|
+
process.exit(0);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
spinner.fail(chalk12.red("Failed to initialize project"));
|
|
1294
|
+
console.error(chalk12.red(`
|
|
1295
|
+
Error: ${error.message}
|
|
1296
|
+
`));
|
|
1297
|
+
process.exit(1);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
// src/commands/setup.ts
|
|
1302
|
+
import { Command as Command12 } from "commander";
|
|
1303
|
+
import inquirer5 from "inquirer";
|
|
1304
|
+
import ora10 from "ora";
|
|
1305
|
+
import chalk13 from "chalk";
|
|
1306
|
+
import fs2 from "fs/promises";
|
|
1307
|
+
import path2 from "path";
|
|
1308
|
+
import os from "os";
|
|
1309
|
+
import { createServer as createServer2 } from "http";
|
|
1310
|
+
import { exec as exec2 } from "child_process";
|
|
1311
|
+
import { promisify as promisify2 } from "util";
|
|
1312
|
+
import { AuthManager as AuthManager12, TaskOperations as TaskOperations8, createSupabaseClient as createSupabaseClient2 } from "@vibetasks/core";
|
|
1313
|
+
import { detectProject as detectProject3 } from "@vibetasks/shared/utils/project-detector";
|
|
1314
|
+
var execAsync2 = promisify2(exec2);
|
|
1315
|
+
var SUPABASE_URL = "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
1316
|
+
var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
1317
|
+
async function openBrowser2(url) {
|
|
1318
|
+
const platform = process.platform;
|
|
1319
|
+
let command;
|
|
1320
|
+
if (platform === "win32") {
|
|
1321
|
+
command = `start "" "${url}"`;
|
|
1322
|
+
} else if (platform === "darwin") {
|
|
1323
|
+
command = `open "${url}"`;
|
|
1324
|
+
} else {
|
|
1325
|
+
command = `xdg-open "${url}"`;
|
|
1326
|
+
}
|
|
1327
|
+
try {
|
|
1328
|
+
await execAsync2(command);
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
async function getAvailablePort2(startPort = 3737) {
|
|
1333
|
+
return new Promise((resolve, reject) => {
|
|
1334
|
+
const server = createServer2();
|
|
1335
|
+
server.listen(startPort, () => {
|
|
1336
|
+
const port = server.address().port;
|
|
1337
|
+
server.close(() => resolve(port));
|
|
1338
|
+
});
|
|
1339
|
+
server.on("error", (error) => {
|
|
1340
|
+
if (error.code === "EADDRINUSE") {
|
|
1341
|
+
resolve(getAvailablePort2(startPort + 1));
|
|
1342
|
+
} else {
|
|
1343
|
+
reject(error);
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
function getClaudeConfigPath() {
|
|
1349
|
+
const configHome = process.env.XDG_CONFIG_HOME || path2.join(os.homedir(), ".config");
|
|
1350
|
+
return path2.join(configHome, "claude-code", "config.json");
|
|
1351
|
+
}
|
|
1352
|
+
async function claudeCodeConfigExists() {
|
|
1353
|
+
try {
|
|
1354
|
+
const configDir = path2.dirname(getClaudeConfigPath());
|
|
1355
|
+
await fs2.access(configDir);
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function showWelcome() {
|
|
1362
|
+
console.log("\n");
|
|
1363
|
+
console.log(chalk13.bold.magenta("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1364
|
+
console.log(chalk13.bold.white(" VibeTasks Setup Wizard"));
|
|
1365
|
+
console.log(chalk13.bold.magenta("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1366
|
+
console.log("");
|
|
1367
|
+
console.log(chalk13.gray(" This wizard will set up VibeTasks in about 2 minutes:"));
|
|
1368
|
+
console.log("");
|
|
1369
|
+
console.log(chalk13.white(" 1.") + chalk13.gray(" Authenticate with your account"));
|
|
1370
|
+
console.log(chalk13.white(" 2.") + chalk13.gray(" Configure Claude Code integration"));
|
|
1371
|
+
console.log(chalk13.white(" 3.") + chalk13.gray(" Initialize project (optional)"));
|
|
1372
|
+
console.log(chalk13.white(" 4.") + chalk13.gray(" Verify everything works"));
|
|
1373
|
+
console.log("");
|
|
1374
|
+
}
|
|
1375
|
+
async function checkExistingAuth() {
|
|
1376
|
+
try {
|
|
1377
|
+
const authManager = new AuthManager12();
|
|
1378
|
+
const token = await authManager.getAccessToken();
|
|
1379
|
+
if (!token) return { authenticated: false };
|
|
1380
|
+
const supabaseUrl = await authManager.getConfig("supabase_url") || SUPABASE_URL;
|
|
1381
|
+
const supabaseKey = await authManager.getConfig("supabase_key") || SUPABASE_ANON_KEY;
|
|
1382
|
+
const supabase = createSupabaseClient2({ supabaseUrl, supabaseKey, accessToken: token });
|
|
1383
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
1384
|
+
if (user?.email) {
|
|
1385
|
+
return { authenticated: true, email: user.email };
|
|
1386
|
+
}
|
|
1387
|
+
return { authenticated: false };
|
|
1388
|
+
} catch {
|
|
1389
|
+
return { authenticated: false };
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async function runBrowserAuth() {
|
|
1393
|
+
const port = await getAvailablePort2();
|
|
1394
|
+
return new Promise((resolve) => {
|
|
1395
|
+
let timeoutId = null;
|
|
1396
|
+
const server = createServer2(async (req, res) => {
|
|
1397
|
+
if (req.method === "OPTIONS") {
|
|
1398
|
+
res.writeHead(200, {
|
|
1399
|
+
"Access-Control-Allow-Origin": "*",
|
|
1400
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1401
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
1402
|
+
});
|
|
1403
|
+
res.end();
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
if (req.url?.startsWith("/callback")) {
|
|
1407
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
1408
|
+
const accessToken = url.searchParams.get("access_token");
|
|
1409
|
+
const refreshToken = url.searchParams.get("refresh_token");
|
|
1410
|
+
const email = url.searchParams.get("email");
|
|
1411
|
+
const supabaseUrl = url.searchParams.get("supabase_url");
|
|
1412
|
+
const supabaseKey = url.searchParams.get("supabase_key");
|
|
1413
|
+
if (!accessToken || !refreshToken || !email) {
|
|
1414
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1415
|
+
res.end(getErrorHtml("Missing authentication data"));
|
|
1416
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1417
|
+
server.close(() => resolve({ success: false }));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
const authManager = new AuthManager12();
|
|
1422
|
+
await authManager.setAccessToken(accessToken);
|
|
1423
|
+
await authManager.setRefreshToken(refreshToken);
|
|
1424
|
+
if (supabaseUrl) await authManager.setConfig("supabase_url", supabaseUrl);
|
|
1425
|
+
if (supabaseKey) await authManager.setConfig("supabase_key", supabaseKey);
|
|
1426
|
+
const storageMethod = authManager.getStorageMethod();
|
|
1427
|
+
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
1428
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1429
|
+
res.end(getSuccessHtml(email));
|
|
1430
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1431
|
+
server.close(() => resolve({ success: true, email, storageLocation }));
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
1434
|
+
res.end(getErrorHtml("Failed to store tokens"));
|
|
1435
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1436
|
+
server.close(() => resolve({ success: false }));
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
res.writeHead(404);
|
|
1441
|
+
res.end();
|
|
1442
|
+
});
|
|
1443
|
+
server.listen(port, async () => {
|
|
1444
|
+
const taskflowPort = process.env.TASKFLOW_WEB_PORT || "2843";
|
|
1445
|
+
const authUrl = `http://localhost:${taskflowPort}/cli-auth?port=${port}`;
|
|
1446
|
+
console.log(chalk13.gray(`
|
|
1447
|
+
If browser doesn't open, visit:`));
|
|
1448
|
+
console.log(chalk13.cyan(` ${authUrl}
|
|
1449
|
+
`));
|
|
1450
|
+
await openBrowser2(authUrl);
|
|
1451
|
+
});
|
|
1452
|
+
server.on("error", () => {
|
|
1453
|
+
resolve({ success: false });
|
|
1454
|
+
});
|
|
1455
|
+
timeoutId = setTimeout(() => {
|
|
1456
|
+
server.close(() => resolve({ success: false }));
|
|
1457
|
+
}, 3 * 60 * 1e3);
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
async function stepAuthentication() {
|
|
1461
|
+
console.log(chalk13.bold.blue("\n\u2501\u2501\u2501 Step 1: Authentication \u2501\u2501\u2501\n"));
|
|
1462
|
+
const existing = await checkExistingAuth();
|
|
1463
|
+
if (existing.authenticated) {
|
|
1464
|
+
console.log(chalk13.green(" \u2713") + chalk13.white(` Already logged in as ${chalk13.cyan(existing.email)}`));
|
|
1465
|
+
const { reauth } = await inquirer5.prompt([{
|
|
1466
|
+
type: "confirm",
|
|
1467
|
+
name: "reauth",
|
|
1468
|
+
message: "Re-authenticate with a different account?",
|
|
1469
|
+
default: false
|
|
1470
|
+
}]);
|
|
1471
|
+
if (!reauth) {
|
|
1472
|
+
return { success: true, email: existing.email, skipped: true };
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
const { authMethod } = await inquirer5.prompt([{
|
|
1476
|
+
type: "list",
|
|
1477
|
+
name: "authMethod",
|
|
1478
|
+
message: "How would you like to authenticate?",
|
|
1479
|
+
choices: [
|
|
1480
|
+
{ name: "Browser login (recommended)", value: "browser" },
|
|
1481
|
+
{ name: "Email & password", value: "terminal" }
|
|
1482
|
+
]
|
|
1483
|
+
}]);
|
|
1484
|
+
if (authMethod === "browser") {
|
|
1485
|
+
const spinner = ora10("Opening browser for authentication...").start();
|
|
1486
|
+
spinner.stop();
|
|
1487
|
+
console.log(chalk13.gray(" Waiting for browser authentication...\n"));
|
|
1488
|
+
const result = await runBrowserAuth();
|
|
1489
|
+
if (result.success) {
|
|
1490
|
+
console.log(chalk13.green("\n \u2713") + chalk13.white(` Logged in as ${chalk13.cyan(result.email)}`));
|
|
1491
|
+
console.log(chalk13.gray(` Tokens stored in: ${result.storageLocation}`));
|
|
1492
|
+
} else {
|
|
1493
|
+
console.log(chalk13.red("\n \u2717 Authentication failed or timed out"));
|
|
1494
|
+
}
|
|
1495
|
+
return result;
|
|
1496
|
+
} else {
|
|
1497
|
+
const answers = await inquirer5.prompt([
|
|
1498
|
+
{
|
|
1499
|
+
type: "input",
|
|
1500
|
+
name: "email",
|
|
1501
|
+
message: "Email:",
|
|
1502
|
+
validate: (input) => input.includes("@") || "Please enter a valid email"
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
type: "password",
|
|
1506
|
+
name: "password",
|
|
1507
|
+
message: "Password:",
|
|
1508
|
+
mask: "*",
|
|
1509
|
+
validate: (input) => input.length >= 6 || "Password must be at least 6 characters"
|
|
1510
|
+
}
|
|
1511
|
+
]);
|
|
1512
|
+
const spinner = ora10("Authenticating...").start();
|
|
1513
|
+
try {
|
|
1514
|
+
const authManager = new AuthManager12();
|
|
1515
|
+
await authManager.setConfig("supabase_url", SUPABASE_URL);
|
|
1516
|
+
await authManager.setConfig("supabase_key", SUPABASE_ANON_KEY);
|
|
1517
|
+
const supabase = createSupabaseClient2({
|
|
1518
|
+
supabaseUrl: SUPABASE_URL,
|
|
1519
|
+
supabaseKey: SUPABASE_ANON_KEY
|
|
1520
|
+
});
|
|
1521
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
1522
|
+
email: answers.email,
|
|
1523
|
+
password: answers.password
|
|
1524
|
+
});
|
|
1525
|
+
if (error) throw error;
|
|
1526
|
+
if (!data.session) throw new Error("No session returned");
|
|
1527
|
+
await authManager.setAccessToken(data.session.access_token);
|
|
1528
|
+
await authManager.setRefreshToken(data.session.refresh_token);
|
|
1529
|
+
const storageMethod = authManager.getStorageMethod();
|
|
1530
|
+
const storageLocation = storageMethod === "keychain" ? "system keychain" : authManager["configManager"].getConfigPath();
|
|
1531
|
+
spinner.succeed(chalk13.green("Authenticated successfully"));
|
|
1532
|
+
console.log(chalk13.gray(` Tokens stored in: ${storageLocation}`));
|
|
1533
|
+
return { success: true, email: answers.email, storageLocation };
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
spinner.fail(chalk13.red("Authentication failed"));
|
|
1536
|
+
console.log(chalk13.red(` ${error.message}`));
|
|
1537
|
+
return { success: false };
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
async function stepClaudeCodeConfig() {
|
|
1542
|
+
console.log(chalk13.bold.blue("\n\u2501\u2501\u2501 Step 2: Claude Code Integration \u2501\u2501\u2501\n"));
|
|
1543
|
+
const hasClaudeCode = await claudeCodeConfigExists();
|
|
1544
|
+
if (!hasClaudeCode) {
|
|
1545
|
+
console.log(chalk13.yellow(" \u26A0") + chalk13.gray(" Claude Code config directory not found"));
|
|
1546
|
+
console.log(chalk13.gray(" Run this command again after installing Claude Code"));
|
|
1547
|
+
const { createAnyway } = await inquirer5.prompt([{
|
|
1548
|
+
type: "confirm",
|
|
1549
|
+
name: "createAnyway",
|
|
1550
|
+
message: "Create config directory anyway?",
|
|
1551
|
+
default: false
|
|
1552
|
+
}]);
|
|
1553
|
+
if (!createAnyway) {
|
|
1554
|
+
return { success: true, skipped: true };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const { configure } = await inquirer5.prompt([{
|
|
1558
|
+
type: "confirm",
|
|
1559
|
+
name: "configure",
|
|
1560
|
+
message: "Configure VibeTasks MCP server for Claude Code?",
|
|
1561
|
+
default: true
|
|
1562
|
+
}]);
|
|
1563
|
+
if (!configure) {
|
|
1564
|
+
return { success: true, skipped: true };
|
|
1565
|
+
}
|
|
1566
|
+
const spinner = ora10("Configuring Claude Code...").start();
|
|
1567
|
+
try {
|
|
1568
|
+
const configPath = getClaudeConfigPath();
|
|
1569
|
+
const configDir = path2.dirname(configPath);
|
|
1570
|
+
await fs2.mkdir(configDir, { recursive: true });
|
|
1571
|
+
let config = {};
|
|
1572
|
+
try {
|
|
1573
|
+
const existing = await fs2.readFile(configPath, "utf-8");
|
|
1574
|
+
config = JSON.parse(existing);
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
1578
|
+
if (!config.hooks) config.hooks = {};
|
|
1579
|
+
if (!config.hooks.SessionStart) config.hooks.SessionStart = [];
|
|
1580
|
+
if (!config.hooks.Stop) config.hooks.Stop = [];
|
|
1581
|
+
let mcpCommand = "vibetasks-mcp";
|
|
1582
|
+
let mcpArgs = [];
|
|
1583
|
+
try {
|
|
1584
|
+
if (process.platform === "win32") {
|
|
1585
|
+
await execAsync2("where vibetasks-mcp");
|
|
1586
|
+
} else {
|
|
1587
|
+
await execAsync2("which vibetasks-mcp");
|
|
1588
|
+
}
|
|
1589
|
+
} catch {
|
|
1590
|
+
mcpCommand = "npx";
|
|
1591
|
+
mcpArgs = ["@vibetasks/mcp-server"];
|
|
1592
|
+
}
|
|
1593
|
+
config.mcpServers.vibetasks = {
|
|
1594
|
+
command: mcpCommand,
|
|
1595
|
+
...mcpArgs.length > 0 && { args: mcpArgs },
|
|
1596
|
+
env: {
|
|
1597
|
+
TASKFLOW_SUPABASE_URL: SUPABASE_URL,
|
|
1598
|
+
TASKFLOW_SUPABASE_KEY: SUPABASE_ANON_KEY
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
const hasSessionStartHook = config.hooks.SessionStart.some(
|
|
1602
|
+
(h) => h.command === mcpCommand || h.command === "vibetasks-mcp"
|
|
1603
|
+
);
|
|
1604
|
+
const hasStopHook = config.hooks.Stop.some(
|
|
1605
|
+
(h) => h.command === mcpCommand || h.command === "vibetasks-mcp"
|
|
1606
|
+
);
|
|
1607
|
+
if (!hasSessionStartHook) {
|
|
1608
|
+
config.hooks.SessionStart.push({
|
|
1609
|
+
type: "command",
|
|
1610
|
+
command: mcpCommand,
|
|
1611
|
+
...mcpArgs.length > 0 && { args: mcpArgs },
|
|
1612
|
+
env: {
|
|
1613
|
+
CLAUDE_HOOK_TYPE: "SessionStart",
|
|
1614
|
+
TASKFLOW_SUPABASE_URL: SUPABASE_URL
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
if (!hasStopHook) {
|
|
1619
|
+
config.hooks.Stop.push({
|
|
1620
|
+
type: "command",
|
|
1621
|
+
command: mcpCommand,
|
|
1622
|
+
...mcpArgs.length > 0 && { args: mcpArgs },
|
|
1623
|
+
env: {
|
|
1624
|
+
CLAUDE_HOOK_TYPE: "SessionEnd",
|
|
1625
|
+
TASKFLOW_SUPABASE_URL: SUPABASE_URL
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
await fs2.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1630
|
+
spinner.succeed(chalk13.green("Claude Code configured"));
|
|
1631
|
+
console.log(chalk13.gray(` Config: ${configPath}`));
|
|
1632
|
+
console.log(chalk13.gray(" Added: MCP server, SessionStart hook, SessionEnd hook"));
|
|
1633
|
+
return { success: true, configured: true, configPath };
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
spinner.fail(chalk13.red("Failed to configure Claude Code"));
|
|
1636
|
+
console.log(chalk13.red(` ${error.message}`));
|
|
1637
|
+
return { success: false };
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
async function stepProjectInit() {
|
|
1641
|
+
console.log(chalk13.bold.blue("\n\u2501\u2501\u2501 Step 3: Project Setup (Optional) \u2501\u2501\u2501\n"));
|
|
1642
|
+
const spinner = ora10("Detecting project...").start();
|
|
1643
|
+
let project;
|
|
1644
|
+
try {
|
|
1645
|
+
project = await detectProject3(process.cwd());
|
|
1646
|
+
spinner.succeed(chalk13.green(`Detected: ${project.name}`));
|
|
1647
|
+
console.log(chalk13.gray(` Source: ${project.source}`));
|
|
1648
|
+
console.log(chalk13.gray(` Path: ${project.path}`));
|
|
1649
|
+
} catch {
|
|
1650
|
+
spinner.info(chalk13.gray("No project detected in current directory"));
|
|
1651
|
+
return { success: true, skipped: true };
|
|
1652
|
+
}
|
|
1653
|
+
const { setupProject } = await inquirer5.prompt([{
|
|
1654
|
+
type: "confirm",
|
|
1655
|
+
name: "setupProject",
|
|
1656
|
+
message: `Auto-tag tasks created here as "${project.name}"?`,
|
|
1657
|
+
default: true
|
|
1658
|
+
}]);
|
|
1659
|
+
if (!setupProject) {
|
|
1660
|
+
return { success: true, skipped: true };
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
const authManager = new AuthManager12();
|
|
1664
|
+
await authManager.setConfig(`project_${process.cwd()}`, project.name);
|
|
1665
|
+
console.log(chalk13.green("\n \u2713") + chalk13.white(` Project "${project.name}" configured`));
|
|
1666
|
+
return { success: true, projectName: project.name };
|
|
1667
|
+
} catch (error) {
|
|
1668
|
+
console.log(chalk13.yellow("\n \u26A0") + chalk13.gray(` Could not save project config: ${error.message}`));
|
|
1669
|
+
return { success: true, skipped: true };
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async function stepVerify() {
|
|
1673
|
+
console.log(chalk13.bold.blue("\n\u2501\u2501\u2501 Step 4: Verification \u2501\u2501\u2501\n"));
|
|
1674
|
+
const result = {
|
|
1675
|
+
supabaseConnected: false,
|
|
1676
|
+
mcpConfigured: false
|
|
1677
|
+
};
|
|
1678
|
+
const supabaseSpinner = ora10("Testing Supabase connection...").start();
|
|
1679
|
+
try {
|
|
1680
|
+
const authManager = new AuthManager12();
|
|
1681
|
+
const taskOps = await TaskOperations8.fromAuthManager(authManager);
|
|
1682
|
+
const tasks = await taskOps.getTasks("all");
|
|
1683
|
+
result.supabaseConnected = true;
|
|
1684
|
+
result.taskCount = tasks.length;
|
|
1685
|
+
supabaseSpinner.succeed(chalk13.green(`Supabase connected (${tasks.length} tasks found)`));
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
supabaseSpinner.fail(chalk13.red("Supabase connection failed"));
|
|
1688
|
+
console.log(chalk13.gray(` ${error.message}`));
|
|
1689
|
+
}
|
|
1690
|
+
const mcpSpinner = ora10("Checking MCP configuration...").start();
|
|
1691
|
+
try {
|
|
1692
|
+
const configPath = getClaudeConfigPath();
|
|
1693
|
+
const configContent = await fs2.readFile(configPath, "utf-8");
|
|
1694
|
+
const config = JSON.parse(configContent);
|
|
1695
|
+
if (config.mcpServers?.vibetasks) {
|
|
1696
|
+
result.mcpConfigured = true;
|
|
1697
|
+
mcpSpinner.succeed(chalk13.green("MCP server configured"));
|
|
1698
|
+
} else {
|
|
1699
|
+
mcpSpinner.warn(chalk13.yellow("MCP server not found in config"));
|
|
1700
|
+
}
|
|
1701
|
+
} catch {
|
|
1702
|
+
mcpSpinner.warn(chalk13.yellow("Could not read Claude Code config"));
|
|
1703
|
+
}
|
|
1704
|
+
return result;
|
|
1705
|
+
}
|
|
1706
|
+
function showCompletion(auth, claude, project, verify) {
|
|
1707
|
+
console.log("\n");
|
|
1708
|
+
console.log(chalk13.bold.green("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1709
|
+
console.log(chalk13.bold.white(" Setup Complete!"));
|
|
1710
|
+
console.log(chalk13.bold.green("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1711
|
+
console.log("");
|
|
1712
|
+
console.log(chalk13.white(" Summary:"));
|
|
1713
|
+
console.log(` ${auth.success ? chalk13.green("\u2713") : chalk13.red("\u2717")} Authentication: ${auth.email || "Not configured"}`);
|
|
1714
|
+
console.log(` ${claude.configured ? chalk13.green("\u2713") : chalk13.yellow("\u25CB")} Claude Code: ${claude.configured ? "Configured" : "Skipped"}`);
|
|
1715
|
+
console.log(` ${project.projectName ? chalk13.green("\u2713") : chalk13.yellow("\u25CB")} Project: ${project.projectName || "None"}`);
|
|
1716
|
+
console.log(` ${verify.supabaseConnected ? chalk13.green("\u2713") : chalk13.red("\u2717")} Connection: ${verify.supabaseConnected ? `${verify.taskCount} tasks` : "Failed"}`);
|
|
1717
|
+
console.log("");
|
|
1718
|
+
if (claude.configured) {
|
|
1719
|
+
console.log(chalk13.yellow(" \u26A1 Important: Restart Claude Code to activate MCP server"));
|
|
1720
|
+
console.log("");
|
|
1721
|
+
}
|
|
1722
|
+
console.log(chalk13.white(" What you can do now:"));
|
|
1723
|
+
console.log("");
|
|
1724
|
+
console.log(chalk13.gray(" CLI Commands:"));
|
|
1725
|
+
console.log(chalk13.cyan(' $ vibetasks add "My first task"'));
|
|
1726
|
+
console.log(chalk13.cyan(" $ vibetasks list"));
|
|
1727
|
+
console.log(chalk13.cyan(" $ vibetasks done <task-id>"));
|
|
1728
|
+
console.log("");
|
|
1729
|
+
if (claude.configured) {
|
|
1730
|
+
console.log(chalk13.gray(" In Claude Code (after restart):"));
|
|
1731
|
+
console.log(chalk13.cyan(' "Create a task to fix the login bug"'));
|
|
1732
|
+
console.log(chalk13.cyan(' "Show my tasks for today"'));
|
|
1733
|
+
console.log(chalk13.cyan(' "What should I work on next?"'));
|
|
1734
|
+
}
|
|
1735
|
+
console.log("");
|
|
1736
|
+
console.log(chalk13.gray(" Need help? https://github.com/vyasapps/vibetasks"));
|
|
1737
|
+
console.log("");
|
|
1738
|
+
}
|
|
1739
|
+
function getSuccessHtml(email) {
|
|
1740
|
+
return `<!DOCTYPE html>
|
|
1741
|
+
<html>
|
|
1742
|
+
<head>
|
|
1743
|
+
<title>VibeTasks - Setup Success</title>
|
|
1744
|
+
<style>
|
|
1745
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1746
|
+
body {
|
|
1747
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1748
|
+
display: flex; align-items: center; justify-content: center;
|
|
1749
|
+
min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1750
|
+
}
|
|
1751
|
+
.container {
|
|
1752
|
+
text-align: center; padding: 60px 40px; background: white;
|
|
1753
|
+
border-radius: 20px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); max-width: 450px;
|
|
1754
|
+
}
|
|
1755
|
+
.check { font-size: 64px; margin-bottom: 20px; }
|
|
1756
|
+
h1 { color: #2d3748; font-size: 24px; margin-bottom: 8px; }
|
|
1757
|
+
.email { color: #667eea; font-weight: 600; margin-bottom: 20px; }
|
|
1758
|
+
p { color: #718096; line-height: 1.6; }
|
|
1759
|
+
.badge {
|
|
1760
|
+
display: inline-block; background: #f0fdf4; color: #15803d;
|
|
1761
|
+
padding: 8px 16px; border-radius: 20px; font-size: 14px; margin-top: 20px;
|
|
1762
|
+
}
|
|
1763
|
+
</style>
|
|
1764
|
+
</head>
|
|
1765
|
+
<body>
|
|
1766
|
+
<div class="container">
|
|
1767
|
+
<div class="check">\u2713</div>
|
|
1768
|
+
<h1>Authentication Successful!</h1>
|
|
1769
|
+
<p class="email">${email}</p>
|
|
1770
|
+
<p>Return to your terminal to continue setup.</p>
|
|
1771
|
+
<div class="badge">You can close this window</div>
|
|
1772
|
+
</div>
|
|
1773
|
+
</body>
|
|
1774
|
+
</html>`;
|
|
1775
|
+
}
|
|
1776
|
+
function getErrorHtml(message) {
|
|
1777
|
+
return `<!DOCTYPE html>
|
|
1778
|
+
<html>
|
|
1779
|
+
<head>
|
|
1780
|
+
<title>VibeTasks - Error</title>
|
|
1781
|
+
<style>
|
|
1782
|
+
body {
|
|
1783
|
+
font-family: system-ui; display: flex; align-items: center;
|
|
1784
|
+
justify-content: center; height: 100vh; margin: 0; background: #f5f5f5;
|
|
1785
|
+
}
|
|
1786
|
+
.container {
|
|
1787
|
+
text-align: center; padding: 40px; background: white;
|
|
1788
|
+
border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
1789
|
+
}
|
|
1790
|
+
h1 { color: #e53e3e; margin-bottom: 16px; }
|
|
1791
|
+
</style>
|
|
1792
|
+
</head>
|
|
1793
|
+
<body>
|
|
1794
|
+
<div class="container">
|
|
1795
|
+
<h1>Authentication Failed</h1>
|
|
1796
|
+
<p>${message}</p>
|
|
1797
|
+
</div>
|
|
1798
|
+
</body>
|
|
1799
|
+
</html>`;
|
|
1800
|
+
}
|
|
1801
|
+
var setupCommand = new Command12("setup").description("Interactive setup wizard for VibeTasks").option("--skip-auth", "Skip authentication step").option("--skip-claude", "Skip Claude Code configuration").option("--skip-project", "Skip project initialization").option("--skip-verify", "Skip verification step").action(async (options) => {
|
|
1802
|
+
showWelcome();
|
|
1803
|
+
const { proceed } = await inquirer5.prompt([{
|
|
1804
|
+
type: "confirm",
|
|
1805
|
+
name: "proceed",
|
|
1806
|
+
message: "Ready to begin setup?",
|
|
1807
|
+
default: true
|
|
1808
|
+
}]);
|
|
1809
|
+
if (!proceed) {
|
|
1810
|
+
console.log(chalk13.gray("\nSetup cancelled. Run `vibetasks setup` anytime to continue.\n"));
|
|
1811
|
+
process.exit(0);
|
|
1812
|
+
}
|
|
1813
|
+
let authResult = { success: false };
|
|
1814
|
+
if (!options.skipAuth) {
|
|
1815
|
+
authResult = await stepAuthentication();
|
|
1816
|
+
if (!authResult.success) {
|
|
1817
|
+
console.log(chalk13.red("\n\u2717 Authentication is required to continue."));
|
|
1818
|
+
console.log(chalk13.gray(" Run `vibetasks setup` to try again.\n"));
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
} else {
|
|
1822
|
+
authResult = { success: true, skipped: true };
|
|
1823
|
+
}
|
|
1824
|
+
let claudeResult = { success: true, skipped: true };
|
|
1825
|
+
if (!options.skipClaude) {
|
|
1826
|
+
claudeResult = await stepClaudeCodeConfig();
|
|
1827
|
+
}
|
|
1828
|
+
let projectResult = { success: true, skipped: true };
|
|
1829
|
+
if (!options.skipProject) {
|
|
1830
|
+
projectResult = await stepProjectInit();
|
|
1831
|
+
}
|
|
1832
|
+
let verifyResult = { supabaseConnected: false, mcpConfigured: false };
|
|
1833
|
+
if (!options.skipVerify && authResult.success && !authResult.skipped) {
|
|
1834
|
+
verifyResult = await stepVerify();
|
|
1835
|
+
} else if (authResult.skipped) {
|
|
1836
|
+
verifyResult = await stepVerify();
|
|
1837
|
+
}
|
|
1838
|
+
showCompletion(authResult, claudeResult, projectResult, verifyResult);
|
|
1839
|
+
process.exit(0);
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
// src/commands/watch.ts
|
|
1843
|
+
import { Command as Command13 } from "commander";
|
|
1844
|
+
import chalk14 from "chalk";
|
|
1845
|
+
import ora11 from "ora";
|
|
1846
|
+
import inquirer6 from "inquirer";
|
|
1847
|
+
import { AuthManager as AuthManager13, TaskOperations as TaskOperations9 } from "@vibetasks/core";
|
|
1848
|
+
import { detectProject as detectProject4 } from "@vibetasks/shared/utils/project-detector";
|
|
1849
|
+
var watchCommand = new Command13("watch").description("Monitor clipboard for errors and offer to capture them as tasks").option("-i, --interval <ms>", "Polling interval in milliseconds", "1000").option("-a, --auto", "Auto-create tasks without prompting").option("-q, --quiet", "Minimal output (only show errors)").option("--project <name>", "Override auto-detected project").action(async (options) => {
|
|
1850
|
+
const interval = parseInt(options.interval || "1000", 10);
|
|
1851
|
+
if (!options.quiet) {
|
|
1852
|
+
console.log();
|
|
1853
|
+
console.log(chalk14.cyan.bold("VibeTasks Clipboard Monitor"));
|
|
1854
|
+
console.log(chalk14.gray("Watching for errors in your clipboard..."));
|
|
1855
|
+
console.log();
|
|
1856
|
+
}
|
|
1857
|
+
let authManager;
|
|
1858
|
+
let taskOps;
|
|
1859
|
+
try {
|
|
1860
|
+
authManager = new AuthManager13();
|
|
1861
|
+
taskOps = await TaskOperations9.fromAuthManager(authManager);
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
if (error.message.includes("Not authenticated")) {
|
|
1864
|
+
console.error(chalk14.yellow("\nYou need to login first"));
|
|
1865
|
+
console.error(chalk14.gray("Run: vibetasks login\n"));
|
|
1866
|
+
} else {
|
|
1867
|
+
console.error(chalk14.red(`
|
|
1868
|
+
Error: ${error.message}
|
|
1869
|
+
`));
|
|
1870
|
+
}
|
|
1871
|
+
process.exit(1);
|
|
1872
|
+
}
|
|
1873
|
+
let projectTag = options.project;
|
|
1874
|
+
if (!projectTag) {
|
|
1875
|
+
try {
|
|
1876
|
+
const detected = await detectProject4(process.cwd());
|
|
1877
|
+
projectTag = detected.name;
|
|
1878
|
+
if (!options.quiet) {
|
|
1879
|
+
console.log(chalk14.gray(`Project: ${chalk14.white(projectTag)}`));
|
|
1880
|
+
}
|
|
1881
|
+
} catch {
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const recentErrors = /* @__PURE__ */ new Set();
|
|
1885
|
+
const handleError = async (error) => {
|
|
1886
|
+
const errorHash = `${error.type}:${error.message.substring(0, 50)}`;
|
|
1887
|
+
if (recentErrors.has(errorHash)) {
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
console.log();
|
|
1891
|
+
console.log(chalk14.red.bold("Error detected in clipboard!"));
|
|
1892
|
+
console.log(chalk14.gray("\u2500".repeat(50)));
|
|
1893
|
+
console.log();
|
|
1894
|
+
console.log(chalk14.yellow(getErrorSummary(error)));
|
|
1895
|
+
console.log();
|
|
1896
|
+
if (error.suggestion) {
|
|
1897
|
+
console.log(chalk14.cyan("Suggestion: ") + error.suggestion);
|
|
1898
|
+
console.log();
|
|
1899
|
+
}
|
|
1900
|
+
if (options.auto) {
|
|
1901
|
+
await createTaskFromError(error, taskOps, projectTag);
|
|
1902
|
+
recentErrors.add(errorHash);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
const { action } = await inquirer6.prompt([
|
|
1906
|
+
{
|
|
1907
|
+
type: "list",
|
|
1908
|
+
name: "action",
|
|
1909
|
+
message: "What would you like to do?",
|
|
1910
|
+
choices: [
|
|
1911
|
+
{ name: "Create task from this error", value: "create" },
|
|
1912
|
+
{ name: "Show full error", value: "show" },
|
|
1913
|
+
{ name: "Ignore", value: "ignore" },
|
|
1914
|
+
{ name: "Stop watching", value: "stop" }
|
|
1915
|
+
]
|
|
1916
|
+
}
|
|
1917
|
+
]);
|
|
1918
|
+
switch (action) {
|
|
1919
|
+
case "create":
|
|
1920
|
+
await createTaskFromError(error, taskOps, projectTag);
|
|
1921
|
+
recentErrors.add(errorHash);
|
|
1922
|
+
break;
|
|
1923
|
+
case "show":
|
|
1924
|
+
console.log();
|
|
1925
|
+
console.log(chalk14.gray("\u2500".repeat(50)));
|
|
1926
|
+
console.log(error.rawText);
|
|
1927
|
+
console.log(chalk14.gray("\u2500".repeat(50)));
|
|
1928
|
+
console.log();
|
|
1929
|
+
const { afterShow } = await inquirer6.prompt([
|
|
1930
|
+
{
|
|
1931
|
+
type: "list",
|
|
1932
|
+
name: "afterShow",
|
|
1933
|
+
message: "Now what?",
|
|
1934
|
+
choices: [
|
|
1935
|
+
{ name: "Create task from this error", value: "create" },
|
|
1936
|
+
{ name: "Ignore", value: "ignore" }
|
|
1937
|
+
]
|
|
1938
|
+
}
|
|
1939
|
+
]);
|
|
1940
|
+
if (afterShow === "create") {
|
|
1941
|
+
await createTaskFromError(error, taskOps, projectTag);
|
|
1942
|
+
recentErrors.add(errorHash);
|
|
1943
|
+
}
|
|
1944
|
+
break;
|
|
1945
|
+
case "stop":
|
|
1946
|
+
monitor.stop();
|
|
1947
|
+
process.exit(0);
|
|
1948
|
+
break;
|
|
1949
|
+
case "ignore":
|
|
1950
|
+
default:
|
|
1951
|
+
if (!options.quiet) {
|
|
1952
|
+
console.log(chalk14.gray("Error ignored, continuing to watch..."));
|
|
1953
|
+
}
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
console.log();
|
|
1957
|
+
};
|
|
1958
|
+
const monitor = createClipboardMonitor({
|
|
1959
|
+
interval,
|
|
1960
|
+
onError: handleError,
|
|
1961
|
+
onStart: () => {
|
|
1962
|
+
if (!options.quiet) {
|
|
1963
|
+
console.log(chalk14.green("Started watching clipboard"));
|
|
1964
|
+
console.log(chalk14.gray(`Interval: ${interval}ms`));
|
|
1965
|
+
if (options.auto) {
|
|
1966
|
+
console.log(chalk14.yellow("Auto-create mode: ON"));
|
|
1967
|
+
}
|
|
1968
|
+
console.log();
|
|
1969
|
+
console.log(chalk14.gray("Press Ctrl+C to stop"));
|
|
1970
|
+
console.log();
|
|
1971
|
+
}
|
|
1972
|
+
},
|
|
1973
|
+
onStop: () => {
|
|
1974
|
+
if (!options.quiet) {
|
|
1975
|
+
console.log();
|
|
1976
|
+
console.log(chalk14.yellow("Stopped watching clipboard"));
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
process.on("SIGINT", () => {
|
|
1981
|
+
monitor.stop();
|
|
1982
|
+
process.exit(0);
|
|
1983
|
+
});
|
|
1984
|
+
process.on("SIGTERM", () => {
|
|
1985
|
+
monitor.stop();
|
|
1986
|
+
process.exit(0);
|
|
1987
|
+
});
|
|
1988
|
+
await monitor.start();
|
|
1989
|
+
await new Promise(() => {
|
|
1990
|
+
});
|
|
1991
|
+
});
|
|
1992
|
+
async function createTaskFromError(error, taskOps, projectTag) {
|
|
1993
|
+
const spinner = ora11("Creating task...").start();
|
|
1994
|
+
try {
|
|
1995
|
+
const title = generateTaskTitle(error);
|
|
1996
|
+
const task = await taskOps.createTask({
|
|
1997
|
+
title,
|
|
1998
|
+
notes: formatErrorForNotes(error),
|
|
1999
|
+
notes_format: "markdown",
|
|
2000
|
+
priority: error.category === "typescript" ? "medium" : "high",
|
|
2001
|
+
project_tag: projectTag,
|
|
2002
|
+
created_by: "human",
|
|
2003
|
+
// User triggered this
|
|
2004
|
+
status: "todo"
|
|
2005
|
+
});
|
|
2006
|
+
const tagName = `error-${error.category}`;
|
|
2007
|
+
try {
|
|
2008
|
+
const tag = await taskOps.findOrCreateTag(tagName, getTagColor(error.category));
|
|
2009
|
+
await taskOps.linkTaskTags(task.id, [tag.id]);
|
|
2010
|
+
} catch {
|
|
2011
|
+
}
|
|
2012
|
+
spinner.succeed(chalk14.green("Task created!"));
|
|
2013
|
+
console.log(chalk14.gray(`ID: ${task.id.substring(0, 8)}...`));
|
|
2014
|
+
console.log(chalk14.white.bold(title));
|
|
2015
|
+
} catch (err) {
|
|
2016
|
+
spinner.fail(chalk14.red("Failed to create task"));
|
|
2017
|
+
console.error(chalk14.red(`Error: ${err.message}`));
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
function generateTaskTitle(error) {
|
|
2021
|
+
const prefix = getCategoryPrefix(error.category);
|
|
2022
|
+
let title = `${prefix} ${error.type}`;
|
|
2023
|
+
if (error.message && error.message.length < 60) {
|
|
2024
|
+
title += `: ${error.message}`;
|
|
2025
|
+
} else if (error.message) {
|
|
2026
|
+
const shortMessage = error.message.split(/[.!?\n]/)[0].trim();
|
|
2027
|
+
if (shortMessage.length < 60) {
|
|
2028
|
+
title += `: ${shortMessage}`;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
if (error.file) {
|
|
2032
|
+
const fileName = error.file.split(/[/\\]/).pop();
|
|
2033
|
+
if (fileName && title.length + fileName.length < 100) {
|
|
2034
|
+
title += ` in ${fileName}`;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
if (title.length > 120) {
|
|
2038
|
+
title = title.substring(0, 117) + "...";
|
|
2039
|
+
}
|
|
2040
|
+
return title;
|
|
2041
|
+
}
|
|
2042
|
+
function getCategoryPrefix(category) {
|
|
2043
|
+
const prefixes = {
|
|
2044
|
+
nodejs: "Fix",
|
|
2045
|
+
npm: "Resolve npm",
|
|
2046
|
+
expo: "Fix Expo",
|
|
2047
|
+
"react-native": "Fix RN",
|
|
2048
|
+
webpack: "Fix build",
|
|
2049
|
+
typescript: "Fix TS",
|
|
2050
|
+
python: "Fix Python",
|
|
2051
|
+
generic: "Fix"
|
|
2052
|
+
};
|
|
2053
|
+
return prefixes[category] || "Fix";
|
|
2054
|
+
}
|
|
2055
|
+
function getTagColor(category) {
|
|
2056
|
+
const colors = {
|
|
2057
|
+
nodejs: "#68A063",
|
|
2058
|
+
// Node green
|
|
2059
|
+
npm: "#CB3837",
|
|
2060
|
+
// npm red
|
|
2061
|
+
expo: "#000020",
|
|
2062
|
+
// Expo dark
|
|
2063
|
+
"react-native": "#61DAFB",
|
|
2064
|
+
// React blue
|
|
2065
|
+
webpack: "#8DD6F9",
|
|
2066
|
+
// Webpack blue
|
|
2067
|
+
typescript: "#3178C6",
|
|
2068
|
+
// TS blue
|
|
2069
|
+
python: "#3776AB",
|
|
2070
|
+
// Python blue
|
|
2071
|
+
generic: "#FF6B6B"
|
|
2072
|
+
// Generic red
|
|
2073
|
+
};
|
|
2074
|
+
return colors[category] || "#FF6B6B";
|
|
2075
|
+
}
|
|
2076
|
+
var checkCommand = new Command13("check-clipboard").description("Check clipboard once for errors").action(async () => {
|
|
2077
|
+
console.log(chalk14.gray("Checking clipboard..."));
|
|
2078
|
+
const error = await checkClipboardForError();
|
|
2079
|
+
if (!error) {
|
|
2080
|
+
console.log(chalk14.green("No errors detected in clipboard."));
|
|
2081
|
+
process.exit(0);
|
|
2082
|
+
}
|
|
2083
|
+
console.log();
|
|
2084
|
+
console.log(chalk14.red.bold("Error detected!"));
|
|
2085
|
+
console.log(chalk14.gray("\u2500".repeat(50)));
|
|
2086
|
+
console.log();
|
|
2087
|
+
console.log(chalk14.yellow(getErrorSummary(error)));
|
|
2088
|
+
console.log();
|
|
2089
|
+
if (error.suggestion) {
|
|
2090
|
+
console.log(chalk14.cyan("Suggestion: ") + error.suggestion);
|
|
2091
|
+
}
|
|
2092
|
+
console.log();
|
|
2093
|
+
console.log(chalk14.gray("Use `vibetasks watch` to monitor continuously."));
|
|
2094
|
+
process.exit(0);
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
// src/commands/archive.ts
|
|
2098
|
+
import { Command as Command14 } from "commander";
|
|
2099
|
+
import chalk15 from "chalk";
|
|
2100
|
+
import ora12 from "ora";
|
|
2101
|
+
import inquirer7 from "inquirer";
|
|
2102
|
+
import { AuthManager as AuthManager14, TaskOperations as TaskOperations10 } from "@vibetasks/core";
|
|
2103
|
+
var archiveCommand = new Command14("archive").description("Archive a task or manage archived tasks").argument("[task-id]", "Task ID to archive (full or first 8 characters)").option("-l, --list", "List all archived tasks").option("-u, --unarchive <id>", "Unarchive a task by ID").option("--pick", "Pick from completed tasks to archive").action(async (taskId, options) => {
|
|
2104
|
+
const spinner = ora12();
|
|
2105
|
+
try {
|
|
2106
|
+
const authManager = new AuthManager14();
|
|
2107
|
+
const taskOps = await TaskOperations10.fromAuthManager(authManager);
|
|
2108
|
+
if (options.list) {
|
|
2109
|
+
spinner.start("Fetching archived tasks...");
|
|
2110
|
+
const archivedTasks = await taskOps.getArchivedTasks();
|
|
2111
|
+
spinner.stop();
|
|
2112
|
+
if (archivedTasks.length === 0) {
|
|
2113
|
+
console.log(chalk15.yellow("\nNo archived tasks found.\n"));
|
|
2114
|
+
process.exit(0);
|
|
2115
|
+
}
|
|
2116
|
+
console.log(chalk15.white("\nArchived Tasks:\n"));
|
|
2117
|
+
archivedTasks.forEach((task2, index) => {
|
|
2118
|
+
const archivedDate = task2.archived_at ? new Date(task2.archived_at).toLocaleDateString() : "Unknown";
|
|
2119
|
+
console.log(
|
|
2120
|
+
chalk15.gray(`${index + 1}. `) + chalk15.white(task2.title) + chalk15.gray(` [${task2.id.slice(0, 8)}]`) + chalk15.gray(` - Archived: ${archivedDate}`)
|
|
2121
|
+
);
|
|
2122
|
+
});
|
|
2123
|
+
console.log(chalk15.gray(`
|
|
2124
|
+
Total: ${archivedTasks.length} archived tasks
|
|
2125
|
+
`));
|
|
2126
|
+
console.log(chalk15.gray("To restore a task: vibetasks archive --unarchive <id>\n"));
|
|
2127
|
+
process.exit(0);
|
|
2128
|
+
}
|
|
2129
|
+
if (options.unarchive) {
|
|
2130
|
+
spinner.start("Restoring task...");
|
|
2131
|
+
let targetId2 = options.unarchive;
|
|
2132
|
+
if (targetId2.length < 32) {
|
|
2133
|
+
const archivedTasks = await taskOps.getArchivedTasks();
|
|
2134
|
+
const matchingTask = archivedTasks.find((t) => t.id.startsWith(targetId2));
|
|
2135
|
+
if (!matchingTask) {
|
|
2136
|
+
spinner.fail(chalk15.red("Archived task not found"));
|
|
2137
|
+
console.error(chalk15.gray(`
|
|
2138
|
+
No archived task found with ID starting with: ${targetId2}
|
|
2139
|
+
`));
|
|
2140
|
+
process.exit(1);
|
|
2141
|
+
}
|
|
2142
|
+
targetId2 = matchingTask.id;
|
|
2143
|
+
}
|
|
2144
|
+
const task2 = await taskOps.unarchiveTask(targetId2);
|
|
2145
|
+
spinner.succeed(chalk15.green("Task restored!"));
|
|
2146
|
+
console.log(chalk15.gray(`
|
|
2147
|
+
Restored: ${task2.title}`));
|
|
2148
|
+
console.log(chalk15.gray(`Status set to: done
|
|
2149
|
+
`));
|
|
2150
|
+
process.exit(0);
|
|
2151
|
+
}
|
|
2152
|
+
if (options.pick || !taskId) {
|
|
2153
|
+
const allTasks = await taskOps.getTasks("completed");
|
|
2154
|
+
if (allTasks.length === 0) {
|
|
2155
|
+
console.log(chalk15.yellow("\nNo completed tasks to archive. Complete some tasks first!\n"));
|
|
2156
|
+
process.exit(0);
|
|
2157
|
+
}
|
|
2158
|
+
const { selectedTask } = await inquirer7.prompt([{
|
|
2159
|
+
type: "list",
|
|
2160
|
+
name: "selectedTask",
|
|
2161
|
+
message: "Pick a completed task to archive:",
|
|
2162
|
+
choices: allTasks.map((t) => ({
|
|
2163
|
+
name: `${t.title}${t.completed_at ? chalk15.gray(` - Completed: ${new Date(t.completed_at).toLocaleDateString()}`) : ""}`,
|
|
2164
|
+
value: t.id
|
|
2165
|
+
}))
|
|
2166
|
+
}]);
|
|
2167
|
+
taskId = selectedTask;
|
|
2168
|
+
}
|
|
2169
|
+
if (!taskId) {
|
|
2170
|
+
console.log(chalk15.yellow("\nUsage: vibetasks archive <task-id>"));
|
|
2171
|
+
console.log(chalk15.gray(" or: vibetasks archive --pick"));
|
|
2172
|
+
console.log(chalk15.gray(" or: vibetasks archive --list"));
|
|
2173
|
+
console.log(chalk15.gray(" or: vibetasks archive --unarchive <id>\n"));
|
|
2174
|
+
process.exit(1);
|
|
2175
|
+
}
|
|
2176
|
+
spinner.start("Archiving task...");
|
|
2177
|
+
let targetId = taskId;
|
|
2178
|
+
if (taskId.length < 32) {
|
|
2179
|
+
const allTasks = await taskOps.getTasks("all", true);
|
|
2180
|
+
const matchingTask = allTasks.find((t) => t.id.startsWith(taskId));
|
|
2181
|
+
if (!matchingTask) {
|
|
2182
|
+
spinner.fail(chalk15.red("Task not found"));
|
|
2183
|
+
console.error(chalk15.gray(`
|
|
2184
|
+
No task found with ID starting with: ${taskId}
|
|
2185
|
+
`));
|
|
2186
|
+
process.exit(1);
|
|
2187
|
+
}
|
|
2188
|
+
targetId = matchingTask.id;
|
|
2189
|
+
}
|
|
2190
|
+
const task = await taskOps.archiveTask(targetId);
|
|
2191
|
+
spinner.succeed(chalk15.green("Task archived!"));
|
|
2192
|
+
console.log(chalk15.gray(`
|
|
2193
|
+
Archived: ${task.title}`));
|
|
2194
|
+
console.log(chalk15.gray(`Archived at: ${(/* @__PURE__ */ new Date()).toLocaleString()}`));
|
|
2195
|
+
console.log(chalk15.gray("\nTo view archived tasks: vibetasks archive --list"));
|
|
2196
|
+
console.log(chalk15.gray(`To restore: vibetasks archive --unarchive ${targetId.slice(0, 8)}
|
|
2197
|
+
`));
|
|
2198
|
+
process.exit(0);
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
spinner.fail(chalk15.red("Operation failed"));
|
|
2201
|
+
if (error.message.includes("Not authenticated")) {
|
|
2202
|
+
console.error(chalk15.yellow("\nYou need to login first"));
|
|
2203
|
+
console.error(chalk15.gray("Run: vibetasks login\n"));
|
|
2204
|
+
} else {
|
|
2205
|
+
console.error(chalk15.red(`
|
|
2206
|
+
Error: ${error.message}
|
|
2207
|
+
`));
|
|
2208
|
+
}
|
|
2209
|
+
process.exit(1);
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
|
|
2213
|
+
// src/commands/daemon.ts
|
|
2214
|
+
import { Command as Command15 } from "commander";
|
|
2215
|
+
import chalk16 from "chalk";
|
|
2216
|
+
import ora13 from "ora";
|
|
2217
|
+
import { spawn } from "child_process";
|
|
2218
|
+
import path3 from "path";
|
|
2219
|
+
import { fileURLToPath } from "url";
|
|
2220
|
+
import { AuthManager as AuthManager15, TaskOperations as TaskOperations11 } from "@vibetasks/core";
|
|
2221
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
2222
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
2223
|
+
var daemonCommand = new Command15("daemon").description("Manage the VibeTasks error capture daemon").addCommand(createStartCommand()).addCommand(createStopCommand()).addCommand(createStatusCommand()).addCommand(createConfigureCommand()).addCommand(createCaptureCommand());
|
|
2224
|
+
function createStartCommand() {
|
|
2225
|
+
return new Command15("start").description("Start the error capture daemon").option("-f, --foreground", "Run in foreground (useful for debugging)").option("--no-notify", "Disable startup notification").action(async (options) => {
|
|
2226
|
+
const spinner = ora13("Starting VibeTasks daemon...").start();
|
|
2227
|
+
try {
|
|
2228
|
+
if (await daemonConfigManager.isDaemonRunning()) {
|
|
2229
|
+
spinner.warn(chalk16.yellow("Daemon is already running"));
|
|
2230
|
+
const pid = await daemonConfigManager.getPid();
|
|
2231
|
+
console.log(chalk16.gray(`PID: ${pid}`));
|
|
2232
|
+
process.exit(0);
|
|
2233
|
+
}
|
|
2234
|
+
const config = await daemonConfigManager.getConfig();
|
|
2235
|
+
if (!config.enabled) {
|
|
2236
|
+
spinner.fail(chalk16.red("Daemon is disabled in configuration"));
|
|
2237
|
+
console.log(chalk16.gray("Run: vibetasks daemon configure --enable"));
|
|
2238
|
+
process.exit(1);
|
|
2239
|
+
}
|
|
2240
|
+
if (options.foreground) {
|
|
2241
|
+
spinner.succeed(chalk16.green("Starting daemon in foreground mode"));
|
|
2242
|
+
console.log(chalk16.gray("Press Ctrl+C to stop\n"));
|
|
2243
|
+
await runDaemonForeground(config);
|
|
2244
|
+
} else {
|
|
2245
|
+
const daemonProcess = await startDaemonBackground(config);
|
|
2246
|
+
if (daemonProcess && daemonProcess.pid) {
|
|
2247
|
+
await daemonConfigManager.savePid(daemonProcess.pid);
|
|
2248
|
+
spinner.succeed(chalk16.green("VibeTasks daemon started"));
|
|
2249
|
+
console.log(chalk16.gray(`PID: ${daemonProcess.pid}`));
|
|
2250
|
+
console.log(chalk16.cyan(`Keyboard shortcut: ${config.keyboard_shortcut}`));
|
|
2251
|
+
console.log(chalk16.gray("\nThe daemon is now running in the background."));
|
|
2252
|
+
console.log(chalk16.gray('Use "vibetasks daemon stop" to stop it.'));
|
|
2253
|
+
} else {
|
|
2254
|
+
throw new Error("Failed to start daemon process");
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
process.exit(0);
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
spinner.fail(chalk16.red("Failed to start daemon"));
|
|
2260
|
+
console.error(chalk16.red(`Error: ${error.message}`));
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
function createStopCommand() {
|
|
2266
|
+
return new Command15("stop").description("Stop the error capture daemon").action(async () => {
|
|
2267
|
+
const spinner = ora13("Stopping VibeTasks daemon...").start();
|
|
2268
|
+
try {
|
|
2269
|
+
const pid = await daemonConfigManager.getPid();
|
|
2270
|
+
if (!pid) {
|
|
2271
|
+
spinner.warn(chalk16.yellow("Daemon is not running"));
|
|
2272
|
+
process.exit(0);
|
|
2273
|
+
}
|
|
2274
|
+
try {
|
|
2275
|
+
process.kill(pid, "SIGTERM");
|
|
2276
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2277
|
+
try {
|
|
2278
|
+
process.kill(pid, 0);
|
|
2279
|
+
process.kill(pid, "SIGKILL");
|
|
2280
|
+
} catch {
|
|
2281
|
+
}
|
|
2282
|
+
await daemonConfigManager.removePid();
|
|
2283
|
+
spinner.succeed(chalk16.green("VibeTasks daemon stopped"));
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
if (error.code === "ESRCH") {
|
|
2286
|
+
await daemonConfigManager.removePid();
|
|
2287
|
+
spinner.warn(chalk16.yellow("Daemon was not running (stale PID file removed)"));
|
|
2288
|
+
} else {
|
|
2289
|
+
throw error;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
process.exit(0);
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
spinner.fail(chalk16.red("Failed to stop daemon"));
|
|
2295
|
+
console.error(chalk16.red(`Error: ${error.message}`));
|
|
2296
|
+
process.exit(1);
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
function createStatusCommand() {
|
|
2301
|
+
return new Command15("status").description("Check daemon status").action(async () => {
|
|
2302
|
+
try {
|
|
2303
|
+
const isRunning = await daemonConfigManager.isDaemonRunning();
|
|
2304
|
+
const config = await daemonConfigManager.getConfig();
|
|
2305
|
+
const pid = await daemonConfigManager.getPid();
|
|
2306
|
+
console.log(chalk16.blue.bold("\nVibeTasks Daemon Status\n"));
|
|
2307
|
+
if (isRunning) {
|
|
2308
|
+
console.log(chalk16.green(" Status: Running"));
|
|
2309
|
+
console.log(chalk16.gray(` PID: ${pid}`));
|
|
2310
|
+
} else {
|
|
2311
|
+
console.log(chalk16.yellow(" Status: Stopped"));
|
|
2312
|
+
}
|
|
2313
|
+
console.log(chalk16.gray(` Enabled: ${config.enabled ? "Yes" : "No"}`));
|
|
2314
|
+
console.log(chalk16.gray(` Keyboard Shortcut: ${config.keyboard_shortcut}`));
|
|
2315
|
+
console.log(chalk16.gray(` Auto-start: ${config.auto_start ? "Yes" : "No"}`));
|
|
2316
|
+
console.log(chalk16.gray(` Auto-create tasks: ${config.auto_create_task ? "Yes" : "No"}`));
|
|
2317
|
+
console.log(chalk16.gray(` Config path: ${daemonConfigManager.getConfigDir()}`));
|
|
2318
|
+
console.log();
|
|
2319
|
+
process.exit(0);
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
console.error(chalk16.red(`Error: ${error.message}`));
|
|
2322
|
+
process.exit(1);
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
function createConfigureCommand() {
|
|
2327
|
+
return new Command15("configure").description("Configure daemon settings").option("--enable", "Enable the daemon").option("--disable", "Disable the daemon").option("--shortcut <keys>", 'Set keyboard shortcut (e.g., "Ctrl+Alt+E")').option("--auto-start <bool>", "Enable/disable auto-start on login").option("--auto-task <bool>", "Automatically create tasks from errors").option("--notification-duration <ms>", "Notification display duration in ms").option("--reset", "Reset to default configuration").action(async (options) => {
|
|
2328
|
+
try {
|
|
2329
|
+
if (options.reset) {
|
|
2330
|
+
const { DEFAULT_DAEMON_CONFIG } = await import("../daemon-config-EUSBQA4E.js");
|
|
2331
|
+
await daemonConfigManager.saveConfig(DEFAULT_DAEMON_CONFIG);
|
|
2332
|
+
console.log(chalk16.green("\nConfiguration reset to defaults\n"));
|
|
2333
|
+
process.exit(0);
|
|
2334
|
+
}
|
|
2335
|
+
const updates = {};
|
|
2336
|
+
if (options.enable) updates.enabled = true;
|
|
2337
|
+
if (options.disable) updates.enabled = false;
|
|
2338
|
+
if (options.shortcut) updates.keyboard_shortcut = options.shortcut;
|
|
2339
|
+
if (options.autoStart !== void 0) updates.auto_start = options.autoStart === "true";
|
|
2340
|
+
if (options.autoTask !== void 0) updates.auto_create_task = options.autoTask === "true";
|
|
2341
|
+
if (options.notificationDuration) updates.notification_duration = parseInt(options.notificationDuration, 10);
|
|
2342
|
+
if (Object.keys(updates).length === 0) {
|
|
2343
|
+
const config = await daemonConfigManager.getConfig();
|
|
2344
|
+
console.log(chalk16.blue.bold("\nDaemon Configuration\n"));
|
|
2345
|
+
console.log(JSON.stringify(config, null, 2));
|
|
2346
|
+
console.log();
|
|
2347
|
+
} else {
|
|
2348
|
+
await daemonConfigManager.saveConfig(updates);
|
|
2349
|
+
console.log(chalk16.green("\nConfiguration updated\n"));
|
|
2350
|
+
if (await daemonConfigManager.isDaemonRunning()) {
|
|
2351
|
+
console.log(chalk16.yellow("Note: Restart the daemon for changes to take effect"));
|
|
2352
|
+
console.log(chalk16.gray("Run: vibetasks daemon stop && vibetasks daemon start\n"));
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
process.exit(0);
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
console.error(chalk16.red(`Error: ${error.message}`));
|
|
2358
|
+
process.exit(1);
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
function createCaptureCommand() {
|
|
2363
|
+
return new Command15("capture").description("Manually capture clipboard and check for errors").option("--create-task", "Create a task from the captured error").option("--raw", "Show raw clipboard content").action(async (options) => {
|
|
2364
|
+
const spinner = ora13("Capturing clipboard...").start();
|
|
2365
|
+
try {
|
|
2366
|
+
const clipboard = await import("clipboardy");
|
|
2367
|
+
const content = await clipboard.default.read();
|
|
2368
|
+
spinner.stop();
|
|
2369
|
+
if (!content || content.trim().length === 0) {
|
|
2370
|
+
console.log(chalk16.yellow("\nClipboard is empty\n"));
|
|
2371
|
+
process.exit(0);
|
|
2372
|
+
}
|
|
2373
|
+
if (options.raw) {
|
|
2374
|
+
console.log(chalk16.blue.bold("\nClipboard Content:\n"));
|
|
2375
|
+
console.log(content);
|
|
2376
|
+
console.log();
|
|
2377
|
+
}
|
|
2378
|
+
const error = detectError(content);
|
|
2379
|
+
if (!error) {
|
|
2380
|
+
console.log(chalk16.yellow("\nNo error detected in clipboard content\n"));
|
|
2381
|
+
if (!options.raw) {
|
|
2382
|
+
console.log(chalk16.gray("First 200 characters:"));
|
|
2383
|
+
console.log(chalk16.gray(content.substring(0, 200) + (content.length > 200 ? "..." : "")));
|
|
2384
|
+
console.log();
|
|
2385
|
+
}
|
|
2386
|
+
process.exit(0);
|
|
2387
|
+
}
|
|
2388
|
+
console.log(chalk16.red.bold("\nError Detected!\n"));
|
|
2389
|
+
console.log(chalk16.cyan("Type: ") + chalk16.white(error.type));
|
|
2390
|
+
console.log(chalk16.cyan("Category: ") + chalk16.white(error.category));
|
|
2391
|
+
console.log(chalk16.cyan("Message: ") + chalk16.white(error.message));
|
|
2392
|
+
if (error.file) {
|
|
2393
|
+
console.log(chalk16.cyan("File: ") + chalk16.white(error.file));
|
|
2394
|
+
if (error.line) {
|
|
2395
|
+
console.log(chalk16.cyan("Line: ") + chalk16.white(error.line.toString()) + (error.column ? chalk16.gray(`:${error.column}`) : ""));
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (error.suggestion) {
|
|
2399
|
+
console.log(chalk16.green("\nSuggestion: ") + chalk16.white(error.suggestion));
|
|
2400
|
+
}
|
|
2401
|
+
if (error.stack) {
|
|
2402
|
+
console.log(chalk16.cyan("\nStack Trace (first 5 lines):"));
|
|
2403
|
+
const stackLines = error.stack.split("\n").slice(0, 5);
|
|
2404
|
+
stackLines.forEach((line) => {
|
|
2405
|
+
console.log(chalk16.gray(" " + line));
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
if (options.createTask) {
|
|
2409
|
+
console.log();
|
|
2410
|
+
await createTaskFromError2(error);
|
|
2411
|
+
}
|
|
2412
|
+
console.log();
|
|
2413
|
+
process.exit(0);
|
|
2414
|
+
} catch (error) {
|
|
2415
|
+
spinner.fail(chalk16.red("Failed to capture clipboard"));
|
|
2416
|
+
console.error(chalk16.red(`Error: ${error.message}`));
|
|
2417
|
+
process.exit(1);
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
async function startDaemonBackground(config) {
|
|
2422
|
+
const workerPath = path3.resolve(__dirname2, "..", "daemon-worker.js");
|
|
2423
|
+
const child = spawn(process.execPath, [workerPath], {
|
|
2424
|
+
detached: true,
|
|
2425
|
+
stdio: "ignore",
|
|
2426
|
+
env: {
|
|
2427
|
+
...process.env,
|
|
2428
|
+
VIBETASKS_DAEMON: "true",
|
|
2429
|
+
VIBETASKS_SHORTCUT: config.keyboard_shortcut
|
|
2430
|
+
}
|
|
2431
|
+
});
|
|
2432
|
+
child.unref();
|
|
2433
|
+
return child;
|
|
2434
|
+
}
|
|
2435
|
+
async function runDaemonForeground(config) {
|
|
2436
|
+
console.log(chalk16.blue.bold("VibeTasks Error Capture Daemon"));
|
|
2437
|
+
console.log(chalk16.gray(`Keyboard shortcut: ${config.keyboard_shortcut}`));
|
|
2438
|
+
console.log(chalk16.gray("Listening for keyboard shortcut...\n"));
|
|
2439
|
+
let keyListener;
|
|
2440
|
+
try {
|
|
2441
|
+
const { GlobalKeyboardListener } = await import("node-global-key-listener");
|
|
2442
|
+
keyListener = new GlobalKeyboardListener();
|
|
2443
|
+
console.log(chalk16.green("Using node-global-key-listener"));
|
|
2444
|
+
const shortcut = daemonConfigManager.parseShortcut(config.keyboard_shortcut);
|
|
2445
|
+
let modifiersPressed = { ctrl: false, alt: false, shift: false };
|
|
2446
|
+
keyListener.addListener((event, down) => {
|
|
2447
|
+
const keyName = event.name?.toUpperCase() || "";
|
|
2448
|
+
if (event.name === "LEFT CTRL" || event.name === "RIGHT CTRL") {
|
|
2449
|
+
modifiersPressed.ctrl = event.state === "DOWN";
|
|
2450
|
+
}
|
|
2451
|
+
if (event.name === "LEFT ALT" || event.name === "RIGHT ALT") {
|
|
2452
|
+
modifiersPressed.alt = event.state === "DOWN";
|
|
2453
|
+
}
|
|
2454
|
+
if (event.name === "LEFT SHIFT" || event.name === "RIGHT SHIFT") {
|
|
2455
|
+
modifiersPressed.shift = event.state === "DOWN";
|
|
2456
|
+
}
|
|
2457
|
+
if (event.state === "DOWN" && keyName === shortcut.key.toUpperCase() && modifiersPressed.ctrl === shortcut.ctrl && modifiersPressed.alt === shortcut.alt && modifiersPressed.shift === shortcut.shift) {
|
|
2458
|
+
console.log(chalk16.cyan(`
|
|
2459
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Shortcut triggered!`));
|
|
2460
|
+
handleShortcutTriggered(config).catch((err) => {
|
|
2461
|
+
console.error(chalk16.red(`Error handling shortcut: ${err.message}`));
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
console.log(chalk16.yellow(`Note: node-global-key-listener not available: ${error.message}`));
|
|
2467
|
+
console.log(chalk16.gray("Install with: npm install node-global-key-listener"));
|
|
2468
|
+
console.log(chalk16.gray("\nFalling back to manual capture mode."));
|
|
2469
|
+
console.log(chalk16.gray('Use "vibetasks daemon capture" to manually capture clipboard.\n'));
|
|
2470
|
+
await new Promise(() => {
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
process.on("SIGINT", () => {
|
|
2474
|
+
console.log(chalk16.yellow("\nShutting down daemon..."));
|
|
2475
|
+
if (keyListener && keyListener.kill) {
|
|
2476
|
+
keyListener.kill();
|
|
2477
|
+
}
|
|
2478
|
+
process.exit(0);
|
|
2479
|
+
});
|
|
2480
|
+
process.on("SIGTERM", () => {
|
|
2481
|
+
console.log(chalk16.yellow("\nReceived SIGTERM, shutting down..."));
|
|
2482
|
+
if (keyListener && keyListener.kill) {
|
|
2483
|
+
keyListener.kill();
|
|
2484
|
+
}
|
|
2485
|
+
process.exit(0);
|
|
2486
|
+
});
|
|
2487
|
+
await new Promise(() => {
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
async function handleShortcutTriggered(config) {
|
|
2491
|
+
try {
|
|
2492
|
+
const clipboard = await import("clipboardy");
|
|
2493
|
+
const content = await clipboard.default.read();
|
|
2494
|
+
if (!content || content.trim().length === 0) {
|
|
2495
|
+
await showNotification("VibeTasks", "Clipboard is empty", "info");
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
const error = detectError(content);
|
|
2499
|
+
if (!error) {
|
|
2500
|
+
await showNotification("VibeTasks", "No error detected in clipboard", "info");
|
|
2501
|
+
console.log(chalk16.gray("Clipboard content did not match error patterns"));
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const summary = getErrorSummary(error);
|
|
2505
|
+
console.log(chalk16.red("Error detected:"));
|
|
2506
|
+
console.log(chalk16.white(summary));
|
|
2507
|
+
await showNotification(
|
|
2508
|
+
"VibeTasks: Error Detected",
|
|
2509
|
+
summary,
|
|
2510
|
+
"error",
|
|
2511
|
+
config.notification_duration
|
|
2512
|
+
);
|
|
2513
|
+
if (config.auto_create_task) {
|
|
2514
|
+
console.log(chalk16.cyan("Creating task..."));
|
|
2515
|
+
await createTaskFromError2(error);
|
|
2516
|
+
}
|
|
2517
|
+
} catch (err) {
|
|
2518
|
+
console.error(chalk16.red(`Error: ${err.message}`));
|
|
2519
|
+
await showNotification("VibeTasks Error", err.message, "error");
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
async function showNotification(title, message, type = "info", duration) {
|
|
2523
|
+
try {
|
|
2524
|
+
const notifier = await import("node-notifier");
|
|
2525
|
+
const options = {
|
|
2526
|
+
title,
|
|
2527
|
+
message: message.substring(0, 256),
|
|
2528
|
+
// Windows limits notification length
|
|
2529
|
+
sound: type === "error",
|
|
2530
|
+
wait: false
|
|
2531
|
+
};
|
|
2532
|
+
if (process.platform === "win32") {
|
|
2533
|
+
options.appID = "VibeTasks";
|
|
2534
|
+
}
|
|
2535
|
+
notifier.default.notify(options);
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
console.log(chalk16.blue(`[Notification] ${title}: ${message}`));
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
function generateTaskTitle2(error) {
|
|
2541
|
+
const prefix = getCategoryPrefix2(error.category);
|
|
2542
|
+
let title = `${prefix} ${error.type}`;
|
|
2543
|
+
if (error.message && error.message.length < 60) {
|
|
2544
|
+
title += `: ${error.message}`;
|
|
2545
|
+
} else if (error.message) {
|
|
2546
|
+
const shortMessage = error.message.split(/[.!?\n]/)[0].trim();
|
|
2547
|
+
if (shortMessage.length < 60) {
|
|
2548
|
+
title += `: ${shortMessage}`;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
if (error.file) {
|
|
2552
|
+
const fileName = error.file.split(/[/\\]/).pop();
|
|
2553
|
+
if (fileName && title.length + fileName.length < 100) {
|
|
2554
|
+
title += ` in ${fileName}`;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
if (title.length > 120) {
|
|
2558
|
+
title = title.substring(0, 117) + "...";
|
|
2559
|
+
}
|
|
2560
|
+
return title;
|
|
2561
|
+
}
|
|
2562
|
+
function getCategoryPrefix2(category) {
|
|
2563
|
+
const prefixes = {
|
|
2564
|
+
nodejs: "Fix",
|
|
2565
|
+
npm: "Resolve npm",
|
|
2566
|
+
expo: "Fix Expo",
|
|
2567
|
+
"react-native": "Fix RN",
|
|
2568
|
+
webpack: "Fix build",
|
|
2569
|
+
typescript: "Fix TS",
|
|
2570
|
+
python: "Fix Python",
|
|
2571
|
+
generic: "Fix"
|
|
2572
|
+
};
|
|
2573
|
+
return prefixes[category] || "Fix";
|
|
2574
|
+
}
|
|
2575
|
+
async function createTaskFromError2(error) {
|
|
2576
|
+
const spinner = ora13("Creating task from error...").start();
|
|
2577
|
+
try {
|
|
2578
|
+
const authManager = new AuthManager15();
|
|
2579
|
+
const taskOps = await TaskOperations11.fromAuthManager(authManager);
|
|
2580
|
+
const title = generateTaskTitle2(error);
|
|
2581
|
+
const notes = formatErrorForNotes(error);
|
|
2582
|
+
const task = await taskOps.createTask({
|
|
2583
|
+
title,
|
|
2584
|
+
notes,
|
|
2585
|
+
notes_format: "markdown",
|
|
2586
|
+
priority: error.category === "typescript" ? "medium" : "high",
|
|
2587
|
+
created_by: "ai",
|
|
2588
|
+
status: "todo"
|
|
2589
|
+
});
|
|
2590
|
+
const tagName = `error-${error.category}`;
|
|
2591
|
+
try {
|
|
2592
|
+
const tag = await taskOps.findOrCreateTag(tagName, getTagColor2(error.category));
|
|
2593
|
+
await taskOps.linkTaskTags(task.id, [tag.id]);
|
|
2594
|
+
} catch {
|
|
2595
|
+
}
|
|
2596
|
+
spinner.succeed(chalk16.green("Task created!"));
|
|
2597
|
+
console.log(chalk16.gray(`ID: ${task.id.substring(0, 8)}...`));
|
|
2598
|
+
console.log(chalk16.white.bold(task.title));
|
|
2599
|
+
await showNotification(
|
|
2600
|
+
"VibeTasks: Task Created",
|
|
2601
|
+
`Created: ${title.substring(0, 100)}`,
|
|
2602
|
+
"success"
|
|
2603
|
+
);
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
spinner.fail(chalk16.red("Failed to create task"));
|
|
2606
|
+
if (err.message.includes("Not authenticated")) {
|
|
2607
|
+
console.log(chalk16.yellow("You need to login first: vibetasks login"));
|
|
2608
|
+
} else {
|
|
2609
|
+
console.log(chalk16.red(`Error: ${err.message}`));
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
function getTagColor2(category) {
|
|
2614
|
+
const colors = {
|
|
2615
|
+
nodejs: "#68A063",
|
|
2616
|
+
// Node green
|
|
2617
|
+
npm: "#CB3837",
|
|
2618
|
+
// npm red
|
|
2619
|
+
expo: "#000020",
|
|
2620
|
+
// Expo dark
|
|
2621
|
+
"react-native": "#61DAFB",
|
|
2622
|
+
// React blue
|
|
2623
|
+
webpack: "#8DD6F9",
|
|
2624
|
+
// Webpack blue
|
|
2625
|
+
typescript: "#3178C6",
|
|
2626
|
+
// TS blue
|
|
2627
|
+
python: "#3776AB",
|
|
2628
|
+
// Python blue
|
|
2629
|
+
generic: "#FF6B6B"
|
|
2630
|
+
// Generic red
|
|
2631
|
+
};
|
|
2632
|
+
return colors[category] || "#FF6B6B";
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
// bin/vibetasks.ts
|
|
2636
|
+
var program = new Command16();
|
|
2637
|
+
program.name("vibetasks").description("VibeTasks CLI - Fast task management for vibers").version("0.2.0");
|
|
2638
|
+
program.addCommand(setupCommand);
|
|
2639
|
+
program.addCommand(loginCommand);
|
|
2640
|
+
program.addCommand(addCommand);
|
|
2641
|
+
program.addCommand(listCommand);
|
|
2642
|
+
program.addCommand(vibingCommand);
|
|
2643
|
+
program.addCommand(doneCommand);
|
|
2644
|
+
program.addCommand(searchCommand);
|
|
2645
|
+
program.addCommand(updateCommand);
|
|
2646
|
+
program.addCommand(deleteCommand);
|
|
2647
|
+
program.addCommand(configCommand);
|
|
2648
|
+
program.addCommand(hooksCommand);
|
|
2649
|
+
program.addCommand(initCommand);
|
|
2650
|
+
program.addCommand(watchCommand);
|
|
2651
|
+
program.addCommand(checkCommand);
|
|
2652
|
+
program.addCommand(archiveCommand);
|
|
2653
|
+
program.addCommand(daemonCommand);
|
|
2654
|
+
program.parse();
|