diff --git a/src/patches/index.ts b/src/patches/index.ts index a1c69861..14e9530a 100644 --- a/src/patches/index.ts +++ b/src/patches/index.ts @@ -73,6 +73,7 @@ import { writeWorktreeMode } from './worktreeMode'; import { writeAllowCustomAgentModels } from './allowCustomAgentModels'; import { writeVoiceMode } from './voiceMode'; import { writeChannelsMode } from './channelsMode'; +import { writeKeybindingCustomization } from './keybindingCustomization'; import { restoreNativeBinaryFromBackup, restoreClijsFromBackup, @@ -174,6 +175,13 @@ const PATCH_DEFINITIONS = [ group: PatchGroup.ALWAYS_APPLIED, description: `Statusline updates will be properly throttled instead of queued (or debounced)`, }, + { + id: 'keybinding-customization', + name: 'Keybinding customization', + group: PatchGroup.ALWAYS_APPLIED, + description: + 'Force-enable custom keybindings when CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1', + }, // Misc Configurable { id: 'model-customizations', @@ -663,6 +671,9 @@ export const applyCustomization = async ( ), condition: config.settings.misc?.statuslineThrottleMs != null, }, + 'keybinding-customization': { + fn: c => writeKeybindingCustomization(c), + }, // Misc Configurable 'patches-applied-indication': { fn: c => diff --git a/src/patches/keybindingCustomization.test.ts b/src/patches/keybindingCustomization.test.ts new file mode 100644 index 00000000..2feafc1f --- /dev/null +++ b/src/patches/keybindingCustomization.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; + +import { writeKeybindingCustomization } from './keybindingCustomization'; + +describe('keybindingCustomization', () => { + it('bypasses the keybinding customization gate', () => { + const file = + 'const x=1;' + + 'function lE(){return u$("tengu_keybinding_customization_release",!1)}' + + 'function DLK(H){let $=new Date()}'; + + const result = writeKeybindingCustomization(file); + + expect(result).not.toBeNull(); + expect(result).toContain( + 'function lE(){return !0;return u$("tengu_keybinding_customization_release",!1)}' + ); + }); + + it('returns unchanged file when already patched', () => { + const file = + 'const x=1;' + + 'function lE(){return !0;return u$("tengu_keybinding_customization_release",!1)}' + + 'function DLK(H){let $=new Date()}'; + + const result = writeKeybindingCustomization(file); + + expect(result).toBe(file); + }); + + it('returns null when the gate pattern is absent', () => { + expect(writeKeybindingCustomization('const x=1;')).toBeNull(); + }); +}); diff --git a/src/patches/keybindingCustomization.ts b/src/patches/keybindingCustomization.ts new file mode 100644 index 00000000..0998912d --- /dev/null +++ b/src/patches/keybindingCustomization.ts @@ -0,0 +1,36 @@ +import { showDiff } from './index'; +import { debug } from '../utils'; + +export const writeKeybindingCustomization = ( + oldFile: string +): string | null => { + const alreadyPatched = + /function [$\w]+\(\)\{return !0;return [$\w]+\("tengu_keybinding_customization_release"/; + + if (alreadyPatched.test(oldFile)) { + debug('patch: keybindingCustomization: already patched'); + return oldFile; + } + + const pattern = + /function [$\w]+\(\)\{return [$\w]+\("tengu_keybinding_customization_release"/; + + const match = oldFile.match(pattern); + + if (!match || match.index === undefined) { + debug( + 'patch: keybindingCustomization: failed to find keybinding customization gate pattern' + ); + return null; + } + + const insertIndex = match.index + match[0].indexOf('{') + 1; + const insertion = 'return !0;'; + + const newFile = + oldFile.slice(0, insertIndex) + insertion + oldFile.slice(insertIndex); + + showDiff(oldFile, newFile, insertion, insertIndex, insertIndex); + + return newFile; +};