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 +2 -2
- package/USER_MANUAL.md +6 -5
- package/bin/imhcode.js +663 -460
- package/package.json +1 -1
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
|
|
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
|
-
- **
|
|
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
|
|
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.
|
|
95
|
+
## 7. Interactive Terminal Control Center (TUI)
|
|
96
96
|
|
|
97
|
-
Run `imhcode gui` to launch a beautiful
|
|
98
|
-
-
|
|
99
|
-
-
|
|
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
|
-
|
|
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:
|
|
128
|
-
|
|
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
|
|
3010
|
-
const
|
|
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
|
-
//
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
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
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
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
|
-
|
|
3047
|
-
|
|
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
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
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
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
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
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
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
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
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
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
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
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
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
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
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
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
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
|
-
|
|
3142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
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
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
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
|
-
|
|
3184
|
-
|
|
3185
|
-
->run("imhcode execute {$sprint}");
|
|
3512
|
+
await pauseAndResume();
|
|
3513
|
+
break;
|
|
3186
3514
|
|
|
3187
|
-
|
|
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
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
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
|
-
|
|
3195
|
-
|
|
3196
|
-
->run("imhcode modify \\"{$desc}\\" --live");
|
|
3533
|
+
await pauseAndResume();
|
|
3534
|
+
break;
|
|
3197
3535
|
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
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
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
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
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
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
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
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
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
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.
|
|
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": {
|