Compare commits
2 Commits
ralph/fix/
...
docs/auto-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24e480ce87 | ||
|
|
662e3865f3 |
@@ -15,6 +15,7 @@ import {
|
||||
} from '@tm/core/auth';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { ContextCommand } from './context.command.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from auth command
|
||||
@@ -117,8 +118,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +134,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +146,7 @@ export class AuthCommand extends Command {
|
||||
const result = this.displayStatus();
|
||||
this.setLastResult(result);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +162,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +387,7 @@ export class AuthCommand extends Command {
|
||||
message: 'Authentication successful'
|
||||
};
|
||||
} catch (error) {
|
||||
this.handleAuthError(error as AuthenticationError);
|
||||
displayError(error, { skipExit: true });
|
||||
|
||||
return {
|
||||
success: false,
|
||||
@@ -453,51 +450,6 @@ export class AuthCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authentication errors
|
||||
*/
|
||||
private handleAuthError(error: AuthenticationError): void {
|
||||
console.error(chalk.red(`\n✗ ${error.message}`));
|
||||
|
||||
switch (error.code) {
|
||||
case 'NETWORK_ERROR':
|
||||
ui.displayWarning(
|
||||
'Please check your internet connection and try again.'
|
||||
);
|
||||
break;
|
||||
case 'INVALID_CREDENTIALS':
|
||||
ui.displayWarning('Please check your credentials and try again.');
|
||||
break;
|
||||
case 'AUTH_EXPIRED':
|
||||
ui.displayWarning(
|
||||
'Your session has expired. Please authenticate again.'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack || ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle general errors
|
||||
*/
|
||||
private handleError(error: any): void {
|
||||
if (error instanceof AuthenticationError) {
|
||||
this.handleAuthError(error);
|
||||
} else {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
|
||||
@@ -8,12 +8,9 @@ import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import search from '@inquirer/search';
|
||||
import ora, { Ora } from 'ora';
|
||||
import {
|
||||
AuthManager,
|
||||
AuthenticationError,
|
||||
type UserContext
|
||||
} from '@tm/core/auth';
|
||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from context command
|
||||
@@ -119,8 +116,7 @@ export class ContextCommand extends Command {
|
||||
const result = this.displayContext();
|
||||
this.setLastResult(result);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,8 +212,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +253,7 @@ export class ContextCommand extends Command {
|
||||
this.authManager.updateContext({
|
||||
orgId: selectedOrg.id,
|
||||
orgName: selectedOrg.name,
|
||||
orgSlug: selectedOrg.slug,
|
||||
// Clear brief when changing org
|
||||
briefId: undefined,
|
||||
briefName: undefined
|
||||
@@ -304,8 +300,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +424,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,8 +470,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,11 +506,13 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Fetch org to get a friendly name (optional)
|
||||
// Fetch org to get a friendly name and slug (optional)
|
||||
let orgName: string | undefined;
|
||||
let orgSlug: string | undefined;
|
||||
try {
|
||||
const org = await this.authManager.getOrganization(brief.accountId);
|
||||
orgName = org?.name;
|
||||
orgSlug = org?.slug;
|
||||
} catch {
|
||||
// Non-fatal if org lookup fails
|
||||
}
|
||||
@@ -528,6 +523,7 @@ export class ContextCommand extends Command {
|
||||
this.authManager.updateContext({
|
||||
orgId: brief.accountId,
|
||||
orgName,
|
||||
orgSlug,
|
||||
briefId: brief.id,
|
||||
briefName
|
||||
});
|
||||
@@ -549,8 +545,7 @@ export class ContextCommand extends Command {
|
||||
try {
|
||||
if (spinner?.isSpinning) spinner.stop();
|
||||
} catch {}
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,26 +674,6 @@ export class ContextCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -7,13 +7,10 @@ import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import ora, { Ora } from 'ora';
|
||||
import {
|
||||
AuthManager,
|
||||
AuthenticationError,
|
||||
type UserContext
|
||||
} from '@tm/core/auth';
|
||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
||||
import { TaskMasterCore, type ExportResult } from '@tm/core';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from export command
|
||||
@@ -197,8 +194,7 @@ export class ExportCommand extends Command {
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (spinner?.isSpinning) spinner.fail('Export failed');
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,26 +330,6 @@ export class ExportCommand extends Command {
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last export result (useful for testing)
|
||||
*/
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
} from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
import {
|
||||
displayHeader,
|
||||
displayDashboards,
|
||||
calculateTaskStatistics,
|
||||
calculateSubtaskStatistics,
|
||||
@@ -106,14 +107,7 @@ export class ListTasksCommand extends Command {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,15 +251,12 @@ export class ListTasksCommand extends Command {
|
||||
* Display in text format with tables
|
||||
*/
|
||||
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
|
||||
const { tasks, tag } = data;
|
||||
const { tasks, tag, storageType } = data;
|
||||
|
||||
// Get file path for display
|
||||
const filePath = this.tmCore ? `.taskmaster/tasks/tasks.json` : undefined;
|
||||
|
||||
// Display header without banner (banner already shown by main CLI)
|
||||
displayHeader({
|
||||
// Display header using utility function
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: tag || 'master',
|
||||
filePath: filePath
|
||||
storageType
|
||||
});
|
||||
|
||||
// No tasks message
|
||||
|
||||
@@ -9,8 +9,9 @@ import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import { displayHeader } from '../ui/index.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
|
||||
/**
|
||||
* Options interface for the next command
|
||||
@@ -58,6 +59,7 @@ export class NextCommand extends Command {
|
||||
* Execute the next command
|
||||
*/
|
||||
private async executeCommand(options: NextCommandOptions): Promise<void> {
|
||||
let hasError = false;
|
||||
try {
|
||||
// Validate options (throws on invalid options)
|
||||
this.validateOptions(options);
|
||||
@@ -76,16 +78,17 @@ export class NextCommand extends Command {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
|
||||
// Allow error to propagate for library compatibility
|
||||
throw new Error(msg.message || 'Unexpected error in next command');
|
||||
hasError = true;
|
||||
displayError(error, { skipExit: true });
|
||||
} finally {
|
||||
// Always clean up resources, even on error
|
||||
await this.cleanup();
|
||||
}
|
||||
|
||||
// Exit after cleanup completes
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,9 +173,10 @@ export class NextCommand extends Command {
|
||||
* Display in text format
|
||||
*/
|
||||
private displayText(result: NextTaskResult): void {
|
||||
// Display header with tag (no file path for next command)
|
||||
displayHeader({
|
||||
tag: result.tag || 'master'
|
||||
// Display header with storage info
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: result.tag || 'master',
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
if (!result.found || !result.task) {
|
||||
@@ -191,7 +195,6 @@ export class NextCommand extends Command {
|
||||
}
|
||||
)
|
||||
);
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
console.log(
|
||||
`\n${chalk.dim('Tip: Try')} ${chalk.cyan('task-master list --status pending')} ${chalk.dim('to see all pending tasks')}`
|
||||
);
|
||||
@@ -208,8 +211,6 @@ export class NextCommand extends Command {
|
||||
headerColor: 'green',
|
||||
showSuggestedActions: true
|
||||
});
|
||||
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type TaskStatus
|
||||
} from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Valid task status values for validation
|
||||
@@ -85,6 +86,7 @@ export class SetStatusCommand extends Command {
|
||||
private async executeCommand(
|
||||
options: SetStatusCommandOptions
|
||||
): Promise<void> {
|
||||
let hasError = false;
|
||||
try {
|
||||
// Validate required options
|
||||
if (!options.id) {
|
||||
@@ -135,16 +137,15 @@ export class SetStatusCommand extends Command {
|
||||
oldStatus: result.oldStatus,
|
||||
newStatus: result.newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
if (!options.silent) {
|
||||
console.error(
|
||||
chalk.red(`Failed to update task ${taskId}: ${errorMessage}`)
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
hasError = true;
|
||||
if (options.format === 'json') {
|
||||
const errorMessage = error?.getSanitizedDetails
|
||||
? error.getSanitizedDetails().message
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: String(error);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
@@ -153,8 +154,13 @@ export class SetStatusCommand extends Command {
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
} else if (!options.silent) {
|
||||
// Show which task failed with context
|
||||
console.error(chalk.red(`\nFailed to update task ${taskId}:`));
|
||||
displayError(error, { skipExit: true });
|
||||
}
|
||||
process.exit(1);
|
||||
// Don't exit here - let finally block clean up first
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,25 +176,26 @@ export class SetStatusCommand extends Command {
|
||||
|
||||
// Display results
|
||||
this.displayResults(this.lastResult, options);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
|
||||
if (!options.silent) {
|
||||
console.error(chalk.red(`Error: ${errorMessage}`));
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
hasError = true;
|
||||
if (options.format === 'json') {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
console.log(JSON.stringify({ success: false, error: errorMessage }));
|
||||
} else if (!options.silent) {
|
||||
displayError(error, { skipExit: true });
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// Clean up resources
|
||||
if (this.tmCore) {
|
||||
await this.tmCore.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Exit after cleanup completes
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,9 @@ import boxen from 'boxen';
|
||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
|
||||
/**
|
||||
* Options interface for the show command
|
||||
@@ -112,14 +114,7 @@ export class ShowCommand extends Command {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +252,15 @@ export class ShowCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display header with storage info
|
||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: activeTag,
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
console.log(); // Add spacing
|
||||
|
||||
// Use the global task details display function
|
||||
displayTaskDetails(result.task, {
|
||||
statusFilter: options.status,
|
||||
@@ -271,8 +275,12 @@ export class ShowCommand extends Command {
|
||||
result: ShowMultipleTasksResult,
|
||||
_options: ShowCommandOptions
|
||||
): void {
|
||||
// Header
|
||||
ui.displayBanner(`Tasks (${result.tasks.length} found)`);
|
||||
// Display header with storage info
|
||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: activeTag,
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
if (result.notFound.length > 0) {
|
||||
console.log(chalk.yellow(`\n⚠ Not found: ${result.notFound.join(', ')}`));
|
||||
@@ -291,8 +299,6 @@ export class ShowCommand extends Command {
|
||||
showDependencies: true
|
||||
})
|
||||
);
|
||||
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@tm/core';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* CLI-specific options interface for the start command
|
||||
@@ -160,8 +161,7 @@ export class StartCommand extends Command {
|
||||
if (spinner) {
|
||||
spinner.fail('Operation failed');
|
||||
}
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,22 +452,6 @@ export class StartCommand extends Command {
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle general errors
|
||||
*/
|
||||
private handleError(error: any): void {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
|
||||
// Show stack trace in development mode or when DEBUG is set
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
if ((isDevelopment || process.env.DEBUG) && error.stack) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,9 @@ export {
|
||||
// UI utilities (for other commands to use)
|
||||
export * as ui from './utils/ui.js';
|
||||
|
||||
// Error handling utilities
|
||||
export { displayError, isDebugMode } from './utils/error-handler.js';
|
||||
|
||||
// Auto-update utilities
|
||||
export {
|
||||
checkForUpdate,
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Brief information for API storage
|
||||
*/
|
||||
export interface BriefInfo {
|
||||
briefId: string;
|
||||
briefName: string;
|
||||
orgSlug?: string;
|
||||
webAppUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header configuration options
|
||||
*/
|
||||
@@ -12,16 +22,44 @@ export interface HeaderOptions {
|
||||
title?: string;
|
||||
tag?: string;
|
||||
filePath?: string;
|
||||
storageType?: 'api' | 'file';
|
||||
briefInfo?: BriefInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Task Master header with project info
|
||||
*/
|
||||
export function displayHeader(options: HeaderOptions = {}): void {
|
||||
const { filePath, tag } = options;
|
||||
const { filePath, tag, storageType, briefInfo } = options;
|
||||
|
||||
// Display tag and file path info
|
||||
if (tag) {
|
||||
// Display different header based on storage type
|
||||
if (storageType === 'api' && briefInfo) {
|
||||
// API storage: Show brief information
|
||||
const briefDisplay = `🏷 Brief: ${chalk.cyan(briefInfo.briefName)} ${chalk.gray(`(${briefInfo.briefId})`)}`;
|
||||
console.log(briefDisplay);
|
||||
|
||||
// Construct and display the brief URL or ID
|
||||
if (briefInfo.webAppUrl && briefInfo.orgSlug) {
|
||||
const briefUrl = `${briefInfo.webAppUrl}/home/${briefInfo.orgSlug}/briefs/${briefInfo.briefId}/plan`;
|
||||
console.log(`Listing tasks from: ${chalk.dim(briefUrl)}`);
|
||||
} else if (briefInfo.webAppUrl) {
|
||||
// Show web app URL and brief ID if org slug is missing
|
||||
console.log(
|
||||
`Listing tasks from: ${chalk.dim(`${briefInfo.webAppUrl} (Brief: ${briefInfo.briefId})`)}`
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`💡 Tip: Run ${chalk.cyan('tm context select')} to set your organization and see the full URL`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Fallback: just show the brief ID if we can't get web app URL
|
||||
console.log(
|
||||
`Listing tasks from: ${chalk.dim(`API (Brief ID: ${briefInfo.briefId})`)}`
|
||||
);
|
||||
}
|
||||
} else if (tag) {
|
||||
// File storage: Show tag information
|
||||
let tagInfo = '';
|
||||
|
||||
if (tag && tag !== 'master') {
|
||||
|
||||
75
apps/cli/src/utils/display-helpers.ts
Normal file
75
apps/cli/src/utils/display-helpers.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @fileoverview Display helper utilities for commands
|
||||
* Provides DRY utilities for displaying headers and other command output
|
||||
*/
|
||||
|
||||
import type { TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayHeader, type BriefInfo } from '../ui/index.js';
|
||||
|
||||
/**
|
||||
* Get web app base URL from environment
|
||||
*/
|
||||
function getWebAppUrl(): string | undefined {
|
||||
const baseDomain =
|
||||
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
|
||||
|
||||
if (!baseDomain) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If it already includes protocol, use as-is
|
||||
if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) {
|
||||
return baseDomain;
|
||||
}
|
||||
|
||||
// Otherwise, add protocol based on domain
|
||||
if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) {
|
||||
return `http://${baseDomain}`;
|
||||
}
|
||||
|
||||
return `https://${baseDomain}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the command header with appropriate storage information
|
||||
* Handles both API and file storage displays
|
||||
*/
|
||||
export function displayCommandHeader(
|
||||
tmCore: TaskMasterCore | undefined,
|
||||
options: {
|
||||
tag?: string;
|
||||
storageType: Exclude<StorageType, 'auto'>;
|
||||
}
|
||||
): void {
|
||||
const { tag, storageType } = options;
|
||||
|
||||
// Get brief info if using API storage
|
||||
let briefInfo: BriefInfo | undefined;
|
||||
if (storageType === 'api' && tmCore) {
|
||||
const storageInfo = tmCore.getStorageDisplayInfo();
|
||||
if (storageInfo) {
|
||||
// Construct full brief info with web app URL
|
||||
briefInfo = {
|
||||
...storageInfo,
|
||||
webAppUrl: getWebAppUrl()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get file path for display (only for file storage)
|
||||
// Note: The file structure is fixed for file storage and won't change.
|
||||
// This is a display-only relative path, not used for actual file operations.
|
||||
const filePath =
|
||||
storageType === 'file' && tmCore
|
||||
? `.taskmaster/tasks/tasks.json`
|
||||
: undefined;
|
||||
|
||||
// Display header
|
||||
displayHeader({
|
||||
tag: tag || 'master',
|
||||
filePath: filePath,
|
||||
storageType: storageType === 'api' ? 'api' : 'file',
|
||||
briefInfo: briefInfo
|
||||
});
|
||||
}
|
||||
60
apps/cli/src/utils/error-handler.ts
Normal file
60
apps/cli/src/utils/error-handler.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @fileoverview Centralized error handling utilities for CLI
|
||||
* Provides consistent error formatting and debug mode detection
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Check if debug mode is enabled via environment variable
|
||||
* Only returns true when DEBUG is explicitly set to 'true' or '1'
|
||||
*
|
||||
* @returns True if debug mode is enabled
|
||||
*/
|
||||
export function isDebugMode(): boolean {
|
||||
return process.env.DEBUG === 'true' || process.env.DEBUG === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error to the user with optional stack trace in debug mode
|
||||
* Handles both TaskMasterError instances and regular errors
|
||||
*
|
||||
* @param error - The error to display
|
||||
* @param options - Display options
|
||||
*/
|
||||
export function displayError(
|
||||
error: any,
|
||||
options: {
|
||||
/** Skip exit, useful when caller wants to handle exit */
|
||||
skipExit?: boolean;
|
||||
/** Force show stack trace regardless of debug mode */
|
||||
forceStack?: boolean;
|
||||
} = {}
|
||||
): void {
|
||||
// Check if it's a TaskMasterError with sanitized details
|
||||
if (error?.getSanitizedDetails) {
|
||||
const sanitized = error.getSanitizedDetails();
|
||||
console.error(chalk.red(`\n${sanitized.message}`));
|
||||
|
||||
// Show stack trace in debug mode or if forced
|
||||
if ((isDebugMode() || options.forceStack) && error.stack) {
|
||||
console.error(chalk.gray('\nStack trace:'));
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
} else {
|
||||
// For other errors, show the message
|
||||
const message = error?.message ?? String(error);
|
||||
console.error(chalk.red(`\nError: ${message}`));
|
||||
|
||||
// Show stack trace in debug mode or if forced
|
||||
if ((isDebugMode() || options.forceStack) && error?.stack) {
|
||||
console.error(chalk.gray('\nStack trace:'));
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if not skipped
|
||||
if (!options.skipExit) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,22 @@ title: CLI Commands
|
||||
sidebarTitle: "CLI Commands"
|
||||
---
|
||||
|
||||
## Debug Mode
|
||||
|
||||
For troubleshooting issues, you can enable debug mode to get detailed error information:
|
||||
|
||||
```bash
|
||||
# Enable debug mode for any command
|
||||
DEBUG=true task-master <command>
|
||||
|
||||
# Example with next command
|
||||
DEBUG=true task-master next
|
||||
```
|
||||
|
||||
Debug mode shows full error messages, stack traces, and additional context. See the [Troubleshooting guide](/getting-started/troubleshooting) for more details.
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Parse PRD">
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
]
|
||||
},
|
||||
"getting-started/api-keys",
|
||||
"getting-started/troubleshooting",
|
||||
"getting-started/faq",
|
||||
"getting-started/contribute"
|
||||
]
|
||||
|
||||
@@ -3,7 +3,32 @@ title: FAQ
|
||||
sidebarTitle: "FAQ"
|
||||
---
|
||||
|
||||
Coming soon.
|
||||
## Common Questions
|
||||
|
||||
### How do I get more detailed error information?
|
||||
|
||||
Enable debug mode by setting the `DEBUG` environment variable:
|
||||
|
||||
```bash
|
||||
DEBUG=true task-master <command>
|
||||
```
|
||||
|
||||
This shows full error messages, stack traces, and additional context. See our [Troubleshooting guide](/getting-started/troubleshooting) for complete details.
|
||||
|
||||
### Why are my commands failing silently?
|
||||
|
||||
Task Master uses sanitized error messages by default to provide a clean user experience. Enable debug mode to see technical details about what went wrong.
|
||||
|
||||
### How do I report a bug?
|
||||
|
||||
When reporting issues, please:
|
||||
1. Enable debug mode and include the full error output
|
||||
2. Provide your environment details (OS, Node.js version, Task Master version)
|
||||
3. Include the command that failed and your project configuration
|
||||
|
||||
See our [Troubleshooting guide](/getting-started/troubleshooting) for complete bug reporting guidelines.
|
||||
|
||||
More frequently asked questions coming soon.
|
||||
|
||||
## 💬 Getting Help
|
||||
|
||||
|
||||
155
apps/docs/getting-started/troubleshooting.mdx
Normal file
155
apps/docs/getting-started/troubleshooting.mdx
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
sidebarTitle: "Troubleshooting"
|
||||
---
|
||||
|
||||
This guide helps you troubleshoot common issues and get more detailed error information when using Task Master.
|
||||
|
||||
## Debug Mode
|
||||
|
||||
Task Master includes a debug mode that provides detailed error information including stack traces. This is especially useful when reporting issues or debugging problems.
|
||||
|
||||
### Enabling Debug Mode
|
||||
|
||||
Set the `DEBUG` environment variable to enable debug mode:
|
||||
|
||||
```bash
|
||||
# Enable debug mode for a single command
|
||||
DEBUG=true task-master next
|
||||
|
||||
# Or use '1' instead of 'true'
|
||||
DEBUG=1 task-master list
|
||||
|
||||
# Enable for entire session (bash/zsh)
|
||||
export DEBUG=true
|
||||
task-master next # Now shows detailed errors
|
||||
|
||||
# Enable for entire session (fish shell)
|
||||
set -x DEBUG true
|
||||
task-master next
|
||||
```
|
||||
|
||||
### What Debug Mode Shows
|
||||
|
||||
With debug mode enabled, errors will include:
|
||||
|
||||
- **Full error messages**: Complete technical details instead of sanitized user messages
|
||||
- **Stack traces**: Detailed information about where errors occurred
|
||||
- **Error context**: Additional metadata about the failed operation
|
||||
- **Error chains**: If an error was caused by another error, both are shown
|
||||
|
||||
### Example Output
|
||||
|
||||
**Normal mode (default):**
|
||||
```
|
||||
Error: Unable to read tasks file
|
||||
```
|
||||
|
||||
**Debug mode:**
|
||||
```
|
||||
TaskMasterError[FILE_READ_ERROR]: Unable to read tasks file
|
||||
|
||||
Stack trace:
|
||||
at TaskService.loadTasks (/path/to/task-service.ts:123)
|
||||
at NextCommand.execute (/path/to/next.command.ts:45)
|
||||
...
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### File Permission Errors
|
||||
|
||||
**Problem**: Commands fail with file access errors
|
||||
|
||||
**Solution**:
|
||||
1. Check file permissions in `.taskmaster/` directory
|
||||
2. Ensure your user has write access to the project directory
|
||||
3. Run with debug mode to see the exact file path causing issues
|
||||
|
||||
### API Connection Issues
|
||||
|
||||
**Problem**: Commands timeout or fail when using API storage
|
||||
|
||||
**Solutions**:
|
||||
1. Check your internet connection
|
||||
2. Verify API keys are correctly configured
|
||||
3. Check if your organization has firewall restrictions
|
||||
4. Use debug mode to see specific network errors
|
||||
|
||||
### Task File Corruption
|
||||
|
||||
**Problem**: Commands report invalid task data
|
||||
|
||||
**Solutions**:
|
||||
1. Check if `.taskmaster/tasks/tasks.json` is valid JSON
|
||||
2. Restore from backup if available
|
||||
3. Run `task-master generate` to regenerate task files from main data
|
||||
|
||||
### Model Configuration Issues
|
||||
|
||||
**Problem**: AI-powered commands fail
|
||||
|
||||
**Solutions**:
|
||||
1. Verify API keys are set: `task-master models`
|
||||
2. Check if the configured model is available
|
||||
3. Try switching to a different model
|
||||
4. Use debug mode to see specific API error messages
|
||||
|
||||
## Getting Help
|
||||
|
||||
When reporting issues, please:
|
||||
|
||||
1. **Enable debug mode** and include the full error output
|
||||
2. **Specify your environment**:
|
||||
- Operating system and version
|
||||
- Node.js version (`node --version`)
|
||||
- Task Master version (`task-master --version`)
|
||||
3. **Include relevant context**:
|
||||
- Command that failed
|
||||
- Project configuration
|
||||
- Recent changes to your setup
|
||||
|
||||
### Support Channels
|
||||
|
||||
- **GitHub Issues**: [Report bugs and request features](https://github.com/eyaltoledano/claude-task-master/issues)
|
||||
- **Discord**: [Join our community for help](https://discord.gg/taskmasterai)
|
||||
- **GitHub Discussions**: [Ask questions and share ideas](https://github.com/eyaltoledano/claude-task-master/discussions)
|
||||
|
||||
## Advanced Debugging
|
||||
|
||||
### Inspecting Task Data
|
||||
|
||||
If you suspect task data corruption:
|
||||
|
||||
```bash
|
||||
# View raw task data
|
||||
cat .taskmaster/tasks/tasks.json | jq '.'
|
||||
|
||||
# Check specific task
|
||||
task-master show <task-id> --format=json
|
||||
```
|
||||
|
||||
### Configuration Debugging
|
||||
|
||||
Check your current configuration:
|
||||
|
||||
```bash
|
||||
# View model configuration
|
||||
task-master models
|
||||
|
||||
# Check if initialization is complete
|
||||
ls -la .taskmaster/
|
||||
```
|
||||
|
||||
### Network Debugging
|
||||
|
||||
For API storage issues:
|
||||
|
||||
```bash
|
||||
# Test with curl (replace with your API endpoint)
|
||||
curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
https://api.anthropic.com/v1/models
|
||||
```
|
||||
|
||||
Remember to enable debug mode (`DEBUG=true`) when troubleshooting to get the most helpful error information.
|
||||
62
output.txt
Normal file
62
output.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -16,6 +16,7 @@ export interface AuthCredentials {
|
||||
export interface UserContext {
|
||||
orgId?: string;
|
||||
orgName?: string;
|
||||
orgSlug?: string;
|
||||
briefId?: string;
|
||||
briefName?: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -52,7 +52,10 @@ export const ERROR_CODES = {
|
||||
INVALID_INPUT: 'INVALID_INPUT',
|
||||
NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
|
||||
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
||||
NOT_FOUND: 'NOT_FOUND'
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
|
||||
// Context errors
|
||||
NO_BRIEF_SELECTED: 'NO_BRIEF_SELECTED'
|
||||
} as const;
|
||||
|
||||
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
||||
|
||||
@@ -161,6 +161,16 @@ export class TaskService {
|
||||
storageType: this.getStorageType()
|
||||
};
|
||||
} catch (error) {
|
||||
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't log it as an internal error
|
||||
if (
|
||||
error instanceof TaskMasterError &&
|
||||
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
|
||||
) {
|
||||
// Just re-throw user-facing errors without wrapping
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Log internal errors
|
||||
this.logger.error('Failed to get task list', error);
|
||||
throw new TaskMasterError(
|
||||
'Failed to get task list',
|
||||
@@ -186,6 +196,14 @@ export class TaskService {
|
||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
||||
return await this.storage.loadTask(String(taskId), activeTag);
|
||||
} catch (error) {
|
||||
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
|
||||
if (
|
||||
error instanceof TaskMasterError &&
|
||||
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TaskMasterError(
|
||||
`Failed to get task ${taskId}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
@@ -522,6 +540,14 @@ export class TaskService {
|
||||
activeTag
|
||||
);
|
||||
} catch (error) {
|
||||
// If it's a user-facing error (like NO_BRIEF_SELECTED), don't wrap it
|
||||
if (
|
||||
error instanceof TaskMasterError &&
|
||||
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TaskMasterError(
|
||||
`Failed to update task status for ${taskIdStr}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
|
||||
@@ -37,6 +37,13 @@ export interface ApiStorageConfig {
|
||||
maxRetries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth context with a guaranteed briefId
|
||||
*/
|
||||
type ContextWithBrief = NonNullable<
|
||||
ReturnType<typeof AuthManager.prototype.getContext>
|
||||
> & { briefId: string };
|
||||
|
||||
/**
|
||||
* ApiStorage implementation using repository pattern
|
||||
* Provides flexibility to swap between different backend implementations
|
||||
@@ -126,7 +133,7 @@ export class ApiStorage implements IStorage {
|
||||
private async loadTagsIntoCache(): Promise<void> {
|
||||
try {
|
||||
const authManager = AuthManager.getInstance();
|
||||
const context = await authManager.getContext();
|
||||
const context = authManager.getContext();
|
||||
|
||||
// If we have a selected brief, create a virtual "tag" for it
|
||||
if (context?.briefId) {
|
||||
@@ -158,15 +165,7 @@ export class ApiStorage implements IStorage {
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
const authManager = AuthManager.getInstance();
|
||||
const context = await authManager.getContext();
|
||||
|
||||
// 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 <brief-id>'
|
||||
);
|
||||
}
|
||||
const context = this.ensureBriefSelected('loadTasks');
|
||||
|
||||
// Load tasks from the current brief context with filters pushed to repository
|
||||
const tasks = await this.retryOperation(() =>
|
||||
@@ -181,12 +180,11 @@ export class ApiStorage implements IStorage {
|
||||
|
||||
return tasks;
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
'Failed to load tasks from API',
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{ operation: 'loadTasks', tag, context: 'brief-based loading' },
|
||||
error as Error
|
||||
);
|
||||
this.wrapError(error, 'Failed to load tasks from API', {
|
||||
operation: 'loadTasks',
|
||||
tag,
|
||||
context: 'brief-based loading'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,16 +235,17 @@ export class ApiStorage implements IStorage {
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
this.ensureBriefSelected('loadTask');
|
||||
|
||||
return await this.retryOperation(() =>
|
||||
this.repository.getTask(this.projectId, taskId)
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
'Failed to load task from API',
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{ operation: 'loadTask', taskId, tag },
|
||||
error as Error
|
||||
);
|
||||
this.wrapError(error, 'Failed to load task from API', {
|
||||
operation: 'loadTask',
|
||||
taskId,
|
||||
tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +324,7 @@ export class ApiStorage implements IStorage {
|
||||
|
||||
try {
|
||||
const authManager = AuthManager.getInstance();
|
||||
const context = await authManager.getContext();
|
||||
const context = authManager.getContext();
|
||||
|
||||
// In our API-based system, we only have one "tag" at a time - the current brief
|
||||
if (context?.briefId) {
|
||||
@@ -510,6 +509,8 @@ export class ApiStorage implements IStorage {
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
this.ensureBriefSelected('updateTaskStatus');
|
||||
|
||||
const existingTask = await this.retryOperation(() =>
|
||||
this.repository.getTask(this.projectId, taskId)
|
||||
);
|
||||
@@ -546,12 +547,12 @@ export class ApiStorage implements IStorage {
|
||||
taskId
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
'Failed to update task status via API',
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{ operation: 'updateTaskStatus', taskId, newStatus, tag },
|
||||
error as Error
|
||||
);
|
||||
this.wrapError(error, 'Failed to update task status via API', {
|
||||
operation: 'updateTaskStatus',
|
||||
taskId,
|
||||
newStatus,
|
||||
tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,6 +770,29 @@ export class ApiStorage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a brief is selected in the current context
|
||||
* @returns The current auth context with a valid briefId
|
||||
*/
|
||||
private ensureBriefSelected(operation: string): ContextWithBrief {
|
||||
const authManager = AuthManager.getInstance();
|
||||
const context = authManager.getContext();
|
||||
|
||||
if (!context?.briefId) {
|
||||
throw new TaskMasterError(
|
||||
'No brief selected',
|
||||
ERROR_CODES.NO_BRIEF_SELECTED,
|
||||
{
|
||||
operation,
|
||||
userMessage:
|
||||
'No brief selected. Please select a brief first using: tm context brief <brief-id> or tm context brief <brief-url>'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return context as ContextWithBrief;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry an operation with exponential backoff
|
||||
*/
|
||||
@@ -787,4 +811,28 @@ export class ApiStorage implements IStorage {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an error unless it's already a NO_BRIEF_SELECTED error
|
||||
*/
|
||||
private wrapError(
|
||||
error: unknown,
|
||||
message: string,
|
||||
context: Record<string, unknown>
|
||||
): never {
|
||||
// If it's already a NO_BRIEF_SELECTED error, don't wrap it
|
||||
if (
|
||||
error instanceof TaskMasterError &&
|
||||
error.is(ERROR_CODES.NO_BRIEF_SELECTED)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TaskMasterError(
|
||||
message,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
context,
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,44 @@ export class TaskMasterCore {
|
||||
return this.taskService.getStorageType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage configuration
|
||||
*/
|
||||
getStorageConfig() {
|
||||
return this.configManager.getStorageConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage display information for headers
|
||||
* Returns context info for API storage, null for file storage
|
||||
*/
|
||||
getStorageDisplayInfo(): {
|
||||
briefId: string;
|
||||
briefName: string;
|
||||
orgSlug?: string;
|
||||
} | null {
|
||||
// Only return info if using API storage
|
||||
const storageType = this.getStorageType();
|
||||
if (storageType !== 'api') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get credentials from auth manager
|
||||
const authManager = AuthManager.getInstance();
|
||||
const credentials = authManager.getCredentials();
|
||||
const selectedContext = credentials?.selectedContext;
|
||||
|
||||
if (!selectedContext?.briefId || !selectedContext?.briefName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
briefId: selectedContext.briefId,
|
||||
briefName: selectedContext.briefName,
|
||||
orgSlug: selectedContext.orgSlug
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current active tag
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
registerAllCommands,
|
||||
checkForUpdate,
|
||||
performAutoUpdate,
|
||||
displayUpgradeNotification
|
||||
displayUpgradeNotification,
|
||||
displayError
|
||||
} from '@tm/cli';
|
||||
|
||||
import {
|
||||
@@ -5156,10 +5157,7 @@ async function runCLI(argv = process.argv) {
|
||||
);
|
||||
} else {
|
||||
// Generic error handling for other errors
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
if (getDebugFlag()) {
|
||||
console.error(error);
|
||||
}
|
||||
displayError(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user