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/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
|
+
});
|