| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const ROOT = __dirname;
- const PLUGIN_NAME = 'tihao';
- const SKILL_NAME = 'tihao';
- const SKILL_NAMES = ['tihao', 'fmode-image-analysis'];
- const LEGACY_PLUGIN_NAMES = ['tihao-sourcing'];
- const LEGACY_SKILL_NAMES = ['tihao-creator-sourcing'];
- function main() {
- const args = new Set(process.argv.slice(2));
- if (args.has('--check')) {
- console.log('tihao package is readable.');
- return;
- }
- const workspaceMode = args.has('--workspace') || process.argv.includes('workspace');
- const baseDir = workspaceMode
- ? path.join(process.cwd(), '.claude')
- : path.join(os.homedir(), '.claude');
- const pluginTarget = path.join(baseDir, 'plugins', PLUGIN_NAME);
- const primarySkillTarget = path.join(baseDir, 'skills', SKILL_NAME);
- copyDir(ROOT, pluginTarget, shouldCopyPackageFile);
- for (const skillName of SKILL_NAMES) {
- copyDir(path.join(ROOT, 'skills', skillName), path.join(baseDir, 'skills', skillName), () => true);
- }
- removeLegacyPlugins(baseDir, pluginTarget);
- removeLegacySkills(baseDir, primarySkillTarget);
- ensureRuntimeDeps(pluginTarget);
- writeWorkspaceMcp(process.cwd(), pluginTarget, workspaceMode);
- console.log(`Installed ${PLUGIN_NAME} plugin to ${pluginTarget}`);
- for (const skillName of SKILL_NAMES) {
- console.log(`Installed ${skillName} skill to ${path.join(baseDir, 'skills', skillName)}`);
- }
- if (args.has('--smoke')) {
- const result = spawnSync(process.execPath, [path.join(pluginTarget, 'scripts/smoke-package.js')], {
- cwd: pluginTarget,
- stdio: 'inherit'
- });
- process.exit(result.status || 0);
- }
- }
- function removeLegacyPlugins(baseDir, currentPluginTarget) {
- const pluginsRoot = path.resolve(baseDir, 'plugins');
- const resolvedCurrent = path.resolve(currentPluginTarget);
- for (const legacyName of LEGACY_PLUGIN_NAMES) {
- const legacyTarget = path.resolve(pluginsRoot, legacyName);
- const relativeLegacy = path.relative(pluginsRoot, legacyTarget);
- if (legacyTarget === resolvedCurrent) continue;
- if (relativeLegacy.startsWith('..') || path.isAbsolute(relativeLegacy)) continue;
- removeDirRecursive(legacyTarget);
- }
- }
- function removeLegacySkills(baseDir, currentSkillTarget) {
- const skillsRoot = path.resolve(baseDir, 'skills');
- const resolvedCurrent = path.resolve(currentSkillTarget);
- for (const legacyName of LEGACY_SKILL_NAMES) {
- const legacyTarget = path.resolve(skillsRoot, legacyName);
- const relativeLegacy = path.relative(skillsRoot, legacyTarget);
- if (legacyTarget === resolvedCurrent) continue;
- if (relativeLegacy.startsWith('..') || path.isAbsolute(relativeLegacy)) continue;
- removeDirRecursive(legacyTarget);
- }
- }
- function removeDirRecursive(target) {
- if (!fs.existsSync(target)) return;
- try {
- fs.rmSync(target, { recursive: true, force: true });
- } catch (error) {
- if (!fs.existsSync(target)) return;
- }
- if (!fs.existsSync(target)) return;
- for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
- const child = path.join(target, entry.name);
- if (entry.isDirectory() && !entry.isSymbolicLink()) {
- removeDirRecursive(child);
- } else {
- fs.unlinkSync(child);
- }
- }
- fs.rmdirSync(target);
- }
- function shouldCopyPackageFile(source) {
- const rel = path.relative(ROOT, source).replace(/\\/g, '/');
- if (!rel) return true;
- if (rel.startsWith('node_modules/')) return false;
- if (rel.startsWith('outputs/')) return false;
- if (rel.startsWith('.claude/')) return false;
- if (rel === '.env' || rel === '.env.local' || rel === '.npmrc') return false;
- return true;
- }
- function copyDir(source, target, predicate) {
- if (!fs.existsSync(source)) return;
- fs.mkdirSync(target, { recursive: true });
- for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
- const sourcePath = path.join(source, entry.name);
- if (!predicate(sourcePath)) continue;
- const targetPath = path.join(target, entry.name);
- if (entry.isDirectory()) {
- copyDir(sourcePath, targetPath, predicate);
- } else if (entry.isFile()) {
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
- fs.copyFileSync(sourcePath, targetPath);
- }
- }
- }
- function writeWorkspaceMcp(cwd, pluginTarget, workspaceMode) {
- if (!workspaceMode) return;
- const mcpFile = path.join(cwd, '.mcp.json');
- const existing = fs.existsSync(mcpFile) ? safeJson(fs.readFileSync(mcpFile, 'utf8')) : {};
- const mcpServers = { ...(existing.mcpServers || {}) };
- for (const legacyName of LEGACY_PLUGIN_NAMES) {
- delete mcpServers[legacyName];
- }
- mcpServers[PLUGIN_NAME] = {
- command: 'node',
- args: [path.join(pluginTarget, 'mcp/src/server.js')]
- };
- const next = {
- ...existing,
- mcpServers
- };
- fs.writeFileSync(mcpFile, JSON.stringify(next, null, 2), 'utf8');
- }
- function ensureRuntimeDeps(pluginTarget) {
- const sdkPath = path.join(pluginTarget, 'node_modules', '@modelcontextprotocol', 'sdk');
- if (fs.existsSync(sdkPath)) return;
- const sourceNodeModules = path.join(ROOT, 'node_modules');
- if (fs.existsSync(sourceNodeModules)) {
- copyDir(sourceNodeModules, path.join(pluginTarget, 'node_modules'), () => true);
- if (fs.existsSync(sdkPath)) return;
- }
- const npmCommand = resolveNpmCommand(['install', '--omit=dev']);
- const result = spawnSync(npmCommand.command, npmCommand.args, {
- cwd: pluginTarget,
- stdio: 'inherit',
- shell: npmCommand.shell
- });
- if (result.status !== 0) {
- throw new Error('Failed to install runtime dependencies for copied plugin.');
- }
- }
- function resolveNpmCommand(args) {
- if (process.env.npm_execpath) {
- return { command: process.execPath, args: [process.env.npm_execpath, ...args], shell: false };
- }
- if (process.platform === 'win32') {
- return { command: 'npm.cmd', args, shell: true };
- }
- return { command: 'npm', args, shell: false };
- }
- function safeJson(raw) {
- try {
- return JSON.parse(raw);
- } catch {
- return {};
- }
- }
- main();
|