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