@vibetasks/core 0.4.2 → 0.5.1
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 +42 -7
- package/dist/index.js +159 -51
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -46,20 +46,33 @@ declare class ConfigManager {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Manages authentication tokens with secure storage
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
|
+
* Priority order for token retrieval:
|
|
51
|
+
* 1. VIBETASKS_TOKEN or VIBETASKS_ACCESS_TOKEN environment variable
|
|
52
|
+
* 2. System keychain (macOS/Windows)
|
|
53
|
+
* 3. Config file (~/.config/taskflow/config.json)
|
|
54
|
+
*
|
|
55
|
+
* Automatically refreshes expired tokens when using stored tokens.
|
|
50
56
|
*/
|
|
51
57
|
declare class AuthManager {
|
|
52
58
|
private configManager;
|
|
53
59
|
private keytar;
|
|
54
60
|
private useKeytar;
|
|
61
|
+
private isRefreshing;
|
|
55
62
|
constructor();
|
|
63
|
+
/**
|
|
64
|
+
* Check for token in environment variables (highest priority)
|
|
65
|
+
* Used by AI agents and CI/CD pipelines
|
|
66
|
+
*/
|
|
67
|
+
private getEnvToken;
|
|
56
68
|
/**
|
|
57
69
|
* Initialize keytar (system keychain) if available
|
|
58
70
|
*/
|
|
59
71
|
private initKeytar;
|
|
60
72
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
* Get the access token
|
|
74
|
+
* Priority: ENV var > Keychain > Config file
|
|
75
|
+
*/
|
|
63
76
|
getAccessToken(): Promise<string | null>;
|
|
64
77
|
/**
|
|
65
78
|
* Set the access token
|
|
@@ -86,13 +99,34 @@ declare class AuthManager {
|
|
|
86
99
|
*/
|
|
87
100
|
setConfig(key: string, value: any): Promise<void>;
|
|
88
101
|
/**
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
* Check if user is authenticated (has access token)
|
|
103
|
+
*/
|
|
91
104
|
isAuthenticated(): Promise<boolean>;
|
|
92
105
|
/**
|
|
93
106
|
* Get storage method being used
|
|
94
107
|
*/
|
|
95
108
|
getStorageMethod(): 'keychain' | 'config-file';
|
|
109
|
+
/**
|
|
110
|
+
* Check if token is expired (with 5 min buffer)
|
|
111
|
+
*/
|
|
112
|
+
private isTokenExpired;
|
|
113
|
+
/**
|
|
114
|
+
* Refresh the access token using the refresh token
|
|
115
|
+
*/
|
|
116
|
+
refreshAccessToken(): Promise<string | null>;
|
|
117
|
+
/**
|
|
118
|
+
* Get a valid access token, refreshing if necessary
|
|
119
|
+
* Environment tokens are always considered valid (no refresh)
|
|
120
|
+
*/
|
|
121
|
+
getValidAccessToken(): Promise<string | null>;
|
|
122
|
+
/**
|
|
123
|
+
* Check if using environment token (for AI agents/CI)
|
|
124
|
+
*/
|
|
125
|
+
isUsingEnvToken(): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Get auth source for debugging
|
|
128
|
+
*/
|
|
129
|
+
getAuthSource(): 'env' | 'keychain' | 'config' | 'none';
|
|
96
130
|
}
|
|
97
131
|
|
|
98
132
|
interface SupabaseConfig {
|
|
@@ -187,8 +221,9 @@ declare class TaskOperations {
|
|
|
187
221
|
private supabase;
|
|
188
222
|
constructor(supabase: SupabaseClient);
|
|
189
223
|
/**
|
|
190
|
-
|
|
191
|
-
|
|
224
|
+
* Create TaskOperations from AuthManager
|
|
225
|
+
* Automatically refreshes expired tokens
|
|
226
|
+
*/
|
|
192
227
|
static fromAuthManager(authManager: AuthManager): Promise<TaskOperations>;
|
|
193
228
|
getTasks(filter?: TaskFilter, includeArchived?: boolean): Promise<Task[]>;
|
|
194
229
|
getTask(taskId: string): Promise<Task>;
|
package/dist/index.js
CHANGED
|
@@ -87,18 +87,68 @@ var ConfigManager = class {
|
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
// src/supabase-client.ts
|
|
91
|
+
import { createClient } from "@supabase/supabase-js";
|
|
92
|
+
function createSupabaseClient(config) {
|
|
93
|
+
const { supabaseUrl, supabaseKey, accessToken } = config;
|
|
94
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
95
|
+
throw new Error("Supabase URL and key are required");
|
|
96
|
+
}
|
|
97
|
+
const client = createClient(supabaseUrl, supabaseKey, {
|
|
98
|
+
auth: {
|
|
99
|
+
persistSession: false,
|
|
100
|
+
// CLI/MCP don't need session persistence
|
|
101
|
+
autoRefreshToken: false
|
|
102
|
+
// We'll handle refresh manually if needed
|
|
103
|
+
},
|
|
104
|
+
global: accessToken ? {
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${accessToken}`
|
|
107
|
+
}
|
|
108
|
+
} : void 0
|
|
109
|
+
});
|
|
110
|
+
return client;
|
|
111
|
+
}
|
|
112
|
+
function createSupabaseClientFromEnv() {
|
|
113
|
+
const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || process.env.SUPABASE_URL;
|
|
114
|
+
const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || process.env.SUPABASE_KEY || process.env.SUPABASE_ANON_KEY;
|
|
115
|
+
const accessToken = process.env.TASKFLOW_ACCESS_TOKEN;
|
|
116
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Supabase configuration not found in environment variables. Set TASKFLOW_SUPABASE_URL and TASKFLOW_SUPABASE_KEY, or run: taskflow login"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return createSupabaseClient({
|
|
122
|
+
supabaseUrl,
|
|
123
|
+
supabaseKey,
|
|
124
|
+
accessToken
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
90
128
|
// src/auth-manager.ts
|
|
91
129
|
var SERVICE_NAME = "taskflow-cli";
|
|
92
130
|
var ACCESS_TOKEN_ACCOUNT = "access-token";
|
|
93
131
|
var REFRESH_TOKEN_ACCOUNT = "refresh-token";
|
|
132
|
+
var DEFAULT_SUPABASE_URL = "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
133
|
+
var DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
134
|
+
var ENV_TOKEN = "VIBETASKS_TOKEN";
|
|
135
|
+
var ENV_ACCESS_TOKEN = "VIBETASKS_ACCESS_TOKEN";
|
|
94
136
|
var AuthManager = class {
|
|
95
137
|
configManager;
|
|
96
138
|
keytar = null;
|
|
97
139
|
useKeytar = false;
|
|
140
|
+
isRefreshing = false;
|
|
98
141
|
constructor() {
|
|
99
142
|
this.configManager = new ConfigManager();
|
|
100
143
|
this.initKeytar();
|
|
101
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Check for token in environment variables (highest priority)
|
|
147
|
+
* Used by AI agents and CI/CD pipelines
|
|
148
|
+
*/
|
|
149
|
+
getEnvToken() {
|
|
150
|
+
return process.env[ENV_TOKEN] || process.env[ENV_ACCESS_TOKEN] || null;
|
|
151
|
+
}
|
|
102
152
|
/**
|
|
103
153
|
* Initialize keytar (system keychain) if available
|
|
104
154
|
*/
|
|
@@ -111,9 +161,14 @@ var AuthManager = class {
|
|
|
111
161
|
}
|
|
112
162
|
}
|
|
113
163
|
/**
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
* Get the access token
|
|
165
|
+
* Priority: ENV var > Keychain > Config file
|
|
166
|
+
*/
|
|
116
167
|
async getAccessToken() {
|
|
168
|
+
const envToken = this.getEnvToken();
|
|
169
|
+
if (envToken) {
|
|
170
|
+
return envToken;
|
|
171
|
+
}
|
|
117
172
|
if (this.keytar === null && process.platform !== "linux") {
|
|
118
173
|
await this.initKeytar();
|
|
119
174
|
}
|
|
@@ -204,8 +259,8 @@ var AuthManager = class {
|
|
|
204
259
|
await this.configManager.setConfig(key, value);
|
|
205
260
|
}
|
|
206
261
|
/**
|
|
207
|
-
|
|
208
|
-
|
|
262
|
+
* Check if user is authenticated (has access token)
|
|
263
|
+
*/
|
|
209
264
|
async isAuthenticated() {
|
|
210
265
|
const token = await this.getAccessToken();
|
|
211
266
|
return token !== null && token !== void 0 && token.length > 0;
|
|
@@ -216,45 +271,102 @@ var AuthManager = class {
|
|
|
216
271
|
getStorageMethod() {
|
|
217
272
|
return this.useKeytar ? "keychain" : "config-file";
|
|
218
273
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Check if token is expired (with 5 min buffer)
|
|
276
|
+
*/
|
|
277
|
+
isTokenExpired(token) {
|
|
278
|
+
try {
|
|
279
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
280
|
+
const expiresAt = payload.exp * 1e3;
|
|
281
|
+
const bufferMs = 5 * 60 * 1e3;
|
|
282
|
+
return Date.now() > expiresAt - bufferMs;
|
|
283
|
+
} catch {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
227
286
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Refresh the access token using the refresh token
|
|
289
|
+
*/
|
|
290
|
+
async refreshAccessToken() {
|
|
291
|
+
if (this.isRefreshing) {
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
293
|
+
return await this.getAccessToken();
|
|
294
|
+
}
|
|
295
|
+
this.isRefreshing = true;
|
|
296
|
+
try {
|
|
297
|
+
const refreshToken = await this.getRefreshToken();
|
|
298
|
+
if (!refreshToken) {
|
|
299
|
+
throw new Error("No refresh token available");
|
|
238
300
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
301
|
+
const supabaseUrl = await this.getConfig("supabase_url") || DEFAULT_SUPABASE_URL;
|
|
302
|
+
const supabaseKey = await this.getConfig("supabase_key") || DEFAULT_SUPABASE_KEY;
|
|
303
|
+
const supabase = createSupabaseClient({ supabaseUrl, supabaseKey });
|
|
304
|
+
const { data, error } = await supabase.auth.refreshSession({
|
|
305
|
+
refresh_token: refreshToken
|
|
306
|
+
});
|
|
307
|
+
if (error) {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
if (!data.session) {
|
|
311
|
+
throw new Error("No session returned from refresh");
|
|
312
|
+
}
|
|
313
|
+
await this.setAccessToken(data.session.access_token);
|
|
314
|
+
await this.setRefreshToken(data.session.refresh_token);
|
|
315
|
+
return data.session.access_token;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const isRefreshTokenUsed = error.message?.includes("Already Used") || error.code === "refresh_token_already_used";
|
|
318
|
+
if (isRefreshTokenUsed) {
|
|
319
|
+
await this.clearTokens();
|
|
320
|
+
}
|
|
321
|
+
if (process.stderr.isTTY) {
|
|
322
|
+
if (isRefreshTokenUsed) {
|
|
323
|
+
console.error("Session expired. Please run: vibetasks login");
|
|
324
|
+
} else {
|
|
325
|
+
console.error("Token refresh failed:", error.message);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
} finally {
|
|
330
|
+
this.isRefreshing = false;
|
|
331
|
+
}
|
|
251
332
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Get a valid access token, refreshing if necessary
|
|
335
|
+
* Environment tokens are always considered valid (no refresh)
|
|
336
|
+
*/
|
|
337
|
+
async getValidAccessToken() {
|
|
338
|
+
const envToken = this.getEnvToken();
|
|
339
|
+
if (envToken) {
|
|
340
|
+
return envToken;
|
|
341
|
+
}
|
|
342
|
+
const token = await this.getAccessToken();
|
|
343
|
+
if (!token) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
if (this.isTokenExpired(token)) {
|
|
347
|
+
const newToken = await this.refreshAccessToken();
|
|
348
|
+
if (newToken) {
|
|
349
|
+
return newToken;
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return token;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if using environment token (for AI agents/CI)
|
|
357
|
+
*/
|
|
358
|
+
isUsingEnvToken() {
|
|
359
|
+
return this.getEnvToken() !== null;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get auth source for debugging
|
|
363
|
+
*/
|
|
364
|
+
getAuthSource() {
|
|
365
|
+
if (this.getEnvToken()) return "env";
|
|
366
|
+
if (this.useKeytar) return "keychain";
|
|
367
|
+
return "config";
|
|
368
|
+
}
|
|
369
|
+
};
|
|
258
370
|
|
|
259
371
|
// src/task-operations.ts
|
|
260
372
|
var TaskOperations = class _TaskOperations {
|
|
@@ -263,19 +375,15 @@ var TaskOperations = class _TaskOperations {
|
|
|
263
375
|
this.supabase = supabase;
|
|
264
376
|
}
|
|
265
377
|
/**
|
|
266
|
-
|
|
267
|
-
|
|
378
|
+
* Create TaskOperations from AuthManager
|
|
379
|
+
* Automatically refreshes expired tokens
|
|
380
|
+
*/
|
|
268
381
|
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.
|
|
272
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
273
|
-
throw new Error(
|
|
274
|
-
"Supabase configuration not found. Run: taskflow login"
|
|
275
|
-
);
|
|
276
|
-
}
|
|
382
|
+
const supabaseUrl = await authManager.getConfig("supabase_url") || "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
383
|
+
const supabaseKey = await authManager.getConfig("supabase_key") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
384
|
+
const accessToken = await authManager.getValidAccessToken();
|
|
277
385
|
if (!accessToken) {
|
|
278
|
-
throw new Error("Not authenticated. Run:
|
|
386
|
+
throw new Error("Not authenticated or session expired. Run: vibetasks login --browser");
|
|
279
387
|
}
|
|
280
388
|
const supabase = createSupabaseClient({
|
|
281
389
|
supabaseUrl,
|
package/package.json
CHANGED