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