kimaki 0.4.52 → 0.4.54

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 CHANGED
@@ -2,7 +2,7 @@
2
2
  // Main CLI entrypoint for the Kimaki Discord bot.
3
3
  // Handles interactive setup, Discord OAuth, slash command registration,
4
4
  // project channel creation, and launching the bot with opencode integration.
5
- import { cac } from 'cac';
5
+ import { cac } from '@xmorse/cac';
6
6
  import { intro, outro, text, password, note, cancel, isCancel, confirm, log, multiselect, spinner, } from '@clack/prompts';
7
7
  import { deduplicateByKey, generateBotInstallUrl, abbreviatePath } from './utils.js';
8
8
  import { getChannelsWithDescriptions, createDiscordClient, getDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, ensureKimakiCategory, createProjectChannels, } from './discord-bot.js';
@@ -204,12 +204,8 @@ async function registerCommands({ token, appId, userCommands = [], agents = [],
204
204
  .setDescription('Merge the worktree branch into the default branch')
205
205
  .toJSON(),
206
206
  new SlashCommandBuilder()
207
- .setName('enable-worktrees')
208
- .setDescription('Enable automatic git worktree creation for new sessions in this channel')
209
- .toJSON(),
210
- new SlashCommandBuilder()
211
- .setName('disable-worktrees')
212
- .setDescription('Disable automatic git worktree creation for new sessions in this channel')
207
+ .setName('toggle-worktrees')
208
+ .setDescription('Toggle automatic git worktree creation for new sessions in this channel')
213
209
  .toJSON(),
214
210
  new SlashCommandBuilder()
215
211
  .setName('add-project')
@@ -267,6 +263,10 @@ async function registerCommands({ token, appId, userCommands = [], agents = [],
267
263
  .setName('model')
268
264
  .setDescription('Set the preferred model for this channel or session')
269
265
  .toJSON(),
266
+ new SlashCommandBuilder()
267
+ .setName('login')
268
+ .setDescription('Authenticate with an AI provider (OAuth or API key). Use this instead of /connect')
269
+ .toJSON(),
270
270
  new SlashCommandBuilder()
271
271
  .setName('agent')
272
272
  .setDescription('Set the preferred agent for this channel or session')
@@ -303,6 +303,14 @@ async function registerCommands({ token, appId, userCommands = [], agents = [],
303
303
  return option;
304
304
  })
305
305
  .toJSON(),
306
+ new SlashCommandBuilder()
307
+ .setName('restart-opencode-server')
308
+ .setDescription('Restart the opencode server for this channel only (fixes state/auth/plugins)')
309
+ .toJSON(),
310
+ new SlashCommandBuilder()
311
+ .setName('sqlitedb')
312
+ .setDescription('Show the location of the SQLite database file')
313
+ .toJSON(),
306
314
  ];
307
315
  // Add user-defined commands with -cmd suffix
308
316
  for (const cmd of userCommands) {
@@ -433,54 +441,57 @@ async function run({ restart, addChannels, useWorktrees, enableVoiceChannels })
433
441
  const forceSetup = Boolean(restart);
434
442
  intro('🤖 Discord Bot Setup');
435
443
  // Step 0: Check if OpenCode CLI is available
436
- const opencodeCheck = spawnSync('which', ['opencode'], { shell: true });
437
- if (opencodeCheck.status !== 0) {
438
- note('OpenCode CLI is required but not found in your PATH.', '⚠️ OpenCode Not Found');
439
- const shouldInstall = await confirm({
440
- message: 'Would you like to install OpenCode right now?',
441
- });
442
- if (isCancel(shouldInstall) || !shouldInstall) {
443
- cancel('OpenCode CLI is required to run this bot');
444
- process.exit(0);
445
- }
446
- const s = spinner();
447
- s.start('Installing OpenCode CLI...');
448
- try {
449
- execSync('curl -fsSL https://opencode.ai/install | bash', {
450
- stdio: 'inherit',
451
- shell: '/bin/bash',
452
- });
453
- s.stop('OpenCode CLI installed successfully!');
454
- // The install script adds opencode to PATH via shell configuration
455
- // For the current process, we need to check common installation paths
456
- const possiblePaths = [
457
- `${process.env.HOME}/.local/bin/opencode`,
458
- `${process.env.HOME}/.opencode/bin/opencode`,
459
- '/usr/local/bin/opencode',
460
- '/opt/opencode/bin/opencode',
461
- ];
462
- const installedPath = possiblePaths.find((p) => {
463
- try {
464
- fs.accessSync(p, fs.constants.F_OK);
465
- return true;
466
- }
467
- catch (error) {
468
- cliLogger.debug(`OpenCode path not found at ${p}:`, error instanceof Error ? error.message : String(error));
469
- return false;
470
- }
444
+ // Skip check if user set OPENCODE_PATH (for custom forks like shuvcode)
445
+ if (!process.env.OPENCODE_PATH) {
446
+ const opencodeCheck = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['opencode'], { shell: true });
447
+ if (opencodeCheck.status !== 0) {
448
+ note('OpenCode CLI is required but not found in your PATH.', '⚠️ OpenCode Not Found');
449
+ const shouldInstall = await confirm({
450
+ message: 'Would you like to install OpenCode right now?',
471
451
  });
472
- if (!installedPath) {
473
- note('OpenCode was installed but may not be available in this session.\n' +
474
- 'Please restart your terminal and run this command again.', '⚠️ Restart Required');
452
+ if (isCancel(shouldInstall) || !shouldInstall) {
453
+ cancel('OpenCode CLI is required to run this bot');
475
454
  process.exit(0);
476
455
  }
477
- // For subsequent spawn calls in this session, we can use the full path
478
- process.env.OPENCODE_PATH = installedPath;
479
- }
480
- catch (error) {
481
- s.stop('Failed to install OpenCode CLI');
482
- cliLogger.error('Installation error:', error instanceof Error ? error.message : String(error));
483
- process.exit(EXIT_NO_RESTART);
456
+ const s = spinner();
457
+ s.start('Installing OpenCode CLI...');
458
+ try {
459
+ execSync('curl -fsSL https://opencode.ai/install | bash', {
460
+ stdio: 'inherit',
461
+ shell: '/bin/bash',
462
+ });
463
+ s.stop('OpenCode CLI installed successfully!');
464
+ // The install script adds opencode to PATH via shell configuration
465
+ // For the current process, we need to check common installation paths
466
+ const possiblePaths = [
467
+ `${process.env.HOME}/.local/bin/opencode`,
468
+ `${process.env.HOME}/.opencode/bin/opencode`,
469
+ '/usr/local/bin/opencode',
470
+ '/opt/opencode/bin/opencode',
471
+ ];
472
+ const installedPath = possiblePaths.find((p) => {
473
+ try {
474
+ fs.accessSync(p, fs.constants.F_OK);
475
+ return true;
476
+ }
477
+ catch (error) {
478
+ cliLogger.debug(`OpenCode path not found at ${p}:`, error instanceof Error ? error.message : String(error));
479
+ return false;
480
+ }
481
+ });
482
+ if (!installedPath) {
483
+ note('OpenCode was installed but may not be available in this session.\n' +
484
+ 'Please restart your terminal and run this command again.', '⚠️ Restart Required');
485
+ process.exit(0);
486
+ }
487
+ // For subsequent spawn calls in this session, we can use the full path
488
+ process.env.OPENCODE_PATH = installedPath;
489
+ }
490
+ catch (error) {
491
+ s.stop('Failed to install OpenCode CLI');
492
+ cliLogger.error('Installation error:', error instanceof Error ? error.message : String(error));
493
+ process.exit(EXIT_NO_RESTART);
494
+ }
484
495
  }
485
496
  }
486
497
  const db = getDatabase();
@@ -1382,5 +1393,35 @@ cli
1382
1393
  process.exit(EXIT_NO_RESTART);
1383
1394
  }
1384
1395
  });
1396
+ cli
1397
+ .command('tunnel', 'Expose a local port via tunnel')
1398
+ .option('-p, --port <port>', 'Local port to expose (required)')
1399
+ .option('-t, --tunnel-id [id]', 'Tunnel ID (random if omitted)')
1400
+ .option('-h, --host [host]', 'Local host (default: localhost)')
1401
+ .option('-d, --domain [domain]', 'Base domain (default: kimaki.xyz)')
1402
+ .option('-s, --server [url]', 'Tunnel server URL (overrides domain)')
1403
+ .action(async (options) => {
1404
+ const { runTunnel, parseCommandFromArgv, CLI_NAME } = await import('traforo/run-tunnel');
1405
+ if (!options.port) {
1406
+ cliLogger.error('Error: --port is required');
1407
+ cliLogger.error(`\nUsage: kimaki tunnel -p <port> [-- command]`);
1408
+ process.exit(EXIT_NO_RESTART);
1409
+ }
1410
+ const port = parseInt(options.port, 10);
1411
+ if (isNaN(port) || port < 1 || port > 65535) {
1412
+ cliLogger.error(`Error: Invalid port number: ${options.port}`);
1413
+ process.exit(EXIT_NO_RESTART);
1414
+ }
1415
+ // Parse command after -- from argv
1416
+ const { command } = parseCommandFromArgv(process.argv);
1417
+ await runTunnel({
1418
+ port,
1419
+ tunnelId: options.tunnelId,
1420
+ localHost: options.host,
1421
+ baseDomain: options.domain || 'kimaki.xyz',
1422
+ serverUrl: options.server,
1423
+ command: command.length > 0 ? command : undefined,
1424
+ });
1425
+ });
1385
1426
  cli.help();
1386
1427
  cli.parse();
@@ -79,15 +79,15 @@ export async function resolveAgentCommandContext({ interaction, appId, }) {
79
79
  }
80
80
  /**
81
81
  * Set the agent preference for a context (session or channel).
82
- * When switching agents for a session, also clears the session model preference
83
- * so the new agent's model takes effect.
82
+ * When switching agents for a session, clears session model preference
83
+ * so the new agent's model takes effect (agent model > channel model).
84
84
  */
85
85
  export function setAgentForContext({ context, agentName, }) {
86
86
  if (context.isThread && context.sessionId) {
87
87
  setSessionAgent(context.sessionId, agentName);
88
88
  // Clear session model so the new agent's model takes effect
89
89
  clearSessionModel(context.sessionId);
90
- agentLogger.log(`Set agent ${agentName} for session ${context.sessionId} (cleared model preference)`);
90
+ agentLogger.log(`Set agent ${agentName} for session ${context.sessionId} (cleared session model)`);
91
91
  }
92
92
  else {
93
93
  setChannelAgent(context.channelId, agentName);