kimaki 0.4.39 → 0.4.41

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +108 -51
  3. package/dist/commands/abort.js +1 -1
  4. package/dist/commands/add-project.js +2 -2
  5. package/dist/commands/agent.js +2 -2
  6. package/dist/commands/fork.js +2 -2
  7. package/dist/commands/model.js +2 -2
  8. package/dist/commands/remove-project.js +2 -2
  9. package/dist/commands/resume.js +2 -2
  10. package/dist/commands/session.js +4 -4
  11. package/dist/commands/share.js +1 -1
  12. package/dist/commands/undo-redo.js +2 -2
  13. package/dist/commands/worktree.js +180 -0
  14. package/dist/database.js +49 -1
  15. package/dist/discord-bot.js +29 -4
  16. package/dist/discord-utils.js +36 -0
  17. package/dist/errors.js +86 -87
  18. package/dist/genai-worker.js +1 -1
  19. package/dist/interaction-handler.js +6 -2
  20. package/dist/markdown.js +5 -1
  21. package/dist/message-formatting.js +2 -2
  22. package/dist/opencode.js +4 -4
  23. package/dist/session-handler.js +2 -2
  24. package/dist/tools.js +3 -3
  25. package/dist/voice-handler.js +3 -3
  26. package/dist/voice.js +4 -4
  27. package/package.json +16 -16
  28. package/src/cli.ts +166 -85
  29. package/src/commands/abort.ts +1 -1
  30. package/src/commands/add-project.ts +2 -2
  31. package/src/commands/agent.ts +2 -2
  32. package/src/commands/fork.ts +2 -2
  33. package/src/commands/model.ts +2 -2
  34. package/src/commands/remove-project.ts +2 -2
  35. package/src/commands/resume.ts +2 -2
  36. package/src/commands/session.ts +4 -4
  37. package/src/commands/share.ts +1 -1
  38. package/src/commands/undo-redo.ts +2 -2
  39. package/src/commands/worktree.ts +243 -0
  40. package/src/database.ts +96 -1
  41. package/src/discord-bot.ts +30 -4
  42. package/src/discord-utils.ts +50 -0
  43. package/src/errors.ts +90 -160
  44. package/src/genai-worker.ts +1 -1
  45. package/src/interaction-handler.ts +7 -2
  46. package/src/markdown.ts +5 -4
  47. package/src/message-formatting.ts +2 -2
  48. package/src/opencode.ts +4 -4
  49. package/src/session-handler.ts +2 -2
  50. package/src/tools.ts +3 -3
  51. package/src/voice-handler.ts +3 -3
  52. package/src/voice.ts +4 -4
package/package.json CHANGED
@@ -2,18 +2,7 @@
2
2
  "name": "kimaki",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.4.39",
6
- "scripts": {
7
- "dev": "tsx --env-file .env src/cli.ts",
8
- "prepublishOnly": "pnpm tsc",
9
- "dev:bun": "DEBUG=1 bun --env-file .env src/cli.ts",
10
- "watch": "tsx scripts/watch-session.ts",
11
- "test:events": "tsx test-events.ts",
12
- "pcm-to-mp3": "bun scripts/pcm-to-mp3",
13
- "test:send": "tsx send-test-message.ts",
14
- "register-commands": "tsx scripts/register-commands.ts",
15
- "format": "oxfmt src"
16
- },
5
+ "version": "0.4.41",
17
6
  "repository": "https://github.com/remorses/kimaki",
18
7
  "bin": "bin.js",
19
8
  "files": [
@@ -34,27 +23,38 @@
34
23
  "@clack/prompts": "^0.11.0",
35
24
  "@discordjs/voice": "^0.19.0",
36
25
  "@google/genai": "^1.34.0",
37
- "@opencode-ai/sdk": "^1.1.12",
26
+ "@opencode-ai/sdk": "^1.1.31",
38
27
  "@purinton/resampler": "^1.0.4",
39
28
  "ai": "^5.0.114",
40
29
  "better-sqlite3": "^12.3.0",
41
30
  "cac": "^6.7.14",
42
31
  "discord.js": "^14.16.3",
43
32
  "domhandler": "^5.0.3",
44
- "errore": "^0.5.2",
45
33
  "glob": "^13.0.0",
46
34
  "htmlparser2": "^10.0.0",
47
35
  "js-yaml": "^4.1.0",
48
36
  "marked": "^16.3.0",
37
+ "mime": "^4.1.0",
49
38
  "picocolors": "^1.1.1",
50
39
  "pretty-ms": "^9.3.0",
51
40
  "ripgrep-js": "^3.0.0",
52
41
  "string-dedent": "^3.0.2",
53
42
  "undici": "^7.16.0",
54
- "zod": "^4.2.1"
43
+ "zod": "^4.2.1",
44
+ "errore": "^0.7.1"
55
45
  },
56
46
  "optionalDependencies": {
57
47
  "@discordjs/opus": "^0.10.0",
58
48
  "prism-media": "^1.3.5"
49
+ },
50
+ "scripts": {
51
+ "dev": "tsx --env-file .env src/cli.ts",
52
+ "dev:bun": "DEBUG=1 bun --env-file .env src/cli.ts",
53
+ "watch": "tsx scripts/watch-session.ts",
54
+ "test:events": "tsx test-events.ts",
55
+ "pcm-to-mp3": "bun scripts/pcm-to-mp3",
56
+ "test:send": "tsx send-test-message.ts",
57
+ "register-commands": "tsx scripts/register-commands.ts",
58
+ "format": "oxfmt src"
59
59
  }
60
- }
60
+ }
package/src/cli.ts CHANGED
@@ -43,6 +43,7 @@ import fs from 'node:fs'
43
43
  import * as errore from 'errore'
44
44
 
45
45
  import { createLogger } from './logger.js'
46
+ import { uploadFilesToDiscord } from './discord-utils.js'
46
47
  import { spawn, spawnSync, execSync, type ExecSyncOptions } from 'node:child_process'
47
48
  import http from 'node:http'
48
49
  import { setDataDir, getDataDir, getLockPort } from './config.js'
@@ -211,7 +212,7 @@ async function registerCommands({
211
212
  })
212
213
  .toJSON(),
213
214
  new SlashCommandBuilder()
214
- .setName('session')
215
+ .setName('new-session')
215
216
  .setDescription('Start a new OpenCode session')
216
217
  .addStringOption((option) => {
217
218
  option.setName('prompt').setDescription('Prompt content for the session').setRequired(true)
@@ -236,6 +237,18 @@ async function registerCommands({
236
237
  return option
237
238
  })
238
239
  .toJSON(),
240
+ new SlashCommandBuilder()
241
+ .setName('new-worktree')
242
+ .setDescription('Create a new git worktree and start a session thread')
243
+ .addStringOption((option) => {
244
+ option
245
+ .setName('name')
246
+ .setDescription('Name for the worktree (will be formatted: lowercase, spaces to dashes)')
247
+ .setRequired(true)
248
+
249
+ return option
250
+ })
251
+ .toJSON(),
239
252
  new SlashCommandBuilder()
240
253
  .setName('add-project')
241
254
  .setDescription('Create Discord channels for a new OpenCode project')
@@ -376,6 +389,133 @@ async function registerCommands({
376
389
  }
377
390
  }
378
391
 
392
+ /**
393
+ * Store channel-directory mappings in the database.
394
+ * Called after Discord login to persist channel configurations.
395
+ */
396
+ function storeChannelDirectories({
397
+ kimakiChannels,
398
+ db,
399
+ }: {
400
+ kimakiChannels: { guild: Guild; channels: ChannelWithTags[] }[]
401
+ db: ReturnType<typeof getDatabase>
402
+ }): void {
403
+ for (const { guild, channels } of kimakiChannels) {
404
+ for (const channel of channels) {
405
+ if (channel.kimakiDirectory) {
406
+ db.prepare(
407
+ 'INSERT OR IGNORE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
408
+ ).run(channel.id, channel.kimakiDirectory, 'text', channel.kimakiApp || null)
409
+
410
+ const voiceChannel = guild.channels.cache.find(
411
+ (ch) => ch.type === ChannelType.GuildVoice && ch.name === channel.name,
412
+ )
413
+
414
+ if (voiceChannel) {
415
+ db.prepare(
416
+ 'INSERT OR IGNORE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
417
+ ).run(voiceChannel.id, channel.kimakiDirectory, 'voice', channel.kimakiApp || null)
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Show the ready message with channel links.
426
+ * Called at the end of startup to display available channels.
427
+ */
428
+ function showReadyMessage({
429
+ kimakiChannels,
430
+ createdChannels,
431
+ appId,
432
+ }: {
433
+ kimakiChannels: { guild: Guild; channels: ChannelWithTags[] }[]
434
+ createdChannels: { name: string; id: string; guildId: string }[]
435
+ appId: string
436
+ }): void {
437
+ const allChannels: {
438
+ name: string
439
+ id: string
440
+ guildId: string
441
+ directory?: string
442
+ }[] = []
443
+
444
+ allChannels.push(...createdChannels)
445
+
446
+ kimakiChannels.forEach(({ guild, channels }) => {
447
+ channels.forEach((ch) => {
448
+ allChannels.push({
449
+ name: ch.name,
450
+ id: ch.id,
451
+ guildId: guild.id,
452
+ directory: ch.kimakiDirectory,
453
+ })
454
+ })
455
+ })
456
+
457
+ if (allChannels.length > 0) {
458
+ const channelLinks = allChannels
459
+ .map((ch) => `• #${ch.name}: https://discord.com/channels/${ch.guildId}/${ch.id}`)
460
+ .join('\n')
461
+
462
+ note(
463
+ `Your kimaki channels are ready! Click any link below to open in Discord:\n\n${channelLinks}\n\nSend a message in any channel to start using OpenCode!`,
464
+ '🚀 Ready to Use',
465
+ )
466
+ }
467
+
468
+ note(
469
+ 'Leave this process running to keep the bot active.\n\nIf you close this process or restart your machine, run `npx kimaki` again to start the bot.',
470
+ '⚠️ Keep Running',
471
+ )
472
+ }
473
+
474
+ /**
475
+ * Background initialization for quick start mode.
476
+ * Starts OpenCode server and registers slash commands without blocking bot startup.
477
+ */
478
+ async function backgroundInit({
479
+ currentDir,
480
+ token,
481
+ appId,
482
+ }: {
483
+ currentDir: string
484
+ token: string
485
+ appId: string
486
+ }): Promise<void> {
487
+ try {
488
+ const opencodeResult = await initializeOpencodeForDirectory(currentDir)
489
+ if (opencodeResult instanceof Error) {
490
+ cliLogger.warn('Background OpenCode init failed:', opencodeResult.message)
491
+ // Still try to register basic commands without user commands/agents
492
+ await registerCommands({ token, appId, userCommands: [], agents: [] })
493
+ return
494
+ }
495
+
496
+ const getClient = opencodeResult
497
+
498
+ const [userCommands, agents] = await Promise.all([
499
+ getClient()
500
+ .command.list({ query: { directory: currentDir } })
501
+ .then((r) => r.data || [])
502
+ .catch(() => []),
503
+ getClient()
504
+ .app.agents({ query: { directory: currentDir } })
505
+ .then((r) => r.data || [])
506
+ .catch(() => []),
507
+ ])
508
+
509
+ await registerCommands({ token, appId, userCommands, agents })
510
+ cliLogger.log('Slash commands registered!')
511
+ } catch (error) {
512
+ cliLogger.error(
513
+ 'Background init failed:',
514
+ error instanceof Error ? error.message : String(error),
515
+ )
516
+ }
517
+ }
518
+
379
519
  async function run({ restart, addChannels }: CliOptions) {
380
520
  const forceSetup = Boolean(restart)
381
521
 
@@ -582,7 +722,7 @@ async function run({ restart, addChannels }: CliOptions) {
582
722
  const currentDir = process.cwd()
583
723
  s.start('Starting OpenCode server...')
584
724
  const opencodePromise = initializeOpencodeForDirectory(currentDir).then((result) => {
585
- if (errore.isError(result)) {
725
+ if (result instanceof Error) {
586
726
  throw new Error(result.message)
587
727
  }
588
728
  return result
@@ -666,25 +806,8 @@ async function run({ restart, addChannels }: CliOptions) {
666
806
  }
667
807
  db.prepare('INSERT OR REPLACE INTO bot_tokens (app_id, token) VALUES (?, ?)').run(appId, token)
668
808
 
669
- for (const { guild, channels } of kimakiChannels) {
670
- for (const channel of channels) {
671
- if (channel.kimakiDirectory) {
672
- db.prepare(
673
- 'INSERT OR IGNORE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
674
- ).run(channel.id, channel.kimakiDirectory, 'text', channel.kimakiApp || null)
675
-
676
- const voiceChannel = guild.channels.cache.find(
677
- (ch) => ch.type === ChannelType.GuildVoice && ch.name === channel.name,
678
- )
679
-
680
- if (voiceChannel) {
681
- db.prepare(
682
- 'INSERT OR IGNORE INTO channel_directories (channel_id, directory, channel_type, app_id) VALUES (?, ?, ?, ?)',
683
- ).run(voiceChannel.id, channel.kimakiDirectory, 'voice', channel.kimakiApp || null)
684
- }
685
- }
686
- }
687
- }
809
+ // Store channel-directory mappings
810
+ storeChannelDirectories({ kimakiChannels, db })
688
811
 
689
812
  if (kimakiChannels.length > 0) {
690
813
  const channelList = kimakiChannels
@@ -700,6 +823,22 @@ async function run({ restart, addChannels }: CliOptions) {
700
823
  note(channelList, 'Existing Kimaki Channels')
701
824
  }
702
825
 
826
+ // Quick start: if setup is already done, start bot immediately and background the rest
827
+ const isQuickStart = existingBot && !forceSetup && !addChannels
828
+ if (isQuickStart) {
829
+ s.start('Starting Discord bot...')
830
+ await startDiscordBot({ token, appId, discordClient })
831
+ s.stop('Discord bot is running!')
832
+
833
+ // Background: OpenCode init + slash command registration (non-blocking)
834
+ void backgroundInit({ currentDir, token, appId })
835
+
836
+ showReadyMessage({ kimakiChannels, createdChannels, appId })
837
+ outro('✨ Bot ready! Listening for messages...')
838
+ return
839
+ }
840
+
841
+ // Full setup path: wait for OpenCode, show prompts, create channels if needed
703
842
  // Await the OpenCode server that was started in parallel with Discord login
704
843
  s.start('Waiting for OpenCode server...')
705
844
  const getClient = await opencodePromise
@@ -862,42 +1001,7 @@ async function run({ restart, addChannels }: CliOptions) {
862
1001
  await startDiscordBot({ token, appId, discordClient })
863
1002
  s.stop('Discord bot is running!')
864
1003
 
865
- const allChannels: {
866
- name: string
867
- id: string
868
- guildId: string
869
- directory?: string
870
- }[] = []
871
-
872
- allChannels.push(...createdChannels)
873
-
874
- kimakiChannels.forEach(({ guild, channels }) => {
875
- channels.forEach((ch) => {
876
- allChannels.push({
877
- name: ch.name,
878
- id: ch.id,
879
- guildId: guild.id,
880
- directory: ch.kimakiDirectory,
881
- })
882
- })
883
- })
884
-
885
- if (allChannels.length > 0) {
886
- const channelLinks = allChannels
887
- .map((ch) => `• #${ch.name}: https://discord.com/channels/${ch.guildId}/${ch.id}`)
888
- .join('\n')
889
-
890
- note(
891
- `Your kimaki channels are ready! Click any link below to open in Discord:\n\n${channelLinks}\n\nSend a message in any channel to start using OpenCode!`,
892
- '🚀 Ready to Use',
893
- )
894
- }
895
-
896
- note(
897
- 'Leave this process running to keep the bot active.\n\nIf you close this process or restart your machine, run `npx kimaki` again to start the bot.',
898
- '⚠️ Keep Running',
899
- )
900
-
1004
+ showReadyMessage({ kimakiChannels, createdChannels, appId })
901
1005
  outro('✨ Setup complete! Listening for new messages... do not close this process.')
902
1006
  }
903
1007
 
@@ -998,34 +1102,11 @@ cli
998
1102
  const s = spinner()
999
1103
  s.start(`Uploading ${resolvedFiles.length} file(s)...`)
1000
1104
 
1001
- for (const file of resolvedFiles) {
1002
- const buffer = fs.readFileSync(file)
1003
-
1004
- const formData = new FormData()
1005
- formData.append(
1006
- 'payload_json',
1007
- JSON.stringify({
1008
- attachments: [{ id: 0, filename: path.basename(file) }],
1009
- }),
1010
- )
1011
- formData.append('files[0]', new Blob([buffer]), path.basename(file))
1012
-
1013
- const response = await fetch(
1014
- `https://discord.com/api/v10/channels/${threadRow.thread_id}/messages`,
1015
- {
1016
- method: 'POST',
1017
- headers: {
1018
- Authorization: `Bot ${botRow.token}`,
1019
- },
1020
- body: formData,
1021
- },
1022
- )
1023
-
1024
- if (!response.ok) {
1025
- const error = await response.text()
1026
- throw new Error(`Discord API error: ${response.status} - ${error}`)
1027
- }
1028
- }
1105
+ await uploadFilesToDiscord({
1106
+ threadId: threadRow.thread_id,
1107
+ botToken: botRow.token,
1108
+ files: resolvedFiles,
1109
+ })
1029
1110
 
1030
1111
  s.stop(`Uploaded ${resolvedFiles.length} file(s)!`)
1031
1112
 
@@ -72,7 +72,7 @@ export async function handleAbortCommand({ command }: CommandContext): Promise<v
72
72
  }
73
73
 
74
74
  const getClient = await initializeOpencodeForDirectory(directory)
75
- if (errore.isError(getClient)) {
75
+ if (getClient instanceof Error) {
76
76
  await command.reply({
77
77
  content: `Failed to abort: ${getClient.message}`,
78
78
  ephemeral: true,
@@ -26,7 +26,7 @@ export async function handleAddProjectCommand({ command, appId }: CommandContext
26
26
  try {
27
27
  const currentDir = process.cwd()
28
28
  const getClient = await initializeOpencodeForDirectory(currentDir)
29
- if (errore.isError(getClient)) {
29
+ if (getClient instanceof Error) {
30
30
  await command.editReply(getClient.message)
31
31
  return
32
32
  }
@@ -94,7 +94,7 @@ export async function handleAddProjectAutocomplete({
94
94
  try {
95
95
  const currentDir = process.cwd()
96
96
  const getClient = await initializeOpencodeForDirectory(currentDir)
97
- if (errore.isError(getClient)) {
97
+ if (getClient instanceof Error) {
98
98
  await interaction.respond([])
99
99
  return
100
100
  }
@@ -162,7 +162,7 @@ export async function handleAgentCommand({
162
162
 
163
163
  try {
164
164
  const getClient = await initializeOpencodeForDirectory(context.dir)
165
- if (errore.isError(getClient)) {
165
+ if (getClient instanceof Error) {
166
166
  await interaction.editReply({ content: getClient.message })
167
167
  return
168
168
  }
@@ -297,7 +297,7 @@ export async function handleQuickAgentCommand({
297
297
 
298
298
  try {
299
299
  const getClient = await initializeOpencodeForDirectory(context.dir)
300
- if (errore.isError(getClient)) {
300
+ if (getClient instanceof Error) {
301
301
  await command.editReply({ content: getClient.message })
302
302
  return
303
303
  }
@@ -73,7 +73,7 @@ export async function handleForkCommand(interaction: ChatInputCommandInteraction
73
73
  const sessionId = row.session_id
74
74
 
75
75
  const getClient = await initializeOpencodeForDirectory(directory)
76
- if (errore.isError(getClient)) {
76
+ if (getClient instanceof Error) {
77
77
  await interaction.editReply({
78
78
  content: `Failed to load messages: ${getClient.message}`,
79
79
  })
@@ -171,7 +171,7 @@ export async function handleForkSelectMenu(
171
171
  await interaction.deferReply({ ephemeral: false })
172
172
 
173
173
  const getClient = await initializeOpencodeForDirectory(directory)
174
- if (errore.isError(getClient)) {
174
+ if (getClient instanceof Error) {
175
175
  await interaction.editReply(`Failed to fork session: ${getClient.message}`)
176
176
  return
177
177
  }
@@ -129,7 +129,7 @@ export async function handleModelCommand({
129
129
 
130
130
  try {
131
131
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
132
- if (errore.isError(getClient)) {
132
+ if (getClient instanceof Error) {
133
133
  await interaction.editReply({ content: getClient.message })
134
134
  return
135
135
  }
@@ -237,7 +237,7 @@ export async function handleProviderSelectMenu(
237
237
 
238
238
  try {
239
239
  const getClient = await initializeOpencodeForDirectory(context.dir)
240
- if (errore.isError(getClient)) {
240
+ if (getClient instanceof Error) {
241
241
  await interaction.editReply({
242
242
  content: getClient.message,
243
243
  components: [],
@@ -42,7 +42,7 @@ export async function handleRemoveProjectCommand({ command, appId }: CommandCont
42
42
  catch: (e) => e as Error,
43
43
  })
44
44
 
45
- if (errore.isError(channel)) {
45
+ if (channel instanceof Error) {
46
46
  logger.error(`Failed to fetch channel ${channel_id}:`, channel)
47
47
  failedChannels.push(`${channel_type}: ${channel_id}`)
48
48
  continue
@@ -116,7 +116,7 @@ export async function handleRemoveProjectAutocomplete({
116
116
  try: () => guild.channels.fetch(channel_id),
117
117
  catch: (e) => e as Error,
118
118
  })
119
- if (errore.isError(channel)) {
119
+ if (channel instanceof Error) {
120
120
  // Channel not in this guild, skip
121
121
  continue
122
122
  }
@@ -61,7 +61,7 @@ export async function handleResumeCommand({ command, appId }: CommandContext): P
61
61
 
62
62
  try {
63
63
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
64
- if (errore.isError(getClient)) {
64
+ if (getClient instanceof Error) {
65
65
  await command.editReply(getClient.message)
66
66
  return
67
67
  }
@@ -173,7 +173,7 @@ export async function handleResumeAutocomplete({
173
173
 
174
174
  try {
175
175
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
176
- if (errore.isError(getClient)) {
176
+ if (getClient instanceof Error) {
177
177
  await interaction.respond([])
178
178
  return
179
179
  }
@@ -1,4 +1,4 @@
1
- // /session command - Start a new OpenCode session.
1
+ // /new-session command - Start a new OpenCode session.
2
2
 
3
3
  import { ChannelType, type TextChannel } from 'discord.js'
4
4
  import fs from 'node:fs'
@@ -59,7 +59,7 @@ export async function handleSessionCommand({ command, appId }: CommandContext):
59
59
 
60
60
  try {
61
61
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
62
- if (errore.isError(getClient)) {
62
+ if (getClient instanceof Error) {
63
63
  await command.editReply(getClient.message)
64
64
  return
65
65
  }
@@ -133,7 +133,7 @@ async function handleAgentAutocomplete({ interaction, appId }: AutocompleteConte
133
133
 
134
134
  try {
135
135
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
136
- if (errore.isError(getClient)) {
136
+ if (getClient instanceof Error) {
137
137
  await interaction.respond([])
138
138
  return
139
139
  }
@@ -216,7 +216,7 @@ export async function handleSessionAutocomplete({
216
216
 
217
217
  try {
218
218
  const getClient = await initializeOpencodeForDirectory(projectDirectory)
219
- if (errore.isError(getClient)) {
219
+ if (getClient instanceof Error) {
220
220
  await interaction.respond([])
221
221
  return
222
222
  }
@@ -65,7 +65,7 @@ export async function handleShareCommand({ command }: CommandContext): Promise<v
65
65
  const sessionId = row.session_id
66
66
 
67
67
  const getClient = await initializeOpencodeForDirectory(directory)
68
- if (errore.isError(getClient)) {
68
+ if (getClient instanceof Error) {
69
69
  await command.reply({
70
70
  content: `Failed to share session: ${getClient.message}`,
71
71
  ephemeral: true,
@@ -67,7 +67,7 @@ export async function handleUndoCommand({ command }: CommandContext): Promise<vo
67
67
  await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
68
68
 
69
69
  const getClient = await initializeOpencodeForDirectory(directory)
70
- if (errore.isError(getClient)) {
70
+ if (getClient instanceof Error) {
71
71
  await command.editReply(`Failed to undo: ${getClient.message}`)
72
72
  return
73
73
  }
@@ -174,7 +174,7 @@ export async function handleRedoCommand({ command }: CommandContext): Promise<vo
174
174
  await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
175
175
 
176
176
  const getClient = await initializeOpencodeForDirectory(directory)
177
- if (errore.isError(getClient)) {
177
+ if (getClient instanceof Error) {
178
178
  await command.editReply(`Failed to redo: ${getClient.message}`)
179
179
  return
180
180
  }