mirror of
https://github.com/Yeachan-Heo/oh-my-claudecode.git
synced 2026-10-22 03:02:58 -05:00
263 lines
9.8 KiB
JavaScript
263 lines
9.8 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Plugin Post-Install Setup
|
||
*
|
||
* Configures HUD statusline when plugin is installed.
|
||
*/
|
||
|
||
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, chmodSync } from 'node:fs';
|
||
import { execSync } from 'node:child_process';
|
||
import { homedir } from 'node:os';
|
||
import { join, dirname } from 'node:path';
|
||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
|
||
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
||
const HUD_DIR = join(CLAUDE_DIR, 'hud');
|
||
const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
|
||
|
||
console.log('[OMC] Running post-install setup...');
|
||
|
||
// 1. Create HUD directory
|
||
if (!existsSync(HUD_DIR)) {
|
||
mkdirSync(HUD_DIR, { recursive: true });
|
||
}
|
||
|
||
// 2. Create HUD wrapper script
|
||
const hudScriptPath = join(HUD_DIR, 'omc-hud.mjs').replace(/\\/g, '/');
|
||
const hudScript = `#!/usr/bin/env node
|
||
/**
|
||
* OMC HUD - Statusline Script
|
||
* Wrapper that imports from plugin cache or development paths
|
||
*/
|
||
|
||
import { existsSync, readdirSync } from "node:fs";
|
||
import { homedir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { pathToFileURL } from "node:url";
|
||
|
||
// Semantic version comparison: returns negative if a < b, positive if a > b, 0 if equal
|
||
function semverCompare(a, b) {
|
||
// Use parseInt to handle pre-release suffixes (e.g. "0-beta" -> 0)
|
||
const pa = a.replace(/^v/, "").split(".").map(s => parseInt(s, 10) || 0);
|
||
const pb = b.replace(/^v/, "").split(".").map(s => parseInt(s, 10) || 0);
|
||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||
const na = pa[i] || 0;
|
||
const nb = pb[i] || 0;
|
||
if (na !== nb) return na - nb;
|
||
}
|
||
// If numeric parts equal, non-pre-release > pre-release
|
||
const aHasPre = /-/.test(a);
|
||
const bHasPre = /-/.test(b);
|
||
if (aHasPre && !bHasPre) return -1;
|
||
if (!aHasPre && bHasPre) return 1;
|
||
return 0;
|
||
}
|
||
|
||
async function main() {
|
||
const home = homedir();
|
||
let pluginCacheDir = null;
|
||
|
||
// 1. Try plugin cache first (marketplace: omc, plugin: oh-my-claudecode)
|
||
// Respect CLAUDE_CONFIG_DIR so installs under a custom config dir are found
|
||
const configDir = process.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
|
||
const pluginCacheBase = join(configDir, "plugins", "cache", "omc", "oh-my-claudecode");
|
||
if (existsSync(pluginCacheBase)) {
|
||
try {
|
||
const versions = readdirSync(pluginCacheBase);
|
||
if (versions.length > 0) {
|
||
const sortedVersions = versions.sort(semverCompare).reverse();
|
||
pluginCacheDir = join(pluginCacheBase, sortedVersions[0]);
|
||
|
||
// Filter to only versions with built dist/hud/index.js
|
||
const builtVersions = sortedVersions.filter(v => {
|
||
const hudPath = join(pluginCacheBase, v, "dist/hud/index.js");
|
||
return existsSync(hudPath);
|
||
});
|
||
if (builtVersions.length > 0) {
|
||
const latestBuilt = builtVersions[0];
|
||
pluginCacheDir = join(pluginCacheBase, latestBuilt);
|
||
const pluginPath = join(pluginCacheBase, latestBuilt, "dist/hud/index.js");
|
||
await import(pathToFileURL(pluginPath).href);
|
||
return;
|
||
}
|
||
}
|
||
} catch { /* continue */ }
|
||
}
|
||
|
||
// 2. Development paths
|
||
const devPaths = [
|
||
join(home, "Workspace/oh-my-claudecode/dist/hud/index.js"),
|
||
join(home, "workspace/oh-my-claudecode/dist/hud/index.js"),
|
||
join(home, "Workspace/oh-my-claudecode/dist/hud/index.js"),
|
||
join(home, "workspace/oh-my-claudecode/dist/hud/index.js"),
|
||
];
|
||
|
||
for (const devPath of devPaths) {
|
||
if (existsSync(devPath)) {
|
||
try {
|
||
await import(pathToFileURL(devPath).href);
|
||
return;
|
||
} catch { /* continue */ }
|
||
}
|
||
}
|
||
|
||
// 3. Marketplace clone (for marketplace installs without a populated cache)
|
||
const marketplaceHudPath = join(configDir, "plugins", "marketplaces", "omc", "dist/hud/index.js");
|
||
if (existsSync(marketplaceHudPath)) {
|
||
try {
|
||
await import(pathToFileURL(marketplaceHudPath).href);
|
||
return;
|
||
} catch { /* continue */ }
|
||
}
|
||
|
||
// 4. Fallback: provide targeted repair guidance
|
||
if (pluginCacheDir && existsSync(pluginCacheDir)) {
|
||
const distDir = join(pluginCacheDir, "dist");
|
||
if (!existsSync(distDir)) {
|
||
console.log(\`[OMC HUD] Plugin installed but not built. Run: cd "\${pluginCacheDir}" && npm install && npm run build\`);
|
||
} else {
|
||
console.log(\`[OMC HUD] Plugin HUD load failed. Run: cd "\${pluginCacheDir}" && npm install && npm run build\`);
|
||
}
|
||
} else if (existsSync(pluginCacheBase)) {
|
||
console.log("[OMC HUD] Plugin cache found but no versions installed. Run: /oh-my-claudecode:omc-setup");
|
||
} else {
|
||
console.log("[OMC HUD] Plugin not installed. Run: /oh-my-claudecode:omc-setup");
|
||
}
|
||
}
|
||
|
||
main();
|
||
`;
|
||
|
||
writeFileSync(hudScriptPath, hudScript);
|
||
try {
|
||
chmodSync(hudScriptPath, 0o755);
|
||
} catch { /* Windows doesn't need this */ }
|
||
console.log('[OMC] Installed HUD wrapper script');
|
||
|
||
// 3. Configure settings.json
|
||
try {
|
||
let settings = {};
|
||
if (existsSync(SETTINGS_FILE)) {
|
||
settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf-8'));
|
||
}
|
||
|
||
// Use the absolute node binary path so nvm/fnm users don't get
|
||
// "node not found" errors in non-interactive shells (issue #892).
|
||
const nodeBin = process.execPath || 'node';
|
||
settings.statusLine = {
|
||
type: 'command',
|
||
command: `"${nodeBin}" "${hudScriptPath.replace(/\\/g, "/")}"`
|
||
};
|
||
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
||
console.log('[OMC] Configured HUD statusLine in settings.json');
|
||
|
||
// Persist the node binary path to .omc-config.json for use by find-node.sh
|
||
try {
|
||
const configPath = join(CLAUDE_DIR, '.omc-config.json');
|
||
let omcConfig = {};
|
||
if (existsSync(configPath)) {
|
||
omcConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||
}
|
||
if (nodeBin !== 'node') {
|
||
omcConfig.nodeBinary = nodeBin;
|
||
writeFileSync(configPath, JSON.stringify(omcConfig, null, 2));
|
||
console.log(`[OMC] Saved node binary path: ${nodeBin}`);
|
||
}
|
||
} catch (e) {
|
||
console.log('[OMC] Warning: Could not save node binary path (non-fatal):', e.message);
|
||
}
|
||
} catch (e) {
|
||
console.log('[OMC] Warning: Could not configure settings.json:', e.message);
|
||
}
|
||
|
||
// Patch hooks.json to use the absolute node binary path so hooks work on all
|
||
// platforms: Windows (no `sh`), nvm/fnm users (node not on PATH in hooks), etc.
|
||
//
|
||
// The source hooks.json uses `node run.cjs` as a portable template; this step
|
||
// substitutes the real process.execPath so Claude Code always invokes the same
|
||
// Node binary that ran this setup script.
|
||
//
|
||
// Two patterns are handled:
|
||
// 1. New format – node "${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" ... (all platforms)
|
||
// 2. Old format – sh "${CLAUDE_PLUGIN_ROOT}/scripts/find-node.sh" ... (Windows
|
||
// backward-compat: migrates old installs to the new run.cjs chain)
|
||
//
|
||
// Fixes issues #909, #899, #892, #869.
|
||
try {
|
||
const hooksJsonPath = join(__dirname, '..', 'hooks', 'hooks.json');
|
||
if (existsSync(hooksJsonPath)) {
|
||
const data = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));
|
||
let patched = false;
|
||
|
||
// Pattern 1 (new): node "${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" <rest>
|
||
const runCjsPattern =
|
||
/^node ("\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/run\.cjs".*)$/;
|
||
|
||
// Pattern 2 (old, Windows backward-compat): sh find-node.sh <target> [args]
|
||
const findNodePattern =
|
||
/^sh "\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/find-node\.sh" "(\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/[^"]+)"(.*)$/;
|
||
|
||
for (const groups of Object.values(data.hooks ?? {})) {
|
||
for (const group of groups) {
|
||
for (const hook of (group.hooks ?? [])) {
|
||
if (typeof hook.command !== 'string') continue;
|
||
|
||
// New run.cjs format — replace bare `node` with absolute path (all platforms)
|
||
const m1 = hook.command.match(runCjsPattern);
|
||
if (m1) {
|
||
hook.command = `"${nodeBin}" ${m1[1]}`;
|
||
patched = true;
|
||
continue;
|
||
}
|
||
|
||
// Old find-node.sh format — migrate to run.cjs + absolute path (Windows only)
|
||
if (process.platform === 'win32') {
|
||
const m2 = hook.command.match(findNodePattern);
|
||
if (m2) {
|
||
hook.command = `"${nodeBin}" "\${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" "${m2[1]}"${m2[2]}`;
|
||
patched = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (patched) {
|
||
writeFileSync(hooksJsonPath, JSON.stringify(data, null, 2) + '\n');
|
||
console.log(`[OMC] Patched hooks.json with absolute node path (${nodeBin}), fixes issues #909, #899, #892`);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('[OMC] Warning: Could not patch hooks.json:', e.message);
|
||
}
|
||
|
||
// 5. Ensure runtime dependencies are installed in the plugin cache directory.
|
||
// The npm-published tarball includes only the files listed in "files" (package.json),
|
||
// which does NOT include node_modules. When Claude Code extracts the plugin into its
|
||
// cache the dependencies are therefore missing, causing ERR_MODULE_NOT_FOUND at runtime.
|
||
// We detect this by probing for a known production dependency (commander) and running a
|
||
// production-only install when it is absent. --ignore-scripts avoids re-triggering this
|
||
// very setup script (and any other lifecycle hooks). Fixes #1113.
|
||
const packageDir = join(__dirname, '..');
|
||
const commanderCheck = join(packageDir, 'node_modules', 'commander');
|
||
if (!existsSync(commanderCheck)) {
|
||
console.log('[OMC] Installing runtime dependencies...');
|
||
try {
|
||
execSync('npm install --omit=dev --ignore-scripts', {
|
||
cwd: packageDir,
|
||
stdio: 'pipe',
|
||
timeout: 60000,
|
||
});
|
||
console.log('[OMC] Runtime dependencies installed successfully');
|
||
} catch (e) {
|
||
console.log('[OMC] Warning: Could not install dependencies:', e.message);
|
||
}
|
||
} else {
|
||
console.log('[OMC] Runtime dependencies already present');
|
||
}
|
||
|
||
console.log('[OMC] Setup complete! Restart Claude Code to activate HUD.');
|