agent4discord 0.1.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 (78) hide show
  1. package/README.ko.md +134 -0
  2. package/README.md +170 -0
  3. package/dist/bot.d.ts +1 -0
  4. package/dist/bot.js +114 -0
  5. package/dist/bot.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +27 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/index.d.ts +9 -0
  10. package/dist/commands/index.js +44 -0
  11. package/dist/commands/index.js.map +1 -0
  12. package/dist/commands/init.d.ts +5 -0
  13. package/dist/commands/init.js +152 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/commands/model.d.ts +5 -0
  16. package/dist/commands/model.js +65 -0
  17. package/dist/commands/model.js.map +1 -0
  18. package/dist/commands/resume.d.ts +6 -0
  19. package/dist/commands/resume.js +113 -0
  20. package/dist/commands/resume.js.map +1 -0
  21. package/dist/config.d.ts +12 -0
  22. package/dist/config.js +49 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/formatters/chunker.d.ts +5 -0
  25. package/dist/formatters/chunker.js +46 -0
  26. package/dist/formatters/chunker.js.map +1 -0
  27. package/dist/formatters/embedBuilder.d.ts +28 -0
  28. package/dist/formatters/embedBuilder.js +32 -0
  29. package/dist/formatters/embedBuilder.js.map +1 -0
  30. package/dist/formatters/toolFormatter.d.ts +4 -0
  31. package/dist/formatters/toolFormatter.js +90 -0
  32. package/dist/formatters/toolFormatter.js.map +1 -0
  33. package/dist/guild.d.ts +22 -0
  34. package/dist/guild.js +41 -0
  35. package/dist/guild.js.map +1 -0
  36. package/dist/interactions/directoryBrowser.d.ts +61 -0
  37. package/dist/interactions/directoryBrowser.js +611 -0
  38. package/dist/interactions/directoryBrowser.js.map +1 -0
  39. package/dist/interactions/index.d.ts +5 -0
  40. package/dist/interactions/index.js +92 -0
  41. package/dist/interactions/index.js.map +1 -0
  42. package/dist/interactions/permissionHandler.d.ts +11 -0
  43. package/dist/interactions/permissionHandler.js +107 -0
  44. package/dist/interactions/permissionHandler.js.map +1 -0
  45. package/dist/interactions/sessionControls.d.ts +9 -0
  46. package/dist/interactions/sessionControls.js +95 -0
  47. package/dist/interactions/sessionControls.js.map +1 -0
  48. package/dist/sessions/eventHandler.d.ts +3 -0
  49. package/dist/sessions/eventHandler.js +209 -0
  50. package/dist/sessions/eventHandler.js.map +1 -0
  51. package/dist/sessions/sessionManager.d.ts +29 -0
  52. package/dist/sessions/sessionManager.js +203 -0
  53. package/dist/sessions/sessionManager.js.map +1 -0
  54. package/dist/sessions/sessionStore.d.ts +4 -0
  55. package/dist/sessions/sessionStore.js +29 -0
  56. package/dist/sessions/sessionStore.js.map +1 -0
  57. package/dist/sessions/streamHandler.d.ts +18 -0
  58. package/dist/sessions/streamHandler.js +119 -0
  59. package/dist/sessions/streamHandler.js.map +1 -0
  60. package/dist/sessions/toolProgress.d.ts +14 -0
  61. package/dist/sessions/toolProgress.js +65 -0
  62. package/dist/sessions/toolProgress.js.map +1 -0
  63. package/dist/sessions/usageTracker.d.ts +12 -0
  64. package/dist/sessions/usageTracker.js +222 -0
  65. package/dist/sessions/usageTracker.js.map +1 -0
  66. package/dist/setup.d.ts +1 -0
  67. package/dist/setup.js +101 -0
  68. package/dist/setup.js.map +1 -0
  69. package/dist/utils/filesystem.d.ts +11 -0
  70. package/dist/utils/filesystem.js +26 -0
  71. package/dist/utils/filesystem.js.map +1 -0
  72. package/dist/utils/logger.d.ts +1 -0
  73. package/dist/utils/logger.js +3 -0
  74. package/dist/utils/logger.js.map +1 -0
  75. package/dist/utils/plugins.d.ts +6 -0
  76. package/dist/utils/plugins.js +66 -0
  77. package/dist/utils/plugins.js.map +1 -0
  78. package/package.json +45 -0
@@ -0,0 +1,152 @@
1
+ import os from 'node:os';
2
+ import { ChannelType, MessageFlags, PermissionsBitField, } from 'discord.js';
3
+ import { loadGuildConfig, saveGuildConfig } from '../guild.js';
4
+ import { buildBrowserMessage } from '../interactions/directoryBrowser.js';
5
+ /**
6
+ * Handle `/a4d init` -- create the A4D category and channel structure.
7
+ */
8
+ export async function handleInit(interaction) {
9
+ const guild = interaction.guild;
10
+ if (!guild) {
11
+ await interaction.reply({ content: 'This command can only be used in a server.', flags: MessageFlags.Ephemeral });
12
+ return;
13
+ }
14
+ // Check bot permissions
15
+ const botMember = guild.members.me;
16
+ if (!botMember || !botMember.permissions.has(PermissionsBitField.Flags.ManageChannels)) {
17
+ await interaction.reply({
18
+ content: 'I need the "Manage Channels" permission to set up A4D. Please check my role permissions.',
19
+ flags: MessageFlags.Ephemeral,
20
+ });
21
+ return;
22
+ }
23
+ // Check if already initialized
24
+ const existing = loadGuildConfig(guild.id);
25
+ if (existing) {
26
+ const channelsValid = await validateChannels(guild, existing);
27
+ if (channelsValid) {
28
+ await interaction.reply({
29
+ content: 'A4D is already set up in this server.',
30
+ flags: MessageFlags.Ephemeral,
31
+ });
32
+ return;
33
+ }
34
+ // Channels were deleted -- re-create
35
+ }
36
+ await interaction.deferReply({ flags: MessageFlags.Ephemeral });
37
+ try {
38
+ const config = await createGuildStructure(guild, interaction.user.id, existing);
39
+ saveGuildConfig(config);
40
+ const sessionChannel = guild.channels.cache.get(config.sessionChannelId);
41
+ const mention = sessionChannel ? `<#${config.sessionChannelId}>` : '#a4d-session';
42
+ await interaction.editReply({
43
+ content: `Setup complete! Head to ${mention} to start a session.`,
44
+ });
45
+ }
46
+ catch (err) {
47
+ console.error(`[init] Failed to set up guild ${guild.id}:`, err);
48
+ await interaction.editReply({
49
+ content: 'Failed to set up A4D. Make sure I have the "Manage Channels" permission.',
50
+ });
51
+ }
52
+ }
53
+ async function validateChannels(guild, config) {
54
+ try {
55
+ const channels = guild.channels.cache;
56
+ return (channels.has(config.generalCategoryId) &&
57
+ channels.has(config.sessionsCategoryId) &&
58
+ channels.has(config.generalChannelId) &&
59
+ channels.has(config.sessionChannelId));
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
65
+ async function createGuildStructure(guild, userId, existing) {
66
+ // Create or reuse "A4D - General" category
67
+ let generalCategory;
68
+ if (existing?.generalCategoryId && guild.channels.cache.has(existing.generalCategoryId)) {
69
+ generalCategory = guild.channels.cache.get(existing.generalCategoryId);
70
+ }
71
+ else {
72
+ generalCategory = await guild.channels.create({
73
+ name: 'A4D - General',
74
+ type: ChannelType.GuildCategory,
75
+ });
76
+ }
77
+ // Create or reuse #a4d-general
78
+ let generalChannelId;
79
+ if (existing?.generalChannelId && guild.channels.cache.has(existing.generalChannelId)) {
80
+ generalChannelId = existing.generalChannelId;
81
+ }
82
+ else {
83
+ const ch = await guild.channels.create({
84
+ name: 'a4d-general',
85
+ type: ChannelType.GuildText,
86
+ parent: generalCategory.id,
87
+ });
88
+ generalChannelId = ch.id;
89
+ }
90
+ // Create or reuse #a4d-session
91
+ let sessionChannelId;
92
+ if (existing?.sessionChannelId && guild.channels.cache.has(existing.sessionChannelId)) {
93
+ sessionChannelId = existing.sessionChannelId;
94
+ }
95
+ else {
96
+ const ch = await guild.channels.create({
97
+ name: 'a4d-session',
98
+ type: ChannelType.GuildText,
99
+ parent: generalCategory.id,
100
+ });
101
+ sessionChannelId = ch.id;
102
+ }
103
+ // Create or reuse #a4d-usage
104
+ let usageChannelId;
105
+ let usageMessageId;
106
+ if (existing?.usageChannelId && guild.channels.cache.has(existing.usageChannelId)) {
107
+ usageChannelId = existing.usageChannelId;
108
+ usageMessageId = existing.usageMessageId;
109
+ }
110
+ else {
111
+ const ch = await guild.channels.create({
112
+ name: 'a4d-usage',
113
+ type: ChannelType.GuildText,
114
+ parent: generalCategory.id,
115
+ });
116
+ usageChannelId = ch.id;
117
+ // Send initial usage embed
118
+ const { buildUsageEmbed } = await import('../sessions/usageTracker.js');
119
+ const usageMsg = await ch.send({ embeds: [buildUsageEmbed()] });
120
+ usageMessageId = usageMsg.id;
121
+ }
122
+ // Create or reuse "A4D - Sessions" category
123
+ let sessionsCategory;
124
+ if (existing?.sessionsCategoryId && guild.channels.cache.has(existing.sessionsCategoryId)) {
125
+ sessionsCategory = guild.channels.cache.get(existing.sessionsCategoryId);
126
+ }
127
+ else {
128
+ sessionsCategory = await guild.channels.create({
129
+ name: 'A4D - Sessions',
130
+ type: ChannelType.GuildCategory,
131
+ });
132
+ }
133
+ // Send directory browser in #a4d-session
134
+ const sessionChannel = guild.channels.cache.get(sessionChannelId);
135
+ if (sessionChannel?.isTextBased()) {
136
+ const browserMsg = await buildBrowserMessage(os.homedir());
137
+ await sessionChannel.send(browserMsg);
138
+ }
139
+ return {
140
+ guildId: guild.id,
141
+ generalCategoryId: generalCategory.id,
142
+ sessionsCategoryId: sessionsCategory.id,
143
+ generalChannelId,
144
+ sessionChannelId,
145
+ usageChannelId,
146
+ usageMessageId,
147
+ initializedAt: new Date().toISOString(),
148
+ initializedBy: userId,
149
+ activeSessions: existing?.activeSessions ?? {},
150
+ };
151
+ }
152
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type ChatInputCommandInteraction } from 'discord.js';
2
+ /**
3
+ * Handle `/a4d model <model>` -- change the model for the current session.
4
+ */
5
+ export declare function handleModel(interaction: ChatInputCommandInteraction): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { MessageFlags, } from 'discord.js';
2
+ import { sessionManager } from '../sessions/sessionManager.js';
3
+ import { loadGuildConfig } from '../guild.js';
4
+ import { buildStatusEmbed, COLORS } from '../formatters/embedBuilder.js';
5
+ /**
6
+ * Handle `/a4d model <model>` -- change the model for the current session.
7
+ */
8
+ export async function handleModel(interaction) {
9
+ const guild = interaction.guild;
10
+ if (!guild) {
11
+ await interaction.reply({ content: 'This command can only be used in a server.', flags: MessageFlags.Ephemeral });
12
+ return;
13
+ }
14
+ const guildConfig = loadGuildConfig(guild.id);
15
+ if (!guildConfig) {
16
+ await interaction.reply({ content: 'A4D is not set up. Run `/a4d init` first.', flags: MessageFlags.Ephemeral });
17
+ return;
18
+ }
19
+ // Must be in a session channel
20
+ const channel = interaction.channel;
21
+ if (channel.parentId !== guildConfig.sessionsCategoryId) {
22
+ await interaction.reply({
23
+ content: 'This command can only be used in a session channel under "A4D - Sessions".',
24
+ flags: MessageFlags.Ephemeral,
25
+ });
26
+ return;
27
+ }
28
+ const session = sessionManager.getSession(channel.id);
29
+ if (!session) {
30
+ await interaction.reply({ content: 'No active session in this channel.', flags: MessageFlags.Ephemeral });
31
+ return;
32
+ }
33
+ const model = interaction.options.getString('model', true);
34
+ try {
35
+ await session.query.setModel(model);
36
+ }
37
+ catch (err) {
38
+ console.error('[model] Failed to set model:', err);
39
+ await interaction.reply({ content: `Failed to change model: ${err}`, flags: MessageFlags.Ephemeral });
40
+ return;
41
+ }
42
+ // Update the pinned status embed
43
+ try {
44
+ const pinned = await channel.messages.fetchPinned();
45
+ const statusMsg = pinned.find((m) => m.author.id === interaction.client.user?.id && m.embeds.length > 0);
46
+ if (statusMsg) {
47
+ const embed = statusMsg.embeds[0];
48
+ const updatedEmbed = buildStatusEmbed({
49
+ status: 'Session Active',
50
+ color: COLORS.IDLE,
51
+ cwd: embed.fields.find((f) => f.name === 'Directory')?.value ?? session.cwd,
52
+ model,
53
+ sessionId: session.sessionId || 'pending',
54
+ costUsd: session.totalCostUsd,
55
+ startedAt: session.createdAt,
56
+ });
57
+ await statusMsg.edit({ embeds: [updatedEmbed] });
58
+ }
59
+ }
60
+ catch {
61
+ // Status embed update is best-effort
62
+ }
63
+ await interaction.reply({ content: `Model changed to **${model}**.`, flags: MessageFlags.Ephemeral });
64
+ }
65
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/commands/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type ChatInputCommandInteraction } from 'discord.js';
2
+ /**
3
+ * Handle `/a4d resume` -- resume a stopped/archived session in the current channel.
4
+ * Reads the sessionId and cwd from the pinned status embed.
5
+ */
6
+ export declare function handleResume(interaction: ChatInputCommandInteraction): Promise<void>;
@@ -0,0 +1,113 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { MessageFlags, ActionRowBuilder, ButtonBuilder, ButtonStyle, } from 'discord.js';
4
+ import { listSessions } from '@anthropic-ai/claude-agent-sdk';
5
+ import { sessionManager } from '../sessions/sessionManager.js';
6
+ import { saveSessionToGuild } from '../sessions/sessionStore.js';
7
+ import { loadGuildConfig } from '../guild.js';
8
+ import { buildStatusEmbed, COLORS } from '../formatters/embedBuilder.js';
9
+ import { requestPermission } from '../interactions/permissionHandler.js';
10
+ /**
11
+ * Handle `/a4d resume` -- resume a stopped/archived session in the current channel.
12
+ * Reads the sessionId and cwd from the pinned status embed.
13
+ */
14
+ export async function handleResume(interaction) {
15
+ const guild = interaction.guild;
16
+ if (!guild) {
17
+ await interaction.reply({ content: 'This command can only be used in a server.', flags: MessageFlags.Ephemeral });
18
+ return;
19
+ }
20
+ const guildConfig = loadGuildConfig(guild.id);
21
+ if (!guildConfig) {
22
+ await interaction.reply({ content: 'A4D is not set up. Run `/a4d init` first.', flags: MessageFlags.Ephemeral });
23
+ return;
24
+ }
25
+ // Check if this channel is under the Sessions category
26
+ const channel = interaction.channel;
27
+ if (channel.parentId !== guildConfig.sessionsCategoryId) {
28
+ await interaction.reply({
29
+ content: 'This command can only be used in a session channel under "A4D - Sessions".',
30
+ flags: MessageFlags.Ephemeral,
31
+ });
32
+ return;
33
+ }
34
+ // Check if there's already an active session in this channel
35
+ const existing = sessionManager.getSession(channel.id);
36
+ if (existing && existing.state !== 'stopped' && existing.state !== 'archived') {
37
+ await interaction.reply({ content: 'This session is already active.', flags: MessageFlags.Ephemeral });
38
+ return;
39
+ }
40
+ // Find the status embed -- try pinned first, then search recent messages
41
+ let statusMsg = (await channel.messages.fetchPinned()).find((m) => m.author.id === interaction.client.user?.id &&
42
+ m.embeds.length > 0 &&
43
+ m.embeds[0].fields.some((f) => f.name === 'Session ID')) ?? null;
44
+ if (!statusMsg) {
45
+ const recent = await channel.messages.fetch({ limit: 50 });
46
+ statusMsg = recent.find((m) => m.author.id === interaction.client.user?.id &&
47
+ m.embeds.length > 0 &&
48
+ m.embeds[0].fields.some((f) => f.name === 'Session ID')) ?? null;
49
+ }
50
+ if (!statusMsg || statusMsg.embeds.length === 0) {
51
+ await interaction.reply({ content: 'No session status embed found in this channel.', flags: MessageFlags.Ephemeral });
52
+ return;
53
+ }
54
+ const embed = statusMsg.embeds[0];
55
+ const rawCwd = embed.fields.find((f) => f.name === 'Directory')?.value;
56
+ let sessionId = embed.fields.find((f) => f.name === 'Session ID')?.value;
57
+ const model = embed.fields.find((f) => f.name === 'Model')?.value || 'opus';
58
+ if (!rawCwd) {
59
+ await interaction.reply({ content: 'Could not find directory info in the status embed.', flags: MessageFlags.Ephemeral });
60
+ return;
61
+ }
62
+ // Resolve ~ back to absolute path
63
+ const cwd = rawCwd.startsWith('~')
64
+ ? path.join(os.homedir(), rawCwd.slice(1))
65
+ : rawCwd;
66
+ // If sessionId is missing or pending, find the most recent session for this directory
67
+ if (!sessionId || sessionId === 'pending') {
68
+ try {
69
+ const sessions = await listSessions({ dir: cwd, limit: 1 });
70
+ if (sessions.length > 0) {
71
+ sessionId = sessions[0].sessionId;
72
+ }
73
+ }
74
+ catch {
75
+ // listSessions may not be available
76
+ }
77
+ }
78
+ if (!sessionId || sessionId === 'pending') {
79
+ await interaction.reply({ content: 'No session found for this directory. Try starting a new session instead.', flags: MessageFlags.Ephemeral });
80
+ return;
81
+ }
82
+ await interaction.deferReply({ flags: MessageFlags.Ephemeral });
83
+ try {
84
+ // If channel was archived (read-only), restore write permissions
85
+ await channel.permissionOverwrites.edit(guild.roles.everyone, {
86
+ SendMessages: null, // reset to inherit
87
+ }).catch(() => { });
88
+ // Resume the session
89
+ const session = sessionManager.resumeSession(guild.id, interaction.user.id, channel.id, sessionId, cwd, model, async (toolName, input) => {
90
+ return requestPermission(channel, interaction.user.id, toolName, input);
91
+ });
92
+ // Persist to guild config
93
+ saveSessionToGuild(guild.id, channel.id, sessionId, cwd, interaction.user.id);
94
+ // Update the status embed to active
95
+ const controlRow = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId('a4d:session:stop').setLabel('Stop').setStyle(ButtonStyle.Danger), new ButtonBuilder().setCustomId('a4d:session:archive').setLabel('Archive').setStyle(ButtonStyle.Secondary));
96
+ const updatedEmbed = buildStatusEmbed({
97
+ status: 'Session Active',
98
+ color: COLORS.IDLE,
99
+ cwd,
100
+ model,
101
+ sessionId,
102
+ costUsd: session.totalCostUsd,
103
+ startedAt: new Date().toISOString(),
104
+ });
105
+ await statusMsg.edit({ embeds: [updatedEmbed], components: [controlRow] });
106
+ await interaction.editReply({ content: 'Session resumed! You can start chatting again.' });
107
+ }
108
+ catch (err) {
109
+ console.error('[resume] Failed to resume session:', err);
110
+ await interaction.editReply({ content: 'Failed to resume session. Check the bot console for details.' });
111
+ }
112
+ }
113
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/commands/resume.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface AppConfig {
2
+ discordToken: string;
3
+ discordClientId: string;
4
+ claudeModel: string;
5
+ permissionMode: string;
6
+ logLevel: string;
7
+ }
8
+ export declare const CONFIG_DIR: string;
9
+ export declare const CONFIG_PATH: string;
10
+ export declare function loadConfig(): AppConfig;
11
+ export declare function saveConfig(config: AppConfig): void;
12
+ export declare function configExists(): boolean;
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ export const CONFIG_DIR = path.join(os.homedir(), '.agent4discord');
5
+ export const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
6
+ const DEFAULTS = {
7
+ claudeModel: 'opus',
8
+ permissionMode: 'default',
9
+ logLevel: 'info',
10
+ };
11
+ export function loadConfig() {
12
+ if (!fs.existsSync(CONFIG_PATH)) {
13
+ throw new Error(`Config file not found at ${CONFIG_PATH}. Run "agent4discord --setup" first.`);
14
+ }
15
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
16
+ const parsed = JSON.parse(raw);
17
+ if (typeof parsed !== 'object' || parsed === null) {
18
+ throw new Error('Invalid config file: expected a JSON object.');
19
+ }
20
+ const obj = parsed;
21
+ if (typeof obj['discordToken'] !== 'string' || obj['discordToken'].length === 0) {
22
+ throw new Error('Config missing required field: discordToken');
23
+ }
24
+ if (typeof obj['discordClientId'] !== 'string' || obj['discordClientId'].length === 0) {
25
+ throw new Error('Config missing required field: discordClientId');
26
+ }
27
+ return {
28
+ discordToken: obj['discordToken'],
29
+ discordClientId: obj['discordClientId'],
30
+ claudeModel: (typeof obj['claudeModel'] === 'string' ? obj['claudeModel'] : DEFAULTS.claudeModel),
31
+ permissionMode: (typeof obj['permissionMode'] === 'string' ? obj['permissionMode'] : DEFAULTS.permissionMode),
32
+ logLevel: (typeof obj['logLevel'] === 'string' ? obj['logLevel'] : DEFAULTS.logLevel),
33
+ };
34
+ }
35
+ export function saveConfig(config) {
36
+ if (!fs.existsSync(CONFIG_DIR)) {
37
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
38
+ }
39
+ const data = JSON.stringify(config, null, 2) + '\n';
40
+ fs.writeFileSync(CONFIG_PATH, data, { encoding: 'utf-8' });
41
+ // Set restrictive permissions on non-Windows platforms
42
+ if (process.platform !== 'win32') {
43
+ fs.chmodSync(CONFIG_PATH, 0o600);
44
+ }
45
+ }
46
+ export function configExists() {
47
+ return fs.existsSync(CONFIG_PATH);
48
+ }
49
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Split a long text into chunks that respect Discord's 2000 char limit.
3
+ * Tries to break at code block boundaries, then newlines, then hard-cut.
4
+ */
5
+ export declare function chunkMessage(text: string, maxLength?: number): string[];
@@ -0,0 +1,46 @@
1
+ const MAX_LENGTH = 2000;
2
+ /**
3
+ * Split a long text into chunks that respect Discord's 2000 char limit.
4
+ * Tries to break at code block boundaries, then newlines, then hard-cut.
5
+ */
6
+ export function chunkMessage(text, maxLength = MAX_LENGTH) {
7
+ if (text.length <= maxLength)
8
+ return [text];
9
+ const chunks = [];
10
+ let remaining = text;
11
+ while (remaining.length > 0) {
12
+ if (remaining.length <= maxLength) {
13
+ chunks.push(remaining);
14
+ break;
15
+ }
16
+ let breakIdx = findBreakPoint(remaining, maxLength);
17
+ chunks.push(remaining.slice(0, breakIdx));
18
+ remaining = remaining.slice(breakIdx);
19
+ }
20
+ return chunks;
21
+ }
22
+ /**
23
+ * Find the best break point within maxLength characters.
24
+ * Priority: code block boundary > double newline > single newline > hard cut.
25
+ */
26
+ function findBreakPoint(text, maxLength) {
27
+ const segment = text.slice(0, maxLength);
28
+ // Try to break at code block end (```)
29
+ const codeBlockEnd = segment.lastIndexOf('\n```\n');
30
+ if (codeBlockEnd > maxLength * 0.3) {
31
+ return codeBlockEnd + 4; // include the closing ``` and newline
32
+ }
33
+ // Try to break at double newline
34
+ const doubleNewline = segment.lastIndexOf('\n\n');
35
+ if (doubleNewline > maxLength * 0.3) {
36
+ return doubleNewline + 1;
37
+ }
38
+ // Try to break at single newline
39
+ const newline = segment.lastIndexOf('\n');
40
+ if (newline > maxLength * 0.3) {
41
+ return newline + 1;
42
+ }
43
+ // Hard cut at maxLength
44
+ return maxLength;
45
+ }
46
+ //# sourceMappingURL=chunker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunker.js","sourceRoot":"","sources":["../../src/formatters/chunker.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,SAAS,GAAG,UAAU;IAC/D,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,IAAI,CAAC;IAErB,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QAED,IAAI,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,IAAY,EAAE,SAAiB;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAEzC,uCAAuC;IACvC,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACpD,IAAI,YAAY,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;QACnC,OAAO,YAAY,GAAG,CAAC,CAAC,CAAC,sCAAsC;IACjE,CAAC;IAED,iCAAiC;IACjC,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,aAAa,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;QACpC,OAAO,aAAa,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,iCAAiC;IACjC,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;QAC9B,OAAO,OAAO,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,wBAAwB;IACxB,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { EmbedBuilder } from 'discord.js';
2
+ export declare const COLORS: {
3
+ readonly STREAMING: 16705372;
4
+ readonly THINKING: 10181046;
5
+ readonly TOOL_PROGRESS: 15105570;
6
+ readonly IDLE: 5763719;
7
+ readonly RUNNING: 16705372;
8
+ readonly PERMISSION: 15105570;
9
+ readonly STOPPED: 15548997;
10
+ readonly ARCHIVED: 9807270;
11
+ readonly ERROR: 15548997;
12
+ };
13
+ /**
14
+ * Build a session status embed.
15
+ */
16
+ export declare function buildStatusEmbed(opts: {
17
+ status: string;
18
+ color: number;
19
+ cwd: string;
20
+ model: string;
21
+ sessionId: string;
22
+ costUsd: number;
23
+ startedAt: string;
24
+ }): EmbedBuilder;
25
+ /**
26
+ * Build an error embed.
27
+ */
28
+ export declare function buildErrorEmbed(message: string): EmbedBuilder;
@@ -0,0 +1,32 @@
1
+ import { EmbedBuilder } from 'discord.js';
2
+ // Embed color constants
3
+ export const COLORS = {
4
+ STREAMING: 0xfee75c, // Yellow -- active streaming
5
+ THINKING: 0x9b59b6, // Purple -- thinking in progress
6
+ TOOL_PROGRESS: 0xe67e22, // Orange -- tool executing
7
+ IDLE: 0x57f287, // Green -- session idle
8
+ RUNNING: 0xfee75c, // Yellow -- session running
9
+ PERMISSION: 0xe67e22, // Orange -- awaiting permission
10
+ STOPPED: 0xed4245, // Red -- session stopped
11
+ ARCHIVED: 0x95a5a6, // Grey -- session archived
12
+ ERROR: 0xed4245, // Red -- error
13
+ };
14
+ /**
15
+ * Build a session status embed.
16
+ */
17
+ export function buildStatusEmbed(opts) {
18
+ return new EmbedBuilder()
19
+ .setTitle(`${opts.status}`)
20
+ .setColor(opts.color)
21
+ .addFields({ name: 'Directory', value: opts.cwd, inline: true }, { name: 'Model', value: opts.model, inline: true }, { name: 'Session ID', value: opts.sessionId || 'pending', inline: false }, { name: 'Cost', value: `$${opts.costUsd.toFixed(4)}`, inline: true }, { name: 'Started', value: opts.startedAt, inline: true });
22
+ }
23
+ /**
24
+ * Build an error embed.
25
+ */
26
+ export function buildErrorEmbed(message) {
27
+ return new EmbedBuilder()
28
+ .setTitle('Error')
29
+ .setDescription(message)
30
+ .setColor(COLORS.ERROR);
31
+ }
32
+ //# sourceMappingURL=embedBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedBuilder.js","sourceRoot":"","sources":["../../src/formatters/embedBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,wBAAwB;AACxB,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,SAAS,EAAE,QAAQ,EAAK,6BAA6B;IACrD,QAAQ,EAAE,QAAQ,EAAM,iCAAiC;IACzD,aAAa,EAAE,QAAQ,EAAE,2BAA2B;IACpD,IAAI,EAAE,QAAQ,EAAU,wBAAwB;IAChD,OAAO,EAAE,QAAQ,EAAO,4BAA4B;IACpD,UAAU,EAAE,QAAQ,EAAI,gCAAgC;IACxD,OAAO,EAAE,QAAQ,EAAO,yBAAyB;IACjD,QAAQ,EAAE,QAAQ,EAAM,2BAA2B;IACnD,KAAK,EAAE,QAAQ,EAAS,eAAe;CAC/B,CAAC;AAEX;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAQhC;IACC,OAAO,IAAI,YAAY,EAAE;SACtB,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;SAC1B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;SACpB,SAAS,CACR,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,EACpD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAClD,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EACzE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EACpE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CACzD,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,IAAI,YAAY,EAAE;SACtB,QAAQ,CAAC,OAAO,CAAC;SACjB,cAAc,CAAC,OAAO,CAAC;SACvB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function getToolEmoji(toolName: string): string;
2
+ export declare function formatThreadName(toolName: string, input: Record<string, any>): string;
3
+ export declare function formatToolInput(toolName: string, input: Record<string, any>): string;
4
+ export declare function formatToolResult(toolName: string, result: unknown): string;