Skip to content

Commit 00af40e

Browse files
committed
feat: add autocomplete for /settings action and value fields
1 parent f2f33a6 commit 00af40e

File tree

2 files changed

+120
-2
lines changed

2 files changed

+120
-2
lines changed

discord/bot.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import {
1010
Routes,
1111
CommandInteraction,
1212
ButtonInteraction,
13+
AutocompleteInteraction,
1314
TextChannel,
1415
EmbedBuilder
1516
} from "npm:discord.js@14.14.1";
1617

1718
import { sanitizeChannelName } from "./utils.ts";
1819
import { handlePaginationInteraction } from "./pagination.ts";
1920
import { checkCommandPermission } from "../core/rbac.ts";
21+
import { SETTINGS_ACTIONS, SETTINGS_VALUES } from "../settings/unified-settings.ts";
2022
import type {
2123
BotConfig,
2224
CommandHandlers,
@@ -281,6 +283,31 @@ export async function createDiscordBot(
281283
}
282284
}
283285
}
286+
287+
// Autocomplete handler for /settings action & value fields
288+
async function handleAutocomplete(interaction: AutocompleteInteraction) {
289+
if (interaction.commandName !== 'settings') return;
290+
291+
const focused = interaction.options.getFocused(true);
292+
const category = interaction.options.getString('category') ?? '';
293+
const action = interaction.options.getString('action') ?? '';
294+
const typed = focused.value.toLowerCase();
295+
296+
let choices: { name: string; value: string }[] = [];
297+
298+
if (focused.name === 'action') {
299+
choices = SETTINGS_ACTIONS[category] ?? [];
300+
} else if (focused.name === 'value') {
301+
choices = SETTINGS_VALUES[action] ?? [];
302+
}
303+
304+
// Filter by what the user has typed so far
305+
const filtered = choices
306+
.filter(c => c.name.toLowerCase().includes(typed) || c.value.toLowerCase().includes(typed))
307+
.slice(0, 25); // Discord max 25 choices
308+
309+
await interaction.respond(filtered);
310+
}
284311

285312
// Button handler - completely generic
286313
async function handleButton(interaction: ButtonInteraction) {
@@ -514,6 +541,8 @@ export async function createDiscordBot(
514541
client.on(Events.InteractionCreate, async (interaction) => {
515542
if (interaction.isCommand()) {
516543
await handleCommand(interaction as CommandInteraction);
544+
} else if (interaction.isAutocomplete()) {
545+
await handleAutocomplete(interaction as AutocompleteInteraction);
517546
} else if (interaction.isButton()) {
518547
await handleButton(interaction as ButtonInteraction);
519548
}

settings/unified-settings.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,13 @@ export const unifiedSettingsCommand = new SlashCommandBuilder()
345345
.addStringOption(option =>
346346
option.setName('action')
347347
.setDescription('Specific action to perform')
348-
.setRequired(false))
348+
.setRequired(false)
349+
.setAutocomplete(true))
349350
.addStringOption(option =>
350351
option.setName('value')
351352
.setDescription('New value for the setting')
352-
.setRequired(false));
353+
.setRequired(false)
354+
.setAutocomplete(true));
353355

354356
// New todos command
355357
export const todosCommand = new SlashCommandBuilder()
@@ -426,6 +428,93 @@ export const mcpCommand = new SlashCommandBuilder()
426428
.setDescription('Server description')
427429
.setRequired(false));
428430

431+
// ── Autocomplete mappings for /settings action & value fields ──
432+
433+
/** Actions available per category */
434+
export const SETTINGS_ACTIONS: Record<string, { name: string; value: string }[]> = {
435+
bot: [
436+
{ name: 'mention-on — Enable mention forwarding', value: 'mention-on' },
437+
{ name: 'mention-off — Disable mention forwarding', value: 'mention-off' },
438+
],
439+
claude: [
440+
{ name: 'set-model — Change the Claude model', value: 'set-model' },
441+
{ name: 'toggle-git-context — Toggle auto git context', value: 'toggle-git-context' },
442+
{ name: 'toggle-system-info — Toggle auto system info', value: 'toggle-system-info' },
443+
{ name: 'set-system-prompt — Set a custom system prompt', value: 'set-system-prompt' },
444+
],
445+
modes: [
446+
{ name: 'set-thinking — Change thinking mode', value: 'set-thinking' },
447+
{ name: 'set-operation — Change operation/permission mode', value: 'set-operation' },
448+
{ name: 'set-effort — Change effort level', value: 'set-effort' },
449+
{ name: 'set-budget — Set max budget per query (USD)', value: 'set-budget' },
450+
{ name: 'toggle-1m — Toggle 1M context beta', value: 'toggle-1m' },
451+
{ name: 'toggle-checkpoint — Toggle file checkpointing', value: 'toggle-checkpoint' },
452+
{ name: 'toggle-sandbox — Toggle sandbox mode', value: 'toggle-sandbox' },
453+
{ name: 'toggle-structured-output — Toggle structured JSON output', value: 'toggle-structured-output' },
454+
{ name: 'set-output-schema — Set custom JSON output schema', value: 'set-output-schema' },
455+
],
456+
output: [
457+
{ name: 'toggle-highlighting — Toggle code syntax highlighting', value: 'toggle-highlighting' },
458+
{ name: 'set-max-length — Set max output length', value: 'set-max-length' },
459+
{ name: 'set-timestamp — Set timestamp format', value: 'set-timestamp' },
460+
],
461+
proxy: [
462+
{ name: 'enable — Enable proxy', value: 'enable' },
463+
{ name: 'disable — Disable proxy', value: 'disable' },
464+
{ name: 'set-url — Set proxy URL', value: 'set-url' },
465+
{ name: 'add-bypass — Add a bypass domain', value: 'add-bypass' },
466+
{ name: 'remove-bypass — Remove a bypass domain', value: 'remove-bypass' },
467+
{ name: 'list-bypass — List bypass domains', value: 'list-bypass' },
468+
],
469+
developer: [
470+
{ name: 'toggle-debug — Toggle debug mode', value: 'toggle-debug' },
471+
{ name: 'toggle-verbose — Toggle verbose error reporting', value: 'toggle-verbose' },
472+
{ name: 'toggle-metrics — Toggle performance metrics', value: 'toggle-metrics' },
473+
{ name: 'show-debug — Show debug information', value: 'show-debug' },
474+
],
475+
reset: [
476+
{ name: 'all — Reset all settings', value: 'all' },
477+
{ name: 'bot — Reset bot settings', value: 'bot' },
478+
{ name: 'claude — Reset Claude settings', value: 'claude' },
479+
{ name: 'modes — Reset mode settings', value: 'modes' },
480+
{ name: 'output — Reset output settings', value: 'output' },
481+
{ name: 'proxy — Reset proxy settings', value: 'proxy' },
482+
{ name: 'developer — Reset developer settings', value: 'developer' },
483+
],
484+
};
485+
486+
/** Values available per action (only for actions that have a known set of choices) */
487+
export const SETTINGS_VALUES: Record<string, { name: string; value: string }[]> = {
488+
'set-thinking': Object.entries(THINKING_MODES).map(([key, mode]) => ({
489+
name: `${key}${mode.description}`,
490+
value: key,
491+
})),
492+
'set-operation': Object.entries(OPERATION_MODES).map(([key, mode]) => ({
493+
name: `${key}${mode.name}`,
494+
value: key,
495+
})),
496+
'set-effort': Object.entries(EFFORT_LEVELS).map(([key, level]) => ({
497+
name: `${key}${level.description}`,
498+
value: key,
499+
})),
500+
'set-model': Object.entries(CLAUDE_MODELS).map(([key, model]) => ({
501+
name: `${key}${model.name}`,
502+
value: key,
503+
})),
504+
'set-timestamp': [
505+
{ name: 'relative — e.g. "2 minutes ago"', value: 'relative' },
506+
{ name: 'absolute — e.g. "2025-01-15 14:30"', value: 'absolute' },
507+
{ name: 'both — Show both formats', value: 'both' },
508+
],
509+
'set-budget': [
510+
{ name: '$0.50', value: '0.50' },
511+
{ name: '$1.00', value: '1.00' },
512+
{ name: '$5.00', value: '5.00' },
513+
{ name: '$10.00', value: '10.00' },
514+
{ name: 'none — Remove budget limit', value: 'none' },
515+
],
516+
};
517+
429518
export const unifiedSettingsCommands = [
430519
unifiedSettingsCommand,
431520
todosCommand,

0 commit comments

Comments
 (0)