Files
ccs/bin/utils/claude-dir-installer.js
T
Kai (Tam Nhu) Tran 67dbd3f259 feat: improve UX for ccs doctor and sync commands (#13)
* 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
2025-11-18 01:19:28 -05:00

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;