Compare commits
7 Commits
task-maste
...
ralph/feat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22a6c79c3c | ||
|
|
191a2fdb92 | ||
|
|
8957110b50 | ||
|
|
9853f39325 | ||
|
|
f76451f411 | ||
|
|
1dbc01ba00 | ||
|
|
0495189af3 |
@@ -22,6 +22,7 @@
|
||||
"test:ci": "vitest run --coverage --reporter=dot"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inquirer/search": "^3.2.0",
|
||||
"@tm/core": "*",
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "5.6.2",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type AuthCredentials
|
||||
} from '@tm/core/auth';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { ContextCommand } from './context.command.js';
|
||||
|
||||
/**
|
||||
* Result type from auth command
|
||||
@@ -351,6 +352,37 @@ export class AuthCommand extends Command {
|
||||
chalk.gray(` Logged in as: ${credentials.email || credentials.userId}`)
|
||||
);
|
||||
|
||||
// Post-auth: Set up workspace context
|
||||
console.log(); // Add spacing
|
||||
try {
|
||||
const contextCommand = new ContextCommand();
|
||||
const contextResult = await contextCommand.setupContextInteractive();
|
||||
if (contextResult.success) {
|
||||
if (contextResult.orgSelected && contextResult.briefSelected) {
|
||||
console.log(
|
||||
chalk.green('✓ Workspace context configured successfully')
|
||||
);
|
||||
} else if (contextResult.orgSelected) {
|
||||
console.log(chalk.green('✓ Organization selected'));
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
chalk.yellow('⚠ Context setup was skipped or encountered issues')
|
||||
);
|
||||
console.log(
|
||||
chalk.gray(' You can set up context later with "tm context"')
|
||||
);
|
||||
}
|
||||
} catch (contextError) {
|
||||
console.log(chalk.yellow('⚠ Context setup encountered an error'));
|
||||
console.log(
|
||||
chalk.gray(' You can set up context later with "tm context"')
|
||||
);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(chalk.gray((contextError as Error).message));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'login',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import search from '@inquirer/search';
|
||||
import ora, { Ora } from 'ora';
|
||||
import {
|
||||
AuthManager,
|
||||
@@ -156,10 +157,14 @@ export class ContextCommand extends Command {
|
||||
|
||||
if (context.briefName || context.briefId) {
|
||||
console.log(chalk.green('\n✓ Brief'));
|
||||
if (context.briefName) {
|
||||
if (context.briefName && context.briefId) {
|
||||
const shortId = context.briefId.slice(0, 8);
|
||||
console.log(
|
||||
chalk.white(` ${context.briefName} `) + chalk.gray(`(${shortId})`)
|
||||
);
|
||||
} else if (context.briefName) {
|
||||
console.log(chalk.white(` ${context.briefName}`));
|
||||
}
|
||||
if (context.briefId) {
|
||||
} else if (context.briefId) {
|
||||
console.log(chalk.gray(` ID: ${context.briefId}`));
|
||||
}
|
||||
}
|
||||
@@ -324,25 +329,53 @@ export class ContextCommand extends Command {
|
||||
};
|
||||
}
|
||||
|
||||
// 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} (${new Date(brief.createdAt).toLocaleDateString()})`,
|
||||
// Prompt for selection with search
|
||||
const selectedBrief = await search<(typeof briefs)[0] | null>({
|
||||
message: 'Search for a brief:',
|
||||
source: async (input) => {
|
||||
const searchTerm = input?.toLowerCase() || '';
|
||||
|
||||
// Static option for no brief
|
||||
const noBriefOption = {
|
||||
name: '(No brief - organization level)',
|
||||
value: null as any,
|
||||
description: 'Clear brief selection'
|
||||
};
|
||||
|
||||
// Filter and map brief options
|
||||
const briefOptions = briefs
|
||||
.filter((brief) => {
|
||||
if (!searchTerm) return true;
|
||||
|
||||
const title = brief.document?.title || '';
|
||||
const shortId = brief.id.slice(0, 8);
|
||||
|
||||
// Search by title first, then by UUID
|
||||
return (
|
||||
title.toLowerCase().includes(searchTerm) ||
|
||||
brief.id.toLowerCase().includes(searchTerm) ||
|
||||
shortId.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
})
|
||||
.map((brief) => {
|
||||
const title =
|
||||
brief.document?.title || `Brief ${brief.id.slice(0, 8)}`;
|
||||
const shortId = brief.id.slice(0, 8);
|
||||
return {
|
||||
name: `${title} ${chalk.gray(`(${shortId})`)}`,
|
||||
value: brief
|
||||
}))
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
return [noBriefOption, ...briefOptions];
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
if (selectedBrief) {
|
||||
// Update context with brief
|
||||
const briefName = `Brief ${selectedBrief.id.slice(0, 8)}`;
|
||||
const briefName =
|
||||
selectedBrief.document?.title ||
|
||||
`Brief ${selectedBrief.id.slice(0, 8)}`;
|
||||
this.authManager.updateContext({
|
||||
briefId: selectedBrief.id,
|
||||
briefName: briefName
|
||||
@@ -354,7 +387,7 @@ export class ContextCommand extends Command {
|
||||
success: true,
|
||||
action: 'select-brief',
|
||||
context: this.authManager.getContext() || undefined,
|
||||
message: `Selected brief: ${selectedBrief.name}`
|
||||
message: `Selected brief: ${selectedBrief.document?.title}`
|
||||
};
|
||||
} else {
|
||||
// Clear brief selection
|
||||
@@ -468,7 +501,7 @@ export class ContextCommand extends Command {
|
||||
if (!briefId) {
|
||||
spinner.fail('Could not extract a brief ID from the provided input');
|
||||
ui.displayError(
|
||||
`Provide a valid brief ID or a Hamster brief URL, e.g. https://${process.env.TM_PUBLIC_BASE_DOMAIN}/home/hamster/briefs/<id>`
|
||||
`Provide a valid brief ID or a Hamster brief URL, e.g. https://${process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN}/home/hamster/briefs/<id>`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -490,7 +523,8 @@ export class ContextCommand extends Command {
|
||||
}
|
||||
|
||||
// Update context: set org and brief
|
||||
const briefName = `Brief ${brief.id.slice(0, 8)}`;
|
||||
const briefName =
|
||||
brief.document?.title || `Brief ${brief.id.slice(0, 8)}`;
|
||||
this.authManager.updateContext({
|
||||
orgId: brief.accountId,
|
||||
orgName,
|
||||
@@ -686,6 +720,53 @@ export class ContextCommand extends Command {
|
||||
return this.authManager.getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive context setup (for post-auth flow)
|
||||
* Prompts user to select org and brief
|
||||
*/
|
||||
async setupContextInteractive(): Promise<{
|
||||
success: boolean;
|
||||
orgSelected: boolean;
|
||||
briefSelected: boolean;
|
||||
}> {
|
||||
try {
|
||||
// Ask if user wants to set up workspace context
|
||||
const { setupContext } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupContext',
|
||||
message: 'Would you like to set up your workspace context now?',
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
if (!setupContext) {
|
||||
return { success: true, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
|
||||
// Select organization
|
||||
const orgResult = await this.selectOrganization();
|
||||
if (!orgResult.success || !orgResult.context?.orgId) {
|
||||
return { success: false, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
|
||||
// Select brief
|
||||
const briefResult = await this.selectBrief(orgResult.context.orgId);
|
||||
return {
|
||||
success: true,
|
||||
orgSelected: true,
|
||||
briefSelected: briefResult.success
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
'\nContext setup skipped due to error. You can set it up later with "tm context"'
|
||||
)
|
||||
);
|
||||
return { success: false, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
|
||||
86
package-lock.json
generated
86
package-lock.json
generated
@@ -104,6 +104,7 @@
|
||||
"name": "@tm/cli",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/search": "^3.2.0",
|
||||
"@tm/core": "*",
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "5.6.2",
|
||||
@@ -124,6 +125,91 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"apps/cli/node_modules/@inquirer/ansi": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz",
|
||||
"integrity": "sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"apps/cli/node_modules/@inquirer/figures": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz",
|
||||
"integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"apps/cli/node_modules/@inquirer/search": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.0.tgz",
|
||||
"integrity": "sha512-a5SzB/qrXafDX1Z4AZW3CsVoiNxcIYCzYP7r9RzrfMpaLpB+yWi5U8BWagZyLmwR0pKbbL5umnGRd0RzGVI8bQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.0",
|
||||
"@inquirer/figures": "^1.0.14",
|
||||
"@inquirer/type": "^3.0.9",
|
||||
"yoctocolors-cjs": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps/cli/node_modules/@inquirer/search/node_modules/@inquirer/core": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.0.tgz",
|
||||
"integrity": "sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.1",
|
||||
"@inquirer/figures": "^1.0.14",
|
||||
"@inquirer/type": "^3.0.9",
|
||||
"cli-width": "^4.1.0",
|
||||
"mute-stream": "^2.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"wrap-ansi": "^6.2.0",
|
||||
"yoctocolors-cjs": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps/cli/node_modules/@inquirer/search/node_modules/@inquirer/type": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.9.tgz",
|
||||
"integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps/docs": {
|
||||
"version": "0.0.6",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,11 +7,13 @@ import path from 'path';
|
||||
import { AuthConfig } from './types.js';
|
||||
|
||||
// Single base domain for all URLs
|
||||
// Build-time: process.env.TM_PUBLIC_BASE_DOMAIN gets replaced by tsup's env option
|
||||
// Runtime vars (TM_*) take precedence over build-time vars (TM_PUBLIC_*)
|
||||
// Build-time: process.env.TM_PUBLIC_BASE_DOMAIN gets replaced by tsdown's env option
|
||||
// Runtime: process.env.TM_BASE_DOMAIN can override for staging/development
|
||||
// Default: https://tryhamster.com for production
|
||||
const BASE_DOMAIN =
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN || // This gets replaced at build time by tsup
|
||||
'https://tryhamster.com';
|
||||
process.env.TM_BASE_DOMAIN || // Runtime override (for staging/tux)
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN; // Build-time (baked into compiled code)
|
||||
|
||||
/**
|
||||
* Default authentication configuration
|
||||
@@ -19,7 +21,7 @@ const BASE_DOMAIN =
|
||||
*/
|
||||
export const DEFAULT_AUTH_CONFIG: AuthConfig = {
|
||||
// Base domain for all services
|
||||
baseUrl: BASE_DOMAIN,
|
||||
baseUrl: BASE_DOMAIN!,
|
||||
|
||||
// Configuration directory and file paths
|
||||
configDir: path.join(os.homedir(), '.taskmaster'),
|
||||
|
||||
@@ -24,6 +24,8 @@ export class CredentialStore {
|
||||
private config: AuthConfig;
|
||||
// Clock skew tolerance for expiry checks (30 seconds)
|
||||
private readonly CLOCK_SKEW_MS = 30_000;
|
||||
// Track if we've already warned about missing expiration to avoid spam
|
||||
private hasWarnedAboutMissingExpiration = false;
|
||||
|
||||
private constructor(config?: Partial<AuthConfig>) {
|
||||
this.config = getAuthConfig(config);
|
||||
@@ -84,7 +86,11 @@ export class CredentialStore {
|
||||
|
||||
// Validate expiration time for tokens
|
||||
if (expiresAtMs === undefined) {
|
||||
// Only log this warning once to avoid spam during auth flows
|
||||
if (!this.hasWarnedAboutMissingExpiration) {
|
||||
this.logger.warn('No valid expiration time provided for token');
|
||||
this.hasWarnedAboutMissingExpiration = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -174,6 +180,9 @@ export class CredentialStore {
|
||||
mode: 0o600
|
||||
});
|
||||
fs.renameSync(tempFile, this.config.configFile);
|
||||
|
||||
// Reset the warning flag so it can be shown again for future invalid tokens
|
||||
this.hasWarnedAboutMissingExpiration = false;
|
||||
} catch (error) {
|
||||
throw new AuthenticationError(
|
||||
`Failed to save auth credentials: ${(error as Error).message}`,
|
||||
|
||||
@@ -29,13 +29,17 @@ export class SupabaseAuthClient {
|
||||
*/
|
||||
getClient(): SupabaseJSClient {
|
||||
if (!this.client) {
|
||||
// Get Supabase configuration from environment - using TM_PUBLIC prefix
|
||||
const supabaseUrl = process.env.TM_PUBLIC_SUPABASE_URL;
|
||||
const supabaseAnonKey = process.env.TM_PUBLIC_SUPABASE_ANON_KEY;
|
||||
// Get Supabase configuration from environment
|
||||
// Runtime vars (TM_*) take precedence over build-time vars (TM_PUBLIC_*)
|
||||
const supabaseUrl =
|
||||
process.env.TM_SUPABASE_URL || process.env.TM_PUBLIC_SUPABASE_URL;
|
||||
const supabaseAnonKey =
|
||||
process.env.TM_SUPABASE_ANON_KEY ||
|
||||
process.env.TM_PUBLIC_SUPABASE_ANON_KEY;
|
||||
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
throw new AuthenticationError(
|
||||
'Supabase configuration missing. Please set TM_PUBLIC_SUPABASE_URL and TM_PUBLIC_SUPABASE_ANON_KEY environment variables.',
|
||||
'Supabase configuration missing. Please set TM_SUPABASE_URL and TM_SUPABASE_ANON_KEY (runtime) or TM_PUBLIC_SUPABASE_URL and TM_PUBLIC_SUPABASE_ANON_KEY (build-time) environment variables.',
|
||||
'CONFIG_MISSING'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -358,11 +358,12 @@ export class ExportService {
|
||||
tasks: any[]
|
||||
): Promise<void> {
|
||||
// Check if we should use the API endpoint or direct Supabase
|
||||
const useAPIEndpoint = process.env.TM_PUBLIC_BASE_DOMAIN;
|
||||
const apiEndpoint =
|
||||
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
|
||||
|
||||
if (useAPIEndpoint) {
|
||||
if (apiEndpoint) {
|
||||
// Use the new bulk import API endpoint
|
||||
const apiUrl = `${process.env.TM_PUBLIC_BASE_DOMAIN}/ai/api/v1/briefs/${briefId}/tasks`;
|
||||
const apiUrl = `${apiEndpoint}/ai/api/v1/briefs/${briefId}/tasks`;
|
||||
|
||||
// Transform tasks to flat structure for API
|
||||
const flatTasks = this.transformTasksForBulkImport(tasks);
|
||||
|
||||
@@ -27,6 +27,12 @@ export interface Brief {
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
document?: {
|
||||
id: string;
|
||||
title: string;
|
||||
document_name: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +177,12 @@ export class OrganizationService {
|
||||
document_id,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
updated_at,
|
||||
document:document_id (
|
||||
id,
|
||||
document_name,
|
||||
title
|
||||
)
|
||||
`)
|
||||
.eq('account_id', orgId);
|
||||
|
||||
@@ -196,7 +207,14 @@ export class OrganizationService {
|
||||
documentId: brief.document_id,
|
||||
status: brief.status,
|
||||
createdAt: brief.created_at,
|
||||
updatedAt: brief.updated_at
|
||||
updatedAt: brief.updated_at,
|
||||
document: brief.document
|
||||
? {
|
||||
id: brief.document.id,
|
||||
document_name: brief.document.document_name,
|
||||
title: brief.document.title
|
||||
}
|
||||
: undefined
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof TaskMasterError) {
|
||||
@@ -224,7 +242,13 @@ export class OrganizationService {
|
||||
document_id,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
updated_at,
|
||||
document:document_id (
|
||||
id,
|
||||
document_name,
|
||||
title,
|
||||
description
|
||||
)
|
||||
`)
|
||||
.eq('id', briefId)
|
||||
.single();
|
||||
@@ -253,7 +277,15 @@ export class OrganizationService {
|
||||
documentId: briefData.document_id,
|
||||
status: briefData.status,
|
||||
createdAt: briefData.created_at,
|
||||
updatedAt: briefData.updated_at
|
||||
updatedAt: briefData.updated_at,
|
||||
document: briefData.document
|
||||
? {
|
||||
id: briefData.document.id,
|
||||
document_name: briefData.document.document_name,
|
||||
title: briefData.document.title,
|
||||
description: briefData.document.description
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof TaskMasterError) {
|
||||
|
||||
@@ -112,6 +112,13 @@ export class ApiStorage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage type
|
||||
*/
|
||||
getType(): 'api' {
|
||||
return 'api';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tags into cache
|
||||
* In our API-based system, "tags" represent briefs
|
||||
|
||||
@@ -44,6 +44,13 @@ export class FileStorage implements IStorage {
|
||||
await this.fileOps.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage type
|
||||
*/
|
||||
getType(): 'file' {
|
||||
return 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about the storage
|
||||
*/
|
||||
|
||||
@@ -82,8 +82,8 @@ export class StorageFactory {
|
||||
apiAccessToken: credentials.token,
|
||||
apiEndpoint:
|
||||
config.storage?.apiEndpoint ||
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN ||
|
||||
'https://tryhamster.com/api'
|
||||
process.env.TM_BASE_DOMAIN ||
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN
|
||||
};
|
||||
config.storage = nextStorage;
|
||||
}
|
||||
@@ -112,6 +112,7 @@ export class StorageFactory {
|
||||
apiAccessToken: credentials.token,
|
||||
apiEndpoint:
|
||||
config.storage?.apiEndpoint ||
|
||||
process.env.TM_BASE_DOMAIN ||
|
||||
process.env.TM_PUBLIC_BASE_DOMAIN ||
|
||||
'https://tryhamster.com/api'
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load .env BEFORE any other imports to ensure env vars are available
|
||||
dotenv.config();
|
||||
|
||||
// Add at the very beginning of the file
|
||||
@@ -16,7 +18,8 @@ if (process.env.DEBUG === '1') {
|
||||
console.error('DEBUG - dev.js received args:', process.argv.slice(2));
|
||||
}
|
||||
|
||||
import { runCLI } from './modules/commands.js';
|
||||
// Use dynamic import to ensure dotenv.config() runs before module-level code executes
|
||||
const { runCLI } = await import('./modules/commands.js');
|
||||
|
||||
// Run the CLI with the process arguments
|
||||
runCLI(process.argv);
|
||||
|
||||
Reference in New Issue
Block a user