disunday 1.0.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.
- package/dist/ai-tool-to-genai.js +208 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/channel-management.js +96 -0
- package/dist/cli.js +1674 -0
- package/dist/commands/abort.js +89 -0
- package/dist/commands/add-project.js +117 -0
- package/dist/commands/agent.js +250 -0
- package/dist/commands/ask-question.js +219 -0
- package/dist/commands/compact.js +126 -0
- package/dist/commands/context-menu.js +171 -0
- package/dist/commands/context.js +89 -0
- package/dist/commands/cost.js +93 -0
- package/dist/commands/create-new-project.js +111 -0
- package/dist/commands/diff.js +77 -0
- package/dist/commands/export.js +100 -0
- package/dist/commands/files.js +73 -0
- package/dist/commands/fork.js +199 -0
- package/dist/commands/help.js +54 -0
- package/dist/commands/login.js +488 -0
- package/dist/commands/merge-worktree.js +165 -0
- package/dist/commands/model.js +325 -0
- package/dist/commands/permissions.js +140 -0
- package/dist/commands/ping.js +13 -0
- package/dist/commands/queue.js +133 -0
- package/dist/commands/remove-project.js +119 -0
- package/dist/commands/rename.js +70 -0
- package/dist/commands/restart-opencode-server.js +77 -0
- package/dist/commands/resume.js +276 -0
- package/dist/commands/run-config.js +79 -0
- package/dist/commands/run.js +240 -0
- package/dist/commands/schedule.js +170 -0
- package/dist/commands/session-info.js +58 -0
- package/dist/commands/session.js +191 -0
- package/dist/commands/settings.js +84 -0
- package/dist/commands/share.js +89 -0
- package/dist/commands/status.js +79 -0
- package/dist/commands/sync.js +119 -0
- package/dist/commands/theme.js +53 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +170 -0
- package/dist/commands/user-command.js +135 -0
- package/dist/commands/verbosity.js +59 -0
- package/dist/commands/worktree-settings.js +50 -0
- package/dist/commands/worktree.js +288 -0
- package/dist/config.js +139 -0
- package/dist/database.js +585 -0
- package/dist/discord-bot.js +700 -0
- package/dist/discord-utils.js +336 -0
- package/dist/discord-utils.test.js +20 -0
- package/dist/errors.js +193 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/format-tables.js +96 -0
- package/dist/format-tables.test.js +418 -0
- package/dist/genai-worker-wrapper.js +109 -0
- package/dist/genai-worker.js +299 -0
- package/dist/genai.js +230 -0
- package/dist/image-utils.js +107 -0
- package/dist/interaction-handler.js +289 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +111 -0
- package/dist/markdown.js +323 -0
- package/dist/markdown.test.js +269 -0
- package/dist/message-formatting.js +447 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/openai-realtime.js +226 -0
- package/dist/opencode.js +224 -0
- package/dist/reaction-handler.js +128 -0
- package/dist/scheduler.js +93 -0
- package/dist/security.js +200 -0
- package/dist/session-handler.js +1436 -0
- package/dist/system-message.js +138 -0
- package/dist/tools.js +354 -0
- package/dist/unnest-code-blocks.js +117 -0
- package/dist/unnest-code-blocks.test.js +432 -0
- package/dist/utils.js +95 -0
- package/dist/voice-handler.js +569 -0
- package/dist/voice.js +344 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-utils.js +134 -0
- package/dist/xml.js +90 -0
- package/dist/xml.test.js +32 -0
- package/package.json +84 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Discord slash command and interaction handler.
|
|
2
|
+
// Processes all slash commands (/session, /resume, /fork, /model, /abort, etc.)
|
|
3
|
+
// and manages autocomplete, select menu interactions for the bot.
|
|
4
|
+
import { Events } from 'discord.js';
|
|
5
|
+
import { handleSessionCommand, handleSessionAutocomplete, } from './commands/session.js';
|
|
6
|
+
import { handleNewWorktreeCommand } from './commands/worktree.js';
|
|
7
|
+
import { handleMergeWorktreeCommand } from './commands/merge-worktree.js';
|
|
8
|
+
import { handleToggleWorktreesCommand } from './commands/worktree-settings.js';
|
|
9
|
+
import { handleResumeCommand, handleResumeAutocomplete, } from './commands/resume.js';
|
|
10
|
+
import { handleAddProjectCommand, handleAddProjectAutocomplete, } from './commands/add-project.js';
|
|
11
|
+
import { handleRemoveProjectCommand, handleRemoveProjectAutocomplete, } from './commands/remove-project.js';
|
|
12
|
+
import { handleCreateNewProjectCommand } from './commands/create-new-project.js';
|
|
13
|
+
import { handlePermissionSelectMenu } from './commands/permissions.js';
|
|
14
|
+
import { handleAbortCommand } from './commands/abort.js';
|
|
15
|
+
import { handleCompactCommand } from './commands/compact.js';
|
|
16
|
+
import { handleShareCommand } from './commands/share.js';
|
|
17
|
+
import { handleRenameCommand } from './commands/rename.js';
|
|
18
|
+
import { handleSessionInfoCommand } from './commands/session-info.js';
|
|
19
|
+
import { handleSyncCommand } from './commands/sync.js';
|
|
20
|
+
import { handleForkCommand, handleForkSelectMenu } from './commands/fork.js';
|
|
21
|
+
import { handleModelCommand, handleProviderSelectMenu, handleModelSelectMenu, } from './commands/model.js';
|
|
22
|
+
import { handleLoginCommand, handleLoginProviderSelectMenu, handleLoginMethodSelectMenu, handleApiKeyModalSubmit, } from './commands/login.js';
|
|
23
|
+
import { handleAgentCommand, handleAgentSelectMenu, handleQuickAgentCommand, } from './commands/agent.js';
|
|
24
|
+
import { handleAskQuestionSelectMenu } from './commands/ask-question.js';
|
|
25
|
+
import { handleQueueCommand, handleClearQueueCommand, } from './commands/queue.js';
|
|
26
|
+
import { handleUndoCommand, handleRedoCommand } from './commands/undo-redo.js';
|
|
27
|
+
import { handleUserCommand } from './commands/user-command.js';
|
|
28
|
+
import { handleVerbosityCommand } from './commands/verbosity.js';
|
|
29
|
+
import { handleThemeCommand } from './commands/theme.js';
|
|
30
|
+
import { handleSettingsCommand } from './commands/settings.js';
|
|
31
|
+
import { handleRestartOpencodeServerCommand } from './commands/restart-opencode-server.js';
|
|
32
|
+
import { handleRunCommand, handleRunAutocomplete } from './commands/run.js';
|
|
33
|
+
import { handleRunConfigCommand } from './commands/run-config.js';
|
|
34
|
+
import { handleStatusCommand } from './commands/status.js';
|
|
35
|
+
import { handleHelpCommand } from './commands/help.js';
|
|
36
|
+
import { handlePingCommand } from './commands/ping.js';
|
|
37
|
+
import { handleContextCommand } from './commands/context.js';
|
|
38
|
+
import { handleCostCommand } from './commands/cost.js';
|
|
39
|
+
import { handleDiffCommand } from './commands/diff.js';
|
|
40
|
+
import { handleExportCommand } from './commands/export.js';
|
|
41
|
+
import { handleFilesCommand } from './commands/files.js';
|
|
42
|
+
import { handleScheduleCommand } from './commands/schedule.js';
|
|
43
|
+
import { handleRetryContextMenu, handleForkContextMenu, } from './commands/context-menu.js';
|
|
44
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
45
|
+
const interactionLogger = createLogger(LogPrefix.INTERACTION);
|
|
46
|
+
export function registerInteractionHandler({ discordClient, appId, }) {
|
|
47
|
+
interactionLogger.log('[REGISTER] Interaction handler registered');
|
|
48
|
+
discordClient.on(Events.InteractionCreate, async (interaction) => {
|
|
49
|
+
try {
|
|
50
|
+
interactionLogger.log(`[INTERACTION] Received: ${interaction.type} - ${interaction.isChatInputCommand()
|
|
51
|
+
? interaction.commandName
|
|
52
|
+
: interaction.isAutocomplete()
|
|
53
|
+
? `autocomplete:${interaction.commandName}`
|
|
54
|
+
: 'other'}`);
|
|
55
|
+
if (interaction.isAutocomplete()) {
|
|
56
|
+
switch (interaction.commandName) {
|
|
57
|
+
case 'new-session':
|
|
58
|
+
await handleSessionAutocomplete({ interaction, appId });
|
|
59
|
+
return;
|
|
60
|
+
case 'resume':
|
|
61
|
+
await handleResumeAutocomplete({ interaction, appId });
|
|
62
|
+
return;
|
|
63
|
+
case 'add-project':
|
|
64
|
+
await handleAddProjectAutocomplete({ interaction, appId });
|
|
65
|
+
return;
|
|
66
|
+
case 'remove-project':
|
|
67
|
+
await handleRemoveProjectAutocomplete({ interaction, appId });
|
|
68
|
+
return;
|
|
69
|
+
case 'run':
|
|
70
|
+
await handleRunAutocomplete({ interaction });
|
|
71
|
+
return;
|
|
72
|
+
default:
|
|
73
|
+
await interaction.respond([]);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (interaction.isChatInputCommand()) {
|
|
78
|
+
interactionLogger.log(`[COMMAND] Processing: ${interaction.commandName}`);
|
|
79
|
+
switch (interaction.commandName) {
|
|
80
|
+
case 'new-session':
|
|
81
|
+
await handleSessionCommand({ command: interaction, appId });
|
|
82
|
+
return;
|
|
83
|
+
case 'new-worktree':
|
|
84
|
+
await handleNewWorktreeCommand({ command: interaction, appId });
|
|
85
|
+
return;
|
|
86
|
+
case 'merge-worktree':
|
|
87
|
+
await handleMergeWorktreeCommand({ command: interaction, appId });
|
|
88
|
+
return;
|
|
89
|
+
case 'toggle-worktrees':
|
|
90
|
+
await handleToggleWorktreesCommand({
|
|
91
|
+
command: interaction,
|
|
92
|
+
appId,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
case 'resume':
|
|
96
|
+
await handleResumeCommand({ command: interaction, appId });
|
|
97
|
+
return;
|
|
98
|
+
case 'add-project':
|
|
99
|
+
await handleAddProjectCommand({ command: interaction, appId });
|
|
100
|
+
return;
|
|
101
|
+
case 'remove-project':
|
|
102
|
+
await handleRemoveProjectCommand({ command: interaction, appId });
|
|
103
|
+
return;
|
|
104
|
+
case 'create-new-project':
|
|
105
|
+
await handleCreateNewProjectCommand({
|
|
106
|
+
command: interaction,
|
|
107
|
+
appId,
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
case 'abort':
|
|
111
|
+
case 'stop':
|
|
112
|
+
await handleAbortCommand({ command: interaction, appId });
|
|
113
|
+
return;
|
|
114
|
+
case 'compact':
|
|
115
|
+
await handleCompactCommand({ command: interaction, appId });
|
|
116
|
+
return;
|
|
117
|
+
case 'share':
|
|
118
|
+
await handleShareCommand({ command: interaction, appId });
|
|
119
|
+
return;
|
|
120
|
+
case 'rename':
|
|
121
|
+
await handleRenameCommand({ command: interaction, appId });
|
|
122
|
+
return;
|
|
123
|
+
case 'session-info':
|
|
124
|
+
await handleSessionInfoCommand({ command: interaction, appId });
|
|
125
|
+
return;
|
|
126
|
+
case 'sync':
|
|
127
|
+
await handleSyncCommand({ command: interaction, appId });
|
|
128
|
+
return;
|
|
129
|
+
case 'fork':
|
|
130
|
+
await handleForkCommand(interaction);
|
|
131
|
+
return;
|
|
132
|
+
case 'model':
|
|
133
|
+
await handleModelCommand({ interaction, appId });
|
|
134
|
+
return;
|
|
135
|
+
case 'login':
|
|
136
|
+
await handleLoginCommand({ interaction, appId });
|
|
137
|
+
return;
|
|
138
|
+
case 'agent':
|
|
139
|
+
await handleAgentCommand({ interaction, appId });
|
|
140
|
+
return;
|
|
141
|
+
case 'queue':
|
|
142
|
+
await handleQueueCommand({ command: interaction, appId });
|
|
143
|
+
return;
|
|
144
|
+
case 'clear-queue':
|
|
145
|
+
await handleClearQueueCommand({ command: interaction, appId });
|
|
146
|
+
return;
|
|
147
|
+
case 'undo':
|
|
148
|
+
await handleUndoCommand({ command: interaction, appId });
|
|
149
|
+
return;
|
|
150
|
+
case 'redo':
|
|
151
|
+
await handleRedoCommand({ command: interaction, appId });
|
|
152
|
+
return;
|
|
153
|
+
case 'verbosity':
|
|
154
|
+
await handleVerbosityCommand({ command: interaction, appId });
|
|
155
|
+
return;
|
|
156
|
+
case 'theme':
|
|
157
|
+
await handleThemeCommand({ command: interaction, appId });
|
|
158
|
+
return;
|
|
159
|
+
case 'restart-opencode-server':
|
|
160
|
+
await handleRestartOpencodeServerCommand({
|
|
161
|
+
command: interaction,
|
|
162
|
+
appId,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
case 'run':
|
|
166
|
+
await handleRunCommand({ command: interaction, appId });
|
|
167
|
+
return;
|
|
168
|
+
case 'run-config':
|
|
169
|
+
await handleRunConfigCommand({ command: interaction, appId });
|
|
170
|
+
return;
|
|
171
|
+
case 'status':
|
|
172
|
+
await handleStatusCommand({ command: interaction, appId });
|
|
173
|
+
return;
|
|
174
|
+
case 'help':
|
|
175
|
+
await handleHelpCommand({ command: interaction, appId });
|
|
176
|
+
return;
|
|
177
|
+
case 'ping':
|
|
178
|
+
await handlePingCommand({ command: interaction, appId });
|
|
179
|
+
return;
|
|
180
|
+
case 'context':
|
|
181
|
+
await handleContextCommand({ command: interaction, appId });
|
|
182
|
+
return;
|
|
183
|
+
case 'cost':
|
|
184
|
+
await handleCostCommand({ command: interaction, appId });
|
|
185
|
+
return;
|
|
186
|
+
case 'diff':
|
|
187
|
+
await handleDiffCommand({ command: interaction, appId });
|
|
188
|
+
return;
|
|
189
|
+
case 'export':
|
|
190
|
+
await handleExportCommand({ command: interaction, appId });
|
|
191
|
+
return;
|
|
192
|
+
case 'files':
|
|
193
|
+
await handleFilesCommand({ command: interaction, appId });
|
|
194
|
+
return;
|
|
195
|
+
case 'settings':
|
|
196
|
+
await handleSettingsCommand({ command: interaction, appId });
|
|
197
|
+
return;
|
|
198
|
+
case 'schedule':
|
|
199
|
+
await handleScheduleCommand({ command: interaction, appId });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Handle quick agent commands (ending with -agent suffix, but not the base /agent command)
|
|
203
|
+
if (interaction.commandName.endsWith('-agent') &&
|
|
204
|
+
interaction.commandName !== 'agent') {
|
|
205
|
+
await handleQuickAgentCommand({ command: interaction, appId });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Handle user-defined commands (ending with -cmd suffix)
|
|
209
|
+
if (interaction.commandName.endsWith('-cmd')) {
|
|
210
|
+
await handleUserCommand({ command: interaction, appId });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (interaction.isStringSelectMenu()) {
|
|
216
|
+
const customId = interaction.customId;
|
|
217
|
+
if (customId.startsWith('fork_select:')) {
|
|
218
|
+
await handleForkSelectMenu(interaction);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (customId.startsWith('model_provider:')) {
|
|
222
|
+
await handleProviderSelectMenu(interaction);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (customId.startsWith('model_select:')) {
|
|
226
|
+
await handleModelSelectMenu(interaction);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (customId.startsWith('agent_select:')) {
|
|
230
|
+
await handleAgentSelectMenu(interaction);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (customId.startsWith('ask_question:')) {
|
|
234
|
+
await handleAskQuestionSelectMenu(interaction);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (customId.startsWith('permission:')) {
|
|
238
|
+
await handlePermissionSelectMenu(interaction);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (customId.startsWith('login_provider:')) {
|
|
242
|
+
await handleLoginProviderSelectMenu(interaction);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (customId.startsWith('login_method:')) {
|
|
246
|
+
await handleLoginMethodSelectMenu(interaction);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (interaction.isModalSubmit()) {
|
|
252
|
+
const customId = interaction.customId;
|
|
253
|
+
if (customId.startsWith('login_apikey:')) {
|
|
254
|
+
await handleApiKeyModalSubmit(interaction);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (interaction.isMessageContextMenuCommand()) {
|
|
260
|
+
interactionLogger.log(`[CONTEXT-MENU] Processing: ${interaction.commandName}`);
|
|
261
|
+
switch (interaction.commandName) {
|
|
262
|
+
case 'Retry this prompt':
|
|
263
|
+
await handleRetryContextMenu({ interaction, appId });
|
|
264
|
+
return;
|
|
265
|
+
case 'Fork from here':
|
|
266
|
+
await handleForkContextMenu({ interaction, appId });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
interactionLogger.error('[INTERACTION] Error handling interaction:', error);
|
|
274
|
+
try {
|
|
275
|
+
if (interaction.isRepliable() &&
|
|
276
|
+
!interaction.replied &&
|
|
277
|
+
!interaction.deferred) {
|
|
278
|
+
await interaction.reply({
|
|
279
|
+
content: 'An error occurred processing this command.',
|
|
280
|
+
ephemeral: true,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (replyError) {
|
|
285
|
+
interactionLogger.error('[INTERACTION] Failed to send error reply:', replyError);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Limit heading depth for Discord.
|
|
2
|
+
// Discord only supports headings up to ### (h3), so this converts
|
|
3
|
+
// ####, #####, etc. to ### to maintain consistent rendering.
|
|
4
|
+
import { Lexer } from 'marked';
|
|
5
|
+
export function limitHeadingDepth(markdown, maxDepth = 3) {
|
|
6
|
+
const lexer = new Lexer();
|
|
7
|
+
const tokens = lexer.lex(markdown);
|
|
8
|
+
let result = '';
|
|
9
|
+
for (const token of tokens) {
|
|
10
|
+
if (token.type === 'heading') {
|
|
11
|
+
const heading = token;
|
|
12
|
+
if (heading.depth > maxDepth) {
|
|
13
|
+
const hashes = '#'.repeat(maxDepth);
|
|
14
|
+
result += hashes + ' ' + heading.text + '\n';
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result += token.raw;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
result += token.raw;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { limitHeadingDepth } from './limit-heading-depth.js';
|
|
3
|
+
test('converts h4 to h3', () => {
|
|
4
|
+
const input = '#### Fourth level heading';
|
|
5
|
+
const result = limitHeadingDepth(input);
|
|
6
|
+
expect(result).toMatchInlineSnapshot(`
|
|
7
|
+
"### Fourth level heading
|
|
8
|
+
"
|
|
9
|
+
`);
|
|
10
|
+
});
|
|
11
|
+
test('converts h5 to h3', () => {
|
|
12
|
+
const input = '##### Fifth level heading';
|
|
13
|
+
const result = limitHeadingDepth(input);
|
|
14
|
+
expect(result).toMatchInlineSnapshot(`
|
|
15
|
+
"### Fifth level heading
|
|
16
|
+
"
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
test('converts h6 to h3', () => {
|
|
20
|
+
const input = '###### Sixth level heading';
|
|
21
|
+
const result = limitHeadingDepth(input);
|
|
22
|
+
expect(result).toMatchInlineSnapshot(`
|
|
23
|
+
"### Sixth level heading
|
|
24
|
+
"
|
|
25
|
+
`);
|
|
26
|
+
});
|
|
27
|
+
test('preserves h3 unchanged', () => {
|
|
28
|
+
const input = '### Third level heading';
|
|
29
|
+
const result = limitHeadingDepth(input);
|
|
30
|
+
expect(result).toMatchInlineSnapshot(`"### Third level heading"`);
|
|
31
|
+
});
|
|
32
|
+
test('preserves h2 unchanged', () => {
|
|
33
|
+
const input = '## Second level heading';
|
|
34
|
+
const result = limitHeadingDepth(input);
|
|
35
|
+
expect(result).toMatchInlineSnapshot(`"## Second level heading"`);
|
|
36
|
+
});
|
|
37
|
+
test('preserves h1 unchanged', () => {
|
|
38
|
+
const input = '# First level heading';
|
|
39
|
+
const result = limitHeadingDepth(input);
|
|
40
|
+
expect(result).toMatchInlineSnapshot(`"# First level heading"`);
|
|
41
|
+
});
|
|
42
|
+
test('handles multiple headings in document', () => {
|
|
43
|
+
const input = `# Title
|
|
44
|
+
|
|
45
|
+
Some text
|
|
46
|
+
|
|
47
|
+
## Section
|
|
48
|
+
|
|
49
|
+
### Subsection
|
|
50
|
+
|
|
51
|
+
#### Too deep
|
|
52
|
+
|
|
53
|
+
##### Even deeper
|
|
54
|
+
|
|
55
|
+
Regular paragraph
|
|
56
|
+
|
|
57
|
+
### Back to normal
|
|
58
|
+
`;
|
|
59
|
+
const result = limitHeadingDepth(input);
|
|
60
|
+
expect(result).toMatchInlineSnapshot(`
|
|
61
|
+
"# Title
|
|
62
|
+
|
|
63
|
+
Some text
|
|
64
|
+
|
|
65
|
+
## Section
|
|
66
|
+
|
|
67
|
+
### Subsection
|
|
68
|
+
|
|
69
|
+
### Too deep
|
|
70
|
+
### Even deeper
|
|
71
|
+
Regular paragraph
|
|
72
|
+
|
|
73
|
+
### Back to normal
|
|
74
|
+
"
|
|
75
|
+
`);
|
|
76
|
+
});
|
|
77
|
+
test('preserves heading with inline formatting', () => {
|
|
78
|
+
const input = '#### Heading with **bold** and `code`';
|
|
79
|
+
const result = limitHeadingDepth(input);
|
|
80
|
+
expect(result).toMatchInlineSnapshot(`
|
|
81
|
+
"### Heading with **bold** and \`code\`
|
|
82
|
+
"
|
|
83
|
+
`);
|
|
84
|
+
});
|
|
85
|
+
test('handles empty markdown', () => {
|
|
86
|
+
const result = limitHeadingDepth('');
|
|
87
|
+
expect(result).toMatchInlineSnapshot(`""`);
|
|
88
|
+
});
|
|
89
|
+
test('handles markdown with no headings', () => {
|
|
90
|
+
const input = 'Just some text\n\nAnd more text';
|
|
91
|
+
const result = limitHeadingDepth(input);
|
|
92
|
+
expect(result).toMatchInlineSnapshot(`
|
|
93
|
+
"Just some text
|
|
94
|
+
|
|
95
|
+
And more text"
|
|
96
|
+
`);
|
|
97
|
+
});
|
|
98
|
+
test('allows custom maxDepth', () => {
|
|
99
|
+
const input = '### Third level';
|
|
100
|
+
const result = limitHeadingDepth(input, 2);
|
|
101
|
+
expect(result).toMatchInlineSnapshot(`
|
|
102
|
+
"## Third level
|
|
103
|
+
"
|
|
104
|
+
`);
|
|
105
|
+
});
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Prefixed logging utility.
|
|
2
|
+
// Uses picocolors for compact frequent logs (log, info, debug).
|
|
3
|
+
// Uses @clack/prompts only for important events (warn, error) with visual distinction.
|
|
4
|
+
import { log as clackLog } from '@clack/prompts';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path, { dirname } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import util from 'node:util';
|
|
9
|
+
import pc from 'picocolors';
|
|
10
|
+
// All known log prefixes - add new ones here to keep alignment consistent
|
|
11
|
+
export const LogPrefix = {
|
|
12
|
+
ABORT: 'ABORT',
|
|
13
|
+
ADD_PROJECT: 'ADD_PROJ',
|
|
14
|
+
AGENT: 'AGENT',
|
|
15
|
+
ASK_QUESTION: 'QUESTION',
|
|
16
|
+
CLI: 'CLI',
|
|
17
|
+
COMPACT: 'COMPACT',
|
|
18
|
+
CREATE_PROJECT: 'NEW_PROJ',
|
|
19
|
+
DB: 'DB',
|
|
20
|
+
DISCORD: 'DISCORD',
|
|
21
|
+
FORK: 'FORK',
|
|
22
|
+
FORMATTING: 'FORMAT',
|
|
23
|
+
GENAI: 'GENAI',
|
|
24
|
+
GENAI_WORKER: 'GENAI_W',
|
|
25
|
+
INTERACTION: 'INTERACT',
|
|
26
|
+
LOGIN: 'LOGIN',
|
|
27
|
+
MARKDOWN: 'MARKDOWN',
|
|
28
|
+
MODEL: 'MODEL',
|
|
29
|
+
OPENAI: 'OPENAI',
|
|
30
|
+
OPENCODE: 'OPENCODE',
|
|
31
|
+
PERMISSIONS: 'PERMS',
|
|
32
|
+
QUEUE: 'QUEUE',
|
|
33
|
+
REACTION: 'REACTION',
|
|
34
|
+
REMOVE_PROJECT: 'RM_PROJ',
|
|
35
|
+
SCHEDULE: 'SCHEDULE',
|
|
36
|
+
RESUME: 'RESUME',
|
|
37
|
+
SESSION: 'SESSION',
|
|
38
|
+
SETTINGS: 'SETTINGS',
|
|
39
|
+
SHARE: 'SHARE',
|
|
40
|
+
THEME: 'THEME',
|
|
41
|
+
TOOLS: 'TOOLS',
|
|
42
|
+
UNDO_REDO: 'UNDO',
|
|
43
|
+
USER_CMD: 'USER_CMD',
|
|
44
|
+
VERBOSITY: 'VERBOSE',
|
|
45
|
+
VOICE: 'VOICE',
|
|
46
|
+
WORKER: 'WORKER',
|
|
47
|
+
WORKTREE: 'WORKTREE',
|
|
48
|
+
XML: 'XML',
|
|
49
|
+
};
|
|
50
|
+
// compute max length from all known prefixes for alignment
|
|
51
|
+
const MAX_PREFIX_LENGTH = Math.max(...Object.values(LogPrefix).map((p) => p.length));
|
|
52
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
53
|
+
const __dirname = dirname(__filename);
|
|
54
|
+
const isDev = !__dirname.includes('node_modules');
|
|
55
|
+
const logFilePath = path.join(__dirname, '..', 'tmp', 'disunday.log');
|
|
56
|
+
// reset log file on startup in dev mode
|
|
57
|
+
if (isDev) {
|
|
58
|
+
const logDir = path.dirname(logFilePath);
|
|
59
|
+
if (!fs.existsSync(logDir)) {
|
|
60
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(logFilePath, `--- disunday log started at ${new Date().toISOString()} ---\n`);
|
|
63
|
+
}
|
|
64
|
+
function formatArg(arg) {
|
|
65
|
+
if (typeof arg === 'string') {
|
|
66
|
+
return arg;
|
|
67
|
+
}
|
|
68
|
+
return util.inspect(arg, { colors: true, depth: 4 });
|
|
69
|
+
}
|
|
70
|
+
function writeToFile(level, prefix, args) {
|
|
71
|
+
if (!isDev) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const timestamp = new Date().toISOString();
|
|
75
|
+
const message = `[${timestamp}] [${level}] [${prefix}] ${args.map(formatArg).join(' ')}\n`;
|
|
76
|
+
fs.appendFileSync(logFilePath, message);
|
|
77
|
+
}
|
|
78
|
+
function getTimestamp() {
|
|
79
|
+
const now = new Date();
|
|
80
|
+
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
|
|
81
|
+
}
|
|
82
|
+
function padPrefix(prefix) {
|
|
83
|
+
return prefix.padEnd(MAX_PREFIX_LENGTH);
|
|
84
|
+
}
|
|
85
|
+
export function createLogger(prefix) {
|
|
86
|
+
const paddedPrefix = padPrefix(prefix);
|
|
87
|
+
return {
|
|
88
|
+
log: (...args) => {
|
|
89
|
+
writeToFile('INFO', prefix, args);
|
|
90
|
+
console.log(pc.dim(getTimestamp()), pc.cyan(paddedPrefix), ...args.map(formatArg));
|
|
91
|
+
},
|
|
92
|
+
error: (...args) => {
|
|
93
|
+
writeToFile('ERROR', prefix, args);
|
|
94
|
+
// use clack for errors - visually distinct
|
|
95
|
+
clackLog.error([paddedPrefix, ...args.map(formatArg)].join(' '));
|
|
96
|
+
},
|
|
97
|
+
warn: (...args) => {
|
|
98
|
+
writeToFile('WARN', prefix, args);
|
|
99
|
+
// use clack for warnings - visually distinct
|
|
100
|
+
clackLog.warn([paddedPrefix, ...args.map(formatArg)].join(' '));
|
|
101
|
+
},
|
|
102
|
+
info: (...args) => {
|
|
103
|
+
writeToFile('INFO', prefix, args);
|
|
104
|
+
console.log(pc.dim(getTimestamp()), pc.blue(paddedPrefix), ...args.map(formatArg));
|
|
105
|
+
},
|
|
106
|
+
debug: (...args) => {
|
|
107
|
+
writeToFile('DEBUG', prefix, args);
|
|
108
|
+
console.log(pc.dim(getTimestamp()), pc.dim(paddedPrefix), ...args.map(formatArg));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|