tachibot-mcp 2.7.4 โ†’ 2.7.5

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.
@@ -17,6 +17,7 @@
17
17
  * - Interface Segregation: Small, focused function signatures
18
18
  * - Dependency Inversion: Functions depend on types, not concrete implementations
19
19
  */
20
+ import { icon } from '../../../utils/ink-renderer.js';
20
21
  // ============================================================================
21
22
  // Tone Detection
22
23
  // ============================================================================
@@ -153,10 +154,10 @@ export const findThirdWay = (config) => {
153
154
  * Synthesize all challenges into coherent report
154
155
  */
155
156
  export const synthesizeChallengerReport = (config) => {
156
- let synthesis = `## ๐ŸŽฏ Devil's Advocate Analysis\n\n`;
157
+ let synthesis = `## ${icon('target')} Devil's Advocate Analysis\n\n`;
157
158
  // Tone Analysis Section
158
- synthesis += `### ๐Ÿ“ข Tone Analysis\n\n`;
159
- synthesis += `**Status:** ${config.tone_analysis.detected ? 'โš ๏ธ Uncontested Tone Detected' : 'โœ… Healthy Debate Room'}\n`;
159
+ synthesis += `### ${icon('comment')} Tone Analysis\n\n`;
160
+ synthesis += `**Status:** ${config.tone_analysis.detected ? `${icon('warning')} Uncontested Tone Detected` : `${icon('check')} Healthy Debate Room`}\n`;
160
161
  synthesis += `**Severity:** ${config.tone_analysis.severity.toUpperCase()}\n`;
161
162
  synthesis += `**Message:** ${config.tone_analysis.message}\n\n`;
162
163
  if (config.tone_analysis.phrases.length > 0) {
@@ -167,14 +168,14 @@ export const synthesizeChallengerReport = (config) => {
167
168
  synthesis += `\n`;
168
169
  }
169
170
  // Claims Analysis
170
- synthesis += `### ๐Ÿ” Claims Analyzed: ${config.claims.length}\n\n`;
171
+ synthesis += `### ${icon('search')} Claims Analyzed: ${config.claims.length}\n\n`;
171
172
  if (config.claims.length > 0) {
172
173
  synthesis += buildClaimsTable(config.claims.slice(0, 5));
173
174
  synthesis += `\n`;
174
175
  }
175
176
  // Fact-Check Results
176
177
  if (config.fact_check) {
177
- synthesis += `### โœ“ Fact-Check Results\n\n`;
178
+ synthesis += `### ${icon('check')} Fact-Check Results\n\n`;
178
179
  synthesis += formatFactCheckResults(config.fact_check);
179
180
  synthesis += `\n`;
180
181
  }
@@ -204,13 +205,13 @@ export const synthesizeChallengerReport = (config) => {
204
205
  });
205
206
  }
206
207
  // Summary
207
- synthesis += `### ๐Ÿ“‹ Summary\n\n`;
208
+ synthesis += `### ${icon('list')} Summary\n\n`;
208
209
  synthesis += `\`\`\`\n`;
209
210
  synthesis += `Thoroughness: ${config.thoroughness}\n`;
210
211
  synthesis += `Claims Analyzed: ${config.claims.length}\n`;
211
- synthesis += `Tone Detected: ${config.tone_analysis.detected ? 'YES โš ๏ธ' : 'NO โœ…'}\n`;
212
- synthesis += `Fact-Checked: ${config.fact_check ? 'YES โœ“' : 'NO'}\n`;
213
- synthesis += `Counter-Evidence: ${config.counter_evidence ? 'YES โœ“' : 'NO'}\n`;
212
+ synthesis += `Tone Detected: ${config.tone_analysis.detected ? `YES ${icon('warning')}` : `NO ${icon('check')}`}\n`;
213
+ synthesis += `Fact-Checked: ${config.fact_check ? `YES ${icon('check')}` : 'NO'}\n`;
214
+ synthesis += `Counter-Evidence: ${config.counter_evidence ? `YES ${icon('check')}` : 'NO'}\n`;
214
215
  synthesis += `Opposite Views: ${config.opposite_views?.length || 0}\n`;
215
216
  synthesis += `Third-Way Options: ${config.third_way?.length || 0}\n`;
216
217
  synthesis += `\`\`\`\n`;
@@ -408,7 +409,7 @@ const buildClaimsTable = (claims) => {
408
409
  claims.forEach(claim => {
409
410
  const priority = (claim.priority * 100).toFixed(0) + '%';
410
411
  const claimText = claim.claim.length > 80 ? claim.claim.substring(0, 77) + '...' : claim.claim;
411
- const testable = claim.testable ? 'โœ“' : 'โœ—';
412
+ const testable = claim.testable ? icon('check') : icon('error');
412
413
  table += `| ${priority} | ${claimText} | ${testable} |\n`;
413
414
  });
414
415
  return table;
@@ -11,6 +11,7 @@
11
11
  * - Interface Segregation: Small, focused function signatures
12
12
  * - Dependency Inversion: Functions depend on types, not concrete implementations
13
13
  */
14
+ import { icon } from '../../../utils/ink-renderer.js';
14
15
  // ============================================================================
15
16
  // Prompt Building
16
17
  // ============================================================================
@@ -193,18 +194,18 @@ export const synthesizeVerifierReport = (config) => {
193
194
  const majorityResponses = config.consensus.clusters.get(config.consensus.majorityCluster) || [];
194
195
  const outlierCount = config.responses.length - majorityResponses.length;
195
196
  const consensusPercent = (config.consensus.agreement * 100).toFixed(1);
196
- let synthesis = `## ๐Ÿ” Multi-Model Verification Report\n\n`;
197
+ let synthesis = `## ${icon('search')} Multi-Model Verification Report\n\n`;
197
198
  // Consensus indicator
198
- synthesis += `### ๐Ÿ“Š Consensus: ${consensusPercent}%\n\n`;
199
+ synthesis += `### ${icon('chart')} Consensus: ${consensusPercent}%\n\n`;
199
200
  const consensusBar = Math.round(config.consensus.agreement * 10);
200
201
  synthesis += `\`\`\`\n`;
201
202
  synthesis += `[${'โฃฟ'.repeat(consensusBar)}${'โฃฟ'.repeat(10 - consensusBar)}] ${consensusPercent}% agreement\n`;
202
203
  synthesis += `\`\`\`\n\n`;
203
204
  // Model responses table
204
- synthesis += `### ๐Ÿค– Model Responses\n\n`;
205
+ synthesis += `### ${icon('cpu')} Model Responses\n\n`;
205
206
  synthesis += buildResponseTable(config.responses, majorityResponses);
206
207
  // Majority analysis
207
- synthesis += `### ๐ŸŽฏ Majority View\n\n`;
208
+ synthesis += `### ${icon('target')} Majority View\n\n`;
208
209
  synthesis += `**Conclusion:** ${config.consensus.majorityCluster}\n`;
209
210
  synthesis += `**Models in agreement:** ${majorityResponses.length}/${config.responses.length}\n\n`;
210
211
  if (majorityResponses.length > 0) {
@@ -226,7 +227,7 @@ export const synthesizeVerifierReport = (config) => {
226
227
  }
227
228
  // Dissenting views
228
229
  if (outlierCount > 0) {
229
- synthesis += `### โš ๏ธ Dissenting Views (${outlierCount})\n\n`;
230
+ synthesis += `### ${icon('warning')} Dissenting Views (${outlierCount})\n\n`;
230
231
  config.outliers.forEach(outlier => {
231
232
  synthesis += `**${outlier.model}:** "${outlier.conclusion || 'unknown'}"\n`;
232
233
  const preview = (outlier.response || '').substring(0, 150).replace(/\n/g, ' ');
@@ -236,14 +237,14 @@ export const synthesizeVerifierReport = (config) => {
236
237
  });
237
238
  }
238
239
  // Summary
239
- synthesis += `### ๐Ÿ“‹ Summary\n\n`;
240
+ synthesis += `### ${icon('list')} Summary\n\n`;
240
241
  synthesis += `\`\`\`\n`;
241
242
  synthesis += `Total Models: ${config.responses.length}\n`;
242
243
  synthesis += `Consensus: ${consensusPercent}%\n`;
243
244
  synthesis += `Majority View: ${config.consensus.majorityCluster}\n`;
244
245
  synthesis += `Agreeing Models: ${majorityResponses.length}\n`;
245
246
  synthesis += `Dissenting: ${outlierCount}\n`;
246
- synthesis += `High Confidence: ${config.consensus.agreement >= 0.8 ? 'YES โœ“' : 'NO'}\n`;
247
+ synthesis += `High Confidence: ${config.consensus.agreement >= 0.8 ? `YES ${icon('check')}` : 'NO'}\n`;
247
248
  synthesis += `\`\`\`\n`;
248
249
  return synthesis;
249
250
  };
@@ -273,10 +274,10 @@ const buildResponseTable = (responses, majorityResponses) => {
273
274
  table += '|:------:|:------|:----------:|-----------:|:--------|\n';
274
275
  responses.forEach((resp) => {
275
276
  const isMajority = majorityResponses.includes(resp);
276
- const statusIcon = isMajority ? 'โœ…' : 'โš ๏ธ';
277
- const conclusionIcon = resp.conclusion === 'true' ? 'โœ“' :
278
- resp.conclusion === 'false' ? 'โœ—' :
279
- resp.conclusion === 'uncertain' ? 'โ“' : 'โ”';
277
+ const statusIcon = isMajority ? icon('check') : icon('warning');
278
+ const conclusionIcon = resp.conclusion === 'true' ? icon('check') :
279
+ resp.conclusion === 'false' ? icon('error') :
280
+ resp.conclusion === 'uncertain' ? icon('question') : '?';
280
281
  const confidence = resp.confidence ? `${Math.round(resp.confidence * 100)}%` : 'N/A';
281
282
  const preview = (resp.response || '').substring(0, 60).replace(/\n/g, ' ').trim();
282
283
  const previewText = preview ? `${preview}...` : 'No response';
@@ -9,6 +9,7 @@ import { ToolExecutionService } from "./orchestrators/collaborative/services/too
9
9
  import { ReasoningMode } from "./reasoning-chain.js";
10
10
  import { isModelAvailable, getAvailableModelNames } from "./utils/model-availability.js";
11
11
  import { formatMemorySaveHint } from "./utils/memory-provider.js";
12
+ import { icon } from "./utils/ink-renderer.js";
12
13
  /** Map string aliases to numeric values */
13
14
  const CONTEXT_WINDOW_MAP = {
14
15
  none: 0,
@@ -162,26 +163,26 @@ export class SequentialThinking {
162
163
  guidance += `Thoughts completed: ${thoughtsSoFar}/${session.totalThoughts}\n\n`;
163
164
  // Suggest next steps based on progress
164
165
  if (progress < 30) {
165
- guidance += "๐Ÿ” **Early Stage**: Focus on understanding and decomposing the problem.\n";
166
+ guidance += `${icon('search')} **Early Stage**: Focus on understanding and decomposing the problem.\n`;
166
167
  guidance += "Consider: What are the key components? What constraints exist?\n";
167
168
  }
168
169
  else if (progress < 60) {
169
- guidance += "๐Ÿ”ง **Middle Stage**: Explore solutions and alternatives.\n";
170
+ guidance += `${icon('wrench')} **Middle Stage**: Explore solutions and alternatives.\n`;
170
171
  guidance += "Consider: What approaches could work? What are the trade-offs?\n";
171
172
  guidance += "You may want to branch to explore alternatives.\n";
172
173
  }
173
174
  else if (progress < 90) {
174
- guidance += "๐ŸŽฏ **Late Stage**: Refine and validate your approach.\n";
175
+ guidance += `${icon('target')} **Late Stage**: Refine and validate your approach.\n`;
175
176
  guidance += "Consider: Are there edge cases? Can we optimize further?\n";
176
177
  guidance += "You may want to revise earlier thoughts with new insights.\n";
177
178
  }
178
179
  else {
179
- guidance += "โœจ **Final Stage**: Synthesize and conclude.\n";
180
+ guidance += `${icon('sparkle')} **Final Stage**: Synthesize and conclude.\n`;
180
181
  guidance += "Consider: What's the final solution? What are the next steps?\n";
181
182
  }
182
183
  // Check if revision might be helpful
183
184
  if (thoughtsSoFar > 3 && !session.thoughts.some(t => t.isRevision)) {
184
- guidance += "\n๐Ÿ’ก **Tip**: Consider revising earlier thoughts if new insights emerged.\n";
185
+ guidance += `\n${icon('lightbulb')} **Tip**: Consider revising earlier thoughts if new insights emerged.\n`;
185
186
  }
186
187
  return guidance;
187
188
  }
@@ -189,7 +190,7 @@ export class SequentialThinking {
189
190
  * Generate a summary of the thinking session
190
191
  */
191
192
  generateSummary(session) {
192
- let summary = `## ๐ŸŽฏ Thinking Session Complete\n\n`;
193
+ let summary = `## ${icon('target')} Thinking Session Complete\n\n`;
193
194
  summary += `**Objective**: ${session.objective || "Not specified"}\n`;
194
195
  summary += `**Total Thoughts**: ${session.thoughts.length}\n`;
195
196
  // Count revisions and branches
@@ -11,7 +11,7 @@
11
11
  * tachi "anything" --mode=solve โ†’ force specific mode
12
12
  */
13
13
  import { z } from "zod";
14
- import { renderBigText } from "../utils/ink-renderer.js";
14
+ import { renderBigText, icon } from "../utils/ink-renderer.js";
15
15
  // Import tool executors
16
16
  import { callGemini, isGeminiAvailable } from "./gemini-tools.js";
17
17
  import { callGrok } from "./grok-tools.js";
@@ -104,14 +104,15 @@ function routeIntent(query) {
104
104
  // ============================================================================
105
105
  // MODE HANDLERS
106
106
  // ============================================================================
107
- const MODE_ICONS = {
108
- research: "๐Ÿ”",
109
- solve: "๐Ÿ”ง",
110
- verify: "โœ“",
111
- creative: "๐Ÿ’ก",
112
- architect: "๐Ÿ—๏ธ",
113
- judge: "โš–๏ธ",
114
- };
107
+ // Use icon() for Nerd Font support with Unicode fallback
108
+ const getModeIcon = (mode) => ({
109
+ research: icon('search'),
110
+ solve: icon('wrench'),
111
+ verify: icon('check'),
112
+ creative: icon('lightbulb'),
113
+ architect: icon('building'),
114
+ judge: icon('scales'),
115
+ }[mode]);
115
116
  const MODE_HEADERS = {
116
117
  research: "RESEARCH",
117
118
  solve: "SOLVE",
@@ -168,7 +169,7 @@ async function solveHandler(query) {
168
169
  { role: "system", content: "You are Qwen3-Coder. Debug and solve the code problem. Provide working code." },
169
170
  { role: "user", content: query }
170
171
  ], OpenRouterModel.QWEN3_CODER_PLUS, 0.2, 6000);
171
- results.push(`**๐Ÿ”ง Qwen Analysis:**\n${qwenResult}`);
172
+ results.push(`**${icon('wrench')} Qwen Analysis:**\n${qwenResult}`);
172
173
  }
173
174
  catch {
174
175
  // Qwen failed, continue with Grok
@@ -187,7 +188,7 @@ async function solveHandler(query) {
187
188
  temperature: 0.3,
188
189
  maxTokens: 2000
189
190
  });
190
- results.push(`\n**๐Ÿ” Related Solutions:**\n${searchResult.content}`);
191
+ results.push(`\n**${icon('search')} Related Solutions:**\n${searchResult.content}`);
191
192
  }
192
193
  catch {
193
194
  // Search failed, continue
@@ -206,7 +207,7 @@ async function solveHandler(query) {
206
207
  async function verifyHandler(query) {
207
208
  const judgePrompt = `You are a critical analyst and judge. For the given question or statement:
208
209
  1. Analyze for correctness, accuracy, and potential issues
209
- 2. Provide a clear verdict: โœ… VALID, โŒ INVALID, or โš ๏ธ NEEDS MORE CONTEXT
210
+ 2. Provide a clear verdict: VALID, INVALID, or NEEDS MORE CONTEXT
210
211
  3. Support your verdict with evidence and reasoning
211
212
  4. List any caveats or edge cases
212
213
  5. Confidence score (0-100%)`;
@@ -214,7 +215,7 @@ async function verifyHandler(query) {
214
215
  if (isGeminiAvailable()) {
215
216
  try {
216
217
  const result = await callGemini(query, undefined, judgePrompt);
217
- return `**โš–๏ธ Gemini Judge:**\n${result}`;
218
+ return `**${icon('scales')} Gemini Judge:**\n${result}`;
218
219
  }
219
220
  catch {
220
221
  // Fall through to GPT
@@ -227,7 +228,7 @@ async function verifyHandler(query) {
227
228
  { role: "system", content: judgePrompt },
228
229
  { role: "user", content: query }
229
230
  ], OPENAI_MODELS.DEFAULT, 0.3, 4000);
230
- return `**โš–๏ธ GPT Judge:**\n${result}`;
231
+ return `**${icon('scales')} GPT Judge:**\n${result}`;
231
232
  }
232
233
  catch {
233
234
  // Fall through to Grok
@@ -239,7 +240,7 @@ async function verifyHandler(query) {
239
240
  { role: "system", content: judgePrompt },
240
241
  { role: "user", content: query }
241
242
  ]);
242
- return `**โš–๏ธ Grok Judge:**\n${result}`;
243
+ return `**${icon('scales')} Grok Judge:**\n${result}`;
243
244
  }
244
245
  catch (error) {
245
246
  return `[Verification failed: ${error instanceof Error ? error.message : "Unknown error"}]`;
@@ -280,7 +281,7 @@ async function architectHandler(query) {
280
281
  temperature: 0.3,
281
282
  maxTokens: 2500
282
283
  });
283
- results.push(`**๐Ÿ” Context & Patterns:**\n${searchResult.content}`);
284
+ results.push(`**${icon('search')} Context & Patterns:**\n${searchResult.content}`);
284
285
  }
285
286
  catch {
286
287
  // Search failed, continue
@@ -325,7 +326,7 @@ Provide:
325
326
  2. KEY REASONS: Top 3 reasons for this choice
326
327
  3. RISKS: Main risks to watch for
327
328
  4. NEXT STEPS: Concrete action items`);
328
- results.push(`\n**โš–๏ธ Final Verdict (Gemini):**\n${judgeResult}`);
329
+ results.push(`\n**${icon('scales')} Final Verdict (Gemini):**\n${judgeResult}`);
329
330
  }
330
331
  catch {
331
332
  // Judge failed
@@ -371,7 +372,7 @@ Provide:
371
372
  2. CONSENSUS: Where models agreed
372
373
  3. DISAGREEMENTS: Where they differed and why
373
374
  4. CONFIDENCE: Your confidence level (0-100%)`);
374
- results.push(`\n**โš–๏ธ FINAL VERDICT (Gemini Judge):**\n${finalVerdict}`);
375
+ results.push(`\n**${icon('scales')} FINAL VERDICT (Gemini Judge):**\n${finalVerdict}`);
375
376
  }
376
377
  catch {
377
378
  // Judge failed
@@ -446,11 +447,11 @@ Examples:
446
447
  }
447
448
  log.info(`Tachi routing to ${resolvedMode}${routeInfo}`);
448
449
  // Build response with header
449
- const icon = MODE_ICONS[resolvedMode];
450
+ const modeIcon = getModeIcon(resolvedMode);
450
451
  const headerText = MODE_HEADERS[resolvedMode];
451
452
  // BigText header (disabled via TACHIBOT_BIG_HEADERS=false)
452
453
  let response = renderBigText(headerText, { font: "block", gradient: "cristal" });
453
- response += `\n${icon} **${resolvedMode.toUpperCase()} MODE**${routeInfo}\n\n`;
454
+ response += `\n${modeIcon} **${resolvedMode.toUpperCase()} MODE**${routeInfo}\n\n`;
454
455
  response += `---\n\n`;
455
456
  // Execute mode handler
456
457
  try {
@@ -316,6 +316,28 @@ export const nerdIcons = {
316
316
  tag: '', // nf-fa-tag
317
317
  bookmark: '', // nf-fa-bookmark
318
318
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
319
+ // REASONING / ANALYSIS (replaces emojis)
320
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
321
+ target: '๓ฐ€˜', // nf-md-bullseye (replaces ๐ŸŽฏ)
322
+ scales: '๓ฐ–ท', // nf-md-scale_balance (replaces โš–๏ธ)
323
+ eye: '', // nf-fa-eye (replaces ๐Ÿ‘€)
324
+ eyeSlash: '', // nf-fa-eye_slash
325
+ building: '', // nf-fa-building (replaces ๐Ÿ—๏ธ)
326
+ handshake: '๓ฑข', // nf-md-handshake (replaces ๐Ÿค)
327
+ flask: '', // nf-fa-flask (replaces ๐Ÿงช)
328
+ sword: '๓ฐ“ฅ', // nf-md-sword (replaces โš”๏ธ)
329
+ swords: '๓ฐš”', // nf-md-sword_cross
330
+ money: '', // nf-fa-dollar (replaces ๐Ÿ’ฐ)
331
+ branch: '', // nf-oct-git_branch (replaces ๐ŸŒฟ)
332
+ lightbulb: '', // nf-fa-lightbulb_o (replaces ๐Ÿ’ก)
333
+ compass: '', // nf-fa-compass
334
+ flag: '', // nf-fa-flag
335
+ trophy: '', // nf-fa-trophy
336
+ shield: '', // nf-fa-shield
337
+ puzzle: '๓ฐ˜—', // nf-md-puzzle (replaces ๐Ÿงฉ)
338
+ thumbUp: '', // nf-fa-thumbs_up
339
+ thumbDown: '', // nf-fa-thumbs_down
340
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
319
341
  // POWERLINE
320
342
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
321
343
  plRight: '', // Powerline right arrow
@@ -499,6 +521,26 @@ export function icon(name) {
499
521
  bookmark: 'โ–ถ',
500
522
  clock: 'โ—ท',
501
523
  calendar: 'โ–ฆ',
524
+ // Reasoning/Analysis
525
+ target: 'โ—Ž',
526
+ scales: 'โš–',
527
+ eye: 'โ—‰',
528
+ eyeSlash: 'โ—Œ',
529
+ building: 'โ–ฃ',
530
+ handshake: 'โ‰ก',
531
+ flask: 'โš—',
532
+ sword: 'โ€ ',
533
+ swords: 'โš”',
534
+ money: '$',
535
+ branch: 'โއ',
536
+ lightbulb: 'โœฆ',
537
+ compass: 'โ—Ž',
538
+ flag: 'โš‘',
539
+ trophy: 'โ—†',
540
+ shield: 'โ—‡',
541
+ puzzle: 'โ–ฆ',
542
+ thumbUp: '+',
543
+ thumbDown: '-',
502
544
  // Powerline (no fallback - just empty)
503
545
  plRight: '',
504
546
  plLeft: '',
@@ -634,6 +676,15 @@ export function renderGradientModelName(model) {
634
676
  export const showBigHeaders = () => {
635
677
  return process.env.TACHIBOT_BIG_HEADERS !== 'false';
636
678
  };
679
+ /**
680
+ * Strip ANSI escape codes from text
681
+ */
682
+ function stripAnsiCodes(text) {
683
+ // Comprehensive ANSI regex - covers SGR, cursor, and other sequences
684
+ // eslint-disable-next-line no-control-regex
685
+ const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
686
+ return text.replace(ansiRegex, '');
687
+ }
637
688
  /**
638
689
  * Render large ASCII art text with optional gradient
639
690
  * Uses ink-big-text for rendering, then applies gradient-string for colors
@@ -645,13 +696,18 @@ export function renderBigText(text, options) {
645
696
  return '';
646
697
  const font = options?.font || 'block';
647
698
  const gradient = options?.gradient;
648
- // Render BigText to string
649
- const ascii = renderInkToString(_jsx(BigText, { text: text, font: font }));
699
+ // Render BigText to string and strip any ANSI codes from Ink
700
+ let ascii = stripAnsiCodes(renderInkToString(_jsx(BigText, { text: text, font: font })));
650
701
  // Apply gradient if specified
651
702
  if (gradient) {
652
703
  const gradFn = gradientString[gradient];
653
704
  if (gradFn && typeof gradFn.multiline === 'function') {
654
- return gradFn.multiline(ascii);
705
+ // Normalize line widths for proper gradient alignment
706
+ // gradient-string.multiline() applies colors per-line, so lines must be equal width
707
+ const lines = ascii.split('\n');
708
+ const maxWidth = Math.max(...lines.map(l => l.length));
709
+ const normalized = lines.map(l => l.padEnd(maxWidth)).join('\n');
710
+ return gradFn.multiline(normalized);
655
711
  }
656
712
  }
657
713
  return ascii;
@@ -9,11 +9,12 @@ export const MAX_SYSTEM_PROMPT_LENGTH = 5000;
9
9
  // Patterns to detect potential injection attempts
10
10
  // NOTE: Role injection (user:/assistant:/system:) pattern removed due to false positives
11
11
  // with legitimate LLM-generated content. For LLM-to-LLM calls, use skipValidation flag.
12
+ // NOTE: Path traversal (../) pattern removed - too many false positives in architecture
13
+ // discussions and code examples mentioning relative paths.
12
14
  const SUSPICIOUS_PATTERNS = [
13
15
  /<\s*script/gi, // XSS attempts
14
16
  /\b(exec|eval)\s*\(/gi, // Code execution attempts (require() removed - false positives in code examples)
15
17
  /;\s*(rm|del|format|drop\s+table)/gi, // Command/SQL injection
16
- /\.\.\//g, // Path traversal
17
18
  /\x00/g, // Null byte injection
18
19
  ];
19
20
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tachibot-mcp",
3
3
  "mcpName": "io.github.byPawel/tachibot-mcp",
4
4
  "displayName": "TachiBot MCP - Universal AI Orchestrator",
5
- "version": "2.7.4",
5
+ "version": "2.7.5",
6
6
  "type": "module",
7
7
  "main": "dist/src/server.js",
8
8
  "bin": {