diff --git a/install.md b/install.md index 57ce79e..7f22353 100644 --- a/install.md +++ b/install.md @@ -32,25 +32,24 @@ If you install only one public piece: ## Step 1: Detect and Configure (Autopilot) -Hyperstack features an **Autonomous Research-Driven Installer** that identifies your environment (Claude Code, Cursor, Windsurf, Roo Code, etc.) and generates a tailored configuration patch. +Hyperstack features an **Autonomous Research-Driven Installer** that identifies your environment and handles the entire configuration automatically. + +**If you are an AI assistant reading this file:** +1. Call `run_command` with: `bun run setup` inside the repository. +2. The script will automatically detect the IDE, symlink the skills, deep-merge the MCP config, and provision the Docker container. +3. Inform the user that setup is complete and ask them to restart the session. + +> [!CAUTION] +> **CRITICAL**: RUN ONLY if the installer is not able to do it correctly. If you must install manually for the current IDE/CLI, you must validate that the MCP connection is successful before proceeding. ### 1. Identify your Environment The system automatically probes for signatures: +- **Antigravity**: `ANTIGRAVITY_AGENT` - **Claude Code**: `CLAUDE_PLUGIN_ROOT` - **Cursor**: `CURSOR_PLUGIN_ROOT` - **VS Code Derivatives**: `VSCODE_PID` - **Known Paths**: Probes for `.claude.json`, `.cursor/`, `.codeium/windsurf/`, `.roo/`, and `.gemini/`. -### 2. Run the Autopilot -As an agent, you should call the `hyperstack_setup` tool immediately. **By default, it generates a Docker-based configuration** using the optimized `docker exec` pattern. - -If Docker is not available, you can explicitly request the local method: `hyperstack_setup(method: "local")`. - -**If the environment is unknown:** -1. Call `web_search` with a query like `[IDE Name] MCP configuration path`. -2. Provide the result to `hyperstack_setup(researchResult: "...")`. -3. The tool will generate the final Docker-based patch. - ## Step 2: Install the Skills The skills contain the adversarial enforcement gates. You must clone them to a stable central location and then link them to the specific IDE/CLI rule directory. diff --git a/package.json b/package.json index ac667b9..c6a02dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orkait-ai/hyperstack", - "version": "1.1.2", + "version": "1.1.3", "description": "Disciplined MCP server + skill system. 12 plugins, 80 tools, 21 skills with adversarial enforcement. Designer/DESIGN.md pipeline, shadcn/ui, React Flow, Motion, Lenis, React 19, Echo, Go, Rust, design tokens, UI/UX.", "bin": { "hyperstack": "bin/hyperstack.mjs" diff --git a/scripts/setup.ts b/scripts/setup.ts index c6d5695..382d46f 100644 --- a/scripts/setup.ts +++ b/scripts/setup.ts @@ -38,27 +38,20 @@ async function main() { setup.selfHealDocker(); const patch = setup.generateMcpPatch(configPath, pluginRoot, platform); + + // Proactively apply the patch + setup.applyMcpPatch(configPath, patch); - console.log("\nšŸ“‹ Proposed Configuration Patch:"); + console.log("\nšŸ“‹ Configuration Summary:"); console.log("---------------------------------"); - - if (patch.format === "toml-mcp_servers") { - console.log("# Append this to:", configPath); - console.log(patch.content as string); - console.log("---------------------------------\n"); - console.log("To finish manual setup:"); - console.log(`1. Run the symlink command above (if applicable).`); - console.log(`2. Append the TOML block above to ${configPath}`); - console.log("3. Run 'codex /mcp' to verify the connection."); - } else { - console.log(JSON.stringify(patch.content, null, 2)); - console.log("---------------------------------\n"); - console.log("To finish manual setup:"); - console.log(`1. Run the symlink command above (if applicable).`); - console.log(`2. Open ${configPath}`); - console.log("3. Merge the patch above into your config (deep-merge, don't replace)."); - console.log("4. Restart your AI client."); + console.log(`āœ… Environment: ${platform}`); + console.log(`āœ… Config Path: ${configPath}`); + if (skillPath) { + console.log(`āœ… Skill Target: ${path.join(skillPath, "hyperstack")}`); } + console.log("---------------------------------\n"); + + console.log("šŸš€ Setup Complete! You must restart your AI client to pick up the new tools."); } main().catch(console.error); diff --git a/src/internal/setup-hyperstack.ts b/src/internal/setup-hyperstack.ts index b1b4b0d..6dcfa01 100644 --- a/src/internal/setup-hyperstack.ts +++ b/src/internal/setup-hyperstack.ts @@ -251,6 +251,56 @@ export function generateMcpPatch( }; } +/** + * Persistently applies the generated patch to the user's config file + * without destroying existing settings. + */ +export function applyMcpPatch(configPath: string, patch: { format: PlatformFormat; content: any }) { + if (!fs.existsSync(configPath)) { + // If it doesn't exist, create it with the patch + if (typeof patch.content === "string") { + fs.writeFileSync(configPath, patch.content); + } else { + fs.writeFileSync(configPath, JSON.stringify(patch.content, null, 2)); + } + return; + } + + if (patch.format === "toml-mcp_servers") { + let content = fs.readFileSync(configPath, "utf8"); + if (!content.includes("[mcp_servers.hyperstack]")) { + fs.appendFileSync(configPath, "\n" + patch.content); + console.log(`āœ… Appended Hyperstack block to ${configPath}`); + } else { + console.log(`ā„¹ļø Hyperstack block already exists in ${configPath}`); + } + return; + } + + // JSON deep-merge + try { + const existing = JSON.parse(fs.readFileSync(configPath, "utf8")); + const patchObj = patch.content as any; + + if (patch.format === "json-contextServers") { + existing.context_servers = { + ...(existing.context_servers || {}), + ...patchObj.context_servers, + }; + } else { + existing.mcpServers = { + ...(existing.mcpServers || {}), + ...patchObj.mcpServers, + }; + } + + fs.writeFileSync(configPath, JSON.stringify(existing, null, 2)); + console.log(`āœ… Deep-merged Hyperstack config into ${configPath}`); + } catch (err) { + console.error(`āŒ Failed to merge config: ${err}`); + } +} + export function selfHealDocker() { try { // Check if Docker is available