@meshxdata/fops 0.0.3 → 0.0.4
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/package.json +1 -1
- package/src/agent/agent.js +139 -38
- package/src/agent/agents.js +224 -0
- package/src/agent/context.js +146 -12
- package/src/agent/index.js +1 -0
- package/src/agent/llm.js +84 -13
- package/src/auth/coda.js +10 -10
- package/src/auth/login.js +13 -13
- package/src/auth/oauth.js +4 -4
- package/src/commands/index.js +74 -15
- package/src/config.js +2 -2
- package/src/doctor.js +67 -9
- package/src/feature-flags.js +197 -0
- package/src/plugins/api.js +14 -0
- package/src/plugins/builtins/stack-api.js +36 -0
- package/src/plugins/loader.js +67 -0
- package/src/plugins/registry.js +2 -0
- package/src/project.js +20 -1
- package/src/setup/aws.js +7 -7
- package/src/setup/setup.js +10 -9
- package/src/setup/wizard.js +73 -6
- package/src/ui/confirm.js +3 -2
- package/src/ui/input.js +2 -2
- package/src/ui/spinner.js +4 -4
- package/src/ui/streaming.js +2 -2
package/package.json
CHANGED
package/src/agent/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execa } from "execa";
|
|
|
3
3
|
import { resolveAnthropicApiKey, resolveOpenAiApiKey, authHelp, offerClaudeLogin } from "../auth/index.js";
|
|
4
4
|
import { PKG } from "../config.js";
|
|
5
5
|
import { renderBanner, promptInput, selectOption } from "../ui/index.js";
|
|
6
|
-
import { FOUNDATION_SYSTEM_PROMPT, gatherStackContext } from "./context.js";
|
|
6
|
+
import { FOUNDATION_SYSTEM_PROMPT, gatherStackContext, getFoundationContextBlock } from "./context.js";
|
|
7
7
|
import { hasClaudeCode, hasClaudeCodeAuth, streamAssistantReply } from "./llm.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -40,13 +40,13 @@ export function formatResponse(text) {
|
|
|
40
40
|
const isShell = /^(bash|sh|shell|zsh)?$/.test(codeLang);
|
|
41
41
|
const maxLen = Math.max(...codeBlock.map((l) => l.length), 0);
|
|
42
42
|
const width = Math.max(maxLen + 4, 20);
|
|
43
|
-
const top = chalk.
|
|
44
|
-
const bot = chalk.
|
|
43
|
+
const top = chalk.dim(" ┌" + "─".repeat(width) + "┐");
|
|
44
|
+
const bot = chalk.dim(" └" + "─".repeat(width) + "┘");
|
|
45
45
|
out.push(top);
|
|
46
46
|
for (const cl of codeBlock) {
|
|
47
47
|
const prefix = isShell ? chalk.green("$ ") : " ";
|
|
48
48
|
const pad = " ".repeat(Math.max(0, width - cl.length - (isShell ? 4 : 2)));
|
|
49
|
-
out.push(chalk.
|
|
49
|
+
out.push(chalk.dim(" │ ") + prefix + chalk.white(cl) + chalk.dim(pad + " │"));
|
|
50
50
|
}
|
|
51
51
|
out.push(bot);
|
|
52
52
|
inCode = false;
|
|
@@ -72,7 +72,7 @@ export function formatResponse(text) {
|
|
|
72
72
|
// Blockquote
|
|
73
73
|
if (/^>\s?/.test(line)) {
|
|
74
74
|
const content = line.replace(/^>\s?/, "");
|
|
75
|
-
out.push(chalk.
|
|
75
|
+
out.push(chalk.dim.italic(" " + renderInline(content)));
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -124,9 +124,9 @@ async function runShellCommand(root, command, { isLast = true } = {}) {
|
|
|
124
124
|
const trimmed = command.replace(/\s+/g, " ").trim();
|
|
125
125
|
const branch = isLast ? "└─" : "├─";
|
|
126
126
|
const cont = isLast ? " " : "│ ";
|
|
127
|
-
const prefix = ` ${chalk.
|
|
127
|
+
const prefix = ` ${chalk.dim(cont)}`;
|
|
128
128
|
|
|
129
|
-
console.log(`\n ${chalk.
|
|
129
|
+
console.log(`\n ${chalk.dim(branch)} ${chalk.white(trimmed)} ${chalk.dim("· running")}`);
|
|
130
130
|
|
|
131
131
|
try {
|
|
132
132
|
// Pipe stdout/stderr so docker/compose use clean non-TTY output
|
|
@@ -150,7 +150,7 @@ async function runShellCommand(root, command, { isLast = true } = {}) {
|
|
|
150
150
|
const line = raw.includes("\r") ? raw.split("\r").filter(Boolean).pop() : raw;
|
|
151
151
|
if (!line?.trim()) continue;
|
|
152
152
|
if (lastWasCR) process.stdout.write("\n");
|
|
153
|
-
process.stdout.write(`${prefix}${chalk.
|
|
153
|
+
process.stdout.write(`${prefix}${chalk.dim(line)}`);
|
|
154
154
|
lastWasCR = raw.includes("\r") && !raw.endsWith("\n");
|
|
155
155
|
if (!lastWasCR) process.stdout.write("\n");
|
|
156
156
|
}
|
|
@@ -175,34 +175,59 @@ async function runShellCommand(root, command, { isLast = true } = {}) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Check if a command matches any plugin-registered auto-run pattern.
|
|
180
|
+
*/
|
|
181
|
+
function isAutoRun(command, registry) {
|
|
182
|
+
if (!registry?.autoRunPatterns?.length) return false;
|
|
183
|
+
return registry.autoRunPatterns.some(({ pattern }) => command.trimStart().startsWith(pattern));
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
/**
|
|
179
187
|
* Present suggested commands in a single picker.
|
|
180
|
-
*
|
|
188
|
+
* Commands matching plugin autoRunPatterns execute immediately.
|
|
189
|
+
* Others show as selectable options plus "Run all" / "Skip".
|
|
181
190
|
* Returns array of { command, ok, output } for commands that were run.
|
|
182
191
|
*/
|
|
183
|
-
export async function askToRunCommand(root, replyText) {
|
|
192
|
+
export async function askToRunCommand(root, replyText, registry) {
|
|
184
193
|
const suggested = extractSuggestedCommands(replyText);
|
|
185
194
|
if (suggested.length === 0) return [];
|
|
186
195
|
|
|
187
|
-
|
|
196
|
+
// Split into auto-run and interactive commands
|
|
197
|
+
const autoRun = suggested.filter((cmd) => isAutoRun(cmd, registry));
|
|
198
|
+
const interactive = suggested.filter((cmd) => !isAutoRun(cmd, registry));
|
|
188
199
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
const results = [];
|
|
201
|
+
|
|
202
|
+
// Execute auto-run commands immediately
|
|
203
|
+
if (autoRun.length > 0) {
|
|
204
|
+
for (let i = 0; i < autoRun.length; i++) {
|
|
205
|
+
const isLast = i === autoRun.length - 1 && interactive.length === 0;
|
|
206
|
+
const result = await runShellCommand(root, autoRun[i], { isLast });
|
|
207
|
+
results.push({ command: autoRun[i], ...result });
|
|
208
|
+
}
|
|
192
209
|
}
|
|
193
|
-
options.push({ label: "Skip", value: "__skip__" });
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
// Prompt for remaining interactive commands
|
|
212
|
+
if (interactive.length > 0) {
|
|
213
|
+
console.log(chalk.cyan("⏺") + chalk.white(" Suggested commands:"));
|
|
196
214
|
|
|
197
|
-
|
|
215
|
+
const options = interactive.map((cmd) => ({ label: cmd, value: cmd }));
|
|
216
|
+
if (interactive.length > 1) {
|
|
217
|
+
options.push({ label: "Run all", value: "__run_all__" });
|
|
218
|
+
}
|
|
219
|
+
options.push({ label: "Skip", value: "__skip__" });
|
|
198
220
|
|
|
199
|
-
|
|
200
|
-
const results = [];
|
|
221
|
+
const choice = await selectOption("Run command?", options);
|
|
201
222
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
223
|
+
if (choice !== null && choice !== "__skip__") {
|
|
224
|
+
const toRun = choice === "__run_all__" ? interactive : [choice];
|
|
225
|
+
for (let i = 0; i < toRun.length; i++) {
|
|
226
|
+
const isLast = i === toRun.length - 1;
|
|
227
|
+
const result = await runShellCommand(root, toRun[i], { isLast });
|
|
228
|
+
results.push({ command: toRun[i], ...result });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
return results;
|
|
@@ -244,7 +269,7 @@ export async function runAgentSingleTurn(root, message, opts = {}) {
|
|
|
244
269
|
console.log(chalk.green("Login complete. Run your command again.\n"));
|
|
245
270
|
process.exit(0);
|
|
246
271
|
}
|
|
247
|
-
console.log(chalk.
|
|
272
|
+
console.log(chalk.dim("Get a valid key from console.anthropic.com (we can open the page in your browser on next run).\n"));
|
|
248
273
|
process.exit(1);
|
|
249
274
|
}
|
|
250
275
|
throw err;
|
|
@@ -255,7 +280,7 @@ export async function runAgentSingleTurn(root, message, opts = {}) {
|
|
|
255
280
|
console.log("");
|
|
256
281
|
|
|
257
282
|
if (runSuggestions) {
|
|
258
|
-
const cmdResults = await askToRunCommand(root, replyText);
|
|
283
|
+
const cmdResults = await askToRunCommand(root, replyText, registry);
|
|
259
284
|
if (cmdResults.some((r) => !r.ok)) {
|
|
260
285
|
console.log(chalk.yellow("\n Some commands failed. Run fops chat for interactive help.\n"));
|
|
261
286
|
}
|
|
@@ -270,7 +295,7 @@ export async function runAgentInteractive(root, opts = {}) {
|
|
|
270
295
|
|
|
271
296
|
if (!useClaudeCode && !anthropicKey && !openaiKey) {
|
|
272
297
|
authHelp();
|
|
273
|
-
console.log(chalk.
|
|
298
|
+
console.log(chalk.dim(" npm install @anthropic-ai/sdk openai # optional deps\n"));
|
|
274
299
|
process.exit(1);
|
|
275
300
|
}
|
|
276
301
|
|
|
@@ -288,44 +313,120 @@ export async function runAgentInteractive(root, opts = {}) {
|
|
|
288
313
|
// Gather base context (without message-specific RAG — that happens per-turn)
|
|
289
314
|
const baseContext = await gatherStackContext(root, { registry });
|
|
290
315
|
const messages = [];
|
|
316
|
+
let activeAgent = null;
|
|
317
|
+
|
|
318
|
+
// Auto-activate agent if requested (e.g. debug agent on startup failure)
|
|
319
|
+
if (opts.initialAgent) {
|
|
320
|
+
const agents = registry?.agents || [];
|
|
321
|
+
const found = agents.find((a) => a.name === opts.initialAgent);
|
|
322
|
+
if (found) {
|
|
323
|
+
activeAgent = found;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
291
326
|
|
|
292
327
|
// Show banner
|
|
293
328
|
renderBanner();
|
|
294
329
|
|
|
295
|
-
|
|
296
|
-
|
|
330
|
+
if (activeAgent) {
|
|
331
|
+
console.log(chalk.cyan(` Agent: ${chalk.bold(activeAgent.name)} — ${activeAgent.description}`));
|
|
332
|
+
console.log(chalk.dim(" /exit-agent to return to general mode • /agents to list specialists\n"));
|
|
333
|
+
} else {
|
|
334
|
+
// Show quick hints
|
|
335
|
+
console.log(chalk.dim(" Try: \"help me set up\" • \"what's running?\" • \"/agents\" to list specialists\n"));
|
|
336
|
+
}
|
|
297
337
|
|
|
298
338
|
// Main chat loop
|
|
299
339
|
while (true) {
|
|
300
|
-
const
|
|
340
|
+
const statusLabel = activeAgent
|
|
341
|
+
? `fops v${PKG.version} \u00b7 agent:${activeAgent.name} \u00b7 /exit-agent \u00b7 /exit to quit`
|
|
342
|
+
: `fops v${PKG.version} \u00b7 /agents \u00b7 /exit to quit \u00b7 \u2191\u2193 history`;
|
|
343
|
+
const input = await promptInput(statusLabel);
|
|
301
344
|
|
|
302
345
|
// Exit on null (esc/ctrl+c) or exit command
|
|
303
346
|
if (input === null || /^\/(exit|quit|q)$/i.test(input)) {
|
|
304
|
-
console.log(chalk.
|
|
347
|
+
console.log(chalk.dim("\nGoodbye!\n"));
|
|
305
348
|
break;
|
|
306
349
|
}
|
|
307
350
|
|
|
308
|
-
|
|
351
|
+
// /agents — list available agents
|
|
352
|
+
if (/^\/agents$/i.test(input.trim())) {
|
|
353
|
+
const agents = registry?.agents || [];
|
|
354
|
+
if (agents.length === 0) {
|
|
355
|
+
console.log(chalk.yellow("\n No agents available.\n"));
|
|
356
|
+
} else {
|
|
357
|
+
console.log(chalk.cyan("\n Available agents:\n"));
|
|
358
|
+
for (const a of agents) {
|
|
359
|
+
const active = activeAgent?.name === a.name ? chalk.green(" (active)") : "";
|
|
360
|
+
console.log(` ${chalk.bold(a.name)}${active} — ${chalk.dim(a.description)}`);
|
|
361
|
+
}
|
|
362
|
+
console.log(chalk.dim(`\n Usage: /agent <name> [task]\n`));
|
|
363
|
+
}
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// /agent <name> [task] — activate an agent
|
|
368
|
+
const agentMatch = input.trim().match(/^\/agent\s+(\S+)(?:\s+(.+))?$/i);
|
|
369
|
+
if (agentMatch) {
|
|
370
|
+
const agentName = agentMatch[1];
|
|
371
|
+
const inlineTask = agentMatch[2] || null;
|
|
372
|
+
const agents = registry?.agents || [];
|
|
373
|
+
const found = agents.find((a) => a.name === agentName);
|
|
374
|
+
if (!found) {
|
|
375
|
+
console.log(chalk.yellow(`\n Unknown agent "${agentName}". Use /agents to see available agents.\n`));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
activeAgent = found;
|
|
379
|
+
console.log(chalk.cyan(`\n Activated agent: ${chalk.bold(found.name)} — ${found.description}\n`));
|
|
380
|
+
if (!inlineTask) continue;
|
|
381
|
+
// Fall through with the inline task as user input
|
|
382
|
+
messages.push({ role: "user", content: inlineTask });
|
|
383
|
+
} else {
|
|
384
|
+
// /exit-agent — deactivate agent
|
|
385
|
+
if (/^\/exit-agent$/i.test(input.trim())) {
|
|
386
|
+
if (!activeAgent) {
|
|
387
|
+
console.log(chalk.dim("\n No agent active.\n"));
|
|
388
|
+
} else {
|
|
389
|
+
console.log(chalk.dim(`\n Deactivated agent: ${activeAgent.name}\n`));
|
|
390
|
+
activeAgent = null;
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
309
394
|
|
|
310
|
-
|
|
395
|
+
if (!input.trim()) continue;
|
|
396
|
+
|
|
397
|
+
messages.push({ role: "user", content: input });
|
|
398
|
+
}
|
|
311
399
|
|
|
312
400
|
try {
|
|
313
401
|
// Re-gather knowledge per turn so RAG is query-specific
|
|
314
402
|
let ragBlock = null;
|
|
315
403
|
if (registry) {
|
|
316
404
|
const { searchKnowledge } = await import("../plugins/knowledge.js");
|
|
317
|
-
ragBlock = await searchKnowledge(registry,
|
|
405
|
+
ragBlock = await searchKnowledge(registry, messages[messages.length - 1].content);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Build system prompt — use agent's prompt when active
|
|
409
|
+
let systemContent;
|
|
410
|
+
if (activeAgent) {
|
|
411
|
+
const contextBlock = activeAgent.contextMode === "minimal"
|
|
412
|
+
? getFoundationContextBlock(root)
|
|
413
|
+
: baseContext;
|
|
414
|
+
systemContent = activeAgent.systemPrompt + "\n\n" + contextBlock
|
|
415
|
+
+ (ragBlock ? "\n\n" + ragBlock : "");
|
|
416
|
+
} else {
|
|
417
|
+
systemContent = FOUNDATION_SYSTEM_PROMPT + "\n\n" + baseContext
|
|
418
|
+
+ (ragBlock ? "\n\n" + ragBlock : "");
|
|
318
419
|
}
|
|
319
|
-
const systemContent = FOUNDATION_SYSTEM_PROMPT + "\n\n" + baseContext
|
|
320
|
-
+ (ragBlock ? "\n\n" + ragBlock : "");
|
|
321
420
|
|
|
322
|
-
const replyText = await streamAssistantReply(root, messages, systemContent, {
|
|
421
|
+
const replyText = await streamAssistantReply(root, messages, systemContent, {
|
|
422
|
+
replaceSystemPrompt: !!activeAgent,
|
|
423
|
+
});
|
|
323
424
|
console.log("");
|
|
324
425
|
console.log(formatResponse(replyText));
|
|
325
426
|
console.log("");
|
|
326
427
|
messages.push({ role: "assistant", content: replyText });
|
|
327
428
|
|
|
328
|
-
const cmdResults = await askToRunCommand(root, replyText);
|
|
429
|
+
const cmdResults = await askToRunCommand(root, replyText, registry);
|
|
329
430
|
// Feed command outputs back into context so the AI knows what happened
|
|
330
431
|
if (cmdResults.length > 0) {
|
|
331
432
|
const summary = cmdResults.map((r) =>
|
|
@@ -350,7 +451,7 @@ export async function runAgentInteractive(root, opts = {}) {
|
|
|
350
451
|
console.log(chalk.red("\nAuthentication failed: invalid or expired API key."));
|
|
351
452
|
const didLogin = await offerClaudeLogin();
|
|
352
453
|
if (didLogin) console.log(chalk.green("\nLogin complete. Try your message again.\n"));
|
|
353
|
-
else console.log(chalk.
|
|
454
|
+
else console.log(chalk.dim("Get a valid key from console.anthropic.com\n"));
|
|
354
455
|
} else {
|
|
355
456
|
console.log(chalk.red("\nError:"), err.message);
|
|
356
457
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in specialized agents for fops interactive chat.
|
|
3
|
+
* Each agent provides a tailored system prompt for its domain.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const BUILTIN_AGENTS = [
|
|
7
|
+
{
|
|
8
|
+
name: "debug",
|
|
9
|
+
description: "Diagnose containers, read logs, fix failures",
|
|
10
|
+
contextMode: "full",
|
|
11
|
+
systemPrompt: `You are FOPS Debug Agent — a container diagnostics specialist. You live in docker logs and know every exit code by heart.
|
|
12
|
+
|
|
13
|
+
## Role
|
|
14
|
+
You diagnose why containers fail, services crash, and health checks timeout. You read logs like a detective reads crime scenes.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
1. Check container status first — look for exited, restarting, unhealthy.
|
|
18
|
+
2. Read the logs. The answer is always in the logs.
|
|
19
|
+
3. Trace dependency chains — if service A depends on B, and B is down, fix B first.
|
|
20
|
+
4. Give the fix command. No hedging.
|
|
21
|
+
|
|
22
|
+
## Commands You Suggest
|
|
23
|
+
- \`fops logs <service>\` — read container logs
|
|
24
|
+
- \`docker compose restart <service>\` — restart a failing service
|
|
25
|
+
- \`fops doctor\` — run full diagnostics
|
|
26
|
+
- \`fops up\` — bring stack up after fixes
|
|
27
|
+
- \`docker compose ps\` — check current state
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
- Always check BOTH container status AND service health. "Running" doesn't mean "working".
|
|
31
|
+
- When multiple services fail, fix them in dependency order.
|
|
32
|
+
- Never suggest \`docker compose down\` as a first resort — diagnose first.
|
|
33
|
+
- Output fix commands in fenced bash blocks.`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "deploy",
|
|
37
|
+
description: "Build images, ECR auth, push/pull workflows",
|
|
38
|
+
contextMode: "full",
|
|
39
|
+
systemPrompt: `You are FOPS Deploy Agent — a build and deployment specialist. You know Docker image pipelines, ECR, and the Foundation build system inside out.
|
|
40
|
+
|
|
41
|
+
## Role
|
|
42
|
+
You handle image builds, registry authentication, push/pull workflows, and deployment sequencing.
|
|
43
|
+
|
|
44
|
+
## Approach
|
|
45
|
+
1. Check which images exist locally and which are missing.
|
|
46
|
+
2. Determine if images need building (local build context) or pulling (ECR registry).
|
|
47
|
+
3. Handle ECR auth before any registry operations.
|
|
48
|
+
4. Sequence builds correctly — base images before dependents.
|
|
49
|
+
|
|
50
|
+
## Commands You Suggest
|
|
51
|
+
- \`fops build\` — build local images
|
|
52
|
+
- \`fops build <service>\` — build a specific service image
|
|
53
|
+
- \`fops download\` — pull images from ECR (requires auth)
|
|
54
|
+
- \`aws ecr get-login-password | docker login ...\` — ECR authentication
|
|
55
|
+
- \`docker compose up -d\` — deploy after builds
|
|
56
|
+
|
|
57
|
+
## Rules
|
|
58
|
+
- Always check ECR auth status before suggesting pulls.
|
|
59
|
+
- Report image ages — stale images (>7 days) may need rebuilding.
|
|
60
|
+
- Suggest building in parallel where possible.
|
|
61
|
+
- Output commands in fenced bash blocks.`,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "data",
|
|
65
|
+
description: "Data mesh ops, Trino queries, API usage",
|
|
66
|
+
contextMode: "full",
|
|
67
|
+
systemPrompt: `You are FOPS Data Agent — a Foundation data platform specialist. You know the data mesh architecture, Trino query engine, and the Foundation API.
|
|
68
|
+
|
|
69
|
+
## Role
|
|
70
|
+
You help with data operations: querying via Trino, managing data products through the API, understanding the data mesh topology, and debugging data pipeline issues.
|
|
71
|
+
|
|
72
|
+
## Domain Knowledge
|
|
73
|
+
- Foundation uses a data mesh architecture with data products as first-class citizens.
|
|
74
|
+
- Trino is the query engine (port 8081). Catalogs: hive, iceberg.
|
|
75
|
+
- The Backend API (port 9001) manages data products, meshes, and metadata.
|
|
76
|
+
- Storage Engine (MinIO, port 9002) provides S3-compatible object storage.
|
|
77
|
+
- Hive Metastore manages table metadata for Trino.
|
|
78
|
+
|
|
79
|
+
## Commands You Suggest
|
|
80
|
+
- \`fops query "<sql>"\` — run Trino SQL queries
|
|
81
|
+
- API calls via curl to localhost:9001/api/...
|
|
82
|
+
- \`fops logs trino\` — check Trino logs for query issues
|
|
83
|
+
- \`fops logs foundation-backend\` — check API logs
|
|
84
|
+
|
|
85
|
+
## Rules
|
|
86
|
+
- When suggesting Trino queries, always specify the catalog and schema.
|
|
87
|
+
- For large result sets, suggest LIMIT clauses.
|
|
88
|
+
- Help users understand the data mesh model — products, meshes, contracts.
|
|
89
|
+
- Output commands and queries in fenced bash blocks.`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "security",
|
|
93
|
+
description: "Vault ops, secrets management, .env audit",
|
|
94
|
+
contextMode: "minimal",
|
|
95
|
+
systemPrompt: `You are FOPS Security Agent — a secrets and security specialist. You handle Vault operations, .env audits, and credential hygiene.
|
|
96
|
+
|
|
97
|
+
## Role
|
|
98
|
+
You manage secrets, audit configurations for leaked credentials, review .env files, and handle Vault operations. You are paranoid by design.
|
|
99
|
+
|
|
100
|
+
## Approach
|
|
101
|
+
1. Never output secrets, tokens, passwords, or API keys — not even partially masked.
|
|
102
|
+
2. Audit .env files for security issues without revealing values.
|
|
103
|
+
3. Check for leaked credentials in logs or config files.
|
|
104
|
+
4. Guide Vault operations for secure secret management.
|
|
105
|
+
|
|
106
|
+
## Commands You Suggest
|
|
107
|
+
- \`fops setup\` — regenerate .env from template
|
|
108
|
+
- \`vault status\` — check Vault seal status
|
|
109
|
+
- \`vault kv list secret/\` — list secret paths (not values)
|
|
110
|
+
- Environment variable audits (checking presence, not values)
|
|
111
|
+
|
|
112
|
+
## Rules
|
|
113
|
+
- NEVER output secret values in any form — masked, partial, or full.
|
|
114
|
+
- When auditing .env, report which keys exist and which are missing — never the values.
|
|
115
|
+
- Flag any credentials found in logs or non-secret files.
|
|
116
|
+
- Suggest rotation for any potentially compromised credentials.
|
|
117
|
+
- Default to minimal context mode — you don't need full docker status to do your job.`,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "review",
|
|
121
|
+
description: "Git diff analysis, code review, pattern checks",
|
|
122
|
+
contextMode: "minimal",
|
|
123
|
+
systemPrompt: `You are FOPS Review Agent — a code review specialist for Foundation projects. You read diffs like prose and catch issues before they ship.
|
|
124
|
+
|
|
125
|
+
## Role
|
|
126
|
+
You review code changes, analyze git diffs, check for anti-patterns, and ensure code quality in Foundation's stack (Node.js, Docker, SQL, config files).
|
|
127
|
+
|
|
128
|
+
## Approach
|
|
129
|
+
1. Look at the diff first — understand what changed and why.
|
|
130
|
+
2. Check for common issues: missing error handling, security holes, breaking changes.
|
|
131
|
+
3. Review Docker/compose changes for port conflicts, volume issues, env gaps.
|
|
132
|
+
4. Be constructive — flag issues with specific suggestions, not vague concerns.
|
|
133
|
+
|
|
134
|
+
## Commands You Suggest
|
|
135
|
+
- \`git diff\` — see unstaged changes
|
|
136
|
+
- \`git diff --staged\` — see staged changes
|
|
137
|
+
- \`git log --oneline -10\` — recent commit history
|
|
138
|
+
- \`git show <commit>\` — inspect a specific commit
|
|
139
|
+
|
|
140
|
+
## Rules
|
|
141
|
+
- Focus on substance: bugs, security issues, performance problems, missing edge cases.
|
|
142
|
+
- Don't nitpick style unless it affects readability significantly.
|
|
143
|
+
- For Docker changes, verify port mappings, volume mounts, and env vars match.
|
|
144
|
+
- Output suggestions as concrete code fixes when possible.`,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "stack",
|
|
148
|
+
description: "Stack API — lifecycle, status, logs, tests, security scans via REST",
|
|
149
|
+
contextMode: "full",
|
|
150
|
+
systemPrompt: `You are FOPS Stack Agent — a specialist for the Foundation Stack API (FastAPI on port 3090). You interact with stacks through REST endpoints.
|
|
151
|
+
|
|
152
|
+
## Role
|
|
153
|
+
You manage Docker Compose stacks through the Stack API: lifecycle operations, observability, testing, security scanning, and Foundation platform tasks.
|
|
154
|
+
|
|
155
|
+
## Endpoints
|
|
156
|
+
|
|
157
|
+
### Health & Discovery
|
|
158
|
+
- \`GET /health\` — API health check
|
|
159
|
+
- \`GET /stacks\` — list all discovered stacks
|
|
160
|
+
|
|
161
|
+
### Lifecycle
|
|
162
|
+
- \`POST /stack/{name}/up\` — bring stack up (optional body: \`{"services":["svc1"]}\`)
|
|
163
|
+
- \`POST /stack/{name}/down\` — tear stack down
|
|
164
|
+
- \`POST /stack/{name}/restart\` — restart stack (optional body: \`{"services":["svc1"]}\`)
|
|
165
|
+
- \`POST /stack/{name}/pull\` — pull latest images
|
|
166
|
+
|
|
167
|
+
### Observability
|
|
168
|
+
- \`GET /stack/{name}/status\` — container states and health
|
|
169
|
+
- \`GET /stack/{name}/logs?tail=100&service=svc\` — fetch logs
|
|
170
|
+
- \`GET /stack/{name}/operations\` — recent operation history
|
|
171
|
+
|
|
172
|
+
### Foundation Platform
|
|
173
|
+
- \`GET /stack/{name}/foundation/health\` — Foundation service health
|
|
174
|
+
- \`POST /stack/{name}/foundation/bootstrap\` — bootstrap Foundation platform
|
|
175
|
+
- \`POST /stack/{name}/foundation/grant-admin\` — grant admin role (body: \`{"email":"user@example.com"}\`)
|
|
176
|
+
- \`POST /stack/{name}/foundation/run-compute-job\` — trigger compute job (body: \`{"job":"job-name"}\`)
|
|
177
|
+
|
|
178
|
+
### QA Testing
|
|
179
|
+
- \`POST /stack/{name}/test\` — run test suite (optional body: \`{"suite":"smoke"}\`)
|
|
180
|
+
- \`GET /stack/{name}/test/suites\` — list available test suites
|
|
181
|
+
|
|
182
|
+
### Security
|
|
183
|
+
- \`GET /stack/{name}/security/images\` — list images in stack
|
|
184
|
+
- \`POST /stack/{name}/security/scan\` — scan a specific image (body: \`{"image":"name:tag"}\`)
|
|
185
|
+
- \`POST /stack/{name}/security/scan-all\` — scan all stack images
|
|
186
|
+
- \`GET /stack/{name}/security/results\` — get scan results
|
|
187
|
+
|
|
188
|
+
## Auth
|
|
189
|
+
- API key: \`X-API-Key: <key>\` header
|
|
190
|
+
- Bearer token: \`Authorization: Bearer <token>\` header
|
|
191
|
+
- Local dev (localhost): often no auth required
|
|
192
|
+
|
|
193
|
+
## Commands You Suggest
|
|
194
|
+
- \`curl http://localhost:3090/health\` — check API health
|
|
195
|
+
- \`curl http://localhost:3090/stacks\` — list stacks
|
|
196
|
+
- \`curl http://localhost:3090/stack/{name}/status\` — get stack status
|
|
197
|
+
- \`curl -X POST http://localhost:3090/stack/{name}/up\` — bring stack up
|
|
198
|
+
- \`curl -X POST http://localhost:3090/stack/{name}/restart\` — restart stack
|
|
199
|
+
- \`curl http://localhost:3090/stack/{name}/logs?tail=50\` — get recent logs
|
|
200
|
+
- \`curl -X POST -H "Content-Type: application/json" -d '{"suite":"smoke"}' http://localhost:3090/stack/{name}/test\` — run tests
|
|
201
|
+
|
|
202
|
+
## Rules
|
|
203
|
+
- Always check \`/health\` first to confirm the API is reachable.
|
|
204
|
+
- For GET requests, suggest simple curl commands. For POST requests, include \`-X POST\` and any required body.
|
|
205
|
+
- Use \`jq\` for formatting JSON output: \`curl ... | jq .\`
|
|
206
|
+
- When diagnosing issues, check \`/stack/{name}/status\` before \`/stack/{name}/logs\`.
|
|
207
|
+
- Output commands in fenced bash blocks.`,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Load built-in agents into the registry.
|
|
213
|
+
*/
|
|
214
|
+
export function loadBuiltinAgents(registry) {
|
|
215
|
+
for (const agent of BUILTIN_AGENTS) {
|
|
216
|
+
registry.agents.push({
|
|
217
|
+
pluginId: "builtin",
|
|
218
|
+
name: agent.name,
|
|
219
|
+
description: agent.description,
|
|
220
|
+
systemPrompt: agent.systemPrompt,
|
|
221
|
+
contextMode: agent.contextMode,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|