kimaki 0.4.7 → 0.4.10

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/README.md CHANGED
@@ -1,7 +1,69 @@
1
- creating a new bot
1
+ # Kimaki Discord Bot
2
2
 
3
- - first tell user to go to https://discord.com/developers/applications and create a new app
4
- - get bot app id
5
- - get bot token in Discord clicking reset bot token. show a password input
3
+ A Discord bot that integrates OpenCode coding sessions with Discord channels and voice.
6
4
 
7
- -
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
+ ### Send a session to Discord
34
+
35
+ Send an OpenCode session to Discord from the CLI:
36
+
37
+ ```bash
38
+ kimaki send-to-discord <session-id>
39
+ ```
40
+
41
+ Options:
42
+ - `-d, --directory <dir>` - Project directory (defaults to current working directory)
43
+
44
+ ### OpenCode Integration
45
+
46
+ To use the `/send-to-kimaki-discord` command in OpenCode:
47
+
48
+ ```bash
49
+ npx kimaki install-plugin
50
+ ```
51
+
52
+ Then use `/send-to-kimaki-discord` in OpenCode to send the current session to Discord.
53
+
54
+ ## Discord Slash Commands
55
+
56
+ Once the bot is running, you can use these commands in Discord:
57
+
58
+ - `/session <prompt>` - Start a new OpenCode session
59
+ - `/resume <session>` - Resume an existing session
60
+ - `/add-project <project>` - Add a new project to Discord
61
+ - `/accept` - Accept a permission request
62
+ - `/accept-always` - Accept and auto-approve similar requests
63
+ - `/reject` - Reject a permission request
64
+
65
+ ## Voice Support
66
+
67
+ Join a voice channel that has an associated project directory, and the bot will join with Jarvis-like voice interaction powered by Gemini.
68
+
69
+ Requires a Gemini API key (prompted during setup).
package/dist/cli.js CHANGED
@@ -6,6 +6,8 @@ import { getChannelsWithDescriptions, createDiscordClient, getDatabase, startDis
6
6
  import { Events, ChannelType, REST, Routes, SlashCommandBuilder, } from 'discord.js';
7
7
  import path from 'node:path';
8
8
  import fs from 'node:fs';
9
+ import { createRequire } from 'node:module';
10
+ import os from 'node:os';
9
11
  import { createLogger } from './logger.js';
10
12
  import { spawn, spawnSync, execSync } from 'node:child_process';
11
13
  const cliLogger = createLogger('CLI');
@@ -435,5 +437,149 @@ cli
435
437
  process.exit(EXIT_NO_RESTART);
436
438
  }
437
439
  });
440
+ cli
441
+ .command('send-to-discord <sessionId>', 'Send an OpenCode session to Discord and create a thread for it')
442
+ .option('-d, --directory <dir>', 'Project directory (defaults to current working directory)')
443
+ .action(async (sessionId, options) => {
444
+ try {
445
+ const directory = options.directory || process.cwd();
446
+ const db = getDatabase();
447
+ const botRow = db
448
+ .prepare('SELECT app_id, token FROM bot_tokens ORDER BY created_at DESC LIMIT 1')
449
+ .get();
450
+ if (!botRow) {
451
+ cliLogger.error('No bot credentials found. Run `kimaki` first to set up the bot.');
452
+ process.exit(EXIT_NO_RESTART);
453
+ }
454
+ const channelRow = db
455
+ .prepare('SELECT channel_id FROM channel_directories WHERE directory = ? AND channel_type = ?')
456
+ .get(directory, 'text');
457
+ if (!channelRow) {
458
+ cliLogger.error(`No Discord channel found for directory: ${directory}\n` +
459
+ 'Run `kimaki --add-channels` to create a channel for this project.');
460
+ process.exit(EXIT_NO_RESTART);
461
+ }
462
+ const s = spinner();
463
+ s.start('Connecting to Discord...');
464
+ const discordClient = await createDiscordClient();
465
+ await new Promise((resolve, reject) => {
466
+ discordClient.once(Events.ClientReady, () => {
467
+ resolve();
468
+ });
469
+ discordClient.once(Events.Error, reject);
470
+ discordClient.login(botRow.token).catch(reject);
471
+ });
472
+ s.stop('Connected to Discord!');
473
+ const channel = await discordClient.channels.fetch(channelRow.channel_id);
474
+ if (!channel || channel.type !== ChannelType.GuildText) {
475
+ cliLogger.error('Could not find the text channel or it is not a text channel');
476
+ discordClient.destroy();
477
+ process.exit(EXIT_NO_RESTART);
478
+ }
479
+ const textChannel = channel;
480
+ s.start('Fetching session from OpenCode...');
481
+ const getClient = await initializeOpencodeForDirectory(directory);
482
+ const sessionResponse = await getClient().session.get({
483
+ path: { id: sessionId },
484
+ });
485
+ if (!sessionResponse.data) {
486
+ s.stop('Session not found');
487
+ discordClient.destroy();
488
+ process.exit(EXIT_NO_RESTART);
489
+ }
490
+ const session = sessionResponse.data;
491
+ s.stop(`Found session: ${session.title}`);
492
+ s.start('Creating Discord thread...');
493
+ const thread = await textChannel.threads.create({
494
+ name: `Resume: ${session.title}`.slice(0, 100),
495
+ autoArchiveDuration: 1440,
496
+ reason: `Resuming session ${sessionId} from CLI`,
497
+ });
498
+ db.prepare('INSERT OR REPLACE INTO thread_sessions (thread_id, session_id) VALUES (?, ?)').run(thread.id, sessionId);
499
+ s.stop('Created Discord thread!');
500
+ s.start('Loading session messages...');
501
+ const messagesResponse = await getClient().session.messages({
502
+ path: { id: sessionId },
503
+ });
504
+ if (!messagesResponse.data) {
505
+ s.stop('Failed to fetch session messages');
506
+ discordClient.destroy();
507
+ process.exit(EXIT_NO_RESTART);
508
+ }
509
+ const messages = messagesResponse.data;
510
+ await thread.send(`📂 **Resumed session:** ${session.title}\n📅 **Created:** ${new Date(session.time.created).toLocaleString()}\n\n*Loading ${messages.length} messages...*`);
511
+ let messageCount = 0;
512
+ for (const message of messages) {
513
+ if (message.info.role === 'user') {
514
+ const userParts = message.parts.filter((p) => p.type === 'text' && !p.synthetic);
515
+ const userTexts = userParts
516
+ .map((p) => {
517
+ if (p.type === 'text') {
518
+ return p.text;
519
+ }
520
+ return '';
521
+ })
522
+ .filter((t) => t.trim());
523
+ const userText = userTexts.join('\n\n');
524
+ if (userText) {
525
+ const truncated = userText.length > 1900 ? userText.slice(0, 1900) + '…' : userText;
526
+ await thread.send(`**User:**\n${truncated}`);
527
+ }
528
+ }
529
+ else if (message.info.role === 'assistant') {
530
+ const textParts = message.parts.filter((p) => p.type === 'text');
531
+ const texts = textParts
532
+ .map((p) => {
533
+ if (p.type === 'text') {
534
+ return p.text;
535
+ }
536
+ return '';
537
+ })
538
+ .filter((t) => t?.trim());
539
+ if (texts.length > 0) {
540
+ const combinedText = texts.join('\n\n');
541
+ const truncated = combinedText.length > 1900 ? combinedText.slice(0, 1900) + '…' : combinedText;
542
+ await thread.send(truncated);
543
+ }
544
+ }
545
+ messageCount++;
546
+ }
547
+ await thread.send(`✅ **Session resumed!** Loaded ${messageCount} messages.\n\nYou can now continue the conversation by sending messages in this thread.`);
548
+ s.stop(`Loaded ${messageCount} messages`);
549
+ const guildId = textChannel.guildId;
550
+ const threadUrl = `https://discord.com/channels/${guildId}/${thread.id}`;
551
+ note(`Session "${session.title}" has been sent to Discord!\n\nThread: ${threadUrl}`, '✅ Success');
552
+ discordClient.destroy();
553
+ process.exit(0);
554
+ }
555
+ catch (error) {
556
+ cliLogger.error('Error:', error instanceof Error ? error.message : String(error));
557
+ process.exit(EXIT_NO_RESTART);
558
+ }
559
+ });
560
+ cli
561
+ .command('install-plugin', 'Install the OpenCode plugin for /send-to-kimaki-discord command')
562
+ .action(async () => {
563
+ try {
564
+ const require = createRequire(import.meta.url);
565
+ const pluginSrc = require.resolve('./opencode-plugin.ts');
566
+ const commandSrc = require.resolve('./opencode-command.md');
567
+ const opencodeConfig = path.join(os.homedir(), '.config', 'opencode');
568
+ const pluginDir = path.join(opencodeConfig, 'plugin');
569
+ const commandDir = path.join(opencodeConfig, 'command');
570
+ fs.mkdirSync(pluginDir, { recursive: true });
571
+ fs.mkdirSync(commandDir, { recursive: true });
572
+ const pluginDest = path.join(pluginDir, 'send-to-kimaki-discord.ts');
573
+ const commandDest = path.join(commandDir, 'send-to-kimaki-discord.md');
574
+ fs.copyFileSync(pluginSrc, pluginDest);
575
+ fs.copyFileSync(commandSrc, commandDest);
576
+ note(`Plugin: ${pluginDest}\nCommand: ${commandDest}\n\nUse /send-to-kimaki-discord in OpenCode to send the current session to Discord.`, '✅ Installed');
577
+ process.exit(0);
578
+ }
579
+ catch (error) {
580
+ cliLogger.error('Error:', error instanceof Error ? error.message : String(error));
581
+ process.exit(EXIT_NO_RESTART);
582
+ }
583
+ });
438
584
  cli.help();
439
585
  cli.parse();
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Kimaki Discord Plugin for OpenCode
3
+ *
4
+ * Adds /send-to-kimaki-discord command that sends the current session to Discord.
5
+ *
6
+ * Installation:
7
+ * kimaki install-plugin
8
+ *
9
+ * Use in OpenCode TUI:
10
+ * /send-to-kimaki-discord
11
+ */
12
+ export const KimakiDiscordPlugin = async ({ client, $, directory, }) => {
13
+ return {
14
+ event: async ({ event }) => {
15
+ if (event.type !== 'command.executed') {
16
+ return;
17
+ }
18
+ const { name, sessionID } = event.properties;
19
+ if (name !== 'send-to-kimaki-discord') {
20
+ return;
21
+ }
22
+ if (!sessionID) {
23
+ await client.tui.showToast({
24
+ body: { message: 'No session ID available', variant: 'error' },
25
+ });
26
+ return;
27
+ }
28
+ await client.tui.showToast({
29
+ body: { message: 'Creating Discord thread...', variant: 'info' },
30
+ });
31
+ try {
32
+ const result = await $ `npx -y kimaki send-to-discord ${sessionID} -d ${directory}`.text();
33
+ const urlMatch = result.match(/https:\/\/discord\.com\/channels\/\S+/);
34
+ const url = urlMatch ? urlMatch[0] : null;
35
+ await client.tui.showToast({
36
+ body: {
37
+ message: url ? `Sent to Discord: ${url}` : 'Session sent to Discord',
38
+ variant: 'success',
39
+ },
40
+ });
41
+ }
42
+ catch (error) {
43
+ const message = error.stderr?.toString().trim() ||
44
+ error.stdout?.toString().trim() ||
45
+ error.message ||
46
+ String(error);
47
+ await client.tui.showToast({
48
+ body: {
49
+ message: `Failed: ${message.slice(0, 100)}`,
50
+ variant: 'error',
51
+ },
52
+ });
53
+ }
54
+ },
55
+ };
56
+ };
package/dist/voice.js CHANGED
@@ -28,7 +28,17 @@ export async function transcribeAudio({ audio, prompt, language, temperature, ge
28
28
  throw new Error('Invalid audio format');
29
29
  }
30
30
  // Build the transcription prompt
31
- let transcriptionPrompt = `Please transcribe this audio file accurately. Here is some relevant information and filenames that may be present in the audio:\n<context>\n${prompt}\n</context>\n`;
31
+ let transcriptionPrompt = `Transcribe this audio accurately. The transcription will be sent to a coding agent (like Claude Code) to execute programming tasks.
32
+
33
+ Assume the speaker is using technical and programming terminology: file paths, function names, CLI commands, package names, API names, programming concepts, etc. Prioritize technical accuracy over literal transcription - if a word sounds like a common programming term, prefer that interpretation.
34
+
35
+ If the spoken message is unclear or ambiguous, rephrase it to better convey the intended meaning for a coding agent. The goal is effective communication of the user's programming intent, not a word-for-word transcription.
36
+
37
+ Here are relevant filenames and context that may appear in the audio:
38
+ <context>
39
+ ${prompt}
40
+ </context>
41
+ `;
32
42
  if (language) {
33
43
  transcriptionPrompt += `\nThe audio is in ${language}.`;
34
44
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kimaki",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.4.7",
5
+ "version": "0.4.10",
6
6
  "scripts": {
7
7
  "dev": "tsx --env-file .env src/cli.ts",
8
8
  "prepublishOnly": "pnpm tsc",
@@ -21,6 +21,7 @@
21
21
  "bin.js"
22
22
  ],
23
23
  "devDependencies": {
24
+ "@opencode-ai/plugin": "^1.0.119",
24
25
  "@types/better-sqlite3": "^7.6.13",
25
26
  "@types/bun": "latest",
26
27
  "@types/js-yaml": "^4.0.9",
package/src/cli.ts CHANGED
@@ -36,6 +36,8 @@ import {
36
36
  } from 'discord.js'
37
37
  import path from 'node:path'
38
38
  import fs from 'node:fs'
39
+ import { createRequire } from 'node:module'
40
+ import os from 'node:os'
39
41
  import { createLogger } from './logger.js'
40
42
  import { spawn, spawnSync, execSync, type ExecSyncOptions } from 'node:child_process'
41
43
 
@@ -655,5 +657,215 @@ cli
655
657
  }
656
658
  })
657
659
 
660
+ cli
661
+ .command(
662
+ 'send-to-discord <sessionId>',
663
+ 'Send an OpenCode session to Discord and create a thread for it',
664
+ )
665
+ .option('-d, --directory <dir>', 'Project directory (defaults to current working directory)')
666
+ .action(async (sessionId: string, options: { directory?: string }) => {
667
+ try {
668
+ const directory = options.directory || process.cwd()
669
+
670
+ const db = getDatabase()
671
+
672
+ const botRow = db
673
+ .prepare(
674
+ 'SELECT app_id, token FROM bot_tokens ORDER BY created_at DESC LIMIT 1',
675
+ )
676
+ .get() as { app_id: string; token: string } | undefined
677
+
678
+ if (!botRow) {
679
+ cliLogger.error('No bot credentials found. Run `kimaki` first to set up the bot.')
680
+ process.exit(EXIT_NO_RESTART)
681
+ }
682
+
683
+ const channelRow = db
684
+ .prepare(
685
+ 'SELECT channel_id FROM channel_directories WHERE directory = ? AND channel_type = ?',
686
+ )
687
+ .get(directory, 'text') as { channel_id: string } | undefined
688
+
689
+ if (!channelRow) {
690
+ cliLogger.error(
691
+ `No Discord channel found for directory: ${directory}\n` +
692
+ 'Run `kimaki --add-channels` to create a channel for this project.',
693
+ )
694
+ process.exit(EXIT_NO_RESTART)
695
+ }
696
+
697
+ const s = spinner()
698
+ s.start('Connecting to Discord...')
699
+
700
+ const discordClient = await createDiscordClient()
701
+
702
+ await new Promise<void>((resolve, reject) => {
703
+ discordClient.once(Events.ClientReady, () => {
704
+ resolve()
705
+ })
706
+ discordClient.once(Events.Error, reject)
707
+ discordClient.login(botRow.token).catch(reject)
708
+ })
709
+
710
+ s.stop('Connected to Discord!')
711
+
712
+ const channel = await discordClient.channels.fetch(channelRow.channel_id)
713
+ if (!channel || channel.type !== ChannelType.GuildText) {
714
+ cliLogger.error('Could not find the text channel or it is not a text channel')
715
+ discordClient.destroy()
716
+ process.exit(EXIT_NO_RESTART)
717
+ }
718
+
719
+ const textChannel = channel as import('discord.js').TextChannel
720
+
721
+ s.start('Fetching session from OpenCode...')
722
+
723
+ const getClient = await initializeOpencodeForDirectory(directory)
724
+ const sessionResponse = await getClient().session.get({
725
+ path: { id: sessionId },
726
+ })
727
+
728
+ if (!sessionResponse.data) {
729
+ s.stop('Session not found')
730
+ discordClient.destroy()
731
+ process.exit(EXIT_NO_RESTART)
732
+ }
733
+
734
+ const session = sessionResponse.data
735
+ s.stop(`Found session: ${session.title}`)
736
+
737
+ s.start('Creating Discord thread...')
738
+
739
+ const thread = await textChannel.threads.create({
740
+ name: `Resume: ${session.title}`.slice(0, 100),
741
+ autoArchiveDuration: 1440,
742
+ reason: `Resuming session ${sessionId} from CLI`,
743
+ })
744
+
745
+ db.prepare(
746
+ 'INSERT OR REPLACE INTO thread_sessions (thread_id, session_id) VALUES (?, ?)',
747
+ ).run(thread.id, sessionId)
748
+
749
+ s.stop('Created Discord thread!')
750
+
751
+ s.start('Loading session messages...')
752
+
753
+ const messagesResponse = await getClient().session.messages({
754
+ path: { id: sessionId },
755
+ })
756
+
757
+ if (!messagesResponse.data) {
758
+ s.stop('Failed to fetch session messages')
759
+ discordClient.destroy()
760
+ process.exit(EXIT_NO_RESTART)
761
+ }
762
+
763
+ const messages = messagesResponse.data
764
+
765
+ await thread.send(
766
+ `📂 **Resumed session:** ${session.title}\n📅 **Created:** ${new Date(session.time.created).toLocaleString()}\n\n*Loading ${messages.length} messages...*`,
767
+ )
768
+
769
+ let messageCount = 0
770
+ for (const message of messages) {
771
+ if (message.info.role === 'user') {
772
+ const userParts = message.parts.filter(
773
+ (p) => p.type === 'text' && !p.synthetic,
774
+ )
775
+ const userTexts = userParts
776
+ .map((p) => {
777
+ if (p.type === 'text') {
778
+ return p.text
779
+ }
780
+ return ''
781
+ })
782
+ .filter((t) => t.trim())
783
+
784
+ const userText = userTexts.join('\n\n')
785
+ if (userText) {
786
+ const truncated = userText.length > 1900 ? userText.slice(0, 1900) + '…' : userText
787
+ await thread.send(`**User:**\n${truncated}`)
788
+ }
789
+ } else if (message.info.role === 'assistant') {
790
+ const textParts = message.parts.filter((p) => p.type === 'text')
791
+ const texts = textParts
792
+ .map((p) => {
793
+ if (p.type === 'text') {
794
+ return p.text
795
+ }
796
+ return ''
797
+ })
798
+ .filter((t) => t?.trim())
799
+
800
+ if (texts.length > 0) {
801
+ const combinedText = texts.join('\n\n')
802
+ const truncated = combinedText.length > 1900 ? combinedText.slice(0, 1900) + '…' : combinedText
803
+ await thread.send(truncated)
804
+ }
805
+ }
806
+ messageCount++
807
+ }
808
+
809
+ await thread.send(
810
+ `✅ **Session resumed!** Loaded ${messageCount} messages.\n\nYou can now continue the conversation by sending messages in this thread.`,
811
+ )
812
+
813
+ s.stop(`Loaded ${messageCount} messages`)
814
+
815
+ const guildId = textChannel.guildId
816
+ const threadUrl = `https://discord.com/channels/${guildId}/${thread.id}`
817
+
818
+ note(
819
+ `Session "${session.title}" has been sent to Discord!\n\nThread: ${threadUrl}`,
820
+ '✅ Success',
821
+ )
822
+
823
+ discordClient.destroy()
824
+ process.exit(0)
825
+ } catch (error) {
826
+ cliLogger.error(
827
+ 'Error:',
828
+ error instanceof Error ? error.message : String(error),
829
+ )
830
+ process.exit(EXIT_NO_RESTART)
831
+ }
832
+ })
833
+
834
+ cli
835
+ .command('install-plugin', 'Install the OpenCode plugin for /send-to-kimaki-discord command')
836
+ .action(async () => {
837
+ try {
838
+ const require = createRequire(import.meta.url)
839
+ const pluginSrc = require.resolve('./opencode-plugin.ts')
840
+ const commandSrc = require.resolve('./opencode-command.md')
841
+
842
+ const opencodeConfig = path.join(os.homedir(), '.config', 'opencode')
843
+ const pluginDir = path.join(opencodeConfig, 'plugin')
844
+ const commandDir = path.join(opencodeConfig, 'command')
845
+
846
+ fs.mkdirSync(pluginDir, { recursive: true })
847
+ fs.mkdirSync(commandDir, { recursive: true })
848
+
849
+ const pluginDest = path.join(pluginDir, 'send-to-kimaki-discord.ts')
850
+ const commandDest = path.join(commandDir, 'send-to-kimaki-discord.md')
851
+
852
+ fs.copyFileSync(pluginSrc, pluginDest)
853
+ fs.copyFileSync(commandSrc, commandDest)
854
+
855
+ note(
856
+ `Plugin: ${pluginDest}\nCommand: ${commandDest}\n\nUse /send-to-kimaki-discord in OpenCode to send the current session to Discord.`,
857
+ '✅ Installed',
858
+ )
859
+
860
+ process.exit(0)
861
+ } catch (error) {
862
+ cliLogger.error(
863
+ 'Error:',
864
+ error instanceof Error ? error.message : String(error),
865
+ )
866
+ process.exit(EXIT_NO_RESTART)
867
+ }
868
+ })
869
+
658
870
  cli.help()
659
871
  cli.parse()
@@ -0,0 +1,4 @@
1
+ ---
2
+ description: Create Discord thread for current session
3
+ ---
4
+ Creating Discord thread for this session...
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Kimaki Discord Plugin for OpenCode
3
+ *
4
+ * Adds /send-to-kimaki-discord command that sends the current session to Discord.
5
+ *
6
+ * Installation:
7
+ * kimaki install-plugin
8
+ *
9
+ * Use in OpenCode TUI:
10
+ * /send-to-kimaki-discord
11
+ */
12
+
13
+ import type { Plugin } from '@opencode-ai/plugin'
14
+
15
+ export const KimakiDiscordPlugin: Plugin = async ({
16
+ client,
17
+ $,
18
+ directory,
19
+ }) => {
20
+ return {
21
+ event: async ({ event }) => {
22
+ if (event.type !== 'command.executed') {
23
+ return
24
+ }
25
+
26
+ const { name, sessionID } = event.properties as {
27
+ name: string
28
+ sessionID: string
29
+ }
30
+
31
+ if (name !== 'send-to-kimaki-discord') {
32
+ return
33
+ }
34
+
35
+ if (!sessionID) {
36
+ await client.tui.showToast({
37
+ body: { message: 'No session ID available', variant: 'error' },
38
+ })
39
+ return
40
+ }
41
+
42
+ await client.tui.showToast({
43
+ body: { message: 'Creating Discord thread...', variant: 'info' },
44
+ })
45
+
46
+ try {
47
+ const result =
48
+ await $`npx -y kimaki send-to-discord ${sessionID} -d ${directory}`.text()
49
+
50
+ const urlMatch = result.match(/https:\/\/discord\.com\/channels\/\S+/)
51
+ const url = urlMatch ? urlMatch[0] : null
52
+
53
+ await client.tui.showToast({
54
+ body: {
55
+ message: url ? `Sent to Discord: ${url}` : 'Session sent to Discord',
56
+ variant: 'success',
57
+ },
58
+ })
59
+ } catch (error: any) {
60
+ const message =
61
+ error.stderr?.toString().trim() ||
62
+ error.stdout?.toString().trim() ||
63
+ error.message ||
64
+ String(error)
65
+
66
+ await client.tui.showToast({
67
+ body: {
68
+ message: `Failed: ${message.slice(0, 100)}`,
69
+ variant: 'error',
70
+ },
71
+ })
72
+ }
73
+ },
74
+ }
75
+ }
package/src/voice.ts CHANGED
@@ -42,7 +42,17 @@ export async function transcribeAudio({
42
42
  }
43
43
 
44
44
  // Build the transcription prompt
45
- let transcriptionPrompt = `Please transcribe this audio file accurately. Here is some relevant information and filenames that may be present in the audio:\n<context>\n${prompt}\n</context>\n`
45
+ let transcriptionPrompt = `Transcribe this audio accurately. The transcription will be sent to a coding agent (like Claude Code) to execute programming tasks.
46
+
47
+ Assume the speaker is using technical and programming terminology: file paths, function names, CLI commands, package names, API names, programming concepts, etc. Prioritize technical accuracy over literal transcription - if a word sounds like a common programming term, prefer that interpretation.
48
+
49
+ If the spoken message is unclear or ambiguous, rephrase it to better convey the intended meaning for a coding agent. The goal is effective communication of the user's programming intent, not a word-for-word transcription.
50
+
51
+ Here are relevant filenames and context that may appear in the audio:
52
+ <context>
53
+ ${prompt}
54
+ </context>
55
+ `
46
56
  if (language) {
47
57
  transcriptionPrompt += `\nThe audio is in ${language}.`
48
58
  }