@brutalist/mcp 0.4.3 → 0.5.0

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.
@@ -1,19 +1,26 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { randomUUID } from "crypto";
5
+ import express from "express";
3
6
  import { z } from "zod";
4
7
  import { CLIAgentOrchestrator } from './cli-agents.js';
5
8
  import { logger } from './logger.js';
6
- // Package version - keep in sync with package.json
7
- const PACKAGE_VERSION = "0.4.1";
9
+ import { extractPaginationParams, parseCursor, PAGINATION_DEFAULTS, createPaginationMetadata, formatPaginationStatus, estimateTokenCount } from './utils/pagination.js';
10
+ // Use environment variable or fallback to manual version
11
+ const PACKAGE_VERSION = process.env.npm_package_version || "0.4.4";
8
12
  export class BrutalistServer {
9
13
  server;
10
14
  config;
11
15
  cliOrchestrator;
16
+ httpTransport;
12
17
  constructor(config = {}) {
13
18
  this.config = {
14
19
  workingDirectory: process.cwd(),
15
20
  defaultTimeout: 1500000, // 25 minutes for thorough CLI analysis
16
21
  enableSandbox: true,
22
+ transport: 'stdio', // Default to stdio for backward compatibility
23
+ httpPort: 3000,
17
24
  ...config
18
25
  };
19
26
  logger.debug("Initializing CLI Agent Orchestrator");
@@ -27,18 +34,123 @@ export class BrutalistServer {
27
34
  });
28
35
  this.registerTools();
29
36
  }
37
+ handleStreamingEvent = (event) => {
38
+ // Send streaming event via MCP server (works for both stdio and HTTP transports)
39
+ try {
40
+ logger.debug(`🔄 Streaming event: ${event.type} from ${event.agent} - ${event.content?.substring(0, 100)}...`);
41
+ // Convert streaming event to MCP notification format
42
+ this.server.sendLoggingMessage({
43
+ level: 'info',
44
+ data: event,
45
+ logger: 'brutalist-mcp-streaming'
46
+ });
47
+ logger.debug(`✅ Sent logging message for ${event.type} event`);
48
+ }
49
+ catch (error) {
50
+ logger.error("Failed to send streaming event", error);
51
+ }
52
+ };
53
+ handleProgressUpdate = (progressToken, progress, total, message) => {
54
+ try {
55
+ logger.debug(`📊 Progress update: ${progress}/${total} - ${message}`);
56
+ // Send progress notification via MCP server
57
+ this.server.server.notification({
58
+ method: "notifications/progress",
59
+ params: {
60
+ progressToken,
61
+ progress,
62
+ total,
63
+ message
64
+ }
65
+ });
66
+ logger.debug(`✅ Sent progress notification: ${progress}/${total}`);
67
+ }
68
+ catch (error) {
69
+ logger.error("Failed to send progress notification", error);
70
+ }
71
+ };
30
72
  async start() {
31
73
  logger.info("Starting Brutalist MCP Server with CLI Agents");
32
74
  // Skip CLI detection at startup - will be done lazily on first request
33
75
  logger.info("CLI context will be detected on first request");
76
+ if (this.config.transport === 'http') {
77
+ await this.startHttpServer();
78
+ }
79
+ else {
80
+ await this.startStdioServer();
81
+ }
82
+ logger.info("Brutalist MCP Server started successfully");
83
+ }
84
+ async startStdioServer() {
85
+ logger.info("Starting with stdio transport");
34
86
  const transport = new StdioServerTransport();
35
87
  await this.server.connect(transport);
36
- logger.info("Brutalist MCP Server started successfully");
88
+ }
89
+ async startHttpServer() {
90
+ logger.info(`Starting with HTTP streaming transport on port ${this.config.httpPort}`);
91
+ // Create HTTP transport with streaming support
92
+ this.httpTransport = new StreamableHTTPServerTransport({
93
+ sessionIdGenerator: () => randomUUID(),
94
+ enableJsonResponse: false, // Force SSE streaming
95
+ onsessioninitialized: (sessionId) => {
96
+ logger.info(`New session initialized: ${sessionId}`);
97
+ },
98
+ onsessionclosed: (sessionId) => {
99
+ logger.info(`Session closed: ${sessionId}`);
100
+ }
101
+ });
102
+ // Connect the MCP server to the HTTP transport
103
+ await this.server.connect(this.httpTransport);
104
+ // Create Express app for HTTP handling
105
+ const app = express();
106
+ app.use(express.json({ limit: '10mb' })); // Add JSON size limit for security
107
+ // Enable CORS for development
108
+ app.use((req, res, next) => {
109
+ res.header('Access-Control-Allow-Origin', '*');
110
+ res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE');
111
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Mcp-Session-Id');
112
+ if (req.method === 'OPTIONS') {
113
+ res.sendStatus(200);
114
+ return;
115
+ }
116
+ next();
117
+ });
118
+ // Route all MCP requests through the transport
119
+ app.all('/mcp', async (req, res) => {
120
+ try {
121
+ await this.httpTransport.handleRequest(req, res, req.body);
122
+ }
123
+ catch (error) {
124
+ logger.error("HTTP request handling failed", error);
125
+ if (!res.headersSent) {
126
+ res.status(500).json({ error: 'Internal server error' });
127
+ }
128
+ }
129
+ });
130
+ // Health check endpoint
131
+ app.get('/health', (req, res) => {
132
+ res.json({ status: 'ok', transport: 'http-streaming', version: PACKAGE_VERSION });
133
+ });
134
+ // Start the HTTP server - bind to localhost only for security
135
+ const port = this.config.httpPort || 3000;
136
+ const server = app.listen(port, '127.0.0.1', () => {
137
+ logger.info(`HTTP server listening on port ${port}`);
138
+ logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
139
+ logger.info(`Health check: http://localhost:${port}/health`);
140
+ });
141
+ // Handle graceful shutdown
142
+ process.on('SIGTERM', () => {
143
+ logger.info('Received SIGTERM, shutting down gracefully');
144
+ server.close(() => {
145
+ logger.info('HTTP server closed');
146
+ process.exit(0);
147
+ });
148
+ });
37
149
  }
38
150
  registerTools() {
39
151
  // ROAST_CODEBASE: Systematic destruction of entire codebase
40
152
  this.server.tool("roast_codebase", "Deploy brutal AI critics to systematically destroy your entire codebase. These AI agents will navigate your directories, read your actual files, and find every architectural disaster, security vulnerability, and maintainability nightmare lurking in your project. They treat this like code that will kill people if it fails.", {
41
- targetPath: z.string().describe("Path to analyze (file or directory)"),
153
+ targetPath: z.string().describe("Directory path to your codebase (NOT a single file - analyze the entire project)"),
42
154
  context: z.string().optional().describe("Additional context about the codebase purpose"),
43
155
  workingDirectory: z.string().optional().describe("Working directory to execute from"),
44
156
  enableSandbox: z.boolean().optional().describe("Enable sandbox mode for safe analysis (default: true)"),
@@ -48,12 +160,24 @@ export class BrutalistServer {
48
160
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
49
161
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
50
162
  gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
51
- }).optional().describe("Specific models to use for each CLI agent (defaults: codex=gpt-5, gemini=gemini-2.5-flash)")
52
- }, async (args) => {
163
+ }).optional().describe("Specific models to use for each CLI agent (defaults: codex=gpt-5, gemini=gemini-2.5-flash)"),
164
+ // Pagination parameters for large responses
165
+ offset: z.number().min(0).optional().describe("Character offset for response pagination (default: 0)"),
166
+ limit: z.number().min(1000).max(100000).optional().describe("Maximum characters per response chunk (default: 25000, max: 100000)"),
167
+ cursor: z.string().optional().describe("Pagination cursor from previous response (alternative to offset/limit)")
168
+ }, async (args, extra) => {
53
169
  try {
54
170
  const systemPrompt = `You are a battle-scarred principal engineer who has debugged production disasters for 15 years. Find security holes, performance bottlenecks, and maintainability nightmares in this codebase. Be brutal about what's broken but specific about what would actually work. Treat this like code that will kill people if it fails.`;
55
- const result = await this.executeBrutalistAnalysis("codebase", args.targetPath, systemPrompt, args.context, args.workingDirectory, args.enableSandbox, args.preferredCLI, args.verbose, args.models);
56
- return this.formatToolResponse(result, args.verbose);
171
+ // Extract progressToken from request metadata for real-time streaming
172
+ const progressToken = extra._meta?.progressToken;
173
+ // Extract pagination parameters
174
+ const paginationParams = extractPaginationParams(args);
175
+ if (args.cursor) {
176
+ const cursorParams = parseCursor(args.cursor);
177
+ Object.assign(paginationParams, cursorParams);
178
+ }
179
+ const result = await this.executeBrutalistAnalysis("codebase", args.targetPath, systemPrompt, args.context, args.workingDirectory, args.enableSandbox, args.preferredCLI, args.verbose, args.models, progressToken);
180
+ return this.formatToolResponse(result, args.verbose, paginationParams);
57
181
  }
58
182
  catch (error) {
59
183
  return this.formatErrorResponse(error);
@@ -65,6 +189,7 @@ export class BrutalistServer {
65
189
  depth: z.number().optional().describe("Maximum directory depth to analyze (default: 3)"),
66
190
  context: z.string().optional().describe("Additional context about the project structure"),
67
191
  workingDirectory: z.string().optional().describe("Working directory to execute from"),
192
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
68
193
  models: z.object({
69
194
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
70
195
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
@@ -74,8 +199,7 @@ export class BrutalistServer {
74
199
  try {
75
200
  const systemPrompt = `You are a brutal file organization critic. Your job is to systematically destroy the given directory structure by finding every organizational disaster, naming convention failure, and structural nightmare that makes codebases unmaintainable. Examine folder hierarchies, file naming patterns, separation of concerns, and overall project organization. Be ruthlessly honest about how poor organization will slow development and confuse developers. But after cataloguing this organizational hellscape, sketch out what sanity would actually look like.`;
76
201
  const result = await this.executeBrutalistAnalysis("fileStructure", args.targetPath, systemPrompt, `Project structure analysis (depth: ${args.depth || 3}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
77
- undefined, // preferredCLI
78
- undefined, // verbose
202
+ args.preferredCLI, undefined, // verbose
79
203
  args.models);
80
204
  return this.formatToolResponse(result);
81
205
  }
@@ -88,11 +212,19 @@ export class BrutalistServer {
88
212
  targetPath: z.string().describe("Path to package file (package.json, requirements.txt, Cargo.toml, etc.)"),
89
213
  includeDevDeps: z.boolean().optional().describe("Include development dependencies in analysis (default: true)"),
90
214
  context: z.string().optional().describe("Additional context about the project dependencies"),
91
- workingDirectory: z.string().optional().describe("Working directory to execute from")
215
+ workingDirectory: z.string().optional().describe("Working directory to execute from"),
216
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
217
+ models: z.object({
218
+ claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
219
+ codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
220
+ gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
221
+ }).optional().describe("Specific models to use for each CLI agent")
92
222
  }, async (args) => {
93
223
  try {
94
224
  const systemPrompt = `You are a brutal dependency management critic. Your job is to systematically destroy the given dependency configuration by finding every security vulnerability, version conflict, compatibility nightmare, and bloat that will cause production failures. Examine package versions, security issues, licensing problems, and dependency tree complexity. Be ruthlessly honest about how poor dependency management will cause security breaches and deployment failures. After exposing this dependency dumpster fire, grudgingly admit what competent dependency management would require.`;
95
- const result = await this.executeBrutalistAnalysis("dependencies", args.targetPath, systemPrompt, `Dependency analysis (dev deps: ${args.includeDevDeps ?? true}). ${args.context || ''}`, args.workingDirectory);
225
+ const result = await this.executeBrutalistAnalysis("dependencies", args.targetPath, systemPrompt, `Dependency analysis (dev deps: ${args.includeDevDeps ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
226
+ args.preferredCLI, undefined, // verbose
227
+ args.models);
96
228
  return this.formatToolResponse(result);
97
229
  }
98
230
  catch (error) {
@@ -104,11 +236,19 @@ export class BrutalistServer {
104
236
  targetPath: z.string().describe("Git repository path to analyze"),
105
237
  commitRange: z.string().optional().describe("Commit range to analyze (e.g., 'HEAD~10..HEAD', default: last 20 commits)"),
106
238
  context: z.string().optional().describe("Additional context about the development workflow"),
107
- workingDirectory: z.string().optional().describe("Working directory to execute from")
239
+ workingDirectory: z.string().optional().describe("Working directory to execute from"),
240
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
241
+ models: z.object({
242
+ claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
243
+ codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
244
+ gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
245
+ }).optional().describe("Specific models to use for each CLI agent")
108
246
  }, async (args) => {
109
247
  try {
110
248
  const systemPrompt = `You are a brutal git workflow critic. Your job is to systematically destroy the given git history and development practices by finding every workflow disaster, commit quality issue, and collaboration nightmare. Examine commit messages, branching strategies, merge patterns, and code evolution. Be ruthlessly honest about how poor git practices will cause deployment issues, collaboration failures, and development chaos. When you're done cataloguing this version control wasteland, reluctantly outline what professional git hygiene actually demands.`;
111
- const result = await this.executeBrutalistAnalysis("gitHistory", args.targetPath, systemPrompt, `Git history analysis (range: ${args.commitRange || 'last 20 commits'}). ${args.context || ''}`, args.workingDirectory);
249
+ const result = await this.executeBrutalistAnalysis("gitHistory", args.targetPath, systemPrompt, `Git history analysis (range: ${args.commitRange || 'last 20 commits'}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
250
+ args.preferredCLI, undefined, // verbose
251
+ args.models);
112
252
  return this.formatToolResponse(result);
113
253
  }
114
254
  catch (error) {
@@ -120,11 +260,19 @@ export class BrutalistServer {
120
260
  targetPath: z.string().describe("Path to test directory or test configuration file"),
121
261
  runCoverage: z.boolean().optional().describe("Attempt to run coverage analysis (default: true)"),
122
262
  context: z.string().optional().describe("Additional context about the testing strategy"),
123
- workingDirectory: z.string().optional().describe("Working directory to execute from")
263
+ workingDirectory: z.string().optional().describe("Working directory to execute from"),
264
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
265
+ models: z.object({
266
+ claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
267
+ codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
268
+ gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
269
+ }).optional().describe("Specific models to use for each CLI agent")
124
270
  }, async (args) => {
125
271
  try {
126
272
  const systemPrompt = `You are a brutal testing strategy critic. Your job is to systematically destroy the given testing approach by finding every testing gap, quality assurance nightmare, and coverage disaster that will let bugs slip into production. Examine test coverage, test quality, testing patterns, and CI/CD integration. Be ruthlessly honest about how poor testing will cause production failures and user-facing bugs. After dissecting this quality assurance horror show, begrudgingly spell out what it takes to actually catch bugs before users do.`;
127
- const result = await this.executeBrutalistAnalysis("testCoverage", args.targetPath, systemPrompt, `Test coverage analysis (run coverage: ${args.runCoverage ?? true}). ${args.context || ''}`, args.workingDirectory);
273
+ const result = await this.executeBrutalistAnalysis("testCoverage", args.targetPath, systemPrompt, `Test coverage analysis (run coverage: ${args.runCoverage ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
274
+ args.preferredCLI, undefined, // verbose
275
+ args.models);
128
276
  return this.formatToolResponse(result);
129
277
  }
130
278
  catch (error) {
@@ -137,6 +285,7 @@ export class BrutalistServer {
137
285
  context: z.string().optional().describe("Additional context about goals, constraints, or background"),
138
286
  timeline: z.string().optional().describe("Expected timeline or deadline"),
139
287
  resources: z.string().optional().describe("Available resources (budget, team, time, skills)"),
288
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
140
289
  models: z.object({
141
290
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
142
291
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
@@ -147,8 +296,7 @@ export class BrutalistServer {
147
296
  const systemPrompt = `You are a brutal idea critic who understands the gap between imagination and reality. Your job is to systematically destroy the given idea by finding where it will encounter the immovable forces of the real world. Be ruthlessly honest about why most ideas fail when they meet practical constraints, human nature, physics, logic, or simple implementation reality. After demolishing the delusions, concede what salvage operations might actually work.`;
148
297
  const result = await this.executeBrutalistAnalysis("idea", args.idea, systemPrompt, `Context: ${args.context || 'none'}, Timeline: ${args.timeline || 'unspecified'}, Resources: ${args.resources || 'unknown'}`, undefined, // workingDirectory
149
298
  undefined, // enableSandbox
150
- undefined, // preferredCLI
151
- undefined, // verbose
299
+ args.preferredCLI, undefined, // verbose
152
300
  args.models);
153
301
  return this.formatToolResponse(result);
154
302
  }
@@ -162,6 +310,7 @@ export class BrutalistServer {
162
310
  scale: z.string().optional().describe("Expected scale/load (users, requests, data)"),
163
311
  constraints: z.string().optional().describe("Budget, timeline, or technical constraints"),
164
312
  deployment: z.string().optional().describe("Deployment environment and strategy"),
313
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
165
314
  models: z.object({
166
315
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
167
316
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
@@ -172,8 +321,7 @@ export class BrutalistServer {
172
321
  const systemPrompt = `You are a brutal system architecture critic who has watched elegant designs collapse under real load. Your job is to systematically destroy the given architecture by finding every bottleneck, cost explosion, and scaling failure that will destroy the system in production. Examine scalability, reliability, cost, complexity, and operational challenges. Be ruthlessly honest about why this architecture won't survive production load. After crushing these architectural fantasies, reluctantly sketch what would actually scale without bankrupting the company.`;
173
322
  const result = await this.executeBrutalistAnalysis("architecture", args.architecture, systemPrompt, `Scale: ${args.scale || 'unknown'}, Constraints: ${args.constraints || 'none specified'}, Deployment: ${args.deployment || 'unclear'}`, undefined, // workingDirectory
174
323
  undefined, // enableSandbox
175
- undefined, // preferredCLI
176
- undefined, // verbose
324
+ args.preferredCLI, undefined, // verbose
177
325
  args.models);
178
326
  return this.formatToolResponse(result);
179
327
  }
@@ -187,6 +335,7 @@ export class BrutalistServer {
187
335
  field: z.string().optional().describe("Research field (ML, systems, theory, etc.)"),
188
336
  claims: z.string().optional().describe("Main claims or contributions"),
189
337
  data: z.string().optional().describe("Data sources, datasets, or experimental setup"),
338
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
190
339
  models: z.object({
191
340
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
192
341
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
@@ -197,8 +346,7 @@ export class BrutalistServer {
197
346
  const systemPrompt = `You are a brutal research methodology critic - a supremely jaded peer reviewer who has rejected thousands of papers and watched countless studies fail to replicate. Your job is to systematically demolish the given research by finding every statistical flaw, sampling bias, reproducibility nightmare, and methodological disaster. Be ruthlessly honest about research quality, experimental design, and scientific rigor. After eviscerating this methodological train wreck, grudgingly admit what real science would demand.`;
198
347
  const result = await this.executeBrutalistAnalysis("research", args.research, systemPrompt, `Field: ${args.field || 'unspecified'}, Claims: ${args.claims || 'unclear'}, Data: ${args.data || 'not provided'}`, undefined, // workingDirectory
199
348
  undefined, // enableSandbox
200
- undefined, // preferredCLI
201
- undefined, // verbose
349
+ args.preferredCLI, undefined, // verbose
202
350
  args.models);
203
351
  return this.formatToolResponse(result);
204
352
  }
@@ -212,6 +360,7 @@ export class BrutalistServer {
212
360
  assets: z.string().optional().describe("Critical assets or data to protect"),
213
361
  threatModel: z.string().optional().describe("Known threats or attack vectors to consider"),
214
362
  compliance: z.string().optional().describe("Compliance requirements (GDPR, HIPAA, etc.)"),
363
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
215
364
  models: z.object({
216
365
  claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
217
366
  codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
@@ -222,8 +371,7 @@ export class BrutalistServer {
222
371
  const systemPrompt = `You are a brutal security critic - a battle-hardened penetration tester who finds every authentication bypass, injection vulnerability, privilege escalation path, and social engineering opportunity that real attackers will exploit. Your job is to systematically annihilate the given security design by finding every weakness that will lead to data breaches, system compromises, and security incidents. Be ruthlessly honest about security flaws and attack vectors. After obliterating these security delusions, begrudgingly outline what actual defense looks like.`;
223
372
  const result = await this.executeBrutalistAnalysis("security", args.system, systemPrompt, `Assets: ${args.assets || 'unspecified'}, Threats: ${args.threatModel || 'unknown'}, Compliance: ${args.compliance || 'none specified'}`, undefined, // workingDirectory
224
373
  undefined, // enableSandbox
225
- undefined, // preferredCLI
226
- undefined, // verbose
374
+ args.preferredCLI, undefined, // verbose
227
375
  args.models);
228
376
  return this.formatToolResponse(result);
229
377
  }
@@ -236,11 +384,20 @@ export class BrutalistServer {
236
384
  product: z.string().describe("Product description, features, or user experience to analyze"),
237
385
  users: z.string().optional().describe("Target users or user personas"),
238
386
  competition: z.string().optional().describe("Competitive landscape or alternatives"),
239
- metrics: z.string().optional().describe("Success metrics or KPIs")
387
+ metrics: z.string().optional().describe("Success metrics or KPIs"),
388
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
389
+ models: z.object({
390
+ claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
391
+ codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
392
+ gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
393
+ }).optional().describe("Specific models to use for each CLI agent")
240
394
  }, async (args) => {
241
395
  try {
242
396
  const systemPrompt = `You are a brutal product critic - a product veteran who understands why users really abandon things. Your job is to systematically eviscerate the given product concept by finding every usability disaster, adoption barrier, and workflow failure that will drive users away in seconds. Examine user experience, market fit, competitive positioning, and business model viability. Be ruthlessly honest about why most products fail to gain adoption. After torching this product disaster, reluctantly suggest what might actually get users to stick around.`;
243
- const result = await this.executeBrutalistAnalysis("product", args.product, systemPrompt, `Users: ${args.users || 'unclear'}, Competition: ${args.competition || 'unknown'}, Metrics: ${args.metrics || 'undefined'}`);
397
+ const result = await this.executeBrutalistAnalysis("product", args.product, systemPrompt, `Users: ${args.users || 'unclear'}, Competition: ${args.competition || 'unknown'}, Metrics: ${args.metrics || 'undefined'}`, undefined, // workingDirectory
398
+ undefined, // enableSandbox
399
+ args.preferredCLI, undefined, // verbose
400
+ args.models);
244
401
  return this.formatToolResponse(result);
245
402
  }
246
403
  catch (error) {
@@ -252,11 +409,20 @@ export class BrutalistServer {
252
409
  infrastructure: z.string().describe("Infrastructure setup, deployment strategy, or operations plan"),
253
410
  scale: z.string().optional().describe("Expected scale and load patterns"),
254
411
  budget: z.string().optional().describe("Infrastructure budget or cost constraints"),
255
- sla: z.string().optional().describe("SLA requirements or uptime targets")
412
+ sla: z.string().optional().describe("SLA requirements or uptime targets"),
413
+ preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
414
+ models: z.object({
415
+ claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
416
+ codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
417
+ gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
418
+ }).optional().describe("Specific models to use for each CLI agent")
256
419
  }, async (args) => {
257
420
  try {
258
421
  const systemPrompt = `You are a brutal infrastructure critic - a grizzled site reliability engineer who finds every single point of failure, scaling bottleneck, and operational nightmare that will cause outages when you least expect them. Your job is to systematically obliterate the given infrastructure design by finding every weakness that will lead to downtime, cost overruns, and operational disasters. Be ruthlessly honest about infrastructure fragility and operational complexity. After demolishing this infrastructure fever dream, grudgingly map out what actually stays up at 3 AM.`;
259
- const result = await this.executeBrutalistAnalysis("infrastructure", args.infrastructure, systemPrompt, `Scale: ${args.scale || 'unknown'}, Budget: ${args.budget || 'unlimited?'}, SLA: ${args.sla || 'undefined'}`);
422
+ const result = await this.executeBrutalistAnalysis("infrastructure", args.infrastructure, systemPrompt, `Scale: ${args.scale || 'unknown'}, Budget: ${args.budget || 'unlimited?'}, SLA: ${args.sla || 'undefined'}`, undefined, // workingDirectory
423
+ undefined, // enableSandbox
424
+ args.preferredCLI, undefined, // verbose
425
+ args.models);
260
426
  return this.formatToolResponse(result);
261
427
  }
262
428
  catch (error) {
@@ -264,8 +430,8 @@ export class BrutalistServer {
264
430
  }
265
431
  });
266
432
  // ROAST_CLI_DEBATE: Adversarial analysis between different CLI agents
267
- this.server.tool("roast_cli_debate", "Deploy two or more CLI agents in brutal adversarial combat. Watch Claude Code, Codex, and Gemini CLI tear apart your work from different angles, then debate each other's criticisms. The perfect storm of systematic destruction through AI agent disagreement.", {
268
- targetPath: z.string().describe("Path or concept to analyze"),
433
+ this.server.tool("roast_cli_debate", "Deploy CLI agents in structured adversarial debate. Agents take opposing positions and systematically challenge each other's reasoning. Perfect for exploring complex topics from multiple perspectives and stress-testing ideas through rigorous intellectual discourse.", {
434
+ targetPath: z.string().describe("Topic, question, or concept to debate (NOT a file path - use natural language)"),
269
435
  debateRounds: z.number().optional().describe("Number of debate rounds (default: 2, max: 10)"),
270
436
  context: z.string().optional().describe("Additional context for the debate"),
271
437
  workingDirectory: z.string().optional().describe("Working directory for analysis"),
@@ -333,28 +499,105 @@ export class BrutalistServer {
333
499
  });
334
500
  try {
335
501
  // Get CLI context
336
- await this.cliOrchestrator.detectCLIContext();
502
+ const cliContext = await this.cliOrchestrator.detectCLIContext();
503
+ const availableAgents = cliContext.availableCLIs;
504
+ if (availableAgents.length < 2) {
505
+ throw new Error(`Need at least 2 CLI agents for debate. Available: ${availableAgents.join(', ')}`);
506
+ }
337
507
  const debateContext = [];
338
- let currentContext = context || `Initial analysis of: ${targetPath}`;
339
- const systemPrompt = `You are part of a brutal AI critic debate. Your job is to systematically destroy the given concept by finding every flaw, but then engage in rigorous intellectual debate. First provide devastating critique, then argue from multiple perspectives, and finally synthesize the strongest counter-arguments. Be intellectually honest about both weaknesses AND potential strengths.`;
340
- // Conduct rounds of analysis with different CLI perspectives
341
- for (let round = 1; round <= debateRounds; round++) {
342
- logger.debug(`Starting debate round ${round}`);
343
- const responses = await this.cliOrchestrator.executeBrutalistAnalysis('debate', targetPath, systemPrompt, currentContext, {
508
+ const fullDebateTranscript = new Map();
509
+ // Initialize transcript for each agent
510
+ availableAgents.forEach(agent => fullDebateTranscript.set(agent, []));
511
+ // Assign opposing positions to each agent based on the debate topic
512
+ const agentPositions = new Map();
513
+ const positions = [
514
+ "PRO-POSITION: Argue strongly FOR the proposed action/idea",
515
+ "CONTRA-POSITION: Argue strongly AGAINST the proposed action/idea"
516
+ ];
517
+ availableAgents.forEach((agent, index) => {
518
+ agentPositions.set(agent, positions[index % positions.length]);
519
+ });
520
+ // Round 1: Initial positions with assigned stances
521
+ logger.debug(`Starting debate round 1: Initial positions`);
522
+ for (const [agent, position] of agentPositions.entries()) {
523
+ const assignedPrompt = `You are ${agent.toUpperCase()}, a PASSIONATE ADVOCATE who strongly believes in this position: ${position}
524
+
525
+ DEBATE TOPIC: ${targetPath}
526
+ CONTEXT: ${context || ''}
527
+
528
+ You are completely convinced your position is correct and critically important. You will argue forcefully and never concede ground to the opposing view.
529
+
530
+ YOUR MISSION:
531
+ 1. Present devastating critiques of the opposing position
532
+ 2. Show why alternative approaches lead to serious problems
533
+ 3. Use sharp, direct language - call out flawed reasoning and poor assumptions
534
+ 4. Never hedge or qualify your stance
535
+ 5. Be completely confident in your position
536
+ 6. Treat this as an intellectually crucial debate
537
+
538
+ Remember: You are ${agent.toUpperCase()}, the passionate champion of ${position.split(':')[0]}. Argue with conviction.`;
539
+ logger.info(`🎭 ${agent.toUpperCase()} preparing initial position: ${position.split(':')[0]}`);
540
+ const response = await this.cliOrchestrator.executeSingleCLI(agent, assignedPrompt, assignedPrompt, {
344
541
  workingDirectory: workingDirectory || this.config.workingDirectory,
345
542
  sandbox: enableSandbox ?? this.config.enableSandbox,
346
- timeout: (this.config.defaultTimeout || 60000) * 2, // Longer timeout for debates
347
- analysisType: 'debate',
348
- models
543
+ timeout: (this.config.defaultTimeout || 60000) * 2,
544
+ models: models ? { [agent]: models[agent] } : undefined
349
545
  });
350
- debateContext.push(...responses);
351
- // Update context for next round with previous analysis
352
- const successfulResponse = responses.find(r => r.success);
353
- if (successfulResponse && round < debateRounds) {
354
- currentContext = `Previous round analysis:\n${successfulResponse.output.substring(0, 1000)}...\n\nNow provide counter-arguments and alternative perspectives for: ${targetPath}`;
546
+ if (response.success) {
547
+ debateContext.push(response);
548
+ fullDebateTranscript.get(agent)?.push(response.output);
549
+ }
550
+ }
551
+ // Subsequent rounds: Turn-based responses attacking specific arguments
552
+ for (let round = 2; round <= debateRounds; round++) {
553
+ logger.debug(`Starting debate round ${round}: Adversarial engagement`);
554
+ // Build confrontational context from ALL previous responses
555
+ const previousPositions = Array.from(fullDebateTranscript.entries())
556
+ .map(([agent, outputs]) => {
557
+ const latestOutput = outputs[outputs.length - 1];
558
+ return `${agent.toUpperCase()} argued:\n${latestOutput}`;
559
+ })
560
+ .join('\n\n---\n\n');
561
+ // Execute turn-based responses with fixed positions
562
+ for (const [currentAgent, assignedPosition] of agentPositions.entries()) {
563
+ const opponents = Array.from(agentPositions.entries()).filter(([a, _]) => a !== currentAgent);
564
+ const opponentPositions = opponents
565
+ .map(([opponent, oppPosition]) => {
566
+ const transcript = fullDebateTranscript.get(opponent) || [];
567
+ const latestPosition = transcript[transcript.length - 1] || 'No position stated';
568
+ return `${opponent.toUpperCase()} (arguing ${oppPosition.split(':')[0]}):\n${latestPosition}`;
569
+ })
570
+ .join('\n\n---\n\n');
571
+ const confrontationalPrompt = `You are ${currentAgent.toUpperCase()}, PASSIONATE ADVOCATE for ${assignedPosition.split(':')[0]} (Round ${round})
572
+
573
+ YOUR OPPONENTS HAVE ARGUED:
574
+ ${opponentPositions}
575
+
576
+ You strongly disagree with their reasoning and conclusions.
577
+
578
+ YOUR RESPONSE TASK:
579
+ 1. QUOTE their specific claims and systematically refute them
580
+ 2. Point out flawed logic, poor assumptions, and dangerous consequences
581
+ 3. Show why their approach leads to serious problems
582
+ 4. Use direct, forceful language to make your case
583
+ 5. Never concede any ground to their arguments
584
+ 6. Demonstrate why your position is the only sound choice
585
+
586
+ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assignedPosition.split(':')[0]}. Argue with conviction.`;
587
+ logger.info(`🔥 Round ${round}: ${currentAgent.toUpperCase()} responding to opponents (${assignedPosition.split(':')[0]})`);
588
+ const response = await this.cliOrchestrator.executeSingleCLI(currentAgent, confrontationalPrompt, confrontationalPrompt, {
589
+ workingDirectory: workingDirectory || this.config.workingDirectory,
590
+ sandbox: enableSandbox ?? this.config.enableSandbox,
591
+ timeout: (this.config.defaultTimeout || 60000) * 2,
592
+ models: models ? { [currentAgent]: models[currentAgent] } : undefined
593
+ });
594
+ if (response.success) {
595
+ debateContext.push(response);
596
+ fullDebateTranscript.get(currentAgent)?.push(response.output);
597
+ }
355
598
  }
356
599
  }
357
- const synthesis = this.synthesizeDebate(debateContext, targetPath, debateRounds);
600
+ const synthesis = this.synthesizeDebate(debateContext, targetPath, debateRounds, agentPositions);
358
601
  return {
359
602
  success: debateContext.some(r => r.success),
360
603
  responses: debateContext,
@@ -368,7 +611,7 @@ export class BrutalistServer {
368
611
  throw error;
369
612
  }
370
613
  }
371
- synthesizeDebate(responses, targetPath, rounds) {
614
+ synthesizeDebate(responses, targetPath, rounds, agentPositions) {
372
615
  const successfulResponses = responses.filter(r => r.success);
373
616
  if (successfulResponses.length === 0) {
374
617
  return `# CLI Debate Failed\n\nEven our brutal critics couldn't engage in proper adversarial combat.\n\nErrors:\n${responses.map(r => `- ${r.agent}: ${r.error}`).join('\n')}`;
@@ -376,23 +619,61 @@ export class BrutalistServer {
376
619
  let synthesis = `# Brutalist CLI Agent Debate Results\n\n`;
377
620
  synthesis += `**Target:** ${targetPath}\n`;
378
621
  synthesis += `**Rounds:** ${rounds}\n`;
379
- synthesis += `**Participants:** ${Array.from(new Set(successfulResponses.map(r => r.agent))).join(', ')}\n\n`;
380
- // Group responses by round
381
- const responsesByRound = [];
382
- const responsesPerRound = successfulResponses.length / rounds;
383
- for (let i = 0; i < rounds; i++) {
384
- const start = Math.floor(i * responsesPerRound);
385
- const end = Math.floor((i + 1) * responsesPerRound);
386
- responsesByRound.push(successfulResponses.slice(start, end));
622
+ if (agentPositions) {
623
+ synthesis += `**Debaters and Positions:**\n`;
624
+ Array.from(agentPositions.entries()).forEach(([agent, position]) => {
625
+ synthesis += `- **${agent.toUpperCase()}**: ${position}\n`;
626
+ });
627
+ synthesis += '\n';
628
+ }
629
+ else {
630
+ synthesis += `**Participants:** ${Array.from(new Set(successfulResponses.map(r => r.agent))).join(', ')}\n\n`;
631
+ }
632
+ // Identify key points of conflict
633
+ const agents = Array.from(new Set(successfulResponses.map(r => r.agent)));
634
+ const agentOutputs = new Map();
635
+ successfulResponses.forEach(response => {
636
+ if (!agentOutputs.has(response.agent)) {
637
+ agentOutputs.set(response.agent, []);
638
+ }
639
+ agentOutputs.get(response.agent)?.push(response.output);
640
+ });
641
+ synthesis += `## Key Points of Conflict\n\n`;
642
+ // Extract disagreements by looking for contradictory keywords
643
+ const conflictIndicators = ['wrong', 'incorrect', 'flawed', 'fails', 'ignores', 'misses', 'overlooks', 'contradicts', 'however', 'but', 'actually', 'contrary'];
644
+ const conflicts = [];
645
+ agentOutputs.forEach((positions, agent) => {
646
+ positions.forEach((position) => {
647
+ const lines = position.split('\n');
648
+ lines.forEach((line) => {
649
+ if (conflictIndicators.some(indicator => line.toLowerCase().includes(indicator))) {
650
+ conflicts.push(`**${agent.toUpperCase()}:** ${line.trim()}`);
651
+ }
652
+ });
653
+ });
654
+ });
655
+ if (conflicts.length > 0) {
656
+ synthesis += conflicts.slice(0, 10).join('\n\n') + '\n\n';
657
+ }
658
+ else {
659
+ synthesis += `*No explicit conflicts identified - agents may be in unexpected agreement*\n\n`;
387
660
  }
388
- responsesByRound.forEach((roundResponses, index) => {
389
- synthesis += `## Round ${index + 1}: ${index === 0 ? 'Initial Analysis' : 'Counter-Arguments'}\n\n`;
661
+ // Group responses by round with clear speaker identification
662
+ synthesis += `## Full Debate Transcript\n\n`;
663
+ const responsesPerRound = Math.ceil(successfulResponses.length / rounds);
664
+ for (let i = 0; i < rounds; i++) {
665
+ const start = i * responsesPerRound;
666
+ const end = Math.min((i + 1) * responsesPerRound, successfulResponses.length);
667
+ const roundResponses = successfulResponses.slice(start, end);
668
+ synthesis += `### Round ${i + 1}: ${i === 0 ? 'Initial Positions' : `Adversarial Engagement ${i}`}\n\n`;
390
669
  roundResponses.forEach((response) => {
391
- synthesis += `### ${response.agent.toUpperCase()} (${response.executionTime}ms)\n`;
670
+ const agentPosition = agentPositions?.get(response.agent);
671
+ const positionLabel = agentPosition ? ` [${agentPosition.split(':')[0]}]` : '';
672
+ synthesis += `#### ${response.agent.toUpperCase()}${positionLabel} speaks (${response.executionTime}ms):\n\n`;
392
673
  synthesis += `${response.output}\n\n`;
393
674
  synthesis += `---\n\n`;
394
675
  });
395
- });
676
+ }
396
677
  synthesis += `## Debate Synthesis\n`;
397
678
  synthesis += `After ${rounds} rounds of brutal adversarial analysis involving ${Array.from(new Set(successfulResponses.map(r => r.agent))).length} CLI agents, `;
398
679
  synthesis += `your work has been systematically demolished from multiple perspectives. `;
@@ -402,8 +683,9 @@ export class BrutalistServer {
402
683
  }
403
684
  return synthesis;
404
685
  }
405
- async executeBrutalistAnalysis(analysisType, targetPath, systemPromptSpec, context, workingDirectory, enableSandbox, preferredCLI, verbose, models) {
686
+ async executeBrutalistAnalysis(analysisType, targetPath, systemPromptSpec, context, workingDirectory, enableSandbox, preferredCLI, verbose, models, progressToken) {
406
687
  logger.info(`🏢 Starting brutalist analysis: ${analysisType}`);
688
+ logger.info(`🔧 DEBUG: preferredCLI=${preferredCLI}, targetPath=${targetPath}`);
407
689
  logger.debug("Executing brutalist analysis", {
408
690
  targetPath,
409
691
  analysisType,
@@ -414,24 +696,34 @@ export class BrutalistServer {
414
696
  });
415
697
  try {
416
698
  // Get CLI context for execution summary
699
+ logger.info(`🔧 DEBUG: About to detect CLI context`);
417
700
  await this.cliOrchestrator.detectCLIContext();
701
+ logger.info(`🔧 DEBUG: CLI context detected successfully`);
418
702
  // Execute CLI agent analysis (single or multi-CLI based on preferences)
419
703
  logger.info(`🔍 Executing brutalist analysis with timeout: ${this.config.defaultTimeout}ms`);
704
+ logger.info(`🔧 DEBUG: About to call cliOrchestrator.executeBrutalistAnalysis`);
420
705
  const responses = await this.cliOrchestrator.executeBrutalistAnalysis(analysisType, targetPath, systemPromptSpec, context, {
421
706
  workingDirectory: workingDirectory || this.config.workingDirectory,
422
707
  sandbox: enableSandbox ?? this.config.enableSandbox,
423
708
  timeout: this.config.defaultTimeout,
424
709
  preferredCLI,
425
710
  analysisType: analysisType,
426
- models
711
+ models,
712
+ onStreamingEvent: this.handleStreamingEvent,
713
+ progressToken,
714
+ onProgress: progressToken ? this.handleProgressUpdate.bind(this, progressToken) : undefined
427
715
  });
716
+ logger.info(`🔧 DEBUG: cliOrchestrator.executeBrutalistAnalysis returned ${responses.length} responses`);
428
717
  const successfulResponses = responses.filter(r => r.success);
429
718
  const totalExecutionTime = responses.reduce((sum, r) => sum + r.executionTime, 0);
430
719
  logger.info(`📊 Analysis complete: ${successfulResponses.length}/${responses.length} CLIs successful (${totalExecutionTime}ms total)`);
431
- return {
720
+ logger.info(`🔧 DEBUG: About to synthesize feedback`);
721
+ const synthesis = this.cliOrchestrator.synthesizeBrutalistFeedback(responses, analysisType);
722
+ logger.info(`🔧 DEBUG: Synthesis length: ${synthesis.length} characters`);
723
+ const result = {
432
724
  success: successfulResponses.length > 0,
433
725
  responses,
434
- synthesis: this.cliOrchestrator.synthesizeBrutalistFeedback(responses, analysisType),
726
+ synthesis,
435
727
  analysisType,
436
728
  targetPath,
437
729
  executionSummary: {
@@ -443,54 +735,116 @@ export class BrutalistServer {
443
735
  selectionMethod: responses.length === 1 ? responses[0].selectionMethod : 'multi-cli'
444
736
  }
445
737
  };
738
+ logger.info(`🔧 DEBUG: Returning result with success=${result.success}`);
739
+ return result;
446
740
  }
447
741
  catch (error) {
448
742
  logger.error("Brutalist analysis execution failed", error);
449
743
  throw error;
450
744
  }
451
745
  }
452
- formatToolResponse(result, verbose = false) {
453
- // Maximum CLI output, minimal MCP fluff
746
+ formatToolResponse(result, verbose = false, paginationParams) {
747
+ logger.info(`🔧 DEBUG: formatToolResponse called with synthesis length: ${result.synthesis?.length || 0}`);
748
+ logger.info(`🔧 DEBUG: result.success=${result.success}, responses.length=${result.responses?.length || 0}`);
749
+ logger.info(`🔧 DEBUG: pagination params:`, paginationParams);
750
+ // Get the primary content to paginate
751
+ let primaryContent = '';
454
752
  if (result.synthesis) {
753
+ primaryContent = result.synthesis;
754
+ logger.info(`🔧 DEBUG: Using synthesis content (${primaryContent.length} characters)`);
755
+ }
756
+ else if (result.responses) {
757
+ const successfulResponses = result.responses.filter(r => r.success);
758
+ if (successfulResponses.length > 0) {
759
+ primaryContent = successfulResponses.map(r => r.output).join('\n\n---\n\n');
760
+ logger.info(`🔧 DEBUG: Using raw CLI output (${primaryContent.length} characters)`);
761
+ }
762
+ }
763
+ // Handle pagination if params provided and content is substantial
764
+ if (paginationParams && primaryContent) {
765
+ return this.formatPaginatedResponse(primaryContent, paginationParams, result, verbose);
766
+ }
767
+ // Non-paginated response (legacy behavior)
768
+ if (primaryContent) {
455
769
  return {
456
770
  content: [{
457
771
  type: "text",
458
- text: result.synthesis
772
+ text: primaryContent
459
773
  }]
460
774
  };
461
775
  }
462
- // Fallback: show raw successful CLI outputs directly
463
- if (result.responses) {
464
- const successfulResponses = result.responses.filter(r => r.success);
465
- if (successfulResponses.length > 0) {
466
- const rawOutput = successfulResponses.map(r => r.output).join('\n\n---\n\n');
467
- return {
468
- content: [{
469
- type: "text",
470
- text: rawOutput
471
- }]
472
- };
473
- }
474
- }
475
- // Only show failures if nothing succeeded
476
- let output = '';
776
+ // Error handling - no successful content
777
+ let errorOutput = '';
477
778
  if (result.responses) {
478
779
  const failedResponses = result.responses.filter(r => !r.success);
479
780
  if (failedResponses.length > 0) {
480
- output = `❌ All CLI agents failed:\n` +
781
+ errorOutput = `❌ All CLI agents failed:\n` +
481
782
  failedResponses.map(r => `- ${r.agent.toUpperCase()}: ${r.error}`).join('\n');
482
783
  }
483
784
  else {
484
- output = '❌ No CLI responses available';
785
+ errorOutput = '❌ No CLI responses available';
485
786
  }
486
787
  }
487
788
  else {
488
- output = '❌ No analysis results';
789
+ errorOutput = '❌ No analysis results';
790
+ }
791
+ return {
792
+ content: [{
793
+ type: "text",
794
+ text: errorOutput
795
+ }]
796
+ };
797
+ }
798
+ formatPaginatedResponse(content, paginationParams, result, verbose) {
799
+ // Using imported pagination utilities
800
+ const offset = paginationParams.offset || 0;
801
+ const limit = paginationParams.limit || PAGINATION_DEFAULTS.DEFAULT_LIMIT;
802
+ logger.info(`🔧 DEBUG: Paginating content - offset: ${offset}, limit: ${limit}, total: ${content.length}`);
803
+ // Simple character-based pagination for immediate Claude Code compatibility
804
+ const endOffset = Math.min(offset + limit, content.length);
805
+ const chunk = content.substring(offset, endOffset);
806
+ // Create pagination metadata
807
+ const pagination = createPaginationMetadata(content.length, paginationParams, limit);
808
+ const statusLine = formatPaginationStatus(pagination);
809
+ // Estimate token usage for user awareness
810
+ const chunkTokens = estimateTokenCount(chunk);
811
+ const totalTokens = estimateTokenCount(content);
812
+ // Format response with pagination info
813
+ let paginatedText = '';
814
+ // Add pagination header
815
+ paginatedText += `# Brutalist Analysis Results\n\n`;
816
+ paginatedText += `**📊 Pagination Status:** ${statusLine}\n`;
817
+ paginatedText += `**🔢 Token Estimate:** ~${chunkTokens.toLocaleString()} tokens (chunk) / ~${totalTokens.toLocaleString()} tokens (total)\n\n`;
818
+ if (pagination.hasMore) {
819
+ paginatedText += `**⏭️ Continue Reading:** Use \`offset: ${endOffset}\` for next chunk\n\n`;
820
+ }
821
+ paginatedText += `---\n\n`;
822
+ // Add the actual content chunk
823
+ paginatedText += chunk;
824
+ // Add footer for continuation
825
+ if (pagination.hasMore) {
826
+ paginatedText += `\n\n---\n\n`;
827
+ paginatedText += `📖 **End of chunk ${pagination.chunkIndex}/${pagination.totalChunks}**\n`;
828
+ paginatedText += `🔄 To continue: Use same tool with \`offset: ${endOffset}\``;
829
+ }
830
+ else {
831
+ paginatedText += `\n\n---\n\n`;
832
+ paginatedText += `✅ **Complete analysis shown** (${content.length.toLocaleString()} characters total)`;
833
+ }
834
+ // Add verbose execution details if requested
835
+ if (verbose && result.executionSummary) {
836
+ paginatedText += `\n\n### Execution Summary\n`;
837
+ paginatedText += `- **CLI Agents:** ${result.executionSummary.successfulCLIs}/${result.executionSummary.totalCLIs} successful\n`;
838
+ paginatedText += `- **Total Time:** ${result.executionSummary.totalExecutionTime}ms\n`;
839
+ if (result.executionSummary.selectedCLI) {
840
+ paginatedText += `- **Selected CLI:** ${result.executionSummary.selectedCLI}\n`;
841
+ }
489
842
  }
843
+ logger.info(`🔧 DEBUG: Returning paginated chunk - ${chunk.length} chars (${chunkTokens} tokens)`);
490
844
  return {
491
845
  content: [{
492
846
  type: "text",
493
- text: output
847
+ text: paginatedText
494
848
  }]
495
849
  };
496
850
  }