kimaki 0.4.23 → 0.4.25
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/LICENSE +21 -0
- package/bin.js +6 -1
- package/dist/ai-tool-to-genai.js +3 -0
- package/dist/channel-management.js +3 -0
- package/dist/cli.js +93 -14
- package/dist/commands/abort.js +78 -0
- package/dist/commands/add-project.js +97 -0
- package/dist/commands/create-new-project.js +78 -0
- package/dist/commands/fork.js +186 -0
- package/dist/commands/model.js +294 -0
- package/dist/commands/permissions.js +126 -0
- package/dist/commands/queue.js +129 -0
- package/dist/commands/resume.js +145 -0
- package/dist/commands/session.js +144 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +161 -0
- package/dist/database.js +3 -0
- package/dist/discord-bot.js +3 -0
- package/dist/discord-utils.js +10 -1
- package/dist/format-tables.js +3 -0
- package/dist/genai-worker-wrapper.js +3 -0
- package/dist/genai-worker.js +3 -0
- package/dist/genai.js +3 -0
- package/dist/interaction-handler.js +71 -697
- package/dist/logger.js +3 -0
- package/dist/markdown.js +3 -0
- package/dist/message-formatting.js +41 -6
- package/dist/opencode.js +3 -0
- package/dist/session-handler.js +47 -3
- package/dist/system-message.js +16 -0
- package/dist/tools.js +3 -0
- package/dist/utils.js +3 -0
- package/dist/voice-handler.js +3 -0
- package/dist/voice.js +3 -0
- package/dist/worker-types.js +3 -0
- package/dist/xml.js +3 -0
- package/package.json +11 -12
- package/src/ai-tool-to-genai.ts +4 -0
- package/src/channel-management.ts +4 -0
- package/src/cli.ts +93 -14
- package/src/commands/abort.ts +94 -0
- package/src/commands/add-project.ts +138 -0
- package/src/commands/create-new-project.ts +111 -0
- package/src/{fork.ts → commands/fork.ts} +39 -5
- package/src/{model-command.ts → commands/model.ts} +7 -5
- package/src/commands/permissions.ts +146 -0
- package/src/commands/queue.ts +181 -0
- package/src/commands/resume.ts +230 -0
- package/src/commands/session.ts +186 -0
- package/src/commands/share.ts +96 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +213 -0
- package/src/database.ts +4 -0
- package/src/discord-bot.ts +4 -0
- package/src/discord-utils.ts +12 -0
- package/src/format-tables.ts +4 -0
- package/src/genai-worker-wrapper.ts +4 -0
- package/src/genai-worker.ts +4 -0
- package/src/genai.ts +4 -0
- package/src/interaction-handler.ts +81 -919
- package/src/logger.ts +4 -0
- package/src/markdown.ts +4 -0
- package/src/message-formatting.ts +52 -7
- package/src/opencode.ts +4 -0
- package/src/session-handler.ts +70 -3
- package/src/system-message.ts +17 -0
- package/src/tools.ts +4 -0
- package/src/utils.ts +4 -0
- package/src/voice-handler.ts +4 -0
- package/src/voice.ts +4 -0
- package/src/worker-types.ts +4 -0
- package/src/xml.ts +4 -0
- package/README.md +0 -48
package/src/logger.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Prefixed logging utility using @clack/prompts.
|
|
2
|
+
// Creates loggers with consistent prefixes for different subsystems
|
|
3
|
+
// (DISCORD, VOICE, SESSION, etc.) for easier debugging.
|
|
4
|
+
|
|
1
5
|
import { log } from '@clack/prompts'
|
|
2
6
|
|
|
3
7
|
export function createLogger(prefix: string) {
|
package/src/markdown.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Session-to-markdown renderer for sharing.
|
|
2
|
+
// Generates shareable markdown from OpenCode sessions, formatting
|
|
3
|
+
// user messages, assistant responses, tool calls, and reasoning blocks.
|
|
4
|
+
|
|
1
5
|
import type { OpencodeClient } from '@opencode-ai/sdk'
|
|
2
6
|
import * as yaml from 'js-yaml'
|
|
3
7
|
import { formatDateTime } from './utils.js'
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
// OpenCode message part formatting for Discord.
|
|
2
|
+
// Converts SDK message parts (text, tools, reasoning) to Discord-friendly format,
|
|
3
|
+
// handles file attachments, and provides tool summary generation.
|
|
4
|
+
|
|
5
|
+
import type { Part, FilePartInput, SessionMessagesResponse } from '@opencode-ai/sdk'
|
|
2
6
|
import type { Message } from 'discord.js'
|
|
3
7
|
import { createLogger } from './logger.js'
|
|
4
8
|
|
|
5
9
|
const logger = createLogger('FORMATTING')
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Collects and formats the last N assistant parts from session messages.
|
|
13
|
+
* Used by both /resume and /fork to show recent assistant context.
|
|
14
|
+
*/
|
|
15
|
+
export function collectLastAssistantParts({
|
|
16
|
+
messages,
|
|
17
|
+
limit = 30,
|
|
18
|
+
}: {
|
|
19
|
+
messages: SessionMessagesResponse
|
|
20
|
+
limit?: number
|
|
21
|
+
}): { partIds: string[]; content: string; skippedCount: number } {
|
|
22
|
+
const allAssistantParts: { id: string; content: string }[] = []
|
|
23
|
+
|
|
24
|
+
for (const message of messages) {
|
|
25
|
+
if (message.info.role === 'assistant') {
|
|
26
|
+
for (const part of message.parts) {
|
|
27
|
+
const content = formatPart(part)
|
|
28
|
+
if (content.trim()) {
|
|
29
|
+
allAssistantParts.push({ id: part.id, content: content.trimEnd() })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const partsToRender = allAssistantParts.slice(-limit)
|
|
36
|
+
const partIds = partsToRender.map((p) => p.id)
|
|
37
|
+
const content = partsToRender.map((p) => p.content).join('\n')
|
|
38
|
+
const skippedCount = allAssistantParts.length - partsToRender.length
|
|
39
|
+
|
|
40
|
+
return { partIds, content, skippedCount }
|
|
41
|
+
}
|
|
42
|
+
|
|
7
43
|
export const TEXT_MIME_TYPES = [
|
|
8
44
|
'text/',
|
|
9
45
|
'application/json',
|
|
@@ -136,7 +172,7 @@ export function getToolSummaryText(part: Part): string {
|
|
|
136
172
|
.map(([key, value]) => {
|
|
137
173
|
if (value === null || value === undefined) return null
|
|
138
174
|
const stringValue = typeof value === 'string' ? value : JSON.stringify(value)
|
|
139
|
-
const truncatedValue = stringValue.length >
|
|
175
|
+
const truncatedValue = stringValue.length > 50 ? stringValue.slice(0, 50) + '…' : stringValue
|
|
140
176
|
return `${key}: ${truncatedValue}`
|
|
141
177
|
})
|
|
142
178
|
.filter(Boolean)
|
|
@@ -163,12 +199,13 @@ export function formatTodoList(part: Part): string {
|
|
|
163
199
|
|
|
164
200
|
export function formatPart(part: Part): string {
|
|
165
201
|
if (part.type === 'text') {
|
|
166
|
-
|
|
202
|
+
if (!part.text?.trim()) return ''
|
|
203
|
+
return `⬥ ${part.text}`
|
|
167
204
|
}
|
|
168
205
|
|
|
169
206
|
if (part.type === 'reasoning') {
|
|
170
207
|
if (!part.text?.trim()) return ''
|
|
171
|
-
return
|
|
208
|
+
return `┣ thinking`
|
|
172
209
|
}
|
|
173
210
|
|
|
174
211
|
if (part.type === 'file') {
|
|
@@ -180,11 +217,11 @@ export function formatPart(part: Part): string {
|
|
|
180
217
|
}
|
|
181
218
|
|
|
182
219
|
if (part.type === 'agent') {
|
|
183
|
-
return
|
|
220
|
+
return `┣ agent ${part.id}`
|
|
184
221
|
}
|
|
185
222
|
|
|
186
223
|
if (part.type === 'snapshot') {
|
|
187
|
-
return
|
|
224
|
+
return `┣ snapshot ${part.snapshot}`
|
|
188
225
|
}
|
|
189
226
|
|
|
190
227
|
if (part.type === 'tool') {
|
|
@@ -218,7 +255,15 @@ export function formatPart(part: Part): string {
|
|
|
218
255
|
toolTitle = `_${stateTitle}_`
|
|
219
256
|
}
|
|
220
257
|
|
|
221
|
-
const icon =
|
|
258
|
+
const icon = (() => {
|
|
259
|
+
if (part.state.status === 'error') {
|
|
260
|
+
return '⨯'
|
|
261
|
+
}
|
|
262
|
+
if (part.tool === 'edit' || part.tool === 'write') {
|
|
263
|
+
return '◼︎'
|
|
264
|
+
}
|
|
265
|
+
return '┣'
|
|
266
|
+
})()
|
|
222
267
|
return `${icon} ${part.tool} ${toolTitle} ${summaryText}`
|
|
223
268
|
}
|
|
224
269
|
|
package/src/opencode.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// OpenCode server process manager.
|
|
2
|
+
// Spawns and maintains OpenCode API servers per project directory,
|
|
3
|
+
// handles automatic restarts on failure, and provides typed SDK clients.
|
|
4
|
+
|
|
1
5
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
2
6
|
import net from 'node:net'
|
|
3
7
|
import {
|
package/src/session-handler.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
// OpenCode session lifecycle manager.
|
|
2
|
+
// Creates, maintains, and sends prompts to OpenCode sessions from Discord threads.
|
|
3
|
+
// Handles streaming events, permissions, abort signals, and message queuing.
|
|
4
|
+
|
|
1
5
|
import type { Part, FilePartInput, Permission } from '@opencode-ai/sdk'
|
|
2
6
|
import type { Message, ThreadChannel } from 'discord.js'
|
|
3
7
|
import prettyMilliseconds from 'pretty-ms'
|
|
4
8
|
import { getDatabase, getSessionModel, getChannelModel } from './database.js'
|
|
5
9
|
import { initializeOpencodeForDirectory, getOpencodeServers } from './opencode.js'
|
|
6
|
-
import { sendThreadMessage } from './discord-utils.js'
|
|
10
|
+
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS } from './discord-utils.js'
|
|
7
11
|
import { formatPart } from './message-formatting.js'
|
|
8
12
|
import { getOpencodeSystemMessage } from './system-message.js'
|
|
9
13
|
import { createLogger } from './logger.js'
|
|
@@ -42,6 +46,39 @@ export const pendingPermissions = new Map<
|
|
|
42
46
|
{ permission: Permission; messageId: string; directory: string }
|
|
43
47
|
>()
|
|
44
48
|
|
|
49
|
+
export type QueuedMessage = {
|
|
50
|
+
prompt: string
|
|
51
|
+
userId: string
|
|
52
|
+
username: string
|
|
53
|
+
queuedAt: number
|
|
54
|
+
images?: FilePartInput[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Queue of messages waiting to be sent after current response finishes
|
|
58
|
+
// Key is threadId, value is array of queued messages
|
|
59
|
+
export const messageQueue = new Map<string, QueuedMessage[]>()
|
|
60
|
+
|
|
61
|
+
export function addToQueue({
|
|
62
|
+
threadId,
|
|
63
|
+
message,
|
|
64
|
+
}: {
|
|
65
|
+
threadId: string
|
|
66
|
+
message: QueuedMessage
|
|
67
|
+
}): number {
|
|
68
|
+
const queue = messageQueue.get(threadId) || []
|
|
69
|
+
queue.push(message)
|
|
70
|
+
messageQueue.set(threadId, queue)
|
|
71
|
+
return queue.length
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getQueueLength(threadId: string): number {
|
|
75
|
+
return messageQueue.get(threadId)?.length || 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function clearQueue(threadId: string): void {
|
|
79
|
+
messageQueue.delete(threadId)
|
|
80
|
+
}
|
|
81
|
+
|
|
45
82
|
export async function handleOpencodeSession({
|
|
46
83
|
prompt,
|
|
47
84
|
thread,
|
|
@@ -296,7 +333,7 @@ export async function handleOpencodeSession({
|
|
|
296
333
|
const thresholdCrossed = Math.floor(currentPercentage / 10) * 10
|
|
297
334
|
if (thresholdCrossed > lastDisplayedContextPercentage && thresholdCrossed >= 10) {
|
|
298
335
|
lastDisplayedContextPercentage = thresholdCrossed
|
|
299
|
-
await sendThreadMessage(thread,
|
|
336
|
+
await sendThreadMessage(thread, `⬥ context usage ${currentPercentage}%`)
|
|
300
337
|
}
|
|
301
338
|
}
|
|
302
339
|
}
|
|
@@ -467,8 +504,38 @@ export async function handleOpencodeSession({
|
|
|
467
504
|
sessionLogger.error('Failed to fetch provider info for context percentage:', e)
|
|
468
505
|
}
|
|
469
506
|
|
|
470
|
-
await sendThreadMessage(thread, `_Completed in ${sessionDuration}${contextInfo}_${attachCommand}${modelInfo}
|
|
507
|
+
await sendThreadMessage(thread, `_Completed in ${sessionDuration}${contextInfo}_${attachCommand}${modelInfo}`, { flags: NOTIFY_MESSAGE_FLAGS })
|
|
471
508
|
sessionLogger.log(`DURATION: Session completed in ${sessionDuration}, port ${port}, model ${usedModel}, tokens ${tokensUsedInSession}`)
|
|
509
|
+
|
|
510
|
+
// Process queued messages after completion
|
|
511
|
+
const queue = messageQueue.get(thread.id)
|
|
512
|
+
if (queue && queue.length > 0) {
|
|
513
|
+
const nextMessage = queue.shift()!
|
|
514
|
+
if (queue.length === 0) {
|
|
515
|
+
messageQueue.delete(thread.id)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
sessionLogger.log(`[QUEUE] Processing queued message from ${nextMessage.username}`)
|
|
519
|
+
|
|
520
|
+
// Show that queued message is being sent
|
|
521
|
+
await sendThreadMessage(thread, `» **${nextMessage.username}:** ${nextMessage.prompt.slice(0, 150)}${nextMessage.prompt.length > 150 ? '...' : ''}`)
|
|
522
|
+
|
|
523
|
+
// Send the queued message as a new prompt (recursive call)
|
|
524
|
+
// Use setImmediate to avoid blocking and allow this finally to complete
|
|
525
|
+
setImmediate(() => {
|
|
526
|
+
handleOpencodeSession({
|
|
527
|
+
prompt: nextMessage.prompt,
|
|
528
|
+
thread,
|
|
529
|
+
projectDirectory,
|
|
530
|
+
images: nextMessage.images,
|
|
531
|
+
channelId,
|
|
532
|
+
}).catch(async (e) => {
|
|
533
|
+
sessionLogger.error(`[QUEUE] Failed to process queued message:`, e)
|
|
534
|
+
const errorMsg = e instanceof Error ? e.message : String(e)
|
|
535
|
+
await sendThreadMessage(thread, `✗ Queued message failed: ${errorMsg.slice(0, 200)}`)
|
|
536
|
+
})
|
|
537
|
+
})
|
|
538
|
+
}
|
|
472
539
|
} else {
|
|
473
540
|
sessionLogger.log(
|
|
474
541
|
`Session was aborted (reason: ${abortController.signal.reason}), skipping duration message`,
|
package/src/system-message.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// OpenCode system prompt generator.
|
|
2
|
+
// Creates the system message injected into every OpenCode session,
|
|
3
|
+
// including Discord-specific formatting rules, diff commands, and permissions info.
|
|
4
|
+
|
|
1
5
|
export function getOpencodeSystemMessage({ sessionId }: { sessionId: string }) {
|
|
2
6
|
return `
|
|
3
7
|
The user is reading your messages from inside Discord, via kimaki.xyz
|
|
@@ -66,6 +70,19 @@ the max heading level is 3, so do not use ####
|
|
|
66
70
|
|
|
67
71
|
headings are discouraged anyway. instead try to use bold text for titles which renders more nicely in Discord
|
|
68
72
|
|
|
73
|
+
## capitalization
|
|
74
|
+
|
|
75
|
+
write casually like a discord user. never capitalize the initials of phrases or acronyms in your messages. use all lowercase instead.
|
|
76
|
+
|
|
77
|
+
examples:
|
|
78
|
+
- write "api" not "API"
|
|
79
|
+
- write "url" not "URL"
|
|
80
|
+
- write "json" not "JSON"
|
|
81
|
+
- write "cli" not "CLI"
|
|
82
|
+
- write "sdk" not "SDK"
|
|
83
|
+
|
|
84
|
+
this makes your messages blend in naturally with how people actually type on discord.
|
|
85
|
+
|
|
69
86
|
## tables
|
|
70
87
|
|
|
71
88
|
discord does NOT support markdown gfm tables.
|
package/src/tools.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Voice assistant tool definitions for the GenAI worker.
|
|
2
|
+
// Provides tools for managing OpenCode sessions (create, submit, abort),
|
|
3
|
+
// listing chats, searching files, and reading session messages.
|
|
4
|
+
|
|
1
5
|
import { tool } from 'ai'
|
|
2
6
|
import { z } from 'zod'
|
|
3
7
|
import { spawn, type ChildProcess } from 'node:child_process'
|
package/src/utils.ts
CHANGED
package/src/voice-handler.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Discord voice channel connection and audio stream handler.
|
|
2
|
+
// Manages joining/leaving voice channels, captures user audio, resamples to 16kHz,
|
|
3
|
+
// and routes audio to the GenAI worker for real-time voice assistant interactions.
|
|
4
|
+
|
|
1
5
|
import {
|
|
2
6
|
VoiceConnectionStatus,
|
|
3
7
|
EndBehaviorType,
|
package/src/voice.ts
CHANGED
package/src/worker-types.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Type definitions for worker thread message passing.
|
|
2
|
+
// Defines the protocol between main thread and GenAI worker for
|
|
3
|
+
// audio streaming, tool calls, and session lifecycle management.
|
|
4
|
+
|
|
1
5
|
import type { Tool as AITool } from 'ai'
|
|
2
6
|
|
|
3
7
|
// Messages sent from main thread to worker
|
package/src/xml.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// XML/HTML tag content extractor.
|
|
2
|
+
// Parses XML-like tags from strings (e.g., channel topics) to extract
|
|
3
|
+
// Kimaki configuration like directory paths and app IDs.
|
|
4
|
+
|
|
1
5
|
import { DomHandler, Parser, ElementType } from 'htmlparser2'
|
|
2
6
|
import type { ChildNode, Element, Text } from 'domhandler'
|
|
3
7
|
import { createLogger } from './logger.js'
|
package/README.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Kimaki Discord Bot
|
|
2
|
-
|
|
3
|
-
A Discord bot that integrates OpenCode coding sessions with Discord channels and voice.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g kimaki
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
12
|
-
|
|
13
|
-
Run the interactive setup:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
kimaki
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
This will guide you through:
|
|
20
|
-
1. Creating a Discord application at https://discord.com/developers/applications
|
|
21
|
-
2. Getting your bot token
|
|
22
|
-
3. Installing the bot to your Discord server
|
|
23
|
-
4. Creating channels for your OpenCode projects
|
|
24
|
-
|
|
25
|
-
## Commands
|
|
26
|
-
|
|
27
|
-
### Start the bot
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
kimaki
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Discord Slash Commands
|
|
34
|
-
|
|
35
|
-
Once the bot is running, you can use these commands in Discord:
|
|
36
|
-
|
|
37
|
-
- `/session <prompt>` - Start a new OpenCode session
|
|
38
|
-
- `/resume <session>` - Resume an existing session
|
|
39
|
-
- `/add-project <project>` - Add a new project to Discord
|
|
40
|
-
- `/accept` - Accept a permission request
|
|
41
|
-
- `/accept-always` - Accept and auto-approve similar requests
|
|
42
|
-
- `/reject` - Reject a permission request
|
|
43
|
-
|
|
44
|
-
## Voice Support
|
|
45
|
-
|
|
46
|
-
Join a voice channel that has an associated project directory, and the bot will join with Jarvis-like voice interaction powered by Gemini.
|
|
47
|
-
|
|
48
|
-
Requires a Gemini API key (prompted during setup).
|