@saccolabs/tars 1.8.2 → 1.9.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.
Files changed (48) hide show
  1. package/context/skills/gws-setup/SKILL.md +71 -0
  2. package/dist/auth/workspace-auth-service.d.ts +10 -0
  3. package/dist/auth/workspace-auth-service.js +78 -0
  4. package/dist/auth/workspace-auth-service.js.map +1 -0
  5. package/dist/cli/commands/setup.js +158 -5
  6. package/dist/cli/commands/setup.js.map +1 -1
  7. package/dist/cli/commands/stop.js +3 -0
  8. package/dist/cli/commands/stop.js.map +1 -1
  9. package/dist/supervisor/dashboard-service.d.ts +12 -0
  10. package/dist/supervisor/dashboard-service.js +109 -0
  11. package/dist/supervisor/dashboard-service.js.map +1 -0
  12. package/dist/supervisor/gemini-engine.js +26 -4
  13. package/dist/supervisor/gemini-engine.js.map +1 -1
  14. package/dist/supervisor/main.js +4 -0
  15. package/dist/supervisor/main.js.map +1 -1
  16. package/package.json +2 -1
  17. package/stock_apps/dashboard/DEPLOY.md +30 -0
  18. package/stock_apps/dashboard/README.md +36 -0
  19. package/stock_apps/dashboard/dash.log +134 -0
  20. package/stock_apps/dashboard/eslint.config.mjs +19 -0
  21. package/stock_apps/dashboard/next.config.ts +12 -0
  22. package/stock_apps/dashboard/package-lock.json +8581 -0
  23. package/stock_apps/dashboard/package.json +42 -0
  24. package/stock_apps/dashboard/postcss.config.mjs +5 -0
  25. package/stock_apps/dashboard/public/file.svg +1 -0
  26. package/stock_apps/dashboard/public/globe.svg +1 -0
  27. package/stock_apps/dashboard/public/next.svg +1 -0
  28. package/stock_apps/dashboard/public/tars-logo.png +0 -0
  29. package/stock_apps/dashboard/public/vercel.svg +1 -0
  30. package/stock_apps/dashboard/public/window.svg +1 -0
  31. package/stock_apps/dashboard/server.js +488 -0
  32. package/stock_apps/dashboard/src/app/globals.css +122 -0
  33. package/stock_apps/dashboard/src/app/icon.png +0 -0
  34. package/stock_apps/dashboard/src/app/layout.tsx +35 -0
  35. package/stock_apps/dashboard/src/app/page.tsx +170 -0
  36. package/stock_apps/dashboard/src/components/FileExplorer.tsx +238 -0
  37. package/stock_apps/dashboard/src/components/IntelligencePanel.tsx +322 -0
  38. package/stock_apps/dashboard/src/components/MetricsPanel.tsx +347 -0
  39. package/stock_apps/dashboard/src/components/SystemActions.tsx +168 -0
  40. package/stock_apps/dashboard/src/context/SocketContext.tsx +62 -0
  41. package/stock_apps/dashboard/src/lib/socket.ts +10 -0
  42. package/stock_apps/dashboard/tsconfig.json +27 -0
  43. package/dist/discord/discord-bot.d.ts +0 -37
  44. package/dist/discord/discord-bot.js +0 -210
  45. package/dist/discord/discord-bot.js.map +0 -1
  46. package/dist/discord/message-formatter.d.ts +0 -95
  47. package/dist/discord/message-formatter.js +0 -482
  48. package/dist/discord/message-formatter.js.map +0 -1
@@ -0,0 +1,168 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import { RefreshCw, Lock, Send, AlertTriangle, CheckCircle2, XCircle } from 'lucide-react';
4
+ import { motion, AnimatePresence } from 'framer-motion';
5
+
6
+ export function SystemActions() {
7
+ const [isRestarting, setIsRestarting] = useState(false);
8
+ const [secretKey, setSecretKey] = useState('');
9
+ const [secretValue, setSecretValue] = useState('');
10
+ const [isSettingSecret, setIsSettingSecret] = useState(false);
11
+ const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
12
+ null
13
+ );
14
+
15
+ const runCommand = async (body: any) => {
16
+ try {
17
+ const res = await fetch('/api/tars/command', {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json' },
20
+ body: JSON.stringify(body)
21
+ });
22
+ const data = await res.json();
23
+ if (!res.ok) throw new Error(data.error || 'Command failed');
24
+ return data;
25
+ } catch (err: any) {
26
+ throw err;
27
+ }
28
+ };
29
+
30
+ const handleRestart = async () => {
31
+ if (
32
+ !confirm(
33
+ 'Are you sure? This will restart the Tars supervisor and terminate the current session.'
34
+ )
35
+ )
36
+ return;
37
+
38
+ setIsRestarting(true);
39
+ setStatus(null);
40
+ try {
41
+ await runCommand({ action: 'restart' });
42
+ setStatus({
43
+ type: 'success',
44
+ message: 'Restart command issued successfully. Check logs.'
45
+ });
46
+ } catch (err: any) {
47
+ setStatus({ type: 'error', message: err.message });
48
+ } finally {
49
+ setIsRestarting(false);
50
+ }
51
+ };
52
+
53
+ const handleSetSecret = async (e: React.FormEvent) => {
54
+ e.preventDefault();
55
+ if (!secretKey || !secretValue) return;
56
+
57
+ setIsSettingSecret(true);
58
+ setStatus(null);
59
+ try {
60
+ await runCommand({ action: 'secret', key: secretKey, value: secretValue });
61
+ setStatus({ type: 'success', message: `Secret '${secretKey}' updated successfully.` });
62
+ setSecretKey('');
63
+ setSecretValue('');
64
+ } catch (err: any) {
65
+ setStatus({ type: 'error', message: err.message });
66
+ } finally {
67
+ setIsSettingSecret(false);
68
+ }
69
+ };
70
+
71
+ return (
72
+ <div className="card bg-[#0c0c0c] border-white/10 p-4 shadow-2xl flex flex-col gap-6">
73
+ <div className="card-header-btop text-accent-danger border-accent-danger/30">
74
+ System Control
75
+ </div>
76
+
77
+ {/* Restart Section */}
78
+ <div className="space-y-3">
79
+ <div className="flex items-center gap-2 mb-1">
80
+ <AlertTriangle size={12} className="text-accent-warning" />
81
+ <span className="text-[10px] font-black text-white/40 uppercase tracking-widest">
82
+ Supervisor Management
83
+ </span>
84
+ </div>
85
+ <button
86
+ onClick={handleRestart}
87
+ disabled={isRestarting}
88
+ className="w-full flex items-center justify-center gap-3 bg-accent-danger/10 hover:bg-accent-danger/20 border border-accent-danger/30 p-3 rounded-xl transition-all group disabled:opacity-50"
89
+ >
90
+ <RefreshCw
91
+ size={16}
92
+ className={`text-accent-danger ${isRestarting ? 'animate-spin' : 'group-hover:rotate-180 transition-transform duration-500'}`}
93
+ />
94
+ <span className="text-xs font-black uppercase tracking-[0.2em] text-white">
95
+ Restart Tars Core
96
+ </span>
97
+ </button>
98
+ </div>
99
+
100
+ {/* Secret Manager Section */}
101
+ <div className="space-y-3">
102
+ <div className="flex items-center gap-2 mb-1">
103
+ <Lock size={12} className="text-accent-primary" />
104
+ <span className="text-[10px] font-black text-white/40 uppercase tracking-widest">
105
+ Vault (Secrets)
106
+ </span>
107
+ </div>
108
+ <form onSubmit={handleSetSecret} className="flex flex-col gap-2">
109
+ <input
110
+ type="text"
111
+ placeholder="KEY (e.g. OPENAI_API_KEY)"
112
+ value={secretKey}
113
+ onChange={(e) => setSecretKey(e.target.value)}
114
+ className="bg-white/5 border border-white/10 rounded-lg p-2 text-[10px] font-mono focus:border-accent-primary/50 outline-none transition-colors text-white"
115
+ />
116
+ <div className="relative">
117
+ <input
118
+ type="password"
119
+ placeholder="VALUE"
120
+ value={secretValue}
121
+ onChange={(e) => setSecretValue(e.target.value)}
122
+ className="w-full bg-white/5 border border-white/10 rounded-lg p-2 text-[10px] font-mono focus:border-accent-primary/50 outline-none transition-colors text-white pr-10"
123
+ />
124
+ <button
125
+ type="submit"
126
+ disabled={isSettingSecret || !secretKey || !secretValue}
127
+ className="absolute right-1 top-1 bottom-1 px-2 bg-accent-primary/20 hover:bg-accent-primary/30 text-accent-primary rounded flex items-center justify-center transition-all disabled:opacity-0"
128
+ >
129
+ {isSettingSecret ? (
130
+ <RefreshCw size={12} className="animate-spin" />
131
+ ) : (
132
+ <Send size={12} />
133
+ )}
134
+ </button>
135
+ </div>
136
+ </form>
137
+ </div>
138
+
139
+ {/* Status Feedback */}
140
+ <AnimatePresence>
141
+ {status && (
142
+ <motion.div
143
+ initial={{ opacity: 0, y: 10 }}
144
+ animate={{ opacity: 1, y: 0 }}
145
+ exit={{ opacity: 0 }}
146
+ className={`p-3 rounded-xl flex items-start gap-3 border ${
147
+ status.type === 'success'
148
+ ? 'bg-accent-secondary/10 border-accent-secondary/20'
149
+ : 'bg-accent-danger/10 border-accent-danger/20'
150
+ }`}
151
+ >
152
+ {status.type === 'success' ? (
153
+ <CheckCircle2
154
+ size={14}
155
+ className="text-accent-secondary shrink-0 mt-0.5"
156
+ />
157
+ ) : (
158
+ <XCircle size={14} className="text-accent-danger shrink-0 mt-0.5" />
159
+ )}
160
+ <span className="text-[9px] font-bold text-white leading-relaxed uppercase tracking-tight">
161
+ {status.message}
162
+ </span>
163
+ </motion.div>
164
+ )}
165
+ </AnimatePresence>
166
+ </div>
167
+ );
168
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import React, { createContext, useContext, useEffect, useState } from 'react';
3
+ import { io, Socket } from 'socket.io-client';
4
+
5
+ interface SocketContextType {
6
+ socket: Socket | null;
7
+ subscribe: (room: string) => void;
8
+ unsubscribe: (room: string) => void;
9
+ }
10
+
11
+ const SocketContext = createContext<SocketContextType | undefined>(undefined);
12
+
13
+ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
14
+ const [socket, setSocket] = useState<Socket | null>(null);
15
+
16
+ useEffect(() => {
17
+ const s = io({
18
+ reconnection: true,
19
+ reconnectionAttempts: Infinity,
20
+ reconnectionDelay: 1000,
21
+ reconnectionDelayMax: 5000,
22
+ timeout: 20000
23
+ });
24
+
25
+ setSocket(s);
26
+
27
+ const handleFocus = () => {
28
+ if (s.disconnected) {
29
+ s.connect();
30
+ }
31
+ };
32
+
33
+ window.addEventListener('focus', handleFocus);
34
+
35
+ return () => {
36
+ window.removeEventListener('focus', handleFocus);
37
+ s.disconnect();
38
+ };
39
+ }, []);
40
+
41
+ const subscribe = (room: string) => {
42
+ socket?.emit('subscribe', room);
43
+ };
44
+
45
+ const unsubscribe = (room: string) => {
46
+ socket?.emit('unsubscribe', room);
47
+ };
48
+
49
+ return (
50
+ <SocketContext.Provider value={{ socket, subscribe, unsubscribe }}>
51
+ {children}
52
+ </SocketContext.Provider>
53
+ );
54
+ };
55
+
56
+ export const useSocket = () => {
57
+ const context = useContext(SocketContext);
58
+ if (context === undefined) {
59
+ throw new Error('useSocket must be used within a SocketProvider');
60
+ }
61
+ return context;
62
+ };
@@ -0,0 +1,10 @@
1
+ import { io, Socket } from 'socket.io-client';
2
+
3
+ let socket: Socket | null = null;
4
+
5
+ export const getSocket = () => {
6
+ if (!socket) {
7
+ socket = io();
8
+ }
9
+ return socket;
10
+ };
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -1,37 +0,0 @@
1
- import { Config } from '../config/config.js';
2
- import { Supervisor } from '../supervisor/supervisor.js';
3
- /**
4
- * Discord bot wrapper for Tars
5
- */
6
- export declare class DiscordBot {
7
- private readonly config;
8
- private readonly client;
9
- private readonly supervisor;
10
- private readonly processor;
11
- private lastChannelId;
12
- constructor(supervisor: Supervisor, config: Config);
13
- /**
14
- * Start the Discord bot
15
- */
16
- start(): Promise<void>;
17
- /**
18
- * Stop the Discord bot
19
- */
20
- stop(): Promise<void>;
21
- /**
22
- * Send a proactive notification to the primary contact
23
- */
24
- notify(content: string): Promise<void>;
25
- /**
26
- * Setup event handlers
27
- */
28
- private setupEventHandlers;
29
- /**
30
- * Handle incoming messages
31
- */
32
- private handleMessage;
33
- /**
34
- * Extract prompt and handle prefix
35
- */
36
- private extractPrompt;
37
- }
@@ -1,210 +0,0 @@
1
- import { Client, GatewayIntentBits, ChannelType, Partials } from 'discord.js';
2
- import logger from '../utils/logger.js';
3
- import { MessageFormatter } from './message-formatter.js';
4
- import { AttachmentProcessor } from '../utils/attachment-processor.js';
5
- /**
6
- * Discord bot wrapper for Tars
7
- */
8
- export class DiscordBot {
9
- config;
10
- client;
11
- supervisor;
12
- processor;
13
- lastChannelId = null;
14
- constructor(supervisor, config) {
15
- this.config = config;
16
- this.supervisor = supervisor;
17
- this.processor = new AttachmentProcessor(config);
18
- this.client = new Client({
19
- intents: [
20
- GatewayIntentBits.Guilds,
21
- GatewayIntentBits.GuildMessages,
22
- GatewayIntentBits.MessageContent,
23
- GatewayIntentBits.DirectMessages
24
- ],
25
- partials: [Partials.Channel, Partials.Message]
26
- });
27
- this.setupEventHandlers();
28
- }
29
- /**
30
- * Start the Discord bot
31
- */
32
- async start() {
33
- await this.client.login(this.config.discordToken);
34
- }
35
- /**
36
- * Stop the Discord bot
37
- */
38
- async stop() {
39
- this.client.destroy();
40
- }
41
- /**
42
- * Send a proactive notification to the primary contact
43
- */
44
- async notify(content) {
45
- if (!this.config.discordOwnerId || !content.trim())
46
- return;
47
- try {
48
- const user = await this.client.users.fetch(this.config.discordOwnerId);
49
- if (user) {
50
- const formatted = MessageFormatter.format(content);
51
- if (formatted.length > 8000) {
52
- const filePath = this.processor.saveResponse(content, 'md');
53
- await user.send({
54
- content: `🔔 **Task Notification** (Response too long, see attached):`,
55
- files: [filePath]
56
- });
57
- }
58
- else {
59
- const chunks = MessageFormatter.split(formatted);
60
- for (let i = 0; i < chunks.length; i++) {
61
- const prefix = i === 0 ? `🔔 **Task Notification:**\n` : ``;
62
- await user.send(prefix + chunks[i]);
63
- }
64
- }
65
- }
66
- }
67
- catch (e) {
68
- logger.error(`Failed to send proactive notification: ${e.message}`);
69
- }
70
- }
71
- /**
72
- * Setup event handlers
73
- */
74
- setupEventHandlers() {
75
- this.client.once('clientReady', (c) => {
76
- logger.info(`🚀 ${this.config.assistantName} online as ${c.user.tag}`);
77
- logger.info(`🧠 Gemini Model: ${this.config.geminiModel}`);
78
- if (this.config.discordOwnerId) {
79
- logger.info(`👤 Primary Contact ID: ${this.config.discordOwnerId}`);
80
- }
81
- else {
82
- logger.warn(`⚠️ No Primary Contact ID set. Will bind to the first user who sends a message.`);
83
- }
84
- });
85
- this.client.on('messageCreate', this.handleMessage.bind(this));
86
- }
87
- /**
88
- * Handle incoming messages
89
- */
90
- async handleMessage(message) {
91
- if (message.author.bot)
92
- return;
93
- const userPrompt = this.extractPrompt(message);
94
- // If null, message wasn't for us. If empty string, check for attachments.
95
- if (userPrompt === null)
96
- return;
97
- // Auto-Bind on first interaction if not set
98
- if (!this.config.discordOwnerId) {
99
- this.config.discordOwnerId = message.author.id;
100
- this.config.saveSettings();
101
- logger.info(`🔒 Automatically bound Primary Contact to user: ${message.author.id}`);
102
- await message.reply(`🔒 **System Alert:** I have permanently bound my background notification channel to your account. I will send proactive alerts here.`);
103
- }
104
- if (!userPrompt && message.attachments.size === 0)
105
- return;
106
- logger.info(`Received request from ${message.author.tag}: "${userPrompt || '[Attachment Only]'}"`);
107
- // Handle Attachments
108
- const attachments = [];
109
- let attachmentContextText = '';
110
- if (message.attachments.size > 0) {
111
- for (const [id, attachment] of message.attachments) {
112
- try {
113
- const filePath = await this.processor.download(attachment);
114
- if (attachment.contentType) {
115
- attachments.push({
116
- path: filePath,
117
- mimeType: attachment.contentType
118
- });
119
- }
120
- attachmentContextText += `\n[User attached file (${attachment.contentType}): ${filePath}]`;
121
- }
122
- catch (err) {
123
- logger.error(`Failed to download attachment: ${err.message}`);
124
- await message.reply(`⚠️ Failed to download ${attachment.name}: ${err.message}`);
125
- }
126
- }
127
- }
128
- const fullPrompt = `${userPrompt}${attachmentContextText}`.trim();
129
- if (!fullPrompt && attachments.length === 0)
130
- return;
131
- let typingInterval = null;
132
- // Start typing indicator loop (Discord typing status lasts 10s)
133
- if ('sendTyping' in message.channel) {
134
- // Initial typing
135
- await message.channel.sendTyping().catch(() => { });
136
- // Loop every 9s to keep it active
137
- typingInterval = setInterval(() => {
138
- if ('sendTyping' in message.channel) {
139
- message.channel.sendTyping().catch(() => { });
140
- }
141
- }, 9000);
142
- }
143
- try {
144
- let fullResponse = '';
145
- await this.supervisor.run(fullPrompt, async (event) => {
146
- if ((event.type === 'text' || event.type === 'message') &&
147
- event.content &&
148
- event.role !== 'user') {
149
- fullResponse += event.content;
150
- }
151
- else if (event.type === 'error') {
152
- await message.reply(`❌ **Error:** ${event.error}`);
153
- }
154
- else if (event.type === 'done') {
155
- if (fullResponse.trim()) {
156
- const formatted = MessageFormatter.format(fullResponse);
157
- if (formatted.length > 8000) {
158
- const filePath = this.processor.saveResponse(fullResponse, 'md');
159
- await message.reply({
160
- content: `📄 **Response too long** (${formatted.length} chars). See attached file:`,
161
- files: [filePath]
162
- });
163
- }
164
- else {
165
- const chunks = MessageFormatter.split(formatted);
166
- for (const chunk of chunks) {
167
- await message.reply(chunk);
168
- }
169
- }
170
- }
171
- else {
172
- logger.warn('Gemini returned an empty response.');
173
- }
174
- }
175
- }, undefined, attachments);
176
- }
177
- catch (error) {
178
- logger.error(`Discord handling error: ${error.message}`);
179
- await message.reply(`❌ **Supervisor Error:** ${error.message}`);
180
- }
181
- finally {
182
- if (typingInterval)
183
- clearInterval(typingInterval);
184
- }
185
- }
186
- /**
187
- * Extract prompt and handle prefix
188
- */
189
- extractPrompt(message) {
190
- const isDM = message.channel.type === ChannelType.DM;
191
- const isMentioned = this.client.user && message.mentions.has(this.client.user);
192
- const customPrefix = `!${this.config.assistantName.toLowerCase()}`;
193
- const hasCustomCommand = message.content.toLowerCase().startsWith(customPrefix);
194
- const hasLegacyCommand = message.content.toLowerCase().startsWith('!tars');
195
- if (!isDM && !isMentioned && !hasCustomCommand && !hasLegacyCommand)
196
- return null;
197
- let prompt = message.content;
198
- if (hasCustomCommand) {
199
- prompt = prompt.substring(customPrefix.length);
200
- }
201
- else if (hasLegacyCommand) {
202
- prompt = prompt.substring(6); // length of '!tars'
203
- }
204
- if (isMentioned && this.client.user) {
205
- prompt = prompt.replace(new RegExp(`<@!?${this.client.user.id}>`, 'g'), '');
206
- }
207
- return prompt.trim();
208
- }
209
- }
210
- //# sourceMappingURL=discord-bot.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"discord-bot.js","sourceRoot":"","sources":["../../src/discord/discord-bot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,MAAM,EACN,iBAAiB,EAEjB,WAAW,EACX,QAAQ,EAMX,MAAM,YAAY,CAAC;AAGpB,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE;;GAEG;AACH,MAAM,OAAO,UAAU;IACF,MAAM,CAAS;IACf,MAAM,CAAS;IACf,UAAU,CAAa;IACvB,SAAS,CAAsB;IAExC,aAAa,GAAkB,IAAI,CAAC;IAE5C,YAAY,UAAsB,EAAE,MAAc;QAC9C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAEjD,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACrB,OAAO,EAAE;gBACL,iBAAiB,CAAC,MAAM;gBACxB,iBAAiB,CAAC,aAAa;gBAC/B,iBAAiB,CAAC,cAAc;gBAChC,iBAAiB,CAAC,cAAc;aACnC;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC;SACjD,CAAC,CAAC;QAEH,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACP,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACN,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,MAAM,CAAC,OAAe;QAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YAAE,OAAO;QAC3D,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YACvE,IAAI,IAAI,EAAE,CAAC;gBACP,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACnD,IAAI,SAAS,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;oBAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;oBAC5D,MAAM,IAAI,CAAC,IAAI,CAAC;wBACZ,OAAO,EAAE,6DAA6D;wBACtE,KAAK,EAAE,CAAC,QAAQ,CAAC;qBACpB,CAAC,CAAC;gBACP,CAAC;qBAAM,CAAC;oBACJ,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;oBACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;wBACrC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC5D,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBACxC,CAAC;gBACL,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC;IACL,CAAC;IAED;;OAEG;IACK,kBAAkB;QACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;YAClC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3D,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;YACxE,CAAC;iBAAM,CAAC;gBACJ,MAAM,CAAC,IAAI,CACP,gFAAgF,CACnF,CAAC;YACN,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CAAC,OAAgB;QACxC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG;YAAE,OAAO;QAE/B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC/C,0EAA0E;QAC1E,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO;QAEhC,4CAA4C;QAC5C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,mDAAmD,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YACpF,MAAM,OAAO,CAAC,KAAK,CACf,sIAAsI,CACzI,CAAC;QACN,CAAC;QAED,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAE1D,MAAM,CAAC,IAAI,CACP,yBAAyB,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,UAAU,IAAI,mBAAmB,GAAG,CACxF,CAAC;QAEF,qBAAqB;QACrB,MAAM,WAAW,GAAwB,EAAE,CAAC;QAC5C,IAAI,qBAAqB,GAAG,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC/B,KAAK,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBACjD,IAAI,CAAC;oBACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;oBAC3D,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC;wBACzB,WAAW,CAAC,IAAI,CAAC;4BACb,IAAI,EAAE,QAAQ;4BACd,QAAQ,EAAE,UAAU,CAAC,WAAW;yBACnC,CAAC,CAAC;oBACP,CAAC;oBACD,qBAAqB,IAAI,0BAA0B,UAAU,CAAC,WAAW,MAAM,QAAQ,GAAG,CAAC;gBAC/F,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAChB,MAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC9D,MAAM,OAAO,CAAC,KAAK,CAAC,yBAAyB,UAAU,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACpF,CAAC;YACL,CAAC;QACL,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,UAAU,GAAG,qBAAqB,EAAE,CAAC,IAAI,EAAE,CAAC;QAClE,IAAI,CAAC,UAAU,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEpD,IAAI,cAAc,GAA0B,IAAI,CAAC;QAEjD,gEAAgE;QAChE,IAAI,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAClC,iBAAiB;YACjB,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAEnD,kCAAkC;YAClC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;gBAC9B,IAAI,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBACjC,OAAO,CAAC,OAAe,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBAC1D,CAAC;YACL,CAAC,EAAE,IAAI,CAAC,CAAC;QACb,CAAC;QAED,IAAI,CAAC;YACD,IAAI,YAAY,GAAG,EAAE,CAAC;YAEtB,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CACrB,UAAU,EACV,KAAK,EAAE,KAAwB,EAAE,EAAE;gBAC/B,IACI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;oBACnD,KAAK,CAAC,OAAO;oBACb,KAAK,CAAC,IAAI,KAAK,MAAM,EACvB,CAAC;oBACC,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC;gBAClC,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAChC,MAAM,OAAO,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;gBACvD,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC/B,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;wBACtB,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;wBAExD,IAAI,SAAS,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;4BAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;4BACjE,MAAM,OAAO,CAAC,KAAK,CAAC;gCAChB,OAAO,EAAE,6BAA6B,SAAS,CAAC,MAAM,6BAA6B;gCACnF,KAAK,EAAE,CAAC,QAAQ,CAAC;6BACpB,CAAC,CAAC;wBACP,CAAC;6BAAM,CAAC;4BACJ,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;4BACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gCACzB,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;4BAC/B,CAAC;wBACL,CAAC;oBACL,CAAC;yBAAM,CAAC;wBACJ,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;oBACtD,CAAC;gBACL,CAAC;YACL,CAAC,EACD,SAAS,EACT,WAAW,CACd,CAAC;QACN,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,MAAM,OAAO,CAAC,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACpE,CAAC;gBAAS,CAAC;YACP,IAAI,cAAc;gBAAE,aAAa,CAAC,cAAc,CAAC,CAAC;QACtD,CAAC;IACL,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,OAAgB;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,CAAC;QACrD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE/E,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC;QACnE,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAChF,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAE3E,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,gBAAgB,IAAI,CAAC,gBAAgB;YAAE,OAAO,IAAI,CAAC;QAEjF,IAAI,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;QAC7B,IAAI,gBAAgB,EAAE,CAAC;YACnB,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,gBAAgB,EAAE,CAAC;YAC1B,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,oBAAoB;QACtD,CAAC;QAED,IAAI,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;CACJ"}
@@ -1,95 +0,0 @@
1
- /**
2
- * Discord Message Formatter
3
- *
4
- * Transforms Gemini CLI output (GitHub Flavored Markdown)
5
- * into Discord-compatible formatting.
6
- *
7
- * Discord supports:
8
- * - Bold: **text**
9
- * - Italic: *text* or _text_
10
- * - Underline: __text__
11
- * - Strikethrough: ~~text~~
12
- * - Code inline: `text`
13
- * - Code block: ```lang\ncode\n```
14
- * - Blockquotes: > text
15
- * - Headers: # (only #, ##, ###)
16
- *
17
- * Discord does NOT support:
18
- * - Markdown tables (we instruct the LLM to avoid these)
19
- * - #### or deeper headers
20
- * - Small text (-#)
21
- */
22
- export declare class MessageFormatter {
23
- private static readonly MAX_MESSAGE_LENGTH;
24
- /**
25
- * Format text for Discord
26
- */
27
- static format(text: string): string;
28
- /**
29
- * Fix critical spacing issues from LLM output
30
- * These are the most common formatting bugs that break Discord rendering
31
- */
32
- private static fixSpacing;
33
- /**
34
- * Fix broken asterisks patterns
35
- * Only fixes obviously broken patterns, avoids aggressive matching
36
- */
37
- private static fixAsterisks;
38
- /**
39
- * Normalize bullet points for Discord
40
- */
41
- private static normalizeBullets;
42
- /**
43
- * Normalize markdown headers to Discord-friendly format
44
- * Discord supports #, ##, ### natively now.
45
- */
46
- private static normalizeHeaders;
47
- /**
48
- * Fix blockquote formatting
49
- */
50
- private static fixBlockquotes;
51
- /**
52
- * Detect and wrap JSON-like content in code blocks
53
- */
54
- private static formatJsonBlocks;
55
- /**
56
- * Strip markdown tables - they don't render well on mobile Discord
57
- * This is a fallback; the LLM should be instructed not to generate tables
58
- */
59
- private static stripTables;
60
- /**
61
- * Fix small text markers (-#) that Discord doesn't support
62
- */
63
- private static fixSmallText;
64
- /**
65
- * Split long messages into Discord-safe chunks intelligently
66
- * Respects semantic boundaries: headers, code blocks, paragraphs
67
- */
68
- static split(text: string, maxLength?: number): string[];
69
- /**
70
- * Find the optimal split point respecting semantic boundaries
71
- * Priority: Header > Code block boundary > Paragraph > Sentence > Hard cut
72
- */
73
- private static findSemanticSplitPoint;
74
- /**
75
- * Format and split in one operation
76
- * Ensures summary line (first line with actionable emoji) stays at the top
77
- */
78
- static formatAndSplit(text: string): string[];
79
- /**
80
- * Extract the summary line from the beginning of formatted text
81
- * Summary lines start with key actionable emojis: 🎯 ⚖️ 📊 ⚠️ ✅ ❓
82
- */
83
- private static extractSummaryLine;
84
- /**
85
- * Parse markdown into sections based on headers (##)
86
- */
87
- static parseSections(text: string): {
88
- title: string;
89
- content: string;
90
- }[];
91
- /**
92
- * Format a data object as a clean Discord-friendly list
93
- */
94
- static formatDataAsEmbed(title: string, data: Record<string, unknown>): string;
95
- }