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/bin/aicc.js ADDED
@@ -0,0 +1,772 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai-control-center CLI entry point.
4
+ *
5
+ * Usage:
6
+ * aicc Launch terminal menu
7
+ * aicc init Interactive project setup
8
+ * aicc start Start all services (web, telegram, twitch, openclaw)
9
+ * aicc web Start web dashboard
10
+ * aicc telegram Start Telegram bot
11
+ * aicc twitch Start Twitch bot
12
+ * aicc openclaw Generate OpenClaw skill
13
+ * aicc status Pipeline status (JSON)
14
+ * aicc feature "desc" Create feature (JSON)
15
+ * aicc deploy Deploy (JSON)
16
+ * aicc health Health check (JSON)
17
+ * aicc --version Show version
18
+ * aicc --help Show help
19
+ */
20
+ import { spawn, spawnSync } from 'child_process';
21
+ import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
22
+ import { dirname, resolve } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));
27
+
28
+ const [,, command, ...args] = process.argv;
29
+
30
+ // Commands that don't require a config file
31
+ const NO_CONFIG_COMMANDS = new Set(['init', '--version', '-v', '--help', '-h']);
32
+
33
+ // ─── PID file management ──────────────────────────────────────────────────────
34
+
35
+ function getPidDir() {
36
+ const wfDir = resolve(process.cwd(), '.ai-workflow');
37
+ const pidDir = resolve(wfDir, '.pids');
38
+ mkdirSync(pidDir, { recursive: true });
39
+ return pidDir;
40
+ }
41
+
42
+ function writePid(service, pid) {
43
+ writeFileSync(resolve(getPidDir(), `${service}.pid`), String(pid));
44
+ }
45
+
46
+ function readPid(service) {
47
+ const f = resolve(getPidDir(), `${service}.pid`);
48
+ if (!existsSync(f)) return null;
49
+ const pid = parseInt(readFileSync(f, 'utf8').trim(), 10);
50
+ // Check if process is still alive
51
+ try { process.kill(pid, 0); return pid; } catch { unlinkSync(f); return null; }
52
+ }
53
+
54
+ function clearPid(service) {
55
+ const f = resolve(getPidDir(), `${service}.pid`);
56
+ try { unlinkSync(f); } catch { /* ok */ }
57
+ }
58
+
59
+ function killPid(service) {
60
+ const pid = readPid(service);
61
+ if (!pid) return false;
62
+ try { process.kill(pid, 'SIGTERM'); clearPid(service); return true; } catch { clearPid(service); return false; }
63
+ }
64
+
65
+ // ─── aicc start — launch all background services ─────────────────────────────
66
+
67
+ async function startAllServices() {
68
+ const { getConfig, env } = await import('../lib/config.js');
69
+ const config = getConfig();
70
+ const aiccBin = resolve(__dirname, 'aicc.js');
71
+
72
+ console.log('');
73
+ console.log(' \x1b[36m\x1b[1mStarting services...\x1b[0m');
74
+ console.log('');
75
+
76
+ // 1. Generate pipeline skills (always — works with or without OpenClaw CLI)
77
+ try {
78
+ const oc = spawnSync(process.execPath, [aiccBin, 'openclaw'], {
79
+ cwd: process.cwd(), encoding: 'utf8', timeout: 15000,
80
+ });
81
+ if (oc.status === 0) {
82
+ console.log(' \x1b[32māœ“\x1b[0m Pipeline skills generated');
83
+ } else {
84
+ console.log(' \x1b[33m~\x1b[0m Skill generation returned non-zero');
85
+ }
86
+ } catch {
87
+ console.log(' \x1b[33m~\x1b[0m Skill generation skipped');
88
+ }
89
+
90
+ // 2. Web dashboard
91
+ const existingWeb = readPid('web');
92
+ if (existingWeb) {
93
+ console.log(` \x1b[32māœ“\x1b[0m Web dashboard already running (PID ${existingWeb})`);
94
+ } else {
95
+ try {
96
+ const webLog = openSync('/tmp/aicc-web.log', 'a');
97
+ const webProc = spawn(process.execPath, [aiccBin, 'web'], {
98
+ cwd: process.cwd(), stdio: ['ignore', webLog, webLog], detached: true,
99
+ });
100
+ webProc.unref();
101
+ writePid('web', webProc.pid);
102
+ const port = config.web?.port || 3847;
103
+ console.log(` \x1b[32māœ“\x1b[0m Web dashboard started (PID ${webProc.pid}) → \x1b[36m\x1b[1mhttp://localhost:${port}\x1b[0m`);
104
+ } catch {
105
+ console.log(' \x1b[33m~\x1b[0m Web dashboard failed to start. Run \x1b[1maicc web\x1b[0m manually.');
106
+ }
107
+ }
108
+
109
+ // 3. Hub server (if configured)
110
+ if (config.hub?.projects?.length) {
111
+ const existingHub = readPid('hub');
112
+ if (existingHub) {
113
+ console.log(` \x1b[32māœ“\x1b[0m Hub server already running (PID ${existingHub})`);
114
+ } else {
115
+ try {
116
+ const hubLog = openSync('/tmp/aicc-hub.log', 'a');
117
+ const hubProc = spawn(process.execPath, [aiccBin, 'hub'], {
118
+ cwd: process.cwd(), stdio: ['ignore', hubLog, hubLog], detached: true,
119
+ });
120
+ hubProc.unref();
121
+ writePid('hub', hubProc.pid);
122
+ const hubPort = config.hub?.port || 3850;
123
+ console.log(` \x1b[32māœ“\x1b[0m Hub server started (PID ${hubProc.pid}) → \x1b[36m\x1b[1mhttp://localhost:${hubPort}/office/\x1b[0m`);
124
+ } catch {
125
+ console.log(' \x1b[33m~\x1b[0m Hub server failed to start. Run \x1b[1maicc hub\x1b[0m manually.');
126
+ }
127
+ }
128
+ }
129
+
130
+ // 4. Telegram bot (if configured)
131
+ // Skip if OpenClaw bridge is active — OpenClaw handles Telegram via its own gateway
132
+ // to avoid 409 Conflict (two bots polling the same token).
133
+ const token = env('TELEGRAM_TOKEN');
134
+ const bridgeHandlesTelegram = config.roleplay?.openclawBridge !== false && (() => {
135
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
136
+ return existsSync(resolve(homeDir, '.openclaw', 'openclaw.json'));
137
+ })();
138
+
139
+ if (bridgeHandlesTelegram) {
140
+ console.log(' \x1b[2mĀ· Telegram bot skipped — OpenClaw bridge handles Telegram\x1b[0m');
141
+ } else if (token) {
142
+ const existingTg = readPid('telegram');
143
+ if (existingTg) {
144
+ console.log(` \x1b[32māœ“\x1b[0m Telegram bot already running (PID ${existingTg})`);
145
+ } else {
146
+ try {
147
+ const tgLog = openSync('/tmp/aicc-telegram.log', 'a');
148
+ const tgProc = spawn(process.execPath, [aiccBin, 'telegram'], {
149
+ cwd: process.cwd(), stdio: ['ignore', tgLog, tgLog], detached: true,
150
+ });
151
+ tgProc.unref();
152
+ writePid('telegram', tgProc.pid);
153
+ console.log(` \x1b[32māœ“\x1b[0m Telegram bot started (PID ${tgProc.pid})`);
154
+ } catch {
155
+ console.log(' \x1b[33m~\x1b[0m Telegram bot failed to start. Run \x1b[1maicc telegram\x1b[0m manually.');
156
+ }
157
+ }
158
+ } else {
159
+ console.log(' \x1b[2mĀ· Telegram bot not configured — skipping\x1b[0m');
160
+ }
161
+
162
+ // 5. Twitch bot (if configured)
163
+ const twitchChannel = env('TWITCH_CHANNEL');
164
+ const twitchToken = env('TWITCH_ACCESS_TOKEN');
165
+ if (twitchChannel && twitchToken) {
166
+ const existingTw = readPid('twitch');
167
+ if (existingTw) {
168
+ console.log(` \x1b[32māœ“\x1b[0m Twitch bot already running (PID ${existingTw})`);
169
+ } else {
170
+ try {
171
+ const twLog = openSync('/tmp/aicc-twitch.log', 'a');
172
+ const twProc = spawn(process.execPath, [aiccBin, 'twitch'], {
173
+ cwd: process.cwd(), stdio: ['ignore', twLog, twLog], detached: true,
174
+ });
175
+ twProc.unref();
176
+ writePid('twitch', twProc.pid);
177
+ console.log(` \x1b[32māœ“\x1b[0m Twitch bot started (PID ${twProc.pid}) → \x1b[36m#${twitchChannel}\x1b[0m`);
178
+ } catch {
179
+ console.log(' \x1b[33m~\x1b[0m Twitch bot failed to start. Run \x1b[1maicc twitch\x1b[0m manually.');
180
+ }
181
+ }
182
+ } else {
183
+ console.log(' \x1b[2mĀ· Twitch bot not configured — skipping\x1b[0m');
184
+ }
185
+
186
+ // 6. OpenClaw bridge (event bus → gateway WebSocket)
187
+ if (config.roleplay?.openclawBridge !== false) {
188
+ try {
189
+ const { isOpenClawAvailable, startBridge } = await import('../lib/openclaw/bridge.js');
190
+ if (isOpenClawAvailable()) {
191
+ const { bus } = await import('../lib/shared/event-bus.js');
192
+ bus.startWatching();
193
+ const started = await startBridge(bus);
194
+ if (started) {
195
+ console.log(' \x1b[32māœ“\x1b[0m OpenClaw bridge started (events → gateway)');
196
+ } else {
197
+ console.log(' \x1b[2mĀ· OpenClaw bridge not started\x1b[0m');
198
+ }
199
+ } else {
200
+ console.log(' \x1b[2mĀ· OpenClaw not configured — bridge skipped\x1b[0m');
201
+ }
202
+ } catch {
203
+ console.log(' \x1b[2mĀ· OpenClaw bridge — skipped\x1b[0m');
204
+ }
205
+ } else {
206
+ console.log(' \x1b[2mĀ· OpenClaw bridge disabled (roleplay.openclawBridge: false)\x1b[0m');
207
+ }
208
+
209
+ // 7. Autonomous pipeline loop (IT Department brain)
210
+ if (config.browserQA?.enabled || config.suggestion?.enabled) {
211
+ try {
212
+ const { startLoop } = await import('../lib/orchestrator/autonomous-loop.js');
213
+ const intervalMs = config.autonomousLoop?.intervalMs || 30_000;
214
+ startLoop(intervalMs);
215
+ console.log(` \x1b[32māœ“\x1b[0m Autonomous loop started (tick every ${intervalMs / 1000}s)`);
216
+ } catch (loopErr) {
217
+ console.log(` \x1b[33m~\x1b[0m Autonomous loop failed: ${loopErr.message}`);
218
+ }
219
+ } else {
220
+ console.log(' \x1b[2mĀ· Autonomous loop skipped — enable browserQA or suggestion in config\x1b[0m');
221
+ }
222
+
223
+ // 8. Cron scheduler (recurring QA)
224
+ if (config.browserQA?.schedule && config.browserQA.schedule !== 'manual') {
225
+ try {
226
+ const { startQACron } = await import('../lib/utils/cron-scheduler.js');
227
+ startQACron();
228
+ console.log(` \x1b[32māœ“\x1b[0m QA cron scheduled (${config.browserQA.schedule})`);
229
+ } catch (cronErr) {
230
+ console.log(` \x1b[33m~\x1b[0m QA cron failed: ${cronErr.message}`);
231
+ }
232
+ }
233
+
234
+ // 9. Watchdog supervisor — monitors all services and auto-respawns crashes
235
+ const existingWd = readPid('watchdog');
236
+ if (existingWd) {
237
+ console.log(` \x1b[32māœ“\x1b[0m Watchdog already running (PID ${existingWd})`);
238
+ } else {
239
+ try {
240
+ const wdLog = openSync('/tmp/aicc-watchdog.log', 'a');
241
+ const wdProc = spawn(process.execPath, [aiccBin, 'watchdog'], {
242
+ cwd: process.cwd(), stdio: ['ignore', wdLog, wdLog], detached: true,
243
+ });
244
+ wdProc.unref();
245
+ writePid('watchdog', wdProc.pid);
246
+ console.log(` \x1b[32māœ“\x1b[0m Watchdog started (PID ${wdProc.pid}) — auto-restarts crashed services`);
247
+ } catch {
248
+ console.log(' \x1b[33m~\x1b[0m Watchdog failed to start.');
249
+ }
250
+ }
251
+
252
+ console.log('');
253
+ }
254
+
255
+ // ─── aicc stop — kill all background services ────────────────────────────────
256
+
257
+ async function stopAllServices() {
258
+ console.log('');
259
+ console.log(' \x1b[36m\x1b[1mStopping services...\x1b[0m');
260
+ console.log('');
261
+
262
+ // Kill watchdog FIRST — otherwise it will respawn services we're stopping
263
+ const wdKilled = killPid('watchdog');
264
+ console.log(wdKilled
265
+ ? ' \x1b[32māœ“\x1b[0m Watchdog stopped'
266
+ : ' \x1b[2mĀ· Watchdog was not running\x1b[0m');
267
+
268
+ // Stop OpenClaw bridge
269
+ try {
270
+ const { stopBridge } = await import('../lib/openclaw/bridge.js');
271
+ stopBridge();
272
+ console.log(' \x1b[32māœ“\x1b[0m OpenClaw bridge stopped');
273
+ } catch {
274
+ // bridge module not loaded, ok
275
+ }
276
+
277
+ const webKilled = killPid('web');
278
+ console.log(webKilled
279
+ ? ' \x1b[32māœ“\x1b[0m Web dashboard stopped'
280
+ : ' \x1b[2mĀ· Web dashboard was not running\x1b[0m');
281
+
282
+ const hubKilled = killPid('hub');
283
+ console.log(hubKilled
284
+ ? ' \x1b[32māœ“\x1b[0m Hub server stopped'
285
+ : ' \x1b[2mĀ· Hub server was not running\x1b[0m');
286
+
287
+ const tgKilled = killPid('telegram');
288
+ console.log(tgKilled
289
+ ? ' \x1b[32māœ“\x1b[0m Telegram bot stopped'
290
+ : ' \x1b[2mĀ· Telegram bot was not running\x1b[0m');
291
+
292
+ const twKilled = killPid('twitch');
293
+ console.log(twKilled
294
+ ? ' \x1b[32māœ“\x1b[0m Twitch bot stopped'
295
+ : ' \x1b[2mĀ· Twitch bot was not running\x1b[0m');
296
+
297
+ console.log('');
298
+ }
299
+
300
+ // ─── Main ─────────────────────────────────────────────────────────────────────
301
+
302
+ async function main() {
303
+ // Handle version / help first
304
+ if (command === '--version' || command === '-v') {
305
+ console.log(`ai-control-center v${pkg.version}`);
306
+ return;
307
+ }
308
+
309
+ if (command === '--help' || command === '-h') {
310
+ console.log(`
311
+ ai-control-center v${pkg.version}
312
+ Multi-AI orchestration control center
313
+
314
+ Usage:
315
+ aicc Launch interactive terminal menu
316
+ aicc init Set up a new project (creates aicc.config.js)
317
+ aicc doctor Check + auto-repair missing skills/files (safe to re-run)
318
+ aicc start Start all services (web, telegram, twitch, openclaw)
319
+ aicc stop Stop all background services
320
+ aicc web Start the web dashboard
321
+ aicc telegram Start the Telegram bot (fails if already running)
322
+ aicc telegram --force Kill any existing instance and restart
323
+ aicc telegram --cleanup Stop the running instance and exit
324
+ aicc twitch Start the Twitch bot
325
+ aicc openclaw Generate OpenClaw skill from config
326
+ aicc skill list List installed skills
327
+ aicc skill search <q> Search skill registry
328
+ aicc skill install <n> Install a skill
329
+ aicc persona list List available personas
330
+ aicc achievements Show unlocked achievements
331
+ aicc hub [port] Start the Hub server
332
+ aicc status Show pipeline status (JSON)
333
+ aicc progress Show pipeline progress + docs (no AI)
334
+ aicc health Show health check (JSON)
335
+ aicc feature "desc" Create a new feature
336
+ aicc events Stream pipeline events as NDJSON
337
+ aicc deploy Deploy to target
338
+ aicc approve Approve current feature
339
+ aicc reject "reason" Reject with feedback
340
+ aicc review Get latest review
341
+ aicc logs Show recent logs
342
+ aicc cleanup Archive workflow files
343
+ aicc reset Abandon current feature
344
+
345
+ AI IT Department:
346
+ aicc assign <url> "goal" Assign project to autonomous AI IT dept
347
+ aicc qa Run Browser QA on target website
348
+ aicc suggest Generate AI feature suggestions
349
+ aicc threads List open discussion threads
350
+
351
+ aicc --version Show version
352
+ aicc --help Show this help
353
+
354
+ Config:
355
+ Place aicc.config.js in your project root.
356
+ Run "aicc init" to create one interactively.
357
+ `);
358
+ return;
359
+ }
360
+
361
+ // Init and doctor don't need config
362
+ if (command === 'init') {
363
+ const { runInit } = await import('../lib/init/wizard.js');
364
+ await runInit();
365
+ return;
366
+ }
367
+
368
+ if (command === 'doctor') {
369
+ const { runDoctor } = await import('../lib/init/doctor.js');
370
+ await runDoctor();
371
+ return;
372
+ }
373
+
374
+ // All other commands require config
375
+ const { loadConfig } = await import('../lib/config.js');
376
+ try {
377
+ await loadConfig();
378
+ } catch (err) {
379
+ console.error(`\n ${err.message}\n`);
380
+ process.exit(1);
381
+ }
382
+
383
+ switch (command) {
384
+ case 'start': {
385
+ await startAllServices();
386
+ break;
387
+ }
388
+
389
+ case 'stop': {
390
+ await stopAllServices();
391
+ break;
392
+ }
393
+
394
+ case 'web': {
395
+ const { startServer } = await import('../lib/web/server.js');
396
+ startServer();
397
+ break;
398
+ }
399
+
400
+ case 'telegram': {
401
+ const forceFlag = args.includes('--force') || args.includes('-f');
402
+ const cleanupFlag = args.includes('--cleanup');
403
+
404
+ // --cleanup: kill existing instance and exit
405
+ if (cleanupFlag) {
406
+ const killed = killPid('telegram');
407
+ console.log(killed ? ' āœ“ Telegram bot stopped.' : ' Ā· Telegram bot was not running.');
408
+ process.exit(0);
409
+ }
410
+
411
+ // Guard: check if already running
412
+ const existingPid = readPid('telegram');
413
+ if (existingPid) {
414
+ if (!forceFlag) {
415
+ console.error(`\n āŒ Telegram bot is already running (PID ${existingPid}).`);
416
+ console.error(' Use --force to kill it and restart, or --cleanup to stop it.\n');
417
+ process.exit(1);
418
+ }
419
+ // --force: kill old instance first
420
+ console.log(` āš ļø Killing existing Telegram bot (PID ${existingPid})...`);
421
+ try { process.kill(existingPid, 'SIGTERM'); } catch { /* already dead */ }
422
+ clearPid('telegram');
423
+ // Wait for Telegram's server to release the long-poll connection (35s timeout + buffer)
424
+ console.log(' Waiting 40s for Telegram long-poll release...');
425
+ await new Promise(r => setTimeout(r, 40000));
426
+ }
427
+
428
+ // Write PID file so future invocations can detect us
429
+ writePid('telegram', process.pid);
430
+
431
+ // Clean up PID on any exit
432
+ const _tgCleanup = () => clearPid('telegram');
433
+ process.on('exit', _tgCleanup);
434
+ process.on('SIGINT', () => { _tgCleanup(); process.exit(0); });
435
+ process.on('SIGTERM', () => { _tgCleanup(); process.exit(0); });
436
+ process.on('SIGHUP', () => {}); // Survive terminal disconnect
437
+
438
+ const { startBot } = await import('../lib/telegram/bot.js');
439
+ startBot();
440
+ break;
441
+ }
442
+
443
+ case 'twitch': {
444
+ const { startBot: startTwitchBot } = await import('../lib/twitch/bot.js');
445
+ startTwitchBot();
446
+ break;
447
+ }
448
+
449
+ case 'hub': {
450
+ const { startHub } = await import('../lib/hub/hub-server.js');
451
+ await startHub();
452
+ break;
453
+ }
454
+
455
+ case 'watchdog': {
456
+ // ─── Process Watchdog ─────────────────────────────────────────────────
457
+ // Monitors all spawned services and auto-respawns any that die.
458
+ // Spawned by `aicc start` as a background supervisor; killed by `aicc stop`.
459
+ process.on('SIGHUP', () => {}); // Survive terminal disconnect
460
+
461
+ writePid('watchdog', process.pid);
462
+ process.on('exit', () => clearPid('watchdog'));
463
+ process.on('SIGINT', () => { clearPid('watchdog'); process.exit(0); });
464
+ process.on('SIGTERM', () => { clearPid('watchdog'); process.exit(0); });
465
+
466
+ const { getConfig: getWdConfig, env: wdEnv } = await import('../lib/config.js');
467
+ const wdConfig = getWdConfig();
468
+ const wdBin = resolve(__dirname, 'aicc.js');
469
+
470
+ // Determine which services should be monitored
471
+ const monitored = new Set(['web']); // web always runs
472
+ if (wdEnv('TELEGRAM_TOKEN')) monitored.add('telegram');
473
+ if (wdConfig.hub?.projects?.length) monitored.add('hub');
474
+ if (wdEnv('TWITCH_CHANNEL') && wdEnv('TWITCH_ACCESS_TOKEN')) monitored.add('twitch');
475
+
476
+ // Track respawn attempts per service — reset after 10 min of stability
477
+ const respawnCount = {}; // { service: count }
478
+ const lastRespawn = {}; // { service: timestamp }
479
+ const MAX_RESPAWNS = 5; // per service before giving up
480
+ const STABILITY_MS = 600_000; // 10 min — reset counter after this
481
+
482
+ const CHECK_INTERVAL = 30_000; // check every 30s
483
+ console.log(` [Watchdog] PID ${process.pid} — monitoring: ${[...monitored].join(', ')}`);
484
+
485
+ setInterval(() => {
486
+ for (const service of monitored) {
487
+ const pid = readPid(service);
488
+ if (pid) {
489
+ // Verify the process is actually alive — PID file alone is not enough
490
+ try {
491
+ process.kill(pid, 0); // throws if process is dead
492
+ // Process alive — reset respawn counter after stability window
493
+ if (respawnCount[service] && Date.now() - (lastRespawn[service] || 0) > STABILITY_MS) {
494
+ respawnCount[service] = 0;
495
+ }
496
+ continue;
497
+ } catch {
498
+ // Stale PID file — process died without cleaning up
499
+ console.log(` [Watchdog] ${service} has stale PID ${pid} — clearing and respawning…`);
500
+ clearPid(service);
501
+ }
502
+ }
503
+
504
+ // Service is dead — check respawn budget
505
+ respawnCount[service] = (respawnCount[service] || 0) + 1;
506
+ if (respawnCount[service] > MAX_RESPAWNS) {
507
+ console.error(` [Watchdog] ${service} crashed ${MAX_RESPAWNS} times in <10 min — halting respawns.`);
508
+ continue;
509
+ }
510
+
511
+ console.log(` [Watchdog] ${service} is DEAD — respawning (attempt ${respawnCount[service]}/${MAX_RESPAWNS})…`);
512
+ lastRespawn[service] = Date.now();
513
+
514
+ // Telegram needs extra delay — Telegram API holds the long-poll for ~35s after
515
+ // the old process dies, and a new instance gets 409 Conflict if it starts too soon.
516
+ const respawnDelay = service === 'telegram' ? 40000 : 0;
517
+ if (respawnDelay) {
518
+ console.log(` [Watchdog] ${service}: waiting ${respawnDelay / 1000}s for Telegram long-poll release...`);
519
+ }
520
+
521
+ setTimeout(() => {
522
+ try {
523
+ const logFd = openSync(`/tmp/aicc-${service}.log`, 'a');
524
+ // Use --force so stale PID files don't block the new instance from starting
525
+ const proc = spawn(process.execPath, [wdBin, service, '--force'], {
526
+ cwd: process.cwd(), stdio: ['ignore', logFd, logFd], detached: true,
527
+ });
528
+ proc.unref();
529
+ console.log(` [Watchdog] ${service} respawned → PID ${proc.pid}`);
530
+ } catch (err) {
531
+ console.error(` [Watchdog] Failed to respawn ${service}: ${err.message}`);
532
+ }
533
+ }, respawnDelay);
534
+ }
535
+ }, CHECK_INTERVAL);
536
+
537
+ break;
538
+ }
539
+
540
+ case 'openclaw': {
541
+ const { generateSkill } = await import('../lib/openclaw/generate-skill.js');
542
+ await generateSkill();
543
+ break;
544
+ }
545
+
546
+ case 'progress': {
547
+ // Show pipeline progress — reads status.json + logs, NO AI calls
548
+ const { existsSync, readFileSync, readdirSync } = await import('fs');
549
+ const { resolve, dirname } = await import('path');
550
+ const { fileURLToPath } = await import('url');
551
+
552
+ // Find project root
553
+ let root = process.cwd();
554
+ while (!existsSync(resolve(root, 'aicc.config.js'))) {
555
+ const parent = dirname(root);
556
+ if (parent === root) break;
557
+ root = parent;
558
+ }
559
+ const wfDir = resolve(root, '.ai-workflow');
560
+ const statusFile = resolve(wfDir, 'status.json');
561
+
562
+ if (!existsSync(statusFile)) {
563
+ console.log('\n No pipeline active. Run: aicc feature "description"\n');
564
+ break;
565
+ }
566
+
567
+ const s = JSON.parse(readFileSync(statusFile, 'utf8'));
568
+ const STAGES = ['idle','inbox','spec_complete','arch_complete','implementation_complete','review_complete','approved','deployed'];
569
+ const ICONS = { idle:'šŸ’¤', inbox:'šŸ“„', spec_complete:'šŸ“', arch_complete:'šŸ—', implementation_complete:'⚔', implementation_failed:'āŒ', review_complete:'šŸ”', approved:'āœ…', rejected:'āŒ', deployed:'šŸš€' };
570
+ const LABELS = { idle:'Idle', inbox:'Inbox', spec_complete:'Spec (Gemini)', arch_complete:'Architecture (Claude)', implementation_complete:'Implementation (Copilot)', implementation_failed:'Implementation FAILED', review_complete:'Review (Gemini)', approved:'Approved', rejected:'Rejected', deployed:'Deployed' };
571
+
572
+ const currentIdx = STAGES.indexOf(s.stage);
573
+
574
+ console.log('\n ── Pipeline Progress ─────────────────────────────────────');
575
+ console.log(` Feature: ${s.current_feature || 'none'}`);
576
+ console.log(` Mode: ${s.pipeline_mode || 'manual'}`);
577
+ console.log(` Updated: ${s.updated_at ? new Date(s.updated_at).toLocaleString() : '-'}`);
578
+ console.log('');
579
+
580
+ STAGES.forEach((stage, i) => {
581
+ const icon = ICONS[stage] || 'ā—‹';
582
+ const label = LABELS[stage] || stage;
583
+ const isCurrent = s.stage === stage;
584
+ const isDone = i < currentIdx;
585
+ const marker = isCurrent ? 'ā–¶' : (isDone ? 'āœ“' : 'ā—‹');
586
+ const dim = isDone ? '' : (isCurrent ? '\x1b[1m' : '\x1b[2m');
587
+ console.log(` ${dim}${marker} ${icon} ${label}\x1b[0m`);
588
+ });
589
+ if (s.stage === 'implementation_failed') {
590
+ console.log(` \x1b[31mā–¶ āŒ Implementation FAILED: ${(s.error || '').slice(0, 80)}\x1b[0m`);
591
+ }
592
+ console.log('');
593
+
594
+ // Show available docs
595
+ const SUBDIRS = [['specs','šŸ“ Spec'],['architecture','šŸ— Arch'],['tasks','šŸ“‹ Tasks'],['reviews','šŸ” Review']];
596
+ const available = [];
597
+ for (const [dir, label] of SUBDIRS) {
598
+ const d = resolve(wfDir, dir);
599
+ if (existsSync(d)) {
600
+ const files = readdirSync(d).filter(f => f.endsWith('.md')).sort().reverse();
601
+ if (files.length) available.push(`${label}: ${files[0]}`);
602
+ }
603
+ }
604
+ if (available.length) {
605
+ console.log(' ── Documents ─────────────────────────────────────────────');
606
+ available.forEach(a => console.log(` ${a}`));
607
+ console.log('');
608
+ console.log(' To read: cat .ai-workflow/<subdir>/<file>');
609
+ console.log(' In Telegram: /docs or /status');
610
+ }
611
+
612
+ // Show last 10 log lines
613
+ const logsDir = resolve(wfDir, 'logs');
614
+ if (existsSync(logsDir)) {
615
+ const logFiles = readdirSync(logsDir).filter(f => f.startsWith('session-')).sort().reverse();
616
+ if (logFiles.length) {
617
+ const lines = readFileSync(resolve(logsDir, logFiles[0]), 'utf8')
618
+ .split('\n').filter(l => l.trim()).slice(-10);
619
+ if (lines.length) {
620
+ console.log('\n ── Recent Activity ───────────────────────────────────────');
621
+ lines.forEach(l => console.log(` ${l}`));
622
+ }
623
+ }
624
+ }
625
+ console.log('');
626
+ break;
627
+ }
628
+
629
+ case undefined:
630
+ case 'menu': {
631
+ // Launch interactive terminal menu
632
+ await import('../lib/index.js');
633
+ break;
634
+ }
635
+
636
+ case 'skill': {
637
+ const subCmd = args[0] || 'list';
638
+ if (subCmd === 'list') {
639
+ const { listSkills } = await import('../lib/utils/skill-loader.js');
640
+ const skills = listSkills();
641
+ if (skills.length === 0) {
642
+ console.log('No skills installed. Use "aicc skill install <name>" to add skills.');
643
+ } else {
644
+ console.log(`šŸ“š Installed Skills (${skills.length}):\n`);
645
+ skills.forEach(s => console.log(` ${s.emoji || 'šŸ“„'} ${s.name} - ${s.description || ''} [${s.stages?.join(', ') || 'all'}]`));
646
+ }
647
+ } else if (subCmd === 'search') {
648
+ const { searchSkills } = await import('../lib/hub/skill-registry.js');
649
+ const query = args.slice(1).join(' ') || '';
650
+ const results = searchSkills(query);
651
+ console.log(`šŸ” Found ${results.length} skills:\n`);
652
+ results.forEach(s => console.log(` ${s.emoji || 'šŸ“„'} ${s.name} - ${s.description}`));
653
+ } else if (subCmd === 'install') {
654
+ const { installSkill } = await import('../lib/utils/skill-loader.js');
655
+ const name = args[1];
656
+ if (!name) { console.log('Usage: aicc skill install <name>'); break; }
657
+ installSkill(name);
658
+ console.log(`āœ… Skill "${name}" installed.`);
659
+ }
660
+ break;
661
+ }
662
+
663
+ case 'persona': {
664
+ const subCmd = args[0] || 'list';
665
+ if (subCmd === 'list') {
666
+ const { listPersonas } = await import('../lib/utils/persona-loader.js');
667
+ const personas = listPersonas();
668
+ console.log('šŸŽ­ Available Personas:\n');
669
+ personas.forEach(p => console.log(` ${p.emoji || 'šŸ‘¤'} ${p.role} - ${p.name || p.role}`));
670
+ }
671
+ break;
672
+ }
673
+
674
+ case 'achievements': {
675
+ const { getUnlockedAchievements, ACHIEVEMENTS, formatAchievementList } = await import('../lib/utils/achievements.js');
676
+ const unlocked = getUnlockedAchievements();
677
+ console.log(formatAchievementList(unlocked));
678
+ break;
679
+ }
680
+
681
+ case 'leaderboard': {
682
+ const { getLeaderboard, formatLeaderboard } = await import('../lib/utils/agent-leaderboard.js');
683
+ const entries = getLeaderboard();
684
+ console.log(formatLeaderboard(entries));
685
+ break;
686
+ }
687
+
688
+ case 'index': {
689
+ const { indexCodebase, formatIndexSummary } = await import('../lib/utils/codebase-indexer.js');
690
+ console.log('šŸ“Š Indexing codebase...');
691
+ const index = await indexCodebase();
692
+ console.log(formatIndexSummary(index));
693
+ console.log('\nāœ… Index saved to .ai-workflow/codebase-index.json');
694
+ break;
695
+ }
696
+
697
+ case 'search': {
698
+ const query = args.join(' ');
699
+ if (!query) { console.log('Usage: aicc search <query>'); break; }
700
+ const { searchIndex, loadIndex } = await import('../lib/utils/codebase-indexer.js');
701
+ const index = loadIndex();
702
+ const { results, total } = searchIndex(query, index);
703
+ console.log(`šŸ” Found ${total} results for "${query}":\n`);
704
+ results.slice(0, 20).forEach(r => {
705
+ if (r.type === 'file') console.log(` šŸ“„ ${r.path}`);
706
+ else if (r.type === 'module') console.log(` šŸ“¦ ${r.name}/ (${r.files?.join(', ')})`);
707
+ else if (r.type === 'api') console.log(` 🌐 ${r.method} ${r.path} → ${r.file}`);
708
+ else if (r.type === 'dependency') console.log(` šŸ“š ${r.name}`);
709
+ });
710
+ break;
711
+ }
712
+
713
+ case 'migrate': {
714
+ const { StorageAdapter } = await import('../lib/utils/db-adapter.js');
715
+ console.log('šŸ“¦ Migrating data to SQLite...');
716
+ const adapter = new StorageAdapter('sqlite');
717
+ const result = await adapter.migrate();
718
+ console.log(`āœ… Migration complete: ${result.costs} cost entries, ${result.audit} audit entries`);
719
+ adapter.close();
720
+ break;
721
+ }
722
+
723
+ case 'mcp': {
724
+ const subCmd = args[0] || 'list';
725
+ if (subCmd === 'list') {
726
+ const { listMCPServers, getRunningServers } = await import('../lib/utils/mcp-client.js');
727
+ const servers = await listMCPServers();
728
+ const running = getRunningServers();
729
+ console.log('šŸ”Œ MCP Servers:\n');
730
+ if (servers.length === 0) {
731
+ console.log(' No MCP servers configured. Add to aicc.config.js:');
732
+ console.log(' mcp: { servers: [{ name: "myserver", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem"] }] }');
733
+ } else {
734
+ servers.forEach(s => {
735
+ const isRunning = running.find(r => r.name === s.name);
736
+ console.log(` ${isRunning ? '🟢' : '⚪'} ${s.name} — ${s.description || s.url || s.command || 'no description'}`);
737
+ if (isRunning) console.log(` Tools: ${isRunning.tools}, PID: ${isRunning.pid}`);
738
+ });
739
+ }
740
+ } else if (subCmd === 'start') {
741
+ const serverName = args[1];
742
+ if (!serverName) { console.log('Usage: aicc mcp start <server-name>'); break; }
743
+ const { listMCPServers, startServer } = await import('../lib/utils/mcp-client.js');
744
+ const servers = await listMCPServers();
745
+ const serverConfig = servers.find(s => s.name === serverName);
746
+ if (!serverConfig) { console.log(`Server "${serverName}" not found in config.`); break; }
747
+ console.log(`Starting MCP server "${serverName}"...`);
748
+ await startServer(serverConfig);
749
+ console.log(`āœ… Server "${serverName}" started.`);
750
+ } else if (subCmd === 'stop') {
751
+ const serverName = args[1];
752
+ const { stopServer, stopAllServers } = await import('../lib/utils/mcp-client.js');
753
+ if (serverName) { stopServer(serverName); console.log(`Stopped "${serverName}".`); }
754
+ else { stopAllServers(); console.log('All MCP servers stopped.'); }
755
+ }
756
+ break;
757
+ }
758
+
759
+ default: {
760
+ // JSON CLI mode — delegate to cli.js for all other commands
761
+ // Re-inject command into argv so cli.js can parse it
762
+ process.argv = [process.argv[0], process.argv[1], command, ...args];
763
+ await import('../lib/cli.js');
764
+ break;
765
+ }
766
+ }
767
+ }
768
+
769
+ main().catch(err => {
770
+ console.error(err.message);
771
+ process.exit(1);
772
+ });