@saccolabs/tars 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/context/GEMINI.md +13 -0
- package/context/config/settings.json-template +36 -0
- package/context/skills/create-extension/SKILL.md +90 -0
- package/context/skills/create-skill/SKILL.md +33 -0
- package/context/skills/manage-extensions/SKILL.md +69 -0
- package/context/skills/tars-ops/SKILL.md +89 -0
- package/dist/cli/commands/discord.d.ts +4 -0
- package/dist/cli/commands/discord.js +48 -0
- package/dist/cli/commands/discord.js.map +1 -0
- package/dist/cli/commands/export.d.ts +3 -0
- package/dist/cli/commands/export.js +39 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/import.d.ts +1 -0
- package/dist/cli/commands/import.js +45 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/logs.d.ts +4 -0
- package/dist/cli/commands/logs.js +23 -0
- package/dist/cli/commands/logs.js.map +1 -0
- package/dist/cli/commands/memory.d.ts +1 -0
- package/dist/cli/commands/memory.js +35 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/commands/secret.d.ts +6 -0
- package/dist/cli/commands/secret.js +46 -0
- package/dist/cli/commands/secret.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +4 -0
- package/dist/cli/commands/setup.js +329 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/start.d.ts +1 -0
- package/dist/cli/commands/start.js +42 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +1 -0
- package/dist/cli/commands/status.js +56 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +1 -0
- package/dist/cli/commands/stop.js +38 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/commands/uninstall.d.ts +1 -0
- package/dist/cli/commands/uninstall.js +91 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +54 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/config.d.ts +14 -0
- package/dist/config/config.js +69 -0
- package/dist/config/config.js.map +1 -0
- package/dist/discord/discord-bot.d.ts +32 -0
- package/dist/discord/discord-bot.js +151 -0
- package/dist/discord/discord-bot.js.map +1 -0
- package/dist/discord/message-formatter.d.ts +95 -0
- package/dist/discord/message-formatter.js +448 -0
- package/dist/discord/message-formatter.js.map +1 -0
- package/dist/memory/knowledge-store.d.ts +24 -0
- package/dist/memory/knowledge-store.js +126 -0
- package/dist/memory/knowledge-store.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +24 -0
- package/dist/memory/memory-manager.js +101 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/scripts/debug-cli.d.ts +1 -0
- package/dist/scripts/debug-cli.js +52 -0
- package/dist/scripts/debug-cli.js.map +1 -0
- package/dist/supervisor/gemini-cli.d.ts +28 -0
- package/dist/supervisor/gemini-cli.js +315 -0
- package/dist/supervisor/gemini-cli.js.map +1 -0
- package/dist/supervisor/heartbeat-service.d.ts +21 -0
- package/dist/supervisor/heartbeat-service.js +143 -0
- package/dist/supervisor/heartbeat-service.js.map +1 -0
- package/dist/supervisor/main.d.ts +1 -0
- package/dist/supervisor/main.js +242 -0
- package/dist/supervisor/main.js.map +1 -0
- package/dist/supervisor/session-manager.d.ts +47 -0
- package/dist/supervisor/session-manager.js +118 -0
- package/dist/supervisor/session-manager.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +32 -0
- package/dist/supervisor/supervisor.js +98 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/types/index.d.ts +42 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/attachment-processor.d.ts +22 -0
- package/dist/utils/attachment-processor.js +79 -0
- package/dist/utils/attachment-processor.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +15 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/secrets-manager.d.ts +27 -0
- package/dist/utils/secrets-manager.js +79 -0
- package/dist/utils/secrets-manager.js.map +1 -0
- package/dist/utils/version.d.ts +3 -0
- package/dist/utils/version.js +23 -0
- package/dist/utils/version.js.map +1 -0
- package/extensions/tasks/gemini-extension.json +14 -0
- package/extensions/tasks/package-lock.json +1209 -0
- package/extensions/tasks/package.json +19 -0
- package/extensions/tasks/src/server.ts +265 -0
- package/extensions/tasks/src/store.ts +92 -0
- package/extensions/tasks/tsconfig.json +14 -0
- package/package.json +55 -0
- package/src/prompts/system.md +25 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import pm2 from 'pm2';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
export async function uninstall() {
|
|
11
|
+
console.log(chalk.red.bold('\n⚠️ DANGER ZONE: Uninstall Tars ⚠️\n'));
|
|
12
|
+
console.log(chalk.white('This action will:'));
|
|
13
|
+
console.log(chalk.red(' 1. Stop and remove the Tars background supervisor'));
|
|
14
|
+
console.log(chalk.red(' 2. PERMANENTLY DELETE ~/.tars (Your Brain, Memories, and Data)'));
|
|
15
|
+
console.log(chalk.red(' 3. Remove all configuration and logs\n'));
|
|
16
|
+
const { confirm } = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'confirm',
|
|
19
|
+
name: 'confirm',
|
|
20
|
+
message: 'Are you absolutely sure you want to proceed?',
|
|
21
|
+
default: false
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
if (!confirm) {
|
|
25
|
+
console.log(chalk.cyan('\nUninstall cancelled.'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { finalConfirm } = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'finalConfirm',
|
|
32
|
+
message: 'Type "delete" to confirm complete removal:',
|
|
33
|
+
validate: (input) => (input === 'delete' ? true : 'You must type "delete" to confirm.')
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
if (finalConfirm !== 'delete') {
|
|
37
|
+
console.log(chalk.cyan('\nUninstall cancelled.'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log('\nStarting uninstallation...\n');
|
|
41
|
+
// 1. Stop Tars
|
|
42
|
+
const stopSpinner = ora('Stopping Tars services...').start();
|
|
43
|
+
await new Promise((resolve) => {
|
|
44
|
+
pm2.connect((err) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
// If PM2 fails, we just try to kill manually
|
|
47
|
+
forceKill();
|
|
48
|
+
resolve();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
pm2.delete('tars-supervisor', (delErr) => {
|
|
52
|
+
pm2.disconnect();
|
|
53
|
+
// Even if delete fails (e.g. not running), force kill
|
|
54
|
+
forceKill();
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
stopSpinner.succeed('Tars services stopped.');
|
|
60
|
+
// 2. Remove ~/.tars
|
|
61
|
+
const cleanSpinner = ora('Removing ~/.tars directory...').start();
|
|
62
|
+
const tarsHome = process.env.TARS_HOME || path.join(process.env.REAL_HOME || os.homedir(), '.tars');
|
|
63
|
+
if (existsSync(tarsHome)) {
|
|
64
|
+
try {
|
|
65
|
+
await fs.rm(tarsHome, { recursive: true, force: true });
|
|
66
|
+
cleanSpinner.succeed('Data directory (~/.tars) permanently removed.');
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
cleanSpinner.fail(`Failed to remove ~/.tars: ${error.message}`);
|
|
70
|
+
console.log(chalk.yellow('\nYou may need to manually remove the directory using: rm -rf ~/.tars'));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
cleanSpinner.info('~/.tars directory not found (already clean).');
|
|
75
|
+
}
|
|
76
|
+
// 3. Final message
|
|
77
|
+
console.log(chalk.green.bold('\n✅ Tars has been scrubbed from this system.'));
|
|
78
|
+
console.log(chalk.white('\nTo complete the removal, uninstall the CLI package:'));
|
|
79
|
+
console.log(chalk.cyan(' npm uninstall -g @saccolabs/tars'));
|
|
80
|
+
console.log(chalk.dim('\nGoodbye! 👋'));
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
function forceKill() {
|
|
84
|
+
try {
|
|
85
|
+
execSync('pkill -9 -f "supervisor/main.js" || true', { stdio: 'ignore' });
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Ignore
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=uninstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../../src/cli/commands/uninstall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { setup } from './commands/setup.js';
|
|
4
|
+
import { start } from './commands/start.js';
|
|
5
|
+
import { stop } from './commands/stop.js';
|
|
6
|
+
import { status } from './commands/status.js';
|
|
7
|
+
import { exportBrain } from './commands/export.js';
|
|
8
|
+
import { importBrain } from './commands/import.js';
|
|
9
|
+
import { logs } from './commands/logs.js';
|
|
10
|
+
import { discord } from './commands/discord.js';
|
|
11
|
+
import { secret } from './commands/secret.js';
|
|
12
|
+
import { memory } from './commands/memory.js';
|
|
13
|
+
import { uninstall } from './commands/uninstall.js';
|
|
14
|
+
import { versionString } from '../utils/version.js';
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program.name('tars').description('Tars — Personal AI Assistant').version(versionString);
|
|
17
|
+
program
|
|
18
|
+
.command('setup')
|
|
19
|
+
.description('Interactive onboarding wizard to configure Tars')
|
|
20
|
+
.action(setup);
|
|
21
|
+
program.command('start').description('Start Tars supervisor in the background').action(start);
|
|
22
|
+
program.command('stop').description('Stop the Tars supervisor').action(stop);
|
|
23
|
+
program.command('status').description('Check the status of Tars supervisor').action(status);
|
|
24
|
+
program
|
|
25
|
+
.command('export')
|
|
26
|
+
.description('Export your brain (memories, tasks, extensions)')
|
|
27
|
+
.option('-o, --output <path>', 'Output path for the archive')
|
|
28
|
+
.action(exportBrain);
|
|
29
|
+
program
|
|
30
|
+
.command('import')
|
|
31
|
+
.description('Import a brain from an archive')
|
|
32
|
+
.argument('<path>', 'Path to the brain archive (.tar.gz)')
|
|
33
|
+
.action(importBrain);
|
|
34
|
+
program.command('logs').description('View real-time logs from the Tars supervisor').action(logs);
|
|
35
|
+
program
|
|
36
|
+
.command('discord')
|
|
37
|
+
.description('View instructions for Discord bot setup and invitation')
|
|
38
|
+
.action(discord);
|
|
39
|
+
program
|
|
40
|
+
.command('secret')
|
|
41
|
+
.description('Manage secure environment variables for extensions')
|
|
42
|
+
.argument('<action>', 'Action to perform (set, list, remove)')
|
|
43
|
+
.argument('[key]', 'Secret key')
|
|
44
|
+
.argument('[value]', 'Secret value (required for set)')
|
|
45
|
+
.action(secret);
|
|
46
|
+
program
|
|
47
|
+
.command('memory')
|
|
48
|
+
.description('Search or sync your brain knowledge')
|
|
49
|
+
.argument('<action>', 'Action to perform (search, sync)')
|
|
50
|
+
.argument('[query...]', 'Search query')
|
|
51
|
+
.action((action, queryArgs) => memory(action, ...queryArgs));
|
|
52
|
+
program.command('uninstall').description('Uninstall Tars and remove all data').action(uninstall);
|
|
53
|
+
program.parse();
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class Config {
|
|
2
|
+
private static instance;
|
|
3
|
+
readonly homeDir: string;
|
|
4
|
+
readonly taskFilePath: string;
|
|
5
|
+
readonly sessionFilePath: string;
|
|
6
|
+
readonly configFilePath: string;
|
|
7
|
+
readonly memoryDbPath: string;
|
|
8
|
+
readonly discordToken: string;
|
|
9
|
+
readonly geminiModel: string;
|
|
10
|
+
readonly heartbeatIntervalMs: number;
|
|
11
|
+
readonly systemPromptPath: string;
|
|
12
|
+
private constructor();
|
|
13
|
+
static getInstance(): Config;
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
import { SecretsManager } from '../utils/secrets-manager.js';
|
|
7
|
+
dotenv.config();
|
|
8
|
+
export class Config {
|
|
9
|
+
static instance;
|
|
10
|
+
// Paths
|
|
11
|
+
homeDir;
|
|
12
|
+
taskFilePath;
|
|
13
|
+
sessionFilePath;
|
|
14
|
+
configFilePath;
|
|
15
|
+
memoryDbPath;
|
|
16
|
+
// Discord
|
|
17
|
+
discordToken;
|
|
18
|
+
// Gemini
|
|
19
|
+
geminiModel;
|
|
20
|
+
heartbeatIntervalMs;
|
|
21
|
+
// System Prompt
|
|
22
|
+
systemPromptPath;
|
|
23
|
+
constructor() {
|
|
24
|
+
// 1. Establish Home Directory (~/.tars)
|
|
25
|
+
// Hardcode the base to be the real user home to avoid recursion if HOME is changed for subprocesses
|
|
26
|
+
const realUserHome = process.env.REAL_HOME || os.homedir();
|
|
27
|
+
this.homeDir =
|
|
28
|
+
process.env.TARS_HOME || path.join(realUserHome.replace('/.tars', ''), '.tars');
|
|
29
|
+
this.configFilePath = path.join(this.homeDir, 'config.json');
|
|
30
|
+
// 1.5 Load Secrets into environment
|
|
31
|
+
const secretsManager = new SecretsManager(this.homeDir);
|
|
32
|
+
const secrets = secretsManager.load();
|
|
33
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
34
|
+
// Load into process.env so they are available globally
|
|
35
|
+
process.env[key] = value;
|
|
36
|
+
}
|
|
37
|
+
// 2. Load JSON Config if exists
|
|
38
|
+
let jsonConfig = {};
|
|
39
|
+
try {
|
|
40
|
+
if (fs.existsSync(this.configFilePath)) {
|
|
41
|
+
jsonConfig = JSON.parse(fs.readFileSync(this.configFilePath, 'utf-8'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.warn(`Could not read config file: ${this.configFilePath}`);
|
|
46
|
+
}
|
|
47
|
+
// 3. Set values (Env vars override JSON config)
|
|
48
|
+
this.discordToken = process.env.DISCORD_TOKEN || jsonConfig.discordToken || '';
|
|
49
|
+
this.geminiModel = process.env.GEMINI_MODEL || jsonConfig.geminiModel || 'auto';
|
|
50
|
+
const hbSec = process.env.HEARTBEAT_INTERVAL_SEC || jsonConfig.heartbeatIntervalSec || '300';
|
|
51
|
+
this.heartbeatIntervalMs = parseInt(String(hbSec), 10) * 1000;
|
|
52
|
+
// 4. Derived Paths
|
|
53
|
+
this.taskFilePath = path.join(this.homeDir, 'data', 'tasks.json');
|
|
54
|
+
this.sessionFilePath = path.join(this.homeDir, 'data', 'session.json');
|
|
55
|
+
this.systemPromptPath = path.join(this.homeDir, '.gemini', 'system.md');
|
|
56
|
+
this.memoryDbPath = path.join(this.homeDir, 'data', 'knowledge.db');
|
|
57
|
+
// Note: validation happens in specific services if needed, but we can do a basic check
|
|
58
|
+
if (!this.discordToken) {
|
|
59
|
+
logger.warn('⚠️ No Discord Token found. Please run `tars setup`.');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
static getInstance() {
|
|
63
|
+
if (!Config.instance) {
|
|
64
|
+
Config.instance = new Config();
|
|
65
|
+
}
|
|
66
|
+
return Config.instance;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
constructor(supervisor: Supervisor, config: Config);
|
|
12
|
+
/**
|
|
13
|
+
* Start the Discord bot
|
|
14
|
+
*/
|
|
15
|
+
start(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Stop the Discord bot
|
|
18
|
+
*/
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Setup event handlers
|
|
22
|
+
*/
|
|
23
|
+
private setupEventHandlers;
|
|
24
|
+
/**
|
|
25
|
+
* Handle incoming messages
|
|
26
|
+
*/
|
|
27
|
+
private handleMessage;
|
|
28
|
+
/**
|
|
29
|
+
* Extract prompt and handle prefix !tars
|
|
30
|
+
*/
|
|
31
|
+
private extractPrompt;
|
|
32
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
constructor(supervisor, config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.supervisor = supervisor;
|
|
16
|
+
this.processor = new AttachmentProcessor(config);
|
|
17
|
+
this.client = new Client({
|
|
18
|
+
intents: [
|
|
19
|
+
GatewayIntentBits.Guilds,
|
|
20
|
+
GatewayIntentBits.GuildMessages,
|
|
21
|
+
GatewayIntentBits.MessageContent,
|
|
22
|
+
GatewayIntentBits.DirectMessages
|
|
23
|
+
],
|
|
24
|
+
partials: [Partials.Channel, Partials.Message]
|
|
25
|
+
});
|
|
26
|
+
this.setupEventHandlers();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start the Discord bot
|
|
30
|
+
*/
|
|
31
|
+
async start() {
|
|
32
|
+
await this.client.login(this.config.discordToken);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stop the Discord bot
|
|
36
|
+
*/
|
|
37
|
+
async stop() {
|
|
38
|
+
this.client.destroy();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Setup event handlers
|
|
42
|
+
*/
|
|
43
|
+
setupEventHandlers() {
|
|
44
|
+
this.client.once('clientReady', (c) => {
|
|
45
|
+
logger.info(`🚀 Tars online as ${c.user.tag}`);
|
|
46
|
+
logger.info(`🧠 Gemini Model: ${this.config.geminiModel}`);
|
|
47
|
+
});
|
|
48
|
+
this.client.on('messageCreate', this.handleMessage.bind(this));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Handle incoming messages
|
|
52
|
+
*/
|
|
53
|
+
async handleMessage(message) {
|
|
54
|
+
if (message.author.bot)
|
|
55
|
+
return;
|
|
56
|
+
const userPrompt = this.extractPrompt(message);
|
|
57
|
+
// If null, message wasn't for us. If empty string, check for attachments.
|
|
58
|
+
if (userPrompt === null)
|
|
59
|
+
return;
|
|
60
|
+
if (!userPrompt && message.attachments.size === 0)
|
|
61
|
+
return;
|
|
62
|
+
logger.info(`Received request from ${message.author.tag}: "${userPrompt || '[Attachment Only]'}"`);
|
|
63
|
+
// Handle Attachments
|
|
64
|
+
let attachmentContext = '';
|
|
65
|
+
if (message.attachments.size > 0) {
|
|
66
|
+
for (const [id, attachment] of message.attachments) {
|
|
67
|
+
try {
|
|
68
|
+
const filePath = await this.processor.download(attachment);
|
|
69
|
+
attachmentContext += `\n[User attached file (${attachment.contentType}): ${filePath}]`;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
logger.error(`Failed to download attachment: ${err.message}`);
|
|
73
|
+
await message.reply(`⚠️ Failed to download ${attachment.name}: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const fullPrompt = `${userPrompt}${attachmentContext}`.trim();
|
|
78
|
+
if (!fullPrompt)
|
|
79
|
+
return;
|
|
80
|
+
let typingInterval = null;
|
|
81
|
+
// Start typing indicator loop (Discord typing status lasts 10s)
|
|
82
|
+
if ('sendTyping' in message.channel) {
|
|
83
|
+
// Initial typing
|
|
84
|
+
await message.channel.sendTyping().catch(() => { });
|
|
85
|
+
// Loop every 9s to keep it active
|
|
86
|
+
typingInterval = setInterval(() => {
|
|
87
|
+
if ('sendTyping' in message.channel) {
|
|
88
|
+
message.channel.sendTyping().catch(() => { });
|
|
89
|
+
}
|
|
90
|
+
}, 9000);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
let fullResponse = '';
|
|
94
|
+
await this.supervisor.run(fullPrompt, async (event) => {
|
|
95
|
+
if ((event.type === 'text' || event.type === 'message') &&
|
|
96
|
+
event.content &&
|
|
97
|
+
event.role !== 'user') {
|
|
98
|
+
fullResponse += event.content;
|
|
99
|
+
}
|
|
100
|
+
else if (event.type === 'error') {
|
|
101
|
+
await message.reply(`❌ **Error:** ${event.error}`);
|
|
102
|
+
}
|
|
103
|
+
else if (event.type === 'done') {
|
|
104
|
+
if (fullResponse.trim()) {
|
|
105
|
+
const formatted = MessageFormatter.format(fullResponse);
|
|
106
|
+
if (formatted.length > 1900) {
|
|
107
|
+
const filePath = this.processor.saveResponse(fullResponse, 'md');
|
|
108
|
+
await message.reply({
|
|
109
|
+
content: `📄 **Response too long** (${formatted.length} chars). See attached file:`,
|
|
110
|
+
files: [filePath]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const chunks = MessageFormatter.split(formatted);
|
|
115
|
+
for (const chunk of chunks) {
|
|
116
|
+
await message.reply(chunk);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
logger.error(`Discord handling error: ${error.message}`);
|
|
125
|
+
await message.reply(`❌ **Supervisor Error:** ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (typingInterval)
|
|
129
|
+
clearInterval(typingInterval);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Extract prompt and handle prefix !tars
|
|
134
|
+
*/
|
|
135
|
+
extractPrompt(message) {
|
|
136
|
+
const isDM = message.channel.type === ChannelType.DM;
|
|
137
|
+
const isMentioned = this.client.user && message.mentions.has(this.client.user);
|
|
138
|
+
const hasCommand = message.content.startsWith('!tars');
|
|
139
|
+
if (!isDM && !isMentioned && !hasCommand)
|
|
140
|
+
return null;
|
|
141
|
+
let prompt = message.content;
|
|
142
|
+
if (hasCommand) {
|
|
143
|
+
prompt = prompt.replace('!tars', '');
|
|
144
|
+
}
|
|
145
|
+
if (isMentioned && this.client.user) {
|
|
146
|
+
prompt = prompt.replace(new RegExp(`<@!?${this.client.user.id}>`, 'g'), '');
|
|
147
|
+
}
|
|
148
|
+
return prompt.trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=discord-bot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-bot.js","sourceRoot":"","sources":["../../src/discord/discord-bot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
}
|