kimaki 0.1.4 → 0.2.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/cli.js +63 -12
- package/dist/discordBot.js +341 -42
- package/dist/genai-worker-wrapper.js +3 -0
- package/dist/genai-worker.js +5 -0
- package/dist/tools.js +46 -5
- package/package.json +1 -1
- package/src/cli.ts +80 -10
- package/src/discordBot.ts +453 -45
- package/src/genai-worker-wrapper.ts +4 -0
- package/src/genai-worker.ts +5 -0
- package/src/tools.ts +59 -4
- package/src/worker-types.ts +3 -0
package/dist/tools.js
CHANGED
|
@@ -9,12 +9,14 @@ import { formatDistanceToNow } from 'date-fns';
|
|
|
9
9
|
import { ShareMarkdown } from './markdown.js';
|
|
10
10
|
import pc from 'picocolors';
|
|
11
11
|
import { initializeOpencodeForDirectory } from './discordBot.js';
|
|
12
|
-
export async function getTools({ onMessageCompleted, directory, }) {
|
|
12
|
+
export async function getTools({ onMessageCompleted, onAllSessionsCompleted, directory, }) {
|
|
13
13
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
14
14
|
const client = getClient();
|
|
15
15
|
const markdownRenderer = new ShareMarkdown(client);
|
|
16
16
|
const providersResponse = await client.config.providers({});
|
|
17
17
|
const providers = providersResponse.data?.providers || [];
|
|
18
|
+
// Track all active OpenCode sessions
|
|
19
|
+
const activeSessions = new Set();
|
|
18
20
|
// Helper: get last assistant model for a session (non-summary)
|
|
19
21
|
const getSessionModel = async (sessionId) => {
|
|
20
22
|
const res = await getClient().session.messages({ path: { id: sessionId } });
|
|
@@ -41,6 +43,9 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
41
43
|
}),
|
|
42
44
|
execute: async ({ sessionId, message }) => {
|
|
43
45
|
const sessionModel = await getSessionModel(sessionId);
|
|
46
|
+
// Track this session as active
|
|
47
|
+
activeSessions.add(sessionId);
|
|
48
|
+
toolsLogger.log(`Session ${sessionId} started, ${activeSessions.size} active sessions`);
|
|
44
49
|
// do not await
|
|
45
50
|
getClient()
|
|
46
51
|
.session.prompt({
|
|
@@ -61,6 +66,14 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
61
66
|
data: response.data,
|
|
62
67
|
markdown,
|
|
63
68
|
});
|
|
69
|
+
// Remove from active sessions
|
|
70
|
+
activeSessions.delete(sessionId);
|
|
71
|
+
toolsLogger.log(`Session ${sessionId} completed, ${activeSessions.size} active sessions remaining`);
|
|
72
|
+
// Check if all sessions are complete
|
|
73
|
+
if (activeSessions.size === 0) {
|
|
74
|
+
toolsLogger.log('All sessions completed');
|
|
75
|
+
onAllSessionsCompleted?.();
|
|
76
|
+
}
|
|
64
77
|
})
|
|
65
78
|
.catch((error) => {
|
|
66
79
|
onMessageCompleted?.({
|
|
@@ -68,6 +81,14 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
68
81
|
messageId: '',
|
|
69
82
|
error,
|
|
70
83
|
});
|
|
84
|
+
// Remove from active sessions even on error
|
|
85
|
+
activeSessions.delete(sessionId);
|
|
86
|
+
toolsLogger.log(`Session ${sessionId} failed, ${activeSessions.size} active sessions remaining`);
|
|
87
|
+
// Check if all sessions are complete
|
|
88
|
+
if (activeSessions.size === 0) {
|
|
89
|
+
toolsLogger.log('All sessions completed');
|
|
90
|
+
onAllSessionsCompleted?.();
|
|
91
|
+
}
|
|
71
92
|
});
|
|
72
93
|
return {
|
|
73
94
|
success: true,
|
|
@@ -108,32 +129,52 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
108
129
|
if (!session.data) {
|
|
109
130
|
throw new Error('Failed to create session');
|
|
110
131
|
}
|
|
132
|
+
const newSessionId = session.data.id;
|
|
133
|
+
// Track this session as active
|
|
134
|
+
activeSessions.add(newSessionId);
|
|
135
|
+
toolsLogger.log(`New session ${newSessionId} created, ${activeSessions.size} active sessions`);
|
|
111
136
|
// do not await
|
|
112
137
|
getClient()
|
|
113
138
|
.session.prompt({
|
|
114
|
-
path: { id:
|
|
139
|
+
path: { id: newSessionId },
|
|
115
140
|
body: {
|
|
116
141
|
parts: [{ type: 'text', text: message }],
|
|
117
142
|
},
|
|
118
143
|
})
|
|
119
144
|
.then(async (response) => {
|
|
120
145
|
const markdown = await markdownRenderer.generate({
|
|
121
|
-
sessionID:
|
|
146
|
+
sessionID: newSessionId,
|
|
122
147
|
lastAssistantOnly: true,
|
|
123
148
|
});
|
|
124
149
|
onMessageCompleted?.({
|
|
125
|
-
sessionId:
|
|
150
|
+
sessionId: newSessionId,
|
|
126
151
|
messageId: '',
|
|
127
152
|
data: response.data,
|
|
128
153
|
markdown,
|
|
129
154
|
});
|
|
155
|
+
// Remove from active sessions
|
|
156
|
+
activeSessions.delete(newSessionId);
|
|
157
|
+
toolsLogger.log(`Session ${newSessionId} completed, ${activeSessions.size} active sessions remaining`);
|
|
158
|
+
// Check if all sessions are complete
|
|
159
|
+
if (activeSessions.size === 0) {
|
|
160
|
+
toolsLogger.log('All sessions completed');
|
|
161
|
+
onAllSessionsCompleted?.();
|
|
162
|
+
}
|
|
130
163
|
})
|
|
131
164
|
.catch((error) => {
|
|
132
165
|
onMessageCompleted?.({
|
|
133
|
-
sessionId:
|
|
166
|
+
sessionId: newSessionId,
|
|
134
167
|
messageId: '',
|
|
135
168
|
error,
|
|
136
169
|
});
|
|
170
|
+
// Remove from active sessions even on error
|
|
171
|
+
activeSessions.delete(newSessionId);
|
|
172
|
+
toolsLogger.log(`Session ${newSessionId} failed, ${activeSessions.size} active sessions remaining`);
|
|
173
|
+
// Check if all sessions are complete
|
|
174
|
+
if (activeSessions.size === 0) {
|
|
175
|
+
toolsLogger.log('All sessions completed');
|
|
176
|
+
onAllSessionsCompleted?.();
|
|
177
|
+
}
|
|
137
178
|
});
|
|
138
179
|
return {
|
|
139
180
|
success: true,
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import path from 'node:path'
|
|
36
36
|
import fs from 'node:fs'
|
|
37
37
|
import { createLogger } from './logger.js'
|
|
38
|
+
import { spawnSync, execSync, type ExecSyncOptions } from 'node:child_process'
|
|
38
39
|
|
|
39
40
|
const cliLogger = createLogger('CLI')
|
|
40
41
|
const cli = cac('kimaki')
|
|
@@ -142,6 +143,73 @@ async function run({ restart, addChannels }: CliOptions) {
|
|
|
142
143
|
|
|
143
144
|
intro('🤖 Discord Bot Setup')
|
|
144
145
|
|
|
146
|
+
// Step 0: Check if OpenCode CLI is available
|
|
147
|
+
const opencodeCheck = spawnSync('which', ['opencode'], { shell: true })
|
|
148
|
+
|
|
149
|
+
if (opencodeCheck.status !== 0) {
|
|
150
|
+
note(
|
|
151
|
+
'OpenCode CLI is required but not found in your PATH.',
|
|
152
|
+
'⚠️ OpenCode Not Found',
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const shouldInstall = await confirm({
|
|
156
|
+
message: 'Would you like to install OpenCode right now?',
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (isCancel(shouldInstall) || !shouldInstall) {
|
|
160
|
+
cancel('OpenCode CLI is required to run this bot')
|
|
161
|
+
process.exit(0)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const s = spinner()
|
|
165
|
+
s.start('Installing OpenCode CLI...')
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
execSync('curl -fsSL https://opencode.ai/install | bash', {
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
shell: '/bin/bash',
|
|
171
|
+
})
|
|
172
|
+
s.stop('OpenCode CLI installed successfully!')
|
|
173
|
+
|
|
174
|
+
// The install script adds opencode to PATH via shell configuration
|
|
175
|
+
// For the current process, we need to check common installation paths
|
|
176
|
+
const possiblePaths = [
|
|
177
|
+
`${process.env.HOME}/.local/bin/opencode`,
|
|
178
|
+
`${process.env.HOME}/.opencode/bin/opencode`,
|
|
179
|
+
'/usr/local/bin/opencode',
|
|
180
|
+
'/opt/opencode/bin/opencode',
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
const installedPath = possiblePaths.find((p) => {
|
|
184
|
+
try {
|
|
185
|
+
fs.accessSync(p, fs.constants.F_OK)
|
|
186
|
+
return true
|
|
187
|
+
} catch {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
if (!installedPath) {
|
|
193
|
+
note(
|
|
194
|
+
'OpenCode was installed but may not be available in this session.\n' +
|
|
195
|
+
'Please restart your terminal and run this command again.',
|
|
196
|
+
'⚠️ Restart Required',
|
|
197
|
+
)
|
|
198
|
+
process.exit(0)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// For subsequent spawn calls in this session, we can use the full path
|
|
202
|
+
process.env.OPENCODE_PATH = installedPath
|
|
203
|
+
} catch (error) {
|
|
204
|
+
s.stop('Failed to install OpenCode CLI')
|
|
205
|
+
cliLogger.error(
|
|
206
|
+
'Installation error:',
|
|
207
|
+
error instanceof Error ? error.message : String(error),
|
|
208
|
+
)
|
|
209
|
+
process.exit(EXIT_NO_RESTART)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
145
213
|
const db = getDatabase()
|
|
146
214
|
let appId: string
|
|
147
215
|
let token: string
|
|
@@ -377,7 +445,7 @@ async function run({ restart, addChannels }: CliOptions) {
|
|
|
377
445
|
let projects: Project[] = []
|
|
378
446
|
|
|
379
447
|
try {
|
|
380
|
-
const projectsResponse = await getClient().project.list()
|
|
448
|
+
const projectsResponse = await getClient().project.list({})
|
|
381
449
|
if (!projectsResponse.data) {
|
|
382
450
|
throw new Error('Failed to fetch projects')
|
|
383
451
|
}
|
|
@@ -436,22 +504,24 @@ async function run({ restart, addChannels }: CliOptions) {
|
|
|
436
504
|
|
|
437
505
|
if (guilds.length === 1) {
|
|
438
506
|
targetGuild = guilds[0]!
|
|
507
|
+
note(`Using server: ${targetGuild.name}`, 'Server Selected')
|
|
439
508
|
} else {
|
|
440
|
-
const
|
|
441
|
-
message: '
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
509
|
+
const guildSelection = await multiselect({
|
|
510
|
+
message: 'Select a Discord server to create channels in:',
|
|
511
|
+
options: guilds.map((guild) => ({
|
|
512
|
+
value: guild.id,
|
|
513
|
+
label: `${guild.name} (${guild.memberCount} members)`,
|
|
514
|
+
})),
|
|
515
|
+
required: true,
|
|
516
|
+
maxItems: 1,
|
|
447
517
|
})
|
|
448
518
|
|
|
449
|
-
if (isCancel(
|
|
519
|
+
if (isCancel(guildSelection)) {
|
|
450
520
|
cancel('Setup cancelled')
|
|
451
521
|
process.exit(0)
|
|
452
522
|
}
|
|
453
523
|
|
|
454
|
-
targetGuild = guilds.find((g) => g.id ===
|
|
524
|
+
targetGuild = guilds.find((g) => g.id === guildSelection[0])!
|
|
455
525
|
}
|
|
456
526
|
|
|
457
527
|
s.start('Creating Discord channels...')
|