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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +584 -0
  3. package/bin/aicc.js +772 -0
  4. package/lib/actions/approve.js +71 -0
  5. package/lib/actions/assign-project.js +132 -0
  6. package/lib/actions/browser-test.js +64 -0
  7. package/lib/actions/cleanup.js +174 -0
  8. package/lib/actions/debug.js +298 -0
  9. package/lib/actions/deploy.js +1229 -0
  10. package/lib/actions/fix-bug.js +134 -0
  11. package/lib/actions/new-feature.js +255 -0
  12. package/lib/actions/reject.js +307 -0
  13. package/lib/actions/review.js +706 -0
  14. package/lib/actions/status.js +47 -0
  15. package/lib/agents/browser-qa-agent.js +611 -0
  16. package/lib/agents/payment-agent.js +116 -0
  17. package/lib/agents/suggestion-agent.js +88 -0
  18. package/lib/cli.js +303 -0
  19. package/lib/config.js +243 -0
  20. package/lib/hub/hub-server.js +440 -0
  21. package/lib/hub/project-poller.js +75 -0
  22. package/lib/hub/skill-registry.js +89 -0
  23. package/lib/hub/state-aggregator.js +204 -0
  24. package/lib/index.js +471 -0
  25. package/lib/init/doctor.js +523 -0
  26. package/lib/init/presets.js +222 -0
  27. package/lib/init/skill-fetcher.js +77 -0
  28. package/lib/init/wizard.js +973 -0
  29. package/lib/integrations/codex-runner.js +128 -0
  30. package/lib/integrations/github-actions.js +248 -0
  31. package/lib/integrations/github-reporter.js +229 -0
  32. package/lib/integrations/screenshot-store.js +102 -0
  33. package/lib/openclaw/bridge.js +650 -0
  34. package/lib/openclaw/generate-skill.js +235 -0
  35. package/lib/openclaw/openclaw.json +64 -0
  36. package/lib/orchestrator/autonomous-loop.js +429 -0
  37. package/lib/orchestrator/thread-triggers.js +63 -0
  38. package/lib/roleplay/agent-messenger.js +75 -0
  39. package/lib/roleplay/discussion-threads.js +303 -0
  40. package/lib/roleplay/health-monitor.js +121 -0
  41. package/lib/roleplay/pm-agent.js +513 -0
  42. package/lib/roleplay/roleplay-config.js +25 -0
  43. package/lib/roleplay/room.js +164 -0
  44. package/lib/shared/action-runner.js +2330 -0
  45. package/lib/shared/event-bus.js +185 -0
  46. package/lib/slack/bot.js +378 -0
  47. package/lib/telegram/bot.js +416 -0
  48. package/lib/telegram/commands.js +1267 -0
  49. package/lib/telegram/keyboards.js +113 -0
  50. package/lib/telegram/notifications.js +247 -0
  51. package/lib/twitch/bot.js +354 -0
  52. package/lib/twitch/commands.js +302 -0
  53. package/lib/twitch/notifications.js +63 -0
  54. package/lib/utils/achievements.js +191 -0
  55. package/lib/utils/activity-log.js +182 -0
  56. package/lib/utils/agent-leaderboard.js +119 -0
  57. package/lib/utils/audit-logger.js +232 -0
  58. package/lib/utils/codebase-context.js +288 -0
  59. package/lib/utils/codebase-indexer.js +381 -0
  60. package/lib/utils/config-schema.js +230 -0
  61. package/lib/utils/context-compressor.js +172 -0
  62. package/lib/utils/correlation.js +63 -0
  63. package/lib/utils/cost-tracker.js +423 -0
  64. package/lib/utils/cron-scheduler.js +53 -0
  65. package/lib/utils/db-adapter.js +293 -0
  66. package/lib/utils/display.js +272 -0
  67. package/lib/utils/errors.js +116 -0
  68. package/lib/utils/format.js +134 -0
  69. package/lib/utils/intent-engine.js +464 -0
  70. package/lib/utils/mcp-client.js +238 -0
  71. package/lib/utils/model-ab-test.js +164 -0
  72. package/lib/utils/notify.js +122 -0
  73. package/lib/utils/persona-loader.js +80 -0
  74. package/lib/utils/pipeline-lock.js +73 -0
  75. package/lib/utils/pipeline.js +214 -0
  76. package/lib/utils/plugin-runner.js +234 -0
  77. package/lib/utils/rate-limiter.js +84 -0
  78. package/lib/utils/rbac.js +74 -0
  79. package/lib/utils/runner.js +1809 -0
  80. package/lib/utils/security.js +191 -0
  81. package/lib/utils/self-healer.js +144 -0
  82. package/lib/utils/skill-loader.js +255 -0
  83. package/lib/utils/spinner.js +132 -0
  84. package/lib/utils/stage-queue.js +50 -0
  85. package/lib/utils/state-machine.js +89 -0
  86. package/lib/utils/status-bar.js +327 -0
  87. package/lib/utils/token-estimator.js +101 -0
  88. package/lib/utils/ux-analyzer.js +101 -0
  89. package/lib/utils/webhook-emitter.js +83 -0
  90. package/lib/web/public/css/styles.css +417 -0
  91. package/lib/web/public/dark-mode.js +44 -0
  92. package/lib/web/public/hub/kanban.html +206 -0
  93. package/lib/web/public/index.html +45 -0
  94. package/lib/web/public/js/app.js +71 -0
  95. package/lib/web/public/js/ask.js +110 -0
  96. package/lib/web/public/js/dashboard.js +165 -0
  97. package/lib/web/public/js/deploy.js +72 -0
  98. package/lib/web/public/js/feature.js +79 -0
  99. package/lib/web/public/js/health.js +65 -0
  100. package/lib/web/public/js/logs.js +93 -0
  101. package/lib/web/public/js/review.js +123 -0
  102. package/lib/web/public/js/ws-client.js +82 -0
  103. package/lib/web/public/office/css/office.css +678 -0
  104. package/lib/web/public/office/index.html +148 -0
  105. package/lib/web/public/office/js/achievements-ui.js +117 -0
  106. package/lib/web/public/office/js/character.js +1056 -0
  107. package/lib/web/public/office/js/chat-bubbles.js +177 -0
  108. package/lib/web/public/office/js/cost-overlay.js +123 -0
  109. package/lib/web/public/office/js/day-night.js +68 -0
  110. package/lib/web/public/office/js/effects.js +632 -0
  111. package/lib/web/public/office/js/engine.js +146 -0
  112. package/lib/web/public/office/js/feature-ticket.js +216 -0
  113. package/lib/web/public/office/js/hub-client.js +60 -0
  114. package/lib/web/public/office/js/main.js +1757 -0
  115. package/lib/web/public/office/js/office-layout.js +1524 -0
  116. package/lib/web/public/office/js/pathfinding.js +144 -0
  117. package/lib/web/public/office/js/pixel-sprites.js +1454 -0
  118. package/lib/web/public/office/js/progress-bars.js +117 -0
  119. package/lib/web/public/office/js/replay.js +191 -0
  120. package/lib/web/public/office/js/sound-effects.js +91 -0
  121. package/lib/web/public/office/js/sprite-renderer.js +211 -0
  122. package/lib/web/public/office/js/stamina-system.js +89 -0
  123. package/lib/web/public/office/js/ui.js +107 -0
  124. package/lib/web/public/onboarding/index.html +243 -0
  125. package/lib/web/public/timeline/index.html +195 -0
  126. package/lib/web/routes/api.js +499 -0
  127. package/lib/web/routes/logs.js +20 -0
  128. package/lib/web/routes/metrics.js +99 -0
  129. package/lib/web/server.js +183 -0
  130. package/lib/web/ws/handler.js +65 -0
  131. package/package.json +67 -0
  132. package/templates/agent-architect.md +69 -0
  133. package/templates/agent-gemini-pm.md +49 -0
  134. package/templates/agent-gemini-reviewer.md +52 -0
  135. package/templates/copilot-instructions.md +36 -0
  136. package/templates/pipelines/mobile.json +27 -0
  137. package/templates/pipelines/nodejs-api.json +27 -0
  138. package/templates/pipelines/python.json +27 -0
  139. package/templates/pipelines/react.json +27 -0
  140. package/templates/pipelines/salesforce.json +27 -0
  141. package/templates/role-gemini.md +97 -0
  142. package/templates/skill-architect.md +114 -0
  143. package/templates/skill-browser-qa.md +50 -0
  144. package/templates/skill-bug-from-qa.md +58 -0
  145. package/templates/skill-chatbot.md +93 -0
  146. package/templates/skill-implement.md +78 -0
  147. package/templates/skill-openclaw.md +174 -0
  148. package/templates/skill-payment.md +110 -0
  149. package/templates/skill-pm-spec.md +77 -0
  150. package/templates/skill-requirement-capture.md +97 -0
  151. package/templates/skill-review.md +108 -0
  152. package/templates/skill-reviewer-qa.md +44 -0
  153. package/templates/skill-suggestion.md +45 -0
  154. 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
+ });