@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,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectError,
|
|
4
|
+
formatErrorForNotes,
|
|
5
|
+
getErrorSummary
|
|
6
|
+
} from "../chunk-PZF4VRDG.js";
|
|
7
|
+
import {
|
|
8
|
+
daemonConfigManager
|
|
9
|
+
} from "../chunk-2KRLRG4G.js";
|
|
10
|
+
|
|
11
|
+
// src/daemon-worker.ts
|
|
12
|
+
import { AuthManager, TaskOperations } from "@vibetasks/core";
|
|
13
|
+
function log(message, level = "info") {
|
|
14
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15
|
+
const prefix = level === "error" ? "[ERROR]" : level === "warn" ? "[WARN]" : "[INFO]";
|
|
16
|
+
console.log(`${timestamp} ${prefix} ${message}`);
|
|
17
|
+
}
|
|
18
|
+
async function main() {
|
|
19
|
+
log("VibeTasks daemon worker starting...");
|
|
20
|
+
const config = await daemonConfigManager.getConfig();
|
|
21
|
+
const shortcut = daemonConfigManager.parseShortcut(config.keyboard_shortcut);
|
|
22
|
+
log(`Configured shortcut: ${config.keyboard_shortcut}`);
|
|
23
|
+
log(`Parsed: Ctrl=${shortcut.ctrl} Alt=${shortcut.alt} Shift=${shortcut.shift} Key=${shortcut.key}`);
|
|
24
|
+
let keyListener = null;
|
|
25
|
+
try {
|
|
26
|
+
const { GlobalKeyboardListener } = await import("node-global-key-listener");
|
|
27
|
+
keyListener = new GlobalKeyboardListener();
|
|
28
|
+
let modifiersPressed = { ctrl: false, alt: false, shift: false };
|
|
29
|
+
let lastTriggerTime = 0;
|
|
30
|
+
const debounceMs = 500;
|
|
31
|
+
keyListener.addListener((event, down) => {
|
|
32
|
+
const keyName = (event.name || "").toUpperCase();
|
|
33
|
+
if (keyName.includes("CTRL")) {
|
|
34
|
+
modifiersPressed.ctrl = event.state === "DOWN";
|
|
35
|
+
}
|
|
36
|
+
if (keyName.includes("ALT")) {
|
|
37
|
+
modifiersPressed.alt = event.state === "DOWN";
|
|
38
|
+
}
|
|
39
|
+
if (keyName.includes("SHIFT")) {
|
|
40
|
+
modifiersPressed.shift = event.state === "DOWN";
|
|
41
|
+
}
|
|
42
|
+
if (event.state === "DOWN" && keyName === shortcut.key.toUpperCase() && modifiersPressed.ctrl === shortcut.ctrl && modifiersPressed.alt === shortcut.alt && modifiersPressed.shift === shortcut.shift) {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
if (now - lastTriggerTime > debounceMs) {
|
|
45
|
+
lastTriggerTime = now;
|
|
46
|
+
log("Keyboard shortcut triggered!");
|
|
47
|
+
handleShortcutTriggered(config).catch((err) => {
|
|
48
|
+
log(`Error handling shortcut: ${err.message}`, "error");
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
log("Keyboard listener initialized successfully");
|
|
54
|
+
log("Daemon is now listening for keyboard shortcuts...");
|
|
55
|
+
await showNotification(
|
|
56
|
+
"VibeTasks Daemon",
|
|
57
|
+
`Running! Press ${config.keyboard_shortcut} to capture errors.`,
|
|
58
|
+
"info"
|
|
59
|
+
);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
log(`Failed to initialize keyboard listener: ${error.message}`, "error");
|
|
62
|
+
log("The daemon will continue running but keyboard shortcuts will not work.", "warn");
|
|
63
|
+
log("Install node-global-key-listener: npm install node-global-key-listener", "warn");
|
|
64
|
+
}
|
|
65
|
+
process.on("SIGINT", async () => {
|
|
66
|
+
log("Received SIGINT, shutting down...");
|
|
67
|
+
await cleanup(keyListener);
|
|
68
|
+
});
|
|
69
|
+
process.on("SIGTERM", async () => {
|
|
70
|
+
log("Received SIGTERM, shutting down...");
|
|
71
|
+
await cleanup(keyListener);
|
|
72
|
+
});
|
|
73
|
+
process.on("uncaughtException", (error) => {
|
|
74
|
+
log(`Uncaught exception: ${error.message}`, "error");
|
|
75
|
+
log(error.stack || "", "error");
|
|
76
|
+
});
|
|
77
|
+
process.on("unhandledRejection", (reason) => {
|
|
78
|
+
log(`Unhandled rejection: ${reason}`, "error");
|
|
79
|
+
});
|
|
80
|
+
await new Promise(() => {
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function cleanup(keyListener) {
|
|
84
|
+
if (keyListener && keyListener.kill) {
|
|
85
|
+
keyListener.kill();
|
|
86
|
+
}
|
|
87
|
+
await daemonConfigManager.removePid();
|
|
88
|
+
log("Daemon shutdown complete");
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
async function handleShortcutTriggered(config) {
|
|
92
|
+
try {
|
|
93
|
+
const clipboard = await import("clipboardy");
|
|
94
|
+
const content = await clipboard.default.read();
|
|
95
|
+
if (!content || content.trim().length === 0) {
|
|
96
|
+
log("Clipboard is empty");
|
|
97
|
+
await showNotification("VibeTasks", "Clipboard is empty", "info");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
log(`Clipboard captured: ${content.length} characters`);
|
|
101
|
+
const error = detectError(content);
|
|
102
|
+
if (!error) {
|
|
103
|
+
log("No error pattern detected in clipboard");
|
|
104
|
+
await showNotification("VibeTasks", "No error detected in clipboard", "info");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
log(`Error detected: ${error.type} - ${error.message.substring(0, 50)}...`);
|
|
108
|
+
const summary = getErrorSummary(error);
|
|
109
|
+
await showNotification(
|
|
110
|
+
"VibeTasks: Error Detected",
|
|
111
|
+
summary,
|
|
112
|
+
"error",
|
|
113
|
+
config.notification_duration
|
|
114
|
+
);
|
|
115
|
+
if (config.auto_create_task) {
|
|
116
|
+
log("Auto-creating task from error...");
|
|
117
|
+
await createTaskFromError(error);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
log(`Error in shortcut handler: ${err.message}`, "error");
|
|
121
|
+
await showNotification("VibeTasks Error", err.message, "error");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function showNotification(title, message, type = "info", duration) {
|
|
125
|
+
try {
|
|
126
|
+
const notifier = await import("node-notifier");
|
|
127
|
+
const options = {
|
|
128
|
+
title,
|
|
129
|
+
message: message.substring(0, 256),
|
|
130
|
+
sound: type === "error",
|
|
131
|
+
wait: false
|
|
132
|
+
};
|
|
133
|
+
if (process.platform === "win32") {
|
|
134
|
+
options.appID = "VibeTasks";
|
|
135
|
+
}
|
|
136
|
+
notifier.default.notify(options);
|
|
137
|
+
log(`Notification shown: ${title}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
log(`Failed to show notification: ${error.message}`, "warn");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function generateTaskTitle(error) {
|
|
143
|
+
const prefixes = {
|
|
144
|
+
nodejs: "Fix",
|
|
145
|
+
npm: "Resolve npm",
|
|
146
|
+
expo: "Fix Expo",
|
|
147
|
+
"react-native": "Fix RN",
|
|
148
|
+
webpack: "Fix build",
|
|
149
|
+
typescript: "Fix TS",
|
|
150
|
+
python: "Fix Python",
|
|
151
|
+
generic: "Fix"
|
|
152
|
+
};
|
|
153
|
+
const prefix = prefixes[error.category] || "Fix";
|
|
154
|
+
let title = `${prefix} ${error.type}`;
|
|
155
|
+
if (error.message && error.message.length < 60) {
|
|
156
|
+
title += `: ${error.message}`;
|
|
157
|
+
} else if (error.message) {
|
|
158
|
+
const shortMessage = error.message.split(/[.!?\n]/)[0].trim();
|
|
159
|
+
if (shortMessage.length < 60) {
|
|
160
|
+
title += `: ${shortMessage}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (title.length > 120) {
|
|
164
|
+
title = title.substring(0, 117) + "...";
|
|
165
|
+
}
|
|
166
|
+
return title;
|
|
167
|
+
}
|
|
168
|
+
async function createTaskFromError(error) {
|
|
169
|
+
try {
|
|
170
|
+
const authManager = new AuthManager();
|
|
171
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
172
|
+
const title = generateTaskTitle(error);
|
|
173
|
+
const notes = formatErrorForNotes(error);
|
|
174
|
+
const task = await taskOps.createTask({
|
|
175
|
+
title,
|
|
176
|
+
notes,
|
|
177
|
+
notes_format: "markdown",
|
|
178
|
+
priority: error.category === "typescript" ? "medium" : "high",
|
|
179
|
+
created_by: "ai",
|
|
180
|
+
status: "todo"
|
|
181
|
+
});
|
|
182
|
+
log(`Task created: ${task.id}`);
|
|
183
|
+
await showNotification(
|
|
184
|
+
"VibeTasks: Task Created",
|
|
185
|
+
`Created: ${title.substring(0, 100)}`,
|
|
186
|
+
"success"
|
|
187
|
+
);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
log(`Failed to create task: ${err.message}`, "error");
|
|
190
|
+
if (err.message.includes("Not authenticated")) {
|
|
191
|
+
log("User not authenticated. Task creation skipped.", "warn");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
main().catch((error) => {
|
|
196
|
+
log(`Fatal error: ${error.message}`, "error");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VibeTasks Claude Code Hook - sync-todos.js
|
|
5
|
+
*
|
|
6
|
+
* This script is triggered by Claude Code's SubagentStop hook.
|
|
7
|
+
* It reads Claude's todo state from stdin and syncs it with VibeTasks.
|
|
8
|
+
*
|
|
9
|
+
* Input (from Claude Code hook):
|
|
10
|
+
* {
|
|
11
|
+
* "todoState": [
|
|
12
|
+
* { "content": "Task description", "status": "completed", "activeForm": "..." },
|
|
13
|
+
* ...
|
|
14
|
+
* ]
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Status mapping:
|
|
18
|
+
* Claude "pending" -> VibeTasks "todo"
|
|
19
|
+
* Claude "in_progress" -> VibeTasks "vibing"
|
|
20
|
+
* Claude "completed" -> VibeTasks "done"
|
|
21
|
+
*
|
|
22
|
+
* The hook can operate in two modes:
|
|
23
|
+
* 1. Using vibetasks CLI (fallback, always available)
|
|
24
|
+
* 2. Using @vibetasks/core directly (faster, when available)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { spawn } from 'child_process';
|
|
28
|
+
import fs from 'fs/promises';
|
|
29
|
+
import path from 'path';
|
|
30
|
+
import os from 'os';
|
|
31
|
+
|
|
32
|
+
// Status mapping from Claude Code to VibeTasks
|
|
33
|
+
const STATUS_MAP = {
|
|
34
|
+
'pending': 'todo',
|
|
35
|
+
'in_progress': 'vibing',
|
|
36
|
+
'completed': 'done',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read stdin until EOF
|
|
41
|
+
*/
|
|
42
|
+
async function readStdin() {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let data = '';
|
|
45
|
+
let resolved = false;
|
|
46
|
+
|
|
47
|
+
process.stdin.setEncoding('utf-8');
|
|
48
|
+
|
|
49
|
+
process.stdin.on('data', (chunk) => {
|
|
50
|
+
data += chunk;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
process.stdin.on('end', () => {
|
|
54
|
+
if (!resolved) {
|
|
55
|
+
resolved = true;
|
|
56
|
+
resolve(data);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
process.stdin.on('error', (err) => {
|
|
61
|
+
if (!resolved) {
|
|
62
|
+
resolved = true;
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Timeout after 5 seconds
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
if (!resolved) {
|
|
70
|
+
resolved = true;
|
|
71
|
+
if (data) {
|
|
72
|
+
resolve(data);
|
|
73
|
+
} else {
|
|
74
|
+
// No data received - that's okay for hooks
|
|
75
|
+
resolve('{}');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, 5000);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get log file path
|
|
84
|
+
*/
|
|
85
|
+
function getLogPath() {
|
|
86
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
87
|
+
return path.join(configHome, 'vibetasks', 'logs', 'hook-sync.log');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Log to file for debugging (hook output may not be visible)
|
|
92
|
+
*/
|
|
93
|
+
async function log(message) {
|
|
94
|
+
const logFile = getLogPath();
|
|
95
|
+
const logDir = path.dirname(logFile);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
99
|
+
const timestamp = new Date().toISOString();
|
|
100
|
+
await fs.appendFile(logFile, `[${timestamp}] ${message}\n`);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Silently ignore logging errors
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Execute vibetasks CLI command
|
|
108
|
+
*/
|
|
109
|
+
function execVibetasks(args) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const isWindows = process.platform === 'win32';
|
|
112
|
+
const command = isWindows ? 'vibetasks.cmd' : 'vibetasks';
|
|
113
|
+
|
|
114
|
+
const proc = spawn(command, args, {
|
|
115
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
116
|
+
shell: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
let stdout = '';
|
|
120
|
+
let stderr = '';
|
|
121
|
+
|
|
122
|
+
proc.stdout.on('data', (data) => {
|
|
123
|
+
stdout += data.toString();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
proc.stderr.on('data', (data) => {
|
|
127
|
+
stderr += data.toString();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
proc.on('close', (code) => {
|
|
131
|
+
if (code === 0) {
|
|
132
|
+
resolve(stdout);
|
|
133
|
+
} else {
|
|
134
|
+
reject(new Error(`vibetasks exited with code ${code}: ${stderr}`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
proc.on('error', (err) => {
|
|
139
|
+
reject(err);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Try to use @vibetasks/core directly for faster sync
|
|
146
|
+
*/
|
|
147
|
+
async function tryDirectSync(todos) {
|
|
148
|
+
try {
|
|
149
|
+
// Dynamic import of the core library
|
|
150
|
+
const { AuthManager, TaskOperations, syncClaudeTodos } = await import('@vibetasks/core');
|
|
151
|
+
|
|
152
|
+
const authManager = new AuthManager();
|
|
153
|
+
const isAuthenticated = await authManager.isAuthenticated();
|
|
154
|
+
|
|
155
|
+
if (!isAuthenticated) {
|
|
156
|
+
await log('Not authenticated - falling back to CLI');
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
161
|
+
const result = await syncClaudeTodos(taskOps, todos, {
|
|
162
|
+
createMissing: false, // Don't create new tasks from Claude todos
|
|
163
|
+
matchThreshold: 0.6,
|
|
164
|
+
syncPending: false, // Only sync in_progress and completed
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
await log(`Direct sync failed: ${error.message}`);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Fallback: Use CLI to sync todos
|
|
176
|
+
*/
|
|
177
|
+
async function syncViaCli(todos) {
|
|
178
|
+
const syncResults = [];
|
|
179
|
+
|
|
180
|
+
for (const todo of todos) {
|
|
181
|
+
// Skip pending todos - we only sync when work starts or completes
|
|
182
|
+
if (todo.status === 'pending') {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const vibetasksStatus = STATUS_MAP[todo.status] || 'todo';
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Search for matching task by title/content
|
|
190
|
+
// Escape special characters in the search query
|
|
191
|
+
const searchQuery = todo.content.replace(/"/g, '\\"').substring(0, 50);
|
|
192
|
+
const searchResult = await execVibetasks(['search', `"${searchQuery}"`]);
|
|
193
|
+
|
|
194
|
+
// Parse search results to find matching task
|
|
195
|
+
const lines = searchResult.split('\n').filter(line => line.trim());
|
|
196
|
+
|
|
197
|
+
let foundMatch = false;
|
|
198
|
+
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
// Try to extract task ID (first 8 chars of UUID pattern)
|
|
201
|
+
const idMatch = line.match(/^([a-f0-9]{8})/i);
|
|
202
|
+
if (idMatch) {
|
|
203
|
+
const taskId = idMatch[1];
|
|
204
|
+
|
|
205
|
+
// Check if the line contains the task content (fuzzy match)
|
|
206
|
+
const contentWords = todo.content.toLowerCase().split(/\s+/).slice(0, 3);
|
|
207
|
+
const lineWords = line.toLowerCase();
|
|
208
|
+
|
|
209
|
+
const matchCount = contentWords.filter(word =>
|
|
210
|
+
word.length > 2 && lineWords.includes(word)
|
|
211
|
+
).length;
|
|
212
|
+
|
|
213
|
+
// Require at least 2 matching words
|
|
214
|
+
if (matchCount >= 2) {
|
|
215
|
+
// Update task status based on Claude's todo status
|
|
216
|
+
if (vibetasksStatus === 'done') {
|
|
217
|
+
await execVibetasks(['done', taskId]);
|
|
218
|
+
syncResults.push({
|
|
219
|
+
content: todo.content,
|
|
220
|
+
action: 'updated',
|
|
221
|
+
taskId,
|
|
222
|
+
newStatus: 'done',
|
|
223
|
+
});
|
|
224
|
+
} else if (vibetasksStatus === 'vibing') {
|
|
225
|
+
await execVibetasks(['vibing', taskId]);
|
|
226
|
+
syncResults.push({
|
|
227
|
+
content: todo.content,
|
|
228
|
+
action: 'updated',
|
|
229
|
+
taskId,
|
|
230
|
+
newStatus: 'vibing',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
foundMatch = true;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!foundMatch) {
|
|
241
|
+
syncResults.push({
|
|
242
|
+
content: todo.content,
|
|
243
|
+
action: 'not_found',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
await log(`Error syncing todo "${todo.content}": ${error.message}`);
|
|
248
|
+
syncResults.push({
|
|
249
|
+
content: todo.content,
|
|
250
|
+
action: 'error',
|
|
251
|
+
error: error.message,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
processed: todos.length,
|
|
259
|
+
synced: syncResults.filter(r => r.action === 'updated').length,
|
|
260
|
+
failed: syncResults.filter(r => r.action === 'error').length,
|
|
261
|
+
results: syncResults,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Main hook handler
|
|
267
|
+
*/
|
|
268
|
+
async function main() {
|
|
269
|
+
try {
|
|
270
|
+
await log('=== Hook triggered ===');
|
|
271
|
+
|
|
272
|
+
// Read input from Claude Code hook
|
|
273
|
+
const input = await readStdin();
|
|
274
|
+
|
|
275
|
+
if (!input || !input.trim() || input.trim() === '{}') {
|
|
276
|
+
await log('No input received - exiting gracefully');
|
|
277
|
+
console.log(JSON.stringify({ success: true, message: 'No todos to sync' }));
|
|
278
|
+
process.exit(0);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await log(`Received input (first 200 chars): ${input.substring(0, 200)}`);
|
|
282
|
+
|
|
283
|
+
// Parse the JSON input
|
|
284
|
+
let hookData;
|
|
285
|
+
try {
|
|
286
|
+
hookData = JSON.parse(input);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
await log(`Failed to parse JSON: ${e.message}`);
|
|
289
|
+
console.log(JSON.stringify({ success: false, error: 'Invalid JSON input' }));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Extract todo state - handle various input formats
|
|
294
|
+
let todoState = hookData.todoState || hookData.todos || hookData.input?.todoState || [];
|
|
295
|
+
|
|
296
|
+
// If todoState is the root object with a different structure
|
|
297
|
+
if (Array.isArray(hookData) && hookData.length > 0 && hookData[0].content) {
|
|
298
|
+
todoState = hookData;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!Array.isArray(todoState) || todoState.length === 0) {
|
|
302
|
+
await log('No todos in hook data');
|
|
303
|
+
console.log(JSON.stringify({ success: true, message: 'No todos in input' }));
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await log(`Processing ${todoState.length} todos...`);
|
|
308
|
+
|
|
309
|
+
// Try direct sync first (faster), fall back to CLI
|
|
310
|
+
let result = await tryDirectSync(todoState);
|
|
311
|
+
|
|
312
|
+
if (!result) {
|
|
313
|
+
await log('Using CLI fallback for sync');
|
|
314
|
+
result = await syncViaCli(todoState);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await log(`Sync complete: ${JSON.stringify(result)}`);
|
|
318
|
+
|
|
319
|
+
// Output results for hook debugging
|
|
320
|
+
console.log(JSON.stringify(result));
|
|
321
|
+
|
|
322
|
+
process.exit(0);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
await log(`Hook error: ${error.message}\n${error.stack}`);
|
|
325
|
+
console.error(JSON.stringify({
|
|
326
|
+
success: false,
|
|
327
|
+
error: error.message,
|
|
328
|
+
}));
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibetasks/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "VibeTasks CLI - Lightning-fast task management from your terminal. Works with Claude Code, Cursor, and all AI coding tools.",
|
|
5
5
|
"author": "Vyas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"vibetasks": "./dist/vibetasks.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"hooks"
|
|
14
15
|
],
|
|
15
16
|
"keywords": [
|
|
16
17
|
"vibetasks",
|
|
@@ -39,18 +40,21 @@
|
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
42
|
"dev": "tsx bin/vibetasks.ts",
|
|
42
|
-
"build": "tsup bin/vibetasks.ts --format esm --clean --outDir dist --shims",
|
|
43
|
+
"build": "tsup bin/vibetasks.ts src/daemon-worker.ts --format esm --clean --outDir dist --shims",
|
|
43
44
|
"typecheck": "tsc --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@vibetasks/core": "^0.
|
|
47
|
-
"@vibetasks/shared": "^1.
|
|
47
|
+
"@vibetasks/core": "^0.3.0",
|
|
48
|
+
"@vibetasks/shared": "^1.1.0",
|
|
48
49
|
"commander": "^11.1.0",
|
|
49
50
|
"chalk": "^5.3.0",
|
|
50
51
|
"ora": "^8.0.1",
|
|
51
52
|
"cli-table3": "^0.6.3",
|
|
52
53
|
"inquirer": "^9.2.12",
|
|
53
|
-
"date-fns": "^3.0.0"
|
|
54
|
+
"date-fns": "^3.0.0",
|
|
55
|
+
"clipboardy": "^4.0.0",
|
|
56
|
+
"node-notifier": "^10.0.1",
|
|
57
|
+
"node-global-key-listener": "^0.3.0"
|
|
54
58
|
},
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@types/inquirer": "^9.0.7",
|