mirror of
https://github.com/logos-co/logos-dev-boost.git
synced 2026-08-27 16:11:11 +00:00
220 lines
7.4 KiB
TypeScript
220 lines
7.4 KiB
TypeScript
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import * as readline from "node:readline";
|
|
import { execSync } from "node:child_process";
|
|
|
|
interface DetectedProject {
|
|
name: string;
|
|
type: string;
|
|
interface: string;
|
|
description: string;
|
|
}
|
|
|
|
interface DetectedTools {
|
|
claudeCode: boolean;
|
|
cursor: boolean;
|
|
codex: boolean;
|
|
gemini: boolean;
|
|
}
|
|
|
|
function detectProject(projectDir: string): DetectedProject {
|
|
const result: DetectedProject = {
|
|
name: "unknown",
|
|
type: "unknown",
|
|
interface: "none",
|
|
description: "",
|
|
};
|
|
|
|
// Check for full-app root
|
|
const projectJsonPath = path.join(projectDir, "project.json");
|
|
if (fs.existsSync(projectJsonPath)) {
|
|
const proj = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
|
|
if (proj.type === "full-app") {
|
|
result.name = proj.name || "unknown";
|
|
result.type = "Full App (module + UI)";
|
|
result.interface = "N/A";
|
|
result.description = proj.description || "";
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Fallback structural detection
|
|
const entries = fs.existsSync(projectDir) ? fs.readdirSync(projectDir) : [];
|
|
const moduleEntry = entries.find((e: string) => e.endsWith("-module") &&
|
|
fs.existsSync(path.join(projectDir, e, "metadata.json")));
|
|
const uiEntry = entries.find((e: string) => e.endsWith("-ui") &&
|
|
fs.existsSync(path.join(projectDir, e, "metadata.json")));
|
|
if (moduleEntry && uiEntry) {
|
|
const moduleMeta = JSON.parse(fs.readFileSync(path.join(projectDir, moduleEntry, "metadata.json"), "utf-8"));
|
|
result.name = moduleMeta.name || "unknown";
|
|
result.type = "Full App (module + UI)";
|
|
result.interface = "N/A";
|
|
result.description = moduleMeta.description || "";
|
|
return result;
|
|
}
|
|
|
|
const metadataPath = path.join(projectDir, "metadata.json");
|
|
if (fs.existsSync(metadataPath)) {
|
|
const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
|
|
result.name = metadata.name || "unknown";
|
|
result.type = metadata.type === "ui" ? "UI App" : "Module";
|
|
result.interface = metadata.interface === "universal" ? "Universal" : "Legacy";
|
|
result.description = metadata.description || "";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function detectTools(): DetectedTools {
|
|
const tools: DetectedTools = {
|
|
claudeCode: false,
|
|
cursor: false,
|
|
codex: false,
|
|
gemini: false,
|
|
};
|
|
|
|
try { execSync("which claude", { stdio: "pipe" }); tools.claudeCode = true; } catch {}
|
|
try { execSync("which cursor", { stdio: "pipe" }); tools.cursor = true; } catch {}
|
|
try { execSync("which codex", { stdio: "pipe" }); tools.codex = true; } catch {}
|
|
try { execSync("which gemini", { stdio: "pipe" }); tools.gemini = true; } catch {}
|
|
|
|
// Also check for config directories
|
|
const home = process.env.HOME || "";
|
|
if (fs.existsSync(path.join(home, ".claude"))) tools.claudeCode = true;
|
|
if (fs.existsSync(path.join(home, ".cursor"))) tools.cursor = true;
|
|
|
|
return tools;
|
|
}
|
|
|
|
function copySkills(boostDir: string, projectDir: string, targetDir: string) {
|
|
const srcSkills = path.join(boostDir, "skills");
|
|
if (!fs.existsSync(srcSkills)) return;
|
|
|
|
const skills = fs.readdirSync(srcSkills).filter((d) => {
|
|
return fs.existsSync(path.join(srcSkills, d, "SKILL.md"));
|
|
});
|
|
|
|
for (const skill of skills) {
|
|
const destDir = path.join(projectDir, targetDir, skill);
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
fs.copyFileSync(
|
|
path.join(srcSkills, skill, "SKILL.md"),
|
|
path.join(destDir, "SKILL.md")
|
|
);
|
|
}
|
|
}
|
|
|
|
function generateMcpJson(projectDir: string, boostDir: string) {
|
|
const mcpConfig: Record<string, unknown> = {
|
|
mcpServers: {
|
|
"logos-dev-boost": {
|
|
command: "node",
|
|
args: [path.join(boostDir, "dist", "mcp-server", "index.js")],
|
|
},
|
|
},
|
|
};
|
|
|
|
// Merge with existing .mcp.json if present
|
|
const mcpPath = path.join(projectDir, ".mcp.json");
|
|
if (fs.existsSync(mcpPath)) {
|
|
const existing = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
if (existing.mcpServers) {
|
|
(mcpConfig.mcpServers as Record<string, unknown>) = {
|
|
...existing.mcpServers,
|
|
...(mcpConfig.mcpServers as Record<string, unknown>),
|
|
};
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
}
|
|
|
|
export async function runInstall(projectDir: string, boostDir: string) {
|
|
const project = detectProject(projectDir);
|
|
const tools = detectTools();
|
|
|
|
console.log("\nlogos-dev-boost install");
|
|
console.log("─".repeat(40));
|
|
console.log(`\nDetected project:`);
|
|
console.log(` Name: ${project.name}`);
|
|
console.log(` Type: ${project.type}`);
|
|
console.log(` Interface: ${project.interface}`);
|
|
if (project.description) {
|
|
console.log(` Desc: ${project.description}`);
|
|
}
|
|
|
|
console.log(`\nDetected AI tools:`);
|
|
console.log(` Claude Code: ${tools.claudeCode ? "yes" : "not found"}`);
|
|
console.log(` Cursor: ${tools.cursor ? "yes" : "not found"}`);
|
|
console.log(` Codex: ${tools.codex ? "yes" : "not found"}`);
|
|
console.log(` Gemini CLI: ${tools.gemini ? "yes" : "not found"}`);
|
|
|
|
const generated: string[] = [];
|
|
|
|
// Generate AGENTS.md (universal — works with all tools)
|
|
console.log("\nGenerating context files...");
|
|
try {
|
|
const genDir = path.join(boostDir, "dist", "generators");
|
|
|
|
execSync(`node "${path.join(genDir, "generate-agents-md.js")}" "${projectDir}" "${boostDir}"`, { stdio: "pipe" });
|
|
generated.push("AGENTS.md");
|
|
|
|
if (tools.claudeCode) {
|
|
execSync(`node "${path.join(genDir, "generate-claude-md.js")}" "${projectDir}" "${boostDir}"`, { stdio: "pipe" });
|
|
generated.push("CLAUDE.md");
|
|
|
|
copySkills(boostDir, projectDir, ".claude/skills");
|
|
generated.push(".claude/skills/ (9 skills)");
|
|
}
|
|
|
|
if (tools.cursor) {
|
|
execSync(`node "${path.join(genDir, "generate-cursor-rules.js")}" "${projectDir}" "${boostDir}"`, { stdio: "pipe" });
|
|
generated.push(".cursor/rules/logos.mdc");
|
|
}
|
|
|
|
// Install skills for non-Claude tools (use .agents/skills/)
|
|
if (tools.codex || tools.gemini) {
|
|
copySkills(boostDir, projectDir, ".agents/skills");
|
|
generated.push(".agents/skills/ (9 skills)");
|
|
}
|
|
|
|
// Generate MCP configuration
|
|
generateMcpJson(projectDir, boostDir);
|
|
generated.push(".mcp.json");
|
|
|
|
} catch (err) {
|
|
console.log(" (some generators not built yet — run 'npm run build' first)");
|
|
}
|
|
|
|
console.log("\nGenerated files:");
|
|
for (const file of generated) {
|
|
console.log(` ${file}`);
|
|
}
|
|
|
|
console.log("\nSetup complete. Your AI tools now have Logos development context.");
|
|
|
|
if (tools.claudeCode) {
|
|
console.log("\nClaude Code: CLAUDE.md + skills loaded automatically.");
|
|
console.log(" MCP: should auto-detect from .mcp.json. If not:");
|
|
console.log(" claude mcp add -s local -t stdio logos-dev-boost node " +
|
|
path.join(boostDir, "dist", "mcp-server", "index.js"));
|
|
}
|
|
|
|
if (tools.cursor) {
|
|
console.log("\nCursor: AGENTS.md + .cursor/rules loaded automatically.");
|
|
console.log(" MCP: Open Command Palette -> '/open MCP Settings' -> toggle on logos-dev-boost");
|
|
}
|
|
|
|
if (tools.codex) {
|
|
console.log("\nCodex: AGENTS.md loaded automatically.");
|
|
console.log(" MCP: codex mcp add logos-dev-boost -- node " +
|
|
path.join(boostDir, "dist", "mcp-server", "index.js"));
|
|
}
|
|
|
|
if (tools.gemini) {
|
|
console.log("\nGemini CLI: AGENTS.md loaded automatically.");
|
|
console.log(" MCP: gemini mcp add -s project -t stdio logos-dev-boost node " +
|
|
path.join(boostDir, "dist", "mcp-server", "index.js"));
|
|
}
|
|
}
|