shortcutxl 0.2.12 → 0.2.13

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 (110) hide show
  1. package/README.md +26 -26
  2. package/agent-docs/README.md +397 -397
  3. package/agent-docs/docs/compaction.md +390 -390
  4. package/agent-docs/docs/custom-provider.md +580 -580
  5. package/agent-docs/docs/extensions.md +1971 -1971
  6. package/agent-docs/docs/packages.md +209 -209
  7. package/agent-docs/docs/rpc.md +1317 -1317
  8. package/agent-docs/docs/sdk.md +962 -962
  9. package/agent-docs/docs/session.md +412 -412
  10. package/agent-docs/docs/termux.md +127 -127
  11. package/agent-docs/docs/tui.md +887 -887
  12. package/agent-docs/examples/README.md +25 -25
  13. package/agent-docs/examples/extensions/README.md +205 -205
  14. package/agent-docs/examples/extensions/antigravity-image-gen.ts +447 -447
  15. package/agent-docs/examples/extensions/auto-commit-on-exit.ts +49 -49
  16. package/agent-docs/examples/extensions/bash-spawn-hook.ts +30 -30
  17. package/agent-docs/examples/extensions/bookmark.ts +50 -50
  18. package/agent-docs/examples/extensions/built-in-tool-renderer.ts +256 -256
  19. package/agent-docs/examples/extensions/claude-rules.ts +86 -86
  20. package/agent-docs/examples/extensions/commands.ts +75 -75
  21. package/agent-docs/examples/extensions/confirm-destructive.ts +59 -59
  22. package/agent-docs/examples/extensions/custom-compaction.ts +126 -126
  23. package/agent-docs/examples/extensions/custom-footer.ts +63 -63
  24. package/agent-docs/examples/extensions/custom-header.ts +73 -73
  25. package/agent-docs/examples/extensions/custom-provider-anthropic/index.ts +660 -660
  26. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +362 -362
  27. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +88 -88
  28. package/agent-docs/examples/extensions/custom-provider-qwen-cli/index.ts +349 -349
  29. package/agent-docs/examples/extensions/dirty-repo-guard.ts +56 -56
  30. package/agent-docs/examples/extensions/doom-overlay/doom-component.ts +133 -133
  31. package/agent-docs/examples/extensions/doom-overlay/doom-keys.ts +108 -108
  32. package/agent-docs/examples/extensions/doom-overlay/index.ts +74 -74
  33. package/agent-docs/examples/extensions/dynamic-resources/index.ts +15 -15
  34. package/agent-docs/examples/extensions/dynamic-tools.ts +77 -77
  35. package/agent-docs/examples/extensions/event-bus.ts +43 -43
  36. package/agent-docs/examples/extensions/file-trigger.ts +41 -41
  37. package/agent-docs/examples/extensions/git-checkpoint.ts +53 -53
  38. package/agent-docs/examples/extensions/handoff.ts +155 -155
  39. package/agent-docs/examples/extensions/hello.ts +25 -25
  40. package/agent-docs/examples/extensions/inline-bash.ts +94 -94
  41. package/agent-docs/examples/extensions/input-transform.ts +43 -43
  42. package/agent-docs/examples/extensions/interactive-shell.ts +209 -209
  43. package/agent-docs/examples/extensions/mac-system-theme.ts +47 -47
  44. package/agent-docs/examples/extensions/message-renderer.ts +59 -59
  45. package/agent-docs/examples/extensions/minimal-mode.ts +430 -430
  46. package/agent-docs/examples/extensions/modal-editor.ts +90 -90
  47. package/agent-docs/examples/extensions/model-status.ts +31 -31
  48. package/agent-docs/examples/extensions/notify.ts +55 -55
  49. package/agent-docs/examples/extensions/overlay-qa-tests.ts +936 -936
  50. package/agent-docs/examples/extensions/overlay-test.ts +159 -159
  51. package/agent-docs/examples/extensions/permission-gate.ts +37 -37
  52. package/agent-docs/examples/extensions/pirate.ts +47 -47
  53. package/agent-docs/examples/extensions/plan-mode/index.ts +363 -363
  54. package/agent-docs/examples/extensions/preset.ts +418 -418
  55. package/agent-docs/examples/extensions/protected-paths.ts +30 -30
  56. package/agent-docs/examples/extensions/qna.ts +122 -122
  57. package/agent-docs/examples/extensions/question.ts +278 -278
  58. package/agent-docs/examples/extensions/questionnaire.ts +440 -440
  59. package/agent-docs/examples/extensions/rainbow-editor.ts +90 -90
  60. package/agent-docs/examples/extensions/reload-runtime.ts +37 -37
  61. package/agent-docs/examples/extensions/rpc-demo.ts +124 -124
  62. package/agent-docs/examples/extensions/sandbox/index.ts +324 -324
  63. package/agent-docs/examples/extensions/send-user-message.ts +97 -97
  64. package/agent-docs/examples/extensions/session-name.ts +27 -27
  65. package/agent-docs/examples/extensions/shutdown-command.ts +69 -69
  66. package/agent-docs/examples/extensions/snake.ts +343 -343
  67. package/agent-docs/examples/extensions/space-invaders.ts +566 -566
  68. package/agent-docs/examples/extensions/ssh.ts +233 -233
  69. package/agent-docs/examples/extensions/status-line.ts +40 -40
  70. package/agent-docs/examples/extensions/subagent/agents.ts +130 -130
  71. package/agent-docs/examples/extensions/subagent/index.ts +1068 -1068
  72. package/agent-docs/examples/extensions/summarize.ts +206 -206
  73. package/agent-docs/examples/extensions/system-prompt-header.ts +17 -17
  74. package/agent-docs/examples/extensions/timed-confirm.ts +72 -72
  75. package/agent-docs/examples/extensions/titlebar-spinner.ts +58 -58
  76. package/agent-docs/examples/extensions/todo.ts +314 -314
  77. package/agent-docs/examples/extensions/tool-override.ts +146 -146
  78. package/agent-docs/examples/extensions/tools.ts +145 -145
  79. package/agent-docs/examples/extensions/trigger-compact.ts +40 -40
  80. package/agent-docs/examples/extensions/truncated-tool.ts +194 -194
  81. package/agent-docs/examples/extensions/widget-placement.ts +17 -17
  82. package/agent-docs/examples/extensions/with-deps/index.ts +37 -37
  83. package/agent-docs/examples/rpc-extension-ui.ts +654 -654
  84. package/agent-docs/examples/sdk/01-minimal.ts +22 -22
  85. package/agent-docs/examples/sdk/02-custom-model.ts +48 -48
  86. package/agent-docs/examples/sdk/03-custom-prompt.ts +55 -55
  87. package/agent-docs/examples/sdk/04-skills.ts +53 -53
  88. package/agent-docs/examples/sdk/05-tools.ts +56 -56
  89. package/agent-docs/examples/sdk/06-extensions.ts +88 -88
  90. package/agent-docs/examples/sdk/07-context-files.ts +40 -40
  91. package/agent-docs/examples/sdk/08-prompt-templates.ts +47 -47
  92. package/agent-docs/examples/sdk/09-api-keys-and-oauth.ts +48 -48
  93. package/agent-docs/examples/sdk/10-settings.ts +54 -54
  94. package/agent-docs/examples/sdk/11-sessions.ts +48 -48
  95. package/agent-docs/examples/sdk/12-full-control.ts +82 -82
  96. package/agent-docs/examples/sdk/README.md +144 -144
  97. package/agent-docs/xll-spec.md +110 -110
  98. package/dist/core/auth-storage.js +21 -2
  99. package/package.json +1 -1
  100. package/xll/ShortcutXL.xll +0 -0
  101. package/xll/modules/debug_render.py +272 -272
  102. package/xll/modules/gameboy.py +241 -241
  103. package/xll/modules/pong.py +188 -188
  104. package/xll/modules/shortcut_xl/_diff_highlight.py +176 -0
  105. package/xll/modules/shortcut_xl/_log.py +12 -12
  106. package/xll/modules/shortcut_xl/_registry.py +44 -44
  107. package/xll/modules/stocks.py +100 -100
  108. /package/skills/{com-advanced-api → COM-advanced-api}/SKILL.md +0 -0
  109. /package/skills/{com-advanced-api → COM-advanced-api}/excel-type-library.py +0 -0
  110. /package/skills/{com-advanced-api → COM-advanced-api}/office-type-library.py +0 -0
@@ -1,56 +1,56 @@
1
- /**
2
- * Dirty Repo Guard Extension
3
- *
4
- * Prevents session changes when there are uncommitted git changes.
5
- * Useful to ensure work is committed before switching context.
6
- */
7
-
8
- import type { ExtensionAPI, ExtensionContext } from 'shortcutxl';
9
-
10
- async function checkDirtyRepo(
11
- shortcut: ExtensionAPI,
12
- ctx: ExtensionContext,
13
- action: string
14
- ): Promise<{ cancel: boolean } | undefined> {
15
- // Check for uncommitted changes
16
- const { stdout, code } = await shortcut.exec('git', ['status', '--porcelain']);
17
-
18
- if (code !== 0) {
19
- // Not a git repo, allow the action
20
- return;
21
- }
22
-
23
- const hasChanges = stdout.trim().length > 0;
24
- if (!hasChanges) {
25
- return;
26
- }
27
-
28
- if (!ctx.hasUI) {
29
- // In non-interactive mode, block by default
30
- return { cancel: true };
31
- }
32
-
33
- // Count changed files
34
- const changedFiles = stdout.trim().split('\n').filter(Boolean).length;
35
-
36
- const choice = await ctx.ui.select(
37
- `You have ${changedFiles} uncommitted file(s). ${action} anyway?`,
38
- ['Yes, proceed anyway', 'No, let me commit first']
39
- );
40
-
41
- if (choice !== 'Yes, proceed anyway') {
42
- ctx.ui.notify('Commit your changes first', 'warning');
43
- return { cancel: true };
44
- }
45
- }
46
-
47
- export default function (shortcut: ExtensionAPI) {
48
- shortcut.on('session_before_switch', async (event, ctx) => {
49
- const action = event.reason === 'new' ? 'new session' : 'switch session';
50
- return checkDirtyRepo(shortcut, ctx, action);
51
- });
52
-
53
- shortcut.on('session_before_fork', async (_event, ctx) => {
54
- return checkDirtyRepo(shortcut, ctx, 'fork');
55
- });
56
- }
1
+ /**
2
+ * Dirty Repo Guard Extension
3
+ *
4
+ * Prevents session changes when there are uncommitted git changes.
5
+ * Useful to ensure work is committed before switching context.
6
+ */
7
+
8
+ import type { ExtensionAPI, ExtensionContext } from 'shortcutxl';
9
+
10
+ async function checkDirtyRepo(
11
+ shortcut: ExtensionAPI,
12
+ ctx: ExtensionContext,
13
+ action: string
14
+ ): Promise<{ cancel: boolean } | undefined> {
15
+ // Check for uncommitted changes
16
+ const { stdout, code } = await shortcut.exec('git', ['status', '--porcelain']);
17
+
18
+ if (code !== 0) {
19
+ // Not a git repo, allow the action
20
+ return;
21
+ }
22
+
23
+ const hasChanges = stdout.trim().length > 0;
24
+ if (!hasChanges) {
25
+ return;
26
+ }
27
+
28
+ if (!ctx.hasUI) {
29
+ // In non-interactive mode, block by default
30
+ return { cancel: true };
31
+ }
32
+
33
+ // Count changed files
34
+ const changedFiles = stdout.trim().split('\n').filter(Boolean).length;
35
+
36
+ const choice = await ctx.ui.select(
37
+ `You have ${changedFiles} uncommitted file(s). ${action} anyway?`,
38
+ ['Yes, proceed anyway', 'No, let me commit first']
39
+ );
40
+
41
+ if (choice !== 'Yes, proceed anyway') {
42
+ ctx.ui.notify('Commit your changes first', 'warning');
43
+ return { cancel: true };
44
+ }
45
+ }
46
+
47
+ export default function (shortcut: ExtensionAPI) {
48
+ shortcut.on('session_before_switch', async (event, ctx) => {
49
+ const action = event.reason === 'new' ? 'new session' : 'switch session';
50
+ return checkDirtyRepo(shortcut, ctx, action);
51
+ });
52
+
53
+ shortcut.on('session_before_fork', async (_event, ctx) => {
54
+ return checkDirtyRepo(shortcut, ctx, 'fork');
55
+ });
56
+ }
@@ -1,133 +1,133 @@
1
- /**
2
- * DOOM Component for overlay mode
3
- *
4
- * Renders DOOM frames using half-block characters (▀) with 24-bit color.
5
- * Height is calculated from width to maintain DOOM's aspect ratio.
6
- */
7
-
8
- import type { Component } from 'shortcutxl';
9
- import { isKeyRelease, type TUI } from 'shortcutxl';
10
- import type { DoomEngine } from './doom-engine.js';
11
- import { DoomKeys, mapKeyToDoom } from './doom-keys.js';
12
-
13
- function renderHalfBlock(
14
- rgba: Uint8Array,
15
- width: number,
16
- height: number,
17
- targetCols: number,
18
- targetRows: number
19
- ): string[] {
20
- const lines: string[] = [];
21
- const scaleX = width / targetCols;
22
- const scaleY = height / (targetRows * 2);
23
-
24
- for (let row = 0; row < targetRows; row++) {
25
- let line = '';
26
- const srcY1 = Math.floor(row * 2 * scaleY);
27
- const srcY2 = Math.floor((row * 2 + 1) * scaleY);
28
-
29
- for (let col = 0; col < targetCols; col++) {
30
- const srcX = Math.floor(col * scaleX);
31
- const idx1 = (srcY1 * width + srcX) * 4;
32
- const idx2 = (srcY2 * width + srcX) * 4;
33
- const r1 = rgba[idx1] ?? 0,
34
- g1 = rgba[idx1 + 1] ?? 0,
35
- b1 = rgba[idx1 + 2] ?? 0;
36
- const r2 = rgba[idx2] ?? 0,
37
- g2 = rgba[idx2 + 1] ?? 0,
38
- b2 = rgba[idx2 + 2] ?? 0;
39
- line += `\x1b[38;2;${r1};${g1};${b1}m\x1b[48;2;${r2};${g2};${b2}m▀`;
40
- }
41
- line += '\x1b[0m';
42
- lines.push(line);
43
- }
44
- return lines;
45
- }
46
-
47
- export class DoomOverlayComponent implements Component {
48
- private engine: DoomEngine;
49
- private tui: TUI;
50
- private interval: ReturnType<typeof setInterval> | null = null;
51
- private onExit: () => void;
52
-
53
- // Opt-in to key release events for smooth movement
54
- wantsKeyRelease = true;
55
-
56
- constructor(tui: TUI, engine: DoomEngine, onExit: () => void, resume = false) {
57
- this.tui = tui;
58
- this.engine = engine;
59
- this.onExit = onExit;
60
-
61
- // Unpause if resuming
62
- if (resume) {
63
- this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
64
- this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
65
- }
66
-
67
- this.startGameLoop();
68
- }
69
-
70
- private startGameLoop(): void {
71
- this.interval = setInterval(() => {
72
- try {
73
- this.engine.tick();
74
- this.tui.requestRender();
75
- } catch {
76
- // WASM error (e.g., exit via DOOM menu) - treat as quit
77
- this.dispose();
78
- this.onExit();
79
- }
80
- }, 1000 / 35);
81
- }
82
-
83
- handleInput(data: string): void {
84
- // Q to pause and exit (but not on release)
85
- if (!isKeyRelease(data) && (data === 'q' || data === 'Q')) {
86
- // Send DOOM's pause key before exiting
87
- this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
88
- this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
89
- this.dispose();
90
- this.onExit();
91
- return;
92
- }
93
-
94
- const doomKeys = mapKeyToDoom(data);
95
- if (doomKeys.length === 0) return;
96
-
97
- const released = isKeyRelease(data);
98
-
99
- for (const key of doomKeys) {
100
- this.engine.pushKey(!released, key);
101
- }
102
- }
103
-
104
- render(width: number): string[] {
105
- // DOOM renders at 640x400 (1.6:1 ratio)
106
- // With half-block characters, each terminal row = 2 pixels
107
- // So effective ratio is 640:200 = 3.2:1 (width:height in terminal cells)
108
- // Add 1 row for footer
109
- const ASPECT_RATIO = 3.2;
110
- const MIN_HEIGHT = 10;
111
- const height = Math.max(MIN_HEIGHT, Math.floor(width / ASPECT_RATIO));
112
-
113
- const rgba = this.engine.getFrameRGBA();
114
- const lines = renderHalfBlock(rgba, this.engine.width, this.engine.height, width, height);
115
-
116
- // Footer
117
- const footer =
118
- ' DOOM | Q=Pause | WASD=Move | Shift+WASD=Run | Space=Use | F=Fire | 1-7=Weapons';
119
- const truncatedFooter = footer.length > width ? footer.slice(0, width) : footer;
120
- lines.push(`\x1b[2m${truncatedFooter}\x1b[0m`);
121
-
122
- return lines;
123
- }
124
-
125
- invalidate(): void {}
126
-
127
- dispose(): void {
128
- if (this.interval) {
129
- clearInterval(this.interval);
130
- this.interval = null;
131
- }
132
- }
133
- }
1
+ /**
2
+ * DOOM Component for overlay mode
3
+ *
4
+ * Renders DOOM frames using half-block characters (▀) with 24-bit color.
5
+ * Height is calculated from width to maintain DOOM's aspect ratio.
6
+ */
7
+
8
+ import type { Component } from 'shortcutxl';
9
+ import { isKeyRelease, type TUI } from 'shortcutxl';
10
+ import type { DoomEngine } from './doom-engine.js';
11
+ import { DoomKeys, mapKeyToDoom } from './doom-keys.js';
12
+
13
+ function renderHalfBlock(
14
+ rgba: Uint8Array,
15
+ width: number,
16
+ height: number,
17
+ targetCols: number,
18
+ targetRows: number
19
+ ): string[] {
20
+ const lines: string[] = [];
21
+ const scaleX = width / targetCols;
22
+ const scaleY = height / (targetRows * 2);
23
+
24
+ for (let row = 0; row < targetRows; row++) {
25
+ let line = '';
26
+ const srcY1 = Math.floor(row * 2 * scaleY);
27
+ const srcY2 = Math.floor((row * 2 + 1) * scaleY);
28
+
29
+ for (let col = 0; col < targetCols; col++) {
30
+ const srcX = Math.floor(col * scaleX);
31
+ const idx1 = (srcY1 * width + srcX) * 4;
32
+ const idx2 = (srcY2 * width + srcX) * 4;
33
+ const r1 = rgba[idx1] ?? 0,
34
+ g1 = rgba[idx1 + 1] ?? 0,
35
+ b1 = rgba[idx1 + 2] ?? 0;
36
+ const r2 = rgba[idx2] ?? 0,
37
+ g2 = rgba[idx2 + 1] ?? 0,
38
+ b2 = rgba[idx2 + 2] ?? 0;
39
+ line += `\x1b[38;2;${r1};${g1};${b1}m\x1b[48;2;${r2};${g2};${b2}m▀`;
40
+ }
41
+ line += '\x1b[0m';
42
+ lines.push(line);
43
+ }
44
+ return lines;
45
+ }
46
+
47
+ export class DoomOverlayComponent implements Component {
48
+ private engine: DoomEngine;
49
+ private tui: TUI;
50
+ private interval: ReturnType<typeof setInterval> | null = null;
51
+ private onExit: () => void;
52
+
53
+ // Opt-in to key release events for smooth movement
54
+ wantsKeyRelease = true;
55
+
56
+ constructor(tui: TUI, engine: DoomEngine, onExit: () => void, resume = false) {
57
+ this.tui = tui;
58
+ this.engine = engine;
59
+ this.onExit = onExit;
60
+
61
+ // Unpause if resuming
62
+ if (resume) {
63
+ this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
64
+ this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
65
+ }
66
+
67
+ this.startGameLoop();
68
+ }
69
+
70
+ private startGameLoop(): void {
71
+ this.interval = setInterval(() => {
72
+ try {
73
+ this.engine.tick();
74
+ this.tui.requestRender();
75
+ } catch {
76
+ // WASM error (e.g., exit via DOOM menu) - treat as quit
77
+ this.dispose();
78
+ this.onExit();
79
+ }
80
+ }, 1000 / 35);
81
+ }
82
+
83
+ handleInput(data: string): void {
84
+ // Q to pause and exit (but not on release)
85
+ if (!isKeyRelease(data) && (data === 'q' || data === 'Q')) {
86
+ // Send DOOM's pause key before exiting
87
+ this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
88
+ this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
89
+ this.dispose();
90
+ this.onExit();
91
+ return;
92
+ }
93
+
94
+ const doomKeys = mapKeyToDoom(data);
95
+ if (doomKeys.length === 0) return;
96
+
97
+ const released = isKeyRelease(data);
98
+
99
+ for (const key of doomKeys) {
100
+ this.engine.pushKey(!released, key);
101
+ }
102
+ }
103
+
104
+ render(width: number): string[] {
105
+ // DOOM renders at 640x400 (1.6:1 ratio)
106
+ // With half-block characters, each terminal row = 2 pixels
107
+ // So effective ratio is 640:200 = 3.2:1 (width:height in terminal cells)
108
+ // Add 1 row for footer
109
+ const ASPECT_RATIO = 3.2;
110
+ const MIN_HEIGHT = 10;
111
+ const height = Math.max(MIN_HEIGHT, Math.floor(width / ASPECT_RATIO));
112
+
113
+ const rgba = this.engine.getFrameRGBA();
114
+ const lines = renderHalfBlock(rgba, this.engine.width, this.engine.height, width, height);
115
+
116
+ // Footer
117
+ const footer =
118
+ ' DOOM | Q=Pause | WASD=Move | Shift+WASD=Run | Space=Use | F=Fire | 1-7=Weapons';
119
+ const truncatedFooter = footer.length > width ? footer.slice(0, width) : footer;
120
+ lines.push(`\x1b[2m${truncatedFooter}\x1b[0m`);
121
+
122
+ return lines;
123
+ }
124
+
125
+ invalidate(): void {}
126
+
127
+ dispose(): void {
128
+ if (this.interval) {
129
+ clearInterval(this.interval);
130
+ this.interval = null;
131
+ }
132
+ }
133
+ }
@@ -1,108 +1,108 @@
1
- /**
2
- * DOOM key codes (from doomkeys.h)
3
- */
4
- export const DoomKeys = {
5
- KEY_RIGHTARROW: 0xae,
6
- KEY_LEFTARROW: 0xac,
7
- KEY_UPARROW: 0xad,
8
- KEY_DOWNARROW: 0xaf,
9
- KEY_STRAFE_L: 0xa0,
10
- KEY_STRAFE_R: 0xa1,
11
- KEY_USE: 0xa2,
12
- KEY_FIRE: 0xa3,
13
- KEY_ESCAPE: 27,
14
- KEY_ENTER: 13,
15
- KEY_TAB: 9,
16
- KEY_F1: 0x80 + 0x3b,
17
- KEY_F2: 0x80 + 0x3c,
18
- KEY_F3: 0x80 + 0x3d,
19
- KEY_F4: 0x80 + 0x3e,
20
- KEY_F5: 0x80 + 0x3f,
21
- KEY_F6: 0x80 + 0x40,
22
- KEY_F7: 0x80 + 0x41,
23
- KEY_F8: 0x80 + 0x42,
24
- KEY_F9: 0x80 + 0x43,
25
- KEY_F10: 0x80 + 0x44,
26
- KEY_F11: 0x80 + 0x57,
27
- KEY_F12: 0x80 + 0x58,
28
- KEY_BACKSPACE: 127,
29
- KEY_PAUSE: 0xff,
30
- KEY_EQUALS: 0x3d,
31
- KEY_MINUS: 0x2d,
32
- KEY_RSHIFT: 0x80 + 0x36,
33
- KEY_RCTRL: 0x80 + 0x1d,
34
- KEY_RALT: 0x80 + 0x38
35
- } as const;
36
-
37
- import { Key, matchesKey, parseKey } from 'shortcutxl';
38
-
39
- /**
40
- * Map terminal key input to DOOM key codes
41
- * Supports both raw terminal input and Kitty protocol sequences
42
- */
43
- export function mapKeyToDoom(data: string): number[] {
44
- // Arrow keys
45
- if (matchesKey(data, Key.up)) return [DoomKeys.KEY_UPARROW];
46
- if (matchesKey(data, Key.down)) return [DoomKeys.KEY_DOWNARROW];
47
- if (matchesKey(data, Key.right)) return [DoomKeys.KEY_RIGHTARROW];
48
- if (matchesKey(data, Key.left)) return [DoomKeys.KEY_LEFTARROW];
49
-
50
- // WASD - check both raw char and Kitty sequences
51
- if (data === 'w' || matchesKey(data, 'w')) return [DoomKeys.KEY_UPARROW];
52
- if (data === 'W' || matchesKey(data, Key.shift('w')))
53
- return [DoomKeys.KEY_UPARROW, DoomKeys.KEY_RSHIFT];
54
- if (data === 's' || matchesKey(data, 's')) return [DoomKeys.KEY_DOWNARROW];
55
- if (data === 'S' || matchesKey(data, Key.shift('s')))
56
- return [DoomKeys.KEY_DOWNARROW, DoomKeys.KEY_RSHIFT];
57
- if (data === 'a' || matchesKey(data, 'a')) return [DoomKeys.KEY_STRAFE_L];
58
- if (data === 'A' || matchesKey(data, Key.shift('a')))
59
- return [DoomKeys.KEY_STRAFE_L, DoomKeys.KEY_RSHIFT];
60
- if (data === 'd' || matchesKey(data, 'd')) return [DoomKeys.KEY_STRAFE_R];
61
- if (data === 'D' || matchesKey(data, Key.shift('d')))
62
- return [DoomKeys.KEY_STRAFE_R, DoomKeys.KEY_RSHIFT];
63
-
64
- // Fire - F key
65
- if (data === 'f' || data === 'F' || matchesKey(data, 'f') || matchesKey(data, Key.shift('f'))) {
66
- return [DoomKeys.KEY_FIRE];
67
- }
68
-
69
- // Use/Open
70
- if (data === ' ' || matchesKey(data, Key.space)) return [DoomKeys.KEY_USE];
71
-
72
- // Menu/UI keys
73
- if (matchesKey(data, Key.enter)) return [DoomKeys.KEY_ENTER];
74
- if (matchesKey(data, Key.escape)) return [DoomKeys.KEY_ESCAPE];
75
- if (matchesKey(data, Key.tab)) return [DoomKeys.KEY_TAB];
76
- if (matchesKey(data, Key.backspace)) return [DoomKeys.KEY_BACKSPACE];
77
-
78
- // Ctrl keys (except Ctrl+C) = fire (legacy support)
79
- const parsed = parseKey(data);
80
- if (parsed?.startsWith('ctrl+') && parsed !== 'ctrl+c') {
81
- return [DoomKeys.KEY_FIRE];
82
- }
83
- if (data.length === 1 && data.charCodeAt(0) < 32 && data !== '\x03') {
84
- return [DoomKeys.KEY_FIRE];
85
- }
86
-
87
- // Weapon selection (0-9)
88
- if (data >= '0' && data <= '9') return [data.charCodeAt(0)];
89
-
90
- // Plus/minus for screen size
91
- if (data === '+' || data === '=') return [DoomKeys.KEY_EQUALS];
92
- if (data === '-') return [DoomKeys.KEY_MINUS];
93
-
94
- // Y/N for prompts
95
- if (data === 'y' || data === 'Y' || matchesKey(data, 'y') || matchesKey(data, Key.shift('y'))) {
96
- return ['y'.charCodeAt(0)];
97
- }
98
- if (data === 'n' || data === 'N' || matchesKey(data, 'n') || matchesKey(data, Key.shift('n'))) {
99
- return ['n'.charCodeAt(0)];
100
- }
101
-
102
- // Other printable characters (for cheats)
103
- if (data.length === 1 && data.charCodeAt(0) >= 32) {
104
- return [data.toLowerCase().charCodeAt(0)];
105
- }
106
-
107
- return [];
108
- }
1
+ /**
2
+ * DOOM key codes (from doomkeys.h)
3
+ */
4
+ export const DoomKeys = {
5
+ KEY_RIGHTARROW: 0xae,
6
+ KEY_LEFTARROW: 0xac,
7
+ KEY_UPARROW: 0xad,
8
+ KEY_DOWNARROW: 0xaf,
9
+ KEY_STRAFE_L: 0xa0,
10
+ KEY_STRAFE_R: 0xa1,
11
+ KEY_USE: 0xa2,
12
+ KEY_FIRE: 0xa3,
13
+ KEY_ESCAPE: 27,
14
+ KEY_ENTER: 13,
15
+ KEY_TAB: 9,
16
+ KEY_F1: 0x80 + 0x3b,
17
+ KEY_F2: 0x80 + 0x3c,
18
+ KEY_F3: 0x80 + 0x3d,
19
+ KEY_F4: 0x80 + 0x3e,
20
+ KEY_F5: 0x80 + 0x3f,
21
+ KEY_F6: 0x80 + 0x40,
22
+ KEY_F7: 0x80 + 0x41,
23
+ KEY_F8: 0x80 + 0x42,
24
+ KEY_F9: 0x80 + 0x43,
25
+ KEY_F10: 0x80 + 0x44,
26
+ KEY_F11: 0x80 + 0x57,
27
+ KEY_F12: 0x80 + 0x58,
28
+ KEY_BACKSPACE: 127,
29
+ KEY_PAUSE: 0xff,
30
+ KEY_EQUALS: 0x3d,
31
+ KEY_MINUS: 0x2d,
32
+ KEY_RSHIFT: 0x80 + 0x36,
33
+ KEY_RCTRL: 0x80 + 0x1d,
34
+ KEY_RALT: 0x80 + 0x38
35
+ } as const;
36
+
37
+ import { Key, matchesKey, parseKey } from 'shortcutxl';
38
+
39
+ /**
40
+ * Map terminal key input to DOOM key codes
41
+ * Supports both raw terminal input and Kitty protocol sequences
42
+ */
43
+ export function mapKeyToDoom(data: string): number[] {
44
+ // Arrow keys
45
+ if (matchesKey(data, Key.up)) return [DoomKeys.KEY_UPARROW];
46
+ if (matchesKey(data, Key.down)) return [DoomKeys.KEY_DOWNARROW];
47
+ if (matchesKey(data, Key.right)) return [DoomKeys.KEY_RIGHTARROW];
48
+ if (matchesKey(data, Key.left)) return [DoomKeys.KEY_LEFTARROW];
49
+
50
+ // WASD - check both raw char and Kitty sequences
51
+ if (data === 'w' || matchesKey(data, 'w')) return [DoomKeys.KEY_UPARROW];
52
+ if (data === 'W' || matchesKey(data, Key.shift('w')))
53
+ return [DoomKeys.KEY_UPARROW, DoomKeys.KEY_RSHIFT];
54
+ if (data === 's' || matchesKey(data, 's')) return [DoomKeys.KEY_DOWNARROW];
55
+ if (data === 'S' || matchesKey(data, Key.shift('s')))
56
+ return [DoomKeys.KEY_DOWNARROW, DoomKeys.KEY_RSHIFT];
57
+ if (data === 'a' || matchesKey(data, 'a')) return [DoomKeys.KEY_STRAFE_L];
58
+ if (data === 'A' || matchesKey(data, Key.shift('a')))
59
+ return [DoomKeys.KEY_STRAFE_L, DoomKeys.KEY_RSHIFT];
60
+ if (data === 'd' || matchesKey(data, 'd')) return [DoomKeys.KEY_STRAFE_R];
61
+ if (data === 'D' || matchesKey(data, Key.shift('d')))
62
+ return [DoomKeys.KEY_STRAFE_R, DoomKeys.KEY_RSHIFT];
63
+
64
+ // Fire - F key
65
+ if (data === 'f' || data === 'F' || matchesKey(data, 'f') || matchesKey(data, Key.shift('f'))) {
66
+ return [DoomKeys.KEY_FIRE];
67
+ }
68
+
69
+ // Use/Open
70
+ if (data === ' ' || matchesKey(data, Key.space)) return [DoomKeys.KEY_USE];
71
+
72
+ // Menu/UI keys
73
+ if (matchesKey(data, Key.enter)) return [DoomKeys.KEY_ENTER];
74
+ if (matchesKey(data, Key.escape)) return [DoomKeys.KEY_ESCAPE];
75
+ if (matchesKey(data, Key.tab)) return [DoomKeys.KEY_TAB];
76
+ if (matchesKey(data, Key.backspace)) return [DoomKeys.KEY_BACKSPACE];
77
+
78
+ // Ctrl keys (except Ctrl+C) = fire (legacy support)
79
+ const parsed = parseKey(data);
80
+ if (parsed?.startsWith('ctrl+') && parsed !== 'ctrl+c') {
81
+ return [DoomKeys.KEY_FIRE];
82
+ }
83
+ if (data.length === 1 && data.charCodeAt(0) < 32 && data !== '\x03') {
84
+ return [DoomKeys.KEY_FIRE];
85
+ }
86
+
87
+ // Weapon selection (0-9)
88
+ if (data >= '0' && data <= '9') return [data.charCodeAt(0)];
89
+
90
+ // Plus/minus for screen size
91
+ if (data === '+' || data === '=') return [DoomKeys.KEY_EQUALS];
92
+ if (data === '-') return [DoomKeys.KEY_MINUS];
93
+
94
+ // Y/N for prompts
95
+ if (data === 'y' || data === 'Y' || matchesKey(data, 'y') || matchesKey(data, Key.shift('y'))) {
96
+ return ['y'.charCodeAt(0)];
97
+ }
98
+ if (data === 'n' || data === 'N' || matchesKey(data, 'n') || matchesKey(data, Key.shift('n'))) {
99
+ return ['n'.charCodeAt(0)];
100
+ }
101
+
102
+ // Other printable characters (for cheats)
103
+ if (data.length === 1 && data.charCodeAt(0) >= 32) {
104
+ return [data.toLowerCase().charCodeAt(0)];
105
+ }
106
+
107
+ return [];
108
+ }