diff --git a/apps/cli/package.json b/apps/cli/package.json index f78a69f7..60c531e1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -29,7 +29,6 @@ "cli-table3": "^0.6.5", "commander": "^12.1.0", "inquirer": "^9.2.10", - "open": "^10.2.0", "ora": "^8.1.0" }, "devDependencies": { diff --git a/apps/cli/src/commands/context.command.ts b/apps/cli/src/commands/context.command.ts new file mode 100644 index 00000000..f798e938 --- /dev/null +++ b/apps/cli/src/commands/context.command.ts @@ -0,0 +1,570 @@ +/** + * @fileoverview Context command for managing org/brief selection + * Provides a clean interface for workspace context management + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import ora from 'ora'; +import { + AuthManager, + AuthenticationError, + type UserContext +} from '@tm/core/auth'; +import * as ui from '../utils/ui.js'; + +/** + * Result type from context command + */ +export interface ContextResult { + success: boolean; + action: 'show' | 'select-org' | 'select-brief' | 'clear' | 'set'; + context?: UserContext; + message?: string; +} + +/** + * ContextCommand extending Commander's Command class + * Manages user's workspace context (org/brief selection) + */ +export class ContextCommand extends Command { + private authManager: AuthManager; + private lastResult?: ContextResult; + + constructor(name?: string) { + super(name || 'context'); + + // Initialize auth manager + this.authManager = AuthManager.getInstance(); + + // Configure the command + this.description( + 'Manage workspace context (organization and brief selection)' + ); + + // Add subcommands + this.addOrgCommand(); + this.addBriefCommand(); + this.addClearCommand(); + this.addSetCommand(); + + // Default action shows current context + this.action(async () => { + await this.executeShow(); + }); + } + + /** + * Add org selection subcommand + */ + private addOrgCommand(): void { + this.command('org') + .description('Select an organization') + .action(async () => { + await this.executeSelectOrg(); + }); + } + + /** + * Add brief selection subcommand + */ + private addBriefCommand(): void { + this.command('brief') + .description('Select a brief within the current organization') + .action(async () => { + await this.executeSelectBrief(); + }); + } + + /** + * Add clear subcommand + */ + private addClearCommand(): void { + this.command('clear') + .description('Clear all context selections') + .action(async () => { + await this.executeClear(); + }); + } + + /** + * Add set subcommand for direct context setting + */ + private addSetCommand(): void { + this.command('set') + .description('Set context directly') + .option('--org ', 'Organization ID') + .option('--org-name ', 'Organization name') + .option('--brief ', 'Brief ID') + .option('--brief-name ', 'Brief name') + .action(async (options) => { + await this.executeSet(options); + }); + } + + /** + * Execute show current context + */ + private async executeShow(): Promise { + try { + const result = this.displayContext(); + this.setLastResult(result); + } catch (error: any) { + this.handleError(error); + process.exit(1); + } + } + + /** + * Display current context + */ + private displayContext(): ContextResult { + // Check authentication first + if (!this.authManager.isAuthenticated()) { + console.log(chalk.yellow('āœ— Not authenticated')); + console.log(chalk.gray('\n Run "tm auth login" to authenticate first')); + + return { + success: false, + action: 'show', + message: 'Not authenticated' + }; + } + + const context = this.authManager.getContext(); + + console.log(chalk.cyan('\nšŸŒ Workspace Context\n')); + + if (context && (context.orgId || context.briefId)) { + if (context.orgName || context.orgId) { + console.log(chalk.green('āœ“ Organization')); + if (context.orgName) { + console.log(chalk.white(` ${context.orgName}`)); + } + if (context.orgId) { + console.log(chalk.gray(` ID: ${context.orgId}`)); + } + } + + if (context.briefName || context.briefId) { + console.log(chalk.green('\nāœ“ Brief')); + if (context.briefName) { + console.log(chalk.white(` ${context.briefName}`)); + } + if (context.briefId) { + console.log(chalk.gray(` ID: ${context.briefId}`)); + } + } + + if (context.updatedAt) { + console.log( + chalk.gray( + `\n Last updated: ${new Date(context.updatedAt).toLocaleString()}` + ) + ); + } + + return { + success: true, + action: 'show', + context, + message: 'Context loaded' + }; + } else { + console.log(chalk.yellow('āœ— No context selected')); + console.log( + chalk.gray('\n Run "tm context org" to select an organization') + ); + console.log(chalk.gray(' Run "tm context brief" to select a brief')); + + return { + success: true, + action: 'show', + message: 'No context selected' + }; + } + } + + /** + * Execute org selection + */ + private async executeSelectOrg(): Promise { + try { + // Check authentication + if (!this.authManager.isAuthenticated()) { + ui.displayError('Not authenticated. Run "tm auth login" first.'); + process.exit(1); + } + + const result = await this.selectOrganization(); + this.setLastResult(result); + + if (!result.success) { + process.exit(1); + } + } catch (error: any) { + this.handleError(error); + process.exit(1); + } + } + + /** + * Select an organization interactively + */ + private async selectOrganization(): Promise { + const spinner = ora('Fetching organizations...').start(); + + try { + // Fetch organizations from API + const organizations = await this.authManager.getOrganizations(); + spinner.stop(); + + if (organizations.length === 0) { + ui.displayWarning('No organizations available'); + return { + success: false, + action: 'select-org', + message: 'No organizations available' + }; + } + + // Prompt for selection + const { selectedOrg } = await inquirer.prompt([ + { + type: 'list', + name: 'selectedOrg', + message: 'Select an organization:', + choices: organizations.map((org) => ({ + name: org.name, + value: org + })) + } + ]); + + // Update context + await this.authManager.updateContext({ + orgId: selectedOrg.id, + orgName: selectedOrg.name, + // Clear brief when changing org + briefId: undefined, + briefName: undefined + }); + + ui.displaySuccess(`Selected organization: ${selectedOrg.name}`); + + return { + success: true, + action: 'select-org', + context: this.authManager.getContext() || undefined, + message: `Selected organization: ${selectedOrg.name}` + }; + } catch (error) { + spinner.fail('Failed to fetch organizations'); + throw error; + } + } + + /** + * Execute brief selection + */ + private async executeSelectBrief(): Promise { + try { + // Check authentication + if (!this.authManager.isAuthenticated()) { + ui.displayError('Not authenticated. Run "tm auth login" first.'); + process.exit(1); + } + + // Check if org is selected + const context = this.authManager.getContext(); + if (!context?.orgId) { + ui.displayError( + 'No organization selected. Run "tm context org" first.' + ); + process.exit(1); + } + + const result = await this.selectBrief(context.orgId); + this.setLastResult(result); + + if (!result.success) { + process.exit(1); + } + } catch (error: any) { + this.handleError(error); + process.exit(1); + } + } + + /** + * Select a brief within the current organization + */ + private async selectBrief(orgId: string): Promise { + const spinner = ora('Fetching briefs...').start(); + + try { + // Fetch briefs from API + const briefs = await this.authManager.getBriefs(orgId); + spinner.stop(); + + if (briefs.length === 0) { + ui.displayWarning('No briefs available in this organization'); + return { + success: false, + action: 'select-brief', + message: 'No briefs available' + }; + } + + // Prompt for selection + const { selectedBrief } = await inquirer.prompt([ + { + type: 'list', + name: 'selectedBrief', + message: 'Select a brief:', + choices: [ + { name: '(No brief - organization level)', value: null }, + ...briefs.map((brief) => ({ + name: `Brief ${brief.id.slice(0, 8)} (${new Date(brief.createdAt).toLocaleDateString()})`, + value: brief + })) + ] + } + ]); + + if (selectedBrief) { + // Update context with brief + const briefName = `Brief ${selectedBrief.id.slice(0, 8)}`; + await this.authManager.updateContext({ + briefId: selectedBrief.id, + briefName: briefName + }); + + ui.displaySuccess(`Selected brief: ${briefName}`); + + return { + success: true, + action: 'select-brief', + context: this.authManager.getContext() || undefined, + message: `Selected brief: ${selectedBrief.name}` + }; + } else { + // Clear brief selection + await this.authManager.updateContext({ + briefId: undefined, + briefName: undefined + }); + + ui.displaySuccess('Cleared brief selection (organization level)'); + + return { + success: true, + action: 'select-brief', + context: this.authManager.getContext() || undefined, + message: 'Cleared brief selection' + }; + } + } catch (error) { + spinner.fail('Failed to fetch briefs'); + throw error; + } + } + + /** + * Execute clear context + */ + private async executeClear(): Promise { + try { + // Check authentication + if (!this.authManager.isAuthenticated()) { + ui.displayError('Not authenticated. Run "tm auth login" first.'); + process.exit(1); + } + + const result = await this.clearContext(); + this.setLastResult(result); + + if (!result.success) { + process.exit(1); + } + } catch (error: any) { + this.handleError(error); + process.exit(1); + } + } + + /** + * Clear all context selections + */ + private async clearContext(): Promise { + try { + await this.authManager.clearContext(); + ui.displaySuccess('Context cleared'); + + return { + success: true, + action: 'clear', + message: 'Context cleared' + }; + } catch (error) { + ui.displayError(`Failed to clear context: ${(error as Error).message}`); + + return { + success: false, + action: 'clear', + message: `Failed to clear context: ${(error as Error).message}` + }; + } + } + + /** + * Execute set context with options + */ + private async executeSet(options: any): Promise { + try { + // Check authentication + if (!this.authManager.isAuthenticated()) { + ui.displayError('Not authenticated. Run "tm auth login" first.'); + process.exit(1); + } + + const result = await this.setContext(options); + this.setLastResult(result); + + if (!result.success) { + process.exit(1); + } + } catch (error: any) { + this.handleError(error); + process.exit(1); + } + } + + /** + * Set context directly from options + */ + private async setContext(options: any): Promise { + try { + const context: Partial = {}; + + if (options.org) { + context.orgId = options.org; + } + if (options.orgName) { + context.orgName = options.orgName; + } + if (options.brief) { + context.briefId = options.brief; + } + if (options.briefName) { + context.briefName = options.briefName; + } + + if (Object.keys(context).length === 0) { + ui.displayWarning('No context options provided'); + return { + success: false, + action: 'set', + message: 'No context options provided' + }; + } + + await this.authManager.updateContext(context); + ui.displaySuccess('Context updated'); + + // Display what was set + if (context.orgName || context.orgId) { + console.log( + chalk.gray(` Organization: ${context.orgName || context.orgId}`) + ); + } + if (context.briefName || context.briefId) { + console.log( + chalk.gray(` Brief: ${context.briefName || context.briefId}`) + ); + } + + return { + success: true, + action: 'set', + context: this.authManager.getContext() || undefined, + message: 'Context updated' + }; + } catch (error) { + ui.displayError(`Failed to set context: ${(error as Error).message}`); + + return { + success: false, + action: 'set', + message: `Failed to set context: ${(error as Error).message}` + }; + } + } + + /** + * Handle errors + */ + private handleError(error: any): void { + if (error instanceof AuthenticationError) { + console.error(chalk.red(`\nāœ— ${error.message}`)); + + if (error.code === 'NOT_AUTHENTICATED') { + ui.displayWarning('Please authenticate first: tm auth login'); + } + } else { + const msg = error?.message ?? String(error); + console.error(chalk.red(`Error: ${msg}`)); + + if (error.stack && process.env.DEBUG) { + console.error(chalk.gray(error.stack)); + } + } + } + + /** + * Set the last result for programmatic access + */ + private setLastResult(result: ContextResult): void { + this.lastResult = result; + } + + /** + * Get the last result (for programmatic usage) + */ + getLastResult(): ContextResult | undefined { + return this.lastResult; + } + + /** + * Get current context (for programmatic usage) + */ + getContext(): UserContext | null { + return this.authManager.getContext(); + } + + /** + * Clean up resources + */ + async cleanup(): Promise { + // No resources to clean up for context command + } + + /** + * Static method to register this command on an existing program + */ + static registerOn(program: Command): Command { + const contextCommand = new ContextCommand(); + program.addCommand(contextCommand); + return contextCommand; + } + + /** + * Alternative registration that returns the command for chaining + */ + static register(program: Command, name?: string): ContextCommand { + const contextCommand = new ContextCommand(name); + program.addCommand(contextCommand); + return contextCommand; + } +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 10fe1c6a..420a1716 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -6,6 +6,7 @@ // Commands export { ListTasksCommand } from './commands/list.command.js'; export { AuthCommand } from './commands/auth.command.js'; +export { ContextCommand } from './commands/context.command.js'; // UI utilities (for other commands to use) export * as ui from './utils/ui.js'; diff --git a/package-lock.json b/package-lock.json index 658d184a..5295aa7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -119,8 +119,6 @@ }, "apps/cli/node_modules/@types/node": { "version": "22.18.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", - "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", "dev": true, "license": "MIT", "dependencies": { @@ -129,8 +127,6 @@ }, "apps/cli/node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -141,8 +137,6 @@ }, "apps/cli/node_modules/boxen": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", "license": "MIT", "dependencies": { "ansi-align": "^3.0.1", @@ -163,8 +157,6 @@ }, "apps/cli/node_modules/boxen/node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -178,8 +170,6 @@ }, "apps/cli/node_modules/boxen/node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -195,8 +185,6 @@ }, "apps/cli/node_modules/camelcase": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "license": "MIT", "engines": { "node": ">=14.16" @@ -207,8 +195,6 @@ }, "apps/cli/node_modules/cli-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -219,8 +205,6 @@ }, "apps/cli/node_modules/commander": { "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", "engines": { "node": ">=18" @@ -228,14 +212,10 @@ }, "apps/cli/node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, "apps/cli/node_modules/inquirer": { "version": "9.3.7", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.7.tgz", - "integrity": "sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==", "license": "MIT", "dependencies": { "@inquirer/figures": "^1.0.3", @@ -257,8 +237,6 @@ }, "apps/cli/node_modules/inquirer/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -272,8 +250,6 @@ }, "apps/cli/node_modules/inquirer/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -288,14 +264,10 @@ }, "apps/cli/node_modules/inquirer/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "apps/cli/node_modules/inquirer/node_modules/ora": { "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -317,8 +289,6 @@ }, "apps/cli/node_modules/inquirer/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -331,8 +301,6 @@ }, "apps/cli/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -340,8 +308,6 @@ }, "apps/cli/node_modules/is-interactive": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "license": "MIT", "engines": { "node": ">=8" @@ -349,8 +315,6 @@ }, "apps/cli/node_modules/is-unicode-supported": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "license": "MIT", "engines": { "node": ">=10" @@ -361,8 +325,6 @@ }, "apps/cli/node_modules/log-symbols": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -377,8 +339,6 @@ }, "apps/cli/node_modules/log-symbols/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -392,8 +352,6 @@ }, "apps/cli/node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -408,8 +366,6 @@ }, "apps/cli/node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "license": "MIT", "engines": { "node": ">=6" @@ -417,8 +373,6 @@ }, "apps/cli/node_modules/mute-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -426,8 +380,6 @@ }, "apps/cli/node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -441,8 +393,6 @@ }, "apps/cli/node_modules/restore-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -454,14 +404,10 @@ }, "apps/cli/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "apps/cli/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -477,8 +423,6 @@ }, "apps/cli/node_modules/string-width/node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -492,8 +436,6 @@ }, "apps/cli/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" @@ -504,15 +446,11 @@ }, "apps/cli/node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "apps/cli/node_modules/widest-line": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "license": "MIT", "dependencies": { "string-width": "^5.0.1" @@ -4016,74 +3954,6 @@ "react": ">=16.8.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", "cpu": [ @@ -4099,346 +3969,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@floating-ui/core": { "version": "1.7.3", "dev": true, @@ -8022,38 +7552,8 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", - "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", - "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", - "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", "cpu": [ "arm64" ], @@ -8064,258 +7564,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", - "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", - "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", - "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", - "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", - "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", - "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", - "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", - "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", - "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", - "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", - "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", - "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", - "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", - "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", - "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", - "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", - "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", - "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "license": "MIT" @@ -9322,8 +8570,6 @@ }, "node_modules/@supabase/auth-js": { "version": "2.71.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz", - "integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" @@ -9331,8 +8577,6 @@ }, "node_modules/@supabase/functions-js": { "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz", - "integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" @@ -9340,8 +8584,6 @@ }, "node_modules/@supabase/node-fetch": { "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -9352,8 +8594,6 @@ }, "node_modules/@supabase/postgrest-js": { "version": "1.21.3", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.21.3.tgz", - "integrity": "sha512-rg3DmmZQKEVCreXq6Am29hMVe1CzemXyIWVYyyua69y6XubfP+DzGfLxME/1uvdgwqdoaPbtjBDpEBhqxq1ZwA==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" @@ -9361,8 +8601,6 @@ }, "node_modules/@supabase/realtime-js": { "version": "2.15.4", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.4.tgz", - "integrity": "sha512-e/FYIWjvQJHOCNACWehnKvg26zosju3694k0NMUNb+JGLdvHJzEa29ZVVLmawd2kvx4hdbv8mxSqfttRnH3+DA==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.13", @@ -9373,8 +8611,6 @@ }, "node_modules/@supabase/storage-js": { "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.11.0.tgz", - "integrity": "sha512-Y+kx/wDgd4oasAgoAq0bsbQojwQ+ejIif8uczZ9qufRHWFLMU5cODT+ApHsSrDufqUcVKt+eyxtOXSkeh2v9ww==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" @@ -9382,8 +8618,6 @@ }, "node_modules/@supabase/supabase-js": { "version": "2.57.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.57.0.tgz", - "integrity": "sha512-h9ttcL0MY4h+cGqZl95F/RuqccuRBjHU9B7Qqvw0Da+pPK2sUlU1/UdvyqUGj37UsnSphr9pdGfeXjesYkBcyA==", "license": "MIT", "dependencies": { "@supabase/auth-js": "2.71.1", @@ -9684,8 +8918,6 @@ }, "node_modules/@types/inquirer": { "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", - "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", "dev": true, "license": "MIT", "dependencies": { @@ -9786,8 +9018,6 @@ }, "node_modules/@types/phoenix": { "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", "license": "MIT" }, "node_modules/@types/react": { @@ -9818,8 +9048,6 @@ }, "node_modules/@types/through": { "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", - "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9847,8 +9075,6 @@ }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -9896,8 +9122,6 @@ }, "node_modules/@vitest/coverage-v8": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", - "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9929,8 +9153,6 @@ }, "node_modules/@vitest/coverage-v8/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9939,8 +9161,6 @@ }, "node_modules/@vitest/coverage-v8/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9957,8 +9177,6 @@ }, "node_modules/@vitest/coverage-v8/node_modules/glob": { "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -9978,8 +9196,6 @@ }, "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9993,8 +9209,6 @@ }, "node_modules/@vitest/coverage-v8/node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -10009,15 +9223,11 @@ }, "node_modules/@vitest/coverage-v8/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/@vitest/coverage-v8/node_modules/test-exclude": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { @@ -10031,8 +9241,6 @@ }, "node_modules/@vitest/expect": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, "license": "MIT", "dependencies": { @@ -10047,8 +9255,6 @@ }, "node_modules/@vitest/mocker": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", "dev": true, "license": "MIT", "dependencies": { @@ -10074,8 +9280,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10087,8 +9291,6 @@ }, "node_modules/@vitest/runner": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, "license": "MIT", "dependencies": { @@ -10101,15 +9303,11 @@ }, "node_modules/@vitest/runner/node_modules/pathe": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, "node_modules/@vitest/snapshot": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10123,15 +9321,11 @@ }, "node_modules/@vitest/snapshot/node_modules/pathe": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, "node_modules/@vitest/spy": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10143,8 +9337,6 @@ }, "node_modules/@vitest/utils": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10861,8 +10053,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -11580,8 +10770,6 @@ }, "node_modules/bundle-require": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", "dev": true, "license": "MIT", "dependencies": { @@ -11778,8 +10966,6 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -11931,8 +11117,6 @@ }, "node_modules/chai": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", "dependencies": { @@ -12006,8 +11190,6 @@ }, "node_modules/check-error": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", "engines": { @@ -12461,8 +11643,6 @@ }, "node_modules/clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", "engines": { "node": ">=0.8" @@ -12594,15 +11774,11 @@ }, "node_modules/confbox": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "dev": true, "license": "MIT", "engines": { @@ -12944,8 +12120,6 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -12995,8 +12169,6 @@ }, "node_modules/defaults": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -13327,8 +12499,6 @@ }, "node_modules/dotenv-expand": { "version": "12.0.2", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.2.tgz", - "integrity": "sha512-lXpXz2ZE1cea1gL4sz2Ipj8y4PiVjytYr3Ij0SWoms1PGxIv7m2CRKuRuCRtHdVuvM/hNJPMxt5PbhboNC4dPQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -13343,8 +12513,6 @@ }, "node_modules/dotenv-mono": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dotenv-mono/-/dotenv-mono-1.5.1.tgz", - "integrity": "sha512-dt7bK/WKQvL0gcdTxjI7wD4MhVR5F4bCk70XMAgnrbWN3fdhpyhWCypYbZalr/vjLURLA7Ib9/VCzazRLJnp1Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -13360,8 +12528,6 @@ }, "node_modules/dotenv-mono/node_modules/dotenv": { "version": "17.2.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", - "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -13698,8 +12864,6 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -13832,380 +12996,6 @@ "postcss": "^8.0.0" } }, - "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/escalade": { "version": "3.2.0", "license": "MIT", @@ -14451,8 +13241,6 @@ }, "node_modules/expect-type": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -15006,8 +13794,6 @@ }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", "dev": true, "license": "MIT", "dependencies": { @@ -18514,8 +17300,6 @@ }, "node_modules/joycon": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, "license": "MIT", "engines": { @@ -18940,8 +17724,6 @@ }, "node_modules/load-tsconfig": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", "dev": true, "license": "MIT", "engines": { @@ -19062,8 +17844,6 @@ }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, @@ -19100,8 +17880,6 @@ }, "node_modules/magicast": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -20563,8 +19341,6 @@ }, "node_modules/mlly": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "dev": true, "license": "MIT", "dependencies": { @@ -21519,8 +20295,6 @@ }, "node_modules/open": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "license": "MIT", "dependencies": { "default-browser": "^5.2.1", @@ -22087,15 +20861,11 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/pathval": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -22178,8 +20948,6 @@ }, "node_modules/pkg-types": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -23505,8 +22273,6 @@ }, "node_modules/rollup": { "version": "4.50.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", - "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "dev": true, "license": "MIT", "dependencies": { @@ -24049,8 +22815,6 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, @@ -24476,8 +23240,6 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, @@ -24490,8 +23252,6 @@ }, "node_modules/std-env": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true, "license": "MIT" }, @@ -25133,8 +23893,6 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, @@ -25144,15 +23902,11 @@ }, "node_modules/tinyexec": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -25168,8 +23922,6 @@ }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -25186,8 +23938,6 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -25207,8 +23957,6 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -25217,8 +23965,6 @@ }, "node_modules/tinyrainbow": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, "license": "MIT", "engines": { @@ -25227,8 +23973,6 @@ }, "node_modules/tinyspy": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -25295,8 +24039,6 @@ }, "node_modules/tree-kill": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -25396,8 +24138,6 @@ }, "node_modules/tsup": { "version": "8.5.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", - "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -25449,8 +24189,6 @@ }, "node_modules/tsup/node_modules/chokidar": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -25465,8 +24203,6 @@ }, "node_modules/tsup/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -25483,8 +24219,6 @@ }, "node_modules/tsup/node_modules/lilconfig": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "license": "MIT", "engines": { @@ -25496,15 +24230,11 @@ }, "node_modules/tsup/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/tsup/node_modules/postcss-load-config": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, "funding": [ { @@ -25546,8 +24276,6 @@ }, "node_modules/tsup/node_modules/readdirp": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { @@ -25560,9 +24288,6 @@ }, "node_modules/tsup/node_modules/source-map": { "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -25574,8 +24299,6 @@ }, "node_modules/tsup/node_modules/yaml": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "optional": true, @@ -25589,8 +24312,6 @@ }, "node_modules/tsx": { "version": "4.20.5", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", - "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", "dev": true, "license": "MIT", "dependencies": { @@ -25755,8 +24476,6 @@ }, "node_modules/ufo": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true, "license": "MIT" }, @@ -26210,8 +24929,6 @@ }, "node_modules/vite": { "version": "5.4.20", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", - "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "dev": true, "license": "MIT", "dependencies": { @@ -26270,8 +24987,6 @@ }, "node_modules/vite-node": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", "dev": true, "license": "MIT", "dependencies": { @@ -26293,8 +25008,6 @@ }, "node_modules/vite-node/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -26311,22 +25024,16 @@ }, "node_modules/vite-node/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/vite-node/node_modules/pathe": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -26342,8 +25049,6 @@ }, "node_modules/vite/node_modules/esbuild": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -26381,8 +25086,6 @@ }, "node_modules/vitest": { "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -26447,8 +25150,6 @@ }, "node_modules/vitest/node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -26465,15 +25166,11 @@ }, "node_modules/vitest/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/vitest/node_modules/pathe": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true, "license": "MIT" }, @@ -26487,8 +25184,6 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "license": "MIT", "dependencies": { "defaults": "^1.0.3" @@ -26659,8 +25354,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -27085,18 +25778,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "packages/logger": { - "name": "@tm/logger", - "version": "1.0.0", - "extraneous": true, - "dependencies": { - "chalk": "^5.3.0" - }, - "devDependencies": { - "@types/node": "^20.11.5", - "typescript": "^5.3.3" - } - }, "packages/tm-core": { "name": "@tm/core", "version": "1.0.0", @@ -27122,8 +25803,6 @@ }, "packages/tm-core/node_modules/@types/node": { "version": "20.19.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", - "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==", "dev": true, "license": "MIT", "dependencies": { @@ -27132,8 +25811,6 @@ }, "packages/tm-core/node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" } diff --git a/packages/tm-core/package.json b/packages/tm-core/package.json index b197b0c4..8a60568a 100644 --- a/packages/tm-core/package.json +++ b/packages/tm-core/package.json @@ -10,58 +10,7 @@ "types": "./src/index.ts", "import": "./dist/index.js", "require": "./dist/index.js" - }, - "./auth": { - "types": "./src/auth/index.ts", - "import": "./dist/auth/index.js", - "require": "./dist/auth/index.js" - }, - "./storage": { - "types": "./src/storage/index.ts", - "import": "./dist/storage/index.js", - "require": "./dist/storage/index.js" - }, - "./config": { - "types": "./src/config/index.ts", - "import": "./dist/config/index.js", - "require": "./dist/config/index.js" - }, - "./providers": { - "types": "./src/providers/index.ts", - "import": "./dist/providers/index.js", - "require": "./dist/providers/index.js" - }, - "./services": { - "types": "./src/services/index.ts", - "import": "./dist/services/index.js", - "require": "./dist/services/index.js" - }, - "./errors": { - "types": "./src/errors/index.ts", - "import": "./dist/errors/index.js", - "require": "./dist/errors/index.js" - }, - "./logger": { - "types": "./src/logger/index.ts", - "import": "./dist/logger/index.js", - "require": "./dist/logger/index.js" - }, - "./types": { - "types": "./src/types/index.ts", - "import": "./dist/types/index.js", - "require": "./dist/types/index.js" - }, - "./interfaces": { - "types": "./src/interfaces/index.ts", - "import": "./dist/interfaces/index.js", - "require": "./dist/interfaces/index.js" - }, - "./utils": { - "types": "./src/utils/index.ts", - "import": "./dist/utils/index.js", - "require": "./dist/utils/index.js" - }, - "./package.json": "./package.json" + } }, "scripts": { "build": "tsup", @@ -77,15 +26,12 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@supabase/supabase-js": "^2.57.0", - "chalk": "^5.3.0", "zod": "^3.22.4" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^20.11.30", "@vitest/coverage-v8": "^2.0.5", - "dotenv-mono": "^1.5.1", "ts-node": "^10.9.2", "tsup": "^8.0.2", "typescript": "^5.4.3", diff --git a/packages/tm-core/src/auth/auth-manager.ts b/packages/tm-core/src/auth/auth-manager.ts index 35ede566..b9b3256c 100644 --- a/packages/tm-core/src/auth/auth-manager.ts +++ b/packages/tm-core/src/auth/auth-manager.ts @@ -6,11 +6,18 @@ import { AuthCredentials, OAuthFlowOptions, AuthenticationError, - AuthConfig + AuthConfig, + UserContext } from './types.js'; import { CredentialStore } from './credential-store.js'; import { OAuthService } from './oauth-service.js'; import { SupabaseAuthClient } from '../clients/supabase-client.js'; +import { + OrganizationService, + type Organization, + type Brief, + type RemoteTask +} from '../services/organization.service.js'; import { getLogger } from '../logger/index.js'; /** @@ -21,11 +28,28 @@ export class AuthManager { private credentialStore: CredentialStore; private oauthService: OAuthService; private supabaseClient: SupabaseAuthClient; + private organizationService?: OrganizationService; private constructor(config?: Partial) { this.credentialStore = new CredentialStore(config); this.supabaseClient = new SupabaseAuthClient(); this.oauthService = new OAuthService(this.credentialStore, config); + + // Initialize Supabase client with session restoration + this.initializeSupabaseSession(); + } + + /** + * Initialize Supabase session from stored credentials + */ + private async initializeSupabaseSession(): Promise { + try { + await this.supabaseClient.initialize(); + } catch (error) { + // Log but don't throw - session might not exist yet + const logger = getLogger('AuthManager'); + logger.debug('No existing session to restore'); + } } /** @@ -75,39 +99,48 @@ export class AuthManager { } /** - * Refresh authentication token + * Refresh authentication token using Supabase session */ async refreshToken(): Promise { - const authData = this.credentialStore.getCredentials({ - allowExpired: true - }); - - if (!authData || !authData.refreshToken) { - throw new AuthenticationError( - 'No refresh token available', - 'NO_REFRESH_TOKEN' - ); - } - try { - // Use Supabase client to refresh the token - const response = await this.supabaseClient.refreshSession( - authData.refreshToken - ); + // Use Supabase's built-in session refresh + const session = await this.supabaseClient.refreshSession(); - // Update authentication data + if (!session) { + throw new AuthenticationError( + 'Failed to refresh session', + 'REFRESH_FAILED' + ); + } + + // Get existing credentials to preserve context + const existingCredentials = this.credentialStore.getCredentials({ + allowExpired: true + }); + + // Update authentication data from session const newAuthData: AuthCredentials = { - ...authData, - token: response.token, - refreshToken: response.refreshToken, - expiresAt: response.expiresAt, - savedAt: new Date().toISOString() + token: session.access_token, + refreshToken: session.refresh_token, + userId: session.user.id, + email: session.user.email, + expiresAt: session.expires_at + ? new Date(session.expires_at * 1000).toISOString() + : undefined, + savedAt: new Date().toISOString(), + selectedContext: existingCredentials?.selectedContext }; this.credentialStore.saveCredentials(newAuthData); return newAuthData; } catch (error) { - throw error; + if (error instanceof AuthenticationError) { + throw error; + } + throw new AuthenticationError( + `Token refresh failed: ${(error as Error).message}`, + 'REFRESH_FAILED' + ); } } @@ -133,4 +166,114 @@ export class AuthManager { isAuthenticated(): boolean { return this.credentialStore.hasValidCredentials(); } + + /** + * Get the current user context (org/brief selection) + */ + getContext(): UserContext | null { + const credentials = this.getCredentials(); + return credentials?.selectedContext || null; + } + + /** + * Update the user context (org/brief selection) + */ + async updateContext(context: Partial): Promise { + const credentials = this.getCredentials(); + if (!credentials) { + throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED'); + } + + // Merge with existing context + const existingContext = credentials.selectedContext || {}; + const newContext: UserContext = { + ...existingContext, + ...context, + updatedAt: new Date().toISOString() + }; + + // Save updated credentials with new context + const updatedCredentials: AuthCredentials = { + ...credentials, + selectedContext: newContext + }; + + this.credentialStore.saveCredentials(updatedCredentials); + } + + /** + * Clear the user context + */ + async clearContext(): Promise { + const credentials = this.getCredentials(); + if (!credentials) { + throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED'); + } + + // Remove context from credentials + const { selectedContext, ...credentialsWithoutContext } = credentials; + this.credentialStore.saveCredentials(credentialsWithoutContext); + } + + /** + * Get the organization service instance + * Uses the Supabase client with the current session or token + */ + private async getOrganizationService(): Promise { + if (!this.organizationService) { + // First check if we have credentials with a token + const credentials = this.getCredentials(); + if (!credentials || !credentials.token) { + throw new AuthenticationError('Not authenticated', 'NOT_AUTHENTICATED'); + } + + // Initialize session if needed (this will load from our storage adapter) + await this.supabaseClient.initialize(); + + // Use the SupabaseAuthClient which now has the session + const supabaseClient = this.supabaseClient.getClient(); + this.organizationService = new OrganizationService(supabaseClient as any); + } + return this.organizationService; + } + + /** + * Get all organizations for the authenticated user + */ + async getOrganizations(): Promise { + const service = await this.getOrganizationService(); + return service.getOrganizations(); + } + + /** + * Get all briefs for a specific organization + */ + async getBriefs(orgId: string): Promise { + const service = await this.getOrganizationService(); + return service.getBriefs(orgId); + } + + /** + * Get a specific organization by ID + */ + async getOrganization(orgId: string): Promise { + const service = await this.getOrganizationService(); + return service.getOrganization(orgId); + } + + /** + * Get a specific brief by ID + */ + async getBrief(briefId: string): Promise { + const service = await this.getOrganizationService(); + return service.getBrief(briefId); + } + + /** + * Get all tasks for a specific brief + */ + async getTasks(briefId: string): Promise { + const service = await this.getOrganizationService(); + return service.getTasks(briefId); + } } diff --git a/packages/tm-core/src/auth/index.ts b/packages/tm-core/src/auth/index.ts index 29b4d1ac..4765a5e0 100644 --- a/packages/tm-core/src/auth/index.ts +++ b/packages/tm-core/src/auth/index.ts @@ -5,12 +5,19 @@ export { AuthManager } from './auth-manager.js'; export { CredentialStore } from './credential-store.js'; export { OAuthService } from './oauth-service.js'; +export { SupabaseSessionStorage } from './supabase-session-storage'; +export type { + Organization, + Brief, + RemoteTask +} from '../services/organization.service.js'; export type { AuthCredentials, OAuthFlowOptions, AuthConfig, - CliData + CliData, + UserContext } from './types.js'; export { AuthenticationError } from './types.js'; diff --git a/packages/tm-core/src/auth/oauth-service.ts b/packages/tm-core/src/auth/oauth-service.ts index 8fa84673..00c3fdec 100644 --- a/packages/tm-core/src/auth/oauth-service.ts +++ b/packages/tm-core/src/auth/oauth-service.ts @@ -181,8 +181,8 @@ export class OAuthService { timestamp: Date.now() }; - // Build authorization URL for web app sign-in page - const authUrl = new URL(`${this.baseUrl}/auth/sign-in`); + // Build authorization URL for CLI-specific sign-in page + const authUrl = new URL(`${this.baseUrl}/auth/cli/sign-in`); // Encode CLI data as base64 const cliParam = Buffer.from(JSON.stringify(cliData)).toString( @@ -272,7 +272,49 @@ export class OAuthService { return; } - // Handle direct token response from server + // Handle authorization code for PKCE flow + const code = url.searchParams.get('code'); + if (code && type === 'pkce_callback') { + try { + this.logger.info('Received authorization code for PKCE flow'); + + // Exchange code for session using PKCE + const session = await this.supabaseClient.exchangeCodeForSession(code); + + // Save authentication data + const authData: AuthCredentials = { + token: session.access_token, + refreshToken: session.refresh_token, + userId: session.user.id, + email: session.user.email, + expiresAt: session.expires_at + ? new Date(session.expires_at * 1000).toISOString() + : undefined, + tokenType: 'standard', + savedAt: new Date().toISOString() + }; + + this.credentialStore.saveCredentials(authData); + + if (server.listening) { + server.close(); + } + // Clear timeout since authentication succeeded + if (timeoutId) { + clearTimeout(timeoutId); + } + resolve(authData); + return; + } catch (error) { + if (server.listening) { + server.close(); + } + reject(error); + return; + } + } + + // Handle direct token response from server (legacy flow) if ( accessToken && (type === 'oauth_success' || type === 'session_transfer') @@ -280,8 +322,23 @@ export class OAuthService { try { this.logger.info(`Received tokens via ${type}`); - // Get user info using the access token if possible - const user = await this.supabaseClient.getUser(accessToken); + // Create a session with the tokens and set it in Supabase client + const session = { + access_token: accessToken, + refresh_token: refreshToken || '', + expires_at: expiresIn + ? Math.floor(Date.now() / 1000) + parseInt(expiresIn) + : undefined, + expires_in: expiresIn ? parseInt(expiresIn) : undefined, + token_type: 'bearer', + user: null as any // Will be populated by setSession + }; + + // Set the session in Supabase client + await this.supabaseClient.setSession(session as any); + + // Get user info from the session + const user = await this.supabaseClient.getUser(); // Calculate expiration time const expiresAt = expiresIn diff --git a/packages/tm-core/src/auth/supabase-session-storage.ts b/packages/tm-core/src/auth/supabase-session-storage.ts new file mode 100644 index 00000000..51ddc83e --- /dev/null +++ b/packages/tm-core/src/auth/supabase-session-storage.ts @@ -0,0 +1,155 @@ +/** + * Custom storage adapter for Supabase Auth sessions in CLI environment + * Implements the SupportedStorage interface required by Supabase Auth + * + * This adapter bridges Supabase's session management with our existing + * auth.json credential storage, maintaining backward compatibility + */ + +import { SupportedStorage } from '@supabase/supabase-js'; +import { CredentialStore } from './credential-store'; +import { AuthCredentials } from './types'; +import { getLogger } from '../logger'; + +const STORAGE_KEY = 'sb-taskmaster-auth-token'; + +export class SupabaseSessionStorage implements SupportedStorage { + private store: CredentialStore; + private logger = getLogger('SupabaseSessionStorage'); + + constructor(store: CredentialStore) { + this.store = store; + } + + /** + * Build a Supabase session object from our credentials + */ + private buildSessionFromCredentials(credentials: AuthCredentials): any { + // Create a session object that Supabase expects + const session = { + access_token: credentials.token, + refresh_token: credentials.refreshToken || '', + expires_at: credentials.expiresAt + ? Math.floor(new Date(credentials.expiresAt).getTime() / 1000) + : Math.floor(Date.now() / 1000) + 3600, // Default to 1 hour + token_type: 'bearer', + user: { + id: credentials.userId, + email: credentials.email || '', + aud: 'authenticated', + role: 'authenticated', + email_confirmed_at: new Date().toISOString(), + app_metadata: {}, + user_metadata: {}, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + } + }; + return session; + } + + /** + * Parse a Supabase session back to our credentials + */ + private parseSessionToCredentials( + sessionData: any + ): Partial { + try { + const session = JSON.parse(sessionData); + return { + token: session.access_token, + refreshToken: session.refresh_token, + userId: session.user?.id || 'unknown', + email: session.user?.email, + expiresAt: session.expires_at + ? new Date(session.expires_at * 1000).toISOString() + : undefined + }; + } catch (error) { + this.logger.error('Error parsing session:', error); + return {}; + } + } + + /** + * Get item from storage - Supabase will request the session with a specific key + */ + getItem(key: string): string | null { + // Supabase uses a specific key pattern for sessions + if (key === STORAGE_KEY || key.includes('auth-token')) { + try { + const credentials = this.store.getCredentials({ allowExpired: true }); + if (credentials && credentials.token) { + // Build and return a session object from our stored credentials + const session = this.buildSessionFromCredentials(credentials); + return JSON.stringify(session); + } + } catch (error) { + this.logger.error('Error getting session:', error); + } + } + return null; + } + + /** + * Set item in storage - Supabase will store the session with a specific key + */ + setItem(key: string, value: string): void { + // Only handle Supabase session keys + if (key === STORAGE_KEY || key.includes('auth-token')) { + try { + // Parse the session and update our credentials + const sessionUpdates = this.parseSessionToCredentials(value); + const existingCredentials = this.store.getCredentials({ + allowExpired: true + }); + + if (sessionUpdates.token) { + const updatedCredentials: AuthCredentials = { + ...existingCredentials, + ...sessionUpdates, + savedAt: new Date().toISOString(), + selectedContext: existingCredentials?.selectedContext + } as AuthCredentials; + + this.store.saveCredentials(updatedCredentials); + } + } catch (error) { + this.logger.error('Error setting session:', error); + } + } + } + + /** + * Remove item from storage - Called when signing out + */ + removeItem(key: string): void { + if (key === STORAGE_KEY || key.includes('auth-token')) { + // Don't actually remove credentials, just clear the tokens + // This preserves other data like selectedContext + try { + const credentials = this.store.getCredentials({ allowExpired: true }); + if (credentials) { + // Keep context but clear auth tokens + const clearedCredentials: AuthCredentials = { + ...credentials, + token: '', + refreshToken: undefined, + expiresAt: undefined + } as AuthCredentials; + this.store.saveCredentials(clearedCredentials); + } + } catch (error) { + this.logger.error('Error removing session:', error); + } + } + } + + /** + * Clear all session data + */ + clear(): void { + // Clear auth tokens but preserve context + this.removeItem(STORAGE_KEY); + } +} diff --git a/packages/tm-core/src/auth/types.ts b/packages/tm-core/src/auth/types.ts index a2dc5d72..f6108f31 100644 --- a/packages/tm-core/src/auth/types.ts +++ b/packages/tm-core/src/auth/types.ts @@ -10,6 +10,15 @@ export interface AuthCredentials { expiresAt?: string | number; tokenType?: 'standard'; savedAt: string; + selectedContext?: UserContext; +} + +export interface UserContext { + orgId?: string; + orgName?: string; + briefId?: string; + briefName?: string; + updatedAt: string; } export interface OAuthFlowOptions { @@ -67,7 +76,11 @@ export type AuthErrorCode = | 'STORAGE_ERROR' | 'NOT_SUPPORTED' | 'REFRESH_FAILED' - | 'INVALID_RESPONSE'; + | 'INVALID_RESPONSE' + | 'PKCE_INIT_FAILED' + | 'PKCE_FAILED' + | 'CODE_EXCHANGE_FAILED' + | 'SESSION_SET_FAILED'; /** * Authentication error class diff --git a/packages/tm-core/src/clients/supabase-client.ts b/packages/tm-core/src/clients/supabase-client.ts index ad326b3a..24606871 100644 --- a/packages/tm-core/src/clients/supabase-client.ts +++ b/packages/tm-core/src/clients/supabase-client.ts @@ -1,19 +1,32 @@ /** - * Supabase client for authentication + * Supabase authentication client for CLI auth flows */ -import { createClient, SupabaseClient, User } from '@supabase/supabase-js'; +import { + createClient, + SupabaseClient as SupabaseJSClient, + User, + Session +} from '@supabase/supabase-js'; import { AuthenticationError } from '../auth/types.js'; import { getLogger } from '../logger/index.js'; +import { SupabaseSessionStorage } from '../auth/supabase-session-storage'; +import { CredentialStore } from '../auth/credential-store'; export class SupabaseAuthClient { - private client: SupabaseClient | null = null; + private client: SupabaseJSClient | null = null; + private sessionStorage: SupabaseSessionStorage; private logger = getLogger('SupabaseAuthClient'); + constructor() { + const credentialStore = new CredentialStore(); + this.sessionStorage = new SupabaseSessionStorage(credentialStore); + } + /** - * Initialize Supabase client + * Get Supabase client with proper session management */ - private getClient(): SupabaseClient { + getClient(): SupabaseJSClient { if (!this.client) { // Get Supabase configuration from environment - using TM_PUBLIC prefix const supabaseUrl = process.env.TM_PUBLIC_SUPABASE_URL; @@ -26,10 +39,12 @@ export class SupabaseAuthClient { ); } + // Create client with custom storage adapter (similar to React Native AsyncStorage) this.client = createClient(supabaseUrl, supabaseAnonKey, { auth: { + storage: this.sessionStorage, autoRefreshToken: true, - persistSession: false, // We handle persistence ourselves + persistSession: true, detectSessionInUrl: false } }); @@ -39,40 +54,159 @@ export class SupabaseAuthClient { } /** - * Note: Code exchange is now handled server-side - * The server returns tokens directly to avoid PKCE issues - * This method is kept for potential future use + * Initialize the client and restore session if available */ - async exchangeCodeForSession(_code: string): Promise<{ - token: string; - refreshToken?: string; - userId: string; - email?: string; - expiresAt?: string; - }> { - throw new AuthenticationError( - 'Code exchange is handled server-side. CLI receives tokens directly.', - 'NOT_SUPPORTED' - ); + async initialize(): Promise { + const client = this.getClient(); + + try { + // Get the current session from storage + const { + data: { session }, + error + } = await client.auth.getSession(); + + if (error) { + this.logger.warn('Failed to restore session:', error); + return null; + } + + if (session) { + this.logger.info('Session restored successfully'); + } + + return session; + } catch (error) { + this.logger.error('Error initializing session:', error); + return null; + } } /** - * Refresh an access token + * Sign in with PKCE flow (for CLI auth) */ - async refreshSession(refreshToken: string): Promise<{ - token: string; - refreshToken?: string; - expiresAt?: string; - }> { - try { - const client = this.getClient(); + async signInWithPKCE(): Promise<{ url: string; codeVerifier: string }> { + const client = this.getClient(); + try { + // Generate PKCE challenge + const { data, error } = await client.auth.signInWithOAuth({ + provider: 'github', + options: { + redirectTo: + process.env.TM_AUTH_CALLBACK_URL || + 'http://localhost:3421/auth/callback', + scopes: 'email' + } + }); + + if (error) { + throw new AuthenticationError( + `Failed to initiate PKCE flow: ${error.message}`, + 'PKCE_INIT_FAILED' + ); + } + + if (!data?.url) { + throw new AuthenticationError( + 'No authorization URL returned', + 'INVALID_RESPONSE' + ); + } + + // Extract code_verifier from the URL or generate it + // Note: Supabase handles PKCE internally, we just need to handle the callback + return { + url: data.url, + codeVerifier: '' // Supabase manages this internally + }; + } catch (error) { + if (error instanceof AuthenticationError) { + throw error; + } + + throw new AuthenticationError( + `Failed to start PKCE flow: ${(error as Error).message}`, + 'PKCE_FAILED' + ); + } + } + + /** + * Exchange authorization code for session (PKCE flow) + */ + async exchangeCodeForSession(code: string): Promise { + const client = this.getClient(); + + try { + const { data, error } = await client.auth.exchangeCodeForSession(code); + + if (error) { + throw new AuthenticationError( + `Failed to exchange code: ${error.message}`, + 'CODE_EXCHANGE_FAILED' + ); + } + + if (!data?.session) { + throw new AuthenticationError( + 'No session returned from code exchange', + 'INVALID_RESPONSE' + ); + } + + this.logger.info('Successfully exchanged code for session'); + return data.session; + } catch (error) { + if (error instanceof AuthenticationError) { + throw error; + } + + throw new AuthenticationError( + `Code exchange failed: ${(error as Error).message}`, + 'CODE_EXCHANGE_FAILED' + ); + } + } + + /** + * Get the current session + */ + async getSession(): Promise { + const client = this.getClient(); + + try { + const { + data: { session }, + error + } = await client.auth.getSession(); + + if (error) { + this.logger.warn('Failed to get session:', error); + return null; + } + + return session; + } catch (error) { + this.logger.error('Error getting session:', error); + return null; + } + } + + /** + * Refresh the current session + */ + async refreshSession(): Promise { + const client = this.getClient(); + + try { this.logger.info('Refreshing session...'); - // Set the session with refresh token - const { data, error } = await client.auth.refreshSession({ - refresh_token: refreshToken - }); + // Supabase will automatically use the stored refresh token + const { + data: { session }, + error + } = await client.auth.refreshSession(); if (error) { this.logger.error('Failed to refresh session:', error); @@ -82,22 +216,11 @@ export class SupabaseAuthClient { ); } - if (!data.session) { - throw new AuthenticationError( - 'No session data returned', - 'INVALID_RESPONSE' - ); + if (session) { + this.logger.info('Successfully refreshed session'); } - this.logger.info('Successfully refreshed session'); - - return { - token: data.session.access_token, - refreshToken: data.session.refresh_token, - expiresAt: data.session.expires_at - ? new Date(data.session.expires_at * 1000).toISOString() - : undefined - }; + return session; } catch (error) { if (error instanceof AuthenticationError) { throw error; @@ -111,21 +234,23 @@ export class SupabaseAuthClient { } /** - * Get user details from token + * Get current user from session */ - async getUser(token: string): Promise { - try { - const client = this.getClient(); + async getUser(): Promise { + const client = this.getClient(); - // Get user with the token - const { data, error } = await client.auth.getUser(token); + try { + const { + data: { user }, + error + } = await client.auth.getUser(); if (error) { this.logger.warn('Failed to get user:', error); return null; } - return data.user; + return user; } catch (error) { this.logger.error('Error getting user:', error); return null; @@ -133,22 +258,55 @@ export class SupabaseAuthClient { } /** - * Sign out (revoke tokens) - * Note: This requires the user to be authenticated with the current session. - * For remote token revocation, a server-side admin API with service_role key would be needed. + * Sign out and clear session */ async signOut(): Promise { - try { - const client = this.getClient(); + const client = this.getClient(); - // Sign out the current session with global scope to revoke all refresh tokens + try { + // Sign out with global scope to revoke all refresh tokens const { error } = await client.auth.signOut({ scope: 'global' }); if (error) { this.logger.warn('Failed to sign out:', error); } + + // Clear cached session data + this.sessionStorage.clear(); } catch (error) { this.logger.error('Error during sign out:', error); } } + + /** + * Set session from external auth (e.g., from server callback) + */ + async setSession(session: Session): Promise { + const client = this.getClient(); + + try { + const { error } = await client.auth.setSession({ + access_token: session.access_token, + refresh_token: session.refresh_token + }); + + if (error) { + throw new AuthenticationError( + `Failed to set session: ${error.message}`, + 'SESSION_SET_FAILED' + ); + } + + this.logger.info('Session set successfully'); + } catch (error) { + if (error instanceof AuthenticationError) { + throw error; + } + + throw new AuthenticationError( + `Failed to set session: ${(error as Error).message}`, + 'SESSION_SET_FAILED' + ); + } + } } diff --git a/packages/tm-core/src/config/config-manager.spec.ts b/packages/tm-core/src/config/config-manager.spec.ts index a85744d5..00a58683 100644 --- a/packages/tm-core/src/config/config-manager.spec.ts +++ b/packages/tm-core/src/config/config-manager.spec.ts @@ -177,7 +177,7 @@ describe('ConfigManager', () => { it('should return storage configuration', () => { const storage = manager.getStorageConfig(); - expect(storage).toEqual({ type: 'auto', apiConfigured: false }); + expect(storage).toEqual({ type: 'file' }); }); it('should return API storage configuration when configured', async () => { @@ -206,65 +206,7 @@ describe('ConfigManager', () => { expect(storage).toEqual({ type: 'api', apiEndpoint: 'https://api.example.com', - apiAccessToken: 'token123', - apiConfigured: true - }); - }); - - it('should return auto storage configuration with apiConfigured flag', async () => { - // Create a new instance with auto storage config and partial API settings - vi.mocked(ConfigMerger).mockImplementationOnce( - () => - ({ - addSource: vi.fn(), - clearSources: vi.fn(), - merge: vi.fn().mockReturnValue({ - storage: { - type: 'auto', - apiEndpoint: 'https://api.example.com' - // No apiAccessToken - partial config - } - }), - getSources: vi.fn().mockReturnValue([]) - }) as any - ); - - const autoManager = await ConfigManager.create(testProjectRoot); - - const storage = autoManager.getStorageConfig(); - expect(storage).toEqual({ - type: 'auto', - apiEndpoint: 'https://api.example.com', - apiAccessToken: undefined, - apiConfigured: true // true because apiEndpoint is provided - }); - }); - - it('should return auto storage with apiConfigured false when no API settings', async () => { - // Create a new instance with auto storage but no API settings - vi.mocked(ConfigMerger).mockImplementationOnce( - () => - ({ - addSource: vi.fn(), - clearSources: vi.fn(), - merge: vi.fn().mockReturnValue({ - storage: { - type: 'auto' - // No API settings at all - } - }), - getSources: vi.fn().mockReturnValue([]) - }) as any - ); - - const autoManager = await ConfigManager.create(testProjectRoot); - - const storage = autoManager.getStorageConfig(); - expect(storage).toEqual({ - type: 'auto', - apiEndpoint: undefined, - apiAccessToken: undefined, - apiConfigured: false // false because no API settings + apiAccessToken: 'token123' }); }); diff --git a/packages/tm-core/src/config/services/environment-config-provider.service.spec.ts b/packages/tm-core/src/config/services/environment-config-provider.service.spec.ts index 9c4ef8b3..36fa579c 100644 --- a/packages/tm-core/src/config/services/environment-config-provider.service.spec.ts +++ b/packages/tm-core/src/config/services/environment-config-provider.service.spec.ts @@ -85,11 +85,6 @@ describe('EnvironmentConfigProvider', () => { provider = new EnvironmentConfigProvider(); // Reset provider config = provider.loadConfig(); expect(config.storage?.type).toBe('api'); - - process.env.TASKMASTER_STORAGE_TYPE = 'auto'; - provider = new EnvironmentConfigProvider(); // Reset provider - config = provider.loadConfig(); - expect(config.storage?.type).toBe('auto'); }); it('should handle nested configuration paths', () => { diff --git a/packages/tm-core/src/config/services/environment-config-provider.service.ts b/packages/tm-core/src/config/services/environment-config-provider.service.ts index 75061519..ec06d79e 100644 --- a/packages/tm-core/src/config/services/environment-config-provider.service.ts +++ b/packages/tm-core/src/config/services/environment-config-provider.service.ts @@ -31,7 +31,7 @@ export class EnvironmentConfigProvider { { env: 'TASKMASTER_STORAGE_TYPE', path: ['storage', 'type'], - validate: (v: string) => ['file', 'api', 'auto'].includes(v) + validate: (v: string) => ['file', 'api'].includes(v) }, { env: 'TASKMASTER_API_ENDPOINT', path: ['storage', 'apiEndpoint'] }, { env: 'TASKMASTER_API_TOKEN', path: ['storage', 'apiAccessToken'] }, diff --git a/packages/tm-core/src/mappers/TaskMapper.ts b/packages/tm-core/src/mappers/TaskMapper.ts new file mode 100644 index 00000000..f1b9be2b --- /dev/null +++ b/packages/tm-core/src/mappers/TaskMapper.ts @@ -0,0 +1,170 @@ +import { Task, Subtask } from '../types/index.js'; +import { Database, Tables } from '../types/database.types.js'; + +type TaskRow = Tables<'tasks'>; +type DependencyRow = Tables<'task_dependencies'>; + +export class TaskMapper { + /** + * Maps database tasks to internal Task format + */ + static mapDatabaseTasksToTasks( + dbTasks: TaskRow[], + dbDependencies: DependencyRow[] + ): Task[] { + if (!dbTasks || dbTasks.length === 0) { + return []; + } + + // Group dependencies by task_id + const dependenciesByTaskId = this.groupDependenciesByTaskId(dbDependencies); + + // Separate parent tasks and subtasks + const parentTasks = dbTasks.filter((t) => !t.parent_task_id); + const subtasksByParentId = this.groupSubtasksByParentId(dbTasks); + + // Map parent tasks with their subtasks + return parentTasks.map((taskRow) => + this.mapDatabaseTaskToTask( + taskRow, + subtasksByParentId.get(taskRow.id) || [], + dependenciesByTaskId + ) + ); + } + + /** + * Maps a single database task to internal Task format + */ + static mapDatabaseTaskToTask( + dbTask: TaskRow, + dbSubtasks: TaskRow[], + dependenciesByTaskId: Map + ): Task { + // Map subtasks + const subtasks: Subtask[] = dbSubtasks.map((subtask, index) => ({ + id: index + 1, // Use numeric ID for subtasks + parentId: dbTask.id, + title: subtask.title, + description: subtask.description || '', + status: this.mapStatus(subtask.status), + priority: this.mapPriority(subtask.priority), + dependencies: dependenciesByTaskId.get(subtask.id) || [], + details: (subtask.metadata as any)?.details || '', + testStrategy: (subtask.metadata as any)?.testStrategy || '', + createdAt: subtask.created_at, + updatedAt: subtask.updated_at, + assignee: subtask.assignee_id || undefined, + complexity: subtask.complexity + ? this.mapComplexityToInternal(subtask.complexity) + : undefined + })); + + return { + id: dbTask.display_id || dbTask.id, // Use display_id if available + title: dbTask.title, + description: dbTask.description || '', + status: this.mapStatus(dbTask.status), + priority: this.mapPriority(dbTask.priority), + dependencies: dependenciesByTaskId.get(dbTask.id) || [], + details: (dbTask.metadata as any)?.details || '', + testStrategy: (dbTask.metadata as any)?.testStrategy || '', + subtasks, + createdAt: dbTask.created_at, + updatedAt: dbTask.updated_at, + assignee: dbTask.assignee_id || undefined, + complexity: dbTask.complexity + ? this.mapComplexityToInternal(dbTask.complexity) + : undefined, + effort: dbTask.estimated_hours || undefined, + actualEffort: dbTask.actual_hours || undefined + }; + } + + /** + * Groups dependencies by task ID + */ + private static groupDependenciesByTaskId( + dependencies: DependencyRow[] + ): Map { + const dependenciesByTaskId = new Map(); + + if (dependencies) { + for (const dep of dependencies) { + const deps = dependenciesByTaskId.get(dep.task_id) || []; + deps.push(dep.depends_on_task_id); + dependenciesByTaskId.set(dep.task_id, deps); + } + } + + return dependenciesByTaskId; + } + + /** + * Groups subtasks by their parent ID + */ + private static groupSubtasksByParentId( + tasks: TaskRow[] + ): Map { + const subtasksByParentId = new Map(); + + for (const task of tasks) { + if (task.parent_task_id) { + const subtasks = subtasksByParentId.get(task.parent_task_id) || []; + subtasks.push(task); + subtasksByParentId.set(task.parent_task_id, subtasks); + } + } + + // Sort subtasks by subtask_position for each parent + for (const subtasks of subtasksByParentId.values()) { + subtasks.sort((a, b) => a.subtask_position - b.subtask_position); + } + + return subtasksByParentId; + } + + /** + * Maps database status to internal status + */ + private static mapStatus( + status: Database['public']['Enums']['task_status'] + ): Task['status'] { + switch (status) { + case 'todo': + return 'pending'; + case 'in_progress': + return 'in-progress'; + case 'done': + return 'done'; + default: + return 'pending'; + } + } + + /** + * Maps database priority to internal priority + */ + private static mapPriority( + priority: Database['public']['Enums']['task_priority'] + ): Task['priority'] { + switch (priority) { + case 'urgent': + return 'critical'; + default: + return priority as Task['priority']; + } + } + + /** + * Maps numeric complexity to descriptive complexity + */ + private static mapComplexityToInternal( + complexity: number + ): Task['complexity'] { + if (complexity <= 2) return 'simple'; + if (complexity <= 5) return 'moderate'; + if (complexity <= 8) return 'complex'; + return 'very-complex'; + } +} diff --git a/packages/tm-core/src/repositories/supabase-task-repository.ts b/packages/tm-core/src/repositories/supabase-task-repository.ts new file mode 100644 index 00000000..a9dd5c4b --- /dev/null +++ b/packages/tm-core/src/repositories/supabase-task-repository.ts @@ -0,0 +1,110 @@ +import { SupabaseClient } from '@supabase/supabase-js'; +import { Task } from '../types/index.js'; +import { Database } from '../types/database.types.js'; +import { TaskMapper } from '../mappers/TaskMapper.js'; +import { AuthManager } from '../auth/auth-manager.js'; + +export class SupabaseTaskRepository { + constructor(private supabase: SupabaseClient) {} + + async getTasks(_projectId?: string): Promise { + // Get the current context to determine briefId + const authManager = AuthManager.getInstance(); + const context = authManager.getContext(); + + if (!context || !context.briefId) { + throw new Error( + 'No brief selected. Please select a brief first using: tm context brief' + ); + } + + // Get all tasks for the brief using the exact query structure + const { data: tasks, error } = await this.supabase + .from('tasks') + .select(` + *, + document:document_id ( + id, + document_name, + title, + description + ) + `) + .eq('brief_id', context.briefId) + .order('position', { ascending: true }) + .order('subtask_position', { ascending: true }) + .order('created_at', { ascending: true }); + + if (error) { + throw new Error(`Failed to fetch tasks: ${error.message}`); + } + + if (!tasks || tasks.length === 0) { + return []; + } + + // Get all dependencies for these tasks + const taskIds = tasks.map((t: any) => t.id); + const { data: depsData, error: depsError } = await this.supabase + .from('task_dependencies') + .select('*') + .in('task_id', taskIds); + + if (depsError) { + throw new Error( + `Failed to fetch task dependencies: ${depsError.message}` + ); + } + + // Use mapper to convert to internal format + return TaskMapper.mapDatabaseTasksToTasks(tasks, depsData || []); + } + + async getTask(accountId: string, taskId: string): Promise { + const { data, error } = await this.supabase + .from('tasks') + .select('*') + .eq('account_id', accountId) + .eq('id', taskId) + .single(); + + if (error) { + if (error.code === 'PGRST116') { + return null; // Not found + } + throw new Error(`Failed to fetch task: ${error.message}`); + } + + // Get dependencies for this task + const { data: depsData } = await this.supabase + .from('task_dependencies') + .select('*') + .eq('task_id', taskId); + + // Get subtasks if this is a parent task + const { data: subtasksData } = await this.supabase + .from('tasks') + .select('*') + .eq('parent_task_id', taskId) + .order('subtask_position', { ascending: true }); + + // Create dependency map + const dependenciesByTaskId = new Map(); + if (depsData) { + dependenciesByTaskId.set( + taskId, + depsData.map( + (d: Database['public']['Tables']['task_dependencies']['Row']) => + d.depends_on_task_id + ) + ); + } + + // Use mapper to convert single task + return TaskMapper.mapDatabaseTaskToTask( + data, + subtasksData || [], + dependenciesByTaskId + ); + } +} diff --git a/packages/tm-core/src/repositories/task-repository.interface.ts b/packages/tm-core/src/repositories/task-repository.interface.ts new file mode 100644 index 00000000..d256e551 --- /dev/null +++ b/packages/tm-core/src/repositories/task-repository.interface.ts @@ -0,0 +1,36 @@ +import { Task, TaskTag } from '../types/index.js'; + +export interface TaskRepository { + // Task operations + getTasks(projectId: string): Promise; + getTask(projectId: string, taskId: string): Promise; + createTask(projectId: string, task: Omit): Promise; + updateTask( + projectId: string, + taskId: string, + updates: Partial + ): Promise; + deleteTask(projectId: string, taskId: string): Promise; + + // Tag operations + getTags(projectId: string): Promise; + getTag(projectId: string, tagName: string): Promise; + createTag(projectId: string, tag: TaskTag): Promise; + updateTag( + projectId: string, + tagName: string, + updates: Partial + ): Promise; + deleteTag(projectId: string, tagName: string): Promise; + + // Bulk operations + bulkCreateTasks( + projectId: string, + tasks: Omit[] + ): Promise; + bulkUpdateTasks( + projectId: string, + updates: Array<{ id: string; updates: Partial }> + ): Promise; + bulkDeleteTasks(projectId: string, taskIds: string[]): Promise; +} diff --git a/packages/tm-core/src/services/index.ts b/packages/tm-core/src/services/index.ts index 8cc3ebeb..d436f950 100644 --- a/packages/tm-core/src/services/index.ts +++ b/packages/tm-core/src/services/index.ts @@ -4,3 +4,5 @@ */ export { TaskService } from './task-service.js'; +export { OrganizationService } from './organization.service.js'; +export type { Organization, Brief } from './organization.service.js'; diff --git a/packages/tm-core/src/services/organization.service.ts b/packages/tm-core/src/services/organization.service.ts new file mode 100644 index 00000000..fadbd781 --- /dev/null +++ b/packages/tm-core/src/services/organization.service.ts @@ -0,0 +1,363 @@ +/** + * @fileoverview Organization and Brief management service + * Handles fetching and managing organizations and briefs from the API + */ + +import { SupabaseClient } from '@supabase/supabase-js'; +import { Database } from '../types/database.types.js'; +import { TaskMasterError, ERROR_CODES } from '../errors/task-master-error.js'; +import { getLogger } from '../logger/index.js'; + +/** + * Organization data structure + */ +export interface Organization { + id: string; + name: string; + slug: string; +} + +/** + * Brief data structure + */ +export interface Brief { + id: string; + accountId: string; + documentId: string; + status: string; + createdAt: string; + updatedAt: string; +} + +/** + * Task data structure from the remote database + */ +export interface RemoteTask { + id: string; + briefId: string; + documentId: string; + position: number | null; + subtaskPosition: number | null; + status: string; + createdAt: string; + updatedAt: string; + // Document details from join + document?: { + id: string; + document_name: string; + title: string; + description: string; + }; +} + +/** + * Service for managing organizations and briefs + */ +export class OrganizationService { + private logger = getLogger('OrganizationService'); + + constructor(private supabaseClient: SupabaseClient) {} + + /** + * Get all organizations for the authenticated user + */ + async getOrganizations(): Promise { + try { + // The user is already authenticated via the Authorization header + // Query the user_accounts view/table (filtered by RLS for current user) + const { data, error } = await this.supabaseClient + .from('user_accounts') + .select(` + id, + name, + slug + `); + + if (error) { + throw new TaskMasterError( + `Failed to fetch organizations: ${error.message}`, + ERROR_CODES.API_ERROR, + { operation: 'getOrganizations' }, + error + ); + } + + if (!data || data.length === 0) { + this.logger.debug('No organizations found for user'); + return []; + } + + // Map to our Organization interface + return data.map((org) => ({ + id: org.id ?? '', + name: org.name ?? '', + slug: org.slug ?? org.id ?? '' // Use ID as fallback if slug is null + })); + } catch (error) { + if (error instanceof TaskMasterError) { + throw error; + } + throw new TaskMasterError( + 'Failed to fetch organizations', + ERROR_CODES.API_ERROR, + { operation: 'getOrganizations' }, + error as Error + ); + } + } + + /** + * Get a specific organization by ID + */ + async getOrganization(orgId: string): Promise { + try { + const { data, error } = await this.supabaseClient + .from('accounts') + .select(` + id, + name, + slug + `) + .eq('id', orgId) + .single(); + + if (error) { + if (error.code === 'PGRST116') { + // No rows found + return null; + } + throw new TaskMasterError( + `Failed to fetch organization: ${error.message}`, + ERROR_CODES.API_ERROR, + { operation: 'getOrganization', orgId }, + error + ); + } + + if (!data) { + return null; + } + + const accountData = + data as Database['public']['Tables']['accounts']['Row']; + return { + id: accountData.id, + name: accountData.name, + slug: accountData.slug || accountData.id + }; + } catch (error) { + if (error instanceof TaskMasterError) { + throw error; + } + throw new TaskMasterError( + 'Failed to fetch organization', + ERROR_CODES.API_ERROR, + { operation: 'getOrganization', orgId }, + error as Error + ); + } + } + + /** + * Get all briefs for a specific organization + */ + async getBriefs(orgId: string): Promise { + try { + const { data, error } = await this.supabaseClient + .from('brief') + .select(` + id, + account_id, + document_id, + status, + created_at, + updated_at + `) + .eq('account_id', orgId); + + if (error) { + throw new TaskMasterError( + `Failed to fetch briefs: ${error.message}`, + ERROR_CODES.API_ERROR, + { operation: 'getBriefs', orgId }, + error + ); + } + + if (!data || data.length === 0) { + this.logger.debug(`No briefs found for organization ${orgId}`); + return []; + } + + // Map to our Brief interface + return data.map((brief: any) => ({ + id: brief.id, + accountId: brief.account_id, + documentId: brief.document_id, + status: brief.status, + createdAt: brief.created_at, + updatedAt: brief.updated_at + })); + } catch (error) { + if (error instanceof TaskMasterError) { + throw error; + } + throw new TaskMasterError( + 'Failed to fetch briefs', + ERROR_CODES.API_ERROR, + { operation: 'getBriefs', orgId }, + error as Error + ); + } + } + + /** + * Get a specific brief by ID + */ + async getBrief(briefId: string): Promise { + try { + const { data, error } = await this.supabaseClient + .from('brief') + .select(` + id, + account_id, + document_id, + status, + created_at, + updated_at + `) + .eq('id', briefId) + .single(); + + if (error) { + if (error.code === 'PGRST116') { + // No rows found + return null; + } + throw new TaskMasterError( + `Failed to fetch brief: ${error.message}`, + ERROR_CODES.API_ERROR, + { operation: 'getBrief', briefId }, + error + ); + } + + if (!data) { + return null; + } + + const briefData = data as any; + return { + id: briefData.id, + accountId: briefData.account_id, + documentId: briefData.document_id, + status: briefData.status, + createdAt: briefData.created_at, + updatedAt: briefData.updated_at + }; + } catch (error) { + if (error instanceof TaskMasterError) { + throw error; + } + throw new TaskMasterError( + 'Failed to fetch brief', + ERROR_CODES.API_ERROR, + { operation: 'getBrief', briefId }, + error as Error + ); + } + } + + /** + * Validate that a user has access to an organization + */ + async validateOrgAccess(orgId: string): Promise { + try { + const org = await this.getOrganization(orgId); + return org !== null; + } catch (error) { + this.logger.error(`Failed to validate org access: ${error}`); + return false; + } + } + + /** + * Validate that a user has access to a brief + */ + async validateBriefAccess(briefId: string): Promise { + try { + const brief = await this.getBrief(briefId); + return brief !== null; + } catch (error) { + this.logger.error(`Failed to validate brief access: ${error}`); + return false; + } + } + + /** + * Get all tasks for a specific brief + */ + async getTasks(briefId: string): Promise { + try { + const { data, error } = await this.supabaseClient + .from('tasks') + .select(` + *, + document:document_id ( + id, + document_name, + title, + description + ) + `) + .eq('brief_id', briefId) + .order('position', { ascending: true }) + .order('subtask_position', { ascending: true }) + .order('created_at', { ascending: true }); + + if (error) { + throw new TaskMasterError( + `Failed to fetch tasks: ${error.message}`, + ERROR_CODES.API_ERROR, + { operation: 'getTasks', briefId }, + error + ); + } + + if (!data || data.length === 0) { + this.logger.debug(`No tasks found for brief ${briefId}`); + return []; + } + + // Map to our RemoteTask interface + return data.map((task: any) => ({ + id: task.id, + briefId: task.brief_id, + documentId: task.document_id, + position: task.position, + subtaskPosition: task.subtask_position, + status: task.status, + createdAt: task.created_at, + updatedAt: task.updated_at, + document: task.document + ? { + id: task.document.id, + document_name: task.document.document_name, + title: task.document.title, + description: task.document.description + } + : undefined + })); + } catch (error) { + if (error instanceof TaskMasterError) { + throw error; + } + throw new TaskMasterError( + 'Failed to fetch tasks', + ERROR_CODES.API_ERROR, + { operation: 'getTasks', briefId }, + error as Error + ); + } + } +} diff --git a/packages/tm-core/src/services/task-service.ts b/packages/tm-core/src/services/task-service.ts index d607777f..137d4564 100644 --- a/packages/tm-core/src/services/task-service.ts +++ b/packages/tm-core/src/services/task-service.ts @@ -22,8 +22,8 @@ export interface TaskListResult { filtered: number; /** The tag these tasks belong to (only present if explicitly provided) */ tag?: string; - /** Storage type being used - includes 'auto' for automatic detection */ - storageType: 'file' | 'api' | 'auto'; + /** Storage type being used */ + storageType: 'file' | 'api'; } /** @@ -166,7 +166,7 @@ export class TaskService { byStatus: Record; withSubtasks: number; blocked: number; - storageType: 'file' | 'api' | 'auto'; + storageType: 'file' | 'api'; }> { const result = await this.getTaskList({ tag, @@ -334,7 +334,7 @@ export class TaskService { /** * Get current storage type */ - getStorageType(): 'file' | 'api' | 'auto' { + getStorageType(): 'file' | 'api' { return this.configManager.getStorageConfig().type; } diff --git a/packages/tm-core/src/storage/api-storage.ts b/packages/tm-core/src/storage/api-storage.ts index 4e443979..e23eb0ca 100644 --- a/packages/tm-core/src/storage/api-storage.ts +++ b/packages/tm-core/src/storage/api-storage.ts @@ -1,27 +1,29 @@ /** - * @fileoverview API-based storage implementation for Hamster integration - * This provides storage via REST API instead of local file system + * @fileoverview API-based storage implementation using repository pattern + * This provides storage via repository abstraction for flexibility */ import type { IStorage, StorageStats } from '../interfaces/storage.interface.js'; -import type { Task, TaskMetadata } from '../types/index.js'; +import type { Task, TaskMetadata, TaskTag } from '../types/index.js'; import { ERROR_CODES, TaskMasterError } from '../errors/task-master-error.js'; +import { TaskRepository } from '../repositories/task-repository.interface.js'; +import { SupabaseTaskRepository } from '../repositories/supabase-task-repository.js'; +import { SupabaseClient } from '@supabase/supabase-js'; +import { AuthManager } from '../auth/auth-manager.js'; /** * API storage configuration */ export interface ApiStorageConfig { - /** API endpoint base URL */ - endpoint: string; - /** Access token for authentication */ - accessToken: string; - /** Optional project ID */ - projectId?: string; - /** Request timeout in milliseconds */ - timeout?: number; + /** Supabase client instance */ + supabaseClient?: SupabaseClient; + /** Custom repository implementation */ + repository?: TaskRepository; + /** Project ID for scoping */ + projectId: string; /** Enable request retries */ enableRetry?: boolean; /** Maximum retry attempts */ @@ -29,64 +31,58 @@ export interface ApiStorageConfig { } /** - * API response wrapper - */ -interface ApiResponse { - success: boolean; - data?: T; - error?: string; - message?: string; -} - -/** - * ApiStorage implementation for Hamster integration - * Fetches and stores tasks via REST API + * ApiStorage implementation using repository pattern + * Provides flexibility to swap between different backend implementations */ export class ApiStorage implements IStorage { - private readonly config: Required; + private readonly repository: TaskRepository; + private readonly projectId: string; + private readonly enableRetry: boolean; + private readonly maxRetries: number; private initialized = false; + private tagsCache: Map = new Map(); constructor(config: ApiStorageConfig) { this.validateConfig(config); - this.config = { - endpoint: config.endpoint.replace(/\/$/, ''), // Remove trailing slash - accessToken: config.accessToken, - projectId: config.projectId || 'default', - timeout: config.timeout || 30000, - enableRetry: config.enableRetry ?? true, - maxRetries: config.maxRetries || 3 - }; + // Use provided repository or create Supabase repository + if (config.repository) { + this.repository = config.repository; + } else if (config.supabaseClient) { + // TODO: SupabaseTaskRepository doesn't implement all TaskRepository methods yet + // Cast for now until full implementation is complete + this.repository = new SupabaseTaskRepository( + config.supabaseClient + ) as unknown as TaskRepository; + } else { + throw new TaskMasterError( + 'Either repository or supabaseClient must be provided', + ERROR_CODES.MISSING_CONFIGURATION + ); + } + + this.projectId = config.projectId; + this.enableRetry = config.enableRetry ?? true; + this.maxRetries = config.maxRetries ?? 3; } /** * Validate API storage configuration */ private validateConfig(config: ApiStorageConfig): void { - if (!config.endpoint) { + if (!config.projectId) { throw new TaskMasterError( - 'API endpoint is required for API storage', + 'Project ID is required for API storage', ERROR_CODES.MISSING_CONFIGURATION ); } - if (!config.accessToken) { + if (!config.repository && !config.supabaseClient) { throw new TaskMasterError( - 'Access token is required for API storage', + 'Either repository or supabaseClient must be provided', ERROR_CODES.MISSING_CONFIGURATION ); } - - // Validate endpoint URL format - try { - new URL(config.endpoint); - } catch { - throw new TaskMasterError( - 'Invalid API endpoint URL', - ERROR_CODES.INVALID_INPUT, - { endpoint: config.endpoint } - ); - } } /** @@ -96,8 +92,8 @@ export class ApiStorage implements IStorage { if (this.initialized) return; try { - // Verify API connectivity - await this.verifyConnection(); + // Load initial tags + await this.loadTagsIntoCache(); this.initialized = true; } catch (error) { throw new TaskMasterError( @@ -110,39 +106,71 @@ export class ApiStorage implements IStorage { } /** - * Verify API connection + * Load tags into cache + * In our API-based system, "tags" represent briefs */ - private async verifyConnection(): Promise { - const response = await this.makeRequest<{ status: string }>('/health'); + private async loadTagsIntoCache(): Promise { + try { + const authManager = AuthManager.getInstance(); + const context = authManager.getContext(); - if (!response.success) { - throw new Error(`API health check failed: ${response.error}`); + // If we have a selected brief, create a virtual "tag" for it + if (context?.briefId) { + // Create a virtual tag representing the current brief + const briefTag: TaskTag = { + name: context.briefId, + tasks: [], // Will be populated when tasks are loaded + metadata: { + briefId: context.briefId, + briefName: context.briefName, + organizationId: context.orgId + } + }; + + this.tagsCache.clear(); + this.tagsCache.set(context.briefId, briefTag); + } + } catch (error) { + // If no brief is selected, that's okay - user needs to select one first + console.debug('No brief selected, starting with empty cache'); } } /** * Load tasks from API + * In our system, the tag parameter represents a brief ID */ async loadTasks(tag?: string): Promise { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/tasks?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/tasks`; + const authManager = AuthManager.getInstance(); + const context = authManager.getContext(); - const response = await this.makeRequest<{ tasks: Task[] }>(endpoint); - - if (!response.success) { - throw new Error(response.error || 'Failed to load tasks'); + // If no brief is selected in context, throw an error + if (!context?.briefId) { + throw new Error( + 'No brief selected. Please select a brief first using: tm context brief ' + ); } - return response.data?.tasks || []; + // Load tasks from the current brief context + const tasks = await this.retryOperation(() => + this.repository.getTasks(this.projectId) + ); + + // Update the tag cache with the loaded task IDs + const briefTag = this.tagsCache.get(context.briefId); + if (briefTag) { + briefTag.tasks = tasks.map((task) => task.id); + } + + return tasks; } catch (error) { throw new TaskMasterError( 'Failed to load tasks from API', ERROR_CODES.STORAGE_ERROR, - { operation: 'loadTasks', tag }, + { operation: 'loadTasks', tag, context: 'brief-based loading' }, error as Error ); } @@ -155,15 +183,29 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/tasks?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/tasks`; + if (tag) { + // Update tag with task IDs + const tagData = this.tagsCache.get(tag) || { + name: tag, + tasks: [], + metadata: {} + }; + tagData.tasks = tasks.map((t) => t.id); - const response = await this.makeRequest(endpoint, 'PUT', { tasks }); + // Save or update tag + if (this.tagsCache.has(tag)) { + await this.repository.updateTag(this.projectId, tag, tagData); + } else { + await this.repository.createTag(this.projectId, tagData); + } - if (!response.success) { - throw new Error(response.error || 'Failed to save tasks'); + this.tagsCache.set(tag, tagData); } + + // Save tasks using bulk operation + await this.retryOperation(() => + this.repository.bulkCreateTasks(this.projectId, tasks) + ); } catch (error) { throw new TaskMasterError( 'Failed to save tasks to API', @@ -181,20 +223,17 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/tasks/${taskId}?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/tasks/${taskId}`; - - const response = await this.makeRequest<{ task: Task }>(endpoint); - - if (!response.success) { - if (response.error?.includes('not found')) { + if (tag) { + // Check if task is in tag + const tagData = this.tagsCache.get(tag); + if (!tagData || !tagData.tasks.includes(taskId)) { return null; } - throw new Error(response.error || 'Failed to load task'); } - return response.data?.task || null; + return await this.retryOperation(() => + this.repository.getTask(this.projectId, taskId) + ); } catch (error) { throw new TaskMasterError( 'Failed to load task from API', @@ -212,14 +251,26 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/tasks/${task.id}?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/tasks/${task.id}`; + // Check if task exists + const existing = await this.repository.getTask(this.projectId, task.id); - const response = await this.makeRequest(endpoint, 'PUT', { task }); + if (existing) { + await this.retryOperation(() => + this.repository.updateTask(this.projectId, task.id, task) + ); + } else { + await this.retryOperation(() => + this.repository.createTask(this.projectId, task) + ); + } - if (!response.success) { - throw new Error(response.error || 'Failed to save task'); + // Update tag if specified + if (tag) { + const tagData = this.tagsCache.get(tag); + if (tagData && !tagData.tasks.includes(task.id)) { + tagData.tasks.push(task.id); + await this.repository.updateTag(this.projectId, tag, tagData); + } } } catch (error) { throw new TaskMasterError( @@ -238,14 +289,17 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/tasks/${taskId}?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/tasks/${taskId}`; + await this.retryOperation(() => + this.repository.deleteTask(this.projectId, taskId) + ); - const response = await this.makeRequest(endpoint, 'DELETE'); - - if (!response.success) { - throw new Error(response.error || 'Failed to delete task'); + // Remove from tag if specified + if (tag) { + const tagData = this.tagsCache.get(tag); + if (tagData) { + tagData.tasks = tagData.tasks.filter((id) => id !== taskId); + await this.repository.updateTag(this.projectId, tag, tagData); + } } } catch (error) { throw new TaskMasterError( @@ -258,21 +312,24 @@ export class ApiStorage implements IStorage { } /** - * List available tags + * List available tags (briefs in our system) */ async listTags(): Promise { await this.ensureInitialized(); try { - const response = await this.makeRequest<{ tags: string[] }>( - `/projects/${this.config.projectId}/tags` - ); + const authManager = AuthManager.getInstance(); + const context = authManager.getContext(); - if (!response.success) { - throw new Error(response.error || 'Failed to list tags'); + // In our API-based system, we only have one "tag" at a time - the current brief + if (context?.briefId) { + // Ensure the current brief is in our cache + await this.loadTagsIntoCache(); + return [context.briefId]; } - return response.data?.tags || []; + // No brief selected, return empty array + return []; } catch (error) { throw new TaskMasterError( 'Failed to list tags from API', @@ -290,19 +347,15 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/metadata?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/metadata`; - - const response = await this.makeRequest<{ metadata: TaskMetadata }>( - endpoint - ); - - if (!response.success) { - return null; + if (tag) { + const tagData = this.tagsCache.get(tag); + return (tagData?.metadata as TaskMetadata) || null; } - return response.data?.metadata || null; + // Return global metadata if no tag specified + // This could be stored in a special system tag + const systemTag = await this.repository.getTag(this.projectId, '_system'); + return (systemTag?.metadata as TaskMetadata) || null; } catch (error) { throw new TaskMasterError( 'Failed to load metadata from API', @@ -320,14 +373,38 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const endpoint = tag - ? `/projects/${this.config.projectId}/metadata?tag=${encodeURIComponent(tag)}` - : `/projects/${this.config.projectId}/metadata`; + if (tag) { + const tagData = this.tagsCache.get(tag) || { + name: tag, + tasks: [], + metadata: {} + }; + tagData.metadata = metadata as any; - const response = await this.makeRequest(endpoint, 'PUT', { metadata }); + if (this.tagsCache.has(tag)) { + await this.repository.updateTag(this.projectId, tag, tagData); + } else { + await this.repository.createTag(this.projectId, tagData); + } - if (!response.success) { - throw new Error(response.error || 'Failed to save metadata'); + this.tagsCache.set(tag, tagData); + } else { + // Save to system tag + const systemTag: TaskTag = { + name: '_system', + tasks: [], + metadata: metadata as any + }; + + const existing = await this.repository.getTag( + this.projectId, + '_system' + ); + if (existing) { + await this.repository.updateTag(this.projectId, '_system', systemTag); + } else { + await this.repository.createTag(this.projectId, systemTag); + } } } catch (error) { throw new TaskMasterError( @@ -358,14 +435,30 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - // First load existing tasks - const existingTasks = await this.loadTasks(tag); + // Use bulk create - repository should handle duplicates + await this.retryOperation(() => + this.repository.bulkCreateTasks(this.projectId, tasks) + ); - // Append new tasks - const allTasks = [...existingTasks, ...tasks]; + // Update tag if specified + if (tag) { + const tagData = this.tagsCache.get(tag) || { + name: tag, + tasks: [], + metadata: {} + }; - // Save all tasks - await this.saveTasks(allTasks, tag); + const newTaskIds = tasks.map((t) => t.id); + tagData.tasks = [...new Set([...tagData.tasks, ...newTaskIds])]; + + if (this.tagsCache.has(tag)) { + await this.repository.updateTag(this.projectId, tag, tagData); + } else { + await this.repository.createTag(this.projectId, tagData); + } + + this.tagsCache.set(tag, tagData); + } } catch (error) { throw new TaskMasterError( 'Failed to append tasks to API', @@ -387,18 +480,9 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - // Load the task - const task = await this.loadTask(taskId, tag); - - if (!task) { - throw new Error(`Task ${taskId} not found`); - } - - // Merge updates - const updatedTask = { ...task, ...updates, id: taskId }; - - // Save updated task - await this.saveTask(updatedTask, tag); + await this.retryOperation(() => + this.repository.updateTask(this.projectId, taskId, updates) + ); } catch (error) { throw new TaskMasterError( 'Failed to update task via API', @@ -423,14 +507,11 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest( - `/projects/${this.config.projectId}/tags/${encodeURIComponent(tag)}`, - 'DELETE' + await this.retryOperation(() => + this.repository.deleteTag(this.projectId, tag) ); - if (!response.success) { - throw new Error(response.error || 'Failed to delete tag'); - } + this.tagsCache.delete(tag); } catch (error) { throw new TaskMasterError( 'Failed to delete tag via API', @@ -448,15 +529,21 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest( - `/projects/${this.config.projectId}/tags/${encodeURIComponent(oldTag)}/rename`, - 'POST', - { newTag } - ); - - if (!response.success) { - throw new Error(response.error || 'Failed to rename tag'); + const tagData = this.tagsCache.get(oldTag); + if (!tagData) { + throw new Error(`Tag ${oldTag} not found`); } + + // Create new tag with same data + const newTagData = { ...tagData, name: newTag }; + await this.repository.createTag(this.projectId, newTagData); + + // Delete old tag + await this.repository.deleteTag(this.projectId, oldTag); + + // Update cache + this.tagsCache.delete(oldTag); + this.tagsCache.set(newTag, newTagData); } catch (error) { throw new TaskMasterError( 'Failed to rename tag via API', @@ -474,15 +561,17 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest( - `/projects/${this.config.projectId}/tags/${encodeURIComponent(sourceTag)}/copy`, - 'POST', - { targetTag } - ); - - if (!response.success) { - throw new Error(response.error || 'Failed to copy tag'); + const sourceData = this.tagsCache.get(sourceTag); + if (!sourceData) { + throw new Error(`Source tag ${sourceTag} not found`); } + + // Create new tag with copied data + const targetData = { ...sourceData, name: targetTag }; + await this.repository.createTag(this.projectId, targetData); + + // Update cache + this.tagsCache.set(targetTag, targetData); } catch (error) { throw new TaskMasterError( 'Failed to copy tag via API', @@ -500,24 +589,22 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest<{ - stats: StorageStats; - }>(`/projects/${this.config.projectId}/stats`); + const tasks = await this.repository.getTasks(this.projectId); + const tags = await this.repository.getTags(this.projectId); - if (!response.success) { - throw new Error(response.error || 'Failed to get stats'); - } + const tagStats = tags.map((tag) => ({ + tag: tag.name, + taskCount: tag.tasks.length, + lastModified: new Date().toISOString() // TODO: Get actual last modified from tag data + })); - // Return stats or default values - return ( - response.data?.stats || { - totalTasks: 0, - totalTags: 0, - storageSize: 0, - lastModified: new Date().toISOString(), - tagStats: [] - } - ); + return { + totalTasks: tasks.length, + totalTags: tags.length, + storageSize: 0, // Not applicable for API storage + lastModified: new Date().toISOString(), + tagStats + }; } catch (error) { throw new TaskMasterError( 'Failed to get stats from API', @@ -535,16 +622,15 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest<{ backupId: string }>( - `/projects/${this.config.projectId}/backup`, - 'POST' - ); + // Export all data + await this.repository.getTasks(this.projectId); + await this.repository.getTags(this.projectId); - if (!response.success) { - throw new Error(response.error || 'Failed to create backup'); - } - - return response.data?.backupId || 'unknown'; + // TODO: In a real implementation, this would: + // 1. Create backup data structure with tasks and tags + // 2. Save the backup to a storage service + // For now, return a backup identifier + return `backup-${this.projectId}-${Date.now()}`; } catch (error) { throw new TaskMasterError( 'Failed to create backup via API', @@ -558,27 +644,16 @@ export class ApiStorage implements IStorage { /** * Restore from backup */ - async restore(backupPath: string): Promise { + async restore(backupId: string): Promise { await this.ensureInitialized(); - try { - const response = await this.makeRequest( - `/projects/${this.config.projectId}/restore`, - 'POST', - { backupId: backupPath } - ); - - if (!response.success) { - throw new Error(response.error || 'Failed to restore backup'); - } - } catch (error) { - throw new TaskMasterError( - 'Failed to restore backup via API', - ERROR_CODES.STORAGE_ERROR, - { operation: 'restore', backupPath }, - error as Error - ); - } + // This would restore from a backup service + // Implementation depends on backup strategy + throw new TaskMasterError( + 'Restore not implemented for API storage', + ERROR_CODES.NOT_IMPLEMENTED, + { operation: 'restore', backupId } + ); } /** @@ -588,14 +663,23 @@ export class ApiStorage implements IStorage { await this.ensureInitialized(); try { - const response = await this.makeRequest( - `/projects/${this.config.projectId}/clear`, - 'POST' - ); - - if (!response.success) { - throw new Error(response.error || 'Failed to clear data'); + // Delete all tasks + const tasks = await this.repository.getTasks(this.projectId); + if (tasks.length > 0) { + await this.repository.bulkDeleteTasks( + this.projectId, + tasks.map((t) => t.id) + ); } + + // Delete all tags + const tags = await this.repository.getTags(this.projectId); + for (const tag of tags) { + await this.repository.deleteTag(this.projectId, tag.name); + } + + // Clear cache + this.tagsCache.clear(); } catch (error) { throw new TaskMasterError( 'Failed to clear data via API', @@ -611,6 +695,7 @@ export class ApiStorage implements IStorage { */ async close(): Promise { this.initialized = false; + this.tagsCache.clear(); } /** @@ -623,102 +708,21 @@ export class ApiStorage implements IStorage { } /** - * Make HTTP request to API + * Retry an operation with exponential backoff */ - private async makeRequest( - path: string, - method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', - body?: unknown - ): Promise> { - const url = `${this.config.endpoint}${path}`; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); - + private async retryOperation( + operation: () => Promise, + attempt: number = 1 + ): Promise { try { - const options: RequestInit = { - method, - headers: { - Authorization: `Bearer ${this.config.accessToken}`, - 'Content-Type': 'application/json', - Accept: 'application/json' - }, - signal: controller.signal - }; - - if (body && (method === 'POST' || method === 'PUT')) { - options.body = JSON.stringify(body); + return await operation(); + } catch (error) { + if (this.enableRetry && attempt < this.maxRetries) { + const delay = Math.pow(2, attempt) * 1000; + await new Promise((resolve) => setTimeout(resolve, delay)); + return this.retryOperation(operation, attempt + 1); } - - let lastError: Error | null = null; - let attempt = 0; - - while (attempt < this.config.maxRetries) { - attempt++; - - try { - const response = await fetch(url, options); - const data = await response.json(); - - if (response.ok) { - return { success: true, data: data as T }; - } - - // Handle specific error codes - if (response.status === 401) { - return { - success: false, - error: 'Authentication failed - check access token' - }; - } - - if (response.status === 404) { - return { - success: false, - error: 'Resource not found' - }; - } - - if (response.status === 429) { - // Rate limited - retry with backoff - if (this.config.enableRetry && attempt < this.config.maxRetries) { - await this.delay(Math.pow(2, attempt) * 1000); - continue; - } - } - - const errorData = data as any; - return { - success: false, - error: - errorData.error || - errorData.message || - `HTTP ${response.status}: ${response.statusText}` - }; - } catch (error) { - lastError = error as Error; - - // Retry on network errors - if (this.config.enableRetry && attempt < this.config.maxRetries) { - await this.delay(Math.pow(2, attempt) * 1000); - continue; - } - } - } - - // All retries exhausted - return { - success: false, - error: lastError?.message || 'Request failed after retries' - }; - } finally { - clearTimeout(timeoutId); + throw error; } } - - /** - * Delay helper for retries - */ - private delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); - } } diff --git a/packages/tm-core/src/storage/storage-factory.ts b/packages/tm-core/src/storage/storage-factory.ts index e1527a43..a928f30a 100644 --- a/packages/tm-core/src/storage/storage-factory.ts +++ b/packages/tm-core/src/storage/storage-factory.ts @@ -13,6 +13,7 @@ import { ApiStorage } from './api-storage.js'; import { ERROR_CODES, TaskMasterError } from '../errors/task-master-error.js'; import { AuthManager } from '../auth/auth-manager.js'; import { getLogger } from '../logger/index.js'; +import { SupabaseAuthClient } from '../clients/supabase-client.js'; /** * Factory for creating storage implementations based on configuration @@ -148,29 +149,13 @@ export class StorageFactory { * Create API storage implementation */ private static createApiStorage(config: Partial): ApiStorage { - const { apiEndpoint, apiAccessToken } = config.storage || {}; - - if (!apiEndpoint) { - throw new TaskMasterError( - 'API endpoint is required for API storage', - ERROR_CODES.MISSING_CONFIGURATION, - { storageType: 'api' } - ); - } - - if (!apiAccessToken) { - throw new TaskMasterError( - 'API access token is required for API storage', - ERROR_CODES.MISSING_CONFIGURATION, - { storageType: 'api' } - ); - } + // Use our SupabaseAuthClient instead of creating a raw Supabase client + const supabaseAuthClient = new SupabaseAuthClient(); + const supabaseClient = supabaseAuthClient.getClient(); return new ApiStorage({ - endpoint: apiEndpoint, - accessToken: apiAccessToken, - projectId: config.projectPath, - timeout: config.retry?.requestTimeout, + supabaseClient, + projectId: config.projectPath || '', enableRetry: config.retry?.retryOnNetworkError, maxRetries: config.retry?.retryAttempts }); diff --git a/packages/tm-core/src/task-master-core.ts b/packages/tm-core/src/task-master-core.ts index 3316e9e2..38e89b5f 100644 --- a/packages/tm-core/src/task-master-core.ts +++ b/packages/tm-core/src/task-master-core.ts @@ -152,7 +152,7 @@ export class TaskMasterCore { /** * Get current storage type */ - getStorageType(): 'file' | 'api' | 'auto' { + getStorageType(): 'file' | 'api' { return this.taskService.getStorageType(); } diff --git a/packages/tm-core/src/types/database.types.ts b/packages/tm-core/src/types/database.types.ts new file mode 100644 index 00000000..102b8284 --- /dev/null +++ b/packages/tm-core/src/types/database.types.ts @@ -0,0 +1,491 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[]; + +export type Database = { + public: { + Tables: { + accounts: { + Row: { + created_at: string | null; + created_by: string | null; + email: string | null; + id: string; + is_personal_account: boolean; + name: string; + picture_url: string | null; + primary_owner_user_id: string; + public_data: Json; + slug: string | null; + updated_at: string | null; + updated_by: string | null; + }; + Insert: { + created_at?: string | null; + created_by?: string | null; + email?: string | null; + id?: string; + is_personal_account?: boolean; + name: string; + picture_url?: string | null; + primary_owner_user_id?: string; + public_data?: Json; + slug?: string | null; + updated_at?: string | null; + updated_by?: string | null; + }; + Update: { + created_at?: string | null; + created_by?: string | null; + email?: string | null; + id?: string; + is_personal_account?: boolean; + name?: string; + picture_url?: string | null; + primary_owner_user_id?: string; + public_data?: Json; + slug?: string | null; + updated_at?: string | null; + updated_by?: string | null; + }; + Relationships: []; + }; + brief: { + Row: { + account_id: string; + created_at: string; + created_by: string; + document_id: string; + id: string; + plan_generation_completed_at: string | null; + plan_generation_error: string | null; + plan_generation_started_at: string | null; + plan_generation_status: Database['public']['Enums']['plan_generation_status']; + status: Database['public']['Enums']['brief_status']; + updated_at: string; + }; + Insert: { + account_id: string; + created_at?: string; + created_by: string; + document_id: string; + id?: string; + plan_generation_completed_at?: string | null; + plan_generation_error?: string | null; + plan_generation_started_at?: string | null; + plan_generation_status?: Database['public']['Enums']['plan_generation_status']; + status?: Database['public']['Enums']['brief_status']; + updated_at?: string; + }; + Update: { + account_id?: string; + created_at?: string; + created_by?: string; + document_id?: string; + id?: string; + plan_generation_completed_at?: string | null; + plan_generation_error?: string | null; + plan_generation_started_at?: string | null; + plan_generation_status?: Database['public']['Enums']['plan_generation_status']; + status?: Database['public']['Enums']['brief_status']; + updated_at?: string; + }; + Relationships: [ + { + foreignKeyName: 'brief_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'brief_document_id_fkey'; + columns: ['document_id']; + isOneToOne: false; + referencedRelation: 'document'; + referencedColumns: ['id']; + } + ]; + }; + document: { + Row: { + account_id: string; + created_at: string; + created_by: string; + description: string | null; + document_name: string; + document_type: Database['public']['Enums']['document_type']; + file_path: string | null; + file_size: number | null; + id: string; + metadata: Json | null; + mime_type: string | null; + processed_at: string | null; + processing_error: string | null; + processing_status: + | Database['public']['Enums']['document_processing_status'] + | null; + source_id: string | null; + source_type: string | null; + title: string; + updated_at: string; + }; + Insert: { + account_id: string; + created_at?: string; + created_by: string; + description?: string | null; + document_name: string; + document_type?: Database['public']['Enums']['document_type']; + file_path?: string | null; + file_size?: number | null; + id?: string; + metadata?: Json | null; + mime_type?: string | null; + processed_at?: string | null; + processing_error?: string | null; + processing_status?: + | Database['public']['Enums']['document_processing_status'] + | null; + source_id?: string | null; + source_type?: string | null; + title: string; + updated_at?: string; + }; + Update: { + account_id?: string; + created_at?: string; + created_by?: string; + description?: string | null; + document_name?: string; + document_type?: Database['public']['Enums']['document_type']; + file_path?: string | null; + file_size?: number | null; + id?: string; + metadata?: Json | null; + mime_type?: string | null; + processed_at?: string | null; + processing_error?: string | null; + processing_status?: + | Database['public']['Enums']['document_processing_status'] + | null; + source_id?: string | null; + source_type?: string | null; + title?: string; + updated_at?: string; + }; + Relationships: [ + { + foreignKeyName: 'document_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; + } + ]; + }; + tasks: { + Row: { + account_id: string; + actual_hours: number; + assignee_id: string | null; + brief_id: string | null; + completed_subtasks: number; + complexity: number | null; + created_at: string; + created_by: string; + description: string | null; + display_id: string | null; + document_id: string | null; + due_date: string | null; + estimated_hours: number | null; + id: string; + metadata: Json; + parent_task_id: string | null; + position: number; + priority: Database['public']['Enums']['task_priority']; + status: Database['public']['Enums']['task_status']; + subtask_position: number; + title: string; + total_subtasks: number; + updated_at: string; + updated_by: string; + }; + Insert: { + account_id: string; + actual_hours?: number; + assignee_id?: string | null; + brief_id?: string | null; + completed_subtasks?: number; + complexity?: number | null; + created_at?: string; + created_by: string; + description?: string | null; + display_id?: string | null; + document_id?: string | null; + due_date?: string | null; + estimated_hours?: number | null; + id?: string; + metadata?: Json; + parent_task_id?: string | null; + position?: number; + priority?: Database['public']['Enums']['task_priority']; + status?: Database['public']['Enums']['task_status']; + subtask_position?: number; + title: string; + total_subtasks?: number; + updated_at?: string; + updated_by: string; + }; + Update: { + account_id?: string; + actual_hours?: number; + assignee_id?: string | null; + brief_id?: string | null; + completed_subtasks?: number; + complexity?: number | null; + created_at?: string; + created_by?: string; + description?: string | null; + display_id?: string | null; + document_id?: string | null; + due_date?: string | null; + estimated_hours?: number | null; + id?: string; + metadata?: Json; + parent_task_id?: string | null; + position?: number; + priority?: Database['public']['Enums']['task_priority']; + status?: Database['public']['Enums']['task_status']; + subtask_position?: number; + title?: string; + total_subtasks?: number; + updated_at?: string; + updated_by?: string; + }; + Relationships: [ + { + foreignKeyName: 'tasks_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'tasks_brief_id_fkey'; + columns: ['brief_id']; + isOneToOne: false; + referencedRelation: 'brief'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'tasks_document_id_fkey'; + columns: ['document_id']; + isOneToOne: false; + referencedRelation: 'document'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'tasks_parent_task_id_fkey'; + columns: ['parent_task_id']; + isOneToOne: false; + referencedRelation: 'tasks'; + referencedColumns: ['id']; + } + ]; + }; + task_dependencies: { + Row: { + account_id: string; + created_at: string; + depends_on_task_id: string; + id: string; + task_id: string; + }; + Insert: { + account_id: string; + created_at?: string; + depends_on_task_id: string; + id?: string; + task_id: string; + }; + Update: { + account_id?: string; + created_at?: string; + depends_on_task_id?: string; + id?: string; + task_id?: string; + }; + Relationships: [ + { + foreignKeyName: 'task_dependencies_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'task_dependencies_depends_on_task_id_fkey'; + columns: ['depends_on_task_id']; + isOneToOne: false; + referencedRelation: 'tasks'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'task_dependencies_task_id_fkey'; + columns: ['task_id']; + isOneToOne: false; + referencedRelation: 'tasks'; + referencedColumns: ['id']; + } + ]; + }; + user_accounts: { + Row: { + id: string | null; + name: string | null; + picture_url: string | null; + role: string | null; + slug: string | null; + }; + Insert: { + id?: string | null; + name?: string | null; + picture_url?: string | null; + role?: string | null; + slug?: string | null; + }; + Update: { + id?: string | null; + name?: string | null; + picture_url?: string | null; + role?: string | null; + slug?: string | null; + }; + Relationships: []; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + brief_status: + | 'draft' + | 'refining' + | 'aligned' + | 'delivering' + | 'delivered' + | 'done' + | 'archived'; + document_processing_status: 'pending' | 'processing' | 'ready' | 'failed'; + document_type: + | 'brief' + | 'blueprint' + | 'file' + | 'note' + | 'transcript' + | 'generated_plan' + | 'generated_task' + | 'generated_summary' + | 'method' + | 'task'; + plan_generation_status: + | 'not_started' + | 'generating' + | 'completed' + | 'failed'; + task_priority: 'low' | 'medium' | 'high' | 'urgent'; + task_status: 'todo' | 'in_progress' | 'done'; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; +}; + +export type Tables< + PublicTableNameOrOptions extends + | keyof (Database['public']['Tables'] & Database['public']['Views']) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views']) + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R; + } + ? R + : never + : PublicTableNameOrOptions extends keyof (Database['public']['Tables'] & + Database['public']['Views']) + ? (Database['public']['Tables'] & + Database['public']['Views'])[PublicTableNameOrOptions] extends { + Row: infer R; + } + ? R + : never + : never; + +export type TablesInsert< + PublicTableNameOrOptions extends + | keyof Database['public']['Tables'] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I; + } + ? I + : never + : PublicTableNameOrOptions extends keyof Database['public']['Tables'] + ? Database['public']['Tables'][PublicTableNameOrOptions] extends { + Insert: infer I; + } + ? I + : never + : never; + +export type TablesUpdate< + PublicTableNameOrOptions extends + | keyof Database['public']['Tables'] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U; + } + ? U + : never + : PublicTableNameOrOptions extends keyof Database['public']['Tables'] + ? Database['public']['Tables'][PublicTableNameOrOptions] extends { + Update: infer U; + } + ? U + : never + : never; + +export type Enums< + PublicEnumNameOrOptions extends + | keyof Database['public']['Enums'] + | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + : never = never +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] + : PublicEnumNameOrOptions extends keyof Database['public']['Enums'] + ? Database['public']['Enums'][PublicEnumNameOrOptions] + : never; diff --git a/packages/tm-core/src/types/index.ts b/packages/tm-core/src/types/index.ts index e877857a..8af22d14 100644 --- a/packages/tm-core/src/types/index.ts +++ b/packages/tm-core/src/types/index.ts @@ -96,6 +96,15 @@ export interface TaskCollection { metadata: TaskMetadata; } +/** + * Task tag for organizing tasks + */ +export interface TaskTag { + name: string; + tasks: string[]; // Task IDs belonging to this tag + metadata: Record; +} + // ============================================================================ // Utility Types // ============================================================================ diff --git a/packages/tm-core/tsconfig.json b/packages/tm-core/tsconfig.json index 80f9cd21..28bb0d7a 100644 --- a/packages/tm-core/tsconfig.json +++ b/packages/tm-core/tsconfig.json @@ -30,17 +30,12 @@ "isolatedModules": true, "paths": { "@/*": ["./src/*"], - "@/auth": ["./src/auth"], - "@/config": ["./src/config"], - "@/errors": ["./src/errors"], - "@/interfaces": ["./src/interfaces"], - "@/logger": ["./src/logger"], - "@/parser": ["./src/parser"], - "@/providers": ["./src/providers"], - "@/services": ["./src/services"], - "@/storage": ["./src/storage"], "@/types": ["./src/types"], - "@/utils": ["./src/utils"] + "@/providers": ["./src/providers"], + "@/storage": ["./src/storage"], + "@/parser": ["./src/parser"], + "@/utils": ["./src/utils"], + "@/errors": ["./src/errors"] } }, "include": ["src/**/*"], diff --git a/packages/tm-core/tsup.config.ts b/packages/tm-core/tsup.config.ts index c548e76b..5f2f73e2 100644 --- a/packages/tm-core/tsup.config.ts +++ b/packages/tm-core/tsup.config.ts @@ -1,33 +1,14 @@ import { defineConfig } from 'tsup'; -import { dotenvLoad } from 'dotenv-mono'; -dotenvLoad(); - -// Get all TM_PUBLIC_* env variables for build-time injection -const getBuildTimeEnvs = () => { - const envs: Record = {}; - for (const [key, value] of Object.entries(process.env)) { - if (key.startsWith('TM_PUBLIC_')) { - // Return the actual value, not JSON.stringify'd - envs[key] = value || ''; - } - } - return envs; -}; export default defineConfig({ entry: { index: 'src/index.ts', - 'auth/index': 'src/auth/index.ts', - 'config/index': 'src/config/index.ts', - 'errors/index': 'src/errors/index.ts', - 'interfaces/index': 'src/interfaces/index.ts', - 'logger/index': 'src/logger/index.ts', - 'parser/index': 'src/parser/index.ts', - 'providers/index': 'src/providers/index.ts', - 'services/index': 'src/services/index.ts', - 'storage/index': 'src/storage/index.ts', 'types/index': 'src/types/index.ts', - 'utils/index': 'src/utils/index.ts' + 'providers/index': 'src/providers/index.ts', + 'storage/index': 'src/storage/index.ts', + 'parser/index': 'src/parser/index.ts', + 'utils/index': 'src/utils/index.ts', + 'errors/index': 'src/errors/index.ts' }, format: ['cjs', 'esm'], dts: true, @@ -39,13 +20,7 @@ export default defineConfig({ target: 'es2022', tsconfig: './tsconfig.json', outDir: 'dist', - // Replace process.env.TM_PUBLIC_* with actual values at build time - env: getBuildTimeEnvs(), - // Auto-external all dependencies from package.json - external: [ - // External all node_modules - everything not starting with . or / - /^[^./]/ - ], + external: ['zod'], esbuildOptions(options) { options.conditions = ['module']; } diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index 82085b5a..baf818c9 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -16,7 +16,7 @@ import ora from 'ora'; // Import ora import { log, readJSON } from './utils.js'; // Import new commands from @tm/cli -import { ListTasksCommand, AuthCommand } from '@tm/cli'; +import { ListTasksCommand, AuthCommand, ContextCommand } from '@tm/cli'; import { parsePRD, @@ -1745,6 +1745,10 @@ function registerCommands(programInstance) { // Handles authentication with tryhamster.com AuthCommand.registerOn(programInstance); + // Register the context command from @tm/cli + // Manages workspace context (org/brief selection) + ContextCommand.registerOn(programInstance); + // expand command programInstance .command('expand') diff --git a/tests/integration/profiles/roo-files-inclusion.test.js b/tests/integration/profiles/roo-files-inclusion.test.js index 77451241..b795479e 100644 --- a/tests/integration/profiles/roo-files-inclusion.test.js +++ b/tests/integration/profiles/roo-files-inclusion.test.js @@ -103,14 +103,10 @@ describe('Roo Files Inclusion in Package', () => { test('source Roo files exist in public/assets directory', () => { // Verify that the source files for Roo integration exist expect( - fs.existsSync( - path.join(process.cwd(), 'public', 'assets', 'roocode', '.roo') - ) + fs.existsSync(path.join(process.cwd(), 'public', 'assets', 'roocode', '.roo')) ).toBe(true); expect( - fs.existsSync( - path.join(process.cwd(), 'public', 'assets', 'roocode', '.roomodes') - ) + fs.existsSync(path.join(process.cwd(), 'public', 'assets', 'roocode', '.roomodes')) ).toBe(true); }); }); diff --git a/tests/integration/profiles/rules-files-inclusion.test.js b/tests/integration/profiles/rules-files-inclusion.test.js index c8cdbe03..00b65bed 100644 --- a/tests/integration/profiles/rules-files-inclusion.test.js +++ b/tests/integration/profiles/rules-files-inclusion.test.js @@ -89,14 +89,10 @@ describe('Rules Files Inclusion in Package', () => { test('source Roo files exist in public/assets directory', () => { // Verify that the source files for Roo integration exist expect( - fs.existsSync( - path.join(process.cwd(), 'public', 'assets', 'roocode', '.roo') - ) + fs.existsSync(path.join(process.cwd(), 'public', 'assets', 'roocode', '.roo')) ).toBe(true); expect( - fs.existsSync( - path.join(process.cwd(), 'public', 'assets', 'roocode', '.roomodes') - ) + fs.existsSync(path.join(process.cwd(), 'public', 'assets', 'roocode', '.roomodes')) ).toBe(true); }); }); diff --git a/tests/unit/prompt-manager.test.js b/tests/unit/prompt-manager.test.js index f135f7d0..8abe1c41 100644 --- a/tests/unit/prompt-manager.test.js +++ b/tests/unit/prompt-manager.test.js @@ -62,11 +62,11 @@ describe('PromptManager', () => { describe('loadPrompt', () => { it('should load and render a prompt from actual files', () => { // Test with an actual prompt that exists - const result = promptManager.loadPrompt('research', { + const result = promptManager.loadPrompt('research', { query: 'test query', projectContext: 'test context' }); - + expect(result.systemPrompt).toBeDefined(); expect(result.userPrompt).toBeDefined(); expect(result.userPrompt).toContain('test query'); @@ -87,7 +87,7 @@ describe('PromptManager', () => { }); const result = promptManager.loadPrompt('test-prompt', { name: 'John' }); - + expect(result.userPrompt).toBe('Hello John, your age is '); }); @@ -100,13 +100,13 @@ describe('PromptManager', () => { it('should use cache for repeated calls', () => { // First call with a real prompt const result1 = promptManager.loadPrompt('research', { query: 'test' }); - + // Mark the result to verify cache is used result1._cached = true; - + // Second call with same parameters should return cached result const result2 = promptManager.loadPrompt('research', { query: 'test' }); - + expect(result2._cached).toBe(true); expect(result1).toBe(result2); // Same object reference }); @@ -127,7 +127,7 @@ describe('PromptManager', () => { const result = promptManager.loadPrompt('array-prompt', { items: ['one', 'two', 'three'] }); - + // The actual implementation doesn't handle {{this}} properly, check what it does produce expect(result.userPrompt).toContain('Item:'); }); @@ -145,14 +145,10 @@ describe('PromptManager', () => { } }); - const withData = promptManager.loadPrompt('conditional-prompt', { - hasData: true - }); + const withData = promptManager.loadPrompt('conditional-prompt', { hasData: true }); expect(withData.userPrompt).toBe('Data exists'); - const withoutData = promptManager.loadPrompt('conditional-prompt', { - hasData: false - }); + const withoutData = promptManager.loadPrompt('conditional-prompt', { hasData: false }); expect(withoutData.userPrompt).toBe('No data'); }); }); @@ -166,7 +162,7 @@ describe('PromptManager', () => { age: 30 } }; - + const result = promptManager.renderTemplate(template, variables); expect(result).toBe('User: John, Age: 30'); }); @@ -176,7 +172,7 @@ describe('PromptManager', () => { const variables = { special: '<>&"\'' }; - + const result = promptManager.renderTemplate(template, variables); expect(result).toBe('Special: <>&"\''); }); @@ -187,8 +183,8 @@ describe('PromptManager', () => { const prompts = promptManager.listPrompts(); expect(prompts).toBeInstanceOf(Array); expect(prompts.length).toBeGreaterThan(0); - - const ids = prompts.map((p) => p.id); + + const ids = prompts.map(p => p.id); expect(ids).toContain('analyze-complexity'); expect(ids).toContain('expand-task'); expect(ids).toContain('add-task'); @@ -196,6 +192,7 @@ describe('PromptManager', () => { }); }); + describe('validateTemplate', () => { it('should validate a correct template', () => { const result = promptManager.validateTemplate('research'); @@ -205,7 +202,7 @@ describe('PromptManager', () => { it('should reject invalid template', () => { const result = promptManager.validateTemplate('non-existent'); expect(result.valid).toBe(false); - expect(result.error).toContain('not found'); + expect(result.error).toContain("not found"); }); }); -}); +}); \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts index a0c7128b..182a88be 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,20 +1,4 @@ import { defineConfig } from 'tsup'; -import { dotenvLoad } from 'dotenv-mono'; - -// Load .env from root level (monorepo support) -dotenvLoad(); - -// Get all TM_PUBLIC_* env variables for build-time injection -const getBuildTimeEnvs = () => { - const envs: Record = {}; - for (const [key, value] of Object.entries(process.env)) { - if (key.startsWith('TM_PUBLIC_')) { - // Return the actual value, not JSON.stringify'd - envs[key] = value || ''; - } - } - return envs; -}; export default defineConfig({ entry: { @@ -34,8 +18,6 @@ export default defineConfig({ '.js': 'jsx', '.ts': 'ts' }, - // Replace process.env.TM_PUBLIC_* with actual values at build time - env: getBuildTimeEnvs(), esbuildOptions(options) { options.platform = 'node'; // Allow importing TypeScript from JavaScript @@ -43,9 +25,31 @@ export default defineConfig({ }, // Bundle our monorepo packages but keep node_modules external noExternal: [/@tm\/.*/], - // Don't bundle any other dependencies (auto-external all node_modules) - // This regex matches anything that doesn't start with . or / - external: [/^[^./]/], - // Add success message for debugging - onSuccess: 'echo "āœ… Build completed successfully"' + external: [ + // Keep native node modules external + 'fs', + 'path', + 'child_process', + 'crypto', + 'os', + 'url', + 'util', + 'stream', + 'http', + 'https', + 'events', + 'assert', + 'buffer', + 'querystring', + 'readline', + 'zlib', + 'tty', + 'net', + 'dgram', + 'dns', + 'tls', + 'cluster', + 'process', + 'module' + ] });