imhcode 2.0.1 → 2.0.3

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
@@ -108,7 +108,13 @@ if (command === 'plan') {
108
108
  process.exit(1);
109
109
  });
110
110
  } else if (command === 'gui') {
111
- runGuiCommand(args.slice(1)).catch(err => {
111
+ console.log('šŸ’” Note: \'imhcode gui\' is deprecated in favor of the interactive TUI.');
112
+ runTuiCommand().catch(err => {
113
+ console.error(err.message ?? err);
114
+ process.exit(1);
115
+ });
116
+ } else if (command === 'init') {
117
+ runInit().catch(err => {
112
118
  console.error(err.message ?? err);
113
119
  process.exit(1);
114
120
  });
@@ -124,8 +130,8 @@ if (command === 'plan') {
124
130
  process.exit(1);
125
131
  });
126
132
  } else {
127
- // Default: framework initialization
128
- runInit().catch(err => {
133
+ // Default: start interactive TUI
134
+ runTuiCommand().catch(err => {
129
135
  console.error(err.message ?? err);
130
136
  process.exit(1);
131
137
  });
@@ -3006,503 +3012,700 @@ async function runScanCommand(restArgs) {
3006
3012
  console.log('');
3007
3013
  }
3008
3014
 
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`);
3015
+ async function runTuiCommand() {
3016
+ const cwd = process.cwd();
3015
3017
 
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
- }
3018
+ // Terminal UI control variables
3019
+ let selectedIndex = 0;
3020
+ let menuItems = [];
3021
+ let inSubMode = false;
3022
+ let currentSubMenu = 'main'; // 'main' or 'agents'
3023
+
3024
+ // Colors & formatting helper
3025
+ const ANSI = {
3026
+ reset: '\x1b[0m',
3027
+ bold: '\x1b[1m',
3028
+ dim: '\x1b[2m',
3029
+ italic: '\x1b[3m',
3030
+ red: '\x1b[31m',
3031
+ green: '\x1b[32m',
3032
+ yellow: '\x1b[33m',
3033
+ blue: '\x1b[34m',
3034
+ magenta: '\x1b[35m',
3035
+ cyan: '\x1b[36m',
3036
+ white: '\x1b[37m',
3037
+ clearScreen: '\x1b[2J\x1b[H',
3038
+ hideCursor: '\x1b[?25l',
3039
+ showCursor: '\x1b[?25h'
3040
+ };
3023
3041
 
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);
3042
+ function buildMenuItems() {
3043
+ const state = detectProjectState(cwd);
3044
+
3045
+ if (currentSubMenu === 'agents') {
3046
+ const agentItems = [
3047
+ { id: 'agent_list', name: 'List Agents', desc: 'List all 19 role-based agents' },
3048
+ { id: 'agent_inspect', name: 'Inspect Agent', desc: 'Show agent configuration and skills' },
3049
+ { id: 'agent_run', name: 'Run Agent', desc: 'Execute a single agent manually' },
3050
+ { id: 'back', name: '← Back to Main', desc: 'Return to the main control center' }
3051
+ ];
3052
+ return agentItems;
3053
+ }
3054
+
3055
+ const items = [];
3056
+
3057
+ // Step 1: Init
3058
+ if (!state.hasConfig) {
3059
+ items.push({ id: 'init', name: 'Initialize Project', desc: 'āš ļø Setup engines & config (Required)', highlight: true });
3060
+ } else {
3061
+ items.push({ id: 'init', name: 'Reinitialize Project', desc: 'Scan engines & reconfigure model routing' });
3062
+ }
3063
+
3064
+ // Step 2: Requirements
3065
+ if (state.hasConfig && !state.hasStart) {
3066
+ items.push({ id: 'write', name: 'Write Requirements', desc: 'āš ļø Edit docs/start.md (Required)', highlight: true });
3067
+ } else if (state.hasConfig) {
3068
+ items.push({ id: 'write', name: 'Write Requirements', desc: 'Edit docs/start.md requirements' });
3069
+ }
3070
+
3071
+ // Step 3: Plan & Brainstorm
3072
+ if (state.hasStart && !state.hasBrainstorm) {
3073
+ items.push({ id: 'plan', name: 'Plan (Generate Brainstorm)', desc: 'šŸ‘‰ Generate docs/brainstorming.md', highlight: true });
3074
+ } else if (state.hasBrainstorm && !state.hasSprints) {
3075
+ items.push({ id: 'plan', name: 'Plan (Generate Sprints)', desc: 'šŸ‘‰ Generate sprint folders & tasks', highlight: true });
3076
+ } else if (state.hasSprints) {
3077
+ items.push({ id: 'plan', name: 'Re-Plan Project', desc: 'Regenerate sprint plans (deletes current plans)' });
3078
+ }
3079
+
3080
+ // Step 4: Execution
3081
+ if (state.hasSprints) {
3082
+ items.push({ id: 'execute', name: `Execute Sprint ${state.currentSprint}`, desc: 'šŸ‘‰ Run planned tasks for active sprint', highlight: true });
3083
+ }
3084
+
3085
+ // Step 5: Test
3086
+ if (state.hasSprints) {
3087
+ items.push({ id: 'test', name: 'Run E2E & Audits', desc: 'Execute final testing/security/SEO sprint' });
3088
+ }
3089
+
3090
+ // Step 6: Codebase Upgrades & Modifications
3091
+ items.push({ id: 'modify', name: 'Modify Code In-Place', desc: 'Run quick, direct codebase modification' });
3092
+ items.push({ id: 'feature', name: 'Add New Feature', desc: 'Plan a mini-sprint for a new feature' });
3093
+
3094
+ // Step 7: Scanners & Imports
3095
+ items.push({ id: 'scan', name: 'Scan Project Stack', desc: 'Analyze tech stack in current/target directory' });
3096
+ items.push({ id: 'import', name: 'Import Existing Codebase', desc: 'Import external project folder' });
3097
+
3098
+ // Step 8: Agents Manager
3099
+ items.push({ id: 'agents_menu', name: 'Agent Manager →', desc: 'Inspect or run generic role-based agents' });
3100
+
3101
+ // Step 9: Help & Exit
3102
+ items.push({ id: 'commands', name: '/commands Reference', desc: 'Show formatted table of CLI commands' });
3103
+ items.push({ id: 'exit', name: 'Exit TUI', desc: 'Quit interactive control center' });
3104
+
3105
+ return items;
3029
3106
  }
3030
3107
 
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);
3108
+ function detectProjectState(dir) {
3109
+ const hasConfig = fs.existsSync(path.join(dir, CONFIG_FILE));
3110
+ const hasStart = fs.existsSync(path.join(dir, START_MD));
3111
+ const hasBrainstorm = fs.existsSync(path.join(dir, BRAINSTORM_MD));
3112
+ const hasSprints = detectSprintDocs(dir);
3113
+
3114
+ let currentSprint = 1;
3115
+ if (hasSprints) {
3116
+ currentSprint = detectCurrentSprint(dir);
3043
3117
  }
3118
+
3119
+ return {
3120
+ hasConfig,
3121
+ hasStart,
3122
+ hasBrainstorm,
3123
+ hasSprints,
3124
+ currentSprint
3125
+ };
3044
3126
  }
3045
3127
 
3046
- // 3. Inject customized views, routes, models, migrations and controllers
3047
- setupLaravelGui(guiPath);
3128
+ function getProjectStatus(state) {
3129
+ if (!state.hasConfig) return `${ANSI.red}ā— Uninitialized (Run Initialize Project)${ANSI.reset}`;
3130
+ if (!state.hasStart) return `${ANSI.yellow}ā— Missing docs/start.md (Run Write Requirements)${ANSI.reset}`;
3131
+ if (!state.hasBrainstorm) return `${ANSI.cyan}ā— Brainstorming Ready (Run Plan)${ANSI.reset}`;
3132
+ if (!state.hasSprints) return `${ANSI.cyan}ā— Sprint Planning Ready (Run Plan)${ANSI.reset}`;
3133
+ return `${ANSI.green}ā— Sprint ${state.currentSprint} Active${ANSI.reset}`;
3134
+ }
3048
3135
 
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
3136
+ function getEngineConfig() {
3137
+ try {
3138
+ const config = loadLocalConfig(cwd);
3139
+ if (config && config.primary_engine) {
3140
+ return `${config.primary_engine} (${config.default_model || 'auto'})`;
3141
+ }
3142
+ } catch {}
3143
+ return 'None';
3058
3144
  }
3059
3145
 
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);
3146
+ function renderMenu() {
3147
+ menuItems = buildMenuItems();
3148
+ // Clamp selection index
3149
+ if (selectedIndex >= menuItems.length) {
3150
+ selectedIndex = Math.max(0, menuItems.length - 1);
3151
+ }
3152
+
3153
+ const state = detectProjectState(cwd);
3154
+ const projectName = path.basename(cwd);
3155
+ const statusStr = getProjectStatus(state);
3156
+ const engineStr = getEngineConfig();
3157
+
3158
+ process.stdout.write(ANSI.clearScreen);
3159
+ process.stdout.write(ANSI.hideCursor);
3160
+
3161
+ const width = 76;
3162
+
3163
+ // Header Banner
3164
+ console.log(ANSI.cyan + ` ā”Œ${'─'.repeat(width - 4)}┐` + ANSI.reset);
3165
+ console.log(ANSI.cyan + ` │` + ANSI.bold + centerText(`${PLATFORM_NAME} Control Center`, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3166
+ console.log(ANSI.cyan + ` │` + ANSI.dim + centerText(PLATFORM_FULL, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3167
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(width - 4)}┤` + ANSI.reset);
3168
+
3169
+ // Project Info
3170
+ const infoLeft = ` Project: ${projectName}`;
3171
+ const infoRight = `Path: ${cwd.length > 35 ? '...' + cwd.slice(-32) : cwd} `;
3172
+ const paddingInfo = width - 4 - infoLeft.length - infoRight.length;
3173
+ console.log(ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + infoLeft + ' '.repeat(paddingInfo) + ANSI.dim + infoRight + ANSI.cyan + `│` + ANSI.reset);
3174
+
3175
+ const statusLine = ` Status: ${statusStr}`;
3176
+ const engineLine = `Engine: ${engineStr} `;
3177
+ const statusClean = statusLine.replace(/\x1b\[[0-9;]*m/g, '');
3178
+ const engineClean = engineLine.replace(/\x1b\[[0-9;]*m/g, '');
3179
+ const paddingStatus = width - 4 - statusClean.length - engineClean.length;
3180
+ console.log(ANSI.cyan + ` │` + ANSI.reset + statusLine + ' '.repeat(paddingStatus) + ANSI.dim + engineLine + ANSI.cyan + `│` + ANSI.reset);
3181
+
3182
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(width - 4)}┤` + ANSI.reset);
3183
+
3184
+ // Menu Items
3185
+ menuItems.forEach((item, index) => {
3186
+ const isSelected = index === selectedIndex;
3187
+ const numStr = ` [${index + 1}] `;
3188
+ const nameStr = item.name;
3189
+
3190
+ let lineText = '';
3191
+ if (isSelected) {
3192
+ lineText = `${ANSI.bold}${ANSI.cyan}ā–ø${ANSI.reset}${ANSI.bold}${numStr}${nameStr}${ANSI.reset}`;
3193
+ } else {
3194
+ lineText = ` ${numStr}${nameStr}`;
3195
+ }
3196
+
3197
+ // Add highlight if item is next action recommended
3198
+ if (item.highlight && !isSelected) {
3199
+ lineText = `${ANSI.yellow}${lineText}${ANSI.reset}`;
3200
+ }
3201
+
3202
+ const cleanLine = lineText.replace(/\x1b\[[0-9;]*m/g, '');
3203
+ const padding = 34 - cleanLine.length;
3204
+
3205
+ let descPart = '';
3206
+ if (item.desc) {
3207
+ descPart = `${ANSI.dim} ${item.desc}${ANSI.reset}`;
3208
+ }
3209
+
3210
+ const cleanDesc = descPart.replace(/\x1b\[[0-9;]*m/g, '');
3211
+ const lineEndPadding = width - 4 - cleanLine.length - Math.max(0, padding) - cleanDesc.length;
3212
+
3213
+ console.log(
3214
+ ANSI.cyan + ` │ ` + ANSI.reset +
3215
+ lineText + ' '.repeat(Math.max(0, padding)) +
3216
+ descPart + ' '.repeat(Math.max(0, lineEndPadding - 1)) +
3217
+ ANSI.cyan + `│` + ANSI.reset
3218
+ );
3219
+ });
3220
+
3221
+ console.log(ANSI.cyan + ` ā””${'─'.repeat(width - 4)}ā”˜` + ANSI.reset);
3222
+ console.log(ANSI.dim + ` ↑↓ Navigate āŽ Select [1-${menuItems.length}] Shortcut q Quit` + ANSI.reset);
3069
3223
  }
3070
3224
 
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);
3225
+ function centerText(text, width) {
3226
+ const cleanText = text.replace(/\x1b\[[0-9;]*m/g, '');
3227
+ const padding = Math.max(0, Math.floor((width - cleanText.length) / 2));
3228
+ return ' '.repeat(padding) + text + ' '.repeat(width - cleanText.length - padding);
3076
3229
  }
3077
- }
3078
3230
 
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, '');
3231
+ // Set up standard input listener
3232
+ const isRaw = process.stdin.isTTY;
3233
+ if (isRaw) {
3234
+ process.stdin.setRawMode(true);
3086
3235
  }
3236
+ process.stdin.resume();
3237
+ process.stdin.setEncoding('utf8');
3238
+
3239
+ renderMenu();
3240
+
3241
+ const handleKeypress = async (key) => {
3242
+ if (inSubMode) return;
3243
+
3244
+ // Graceful exit keys
3245
+ if (key === '\u0003' || key === 'q' || key === 'Q') {
3246
+ cleanupAndExit();
3247
+ return;
3248
+ }
3087
3249
 
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');
3250
+ if (key === '\u001b[A') {
3251
+ // Up arrow
3252
+ selectedIndex = (selectedIndex - 1 + menuItems.length) % menuItems.length;
3253
+ renderMenu();
3254
+ } else if (key === '\u001b[B') {
3255
+ // Down arrow
3256
+ selectedIndex = (selectedIndex + 1) % menuItems.length;
3257
+ renderMenu();
3258
+ } else if (key === '\r' || key === '\n') {
3259
+ // Enter key
3260
+ await handleAction(menuItems[selectedIndex]);
3261
+ } else {
3262
+ // Direct number shortcuts
3263
+ const code = key.charCodeAt(0);
3264
+ if (code >= 49 && code <= 57) { // '1'-'9'
3265
+ const index = code - 49;
3266
+ if (index < menuItems.length) {
3267
+ selectedIndex = index;
3268
+ renderMenu();
3269
+ await handleAction(menuItems[index]);
3270
+ }
3271
+ } else if (key === '0') {
3272
+ const index = 9; // '0' is 10th item
3273
+ if (index < menuItems.length) {
3274
+ selectedIndex = index;
3275
+ renderMenu();
3276
+ await handleAction(menuItems[index]);
3277
+ }
3278
+ }
3279
+ }
3280
+ };
3281
+
3282
+ process.stdin.on('data', handleKeypress);
3283
+
3284
+ function cleanupAndExit() {
3285
+ if (isRaw) {
3286
+ process.stdin.setRawMode(false);
3287
+ }
3288
+ process.stdin.pause();
3289
+ process.stdout.write(ANSI.showCursor);
3290
+ console.log('\nšŸ•Œ Thank you for using IMH-Code. Goodbye!\n');
3291
+ process.exit(0);
3100
3292
  }
3101
3293
 
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'));
3294
+ async function pauseAndResume() {
3295
+ inSubMode = true;
3296
+ if (isRaw) {
3297
+ process.stdin.setRawMode(false);
3128
3298
  }
3299
+ process.stdin.pause();
3300
+ process.stdout.write(ANSI.showCursor + '\n');
3301
+
3302
+ // Return a promise that resolves when user presses enter
3303
+ return new Promise((resolve) => {
3304
+ const rl = readline.createInterface({
3305
+ input: process.stdin,
3306
+ output: process.stdout
3307
+ });
3308
+ rl.question(ANSI.cyan + '\nPress [Enter] to return to the main menu...' + ANSI.reset, () => {
3309
+ rl.close();
3310
+ inSubMode = false;
3311
+ if (isRaw) {
3312
+ process.stdin.setRawMode(true);
3313
+ }
3314
+ process.stdin.resume();
3315
+ renderMenu();
3316
+ resolve();
3317
+ });
3318
+ });
3319
+ }
3129
3320
 
3130
- public function create(Request $request) {
3131
- $name = $request->input('name');
3132
- $path = $request->input('path');
3321
+ async function handleAction(item) {
3322
+ const state = detectProjectState(cwd);
3323
+
3324
+ switch (item.id) {
3325
+ case 'init':
3326
+ inSubMode = true;
3327
+ if (isRaw) process.stdin.setRawMode(false);
3328
+ process.stdin.pause();
3329
+ process.stdout.write(ANSI.showCursor + '\n');
3133
3330
 
3134
- DB::table('projects')->insert([
3135
- 'name' => $name,
3136
- 'path' => $path,
3137
- 'created_at' => now(),
3138
- 'updated_at' => now(),
3139
- ]);
3331
+ try {
3332
+ await runInit();
3333
+ } catch (err) {
3334
+ console.error(`āŒ Initialization failed: ${err.message ?? err}`);
3335
+ }
3336
+
3337
+ inSubMode = false;
3338
+ if (isRaw) process.stdin.setRawMode(true);
3339
+ process.stdin.resume();
3340
+ renderMenu();
3341
+ break;
3140
3342
 
3141
- // Run imhcode init
3142
- Process::path($path)->run('imhcode');
3343
+ case 'write':
3344
+ inSubMode = true;
3345
+ if (isRaw) process.stdin.setRawMode(false);
3346
+ process.stdin.pause();
3347
+ process.stdout.write(ANSI.showCursor + '\n');
3348
+
3349
+ // Let user choose how they want to edit
3350
+ console.log('Requirements Editor Options:');
3351
+ console.log(' [1] Open docs/start.md in default terminal editor (nano, vi, notepad)');
3352
+ console.log(' [2] Open docs/start.md in VS Code (code)');
3353
+ console.log(' [3] Edit project description directly in terminal');
3354
+
3355
+ const rlEdit = readline.createInterface({ input: process.stdin, output: process.stdout });
3356
+ const choice = await new Promise(res => rlEdit.question('\nSelect option [1-3] (default: 1): ', res));
3357
+ rlEdit.close();
3143
3358
 
3144
- return redirect()->route('dashboard');
3145
- }
3359
+ const startMdPath = path.join(cwd, START_MD);
3360
+
3361
+ if (choice === '2') {
3362
+ try {
3363
+ execSync(`code "${startMdPath}"`, { stdio: 'inherit' });
3364
+ console.log('šŸ“‚ Opened docs/start.md in VS Code.');
3365
+ } catch {
3366
+ console.log('āš ļø Failed to launch VS Code ("code" CLI not in path). Fallback to standard editor.');
3367
+ const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3368
+ execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3369
+ }
3370
+ } else if (choice === '3') {
3371
+ console.log('\nEnter your new project description below (press Enter on empty line to finish):\n');
3372
+ const lines = [];
3373
+ const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
3374
+ while (true) {
3375
+ const line = await new Promise(res => rlInput.question('> ', res));
3376
+ if (line.trim() === '') break;
3377
+ lines.push(line);
3378
+ }
3379
+ rlInput.close();
3380
+
3381
+ if (lines.length > 0) {
3382
+ // Write to docs/start.md within markers
3383
+ let startContent = '';
3384
+ if (fs.existsSync(startMdPath)) {
3385
+ startContent = fs.readFileSync(startMdPath, 'utf8');
3386
+ }
3387
+
3388
+ const desc = lines.join('\n');
3389
+ const markerStart = '<!-- WRITE_PROMPT_HERE -->';
3390
+ const markerEnd = '<!-- END_PROMPT -->';
3391
+
3392
+ if (startContent.includes(markerStart) && startContent.includes(markerEnd)) {
3393
+ const before = startContent.split(markerStart)[0];
3394
+ const after = startContent.split(markerEnd)[1];
3395
+ const newContent = `${before}${markerStart}\n${desc}\n${markerEnd}${after}`;
3396
+ fs.writeFileSync(startMdPath, newContent, 'utf8');
3397
+ } else {
3398
+ // Write a simple template with markers
3399
+ const newContent = `# šŸ•Œ IMH-Code — Project Start\n\n${markerStart}\n${desc}\n${markerEnd}\n`;
3400
+ fs.writeFileSync(startMdPath, newContent, 'utf8');
3401
+ }
3402
+ console.log('āœ… Requirements updated successfully in docs/start.md!');
3403
+ }
3404
+ } else {
3405
+ const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3406
+ try {
3407
+ execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3408
+ } catch (err) {
3409
+ console.error(`āŒ Failed to launch editor: ${err.message}`);
3410
+ }
3411
+ }
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
+ inSubMode = false;
3414
+ if (isRaw) process.stdin.setRawMode(true);
3415
+ process.stdin.resume();
3416
+ renderMenu();
3417
+ break;
3418
+
3419
+ case 'plan':
3420
+ inSubMode = true;
3421
+ if (isRaw) process.stdin.setRawMode(false);
3422
+ process.stdin.pause();
3423
+ process.stdout.write(ANSI.showCursor + '\n');
3424
+
3425
+ // If sprints already exist, confirm deletion first
3426
+ let proceed = true;
3427
+ if (state.hasSprints) {
3428
+ const rlConfirm = readline.createInterface({ input: process.stdin, output: process.stdout });
3429
+ 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));
3430
+ rlConfirm.close();
3431
+ if (confirmAns.toLowerCase() !== 'y') {
3432
+ proceed = false;
3433
+ } else {
3434
+ // Delete brainstorming.md and docs/sprint-*
3435
+ try {
3436
+ if (fs.existsSync(path.join(cwd, BRAINSTORM_MD))) fs.unlinkSync(path.join(cwd, BRAINSTORM_MD));
3437
+ let sNum = 1;
3438
+ while (true) {
3439
+ const sDir = path.join(cwd, DOCS_DIR, `sprint-${sNum}`);
3440
+ if (fs.existsSync(sDir)) {
3441
+ rmRecursiveSync(sDir);
3442
+ sNum++;
3443
+ } else {
3444
+ break;
3162
3445
  }
3446
+ }
3447
+ } catch (e) {
3448
+ console.warn('āš ļø Could not clean up old plan files: ', e.message);
3163
3449
  }
3450
+ }
3451
+ }
3452
+
3453
+ if (proceed) {
3454
+ try {
3455
+ await runPlanCommand([]);
3456
+ } catch (err) {
3457
+ console.error(`āŒ Planning failed: ${err.message ?? err}`);
3458
+ }
3459
+ }
3460
+
3461
+ await pauseAndResume();
3462
+ break;
3463
+
3464
+ case 'execute':
3465
+ inSubMode = true;
3466
+ if (isRaw) process.stdin.setRawMode(false);
3467
+ process.stdin.pause();
3468
+ process.stdout.write(ANSI.showCursor + '\n');
3469
+
3470
+ try {
3471
+ await runExecuteCommand([state.currentSprint.toString()]);
3472
+ } catch (err) {
3473
+ console.error(`āŒ Execution failed: ${err.message ?? err}`);
3164
3474
  }
3475
+
3476
+ await pauseAndResume();
3477
+ break;
3165
3478
 
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
- }
3479
+ case 'test':
3480
+ inSubMode = true;
3481
+ if (isRaw) process.stdin.setRawMode(false);
3482
+ process.stdin.pause();
3483
+ process.stdout.write(ANSI.showCursor + '\n');
3484
+
3485
+ try {
3486
+ await runTestCommand([]);
3487
+ } catch (err) {
3488
+ console.error(`āŒ Testing failed: ${err.message ?? err}`);
3174
3489
  }
3490
+
3491
+ await pauseAndResume();
3492
+ break;
3175
3493
 
3176
- return view('project', compact('project', 'sprints', 'logs'));
3177
- }
3494
+ case 'modify':
3495
+ inSubMode = true;
3496
+ if (isRaw) process.stdin.setRawMode(false);
3497
+ process.stdin.pause();
3498
+ process.stdout.write(ANSI.showCursor + '\n');
3178
3499
 
3179
- public function execute($id, Request $request) {
3180
- $project = DB::table('projects')->where('id', $id)->first();
3181
- $sprint = $request->input('sprint');
3500
+ const rlMod = readline.createInterface({ input: process.stdin, output: process.stdout });
3501
+ const modDesc = await new Promise(res => rlMod.question('Enter description of the modification: ', res));
3502
+ rlMod.close();
3503
+
3504
+ if (modDesc.trim() !== '') {
3505
+ try {
3506
+ await runModifyCommand([modDesc, '--live']);
3507
+ } catch (err) {
3508
+ console.error(`āŒ Modification failed: ${err.message ?? err}`);
3509
+ }
3510
+ }
3182
3511
 
3183
- $result = Process::path($project->path)
3184
- ->timeout(600)
3185
- ->run("imhcode execute {$sprint}");
3512
+ await pauseAndResume();
3513
+ break;
3186
3514
 
3187
- return back()->with('output', $result->output());
3188
- }
3515
+ case 'feature':
3516
+ inSubMode = true;
3517
+ if (isRaw) process.stdin.setRawMode(false);
3518
+ process.stdin.pause();
3519
+ process.stdout.write(ANSI.showCursor + '\n');
3520
+
3521
+ const rlFeat = readline.createInterface({ input: process.stdin, output: process.stdout });
3522
+ const featDesc = await new Promise(res => rlFeat.question('Enter description of the feature to plan: ', res));
3523
+ rlFeat.close();
3189
3524
 
3190
- public function modify($id, Request $request) {
3191
- $project = DB::table('projects')->where('id', $id)->first();
3192
- $desc = $request->input('description');
3525
+ if (featDesc.trim() !== '') {
3526
+ try {
3527
+ await runFeatureCommand([featDesc]);
3528
+ } catch (err) {
3529
+ console.error(`āŒ Feature planning failed: ${err.message ?? err}`);
3530
+ }
3531
+ }
3193
3532
 
3194
- $result = Process::path($project->path)
3195
- ->timeout(600)
3196
- ->run("imhcode modify \\"{$desc}\\" --live");
3533
+ await pauseAndResume();
3534
+ break;
3197
3535
 
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
- });
3536
+ case 'scan':
3537
+ inSubMode = true;
3538
+ if (isRaw) process.stdin.setRawMode(false);
3539
+ process.stdin.pause();
3540
+ process.stdout.write(ANSI.showCursor + '\n');
3541
+
3542
+ const rlScan = readline.createInterface({ input: process.stdin, output: process.stdout });
3543
+ const scanPath = await new Promise(res => rlScan.question('Enter directory path to scan (default: .): ', res));
3544
+ rlScan.close();
3545
+
3546
+ try {
3547
+ await runScanCommand([scanPath.trim() || '.']);
3548
+ } catch (err) {
3549
+ console.error(`āŒ Scan failed: ${err.message ?? err}`);
3239
3550
  }
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
- }
3551
+
3552
+ await pauseAndResume();
3553
+ break;
3554
+
3555
+ case 'import':
3556
+ inSubMode = true;
3557
+ if (isRaw) process.stdin.setRawMode(false);
3558
+ process.stdin.pause();
3559
+ process.stdout.write(ANSI.showCursor + '\n');
3560
+
3561
+ const rlImport = readline.createInterface({ input: process.stdin, output: process.stdout });
3562
+ const importPath = await new Promise(res => rlImport.question('Enter directory path to import: ', res));
3563
+ rlImport.close();
3564
+
3565
+ if (importPath.trim() !== '') {
3566
+ try {
3567
+ await runImportCommand([importPath.trim()]);
3568
+ } catch (err) {
3569
+ console.error(`āŒ Import failed: ${err.message ?? err}`);
3570
+ }
3273
3571
  }
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
- }
3572
+
3573
+ await pauseAndResume();
3574
+ break;
3575
+
3576
+ case 'agents_menu':
3577
+ currentSubMenu = 'agents';
3578
+ selectedIndex = 0;
3579
+ renderMenu();
3580
+ break;
3581
+
3582
+ case 'agent_list':
3583
+ inSubMode = true;
3584
+ if (isRaw) process.stdin.setRawMode(false);
3585
+ process.stdin.pause();
3586
+ process.stdout.write(ANSI.showCursor + '\n');
3587
+
3588
+ try {
3589
+ await runAgentCommand('list', []);
3590
+ } catch (err) {
3591
+ console.error(`āŒ Listing failed: ${err.message ?? err}`);
3383
3592
  }
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
- );
3593
+
3594
+ await pauseAndResume();
3595
+ break;
3596
+
3597
+ case 'agent_inspect':
3598
+ inSubMode = true;
3599
+ if (isRaw) process.stdin.setRawMode(false);
3600
+ process.stdin.pause();
3601
+ process.stdout.write(ANSI.showCursor + '\n');
3602
+
3603
+ console.log('Available Agents:');
3604
+ console.log(' planner, designer, nextjs-executor, react-executor, vue-executor,');
3605
+ console.log(' laravel-executor, python-executor, java-executor, flutter-executor,');
3606
+ console.log(' react-native-executor, ios-executor, android-executor, systems-executor,');
3607
+ console.log(' web3-executor, devops-executor, tester, security-auditor, seo-optimizer, debugger\n');
3608
+
3609
+ const rlInspect = readline.createInterface({ input: process.stdin, output: process.stdout });
3610
+ const inspectAgentId = await new Promise(res => rlInspect.question('Enter Agent ID to inspect: ', res));
3611
+ rlInspect.close();
3612
+
3613
+ if (inspectAgentId.trim() !== '') {
3614
+ try {
3615
+ await runAgentCommand('inspect', [inspectAgentId.trim()]);
3616
+ } catch (err) {
3617
+ console.error(`āŒ Agent inspection failed: ${err.message ?? err}`);
3618
+ }
3619
+ }
3620
+
3621
+ await pauseAndResume();
3622
+ break;
3623
+
3624
+ case 'agent_run':
3625
+ inSubMode = true;
3626
+ if (isRaw) process.stdin.setRawMode(false);
3627
+ process.stdin.pause();
3628
+ process.stdout.write(ANSI.showCursor + '\n');
3629
+
3630
+ console.log('Available Agents:');
3631
+ console.log(' planner, designer, nextjs-executor, react-executor, vue-executor,');
3632
+ console.log(' laravel-executor, python-executor, java-executor, flutter-executor,');
3633
+ console.log(' react-native-executor, ios-executor, android-executor, systems-executor,');
3634
+ console.log(' web3-executor, devops-executor, tester, security-auditor, seo-optimizer, debugger\n');
3635
+
3636
+ const rlRunAgent = readline.createInterface({ input: process.stdin, output: process.stdout });
3637
+ const runAgentId = await new Promise(res => rlRunAgent.question('Enter Agent ID to run: ', res));
3638
+ const runTask = await new Promise(res => rlRunAgent.question('Enter task description: ', res));
3639
+ rlRunAgent.close();
3640
+
3641
+ if (runAgentId.trim() !== '' && runTask.trim() !== '') {
3642
+ try {
3643
+ await runAgentCommand('run', [runAgentId.trim(), runTask.trim(), '--live']);
3644
+ } catch (err) {
3645
+ console.error(`āŒ Agent execution failed: ${err.message ?? err}`);
3646
+ }
3647
+ }
3648
+
3649
+ await pauseAndResume();
3650
+ break;
3651
+
3652
+ case 'back':
3653
+ currentSubMenu = 'main';
3654
+ selectedIndex = 0;
3655
+ renderMenu();
3656
+ break;
3657
+
3658
+ case 'commands':
3659
+ inSubMode = true;
3660
+ if (isRaw) process.stdin.setRawMode(false);
3661
+ process.stdin.pause();
3662
+ process.stdout.write(ANSI.showCursor + '\n');
3663
+
3664
+ showCommandsTable();
3665
+
3666
+ await pauseAndResume();
3667
+ break;
3668
+
3669
+ case 'exit':
3670
+ cleanupAndExit();
3671
+ break;
3496
3672
  }
3497
- }
3498
- `;
3499
- fs.writeFileSync(path.join(consoleDir, 'RegisterProject.php'), commandContent, 'utf8');
3673
+ }
3500
3674
 
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
3675
+ function showCommandsTable() {
3676
+ const width = 76;
3677
+ console.log(ANSI.cyan + ` ā”Œ${'─'.repeat(width - 4)}┐` + ANSI.reset);
3678
+ console.log(ANSI.cyan + ` │` + ANSI.bold + centerText(`šŸ“– CLI Commands Reference`, width - 4) + ANSI.cyan + `│` + ANSI.reset);
3679
+ console.log(ANSI.cyan + ` ā”œ${'─'.repeat(20)}┬${'─'.repeat(width - 25)}┤` + ANSI.reset);
3680
+
3681
+ const cmds = [
3682
+ { cmd: 'imhcode', desc: 'Launch interactive terminal TUI' },
3683
+ { cmd: 'imhcode init', desc: 'Non-interactive environment setup' },
3684
+ { cmd: 'imhcode plan', desc: 'Generate brainstorm or sprint plans' },
3685
+ { cmd: 'imhcode execute N', desc: 'Execute Sprint N tasks' },
3686
+ { cmd: 'imhcode test', desc: 'Run testing/security/SEO sprint' },
3687
+ { cmd: 'imhcode modify', desc: 'Modify codebase in-place' },
3688
+ { cmd: 'imhcode feature', desc: 'Plan a mini-sprint for new feature' },
3689
+ { cmd: 'imhcode fix', desc: 'Quick targeted bug fix' },
3690
+ { cmd: 'imhcode scan', desc: 'Scan current folder for frameworks' },
3691
+ { cmd: 'imhcode import', desc: 'Import existing codebase' },
3692
+ { cmd: 'imhcode agent list',desc: 'List all 19 generic agents' },
3693
+ { cmd: 'imhcode report', desc: 'Generate final project team report' },
3694
+ { cmd: '--help / -h', desc: 'Display general help instructions' }
3695
+ ];
3696
+
3697
+ cmds.forEach(item => {
3698
+ const col1 = ` ${item.cmd}`;
3699
+ const col2 = ` ${item.desc}`;
3700
+ const pad1 = 18 - col1.length;
3701
+ const pad2 = width - 25 - col2.length;
3702
+ console.log(
3703
+ ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + col1 + ' '.repeat(Math.max(0, pad1)) +
3704
+ ANSI.cyan + `│` + ANSI.reset + col2 + ' '.repeat(Math.max(0, pad2)) +
3705
+ ANSI.cyan + `│` + ANSI.reset
3706
+ );
3707
+ });
3708
+
3709
+ console.log(ANSI.cyan + ` ā””${'─'.repeat(20)}┓${'─'.repeat(width - 25)}ā”˜` + ANSI.reset);
3506
3710
  }
3507
3711
  }
3508
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imhcode",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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": {