@vibetasks/core 0.1.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/index.d.ts +179 -0
- package/dist/index.js +456 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
interface TaskFlowConfig {
|
|
4
|
+
supabase_url?: string;
|
|
5
|
+
supabase_key?: string;
|
|
6
|
+
default_priority?: 'none' | 'low' | 'medium' | 'high';
|
|
7
|
+
list_limit?: number;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Manages TaskFlow configuration stored in ~/.config/taskflow/config.json
|
|
12
|
+
*/
|
|
13
|
+
declare class ConfigManager {
|
|
14
|
+
private configDir;
|
|
15
|
+
private configPath;
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Ensure the config directory exists
|
|
19
|
+
*/
|
|
20
|
+
ensureConfigDir(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Get the full configuration object
|
|
23
|
+
*/
|
|
24
|
+
getConfig(): Promise<TaskFlowConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Set a configuration value
|
|
27
|
+
*/
|
|
28
|
+
setConfig(key: string, value: any): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get a single configuration value
|
|
31
|
+
*/
|
|
32
|
+
get(key: string): Promise<any>;
|
|
33
|
+
/**
|
|
34
|
+
* Delete a configuration value
|
|
35
|
+
*/
|
|
36
|
+
delete(key: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Clear all configuration
|
|
39
|
+
*/
|
|
40
|
+
clear(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Get the config file path (useful for debugging)
|
|
43
|
+
*/
|
|
44
|
+
getConfigPath(): string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Manages authentication tokens with secure storage
|
|
49
|
+
* Uses system keychain (keytar) when available, falls back to config file
|
|
50
|
+
*/
|
|
51
|
+
declare class AuthManager {
|
|
52
|
+
private configManager;
|
|
53
|
+
private keytar;
|
|
54
|
+
private useKeytar;
|
|
55
|
+
constructor();
|
|
56
|
+
/**
|
|
57
|
+
* Initialize keytar (system keychain) if available
|
|
58
|
+
*/
|
|
59
|
+
private initKeytar;
|
|
60
|
+
/**
|
|
61
|
+
* Get the access token
|
|
62
|
+
*/
|
|
63
|
+
getAccessToken(): Promise<string | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Set the access token
|
|
66
|
+
*/
|
|
67
|
+
setAccessToken(token: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Get the refresh token
|
|
70
|
+
*/
|
|
71
|
+
getRefreshToken(): Promise<string | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Set the refresh token
|
|
74
|
+
*/
|
|
75
|
+
setRefreshToken(token: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Delete all stored tokens
|
|
78
|
+
*/
|
|
79
|
+
clearTokens(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Get a config value (pass-through to ConfigManager)
|
|
82
|
+
*/
|
|
83
|
+
getConfig(key: string): Promise<any>;
|
|
84
|
+
/**
|
|
85
|
+
* Set a config value (pass-through to ConfigManager)
|
|
86
|
+
*/
|
|
87
|
+
setConfig(key: string, value: any): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Check if user is authenticated (has access token)
|
|
90
|
+
*/
|
|
91
|
+
isAuthenticated(): Promise<boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* Get storage method being used
|
|
94
|
+
*/
|
|
95
|
+
getStorageMethod(): 'keychain' | 'config-file';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface SupabaseConfig {
|
|
99
|
+
supabaseUrl: string;
|
|
100
|
+
supabaseKey: string;
|
|
101
|
+
accessToken?: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create a Supabase client with the provided configuration
|
|
105
|
+
*
|
|
106
|
+
* @param config - Supabase configuration including URL, key, and optional access token
|
|
107
|
+
* @returns Configured Supabase client
|
|
108
|
+
*/
|
|
109
|
+
declare function createSupabaseClient(config: SupabaseConfig): SupabaseClient;
|
|
110
|
+
/**
|
|
111
|
+
* Create a Supabase client from environment variables
|
|
112
|
+
*
|
|
113
|
+
* @returns Configured Supabase client
|
|
114
|
+
* @throws Error if environment variables are not set
|
|
115
|
+
*/
|
|
116
|
+
declare function createSupabaseClientFromEnv(): SupabaseClient;
|
|
117
|
+
|
|
118
|
+
type TaskFilter = 'all' | 'today' | 'upcoming' | 'completed';
|
|
119
|
+
type TaskPriority = 'none' | 'low' | 'medium' | 'high';
|
|
120
|
+
interface Tag {
|
|
121
|
+
id: string;
|
|
122
|
+
user_id?: string;
|
|
123
|
+
name: string;
|
|
124
|
+
color: string;
|
|
125
|
+
created_at?: string;
|
|
126
|
+
}
|
|
127
|
+
interface Task {
|
|
128
|
+
id: string;
|
|
129
|
+
user_id?: string;
|
|
130
|
+
title: string;
|
|
131
|
+
notes?: string;
|
|
132
|
+
notes_format?: 'html' | 'markdown';
|
|
133
|
+
completed?: boolean;
|
|
134
|
+
completed_at?: string;
|
|
135
|
+
due_date?: string;
|
|
136
|
+
start_date?: string;
|
|
137
|
+
duration_minutes?: number;
|
|
138
|
+
scheduled_time?: string;
|
|
139
|
+
recurrence_rule?: string;
|
|
140
|
+
recurrence_end?: string;
|
|
141
|
+
priority?: TaskPriority;
|
|
142
|
+
parent_task_id?: string;
|
|
143
|
+
position?: number;
|
|
144
|
+
created_at?: string;
|
|
145
|
+
updated_at?: string;
|
|
146
|
+
tags?: Tag[];
|
|
147
|
+
attachments?: any[];
|
|
148
|
+
subtasks?: Task[];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* TaskOperations class provides a high-level API for task management
|
|
152
|
+
* Wraps the existing task-service.ts functionality with authentication and configuration
|
|
153
|
+
*/
|
|
154
|
+
declare class TaskOperations {
|
|
155
|
+
private supabase;
|
|
156
|
+
constructor(supabase: SupabaseClient);
|
|
157
|
+
/**
|
|
158
|
+
* Create TaskOperations from AuthManager
|
|
159
|
+
*/
|
|
160
|
+
static fromAuthManager(authManager: AuthManager): Promise<TaskOperations>;
|
|
161
|
+
getTasks(filter?: TaskFilter): Promise<Task[]>;
|
|
162
|
+
getTask(taskId: string): Promise<Task>;
|
|
163
|
+
createTask(task: Partial<Task>): Promise<Task>;
|
|
164
|
+
updateTask(taskId: string, updates: Partial<Task>): Promise<Task>;
|
|
165
|
+
deleteTask(taskId: string): Promise<void>;
|
|
166
|
+
completeTask(taskId: string): Promise<Task>;
|
|
167
|
+
uncompleteTask(taskId: string): Promise<Task>;
|
|
168
|
+
getTags(): Promise<Tag[]>;
|
|
169
|
+
createTag(name: string, color?: string): Promise<Tag>;
|
|
170
|
+
linkTaskTags(taskId: string, tagIds: string[]): Promise<void>;
|
|
171
|
+
unlinkAllTaskTags(taskId: string): Promise<void>;
|
|
172
|
+
searchTasks(query: string, limit?: number): Promise<Task[]>;
|
|
173
|
+
/**
|
|
174
|
+
* Find tag by name (case-insensitive), create if doesn't exist
|
|
175
|
+
*/
|
|
176
|
+
findOrCreateTag(name: string, color?: string): Promise<Tag>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { AuthManager, ConfigManager, type SupabaseConfig, type Tag, type Task, type TaskFilter, type TaskFlowConfig, TaskOperations, type TaskPriority, createSupabaseClient, createSupabaseClientFromEnv };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// src/config-manager.ts
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
var ConfigManager = class {
|
|
6
|
+
configDir;
|
|
7
|
+
configPath;
|
|
8
|
+
constructor() {
|
|
9
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
10
|
+
this.configDir = path.join(configHome, "taskflow");
|
|
11
|
+
this.configPath = path.join(this.configDir, "config.json");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Ensure the config directory exists
|
|
15
|
+
*/
|
|
16
|
+
async ensureConfigDir() {
|
|
17
|
+
try {
|
|
18
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`Failed to create config directory: ${error.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the full configuration object
|
|
25
|
+
*/
|
|
26
|
+
async getConfig() {
|
|
27
|
+
try {
|
|
28
|
+
const data = await fs.readFile(this.configPath, "utf-8");
|
|
29
|
+
return JSON.parse(data);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code === "ENOENT") {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Failed to read config: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Set a configuration value
|
|
39
|
+
*/
|
|
40
|
+
async setConfig(key, value) {
|
|
41
|
+
await this.ensureConfigDir();
|
|
42
|
+
const config = await this.getConfig();
|
|
43
|
+
config[key] = value;
|
|
44
|
+
try {
|
|
45
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Failed to write config: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get a single configuration value
|
|
52
|
+
*/
|
|
53
|
+
async get(key) {
|
|
54
|
+
const config = await this.getConfig();
|
|
55
|
+
return config[key];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Delete a configuration value
|
|
59
|
+
*/
|
|
60
|
+
async delete(key) {
|
|
61
|
+
await this.ensureConfigDir();
|
|
62
|
+
const config = await this.getConfig();
|
|
63
|
+
delete config[key];
|
|
64
|
+
try {
|
|
65
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to write config: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clear all configuration
|
|
72
|
+
*/
|
|
73
|
+
async clear() {
|
|
74
|
+
try {
|
|
75
|
+
await fs.unlink(this.configPath);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error.code !== "ENOENT") {
|
|
78
|
+
throw new Error(`Failed to clear config: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the config file path (useful for debugging)
|
|
84
|
+
*/
|
|
85
|
+
getConfigPath() {
|
|
86
|
+
return this.configPath;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/auth-manager.ts
|
|
91
|
+
var SERVICE_NAME = "taskflow-cli";
|
|
92
|
+
var ACCESS_TOKEN_ACCOUNT = "access-token";
|
|
93
|
+
var REFRESH_TOKEN_ACCOUNT = "refresh-token";
|
|
94
|
+
var AuthManager = class {
|
|
95
|
+
configManager;
|
|
96
|
+
keytar = null;
|
|
97
|
+
useKeytar = false;
|
|
98
|
+
constructor() {
|
|
99
|
+
this.configManager = new ConfigManager();
|
|
100
|
+
this.initKeytar();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Initialize keytar (system keychain) if available
|
|
104
|
+
*/
|
|
105
|
+
async initKeytar() {
|
|
106
|
+
try {
|
|
107
|
+
this.keytar = await import("keytar");
|
|
108
|
+
this.useKeytar = true;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.useKeytar = false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the access token
|
|
115
|
+
*/
|
|
116
|
+
async getAccessToken() {
|
|
117
|
+
if (this.keytar === null && process.platform !== "linux") {
|
|
118
|
+
await this.initKeytar();
|
|
119
|
+
}
|
|
120
|
+
if (this.useKeytar && this.keytar) {
|
|
121
|
+
try {
|
|
122
|
+
const token = await this.keytar.getPassword(SERVICE_NAME, ACCESS_TOKEN_ACCOUNT);
|
|
123
|
+
if (token) return token;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return await this.configManager.get("access_token");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Set the access token
|
|
131
|
+
*/
|
|
132
|
+
async setAccessToken(token) {
|
|
133
|
+
if (this.keytar === null && process.platform !== "linux") {
|
|
134
|
+
await this.initKeytar();
|
|
135
|
+
}
|
|
136
|
+
if (this.useKeytar && this.keytar) {
|
|
137
|
+
try {
|
|
138
|
+
await this.keytar.setPassword(SERVICE_NAME, ACCESS_TOKEN_ACCOUNT, token);
|
|
139
|
+
return;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
await this.configManager.setConfig("access_token", token);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the refresh token
|
|
147
|
+
*/
|
|
148
|
+
async getRefreshToken() {
|
|
149
|
+
if (this.keytar === null && process.platform !== "linux") {
|
|
150
|
+
await this.initKeytar();
|
|
151
|
+
}
|
|
152
|
+
if (this.useKeytar && this.keytar) {
|
|
153
|
+
try {
|
|
154
|
+
const token = await this.keytar.getPassword(SERVICE_NAME, REFRESH_TOKEN_ACCOUNT);
|
|
155
|
+
if (token) return token;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return await this.configManager.get("refresh_token");
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Set the refresh token
|
|
163
|
+
*/
|
|
164
|
+
async setRefreshToken(token) {
|
|
165
|
+
if (this.keytar === null && process.platform !== "linux") {
|
|
166
|
+
await this.initKeytar();
|
|
167
|
+
}
|
|
168
|
+
if (this.useKeytar && this.keytar) {
|
|
169
|
+
try {
|
|
170
|
+
await this.keytar.setPassword(SERVICE_NAME, REFRESH_TOKEN_ACCOUNT, token);
|
|
171
|
+
return;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await this.configManager.setConfig("refresh_token", token);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Delete all stored tokens
|
|
179
|
+
*/
|
|
180
|
+
async clearTokens() {
|
|
181
|
+
if (this.keytar === null && process.platform !== "linux") {
|
|
182
|
+
await this.initKeytar();
|
|
183
|
+
}
|
|
184
|
+
if (this.useKeytar && this.keytar) {
|
|
185
|
+
try {
|
|
186
|
+
await this.keytar.deletePassword(SERVICE_NAME, ACCESS_TOKEN_ACCOUNT);
|
|
187
|
+
await this.keytar.deletePassword(SERVICE_NAME, REFRESH_TOKEN_ACCOUNT);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
await this.configManager.delete("access_token");
|
|
192
|
+
await this.configManager.delete("refresh_token");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get a config value (pass-through to ConfigManager)
|
|
196
|
+
*/
|
|
197
|
+
async getConfig(key) {
|
|
198
|
+
return await this.configManager.get(key);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Set a config value (pass-through to ConfigManager)
|
|
202
|
+
*/
|
|
203
|
+
async setConfig(key, value) {
|
|
204
|
+
await this.configManager.setConfig(key, value);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if user is authenticated (has access token)
|
|
208
|
+
*/
|
|
209
|
+
async isAuthenticated() {
|
|
210
|
+
const token = await this.getAccessToken();
|
|
211
|
+
return token !== null && token !== void 0 && token.length > 0;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get storage method being used
|
|
215
|
+
*/
|
|
216
|
+
getStorageMethod() {
|
|
217
|
+
return this.useKeytar ? "keychain" : "config-file";
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/supabase-client.ts
|
|
222
|
+
import { createClient } from "@supabase/supabase-js";
|
|
223
|
+
function createSupabaseClient(config) {
|
|
224
|
+
const { supabaseUrl, supabaseKey, accessToken } = config;
|
|
225
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
226
|
+
throw new Error("Supabase URL and key are required");
|
|
227
|
+
}
|
|
228
|
+
const client = createClient(supabaseUrl, supabaseKey, {
|
|
229
|
+
auth: {
|
|
230
|
+
persistSession: false,
|
|
231
|
+
// CLI/MCP don't need session persistence
|
|
232
|
+
autoRefreshToken: false
|
|
233
|
+
// We'll handle refresh manually if needed
|
|
234
|
+
},
|
|
235
|
+
global: accessToken ? {
|
|
236
|
+
headers: {
|
|
237
|
+
Authorization: `Bearer ${accessToken}`
|
|
238
|
+
}
|
|
239
|
+
} : void 0
|
|
240
|
+
});
|
|
241
|
+
return client;
|
|
242
|
+
}
|
|
243
|
+
function createSupabaseClientFromEnv() {
|
|
244
|
+
const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || process.env.SUPABASE_URL;
|
|
245
|
+
const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || process.env.SUPABASE_KEY || process.env.SUPABASE_ANON_KEY;
|
|
246
|
+
const accessToken = process.env.TASKFLOW_ACCESS_TOKEN;
|
|
247
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
"Supabase configuration not found in environment variables. Set TASKFLOW_SUPABASE_URL and TASKFLOW_SUPABASE_KEY, or run: taskflow login"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return createSupabaseClient({
|
|
253
|
+
supabaseUrl,
|
|
254
|
+
supabaseKey,
|
|
255
|
+
accessToken
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/task-operations.ts
|
|
260
|
+
var TaskOperations = class _TaskOperations {
|
|
261
|
+
supabase;
|
|
262
|
+
constructor(supabase) {
|
|
263
|
+
this.supabase = supabase;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Create TaskOperations from AuthManager
|
|
267
|
+
*/
|
|
268
|
+
static async fromAuthManager(authManager) {
|
|
269
|
+
const supabaseUrl = await authManager.getConfig("supabase_url");
|
|
270
|
+
const supabaseKey = await authManager.getConfig("supabase_key");
|
|
271
|
+
const accessToken = await authManager.getAccessToken();
|
|
272
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"Supabase configuration not found. Run: taskflow login"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (!accessToken) {
|
|
278
|
+
throw new Error("Not authenticated. Run: taskflow login");
|
|
279
|
+
}
|
|
280
|
+
const supabase = createSupabaseClient({
|
|
281
|
+
supabaseUrl,
|
|
282
|
+
supabaseKey,
|
|
283
|
+
accessToken
|
|
284
|
+
});
|
|
285
|
+
return new _TaskOperations(supabase);
|
|
286
|
+
}
|
|
287
|
+
// ============================================
|
|
288
|
+
// TASK CRUD OPERATIONS
|
|
289
|
+
// ============================================
|
|
290
|
+
async getTasks(filter = "all") {
|
|
291
|
+
let query = this.supabase.from("tasks").select(`
|
|
292
|
+
*,
|
|
293
|
+
tags:task_tags(tag:tags(*))
|
|
294
|
+
`).is("parent_task_id", null).order("position", { ascending: true });
|
|
295
|
+
if (filter === "today") {
|
|
296
|
+
const today = /* @__PURE__ */ new Date();
|
|
297
|
+
today.setHours(0, 0, 0, 0);
|
|
298
|
+
const tomorrow = new Date(today);
|
|
299
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
300
|
+
query = query.gte("due_date", today.toISOString()).lt("due_date", tomorrow.toISOString()).eq("completed", false);
|
|
301
|
+
} else if (filter === "upcoming") {
|
|
302
|
+
const tomorrow = /* @__PURE__ */ new Date();
|
|
303
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
304
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
305
|
+
query = query.gte("due_date", tomorrow.toISOString()).eq("completed", false);
|
|
306
|
+
} else if (filter === "completed") {
|
|
307
|
+
query = query.eq("completed", true).order("completed_at", { ascending: false });
|
|
308
|
+
} else {
|
|
309
|
+
query = query.eq("completed", false);
|
|
310
|
+
}
|
|
311
|
+
const { data, error } = await query;
|
|
312
|
+
if (error) throw error;
|
|
313
|
+
return (data || []).map((task) => ({
|
|
314
|
+
...task,
|
|
315
|
+
tags: task.tags?.map((t) => t.tag).filter(Boolean) || []
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
async getTask(taskId) {
|
|
319
|
+
const { data, error } = await this.supabase.from("tasks").select(`
|
|
320
|
+
*,
|
|
321
|
+
tags:task_tags(tag:tags(*))
|
|
322
|
+
`).eq("id", taskId).single();
|
|
323
|
+
if (error) throw error;
|
|
324
|
+
return {
|
|
325
|
+
...data,
|
|
326
|
+
tags: data.tags?.map((t) => t.tag).filter(Boolean) || []
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async createTask(task) {
|
|
330
|
+
const { data: { user } } = await this.supabase.auth.getUser();
|
|
331
|
+
if (!user) throw new Error("Not authenticated");
|
|
332
|
+
const { data, error } = await this.supabase.from("tasks").insert({
|
|
333
|
+
user_id: user.id,
|
|
334
|
+
title: task.title,
|
|
335
|
+
notes: task.notes,
|
|
336
|
+
notes_format: task.notes_format || "markdown",
|
|
337
|
+
due_date: task.due_date,
|
|
338
|
+
start_date: task.start_date,
|
|
339
|
+
duration_minutes: task.duration_minutes,
|
|
340
|
+
scheduled_time: task.scheduled_time,
|
|
341
|
+
recurrence_rule: task.recurrence_rule,
|
|
342
|
+
recurrence_end: task.recurrence_end,
|
|
343
|
+
priority: task.priority || "none",
|
|
344
|
+
parent_task_id: task.parent_task_id,
|
|
345
|
+
position: task.position || 0
|
|
346
|
+
}).select().single();
|
|
347
|
+
if (error) throw error;
|
|
348
|
+
if (task.tags && task.tags.length > 0) {
|
|
349
|
+
await this.linkTaskTags(data.id, task.tags.map((t) => t.id));
|
|
350
|
+
}
|
|
351
|
+
return data;
|
|
352
|
+
}
|
|
353
|
+
async updateTask(taskId, updates) {
|
|
354
|
+
const updateData = {};
|
|
355
|
+
if (updates.title !== void 0) updateData.title = updates.title;
|
|
356
|
+
if (updates.notes !== void 0) updateData.notes = updates.notes;
|
|
357
|
+
if (updates.notes_format !== void 0) updateData.notes_format = updates.notes_format;
|
|
358
|
+
if (updates.completed !== void 0) {
|
|
359
|
+
updateData.completed = updates.completed;
|
|
360
|
+
updateData.completed_at = updates.completed ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
361
|
+
}
|
|
362
|
+
if (updates.due_date !== void 0) updateData.due_date = updates.due_date;
|
|
363
|
+
if (updates.start_date !== void 0) updateData.start_date = updates.start_date;
|
|
364
|
+
if (updates.duration_minutes !== void 0) updateData.duration_minutes = updates.duration_minutes;
|
|
365
|
+
if (updates.scheduled_time !== void 0) updateData.scheduled_time = updates.scheduled_time;
|
|
366
|
+
if (updates.recurrence_rule !== void 0) updateData.recurrence_rule = updates.recurrence_rule;
|
|
367
|
+
if (updates.recurrence_end !== void 0) updateData.recurrence_end = updates.recurrence_end;
|
|
368
|
+
if (updates.priority !== void 0) updateData.priority = updates.priority;
|
|
369
|
+
if (updates.position !== void 0) updateData.position = updates.position;
|
|
370
|
+
const { data, error } = await this.supabase.from("tasks").update(updateData).eq("id", taskId).select().single();
|
|
371
|
+
if (error) throw error;
|
|
372
|
+
if (updates.tags !== void 0) {
|
|
373
|
+
await this.unlinkAllTaskTags(taskId);
|
|
374
|
+
if (updates.tags.length > 0) {
|
|
375
|
+
await this.linkTaskTags(taskId, updates.tags.map((t) => t.id));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return data;
|
|
379
|
+
}
|
|
380
|
+
async deleteTask(taskId) {
|
|
381
|
+
const { error } = await this.supabase.from("tasks").delete().eq("id", taskId);
|
|
382
|
+
if (error) throw error;
|
|
383
|
+
}
|
|
384
|
+
async completeTask(taskId) {
|
|
385
|
+
const { data, error } = await this.supabase.from("tasks").update({
|
|
386
|
+
completed: true,
|
|
387
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
388
|
+
}).eq("id", taskId).select().single();
|
|
389
|
+
if (error) throw error;
|
|
390
|
+
return data;
|
|
391
|
+
}
|
|
392
|
+
async uncompleteTask(taskId) {
|
|
393
|
+
const { data, error } = await this.supabase.from("tasks").update({
|
|
394
|
+
completed: false,
|
|
395
|
+
completed_at: null
|
|
396
|
+
}).eq("id", taskId).select().single();
|
|
397
|
+
if (error) throw error;
|
|
398
|
+
return data;
|
|
399
|
+
}
|
|
400
|
+
// ============================================
|
|
401
|
+
// TAG OPERATIONS
|
|
402
|
+
// ============================================
|
|
403
|
+
async getTags() {
|
|
404
|
+
const { data, error } = await this.supabase.from("tags").select("*").order("name", { ascending: true });
|
|
405
|
+
if (error) throw error;
|
|
406
|
+
return data;
|
|
407
|
+
}
|
|
408
|
+
async createTag(name, color = "#8B5CF6") {
|
|
409
|
+
const { data: { user } } = await this.supabase.auth.getUser();
|
|
410
|
+
if (!user) throw new Error("Not authenticated");
|
|
411
|
+
const { data, error } = await this.supabase.from("tags").insert({ user_id: user.id, name, color }).select().single();
|
|
412
|
+
if (error) throw error;
|
|
413
|
+
return data;
|
|
414
|
+
}
|
|
415
|
+
async linkTaskTags(taskId, tagIds) {
|
|
416
|
+
if (tagIds.length === 0) return;
|
|
417
|
+
const { error } = await this.supabase.from("task_tags").insert(tagIds.map((tagId) => ({ task_id: taskId, tag_id: tagId })));
|
|
418
|
+
if (error) throw error;
|
|
419
|
+
}
|
|
420
|
+
async unlinkAllTaskTags(taskId) {
|
|
421
|
+
const { error } = await this.supabase.from("task_tags").delete().eq("task_id", taskId);
|
|
422
|
+
if (error) throw error;
|
|
423
|
+
}
|
|
424
|
+
// ============================================
|
|
425
|
+
// SEARCH & UTILITY
|
|
426
|
+
// ============================================
|
|
427
|
+
async searchTasks(query, limit = 20) {
|
|
428
|
+
const { data, error } = await this.supabase.from("tasks").select(`
|
|
429
|
+
*,
|
|
430
|
+
tags:task_tags(tag:tags(*))
|
|
431
|
+
`).ilike("title", `%${query}%`).is("parent_task_id", null).eq("completed", false).order("position", { ascending: true }).limit(limit);
|
|
432
|
+
if (error) throw error;
|
|
433
|
+
return (data || []).map((task) => ({
|
|
434
|
+
...task,
|
|
435
|
+
tags: task.tags?.map((t) => t.tag).filter(Boolean) || []
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Find tag by name (case-insensitive), create if doesn't exist
|
|
440
|
+
*/
|
|
441
|
+
async findOrCreateTag(name, color) {
|
|
442
|
+
const tags = await this.getTags();
|
|
443
|
+
const existing = tags.find((t) => t.name.toLowerCase() === name.toLowerCase());
|
|
444
|
+
if (existing) {
|
|
445
|
+
return existing;
|
|
446
|
+
}
|
|
447
|
+
return await this.createTag(name, color);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
export {
|
|
451
|
+
AuthManager,
|
|
452
|
+
ConfigManager,
|
|
453
|
+
TaskOperations,
|
|
454
|
+
createSupabaseClient,
|
|
455
|
+
createSupabaseClientFromEnv
|
|
456
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibetasks/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared core logic for VibeTasks MCP server and CLI - authentication, task operations, and config management",
|
|
5
|
+
"author": "Vyas",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"vibetasks",
|
|
15
|
+
"vibe",
|
|
16
|
+
"vibecoding",
|
|
17
|
+
"mcp",
|
|
18
|
+
"task-management",
|
|
19
|
+
"supabase",
|
|
20
|
+
"authentication",
|
|
21
|
+
"productivity"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/vyassathya/vibetasks.git",
|
|
26
|
+
"directory": "packages/mcp-core"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "tsx src/index.ts",
|
|
33
|
+
"build": "tsup src/index.ts --format esm --clean --dts",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@supabase/supabase-js": "^2.39.0",
|
|
38
|
+
"keytar": "^7.9.0",
|
|
39
|
+
"zod": "^3.22.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"tsx": "^4.7.0",
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"typescript": "^5.3.3"
|
|
46
|
+
}
|
|
47
|
+
}
|