imhcode 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,11 +51,11 @@ IMH-Code enforces a fast-first software delivery pipeline:
51
51
 
52
52
  ## šŸ› ļø Usability Upgrades (v2.0)
53
53
 
54
- Version 2.0 introduces powerful new commands for codebase scanning, project imports, in-place changes, and graphical control:
54
+ Version 2.0 introduces powerful new commands for codebase scanning, project imports, in-place changes, and interactive control:
55
55
  - **Scan & Import**: Run `imhcode scan` or `imhcode import` to bring existing projects (Next.js, Laravel, React, etc.) under orchestration. Maps project folders to `.imhcode/import-map.json` automatically.
56
56
  - **In-place Modifications**: Run `imhcode modify "task"` to auto-detect the target agent and run in-place code adjustments.
57
57
  - **Feature Sprint Planning**: Run `imhcode feature "desc"` to plan a targeted 1-3 task mini-sprint for adding a new feature.
58
- - **Laravel GUI Dashboard**: Run `imhcode gui` to launch a beautiful dark-mode control center web application (Laravel 12 + Livewire 3 + Tailwind CSS v4) to manage your projects, visual Kanbans, and modifications directly from the web browser.
58
+ - **Interactive Terminal TUI**: Run `imhcode` (or `imhcode gui`) to launch a beautiful, interactive terminal user interface (TUI) to manage your projects, sprints, and code modifications via a simple console-based dashboard.
59
59
 
60
60
  ---
61
61
 
package/USER_MANUAL.md CHANGED
@@ -63,7 +63,7 @@ imhcode execute-feature [N] # Execute planned feature sprint N
63
63
  imhcode fix "bug desc" # Targeted bug fix (alias for modify)
64
64
  imhcode scan [path] # Scan local stack and project configuration
65
65
  imhcode import [path] # Import existing codebase and generate context
66
- imhcode gui [--port N] # Start the Laravel GUI Control Center web server
66
+ imhcode gui # Start the interactive terminal control center (TUI)
67
67
  ```
68
68
 
69
69
  ---
@@ -92,8 +92,9 @@ Instead of starting greenfield projects, you can modify existing code bases in-p
92
92
  - Run `imhcode modify "Add a contact form to the page"` to auto-detect the best agent, compile a dynamic context-aware prompt, and run in-place code edits.
93
93
  - Run `imhcode feature "Add Stripe checkout"` to plan a targeted mini-sprint of 1-3 tasks for a new feature addition. Run `imhcode execute-feature {N}` to execute.
94
94
 
95
- ## 7. Laravel GUI Control Center
95
+ ## 7. Interactive Terminal Control Center (TUI)
96
96
 
97
- Run `imhcode gui` to launch a beautiful dark-mode web console (Laravel 12 + Livewire 3 + Tailwind CSS v4) to manage your projects, sprints, and code modifications via a simple graphical user interface.
98
- - Scaffolded automatically under `~/.imhcode/gui/`.
99
- - Manage projects, visual kanbans, trigger executes, and run quick modifications directly from the web browser.
97
+ Run `imhcode` (or `imhcode gui`) to launch a beautiful, interactive terminal user interface (TUI) to manage your projects, sprints, and code modifications via a simple console-based dashboard.
98
+ - Navigate the full 6-phase pipeline using arrow keys and keyboard shortcuts.
99
+ - Guided wizard detects your project status and highlights recommended actions automatically.
100
+ - Easily edit project requirements inline or via your preferred local editor, run plans, trigger task executions, and manage role-based agents in one unified terminal interface.
package/bin/imhcode.js CHANGED
@@ -3006,503 +3006,700 @@ async function runScanCommand(restArgs) {
3006
3006
  console.log('');
3007
3007
  }
3008
3008
 
3009
- async function runGuiCommand(restArgs) {
3010
- const portIdx = restArgs.indexOf('--port');
3011
- const port = portIdx >= 0 ? parseInt(restArgs[portIdx + 1], 10) : 8000;
3012
- const openBrowser = restArgs.includes('--open');
3013
-
3014
- console.log(`\nšŸ•Œ IMH-Code — Starting GUI Dashboard`);
3009
+ async function runTuiCommand() {
3010
+ const cwd = process.cwd();
3015
3011
 
3016
- // 1. Verify environment
3017
- try {
3018
- execSync('php -v', { stdio: 'ignore' });
3019
- } catch {
3020
- console.error('āŒ Error: PHP is not installed or not in PATH. Laravel GUI requires PHP 8.2+.');
3021
- process.exit(1);
3022
- }
3012
+ // Terminal UI control variables
3013
+ let selectedIndex = 0;
3014
+ let menuItems = [];
3015
+ let inSubMode = false;
3016
+ let currentSubMenu = 'main'; // 'main' or 'agents'
3017
+
3018
+ // Colors & formatting helper
3019
+ const ANSI = {
3020
+ reset: '\x1b[0m',
3021
+ bold: '\x1b[1m',
3022
+ dim: '\x1b[2m',
3023
+ italic: '\x1b[3m',
3024
+ red: '\x1b[31m',
3025
+ green: '\x1b[32m',
3026
+ yellow: '\x1b[33m',
3027
+ blue: '\x1b[34m',
3028
+ magenta: '\x1b[35m',
3029
+ cyan: '\x1b[36m',
3030
+ white: '\x1b[37m',
3031
+ clearScreen: '\x1b[2J\x1b[H',
3032
+ hideCursor: '\x1b[?25l',
3033
+ showCursor: '\x1b[?25h'
3034
+ };
3023
3035
 
3024
- try {
3025
- execSync('composer --version', { stdio: 'ignore' });
3026
- } catch {
3027
- console.error('āŒ Error: Composer is not installed or not in PATH. Laravel GUI requires Composer.');
3028
- process.exit(1);
3036
+ function buildMenuItems() {
3037
+ const state = detectProjectState(cwd);
3038
+
3039
+ if (currentSubMenu === 'agents') {
3040
+ const agentItems = [
3041
+ { id: 'agent_list', name: 'List Agents', desc: 'List all 19 role-based agents' },
3042
+ { id: 'agent_inspect', name: 'Inspect Agent', desc: 'Show agent configuration and skills' },
3043
+ { id: 'agent_run', name: 'Run Agent', desc: 'Execute a single agent manually' },
3044
+ { id: 'back', name: '← Back to Main', desc: 'Return to the main control center' }
3045
+ ];
3046
+ return agentItems;
3047
+ }
3048
+
3049
+ const items = [];
3050
+
3051
+ // Step 1: Init
3052
+ if (!state.hasConfig) {
3053
+ items.push({ id: 'init', name: 'Initialize Project', desc: 'āš ļø Setup engines & config (Required)', highlight: true });
3054
+ } else {
3055
+ items.push({ id: 'init', name: 'Reinitialize Project', desc: 'Scan engines & reconfigure model routing' });
3056
+ }
3057
+
3058
+ // Step 2: Requirements
3059
+ if (state.hasConfig && !state.hasStart) {
3060
+ items.push({ id: 'write', name: 'Write Requirements', desc: 'āš ļø Edit docs/start.md (Required)', highlight: true });
3061
+ } else if (state.hasConfig) {
3062
+ items.push({ id: 'write', name: 'Write Requirements', desc: 'Edit docs/start.md requirements' });
3063
+ }
3064
+
3065
+ // Step 3: Plan & Brainstorm
3066
+ if (state.hasStart && !state.hasBrainstorm) {
3067
+ items.push({ id: 'plan', name: 'Plan (Generate Brainstorm)', desc: 'šŸ‘‰ Generate docs/brainstorming.md', highlight: true });
3068
+ } else if (state.hasBrainstorm && !state.hasSprints) {
3069
+ items.push({ id: 'plan', name: 'Plan (Generate Sprints)', desc: 'šŸ‘‰ Generate sprint folders & tasks', highlight: true });
3070
+ } else if (state.hasSprints) {
3071
+ items.push({ id: 'plan', name: 'Re-Plan Project', desc: 'Regenerate sprint plans (deletes current plans)' });
3072
+ }
3073
+
3074
+ // Step 4: Execution
3075
+ if (state.hasSprints) {
3076
+ items.push({ id: 'execute', name: `Execute Sprint ${state.currentSprint}`, desc: 'šŸ‘‰ Run planned tasks for active sprint', highlight: true });
3077
+ }
3078
+
3079
+ // Step 5: Test
3080
+ if (state.hasSprints) {
3081
+ items.push({ id: 'test', name: 'Run E2E & Audits', desc: 'Execute final testing/security/SEO sprint' });
3082
+ }
3083
+
3084
+ // Step 6: Codebase Upgrades & Modifications
3085
+ items.push({ id: 'modify', name: 'Modify Code In-Place', desc: 'Run quick, direct codebase modification' });
3086
+ items.push({ id: 'feature', name: 'Add New Feature', desc: 'Plan a mini-sprint for a new feature' });
3087
+
3088
+ // Step 7: Scanners & Imports
3089
+ items.push({ id: 'scan', name: 'Scan Project Stack', desc: 'Analyze tech stack in current/target directory' });
3090
+ items.push({ id: 'import', name: 'Import Existing Codebase', desc: 'Import external project folder' });
3091
+
3092
+ // Step 8: Agents Manager
3093
+ items.push({ id: 'agents_menu', name: 'Agent Manager →', desc: 'Inspect or run generic role-based agents' });
3094
+
3095
+ // Step 9: Help & Exit
3096
+ items.push({ id: 'commands', name: '/commands Reference', desc: 'Show formatted table of CLI commands' });
3097
+ items.push({ id: 'exit', name: 'Exit TUI', desc: 'Quit interactive control center' });
3098
+
3099
+ return items;
3029
3100
  }
3030
3101
 
3031
- const guiPath = path.join(GLOBAL_DIR, 'gui');
3032
-
3033
- // 2. Setup/Scaffold if not present
3034
- if (!fs.existsSync(guiPath)) {
3035
- console.log(`ā³ Laravel Control Center not installed. Scaffolding in: ${guiPath}`);
3036
- console.log(` (This may take a minute...)`);
3037
- try {
3038
- execSync(`composer create-project laravel/laravel "${guiPath}" --prefer-dist --no-interaction`, { stdio: 'inherit' });
3039
- console.log('āœ… Laravel framework scaffolded.');
3040
- } catch (e) {
3041
- console.error('āŒ Scaffolding failed:', e.message);
3042
- process.exit(1);
3102
+ function detectProjectState(dir) {
3103
+ const hasConfig = fs.existsSync(path.join(dir, CONFIG_FILE));
3104
+ const hasStart = fs.existsSync(path.join(dir, START_MD));
3105
+ const hasBrainstorm = fs.existsSync(path.join(dir, BRAINSTORM_MD));
3106
+ const hasSprints = detectSprintDocs(dir);
3107
+
3108
+ let currentSprint = 1;
3109
+ if (hasSprints) {
3110
+ currentSprint = detectCurrentSprint(dir);
3043
3111
  }
3112
+
3113
+ return {
3114
+ hasConfig,
3115
+ hasStart,
3116
+ hasBrainstorm,
3117
+ hasSprints,
3118
+ currentSprint
3119
+ };
3044
3120
  }
3045
3121
 
3046
- // 3. Inject customized views, routes, models, migrations and controllers
3047
- setupLaravelGui(guiPath);
3122
+ function getProjectStatus(state) {
3123
+ if (!state.hasConfig) return `${ANSI.red}ā— Uninitialized (Run Initialize Project)${ANSI.reset}`;
3124
+ if (!state.hasStart) return `${ANSI.yellow}ā— Missing docs/start.md (Run Write Requirements)${ANSI.reset}`;
3125
+ if (!state.hasBrainstorm) return `${ANSI.cyan}ā— Brainstorming Ready (Run Plan)${ANSI.reset}`;
3126
+ if (!state.hasSprints) return `${ANSI.cyan}ā— Sprint Planning Ready (Run Plan)${ANSI.reset}`;
3127
+ return `${ANSI.green}ā— Sprint ${state.currentSprint} Active${ANSI.reset}`;
3128
+ }
3048
3129
 
3049
- // 3b. Automatically register the folder from which imhcode gui was executed
3050
- const currentPath = process.cwd();
3051
- const folderName = path.basename(currentPath);
3052
- try {
3053
- const escapedPath = currentPath.replace(/\\/g, '\\\\');
3054
- execSync(`php artisan imhcode:register "${folderName}" "${escapedPath}"`, { cwd: guiPath, stdio: 'ignore' });
3055
- console.log(`āœ… Automatically registered project "${folderName}" (${currentPath})`);
3056
- } catch (e) {
3057
- // Suppress errors if database is not migrated yet or locked
3130
+ function getEngineConfig() {
3131
+ try {
3132
+ const config = loadLocalConfig(cwd);
3133
+ if (config && config.primary_engine) {
3134
+ return `${config.primary_engine} (${config.default_model || 'auto'})`;
3135
+ }
3136
+ } catch {}
3137
+ return 'None';
3058
3138
  }
3059
3139
 
3060
- // 4. Serve
3061
- console.log(`\nšŸš€ Starting Laravel server on http://localhost:${port}`);
3062
- if (openBrowser) {
3063
- setTimeout(() => {
3064
- try {
3065
- const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
3066
- execSync(`${cmd} http://localhost:${port}`);
3067
- } catch {}
3068
- }, 2000);
3140
+ function renderMenu() {
3141
+ menuItems = buildMenuItems();
3142
+ // Clamp selection index
3143
+ if (selectedIndex >= menuItems.length) {
3144
+ selectedIndex = Math.max(0, menuItems.length - 1);
3145
+ }
3146
+
3147
+ const state = detectProjectState(cwd);
3148
+ const projectName = path.basename(cwd);
3149
+ const statusStr = getProjectStatus(state);
3150
+ const engineStr = getEngineConfig();
3151
+
3152
+ process.stdout.write(ANSI.clearScreen);
3153
+ process.stdout.write(ANSI.hideCursor);
3154
+
3155
+ const width = 76;
3156
+
3157
+ // Header Banner
3158
+ console.log(ANSI.cyan + ` ā”Œ${'─'.repeat(width - 4)}┐` + ANSI.reset);
3159
+ console.log(ANSI.cyan + ` │` + ANSI.bold + centerText(`${PLATFORM_NAME} Control Center`, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3160
+ console.log(ANSI.cyan + ` │` + ANSI.dim + centerText(PLATFORM_FULL, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3161
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(width - 4)}┤` + ANSI.reset);
3162
+
3163
+ // Project Info
3164
+ const infoLeft = ` Project: ${projectName}`;
3165
+ const infoRight = `Path: ${cwd.length > 35 ? '...' + cwd.slice(-32) : cwd} `;
3166
+ const paddingInfo = width - 4 - infoLeft.length - infoRight.length;
3167
+ console.log(ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + infoLeft + ' '.repeat(paddingInfo) + ANSI.dim + infoRight + ANSI.cyan + `│` + ANSI.reset);
3168
+
3169
+ const statusLine = ` Status: ${statusStr}`;
3170
+ const engineLine = `Engine: ${engineStr} `;
3171
+ const statusClean = statusLine.replace(/\x1b\[[0-9;]*m/g, '');
3172
+ const engineClean = engineLine.replace(/\x1b\[[0-9;]*m/g, '');
3173
+ const paddingStatus = width - 4 - statusClean.length - engineClean.length;
3174
+ console.log(ANSI.cyan + ` │` + ANSI.reset + statusLine + ' '.repeat(paddingStatus) + ANSI.dim + engineLine + ANSI.cyan + `│` + ANSI.reset);
3175
+
3176
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(width - 4)}┤` + ANSI.reset);
3177
+
3178
+ // Menu Items
3179
+ menuItems.forEach((item, index) => {
3180
+ const isSelected = index === selectedIndex;
3181
+ const numStr = ` [${index + 1}] `;
3182
+ const nameStr = item.name;
3183
+
3184
+ let lineText = '';
3185
+ if (isSelected) {
3186
+ lineText = `${ANSI.bold}${ANSI.cyan}ā–ø${ANSI.reset}${ANSI.bold}${numStr}${nameStr}${ANSI.reset}`;
3187
+ } else {
3188
+ lineText = ` ${numStr}${nameStr}`;
3189
+ }
3190
+
3191
+ // Add highlight if item is next action recommended
3192
+ if (item.highlight && !isSelected) {
3193
+ lineText = `${ANSI.yellow}${lineText}${ANSI.reset}`;
3194
+ }
3195
+
3196
+ const cleanLine = lineText.replace(/\x1b\[[0-9;]*m/g, '');
3197
+ const padding = 34 - cleanLine.length;
3198
+
3199
+ let descPart = '';
3200
+ if (item.desc) {
3201
+ descPart = `${ANSI.dim} ${item.desc}${ANSI.reset}`;
3202
+ }
3203
+
3204
+ const cleanDesc = descPart.replace(/\x1b\[[0-9;]*m/g, '');
3205
+ const lineEndPadding = width - 4 - cleanLine.length - Math.max(0, padding) - cleanDesc.length;
3206
+
3207
+ console.log(
3208
+ ANSI.cyan + ` │ ` + ANSI.reset +
3209
+ lineText + ' '.repeat(Math.max(0, padding)) +
3210
+ descPart + ' '.repeat(Math.max(0, lineEndPadding - 1)) +
3211
+ ANSI.cyan + `│` + ANSI.reset
3212
+ );
3213
+ });
3214
+
3215
+ console.log(ANSI.cyan + ` ā””${'─'.repeat(width - 4)}ā”˜` + ANSI.reset);
3216
+ console.log(ANSI.dim + ` ↑↓ Navigate āŽ Select [1-${menuItems.length}] Shortcut q Quit` + ANSI.reset);
3069
3217
  }
3070
3218
 
3071
- try {
3072
- execSync(`php artisan serve --port=${port}`, { cwd: guiPath, stdio: 'inherit' });
3073
- } catch (err) {
3074
- console.error('āŒ Server execution failed:', err.message);
3075
- process.exit(1);
3219
+ function centerText(text, width) {
3220
+ const cleanText = text.replace(/\x1b\[[0-9;]*m/g, '');
3221
+ const padding = Math.max(0, Math.floor((width - cleanText.length) / 2));
3222
+ return ' '.repeat(padding) + text + ' '.repeat(width - cleanText.length - padding);
3076
3223
  }
3077
- }
3078
3224
 
3079
- function setupLaravelGui(guiPath) {
3080
- // Write custom config database.sqlite
3081
- const dbDir = path.join(guiPath, 'database');
3082
- if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
3083
- const dbFile = path.join(dbDir, 'database.sqlite');
3084
- if (!fs.existsSync(dbFile)) {
3085
- fs.writeFileSync(dbFile, '');
3225
+ // Set up standard input listener
3226
+ const isRaw = process.stdin.isTTY;
3227
+ if (isRaw) {
3228
+ process.stdin.setRawMode(true);
3086
3229
  }
3230
+ process.stdin.resume();
3231
+ process.stdin.setEncoding('utf8');
3232
+
3233
+ renderMenu();
3234
+
3235
+ const handleKeypress = async (key) => {
3236
+ if (inSubMode) return;
3237
+
3238
+ // Graceful exit keys
3239
+ if (key === '\u0003' || key === 'q' || key === 'Q') {
3240
+ cleanupAndExit();
3241
+ return;
3242
+ }
3243
+
3244
+ if (key === '\u001b[A') {
3245
+ // Up arrow
3246
+ selectedIndex = (selectedIndex - 1 + menuItems.length) % menuItems.length;
3247
+ renderMenu();
3248
+ } else if (key === '\u001b[B') {
3249
+ // Down arrow
3250
+ selectedIndex = (selectedIndex + 1) % menuItems.length;
3251
+ renderMenu();
3252
+ } else if (key === '\r' || key === '\n') {
3253
+ // Enter key
3254
+ await handleAction(menuItems[selectedIndex]);
3255
+ } else {
3256
+ // Direct number shortcuts
3257
+ const code = key.charCodeAt(0);
3258
+ if (code >= 49 && code <= 57) { // '1'-'9'
3259
+ const index = code - 49;
3260
+ if (index < menuItems.length) {
3261
+ selectedIndex = index;
3262
+ renderMenu();
3263
+ await handleAction(menuItems[index]);
3264
+ }
3265
+ } else if (key === '0') {
3266
+ const index = 9; // '0' is 10th item
3267
+ if (index < menuItems.length) {
3268
+ selectedIndex = index;
3269
+ renderMenu();
3270
+ await handleAction(menuItems[index]);
3271
+ }
3272
+ }
3273
+ }
3274
+ };
3275
+
3276
+ process.stdin.on('data', handleKeypress);
3087
3277
 
3088
- // Edit .env for SQLite
3089
- const envPath = path.join(guiPath, '.env');
3090
- if (fs.existsSync(envPath)) {
3091
- let envContent = fs.readFileSync(envPath, 'utf8');
3092
- // Remove default DB settings
3093
- envContent = envContent.replace(/DB_CONNECTION=\w+/g, 'DB_CONNECTION=sqlite');
3094
- envContent = envContent.replace(/DB_HOST=[^\n]+/g, '');
3095
- envContent = envContent.replace(/DB_PORT=[^\n]+/g, '');
3096
- envContent = envContent.replace(/DB_DATABASE=[^\n]+/g, '');
3097
- envContent = envContent.replace(/DB_USERNAME=[^\n]+/g, '');
3098
- envContent = envContent.replace(/DB_PASSWORD=[^\n]+/g, '');
3099
- fs.writeFileSync(envPath, envContent, 'utf8');
3278
+ function cleanupAndExit() {
3279
+ if (isRaw) {
3280
+ process.stdin.setRawMode(false);
3281
+ }
3282
+ process.stdin.pause();
3283
+ process.stdout.write(ANSI.showCursor);
3284
+ console.log('\nšŸ•Œ Thank you for using IMH-Code. Goodbye!\n');
3285
+ process.exit(0);
3100
3286
  }
3101
3287
 
3102
- // Generate Model: Project.php
3103
- const modelDir = path.join(guiPath, 'app', 'Models');
3104
- if (!fs.existsSync(modelDir)) fs.mkdirSync(modelDir, { recursive: true });
3105
- const modelContent = `<?php
3106
- namespace App\\Models;
3107
- use Illuminate\\Database\\Eloquent\\Model;
3108
- class Project extends Model {
3109
- protected $fillable = ['name', 'path', 'frontend_stack', 'backend_stack', 'database'];
3110
- }
3111
- `;
3112
- fs.writeFileSync(path.join(modelDir, 'Project.php'), modelContent, 'utf8');
3113
-
3114
- // Generate Controller: DashboardController.php
3115
- const controllerDir = path.join(guiPath, 'app', 'Http', 'Controllers');
3116
- if (!fs.existsSync(controllerDir)) fs.mkdirSync(controllerDir, { recursive: true });
3117
- const controllerContent = `<?php
3118
- namespace App\\Http\\Controllers;
3119
-
3120
- use Illuminate\\Http\\Request;
3121
- use Illuminate\\Support\\Facades\\Process;
3122
- use Illuminate\\Support\\Facades\\DB;
3123
-
3124
- class DashboardController extends Controller {
3125
- public function index() {
3126
- $projects = DB::table('projects')->get();
3127
- return view('dashboard', compact('projects'));
3288
+ async function pauseAndResume() {
3289
+ inSubMode = true;
3290
+ if (isRaw) {
3291
+ process.stdin.setRawMode(false);
3128
3292
  }
3293
+ process.stdin.pause();
3294
+ process.stdout.write(ANSI.showCursor + '\n');
3295
+
3296
+ // Return a promise that resolves when user presses enter
3297
+ return new Promise((resolve) => {
3298
+ const rl = readline.createInterface({
3299
+ input: process.stdin,
3300
+ output: process.stdout
3301
+ });
3302
+ rl.question(ANSI.cyan + '\nPress [Enter] to return to the main menu...' + ANSI.reset, () => {
3303
+ rl.close();
3304
+ inSubMode = false;
3305
+ if (isRaw) {
3306
+ process.stdin.setRawMode(true);
3307
+ }
3308
+ process.stdin.resume();
3309
+ renderMenu();
3310
+ resolve();
3311
+ });
3312
+ });
3313
+ }
3129
3314
 
3130
- public function create(Request $request) {
3131
- $name = $request->input('name');
3132
- $path = $request->input('path');
3315
+ async function handleAction(item) {
3316
+ const state = detectProjectState(cwd);
3317
+
3318
+ switch (item.id) {
3319
+ case 'init':
3320
+ inSubMode = true;
3321
+ if (isRaw) process.stdin.setRawMode(false);
3322
+ process.stdin.pause();
3323
+ process.stdout.write(ANSI.showCursor + '\n');
3133
3324
 
3134
- DB::table('projects')->insert([
3135
- 'name' => $name,
3136
- 'path' => $path,
3137
- 'created_at' => now(),
3138
- 'updated_at' => now(),
3139
- ]);
3325
+ try {
3326
+ await runInit();
3327
+ } catch (err) {
3328
+ console.error(`āŒ Initialization failed: ${err.message ?? err}`);
3329
+ }
3330
+
3331
+ inSubMode = false;
3332
+ if (isRaw) process.stdin.setRawMode(true);
3333
+ process.stdin.resume();
3334
+ renderMenu();
3335
+ break;
3140
3336
 
3141
- // Run imhcode init
3142
- Process::path($path)->run('imhcode');
3337
+ case 'write':
3338
+ inSubMode = true;
3339
+ if (isRaw) process.stdin.setRawMode(false);
3340
+ process.stdin.pause();
3341
+ process.stdout.write(ANSI.showCursor + '\n');
3342
+
3343
+ // Let user choose how they want to edit
3344
+ console.log('Requirements Editor Options:');
3345
+ console.log(' [1] Open docs/start.md in default terminal editor (nano, vi, notepad)');
3346
+ console.log(' [2] Open docs/start.md in VS Code (code)');
3347
+ console.log(' [3] Edit project description directly in terminal');
3348
+
3349
+ const rlEdit = readline.createInterface({ input: process.stdin, output: process.stdout });
3350
+ const choice = await new Promise(res => rlEdit.question('\nSelect option [1-3] (default: 1): ', res));
3351
+ rlEdit.close();
3143
3352
 
3144
- return redirect()->route('dashboard');
3145
- }
3353
+ const startMdPath = path.join(cwd, START_MD);
3354
+
3355
+ if (choice === '2') {
3356
+ try {
3357
+ execSync(`code "${startMdPath}"`, { stdio: 'inherit' });
3358
+ console.log('šŸ“‚ Opened docs/start.md in VS Code.');
3359
+ } catch {
3360
+ console.log('āš ļø Failed to launch VS Code ("code" CLI not in path). Fallback to standard editor.');
3361
+ const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3362
+ execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3363
+ }
3364
+ } else if (choice === '3') {
3365
+ console.log('\nEnter your new project description below (press Enter on empty line to finish):\n');
3366
+ const lines = [];
3367
+ const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
3368
+ while (true) {
3369
+ const line = await new Promise(res => rlInput.question('> ', res));
3370
+ if (line.trim() === '') break;
3371
+ lines.push(line);
3372
+ }
3373
+ rlInput.close();
3374
+
3375
+ if (lines.length > 0) {
3376
+ // Write to docs/start.md within markers
3377
+ let startContent = '';
3378
+ if (fs.existsSync(startMdPath)) {
3379
+ startContent = fs.readFileSync(startMdPath, 'utf8');
3380
+ }
3381
+
3382
+ const desc = lines.join('\n');
3383
+ const markerStart = '<!-- WRITE_PROMPT_HERE -->';
3384
+ const markerEnd = '<!-- END_PROMPT -->';
3385
+
3386
+ if (startContent.includes(markerStart) && startContent.includes(markerEnd)) {
3387
+ const before = startContent.split(markerStart)[0];
3388
+ const after = startContent.split(markerEnd)[1];
3389
+ const newContent = `${before}${markerStart}\n${desc}\n${markerEnd}${after}`;
3390
+ fs.writeFileSync(startMdPath, newContent, 'utf8');
3391
+ } else {
3392
+ // Write a simple template with markers
3393
+ const newContent = `# šŸ•Œ IMH-Code — Project Start\n\n${markerStart}\n${desc}\n${markerEnd}\n`;
3394
+ fs.writeFileSync(startMdPath, newContent, 'utf8');
3395
+ }
3396
+ console.log('āœ… Requirements updated successfully in docs/start.md!');
3397
+ }
3398
+ } else {
3399
+ const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3400
+ try {
3401
+ execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3402
+ } catch (err) {
3403
+ console.error(`āŒ Failed to launch editor: ${err.message}`);
3404
+ }
3405
+ }
3406
+
3407
+ inSubMode = false;
3408
+ if (isRaw) process.stdin.setRawMode(true);
3409
+ process.stdin.resume();
3410
+ renderMenu();
3411
+ break;
3146
3412
 
3147
- public function show($id) {
3148
- $project = DB::table('projects')->where('id', $id)->first();
3149
- if (!$project) abort(404);
3150
-
3151
- // Scan folder for sprints
3152
- $sprints = [];
3153
- $docsPath = $project->path . '/docs';
3154
- if (is_dir($docsPath)) {
3155
- $files = scandir($docsPath);
3156
- foreach ($files as $file) {
3157
- if (preg_match('/^sprint-(\\d+)$/', $file, $matches)) {
3158
- $sprints[] = [
3159
- 'num' => $matches[1],
3160
- 'name' => 'Sprint ' . $matches[1],
3161
- ];
3413
+ case 'plan':
3414
+ inSubMode = true;
3415
+ if (isRaw) process.stdin.setRawMode(false);
3416
+ process.stdin.pause();
3417
+ process.stdout.write(ANSI.showCursor + '\n');
3418
+
3419
+ // If sprints already exist, confirm deletion first
3420
+ let proceed = true;
3421
+ if (state.hasSprints) {
3422
+ const rlConfirm = readline.createInterface({ input: process.stdin, output: process.stdout });
3423
+ const confirmAns = await new Promise(res => rlConfirm.question(ANSI.yellow + 'āš ļø Sprints already exist. Re-planning will delete docs/brainstorming.md and all planned sprint directories. Proceed? (y/N): ' + ANSI.reset, res));
3424
+ rlConfirm.close();
3425
+ if (confirmAns.toLowerCase() !== 'y') {
3426
+ proceed = false;
3427
+ } else {
3428
+ // Delete brainstorming.md and docs/sprint-*
3429
+ try {
3430
+ if (fs.existsSync(path.join(cwd, BRAINSTORM_MD))) fs.unlinkSync(path.join(cwd, BRAINSTORM_MD));
3431
+ let sNum = 1;
3432
+ while (true) {
3433
+ const sDir = path.join(cwd, DOCS_DIR, `sprint-${sNum}`);
3434
+ if (fs.existsSync(sDir)) {
3435
+ rmRecursiveSync(sDir);
3436
+ sNum++;
3437
+ } else {
3438
+ break;
3162
3439
  }
3440
+ }
3441
+ } catch (e) {
3442
+ console.warn('āš ļø Could not clean up old plan files: ', e.message);
3163
3443
  }
3444
+ }
3445
+ }
3446
+
3447
+ if (proceed) {
3448
+ try {
3449
+ await runPlanCommand([]);
3450
+ } catch (err) {
3451
+ console.error(`āŒ Planning failed: ${err.message ?? err}`);
3452
+ }
3164
3453
  }
3454
+
3455
+ await pauseAndResume();
3456
+ break;
3165
3457
 
3166
- // Get logs
3167
- $logs = [];
3168
- $sessionsPath = $project->path . '/.imhcode/sessions';
3169
- if (is_dir($sessionsPath)) {
3170
- $files = array_diff(scandir($sessionsPath), ['.', '..']);
3171
- foreach ($files as $file) {
3172
- $logs[] = $file;
3173
- }
3458
+ case 'execute':
3459
+ inSubMode = true;
3460
+ if (isRaw) process.stdin.setRawMode(false);
3461
+ process.stdin.pause();
3462
+ process.stdout.write(ANSI.showCursor + '\n');
3463
+
3464
+ try {
3465
+ await runExecuteCommand([state.currentSprint.toString()]);
3466
+ } catch (err) {
3467
+ console.error(`āŒ Execution failed: ${err.message ?? err}`);
3174
3468
  }
3469
+
3470
+ await pauseAndResume();
3471
+ break;
3175
3472
 
3176
- return view('project', compact('project', 'sprints', 'logs'));
3177
- }
3473
+ case 'test':
3474
+ inSubMode = true;
3475
+ if (isRaw) process.stdin.setRawMode(false);
3476
+ process.stdin.pause();
3477
+ process.stdout.write(ANSI.showCursor + '\n');
3478
+
3479
+ try {
3480
+ await runTestCommand([]);
3481
+ } catch (err) {
3482
+ console.error(`āŒ Testing failed: ${err.message ?? err}`);
3483
+ }
3484
+
3485
+ await pauseAndResume();
3486
+ break;
3178
3487
 
3179
- public function execute($id, Request $request) {
3180
- $project = DB::table('projects')->where('id', $id)->first();
3181
- $sprint = $request->input('sprint');
3488
+ case 'modify':
3489
+ inSubMode = true;
3490
+ if (isRaw) process.stdin.setRawMode(false);
3491
+ process.stdin.pause();
3492
+ process.stdout.write(ANSI.showCursor + '\n');
3493
+
3494
+ const rlMod = readline.createInterface({ input: process.stdin, output: process.stdout });
3495
+ const modDesc = await new Promise(res => rlMod.question('Enter description of the modification: ', res));
3496
+ rlMod.close();
3497
+
3498
+ if (modDesc.trim() !== '') {
3499
+ try {
3500
+ await runModifyCommand([modDesc, '--live']);
3501
+ } catch (err) {
3502
+ console.error(`āŒ Modification failed: ${err.message ?? err}`);
3503
+ }
3504
+ }
3182
3505
 
3183
- $result = Process::path($project->path)
3184
- ->timeout(600)
3185
- ->run("imhcode execute {$sprint}");
3506
+ await pauseAndResume();
3507
+ break;
3186
3508
 
3187
- return back()->with('output', $result->output());
3188
- }
3509
+ case 'feature':
3510
+ inSubMode = true;
3511
+ if (isRaw) process.stdin.setRawMode(false);
3512
+ process.stdin.pause();
3513
+ process.stdout.write(ANSI.showCursor + '\n');
3189
3514
 
3190
- public function modify($id, Request $request) {
3191
- $project = DB::table('projects')->where('id', $id)->first();
3192
- $desc = $request->input('description');
3515
+ const rlFeat = readline.createInterface({ input: process.stdin, output: process.stdout });
3516
+ const featDesc = await new Promise(res => rlFeat.question('Enter description of the feature to plan: ', res));
3517
+ rlFeat.close();
3518
+
3519
+ if (featDesc.trim() !== '') {
3520
+ try {
3521
+ await runFeatureCommand([featDesc]);
3522
+ } catch (err) {
3523
+ console.error(`āŒ Feature planning failed: ${err.message ?? err}`);
3524
+ }
3525
+ }
3193
3526
 
3194
- $result = Process::path($project->path)
3195
- ->timeout(600)
3196
- ->run("imhcode modify \\"{$desc}\\" --live");
3527
+ await pauseAndResume();
3528
+ break;
3197
3529
 
3198
- return back()->with('output', $result->output());
3199
- }
3200
- }
3201
- `;
3202
- fs.writeFileSync(path.join(controllerDir, 'DashboardController.php'), controllerContent, 'utf8');
3203
-
3204
- // Generate web.php routes
3205
- const routesDir = path.join(guiPath, 'routes');
3206
- if (!fs.existsSync(routesDir)) fs.mkdirSync(routesDir, { recursive: true });
3207
- const routesContent = `<?php
3208
- use Illuminate\\Support\\Facades\\Route;
3209
- use App\\Http\\Controllers\\DashboardController;
3210
-
3211
- Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
3212
- Route::post('/project/create', [DashboardController::class, 'create'])->name('project.create');
3213
- Route::get('/project/{id}', [DashboardController::class, 'show'])->name('project.show');
3214
- Route::post('/project/{id}/execute', [DashboardController::class, 'execute'])->name('project.execute');
3215
- Route::post('/project/{id}/modify', [DashboardController::class, 'modify'])->name('project.modify');
3216
- `;
3217
- fs.writeFileSync(path.join(routesDir, 'web.php'), routesContent, 'utf8');
3218
-
3219
- // Generate Migration: create_projects_table
3220
- const migrationsDir = path.join(guiPath, 'database', 'migrations');
3221
- if (!fs.existsSync(migrationsDir)) fs.mkdirSync(migrationsDir, { recursive: true });
3222
- const migrationContent = `<?php
3223
- use Illuminate\\Database\\Migrations\\Migration;
3224
- use Illuminate\\Database\\Schema\\Blueprint;
3225
- use Illuminate\\Support\\Facades\\Schema;
3226
-
3227
- return new class extends Migration {
3228
- public function up() {
3229
- if (!Schema::hasTable('projects')) {
3230
- Schema::create('projects', function (Blueprint $table) {
3231
- $table->id();
3232
- $table->string('name');
3233
- $table->string('path');
3234
- $table->string('frontend_stack')->nullable();
3235
- $table->string('backend_stack')->nullable();
3236
- $table->string('database')->nullable();
3237
- $table->timestamps();
3238
- });
3530
+ case 'scan':
3531
+ inSubMode = true;
3532
+ if (isRaw) process.stdin.setRawMode(false);
3533
+ process.stdin.pause();
3534
+ process.stdout.write(ANSI.showCursor + '\n');
3535
+
3536
+ const rlScan = readline.createInterface({ input: process.stdin, output: process.stdout });
3537
+ const scanPath = await new Promise(res => rlScan.question('Enter directory path to scan (default: .): ', res));
3538
+ rlScan.close();
3539
+
3540
+ try {
3541
+ await runScanCommand([scanPath.trim() || '.']);
3542
+ } catch (err) {
3543
+ console.error(`āŒ Scan failed: ${err.message ?? err}`);
3239
3544
  }
3240
- }
3241
- public function down() {
3242
- Schema::dropIfExists('projects');
3243
- }
3244
- };
3245
- `;
3246
- fs.writeFileSync(path.join(migrationsDir, '2026_07_05_000000_create_imhcode_projects_table.php'), migrationContent, 'utf8');
3247
-
3248
- // Generate Views
3249
- const viewsDir = path.join(guiPath, 'resources', 'views');
3250
- if (!fs.existsSync(viewsDir)) fs.mkdirSync(viewsDir, { recursive: true });
3251
-
3252
- const dashboardView = `<!DOCTYPE html>
3253
- <html lang="en" class="dark">
3254
- <head>
3255
- <meta charset="UTF-8">
3256
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
3257
- <title>IMH-Code Control Center</title>
3258
- <script src="https://cdn.tailwindcss.com"></script>
3259
- <script>
3260
- tailwind.config = {
3261
- darkMode: 'class',
3262
- theme: {
3263
- extend: {
3264
- colors: {
3265
- dark: {
3266
- 50: '#f8fafc',
3267
- 900: '#0f172a',
3268
- 950: '#020617'
3269
- }
3270
- }
3271
- }
3272
- }
3545
+
3546
+ await pauseAndResume();
3547
+ break;
3548
+
3549
+ case 'import':
3550
+ inSubMode = true;
3551
+ if (isRaw) process.stdin.setRawMode(false);
3552
+ process.stdin.pause();
3553
+ process.stdout.write(ANSI.showCursor + '\n');
3554
+
3555
+ const rlImport = readline.createInterface({ input: process.stdin, output: process.stdout });
3556
+ const importPath = await new Promise(res => rlImport.question('Enter directory path to import: ', res));
3557
+ rlImport.close();
3558
+
3559
+ if (importPath.trim() !== '') {
3560
+ try {
3561
+ await runImportCommand([importPath.trim()]);
3562
+ } catch (err) {
3563
+ console.error(`āŒ Import failed: ${err.message ?? err}`);
3564
+ }
3273
3565
  }
3274
- </script>
3275
- </head>
3276
- <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3277
- <div class="flex">
3278
- <!-- Sidebar -->
3279
- <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3280
- <div class="flex items-center gap-3 mb-8">
3281
- <span class="text-2xl">šŸ•Œ</span>
3282
- <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3283
- </div>
3284
- <nav class="space-y-2">
3285
- <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-indigo-600/10 text-indigo-400 font-medium">
3286
- Dashboard
3287
- </a>
3288
- </nav>
3289
- </aside>
3290
-
3291
- <!-- Main Content -->
3292
- <main class="flex-1 p-10">
3293
- <header class="flex justify-between items-center mb-10">
3294
- <div>
3295
- <h2 class="text-3xl font-bold text-white tracking-tight">Control Center</h2>
3296
- <p class="text-slate-400 mt-1">Manage and orchestrate multi-agent coding harnesses.</p>
3297
- </div>
3298
- </header>
3299
-
3300
- @if(session('output'))
3301
- <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3302
- <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3303
- <pre class="font-mono text-sm bg-black/50 p-4 rounded-lg overflow-x-auto text-green-400 max-h-96">{{ session('output') }}</pre>
3304
- </div>
3305
- @endif
3306
-
3307
- <!-- Grid -->
3308
- <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
3309
- <!-- Project List -->
3310
- <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6">
3311
- <h3 class="text-xl font-semibold text-white mb-6">Your Projects</h3>
3312
- @if($projects->isEmpty())
3313
- <div class="text-center py-12 text-slate-500">
3314
- <p>No projects imported yet.</p>
3315
- </div>
3316
- @else
3317
- <div class="space-y-4">
3318
- @foreach($projects as $p)
3319
- <div class="p-4 bg-slate-950/40 border border-slate-800/80 rounded-xl flex justify-between items-center hover:border-slate-700 transition">
3320
- <div>
3321
- <h4 class="font-medium text-white">{{ $p->name }}</h4>
3322
- <p class="text-xs text-slate-500 mt-1">{{ $p->path }}</p>
3323
- </div>
3324
- <a href="/project/{{ $p->id }}" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-lg text-sm font-medium transition">
3325
- Open
3326
- </a>
3327
- </div>
3328
- @endforeach
3329
- </div>
3330
- @endif
3331
- </div>
3332
-
3333
- <!-- Create/Import Form -->
3334
- <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3335
- <div>
3336
- <h3 class="text-xl font-semibold text-white mb-2">Import Project</h3>
3337
- <p class="text-sm text-slate-400">Initialize or import an existing project directory.</p>
3338
- </div>
3339
- <form action="/project/create" method="POST" class="space-y-4">
3340
- @csrf
3341
- <div>
3342
- <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Project Name</label>
3343
- <input type="text" name="name" required class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200">
3344
- </div>
3345
- <div>
3346
- <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Local Path</label>
3347
- <input type="text" name="path" required class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200" placeholder="/path/to/project">
3348
- </div>
3349
- <button type="submit" class="w-full py-3 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg text-sm transition">
3350
- Initialize & Open
3351
- </button>
3352
- </form>
3353
- </div>
3354
- </div>
3355
- </main>
3356
- </div>
3357
- </body>
3358
- </html>
3359
- `;
3360
- fs.writeFileSync(path.join(viewsDir, 'dashboard.blade.php'), dashboardView, 'utf8');
3361
-
3362
- const projectView = `<!DOCTYPE html>
3363
- <html lang="en" class="dark">
3364
- <head>
3365
- <meta charset="UTF-8">
3366
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
3367
- <title>Project: {{ $project->name }}</title>
3368
- <script src="https://cdn.tailwindcss.com"></script>
3369
- <script>
3370
- tailwind.config = {
3371
- darkMode: 'class',
3372
- theme: {
3373
- extend: {
3374
- colors: {
3375
- dark: {
3376
- 50: '#f8fafc',
3377
- 900: '#0f172a',
3378
- 950: '#020617'
3379
- }
3380
- }
3381
- }
3382
- }
3566
+
3567
+ await pauseAndResume();
3568
+ break;
3569
+
3570
+ case 'agents_menu':
3571
+ currentSubMenu = 'agents';
3572
+ selectedIndex = 0;
3573
+ renderMenu();
3574
+ break;
3575
+
3576
+ case 'agent_list':
3577
+ inSubMode = true;
3578
+ if (isRaw) process.stdin.setRawMode(false);
3579
+ process.stdin.pause();
3580
+ process.stdout.write(ANSI.showCursor + '\n');
3581
+
3582
+ try {
3583
+ await runAgentCommand('list', []);
3584
+ } catch (err) {
3585
+ console.error(`āŒ Listing failed: ${err.message ?? err}`);
3383
3586
  }
3384
- </script>
3385
- </head>
3386
- <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3387
- <div class="flex">
3388
- <!-- Sidebar -->
3389
- <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3390
- <div class="flex items-center gap-3 mb-8">
3391
- <span class="text-2xl">šŸ•Œ</span>
3392
- <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3393
- </div>
3394
- <nav class="space-y-2">
3395
- <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 font-medium hover:bg-slate-800">
3396
- Dashboard
3397
- </a>
3398
- </nav>
3399
- </aside>
3400
-
3401
- <!-- Main Content -->
3402
- <main class="flex-1 p-10">
3403
- <header class="flex justify-between items-center mb-10">
3404
- <div>
3405
- <h2 class="text-3xl font-bold text-white tracking-tight">{{ $project->name }}</h2>
3406
- <p class="text-slate-400 mt-1">{{ $project->path }}</p>
3407
- </div>
3408
- </header>
3409
-
3410
- @if(session('output'))
3411
- <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3412
- <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3413
- <pre class="font-mono text-sm bg-black/50 p-4 rounded-lg overflow-x-auto text-green-400 max-h-96">{{ session('output') }}</pre>
3414
- </div>
3415
- @endif
3416
-
3417
- <!-- Action Grid -->
3418
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
3419
- <!-- Sprints Kanban / Executor -->
3420
- <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 lg:col-span-2 space-y-6">
3421
- <h3 class="text-xl font-semibold text-white">Sprints and Execution</h3>
3422
- @if(empty($sprints))
3423
- <div class="text-center py-12 text-slate-500">
3424
- <p>No active sprints detected. Use the CLI or modification panel to start development.</p>
3425
- </div>
3426
- @else
3427
- <div class="space-y-4">
3428
- @foreach($sprints as $s)
3429
- <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl flex justify-between items-center">
3430
- <div>
3431
- <h4 class="font-semibold text-white">{{ $s['name'] }}</h4>
3432
- </div>
3433
- <form action="/project/{{ $project->id }}/execute" method="POST">
3434
- @csrf
3435
- <input type="hidden" name="sprint" value="{{ $s['num'] }}">
3436
- <button type="submit" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-sm font-medium transition">
3437
- Run Sprint
3438
- </button>
3439
- </form>
3440
- </div>
3441
- @endforeach
3442
- </div>
3443
- @endif
3444
- </div>
3445
-
3446
- <!-- Quick Modification Panel -->
3447
- <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3448
- <div>
3449
- <h3 class="text-xl font-semibold text-white mb-2">Modify Project</h3>
3450
- <p class="text-sm text-slate-400">Targeted, in-place codebase modifications.</p>
3451
- </div>
3452
- <form action="/project/{{ $project->id }}/modify" method="POST" class="space-y-4">
3453
- @csrf
3454
- <div>
3455
- <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Describe Change</label>
3456
- <textarea name="description" required rows="4" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200" placeholder="e.g. Add a logout button to the navbar..."></textarea>
3457
- </div>
3458
- <button type="submit" class="w-full py-3 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg text-sm transition">
3459
- Apply Modification
3460
- </button>
3461
- </form>
3462
- </div>
3463
- </div>
3464
- </main>
3465
- </div>
3466
- </body>
3467
- </html>
3468
- `;
3469
- fs.writeFileSync(path.join(viewsDir, 'project.blade.php'), projectView, 'utf8');
3470
-
3471
- // Generate Command: RegisterProject.php
3472
- const consoleDir = path.join(guiPath, 'app', 'Console', 'Commands');
3473
- if (!fs.existsSync(consoleDir)) fs.mkdirSync(consoleDir, { recursive: true });
3474
- const commandContent = `<?php
3475
- namespace App\\Console\\Commands;
3476
-
3477
- use Illuminate\\Console\\Command;
3478
- use Illuminate\\Support\\Facades\\DB;
3479
-
3480
- class RegisterProject extends Command {
3481
- protected $signature = 'imhcode:register {name} {path}';
3482
- protected $description = 'Register a project in the dashboard';
3483
-
3484
- public function handle() {
3485
- $name = $this->argument('name');
3486
- $path = $this->argument('path');
3487
-
3488
- DB::table('projects')->updateOrInsert(
3489
- ['path' => $path],
3490
- [
3491
- 'name' => $name,
3492
- 'created_at' => now(),
3493
- 'updated_at' => now()
3494
- ]
3495
- );
3587
+
3588
+ await pauseAndResume();
3589
+ break;
3590
+
3591
+ case 'agent_inspect':
3592
+ inSubMode = true;
3593
+ if (isRaw) process.stdin.setRawMode(false);
3594
+ process.stdin.pause();
3595
+ process.stdout.write(ANSI.showCursor + '\n');
3596
+
3597
+ console.log('Available Agents:');
3598
+ console.log(' planner, designer, nextjs-executor, react-executor, vue-executor,');
3599
+ console.log(' laravel-executor, python-executor, java-executor, flutter-executor,');
3600
+ console.log(' react-native-executor, ios-executor, android-executor, systems-executor,');
3601
+ console.log(' web3-executor, devops-executor, tester, security-auditor, seo-optimizer, debugger\n');
3602
+
3603
+ const rlInspect = readline.createInterface({ input: process.stdin, output: process.stdout });
3604
+ const inspectAgentId = await new Promise(res => rlInspect.question('Enter Agent ID to inspect: ', res));
3605
+ rlInspect.close();
3606
+
3607
+ if (inspectAgentId.trim() !== '') {
3608
+ try {
3609
+ await runAgentCommand('inspect', [inspectAgentId.trim()]);
3610
+ } catch (err) {
3611
+ console.error(`āŒ Agent inspection failed: ${err.message ?? err}`);
3612
+ }
3613
+ }
3614
+
3615
+ await pauseAndResume();
3616
+ break;
3617
+
3618
+ case 'agent_run':
3619
+ inSubMode = true;
3620
+ if (isRaw) process.stdin.setRawMode(false);
3621
+ process.stdin.pause();
3622
+ process.stdout.write(ANSI.showCursor + '\n');
3623
+
3624
+ console.log('Available Agents:');
3625
+ console.log(' planner, designer, nextjs-executor, react-executor, vue-executor,');
3626
+ console.log(' laravel-executor, python-executor, java-executor, flutter-executor,');
3627
+ console.log(' react-native-executor, ios-executor, android-executor, systems-executor,');
3628
+ console.log(' web3-executor, devops-executor, tester, security-auditor, seo-optimizer, debugger\n');
3629
+
3630
+ const rlRunAgent = readline.createInterface({ input: process.stdin, output: process.stdout });
3631
+ const runAgentId = await new Promise(res => rlRunAgent.question('Enter Agent ID to run: ', res));
3632
+ const runTask = await new Promise(res => rlRunAgent.question('Enter task description: ', res));
3633
+ rlRunAgent.close();
3634
+
3635
+ if (runAgentId.trim() !== '' && runTask.trim() !== '') {
3636
+ try {
3637
+ await runAgentCommand('run', [runAgentId.trim(), runTask.trim(), '--live']);
3638
+ } catch (err) {
3639
+ console.error(`āŒ Agent execution failed: ${err.message ?? err}`);
3640
+ }
3641
+ }
3642
+
3643
+ await pauseAndResume();
3644
+ break;
3645
+
3646
+ case 'back':
3647
+ currentSubMenu = 'main';
3648
+ selectedIndex = 0;
3649
+ renderMenu();
3650
+ break;
3651
+
3652
+ case 'commands':
3653
+ inSubMode = true;
3654
+ if (isRaw) process.stdin.setRawMode(false);
3655
+ process.stdin.pause();
3656
+ process.stdout.write(ANSI.showCursor + '\n');
3657
+
3658
+ showCommandsTable();
3659
+
3660
+ await pauseAndResume();
3661
+ break;
3662
+
3663
+ case 'exit':
3664
+ cleanupAndExit();
3665
+ break;
3496
3666
  }
3497
- }
3498
- `;
3499
- fs.writeFileSync(path.join(consoleDir, 'RegisterProject.php'), commandContent, 'utf8');
3667
+ }
3500
3668
 
3501
- // Trigger migration
3502
- try {
3503
- execSync('php artisan migrate --force', { cwd: guiPath, stdio: 'ignore' });
3504
- } catch (e) {
3505
- // Suppress if DB is locked or already migrated
3669
+ function showCommandsTable() {
3670
+ const width = 76;
3671
+ console.log(ANSI.cyan + ` ā”Œ${'─'.repeat(width - 4)}┐` + ANSI.reset);
3672
+ console.log(ANSI.cyan + ` │` + ANSI.bold + centerText(`šŸ“– CLI Commands Reference`, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3673
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(20)}┬${'─'.repeat(width - 25)}┤` + ANSI.reset);
3674
+
3675
+ const cmds = [
3676
+ { cmd: 'imhcode', desc: 'Launch interactive terminal TUI' },
3677
+ { cmd: 'imhcode init', desc: 'Non-interactive environment setup' },
3678
+ { cmd: 'imhcode plan', desc: 'Generate brainstorm or sprint plans' },
3679
+ { cmd: 'imhcode execute N', desc: 'Execute Sprint N tasks' },
3680
+ { cmd: 'imhcode test', desc: 'Run testing/security/SEO sprint' },
3681
+ { cmd: 'imhcode modify', desc: 'Modify codebase in-place' },
3682
+ { cmd: 'imhcode feature', desc: 'Plan a mini-sprint for new feature' },
3683
+ { cmd: 'imhcode fix', desc: 'Quick targeted bug fix' },
3684
+ { cmd: 'imhcode scan', desc: 'Scan current folder for frameworks' },
3685
+ { cmd: 'imhcode import', desc: 'Import existing codebase' },
3686
+ { cmd: 'imhcode agent list',desc: 'List all 19 generic agents' },
3687
+ { cmd: 'imhcode report', desc: 'Generate final project team report' },
3688
+ { cmd: '--help / -h', desc: 'Display general help instructions' }
3689
+ ];
3690
+
3691
+ cmds.forEach(item => {
3692
+ const col1 = ` ${item.cmd}`;
3693
+ const col2 = ` ${item.desc}`;
3694
+ const pad1 = 18 - col1.length;
3695
+ const pad2 = width - 25 - col2.length;
3696
+ console.log(
3697
+ ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + col1 + ' '.repeat(Math.max(0, pad1)) +
3698
+ ANSI.cyan + `│` + ANSI.reset + col2 + ' '.repeat(Math.max(0, pad2)) +
3699
+ ANSI.cyan + `│` + ANSI.reset
3700
+ );
3701
+ });
3702
+
3703
+ console.log(ANSI.cyan + ` ā””${'─'.repeat(20)}┓${'─'.repeat(width - 25)}ā”˜` + ANSI.reset);
3506
3704
  }
3507
3705
  }
3508
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imhcode",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "IMH-Code — Imam Hussain Coding Harness Platform. A fast-first multi-agent AI coding framework with intelligent model routing. 19 generic role-based agents (planner, nextjs-executor, laravel-executor, etc.), configurable testing strategy, and 7 token-saving optimizations for rapid MVP development.",
5
5
  "main": "index.js",
6
6
  "bin": {