mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 22:14:22 +00:00
* feat: enhance UX for 'ccs sync' and 'ccs doctor' commands - Add ora spinners for real-time progress feedback - Add cli-table3 for structured health check summary table - Display detailed component information in doctor report - Show file/directory counts during sync operations - Improve visual hierarchy and readability - Fix Unicode checkmark violation (replaced with [OK]) Breaking changes: None Dependencies added: ora@^5.4.1, cli-table3@^0.6.5 Version bumped to 4.3.0 * fix: match doctor summary header width to table width * feat: improve settings validation to check API keys, not just endpoints - Check if API keys are configured or still placeholders - Show 'Key configured' vs 'Placeholder key (not configured)' - Previous message 'Valid (API: api.z.ai)' was misleading - Now users can clearly see which profiles need API key configuration - Warnings shown for placeholder keys (yellow [!]) * feat: add category grouping and alignment to doctor checks - Group checks into logical categories: System, Configuration, Profiles & Delegation, System Health - Add consistent padding (26 chars) for aligned columns - Add 2-space indent for better visual hierarchy - Separate categories with blank lines for better readability - Much cleaner and easier to scan than before * fix: perfect alignment for all doctor check output - Use .padEnd(26) consistently for all component names - Remove extra space before status icons - Now all status icons [OK]/[X]/[!]/[i] align perfectly - All component names pad to exactly 26 characters
161 lines
4.6 KiB
JavaScript
161 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const ora = require('ora');
|
|
const { colored } = require('./helpers');
|
|
|
|
/**
|
|
* ClaudeDirInstaller - Manages copying .claude/ directory from package to ~/.ccs/.claude/
|
|
* v4.1.1: Fix for npm install not copying .claude/ directory
|
|
*/
|
|
class ClaudeDirInstaller {
|
|
constructor() {
|
|
this.homeDir = os.homedir();
|
|
this.ccsClaudeDir = path.join(this.homeDir, '.ccs', '.claude');
|
|
}
|
|
|
|
/**
|
|
* Copy .claude/ directory from package to ~/.ccs/.claude/
|
|
* @param {string} packageDir - Package installation directory (default: auto-detect)
|
|
* @param {boolean} silent - Suppress spinner output
|
|
*/
|
|
install(packageDir, silent = false) {
|
|
const spinner = silent ? null : ora('Copying .claude/ items to ~/.ccs/.claude/').start();
|
|
|
|
try {
|
|
// Auto-detect package directory if not provided
|
|
if (!packageDir) {
|
|
// Try to find package root by going up from this file
|
|
packageDir = path.join(__dirname, '..', '..');
|
|
}
|
|
|
|
const packageClaudeDir = path.join(packageDir, '.claude');
|
|
|
|
if (!fs.existsSync(packageClaudeDir)) {
|
|
const msg = 'Package .claude/ directory not found';
|
|
if (spinner) {
|
|
spinner.warn(`[!] ${msg}`);
|
|
console.log(` Searched in: ${packageClaudeDir}`);
|
|
console.log(' This may be a development installation');
|
|
} else {
|
|
console.log(`[!] ${msg}`);
|
|
console.log(` Searched in: ${packageClaudeDir}`);
|
|
console.log(' This may be a development installation');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Remove old version before copying new one
|
|
if (fs.existsSync(this.ccsClaudeDir)) {
|
|
if (spinner) spinner.text = 'Removing old .claude/ items...';
|
|
fs.rmSync(this.ccsClaudeDir, { recursive: true, force: true });
|
|
}
|
|
|
|
// Use fs.cpSync for recursive copy (Node.js 16.7.0+)
|
|
// Fallback to manual copy for older Node.js versions
|
|
if (spinner) spinner.text = 'Copying .claude/ items...';
|
|
|
|
if (fs.cpSync) {
|
|
fs.cpSync(packageClaudeDir, this.ccsClaudeDir, { recursive: true });
|
|
} else {
|
|
// Fallback for Node.js < 16.7.0
|
|
this._copyDirRecursive(packageClaudeDir, this.ccsClaudeDir);
|
|
}
|
|
|
|
// Count files and directories
|
|
const itemCount = this._countItems(this.ccsClaudeDir);
|
|
const msg = `Copied .claude/ items (${itemCount.files} files, ${itemCount.dirs} directories)`;
|
|
|
|
if (spinner) {
|
|
spinner.succeed(colored('[OK]', 'green') + ` ${msg}`);
|
|
} else {
|
|
console.log(`[OK] ${msg}`);
|
|
}
|
|
return true;
|
|
} catch (err) {
|
|
const msg = `Failed to copy .claude/ directory: ${err.message}`;
|
|
if (spinner) {
|
|
spinner.fail(colored('[!]', 'yellow') + ` ${msg}`);
|
|
console.warn(' CCS items may not be available');
|
|
} else {
|
|
console.warn(`[!] ${msg}`);
|
|
console.warn(' CCS items may not be available');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively copy directory (fallback for Node.js < 16.7.0)
|
|
* @param {string} src - Source directory
|
|
* @param {string} dest - Destination directory
|
|
* @private
|
|
*/
|
|
_copyDirRecursive(src, dest) {
|
|
// Create destination directory
|
|
if (!fs.existsSync(dest)) {
|
|
fs.mkdirSync(dest, { recursive: true });
|
|
}
|
|
|
|
// Read source directory
|
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const srcPath = path.join(src, entry.name);
|
|
const destPath = path.join(dest, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
// Recursively copy subdirectory
|
|
this._copyDirRecursive(srcPath, destPath);
|
|
} else {
|
|
// Copy file
|
|
fs.copyFileSync(srcPath, destPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Count files and directories in a path
|
|
* @param {string} dirPath - Directory to count
|
|
* @returns {Object} { files: number, dirs: number }
|
|
* @private
|
|
*/
|
|
_countItems(dirPath) {
|
|
let files = 0;
|
|
let dirs = 0;
|
|
|
|
const countRecursive = (p) => {
|
|
const entries = fs.readdirSync(p, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory()) {
|
|
dirs++;
|
|
countRecursive(path.join(p, entry.name));
|
|
} else {
|
|
files++;
|
|
}
|
|
}
|
|
};
|
|
|
|
try {
|
|
countRecursive(dirPath);
|
|
} catch (e) {
|
|
// Ignore errors
|
|
}
|
|
|
|
return { files, dirs };
|
|
}
|
|
|
|
/**
|
|
* Check if ~/.ccs/.claude/ exists and is valid
|
|
* @returns {boolean} True if directory exists
|
|
*/
|
|
isInstalled() {
|
|
return fs.existsSync(this.ccsClaudeDir);
|
|
}
|
|
}
|
|
|
|
module.exports = ClaudeDirInstaller;
|