ai-control-center 1.15.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/LICENSE +21 -0
- package/README.md +584 -0
- package/bin/aicc.js +772 -0
- package/lib/actions/approve.js +71 -0
- package/lib/actions/assign-project.js +132 -0
- package/lib/actions/browser-test.js +64 -0
- package/lib/actions/cleanup.js +174 -0
- package/lib/actions/debug.js +298 -0
- package/lib/actions/deploy.js +1229 -0
- package/lib/actions/fix-bug.js +134 -0
- package/lib/actions/new-feature.js +255 -0
- package/lib/actions/reject.js +307 -0
- package/lib/actions/review.js +706 -0
- package/lib/actions/status.js +47 -0
- package/lib/agents/browser-qa-agent.js +611 -0
- package/lib/agents/payment-agent.js +116 -0
- package/lib/agents/suggestion-agent.js +88 -0
- package/lib/cli.js +303 -0
- package/lib/config.js +243 -0
- package/lib/hub/hub-server.js +440 -0
- package/lib/hub/project-poller.js +75 -0
- package/lib/hub/skill-registry.js +89 -0
- package/lib/hub/state-aggregator.js +204 -0
- package/lib/index.js +471 -0
- package/lib/init/doctor.js +523 -0
- package/lib/init/presets.js +222 -0
- package/lib/init/skill-fetcher.js +77 -0
- package/lib/init/wizard.js +973 -0
- package/lib/integrations/codex-runner.js +128 -0
- package/lib/integrations/github-actions.js +248 -0
- package/lib/integrations/github-reporter.js +229 -0
- package/lib/integrations/screenshot-store.js +102 -0
- package/lib/openclaw/bridge.js +650 -0
- package/lib/openclaw/generate-skill.js +235 -0
- package/lib/openclaw/openclaw.json +64 -0
- package/lib/orchestrator/autonomous-loop.js +429 -0
- package/lib/orchestrator/thread-triggers.js +63 -0
- package/lib/roleplay/agent-messenger.js +75 -0
- package/lib/roleplay/discussion-threads.js +303 -0
- package/lib/roleplay/health-monitor.js +121 -0
- package/lib/roleplay/pm-agent.js +513 -0
- package/lib/roleplay/roleplay-config.js +25 -0
- package/lib/roleplay/room.js +164 -0
- package/lib/shared/action-runner.js +2330 -0
- package/lib/shared/event-bus.js +185 -0
- package/lib/slack/bot.js +378 -0
- package/lib/telegram/bot.js +416 -0
- package/lib/telegram/commands.js +1267 -0
- package/lib/telegram/keyboards.js +113 -0
- package/lib/telegram/notifications.js +247 -0
- package/lib/twitch/bot.js +354 -0
- package/lib/twitch/commands.js +302 -0
- package/lib/twitch/notifications.js +63 -0
- package/lib/utils/achievements.js +191 -0
- package/lib/utils/activity-log.js +182 -0
- package/lib/utils/agent-leaderboard.js +119 -0
- package/lib/utils/audit-logger.js +232 -0
- package/lib/utils/codebase-context.js +288 -0
- package/lib/utils/codebase-indexer.js +381 -0
- package/lib/utils/config-schema.js +230 -0
- package/lib/utils/context-compressor.js +172 -0
- package/lib/utils/correlation.js +63 -0
- package/lib/utils/cost-tracker.js +423 -0
- package/lib/utils/cron-scheduler.js +53 -0
- package/lib/utils/db-adapter.js +293 -0
- package/lib/utils/display.js +272 -0
- package/lib/utils/errors.js +116 -0
- package/lib/utils/format.js +134 -0
- package/lib/utils/intent-engine.js +464 -0
- package/lib/utils/mcp-client.js +238 -0
- package/lib/utils/model-ab-test.js +164 -0
- package/lib/utils/notify.js +122 -0
- package/lib/utils/persona-loader.js +80 -0
- package/lib/utils/pipeline-lock.js +73 -0
- package/lib/utils/pipeline.js +214 -0
- package/lib/utils/plugin-runner.js +234 -0
- package/lib/utils/rate-limiter.js +84 -0
- package/lib/utils/rbac.js +74 -0
- package/lib/utils/runner.js +1809 -0
- package/lib/utils/security.js +191 -0
- package/lib/utils/self-healer.js +144 -0
- package/lib/utils/skill-loader.js +255 -0
- package/lib/utils/spinner.js +132 -0
- package/lib/utils/stage-queue.js +50 -0
- package/lib/utils/state-machine.js +89 -0
- package/lib/utils/status-bar.js +327 -0
- package/lib/utils/token-estimator.js +101 -0
- package/lib/utils/ux-analyzer.js +101 -0
- package/lib/utils/webhook-emitter.js +83 -0
- package/lib/web/public/css/styles.css +417 -0
- package/lib/web/public/dark-mode.js +44 -0
- package/lib/web/public/hub/kanban.html +206 -0
- package/lib/web/public/index.html +45 -0
- package/lib/web/public/js/app.js +71 -0
- package/lib/web/public/js/ask.js +110 -0
- package/lib/web/public/js/dashboard.js +165 -0
- package/lib/web/public/js/deploy.js +72 -0
- package/lib/web/public/js/feature.js +79 -0
- package/lib/web/public/js/health.js +65 -0
- package/lib/web/public/js/logs.js +93 -0
- package/lib/web/public/js/review.js +123 -0
- package/lib/web/public/js/ws-client.js +82 -0
- package/lib/web/public/office/css/office.css +678 -0
- package/lib/web/public/office/index.html +148 -0
- package/lib/web/public/office/js/achievements-ui.js +117 -0
- package/lib/web/public/office/js/character.js +1056 -0
- package/lib/web/public/office/js/chat-bubbles.js +177 -0
- package/lib/web/public/office/js/cost-overlay.js +123 -0
- package/lib/web/public/office/js/day-night.js +68 -0
- package/lib/web/public/office/js/effects.js +632 -0
- package/lib/web/public/office/js/engine.js +146 -0
- package/lib/web/public/office/js/feature-ticket.js +216 -0
- package/lib/web/public/office/js/hub-client.js +60 -0
- package/lib/web/public/office/js/main.js +1757 -0
- package/lib/web/public/office/js/office-layout.js +1524 -0
- package/lib/web/public/office/js/pathfinding.js +144 -0
- package/lib/web/public/office/js/pixel-sprites.js +1454 -0
- package/lib/web/public/office/js/progress-bars.js +117 -0
- package/lib/web/public/office/js/replay.js +191 -0
- package/lib/web/public/office/js/sound-effects.js +91 -0
- package/lib/web/public/office/js/sprite-renderer.js +211 -0
- package/lib/web/public/office/js/stamina-system.js +89 -0
- package/lib/web/public/office/js/ui.js +107 -0
- package/lib/web/public/onboarding/index.html +243 -0
- package/lib/web/public/timeline/index.html +195 -0
- package/lib/web/routes/api.js +499 -0
- package/lib/web/routes/logs.js +20 -0
- package/lib/web/routes/metrics.js +99 -0
- package/lib/web/server.js +183 -0
- package/lib/web/ws/handler.js +65 -0
- package/package.json +67 -0
- package/templates/agent-architect.md +69 -0
- package/templates/agent-gemini-pm.md +49 -0
- package/templates/agent-gemini-reviewer.md +52 -0
- package/templates/copilot-instructions.md +36 -0
- package/templates/pipelines/mobile.json +27 -0
- package/templates/pipelines/nodejs-api.json +27 -0
- package/templates/pipelines/python.json +27 -0
- package/templates/pipelines/react.json +27 -0
- package/templates/pipelines/salesforce.json +27 -0
- package/templates/role-gemini.md +97 -0
- package/templates/skill-architect.md +114 -0
- package/templates/skill-browser-qa.md +50 -0
- package/templates/skill-bug-from-qa.md +58 -0
- package/templates/skill-chatbot.md +93 -0
- package/templates/skill-implement.md +78 -0
- package/templates/skill-openclaw.md +174 -0
- package/templates/skill-payment.md +110 -0
- package/templates/skill-pm-spec.md +77 -0
- package/templates/skill-requirement-capture.md +97 -0
- package/templates/skill-review.md +108 -0
- package/templates/skill-reviewer-qa.md +44 -0
- package/templates/skill-suggestion.md +45 -0
- package/templates/skill-template.md +142 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { approveAction } from './actions/approve.js';
|
|
5
|
+
import { cleanupAction } from './actions/cleanup.js';
|
|
6
|
+
import { debugAction } from './actions/debug.js';
|
|
7
|
+
import { deployAction } from './actions/deploy.js';
|
|
8
|
+
import { fixBugAction } from './actions/fix-bug.js';
|
|
9
|
+
import { newFeatureAction } from './actions/new-feature.js';
|
|
10
|
+
import { rejectAction } from './actions/reject.js';
|
|
11
|
+
import { reviewAction } from './actions/review.js';
|
|
12
|
+
import { showStatusAction } from './actions/status.js';
|
|
13
|
+
import { initSessionLog } from './utils/activity-log.js';
|
|
14
|
+
import { printHeader } from './utils/display.js';
|
|
15
|
+
import { getStatus, getWorkflowDir, updateStatus } from './utils/pipeline.js';
|
|
16
|
+
import { statusBar } from './utils/status-bar.js';
|
|
17
|
+
import { AILimitError } from './utils/runner.js';
|
|
18
|
+
|
|
19
|
+
// โโโ Tab navigation shim โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
20
|
+
//
|
|
21
|
+
// Intercepts raw stdin data BEFORE readline/inquirer processes it.
|
|
22
|
+
// Converts:
|
|
23
|
+
// Tab (0x09) โ Down arrow (ESC[B)
|
|
24
|
+
// Shift+Tab (ESC[Z) โ Up arrow (ESC[A)
|
|
25
|
+
//
|
|
26
|
+
// This lets Terminus (iPhone) users navigate with Tab/Shift+Tab
|
|
27
|
+
// instead of needing arrow keys.
|
|
28
|
+
|
|
29
|
+
function enableTabNavigation() {
|
|
30
|
+
const _emit = process.stdin.emit.bind(process.stdin);
|
|
31
|
+
|
|
32
|
+
process.stdin.emit = function (event, data, ...rest) {
|
|
33
|
+
if (event === 'data') {
|
|
34
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(String(data));
|
|
35
|
+
|
|
36
|
+
// Tab โ Down arrow
|
|
37
|
+
if (buf.length === 1 && buf[0] === 0x09) {
|
|
38
|
+
return _emit('data', Buffer.from('\x1B[B'), ...rest);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Shift+Tab โ Up arrow
|
|
42
|
+
if (buf.toString() === '\x1B[Z') {
|
|
43
|
+
return _emit('data', Buffer.from('\x1B[A'), ...rest);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return _emit(event, data, ...rest);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
enableTabNavigation();
|
|
52
|
+
statusBar.install(inquirer);
|
|
53
|
+
|
|
54
|
+
// Start a fresh session log file for this run
|
|
55
|
+
initSessionLog(getWorkflowDir());
|
|
56
|
+
|
|
57
|
+
// โโโ Menu item helper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
58
|
+
// num โ dimmed number prefix; user can type to jump (inquirer type-to-search)
|
|
59
|
+
// label โ padded to a fixed width so badges always start in the same column
|
|
60
|
+
// badge โ status hint; shown right after the padded label
|
|
61
|
+
|
|
62
|
+
const LABEL_WIDTH = 26; // fixed label column width (adjust if labels grow)
|
|
63
|
+
|
|
64
|
+
function item(num, label, badge = '', badgeColor = 'dim') {
|
|
65
|
+
const n = chalk.dim(`${num} `);
|
|
66
|
+
const padded = label.padEnd(LABEL_WIDTH);
|
|
67
|
+
const text = badge
|
|
68
|
+
? `${padded} ${chalk[badgeColor](badge)}`
|
|
69
|
+
: padded;
|
|
70
|
+
return n + text;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sep() {
|
|
74
|
+
return new inquirer.Separator(chalk.dim(' ' + 'โ'.repeat(48)));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// โโโ Reliable "press Enter" via inquirer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
78
|
+
|
|
79
|
+
async function pressEnterToContinue() {
|
|
80
|
+
await inquirer.prompt([{
|
|
81
|
+
type: 'input',
|
|
82
|
+
name: '_',
|
|
83
|
+
message: chalk.dim('Back to menu'),
|
|
84
|
+
prefix: chalk.dim('โต'),
|
|
85
|
+
}]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// โโโ Smart Navigation: Suggest Next Step โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
89
|
+
// Uses rawlist so the user can type a number + Enter โ works on mobile (Terminus)
|
|
90
|
+
// without needing arrow keys.
|
|
91
|
+
|
|
92
|
+
async function promptNextStep(status) {
|
|
93
|
+
const { stage } = status;
|
|
94
|
+
|
|
95
|
+
// Build context-aware choices for each pipeline stage
|
|
96
|
+
const contextChoices = [];
|
|
97
|
+
|
|
98
|
+
if (stage === 'implementation_complete') {
|
|
99
|
+
contextChoices.push({ name: `1 ${'Review Code'.padEnd(24)} run Gemini review`, value: 'review' });
|
|
100
|
+
contextChoices.push({ name: `2 ${'Reject / Request Fixes'.padEnd(24)} skip review, dispatch to Copilot`, value: 'reject' });
|
|
101
|
+
} else if (stage === 'rejected') {
|
|
102
|
+
contextChoices.push({ name: `1 ${'Review Code'.padEnd(24)} re-review after fixes`, value: 'review' });
|
|
103
|
+
contextChoices.push({ name: `2 ${'Re-send to Copilot'.padEnd(24)} dispatch existing review`, value: 'reject' });
|
|
104
|
+
} else if (stage === 'review_complete') {
|
|
105
|
+
console.log(chalk.green('\n โ Review complete โ ready for approval'));
|
|
106
|
+
contextChoices.push({ name: `1 ${'Approve Feature'.padEnd(24)} mark approved, then deploy`, value: 'approve' });
|
|
107
|
+
contextChoices.push({ name: `2 ${'Reject / Request Fixes'.padEnd(24)} send blockers to Copilot`, value: 'reject' });
|
|
108
|
+
} else if (stage === 'approved') {
|
|
109
|
+
console.log(chalk.green('\n โ Feature approved โ ready to deploy'));
|
|
110
|
+
contextChoices.push({ name: `1 ${'Deploy to Org'.padEnd(24)} deploy now`, value: 'deploy' });
|
|
111
|
+
} else if (stage === 'arch_complete') {
|
|
112
|
+
contextChoices.push({ name: `1 ${'Review Code'.padEnd(24)} force review (skip Copilot)`, value: 'review' });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// No relevant next action for this stage โ just go back to menu
|
|
116
|
+
if (contextChoices.length === 0) {
|
|
117
|
+
await pressEnterToContinue();
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
contextChoices.push({ name: `S ${'Pipeline Status'.padEnd(24)} view current stage`, value: 'status' });
|
|
122
|
+
contextChoices.push({ name: `M ${'Main Menu'.padEnd(24)} back`, value: 'menu' });
|
|
123
|
+
|
|
124
|
+
console.log(chalk.dim('\n โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
|
|
125
|
+
|
|
126
|
+
const { choice } = await inquirer.prompt([{
|
|
127
|
+
type: 'rawlist',
|
|
128
|
+
name: 'choice',
|
|
129
|
+
message: 'What next?',
|
|
130
|
+
prefix: chalk.cyan('โ'),
|
|
131
|
+
choices: contextChoices,
|
|
132
|
+
}]);
|
|
133
|
+
|
|
134
|
+
return choice === 'menu' ? null : choice;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// โโโ Auto-Pilot Logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
138
|
+
const AUTO_PILOT_MAX_RUNS = 3; // max auto review+fix cycles before handing back to user
|
|
139
|
+
|
|
140
|
+
async function runAutoPilot(status) {
|
|
141
|
+
const { stage } = status;
|
|
142
|
+
const runs = status.auto_review_count || 0;
|
|
143
|
+
|
|
144
|
+
// 1. Stop at Deployment โ notify user and switch to manual
|
|
145
|
+
if (stage === 'review_complete' || stage === 'approved') {
|
|
146
|
+
process.stdout.write('\x07'); // terminal bell
|
|
147
|
+
console.log(chalk.green('\n โ Auto-Pilot: Feature ready โ choose Approve or Deploy below'));
|
|
148
|
+
updateStatus({ pipeline_mode: 'manual', auto_review_count: 0 });
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. Max runs reached โ stop auto-pilot and hand back to user
|
|
153
|
+
if (runs >= AUTO_PILOT_MAX_RUNS) {
|
|
154
|
+
process.stdout.write('\x07'); // terminal bell
|
|
155
|
+
console.log(chalk.yellow(
|
|
156
|
+
`\n โ Auto-Pilot stopped after ${AUTO_PILOT_MAX_RUNS} review attempts โ manual intervention needed.`
|
|
157
|
+
));
|
|
158
|
+
updateStatus({ pipeline_mode: 'manual', auto_review_count: 0 });
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 3. Auto-Review Loop (implementation_complete or rejected โ review โ fix โ loop)
|
|
163
|
+
if (stage === 'implementation_complete' || (stage === 'rejected' && status.next === 'review')) {
|
|
164
|
+
console.log(chalk.cyan(
|
|
165
|
+
`\n โก Auto-Pilot: running review (attempt ${runs + 1} / ${AUTO_PILOT_MAX_RUNS})...`
|
|
166
|
+
));
|
|
167
|
+
updateStatus({ auto_review_count: runs + 1 });
|
|
168
|
+
await reviewAction();
|
|
169
|
+
return 'continue'; // Loop again
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 4. Unrecognised stage โ pause and ask user to intervene
|
|
173
|
+
console.log(chalk.yellow(`\n Auto-Pilot paused: stage "${stage}" requires manual input.`));
|
|
174
|
+
updateStatus({ pipeline_mode: 'manual', auto_review_count: 0 });
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// โโโ Reset / Abandon feature โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
179
|
+
|
|
180
|
+
async function resetFeatureAction(status) {
|
|
181
|
+
const { confirmed } = await inquirer.prompt([{
|
|
182
|
+
type: 'confirm',
|
|
183
|
+
name: 'confirmed',
|
|
184
|
+
message: chalk.red(`Abandon "${status.current_feature}" and reset to idle?`),
|
|
185
|
+
default: false,
|
|
186
|
+
}]);
|
|
187
|
+
|
|
188
|
+
if (!confirmed) {
|
|
189
|
+
console.log(chalk.dim('\n Cancelled.\n'));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
updateStatus({ stage: 'idle', current_feature: null, next: null, pipeline_mode: null });
|
|
194
|
+
console.log(chalk.green('\n โ Feature abandoned โ pipeline reset to idle.\n'));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// โโโ Main loop โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
198
|
+
|
|
199
|
+
async function mainMenu() {
|
|
200
|
+
let nextAction = null;
|
|
201
|
+
|
|
202
|
+
// eslint-disable-next-line no-constant-condition
|
|
203
|
+
while (true) {
|
|
204
|
+
const status = getStatus();
|
|
205
|
+
|
|
206
|
+
// โโโ Auto-Pilot Check โโโ
|
|
207
|
+
if (status.pipeline_mode === 'auto' && !nextAction) {
|
|
208
|
+
const autoResult = await runAutoPilot(status);
|
|
209
|
+
if (autoResult === 'continue') continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!nextAction) {
|
|
213
|
+
printHeader();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const stage = status.stage || 'idle';
|
|
217
|
+
const hasFeature = !!status.current_feature;
|
|
218
|
+
const isReviewReady = stage === 'review_complete';
|
|
219
|
+
|
|
220
|
+
// Stages where Gemini review can run (includes stuck/partial pipeline stages)
|
|
221
|
+
const canReview = hasFeature && [
|
|
222
|
+
'inbox', 'spec_complete',
|
|
223
|
+
'arch_complete', 'implementation_complete', 'rejected', 'review_complete',
|
|
224
|
+
].includes(stage);
|
|
225
|
+
|
|
226
|
+
// Pipeline is active (not yet approved/idle)
|
|
227
|
+
const isPipelineActive = hasFeature && !['approved', 'idle'].includes(stage);
|
|
228
|
+
|
|
229
|
+
const reviewBadge = isReviewReady
|
|
230
|
+
? ['(ready)', 'green']
|
|
231
|
+
: stage === 'rejected'
|
|
232
|
+
? ['(re-review after fixes)', 'yellow']
|
|
233
|
+
: stage === 'inbox' || stage === 'spec_complete'
|
|
234
|
+
? ['(pipeline stuck โ force review)', 'yellow']
|
|
235
|
+
: canReview
|
|
236
|
+
? ['(run after Copilot implements)', 'yellow']
|
|
237
|
+
: ['(no active feature)', 'dim'];
|
|
238
|
+
|
|
239
|
+
const deployBadge = stage === 'approved'
|
|
240
|
+
? ['(ready!)', 'green']
|
|
241
|
+
: isPipelineActive
|
|
242
|
+
? ['(not approved yet)', 'yellow']
|
|
243
|
+
: ['', 'dim'];
|
|
244
|
+
|
|
245
|
+
const autoBadge = status.pipeline_mode === 'auto' ? ['auto ON', 'cyan'] : ['auto off', 'dim'];
|
|
246
|
+
|
|
247
|
+
// โโ Menu choices โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
248
|
+
// Labels padded to LABEL_WIDTH so all badges align in one column.
|
|
249
|
+
// Numbers let mobile users type a digit + Enter to jump directly.
|
|
250
|
+
const choices = [
|
|
251
|
+
{ name: item(1, 'New Feature', 'start the full pipeline (Gemini โ Claude โ Copilot)', 'dim'), value: 'new-feature' },
|
|
252
|
+
{ name: item(2, 'Fix a Bug', 'submit a bug fix through the same pipeline', 'dim'), value: 'fix-bug' },
|
|
253
|
+
|
|
254
|
+
sep(),
|
|
255
|
+
|
|
256
|
+
{
|
|
257
|
+
name: item(3, 'Review Code', ...reviewBadge),
|
|
258
|
+
value: 'review',
|
|
259
|
+
disabled: !canReview && !isReviewReady,
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
sep(),
|
|
263
|
+
|
|
264
|
+
{
|
|
265
|
+
name: isReviewReady
|
|
266
|
+
? item(4, 'Approve Feature', 'mark as approved โ enables Deploy', 'green')
|
|
267
|
+
: item(4, 'Approve Feature', 'requires review_complete', 'dim'),
|
|
268
|
+
value: 'approve',
|
|
269
|
+
disabled: !isReviewReady,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: !hasFeature
|
|
273
|
+
? item(5, 'Reject / Request Fixes', 'no active feature', 'dim')
|
|
274
|
+
: stage === 'rejected'
|
|
275
|
+
? item(5, 'Re-send Fixes to Copilot', 're-dispatch existing review โ Copilot', 'yellow')
|
|
276
|
+
: item(5, 'Reject / Request Fixes',
|
|
277
|
+
isReviewReady || stage === 'arch_complete' || stage === 'implementation_complete'
|
|
278
|
+
? 'dispatch blockers โ Copilot' : 'flag issue', 'red'),
|
|
279
|
+
value: 'reject',
|
|
280
|
+
disabled: !hasFeature,
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
sep(),
|
|
284
|
+
|
|
285
|
+
{ name: item(6, 'Deploy to Org', ...deployBadge), value: 'deploy' },
|
|
286
|
+
{ name: item(7, 'Debug / View Logs', 'Apex logs, tests, org', 'dim'), value: 'debug' },
|
|
287
|
+
|
|
288
|
+
sep(),
|
|
289
|
+
|
|
290
|
+
{ name: item(8, 'Pipeline Status', 'show stage history', 'dim'), value: 'status' },
|
|
291
|
+
{ name: item(9, 'Clean Up Workspace', 'archive workflow files','dim'), value: 'cleanup' },
|
|
292
|
+
|
|
293
|
+
sep(),
|
|
294
|
+
|
|
295
|
+
{ name: item('Q', 'Browser QA', 'run Playwright QA on target website', 'cyan'), value: 'browser-qa' },
|
|
296
|
+
{ name: item('S', 'AI Suggestions', 'generate feature suggestions', 'cyan'), value: 'suggest' },
|
|
297
|
+
{ name: item('P', 'Assign Project', 'assign URL + goal to AI IT dept', 'cyan'), value: 'assign-project' },
|
|
298
|
+
|
|
299
|
+
sep(),
|
|
300
|
+
|
|
301
|
+
{ name: item('A', 'Toggle Auto-Pilot', ...autoBadge), value: 'toggle-auto', disabled: !isPipelineActive },
|
|
302
|
+
{
|
|
303
|
+
name: isPipelineActive
|
|
304
|
+
? item('R', 'Reset / Abandon', status.current_feature || '', 'red')
|
|
305
|
+
: item('R', 'Reset / Abandon', 'no active feature', 'dim'),
|
|
306
|
+
value: 'reset',
|
|
307
|
+
disabled: !isPipelineActive,
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
sep(),
|
|
311
|
+
|
|
312
|
+
{ name: chalk.dim(` 0 ${'Exit'.padEnd(LABEL_WIDTH)}`), value: 'exit' },
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
let action = nextAction;
|
|
316
|
+
|
|
317
|
+
if (!action) {
|
|
318
|
+
// Keyboard hint โ shown once just above the prompt (not in every header)
|
|
319
|
+
console.log(chalk.dim(' Tab / Shift+Tab navigate 1-9 A R jump 0 exit'));
|
|
320
|
+
console.log('');
|
|
321
|
+
|
|
322
|
+
const { action: selected } = await inquirer.prompt([{
|
|
323
|
+
type: 'list',
|
|
324
|
+
name: 'action',
|
|
325
|
+
message: 'What would you like to do?',
|
|
326
|
+
prefix: chalk.cyan('โ'),
|
|
327
|
+
choices,
|
|
328
|
+
pageSize: 20,
|
|
329
|
+
}]);
|
|
330
|
+
action = selected;
|
|
331
|
+
} else {
|
|
332
|
+
nextAction = null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (action === 'exit') {
|
|
336
|
+
console.log(chalk.dim('\n Goodbye.\n'));
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let actionCompleted = false;
|
|
341
|
+
try {
|
|
342
|
+
switch (action) {
|
|
343
|
+
case 'new-feature':
|
|
344
|
+
actionCompleted = await newFeatureAction('feature');
|
|
345
|
+
break;
|
|
346
|
+
case 'fix-bug': actionCompleted = await fixBugAction(); break;
|
|
347
|
+
case 'review': actionCompleted = await reviewAction(); break;
|
|
348
|
+
case 'approve': actionCompleted = await approveAction(); break;
|
|
349
|
+
case 'reject': actionCompleted = await rejectAction(); break;
|
|
350
|
+
case 'deploy': actionCompleted = await deployAction(); break;
|
|
351
|
+
case 'debug': await debugAction(); break;
|
|
352
|
+
case 'status': showStatusAction(); break;
|
|
353
|
+
case 'cleanup': await cleanupAction(false); break;
|
|
354
|
+
case 'reset': await resetFeatureAction(status); break;
|
|
355
|
+
case 'browser-qa': {
|
|
356
|
+
console.log(chalk.cyan('\n ๐ฌ Running Browser QA...\n'));
|
|
357
|
+
try {
|
|
358
|
+
const { browserTestAction } = await import('./actions/browser-test.js');
|
|
359
|
+
const qaResult = await browserTestAction();
|
|
360
|
+
if (qaResult.skipped) {
|
|
361
|
+
console.log(chalk.yellow(' Browser QA disabled. Enable in aicc.config.js: browserQA.enabled = true'));
|
|
362
|
+
} else if (qaResult.report?.summary) {
|
|
363
|
+
const s = qaResult.report.summary;
|
|
364
|
+
const icon = s.failed === 0 ? chalk.green('โ
') : chalk.red('โ');
|
|
365
|
+
console.log(` ${icon} ${s.passed}/${s.totalRoutes} passed (${s.passRate}% pass rate)`);
|
|
366
|
+
if (s.failed > 0) console.log(chalk.red(` ${s.failed} page(s) failed โ check .ai-workflow/qa-reports/`));
|
|
367
|
+
}
|
|
368
|
+
} catch (qaErr) {
|
|
369
|
+
console.log(chalk.red(` QA failed: ${qaErr.message}`));
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case 'suggest': {
|
|
374
|
+
console.log(chalk.cyan('\n ๐ก Running Suggestion Agent...\n'));
|
|
375
|
+
try {
|
|
376
|
+
const { runSuggestionAgent } = await import('./agents/suggestion-agent.js');
|
|
377
|
+
const sugResult = await runSuggestionAgent();
|
|
378
|
+
if (sugResult.skipped) {
|
|
379
|
+
console.log(chalk.yellow(' Suggestion agent disabled in config.'));
|
|
380
|
+
} else if (sugResult.suggestions?.length > 0) {
|
|
381
|
+
for (const [i, s] of sugResult.suggestions.entries()) {
|
|
382
|
+
console.log(` ${i + 1}. ${chalk.white(s.title)} [${s.priority}/${s.effort}]`);
|
|
383
|
+
console.log(chalk.dim(` ${s.why || s.description || ''}`));
|
|
384
|
+
}
|
|
385
|
+
console.log(chalk.dim(`\n Saved to: ${sugResult.filePath}`));
|
|
386
|
+
} else {
|
|
387
|
+
console.log(' No suggestions generated.');
|
|
388
|
+
}
|
|
389
|
+
} catch (sugErr) {
|
|
390
|
+
console.log(chalk.red(` Suggestion failed: ${sugErr.message}`));
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case 'assign-project': {
|
|
395
|
+
const { url: assignUrl } = await inquirer.prompt([{ type: 'input', name: 'url', message: 'Target URL:' }]);
|
|
396
|
+
const { goal: assignGoal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Project goal:' }]);
|
|
397
|
+
if (!assignUrl || !assignGoal) { console.log(chalk.yellow(' URL and goal are required.')); break; }
|
|
398
|
+
console.log(chalk.cyan(`\n ๐ฏ Assigning project: ${assignUrl}\n`));
|
|
399
|
+
try {
|
|
400
|
+
const { assignProjectAction } = await import('./actions/assign-project.js');
|
|
401
|
+
const aResult = await assignProjectAction({ url: assignUrl, goal: assignGoal });
|
|
402
|
+
if (aResult.success) {
|
|
403
|
+
console.log(chalk.green(` โ
Project assigned: ${aResult.projectId}`));
|
|
404
|
+
console.log(chalk.dim(' The autonomous loop will start Browser QA next.'));
|
|
405
|
+
} else {
|
|
406
|
+
console.log(chalk.red(' Assignment failed.'));
|
|
407
|
+
}
|
|
408
|
+
} catch (aErr) {
|
|
409
|
+
console.log(chalk.red(` Assignment failed: ${aErr.message}`));
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case 'toggle-auto':
|
|
414
|
+
updateStatus({ pipeline_mode: status.pipeline_mode === 'auto' ? 'manual' : 'auto' });
|
|
415
|
+
console.log(chalk.cyan(`\n โก Auto-Pilot is now ${status.pipeline_mode === 'auto' ? 'OFF' : 'ON'}\n`));
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (err instanceof AILimitError) {
|
|
420
|
+
// โโ AI usage / rate limit hit โ show prominent halt panel โโโโโโโโโโโโโโ
|
|
421
|
+
statusBar.clear();
|
|
422
|
+
process.stdout.write('\x07'); // terminal bell
|
|
423
|
+
const bar = chalk.dim(' โ ');
|
|
424
|
+
const RECOVERY = {
|
|
425
|
+
GEMINI: [
|
|
426
|
+
`All auto-retries exhausted (3 ร 65 s) โ wait a few minutes, then retry`,
|
|
427
|
+
`Fall back to stable model: ${chalk.cyan('GEMINI_MODEL=gemini-2.5-pro npm run control')}`,
|
|
428
|
+
],
|
|
429
|
+
CLAUDE: [
|
|
430
|
+
`Wait a few minutes โ Claude rate limits reset automatically`,
|
|
431
|
+
`Switch model: ${chalk.cyan('CLAUDE_MODEL=claude-haiku-4-5-20251001 npm run control')}`,
|
|
432
|
+
],
|
|
433
|
+
COPILOT: [
|
|
434
|
+
`Wait a few minutes โ Copilot rate limits reset automatically`,
|
|
435
|
+
`Switch model: ${chalk.cyan('COPILOT_MODEL=claude-haiku-4.5 npm run control')}`,
|
|
436
|
+
],
|
|
437
|
+
};
|
|
438
|
+
const steps = RECOVERY[err.agent] || [`Wait and retry`];
|
|
439
|
+
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log(chalk.red.bold(` โ ${err.agent} โ USAGE LIMIT REACHED ยท pipeline stopped`));
|
|
442
|
+
console.log(chalk.dim(' โ'));
|
|
443
|
+
console.log(`${bar}${chalk.yellow(err.message)}`);
|
|
444
|
+
console.log(chalk.dim(' โ'));
|
|
445
|
+
console.log(`${bar}${chalk.bold('What to do:')}`);
|
|
446
|
+
steps.forEach((s, i) => console.log(`${bar} ${chalk.dim(`${i + 1}.`)} ${s}`));
|
|
447
|
+
console.log('');
|
|
448
|
+
} else {
|
|
449
|
+
console.log(chalk.red(`\n โ ${err.message}\n`));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Smart Navigation: only suggest next step when the action actually ran.
|
|
454
|
+
// Cancelled actions (returned false/undefined) go straight back to the main menu.
|
|
455
|
+
const pipelineActions = ['new-feature', 'fix-bug', 'review', 'approve', 'reject', 'deploy'];
|
|
456
|
+
if (pipelineActions.includes(action) && actionCompleted) {
|
|
457
|
+
if (getStatus().pipeline_mode === 'auto') {
|
|
458
|
+
nextAction = null; // Skip prompt, let runAutoPilot handle the next step
|
|
459
|
+
} else {
|
|
460
|
+
nextAction = await promptNextStep(getStatus());
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
await pressEnterToContinue();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
mainMenu().catch(err => {
|
|
469
|
+
console.error(chalk.red('\n Fatal:'), err.message);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
});
|