mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat(ccw): migrate backend to TypeScript
- Convert 40 JS files to TypeScript (CLI, tools, core, MCP server) - Add Zod for runtime parameter validation - Add type definitions in src/types/ - Keep src/templates/ as JavaScript (dashboard frontend) - Update bin entries to use dist/ - Add tsconfig.json with strict mode - Add backward-compatible exports for tests - All 39 tests passing Breaking changes: None (backward compatible) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,26 @@ import {
|
||||
getExecutionDetail
|
||||
} from '../tools/cli-executor.js';
|
||||
|
||||
interface CliExecOptions {
|
||||
tool?: string;
|
||||
mode?: string;
|
||||
model?: string;
|
||||
cd?: string;
|
||||
includeDirs?: string;
|
||||
timeout?: string;
|
||||
noStream?: boolean;
|
||||
}
|
||||
|
||||
interface HistoryOptions {
|
||||
limit?: string;
|
||||
tool?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show CLI tool status
|
||||
*/
|
||||
async function statusAction() {
|
||||
async function statusAction(): Promise<void> {
|
||||
console.log(chalk.bold.cyan('\n CLI Tools Status\n'));
|
||||
|
||||
const status = await getCliToolsStatus();
|
||||
@@ -37,7 +53,7 @@ async function statusAction() {
|
||||
* @param {string} prompt - Prompt to execute
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function execAction(prompt, options) {
|
||||
async function execAction(prompt: string | undefined, options: CliExecOptions): Promise<void> {
|
||||
if (!prompt) {
|
||||
console.error(chalk.red('Error: Prompt is required'));
|
||||
console.error(chalk.gray('Usage: ccw cli exec "<prompt>" --tool gemini'));
|
||||
@@ -49,7 +65,7 @@ async function execAction(prompt, options) {
|
||||
console.log(chalk.cyan(`\n Executing ${tool} (${mode} mode)...\n`));
|
||||
|
||||
// Streaming output handler
|
||||
const onOutput = noStream ? null : (chunk) => {
|
||||
const onOutput = noStream ? null : (chunk: any) => {
|
||||
process.stdout.write(chunk.data);
|
||||
};
|
||||
|
||||
@@ -63,7 +79,7 @@ async function execAction(prompt, options) {
|
||||
include: includeDirs,
|
||||
timeout: timeout ? parseInt(timeout, 10) : 300000,
|
||||
stream: !noStream
|
||||
}, onOutput);
|
||||
});
|
||||
|
||||
// If not streaming, print output now
|
||||
if (noStream && result.stdout) {
|
||||
@@ -82,7 +98,8 @@ async function execAction(prompt, options) {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red(` Error: ${error.message}`));
|
||||
const err = error as Error;
|
||||
console.error(chalk.red(` Error: ${err.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -91,8 +108,8 @@ async function execAction(prompt, options) {
|
||||
* Show execution history
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function historyAction(options) {
|
||||
const { limit = 20, tool, status } = options;
|
||||
async function historyAction(options: HistoryOptions): Promise<void> {
|
||||
const { limit = '20', tool, status } = options;
|
||||
|
||||
console.log(chalk.bold.cyan('\n CLI Execution History\n'));
|
||||
|
||||
@@ -125,7 +142,7 @@ async function historyAction(options) {
|
||||
* Show execution detail
|
||||
* @param {string} executionId - Execution ID
|
||||
*/
|
||||
async function detailAction(executionId) {
|
||||
async function detailAction(executionId: string | undefined): Promise<void> {
|
||||
if (!executionId) {
|
||||
console.error(chalk.red('Error: Execution ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw cli detail <execution-id>'));
|
||||
@@ -173,8 +190,8 @@ async function detailAction(executionId) {
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
function getTimeAgo(date) {
|
||||
const seconds = Math.floor((new Date() - date) / 1000);
|
||||
function getTimeAgo(date: Date): string {
|
||||
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
||||
|
||||
if (seconds < 60) return 'just now';
|
||||
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
||||
@@ -189,7 +206,11 @@ function getTimeAgo(date) {
|
||||
* @param {string[]} args - Arguments array
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
export async function cliCommand(subcommand, args, options) {
|
||||
export async function cliCommand(
|
||||
subcommand: string,
|
||||
args: string | string[],
|
||||
options: CliExecOptions | HistoryOptions
|
||||
): Promise<void> {
|
||||
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
|
||||
|
||||
switch (subcommand) {
|
||||
@@ -198,11 +219,11 @@ export async function cliCommand(subcommand, args, options) {
|
||||
break;
|
||||
|
||||
case 'exec':
|
||||
await execAction(argsArray[0], options);
|
||||
await execAction(argsArray[0], options as CliExecOptions);
|
||||
break;
|
||||
|
||||
case 'history':
|
||||
await historyAction(options);
|
||||
await historyAction(options as HistoryOptions);
|
||||
break;
|
||||
|
||||
case 'detail':
|
||||
@@ -7,6 +7,7 @@ import chalk from 'chalk';
|
||||
import { showHeader, createSpinner, info, warning, error, summaryBox, divider } from '../utils/ui.js';
|
||||
import { createManifest, addFileEntry, addDirectoryEntry, saveManifest, findManifest, getAllManifests } from '../core/manifest.js';
|
||||
import { validatePath } from '../utils/path-resolver.js';
|
||||
import type { Spinner } from 'ora';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -17,13 +18,24 @@ const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
|
||||
// Subdirectories that should always be installed to global (~/.claude/)
|
||||
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
|
||||
|
||||
interface InstallOptions {
|
||||
mode?: string;
|
||||
path?: string;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
interface CopyResult {
|
||||
files: number;
|
||||
directories: number;
|
||||
}
|
||||
|
||||
// Get package root directory (ccw/src/commands -> ccw)
|
||||
function getPackageRoot() {
|
||||
function getPackageRoot(): string {
|
||||
return join(__dirname, '..', '..');
|
||||
}
|
||||
|
||||
// Get source installation directory (parent of ccw)
|
||||
function getSourceDir() {
|
||||
function getSourceDir(): string {
|
||||
return join(getPackageRoot(), '..');
|
||||
}
|
||||
|
||||
@@ -31,7 +43,7 @@ function getSourceDir() {
|
||||
* Install command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function installCommand(options) {
|
||||
export async function installCommand(options: InstallOptions): Promise<void> {
|
||||
const version = getVersion();
|
||||
|
||||
// Show beautiful header
|
||||
@@ -67,7 +79,7 @@ export async function installCommand(options) {
|
||||
// Interactive mode selection
|
||||
const mode = options.mode || await selectMode();
|
||||
|
||||
let installPath;
|
||||
let installPath: string;
|
||||
if (mode === 'Global') {
|
||||
installPath = homedir();
|
||||
info(`Global installation to: ${installPath}`);
|
||||
@@ -76,7 +88,7 @@ export async function installCommand(options) {
|
||||
|
||||
// Validate the installation path
|
||||
const pathValidation = validatePath(inputPath, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
if (!pathValidation.valid || !pathValidation.path) {
|
||||
error(`Invalid installation path: ${pathValidation.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -171,7 +183,8 @@ export async function installCommand(options) {
|
||||
|
||||
} catch (err) {
|
||||
spinner.fail('Installation failed');
|
||||
error(err.message);
|
||||
const errMsg = err as Error;
|
||||
error(errMsg.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -212,7 +225,7 @@ export async function installCommand(options) {
|
||||
* Interactive mode selection
|
||||
* @returns {Promise<string>} - Selected mode
|
||||
*/
|
||||
async function selectMode() {
|
||||
async function selectMode(): Promise<string> {
|
||||
const { mode } = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'mode',
|
||||
@@ -236,13 +249,13 @@ async function selectMode() {
|
||||
* Interactive path selection
|
||||
* @returns {Promise<string>} - Selected path
|
||||
*/
|
||||
async function selectPath() {
|
||||
async function selectPath(): Promise<string> {
|
||||
const { path } = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
message: 'Enter installation path:',
|
||||
default: process.cwd(),
|
||||
validate: (input) => {
|
||||
validate: (input: string) => {
|
||||
if (!input) return 'Path is required';
|
||||
if (!existsSync(input)) {
|
||||
return `Path does not exist: ${input}`;
|
||||
@@ -259,7 +272,7 @@ async function selectPath() {
|
||||
* @param {string} installPath - Installation path
|
||||
* @param {Object} manifest - Existing manifest
|
||||
*/
|
||||
async function createBackup(installPath, manifest) {
|
||||
async function createBackup(installPath: string, manifest: any): Promise<void> {
|
||||
const spinner = createSpinner('Creating backup...').start();
|
||||
|
||||
try {
|
||||
@@ -276,7 +289,8 @@ async function createBackup(installPath, manifest) {
|
||||
|
||||
spinner.succeed(`Backup created: ${backupDir}`);
|
||||
} catch (err) {
|
||||
spinner.warn(`Backup failed: ${err.message}`);
|
||||
const errMsg = err as Error;
|
||||
spinner.warn(`Backup failed: ${errMsg.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +302,12 @@ async function createBackup(installPath, manifest) {
|
||||
* @param {string[]} excludeDirs - Directory names to exclude (optional)
|
||||
* @returns {Object} - Count of files and directories
|
||||
*/
|
||||
async function copyDirectory(src, dest, manifest = null, excludeDirs = []) {
|
||||
async function copyDirectory(
|
||||
src: string,
|
||||
dest: string,
|
||||
manifest: any = null,
|
||||
excludeDirs: string[] = []
|
||||
): Promise<CopyResult> {
|
||||
let files = 0;
|
||||
let directories = 0;
|
||||
|
||||
@@ -329,7 +348,7 @@ async function copyDirectory(src, dest, manifest = null, excludeDirs = []) {
|
||||
* Get package version
|
||||
* @returns {string} - Version string
|
||||
*/
|
||||
function getVersion() {
|
||||
function getVersion(): string {
|
||||
try {
|
||||
// First try root package.json (parent of ccw)
|
||||
const rootPkgPath = join(getSourceDir(), 'package.json');
|
||||
@@ -5,7 +5,7 @@ import { getAllManifests } from '../core/manifest.js';
|
||||
/**
|
||||
* List command handler - shows all installations
|
||||
*/
|
||||
export async function listCommand() {
|
||||
export async function listCommand(): Promise<void> {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Installed Claude Code Workflow Instances\n'));
|
||||
|
||||
@@ -2,19 +2,26 @@ import { startServer } from '../core/server.js';
|
||||
import { launchBrowser } from '../utils/browser-launcher.js';
|
||||
import { resolvePath, validatePath } from '../utils/path-resolver.js';
|
||||
import chalk from 'chalk';
|
||||
import type { Server } from 'http';
|
||||
|
||||
interface ServeOptions {
|
||||
port?: number;
|
||||
path?: string;
|
||||
browser?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve command handler - starts dashboard server with live path switching
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function serveCommand(options) {
|
||||
export async function serveCommand(options: ServeOptions): Promise<void> {
|
||||
const port = options.port || 3456;
|
||||
|
||||
// Validate project path
|
||||
let initialPath = process.cwd();
|
||||
if (options.path) {
|
||||
const pathValidation = validatePath(options.path, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
if (!pathValidation.valid || !pathValidation.path) {
|
||||
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -40,7 +47,8 @@ export async function serveCommand(options) {
|
||||
await launchBrowser(url);
|
||||
console.log(chalk.green.bold('\n Dashboard opened in browser!'));
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${err.message}`));
|
||||
const error = err as Error;
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${error.message}`));
|
||||
console.log(chalk.gray(` Open manually: ${url}`));
|
||||
}
|
||||
}
|
||||
@@ -57,8 +65,9 @@ export async function serveCommand(options) {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
const err = error as Error & { code?: string };
|
||||
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(chalk.yellow(` Port ${port} is already in use.`));
|
||||
console.error(chalk.gray(` Try a different port: ccw serve --port ${port + 1}\n`));
|
||||
}
|
||||
@@ -8,18 +8,61 @@ import http from 'http';
|
||||
import { executeTool } from '../tools/index.js';
|
||||
|
||||
// Handle EPIPE errors gracefully (occurs when piping to head/jq that closes early)
|
||||
process.stdout.on('error', (err) => {
|
||||
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EPIPE') {
|
||||
process.exit(0);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
interface ListOptions {
|
||||
location?: string;
|
||||
metadata?: boolean;
|
||||
}
|
||||
|
||||
interface InitOptions {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface ReadOptions {
|
||||
type?: string;
|
||||
taskId?: string;
|
||||
filename?: string;
|
||||
dimension?: string;
|
||||
iteration?: string;
|
||||
raw?: boolean;
|
||||
}
|
||||
|
||||
interface WriteOptions {
|
||||
type?: string;
|
||||
content?: string;
|
||||
taskId?: string;
|
||||
filename?: string;
|
||||
dimension?: string;
|
||||
iteration?: string;
|
||||
}
|
||||
|
||||
interface UpdateOptions {
|
||||
type?: string;
|
||||
content?: string;
|
||||
taskId?: string;
|
||||
}
|
||||
|
||||
interface ArchiveOptions {
|
||||
updateStatus?: boolean;
|
||||
}
|
||||
|
||||
interface MkdirOptions {
|
||||
subdir?: string;
|
||||
}
|
||||
|
||||
interface StatsOptions {}
|
||||
|
||||
/**
|
||||
* Notify dashboard of granular events (fire and forget)
|
||||
* @param {Object} data - Event data
|
||||
*/
|
||||
function notifyDashboard(data) {
|
||||
function notifyDashboard(data: any): void {
|
||||
const DASHBOARD_PORT = process.env.CCW_PORT || 3456;
|
||||
const payload = JSON.stringify({
|
||||
...data,
|
||||
@@ -49,7 +92,7 @@ function notifyDashboard(data) {
|
||||
* List sessions
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function listAction(options) {
|
||||
async function listAction(options: ListOptions): Promise<void> {
|
||||
const params = {
|
||||
operation: 'list',
|
||||
location: options.location || 'both',
|
||||
@@ -63,7 +106,7 @@ async function listAction(options) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { active = [], archived = [], total } = result.result;
|
||||
const { active = [], archived = [], total } = (result.result as any);
|
||||
|
||||
console.log(chalk.bold.cyan('\nWorkflow Sessions\n'));
|
||||
|
||||
@@ -100,7 +143,7 @@ async function listAction(options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function initAction(sessionId, options) {
|
||||
async function initAction(sessionId: string | undefined, options: InitOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session init <session_id> [--type <type>]'));
|
||||
@@ -128,7 +171,7 @@ async function initAction(sessionId, options) {
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Session "${sessionId}" initialized`));
|
||||
console.log(chalk.gray(` Location: ${result.result.path}`));
|
||||
console.log(chalk.gray(` Location: ${(result.result as any).path}`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,14 +179,14 @@ async function initAction(sessionId, options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function readAction(sessionId, options) {
|
||||
async function readAction(sessionId: string | undefined, options: ReadOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session read <session_id> --type <content_type>'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const params = {
|
||||
const params: any = {
|
||||
operation: 'read',
|
||||
session_id: sessionId,
|
||||
content_type: options.type || 'session'
|
||||
@@ -164,9 +207,9 @@ async function readAction(sessionId, options) {
|
||||
|
||||
// Output raw content for piping
|
||||
if (options.raw) {
|
||||
console.log(typeof result.result.content === 'string'
|
||||
? result.result.content
|
||||
: JSON.stringify(result.result.content, null, 2));
|
||||
console.log(typeof (result.result as any).content === 'string'
|
||||
? (result.result as any).content
|
||||
: JSON.stringify((result.result as any).content, null, 2));
|
||||
} else {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
@@ -177,7 +220,7 @@ async function readAction(sessionId, options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function writeAction(sessionId, options) {
|
||||
async function writeAction(sessionId: string | undefined, options: WriteOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session write <session_id> --type <content_type> --content <json>'));
|
||||
@@ -189,7 +232,7 @@ async function writeAction(sessionId, options) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let content;
|
||||
let content: any;
|
||||
try {
|
||||
content = JSON.parse(options.content);
|
||||
} catch {
|
||||
@@ -197,7 +240,7 @@ async function writeAction(sessionId, options) {
|
||||
content = options.content;
|
||||
}
|
||||
|
||||
const params = {
|
||||
const params: any = {
|
||||
operation: 'write',
|
||||
session_id: sessionId,
|
||||
content_type: options.type || 'session',
|
||||
@@ -254,10 +297,10 @@ async function writeAction(sessionId, options) {
|
||||
sessionId: sessionId,
|
||||
entityId: entityId,
|
||||
contentType: contentType,
|
||||
payload: result.result.written_content || content
|
||||
payload: (result.result as any).written_content || content
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Content written to ${result.result.path}`));
|
||||
console.log(chalk.green(`✓ Content written to ${(result.result as any).path}`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +308,7 @@ async function writeAction(sessionId, options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function updateAction(sessionId, options) {
|
||||
async function updateAction(sessionId: string | undefined, options: UpdateOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session update <session_id> --content <json>'));
|
||||
@@ -277,16 +320,17 @@ async function updateAction(sessionId, options) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let content;
|
||||
let content: any;
|
||||
try {
|
||||
content = JSON.parse(options.content);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
console.error(chalk.red('Content must be valid JSON for update operation'));
|
||||
console.error(chalk.gray(`Parse error: ${e.message}`));
|
||||
console.error(chalk.gray(`Parse error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const params = {
|
||||
const params: any = {
|
||||
operation: 'update',
|
||||
session_id: sessionId,
|
||||
content_type: options.type || 'session',
|
||||
@@ -309,7 +353,7 @@ async function updateAction(sessionId, options) {
|
||||
type: eventType,
|
||||
sessionId: sessionId,
|
||||
entityId: options.taskId || null,
|
||||
payload: result.result.merged_data || content
|
||||
payload: (result.result as any).merged_data || content
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Session "${sessionId}" updated`));
|
||||
@@ -320,7 +364,7 @@ async function updateAction(sessionId, options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function archiveAction(sessionId, options) {
|
||||
async function archiveAction(sessionId: string | undefined, options: ArchiveOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session archive <session_id>'));
|
||||
@@ -348,7 +392,7 @@ async function archiveAction(sessionId, options) {
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Session "${sessionId}" archived`));
|
||||
console.log(chalk.gray(` Location: ${result.result.destination}`));
|
||||
console.log(chalk.gray(` Location: ${(result.result as any).destination}`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +400,7 @@ async function archiveAction(sessionId, options) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {string} newStatus - New status value
|
||||
*/
|
||||
async function statusAction(sessionId, newStatus) {
|
||||
async function statusAction(sessionId: string | undefined, newStatus: string | undefined): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session status <session_id> <status>'));
|
||||
@@ -406,7 +450,11 @@ async function statusAction(sessionId, newStatus) {
|
||||
* @param {string} taskId - Task ID
|
||||
* @param {string} newStatus - New status value
|
||||
*/
|
||||
async function taskAction(sessionId, taskId, newStatus) {
|
||||
async function taskAction(
|
||||
sessionId: string | undefined,
|
||||
taskId: string | undefined,
|
||||
newStatus: string | undefined
|
||||
): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session task <session_id> <task_id> <status>'));
|
||||
@@ -442,11 +490,11 @@ async function taskAction(sessionId, taskId, newStatus) {
|
||||
|
||||
const readResult = await executeTool('session_manager', readParams);
|
||||
|
||||
let currentTask = {};
|
||||
let currentTask: any = {};
|
||||
let oldStatus = 'unknown';
|
||||
|
||||
if (readResult.success) {
|
||||
currentTask = readResult.result.content || {};
|
||||
currentTask = (readResult.result as any).content || {};
|
||||
oldStatus = currentTask.status || 'unknown';
|
||||
}
|
||||
|
||||
@@ -493,7 +541,7 @@ async function taskAction(sessionId, taskId, newStatus) {
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function mkdirAction(sessionId, options) {
|
||||
async function mkdirAction(sessionId: string | undefined, options: MkdirOptions): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session mkdir <session_id> --subdir <subdir>'));
|
||||
@@ -522,23 +570,18 @@ async function mkdirAction(sessionId, options) {
|
||||
notifyDashboard({
|
||||
type: 'DIRECTORY_CREATED',
|
||||
sessionId: sessionId,
|
||||
payload: { directories: result.result.directories_created }
|
||||
payload: { directories: (result.result as any).directories_created }
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Directory created: ${result.result.directories_created.join(', ')}`));
|
||||
console.log(chalk.green(`✓ Directory created: ${(result.result as any).directories_created.join(', ')}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute raw operation (advanced)
|
||||
* @param {string} jsonParams - JSON parameters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Delete file within session
|
||||
* @param {string} sessionId - Session ID
|
||||
* @param {string} filePath - Relative file path
|
||||
*/
|
||||
async function deleteAction(sessionId, filePath) {
|
||||
async function deleteAction(sessionId: string | undefined, filePath: string | undefined): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session delete <session_id> <file_path>'));
|
||||
@@ -571,14 +614,14 @@ async function deleteAction(sessionId, filePath) {
|
||||
payload: { file_path: filePath }
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ File deleted: ${result.result.deleted}`));
|
||||
console.log(chalk.green(`✓ File deleted: ${(result.result as any).deleted}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session statistics
|
||||
* @param {string} sessionId - Session ID
|
||||
*/
|
||||
async function statsAction(sessionId, options = {}) {
|
||||
async function statsAction(sessionId: string | undefined, options: StatsOptions = {}): Promise<void> {
|
||||
if (!sessionId) {
|
||||
console.error(chalk.red('Session ID is required'));
|
||||
console.error(chalk.gray('Usage: ccw session stats <session_id>'));
|
||||
@@ -597,7 +640,7 @@ async function statsAction(sessionId, options = {}) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { tasks, summaries, has_plan, location } = result.result;
|
||||
const { tasks, summaries, has_plan, location } = (result.result as any);
|
||||
|
||||
console.log(chalk.bold.cyan(`\nSession Statistics: ${sessionId}`));
|
||||
console.log(chalk.gray(`Location: ${location}\n`));
|
||||
@@ -614,19 +657,21 @@ async function statsAction(sessionId, options = {}) {
|
||||
console.log(chalk.gray(` Summaries: ${summaries}`));
|
||||
console.log(chalk.gray(` Plan: ${has_plan ? 'Yes' : 'No'}`));
|
||||
}
|
||||
async function execAction(jsonParams) {
|
||||
|
||||
async function execAction(jsonParams: string | undefined): Promise<void> {
|
||||
if (!jsonParams) {
|
||||
console.error(chalk.red('JSON parameters required'));
|
||||
console.error(chalk.gray('Usage: ccw session exec \'{"operation":"list","location":"active"}\''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let params;
|
||||
let params: any;
|
||||
try {
|
||||
params = JSON.parse(jsonParams);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
console.error(chalk.red('Invalid JSON'));
|
||||
console.error(chalk.gray(`Parse error: ${e.message}`));
|
||||
console.error(chalk.gray(`Parse error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -636,7 +681,7 @@ async function execAction(jsonParams) {
|
||||
if (result.success && params.operation) {
|
||||
const writeOps = ['init', 'write', 'update', 'archive', 'mkdir', 'delete'];
|
||||
if (writeOps.includes(params.operation)) {
|
||||
const eventMap = {
|
||||
const eventMap: Record<string, string> = {
|
||||
init: 'SESSION_CREATED',
|
||||
write: 'CONTENT_WRITTEN',
|
||||
update: 'SESSION_UPDATED',
|
||||
@@ -662,7 +707,11 @@ async function execAction(jsonParams) {
|
||||
* @param {string[]} args - Arguments
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
export async function sessionCommand(subcommand, args, options) {
|
||||
export async function sessionCommand(
|
||||
subcommand: string,
|
||||
args: string | string[],
|
||||
options: any
|
||||
): Promise<void> {
|
||||
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
|
||||
|
||||
switch (subcommand) {
|
||||
@@ -4,12 +4,17 @@ import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface StopOptions {
|
||||
port?: number;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find process using a specific port (Windows)
|
||||
* @param {number} port - Port number
|
||||
* @returns {Promise<string|null>} PID or null
|
||||
*/
|
||||
async function findProcessOnPort(port) {
|
||||
async function findProcessOnPort(port: number): Promise<string | null> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
|
||||
const lines = stdout.trim().split('\n');
|
||||
@@ -28,7 +33,7 @@ async function findProcessOnPort(port) {
|
||||
* @param {string} pid - Process ID
|
||||
* @returns {Promise<boolean>} Success status
|
||||
*/
|
||||
async function killProcess(pid) {
|
||||
async function killProcess(pid: string): Promise<boolean> {
|
||||
try {
|
||||
await execAsync(`taskkill /PID ${pid} /F`);
|
||||
return true;
|
||||
@@ -41,7 +46,7 @@ async function killProcess(pid) {
|
||||
* Stop command handler - stops the running CCW dashboard server
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function stopCommand(options) {
|
||||
export async function stopCommand(options: StopOptions): Promise<void> {
|
||||
const port = options.port || 3456;
|
||||
const force = options.force || false;
|
||||
|
||||
@@ -96,6 +101,7 @@ export async function stopCommand(options) {
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
||||
const error = err as Error;
|
||||
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,32 @@
|
||||
import chalk from 'chalk';
|
||||
import { listTools, executeTool, getTool, getAllToolSchemas } from '../tools/index.js';
|
||||
|
||||
interface ToolOptions {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface ExecOptions {
|
||||
path?: string;
|
||||
old?: string;
|
||||
new?: string;
|
||||
action?: string;
|
||||
query?: string;
|
||||
limit?: string;
|
||||
file?: string;
|
||||
files?: string;
|
||||
languages?: string;
|
||||
mode?: string;
|
||||
operation?: string;
|
||||
line?: string;
|
||||
text?: string;
|
||||
dryRun?: boolean;
|
||||
replaceAll?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available tools
|
||||
*/
|
||||
async function listAction() {
|
||||
async function listAction(): Promise<void> {
|
||||
const tools = listTools();
|
||||
|
||||
if (tools.length === 0) {
|
||||
@@ -29,8 +51,8 @@ async function listAction() {
|
||||
console.log(chalk.gray(' Parameters:'));
|
||||
for (const [name, schema] of Object.entries(props)) {
|
||||
const req = required.includes(name) ? chalk.red('*') : '';
|
||||
const defaultVal = schema.default !== undefined ? chalk.gray(` (default: ${schema.default})`) : '';
|
||||
console.log(chalk.gray(` - ${name}${req}: ${schema.description}${defaultVal}`));
|
||||
const defaultVal = (schema as any).default !== undefined ? chalk.gray(` (default: ${(schema as any).default})`) : '';
|
||||
console.log(chalk.gray(` - ${name}${req}: ${(schema as any).description}${defaultVal}`));
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
@@ -40,7 +62,7 @@ async function listAction() {
|
||||
/**
|
||||
* Show tool schema in MCP-compatible JSON format
|
||||
*/
|
||||
async function schemaAction(options) {
|
||||
async function schemaAction(options: ToolOptions): Promise<void> {
|
||||
const { name } = options;
|
||||
|
||||
if (name) {
|
||||
@@ -72,7 +94,7 @@ async function schemaAction(options) {
|
||||
* @param {string|undefined} jsonParams - JSON string of parameters
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
async function execAction(toolName, jsonParams, options) {
|
||||
async function execAction(toolName: string | undefined, jsonParams: string | undefined, options: ExecOptions): Promise<void> {
|
||||
if (!toolName) {
|
||||
console.error(chalk.red('Tool name is required'));
|
||||
console.error(chalk.gray('Usage: ccw tool exec <tool_name> \'{"param": "value"}\''));
|
||||
@@ -89,15 +111,16 @@ async function execAction(toolName, jsonParams, options) {
|
||||
}
|
||||
|
||||
// Build params from CLI options or JSON
|
||||
let params = {};
|
||||
let params: any = {};
|
||||
|
||||
// Check if JSON params provided
|
||||
if (jsonParams && jsonParams.trim().startsWith('{')) {
|
||||
try {
|
||||
params = JSON.parse(jsonParams);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
console.error(chalk.red('Invalid JSON parameters'));
|
||||
console.error(chalk.gray(`Parse error: ${e.message}`));
|
||||
console.error(chalk.gray(`Parse error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (toolName === 'edit_file') {
|
||||
@@ -146,7 +169,7 @@ async function execAction(toolName, jsonParams, options) {
|
||||
* @param {string[]} args - Arguments array [toolName, jsonParams, ...]
|
||||
* @param {Object} options - CLI options
|
||||
*/
|
||||
export async function toolCommand(subcommand, args, options) {
|
||||
export async function toolCommand(subcommand: string, args: string | string[], options: ExecOptions): Promise<void> {
|
||||
// args is now an array due to [args...] in cli.js
|
||||
const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
|
||||
|
||||
@@ -9,11 +9,18 @@ import { getAllManifests, deleteManifest } from '../core/manifest.js';
|
||||
// Global subdirectories that should be protected when Global installation exists
|
||||
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
|
||||
|
||||
interface UninstallOptions {}
|
||||
|
||||
interface FileEntry {
|
||||
path: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function uninstallCommand(options) {
|
||||
export async function uninstallCommand(options: UninstallOptions): Promise<void> {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Uninstall Claude Code Workflow\n'));
|
||||
|
||||
@@ -42,7 +49,7 @@ export async function uninstallCommand(options) {
|
||||
divider();
|
||||
|
||||
// Select installation to uninstall
|
||||
let selectedManifest;
|
||||
let selectedManifest: any;
|
||||
|
||||
if (manifests.length === 1) {
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
@@ -117,7 +124,7 @@ export async function uninstallCommand(options) {
|
||||
|
||||
let removedFiles = 0;
|
||||
let removedDirs = 0;
|
||||
let failedFiles = [];
|
||||
let failedFiles: FileEntry[] = [];
|
||||
|
||||
try {
|
||||
// Remove files first (in reverse order to handle nested files)
|
||||
@@ -152,7 +159,8 @@ export async function uninstallCommand(options) {
|
||||
removedFiles++;
|
||||
}
|
||||
} catch (err) {
|
||||
failedFiles.push({ path: filePath, error: err.message });
|
||||
const error = err as Error;
|
||||
failedFiles.push({ path: filePath, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +168,7 @@ export async function uninstallCommand(options) {
|
||||
const directories = [...(selectedManifest.directories || [])].reverse();
|
||||
|
||||
// Sort by path length (deepest first)
|
||||
directories.sort((a, b) => b.path.length - a.path.length);
|
||||
directories.sort((a: any, b: any) => b.path.length - a.path.length);
|
||||
|
||||
for (const dirEntry of directories) {
|
||||
const dirPath = dirEntry.path;
|
||||
@@ -197,7 +205,8 @@ export async function uninstallCommand(options) {
|
||||
|
||||
} catch (err) {
|
||||
spinner.fail('Uninstall failed');
|
||||
error(err.message);
|
||||
const errMsg = err as Error;
|
||||
error(errMsg.message);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,7 +216,7 @@ export async function uninstallCommand(options) {
|
||||
// Show summary
|
||||
console.log('');
|
||||
|
||||
const summaryLines = [];
|
||||
const summaryLines: string[] = [];
|
||||
|
||||
if (failedFiles.length > 0) {
|
||||
summaryLines.push(chalk.yellow.bold('⚠ Partially Completed'));
|
||||
@@ -216,15 +225,15 @@ export async function uninstallCommand(options) {
|
||||
}
|
||||
|
||||
summaryLines.push('');
|
||||
summaryLines.push(chalk.white(`Files removed: ${chalk.green(removedFiles)}`));
|
||||
summaryLines.push(chalk.white(`Directories removed: ${chalk.green(removedDirs)}`));
|
||||
summaryLines.push(chalk.white(`Files removed: ${chalk.green(removedFiles.toString())}`));
|
||||
summaryLines.push(chalk.white(`Directories removed: ${chalk.green(removedDirs.toString())}`));
|
||||
|
||||
if (skippedFiles > 0) {
|
||||
summaryLines.push(chalk.white(`Global files preserved: ${chalk.cyan(skippedFiles)}`));
|
||||
summaryLines.push(chalk.white(`Global files preserved: ${chalk.cyan(skippedFiles.toString())}`));
|
||||
}
|
||||
|
||||
if (failedFiles.length > 0) {
|
||||
summaryLines.push(chalk.white(`Failed: ${chalk.red(failedFiles.length)}`));
|
||||
summaryLines.push(chalk.white(`Failed: ${chalk.red(failedFiles.length.toString())}`));
|
||||
summaryLines.push('');
|
||||
summaryLines.push(chalk.gray('Some files could not be removed.'));
|
||||
summaryLines.push(chalk.gray('They may be in use or require elevated permissions.'));
|
||||
@@ -254,7 +263,7 @@ export async function uninstallCommand(options) {
|
||||
* Recursively remove empty directories
|
||||
* @param {string} dirPath - Directory path
|
||||
*/
|
||||
async function removeEmptyDirs(dirPath) {
|
||||
async function removeEmptyDirs(dirPath: string): Promise<void> {
|
||||
if (!existsSync(dirPath)) return;
|
||||
|
||||
const stat = statSync(dirPath);
|
||||
@@ -276,4 +285,3 @@ async function removeEmptyDirs(dirPath) {
|
||||
rmdirSync(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,27 @@ const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
|
||||
// Subdirectories that should always be installed to global (~/.claude/)
|
||||
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
|
||||
|
||||
interface UpgradeOptions {
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
interface UpgradeResult {
|
||||
files: number;
|
||||
directories: number;
|
||||
}
|
||||
|
||||
interface CopyResult {
|
||||
files: number;
|
||||
directories: number;
|
||||
}
|
||||
|
||||
// Get package root directory (ccw/src/commands -> ccw)
|
||||
function getPackageRoot() {
|
||||
function getPackageRoot(): string {
|
||||
return join(__dirname, '..', '..');
|
||||
}
|
||||
|
||||
// Get source installation directory (parent of ccw)
|
||||
function getSourceDir() {
|
||||
function getSourceDir(): string {
|
||||
return join(getPackageRoot(), '..');
|
||||
}
|
||||
|
||||
@@ -30,7 +44,7 @@ function getSourceDir() {
|
||||
* Get package version
|
||||
* @returns {string} - Version string
|
||||
*/
|
||||
function getVersion() {
|
||||
function getVersion(): string {
|
||||
try {
|
||||
// First try root package.json (parent of ccw)
|
||||
const rootPkgPath = join(getSourceDir(), 'package.json');
|
||||
@@ -51,7 +65,7 @@ function getVersion() {
|
||||
* Upgrade command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function upgradeCommand(options) {
|
||||
export async function upgradeCommand(options: UpgradeOptions): Promise<void> {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Upgrade Claude Code Workflow\n'));
|
||||
|
||||
@@ -69,7 +83,7 @@ export async function upgradeCommand(options) {
|
||||
// Display current installations
|
||||
console.log(chalk.white.bold(' Current installations:\n'));
|
||||
|
||||
const upgradeTargets = [];
|
||||
const upgradeTargets: any[] = [];
|
||||
|
||||
for (let i = 0; i < manifests.length; i++) {
|
||||
const m = manifests[i];
|
||||
@@ -116,7 +130,7 @@ export async function upgradeCommand(options) {
|
||||
}
|
||||
|
||||
// Select which installations to upgrade
|
||||
let selectedManifests = [];
|
||||
let selectedManifests: any[] = [];
|
||||
|
||||
if (options.all) {
|
||||
selectedManifests = upgradeTargets.map(t => t.manifest);
|
||||
@@ -154,12 +168,12 @@ export async function upgradeCommand(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifests = selections.map(i => upgradeTargets[i].manifest);
|
||||
selectedManifests = selections.map((i: number) => upgradeTargets[i].manifest);
|
||||
}
|
||||
|
||||
// Perform upgrades
|
||||
console.log('');
|
||||
const results = [];
|
||||
const results: any[] = [];
|
||||
const sourceDir = getSourceDir();
|
||||
|
||||
for (const manifest of selectedManifests) {
|
||||
@@ -170,9 +184,10 @@ export async function upgradeCommand(options) {
|
||||
upgradeSpinner.succeed(`Upgraded ${manifest.installation_mode}: ${result.files} files`);
|
||||
results.push({ manifest, success: true, ...result });
|
||||
} catch (err) {
|
||||
const errMsg = err as Error;
|
||||
upgradeSpinner.fail(`Failed to upgrade ${manifest.installation_mode}`);
|
||||
error(err.message);
|
||||
results.push({ manifest, success: false, error: err.message });
|
||||
error(errMsg.message);
|
||||
results.push({ manifest, success: false, error: errMsg.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +234,7 @@ export async function upgradeCommand(options) {
|
||||
* @param {string} version - Version string
|
||||
* @returns {Promise<Object>} - Upgrade result
|
||||
*/
|
||||
async function performUpgrade(manifest, sourceDir, version) {
|
||||
async function performUpgrade(manifest: any, sourceDir: string, version: string): Promise<UpgradeResult> {
|
||||
const installPath = manifest.installation_path;
|
||||
const mode = manifest.installation_mode;
|
||||
|
||||
@@ -294,7 +309,12 @@ async function performUpgrade(manifest, sourceDir, version) {
|
||||
* @param {string[]} excludeDirs - Directory names to exclude (optional)
|
||||
* @returns {Object} - Count of files and directories
|
||||
*/
|
||||
async function copyDirectory(src, dest, manifest, excludeDirs = []) {
|
||||
async function copyDirectory(
|
||||
src: string,
|
||||
dest: string,
|
||||
manifest: any,
|
||||
excludeDirs: string[] = []
|
||||
): Promise<CopyResult> {
|
||||
let files = 0;
|
||||
let directories = 0;
|
||||
|
||||
@@ -3,12 +3,24 @@ import { launchBrowser } from '../utils/browser-launcher.js';
|
||||
import { validatePath } from '../utils/path-resolver.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
interface ViewOptions {
|
||||
port?: number;
|
||||
path?: string;
|
||||
browser?: boolean;
|
||||
}
|
||||
|
||||
interface SwitchWorkspaceResult {
|
||||
success: boolean;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if server is already running on the specified port
|
||||
* @param {number} port - Port to check
|
||||
* @returns {Promise<boolean>} True if server is running
|
||||
*/
|
||||
async function isServerRunning(port) {
|
||||
async function isServerRunning(port: number): Promise<boolean> {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
||||
@@ -30,14 +42,15 @@ async function isServerRunning(port) {
|
||||
* @param {string} path - New workspace path
|
||||
* @returns {Promise<Object>} Result with success status
|
||||
*/
|
||||
async function switchWorkspace(port, path) {
|
||||
async function switchWorkspace(port: number, path: string): Promise<SwitchWorkspaceResult> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://localhost:${port}/api/switch-path?path=${encodeURIComponent(path)}`
|
||||
);
|
||||
return await response.json();
|
||||
return await response.json() as SwitchWorkspaceResult;
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message };
|
||||
const error = err as Error;
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +60,14 @@ async function switchWorkspace(port, path) {
|
||||
* If not running, starts a new server
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function viewCommand(options) {
|
||||
export async function viewCommand(options: ViewOptions): Promise<void> {
|
||||
const port = options.port || 3456;
|
||||
|
||||
// Resolve workspace path
|
||||
let workspacePath = process.cwd();
|
||||
if (options.path) {
|
||||
const pathValidation = validatePath(options.path, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
if (!pathValidation.valid || !pathValidation.path) {
|
||||
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -76,7 +89,7 @@ export async function viewCommand(options) {
|
||||
console.log(chalk.green(` Workspace switched successfully`));
|
||||
|
||||
// Open browser with the new path
|
||||
const url = `http://localhost:${port}/?path=${encodeURIComponent(result.path)}`;
|
||||
const url = `http://localhost:${port}/?path=${encodeURIComponent(result.path!)}`;
|
||||
|
||||
if (options.browser !== false) {
|
||||
console.log(chalk.cyan(' Opening in browser...'));
|
||||
@@ -84,7 +97,8 @@ export async function viewCommand(options) {
|
||||
await launchBrowser(url);
|
||||
console.log(chalk.green.bold('\n Dashboard opened!\n'));
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${err.message}`));
|
||||
const error = err as Error;
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${error.message}`));
|
||||
console.log(chalk.gray(` Open manually: ${url}\n`));
|
||||
}
|
||||
} else {
|
||||
Reference in New Issue
Block a user