@vibetasks/core 0.5.0 → 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 CHANGED
@@ -46,20 +46,33 @@ declare class ConfigManager {
46
46
 
47
47
  /**
48
48
  * Manages authentication tokens with secure storage
49
- * Uses system keychain (keytar) when available, falls back to config file
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
- * Get the access token
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
- * Check if user is authenticated (has access token)
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
- * Create TaskOperations from AuthManager
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
- * Get the access token
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
- * Check if user is authenticated (has access token)
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
- // 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");
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
- 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}`
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
- } : 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
- );
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
- return createSupabaseClient({
253
- supabaseUrl,
254
- supabaseKey,
255
- accessToken
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
- * Create TaskOperations from AuthManager
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.getAccessToken();
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: taskflow login");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Shared core logic for VibeTasks MCP server and CLI - authentication, task operations, and config management",
5
5
  "author": "Vyas",
6
6
  "license": "MIT",