gencode-ai 0.1.0
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/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# Proposal: Keyboard Shortcuts
|
|
2
|
+
|
|
3
|
+
- **Proposal ID**: 0024
|
|
4
|
+
- **Author**: mycode team
|
|
5
|
+
- **Status**: Draft
|
|
6
|
+
- **Created**: 2025-01-15
|
|
7
|
+
- **Updated**: 2025-01-15
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Implement a keyboard shortcuts system for common CLI operations, providing power users with efficient navigation and control. Shortcuts enable quick access to commands, history, and UI controls without typing full commands.
|
|
12
|
+
|
|
13
|
+
## Motivation
|
|
14
|
+
|
|
15
|
+
Currently, mycode requires typing full commands for all operations:
|
|
16
|
+
|
|
17
|
+
1. **Slow navigation**: Must type /sessions, /resume, etc.
|
|
18
|
+
2. **No quick actions**: Can't quickly cancel, retry, or navigate
|
|
19
|
+
3. **Limited editing**: Basic readline only
|
|
20
|
+
4. **No vim/emacs mode**: Power users miss modal editing
|
|
21
|
+
5. **Inefficient workflow**: Many keystrokes for common operations
|
|
22
|
+
|
|
23
|
+
Keyboard shortcuts enable faster, more efficient interaction.
|
|
24
|
+
|
|
25
|
+
## Claude Code Reference
|
|
26
|
+
|
|
27
|
+
Claude Code provides several keyboard shortcuts:
|
|
28
|
+
|
|
29
|
+
### Known Shortcuts
|
|
30
|
+
| Shortcut | Action |
|
|
31
|
+
|----------|--------|
|
|
32
|
+
| Ctrl+C | Cancel current operation |
|
|
33
|
+
| Ctrl+D | Exit (EOF) |
|
|
34
|
+
| Ctrl+L | Clear screen |
|
|
35
|
+
| Up/Down | Navigate history |
|
|
36
|
+
| Tab | Autocomplete |
|
|
37
|
+
| Escape | Cancel input / Exit mode |
|
|
38
|
+
|
|
39
|
+
### Expected Features
|
|
40
|
+
- Command history navigation
|
|
41
|
+
- Input line editing
|
|
42
|
+
- Quick command access
|
|
43
|
+
- Mode switching
|
|
44
|
+
- Context-sensitive shortcuts
|
|
45
|
+
|
|
46
|
+
## Detailed Design
|
|
47
|
+
|
|
48
|
+
### API Design
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// src/cli/shortcuts/types.ts
|
|
52
|
+
interface KeyBinding {
|
|
53
|
+
key: string; // e.g., "ctrl+r", "meta+enter"
|
|
54
|
+
action: string; // Action identifier
|
|
55
|
+
context?: ShortcutContext;
|
|
56
|
+
description: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ShortcutContext =
|
|
60
|
+
| 'input' // During user input
|
|
61
|
+
| 'running' // While agent is running
|
|
62
|
+
| 'permission' // During permission prompt
|
|
63
|
+
| 'menu' // In menu/selection mode
|
|
64
|
+
| 'global'; // Always active
|
|
65
|
+
|
|
66
|
+
interface ShortcutConfig {
|
|
67
|
+
bindings: KeyBinding[];
|
|
68
|
+
enableVimMode: boolean;
|
|
69
|
+
enableEmacsMode: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ShortcutAction {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
handler: (context: ActionContext) => Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ActionContext {
|
|
79
|
+
input: string;
|
|
80
|
+
cursorPosition: number;
|
|
81
|
+
session: Session;
|
|
82
|
+
isRunning: boolean;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Shortcut Manager
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// src/cli/shortcuts/manager.ts
|
|
90
|
+
class ShortcutManager {
|
|
91
|
+
private bindings: Map<string, KeyBinding[]> = new Map();
|
|
92
|
+
private actions: Map<string, ShortcutAction> = new Map();
|
|
93
|
+
private currentContext: ShortcutContext = 'input';
|
|
94
|
+
private vimMode: VimMode | null = null;
|
|
95
|
+
|
|
96
|
+
constructor(config?: Partial<ShortcutConfig>) {
|
|
97
|
+
this.loadDefaultBindings();
|
|
98
|
+
if (config?.enableVimMode) {
|
|
99
|
+
this.vimMode = new VimMode();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private loadDefaultBindings(): void {
|
|
104
|
+
const defaults: KeyBinding[] = [
|
|
105
|
+
// Global
|
|
106
|
+
{ key: 'ctrl+c', action: 'cancel', context: 'global', description: 'Cancel operation' },
|
|
107
|
+
{ key: 'ctrl+d', action: 'exit', context: 'global', description: 'Exit mycode' },
|
|
108
|
+
{ key: 'ctrl+l', action: 'clear', context: 'global', description: 'Clear screen' },
|
|
109
|
+
|
|
110
|
+
// Input mode
|
|
111
|
+
{ key: 'up', action: 'history_prev', context: 'input', description: 'Previous history' },
|
|
112
|
+
{ key: 'down', action: 'history_next', context: 'input', description: 'Next history' },
|
|
113
|
+
{ key: 'ctrl+r', action: 'history_search', context: 'input', description: 'Search history' },
|
|
114
|
+
{ key: 'tab', action: 'autocomplete', context: 'input', description: 'Autocomplete' },
|
|
115
|
+
{ key: 'ctrl+a', action: 'line_start', context: 'input', description: 'Go to line start' },
|
|
116
|
+
{ key: 'ctrl+e', action: 'line_end', context: 'input', description: 'Go to line end' },
|
|
117
|
+
{ key: 'ctrl+w', action: 'delete_word', context: 'input', description: 'Delete word' },
|
|
118
|
+
{ key: 'ctrl+u', action: 'delete_line', context: 'input', description: 'Delete to start' },
|
|
119
|
+
{ key: 'meta+enter', action: 'submit_multiline', context: 'input', description: 'Submit multiline' },
|
|
120
|
+
|
|
121
|
+
// Running mode
|
|
122
|
+
{ key: 'ctrl+c', action: 'abort', context: 'running', description: 'Abort agent' },
|
|
123
|
+
{ key: 'escape', action: 'interrupt', context: 'running', description: 'Interrupt gracefully' },
|
|
124
|
+
|
|
125
|
+
// Permission mode
|
|
126
|
+
{ key: '1', action: 'allow_once', context: 'permission', description: 'Allow once' },
|
|
127
|
+
{ key: '2', action: 'allow_session', context: 'permission', description: 'Allow for session' },
|
|
128
|
+
{ key: '3', action: 'allow_always', context: 'permission', description: 'Always allow' },
|
|
129
|
+
{ key: '4', action: 'deny', context: 'permission', description: 'Deny' },
|
|
130
|
+
{ key: 'y', action: 'allow_once', context: 'permission', description: 'Yes (allow once)' },
|
|
131
|
+
{ key: 'n', action: 'deny', context: 'permission', description: 'No (deny)' },
|
|
132
|
+
|
|
133
|
+
// Quick commands
|
|
134
|
+
{ key: 'ctrl+s', action: 'save_session', context: 'input', description: 'Save session' },
|
|
135
|
+
{ key: 'ctrl+n', action: 'new_session', context: 'input', description: 'New session' },
|
|
136
|
+
{ key: 'ctrl+o', action: 'open_session', context: 'input', description: 'Open session picker' },
|
|
137
|
+
{ key: 'f1', action: 'help', context: 'global', description: 'Show help' },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
for (const binding of defaults) {
|
|
141
|
+
this.addBinding(binding);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
registerAction(action: ShortcutAction): void {
|
|
146
|
+
this.actions.set(action.id, action);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
addBinding(binding: KeyBinding): void {
|
|
150
|
+
const existing = this.bindings.get(binding.key) || [];
|
|
151
|
+
existing.push(binding);
|
|
152
|
+
this.bindings.set(binding.key, existing);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setContext(context: ShortcutContext): void {
|
|
156
|
+
this.currentContext = context;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async handleKeypress(key: KeypressEvent): Promise<boolean> {
|
|
160
|
+
// Normalize key
|
|
161
|
+
const keyStr = this.normalizeKey(key);
|
|
162
|
+
|
|
163
|
+
// Check vim mode first
|
|
164
|
+
if (this.vimMode) {
|
|
165
|
+
const handled = await this.vimMode.handleKey(key);
|
|
166
|
+
if (handled) return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Find matching bindings
|
|
170
|
+
const bindings = this.bindings.get(keyStr) || [];
|
|
171
|
+
const matching = bindings.filter(b =>
|
|
172
|
+
b.context === this.currentContext || b.context === 'global'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (matching.length === 0) return false;
|
|
176
|
+
|
|
177
|
+
// Execute first matching action
|
|
178
|
+
const binding = matching[0];
|
|
179
|
+
const action = this.actions.get(binding.action);
|
|
180
|
+
if (action) {
|
|
181
|
+
await action.handler(this.getContext());
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private normalizeKey(event: KeypressEvent): string {
|
|
189
|
+
const parts: string[] = [];
|
|
190
|
+
if (event.ctrl) parts.push('ctrl');
|
|
191
|
+
if (event.meta) parts.push('meta');
|
|
192
|
+
if (event.shift && event.name.length > 1) parts.push('shift');
|
|
193
|
+
parts.push(event.name.toLowerCase());
|
|
194
|
+
return parts.join('+');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
listBindings(context?: ShortcutContext): KeyBinding[] {
|
|
198
|
+
const all = Array.from(this.bindings.values()).flat();
|
|
199
|
+
return context
|
|
200
|
+
? all.filter(b => b.context === context || b.context === 'global')
|
|
201
|
+
: all;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Vim Mode
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// src/cli/shortcuts/vim-mode.ts
|
|
210
|
+
type VimState = 'normal' | 'insert' | 'visual' | 'command';
|
|
211
|
+
|
|
212
|
+
class VimMode {
|
|
213
|
+
private state: VimState = 'insert';
|
|
214
|
+
private commandBuffer: string = '';
|
|
215
|
+
private registerContent: string = '';
|
|
216
|
+
|
|
217
|
+
handleKey(event: KeypressEvent): boolean {
|
|
218
|
+
switch (this.state) {
|
|
219
|
+
case 'insert':
|
|
220
|
+
return this.handleInsertMode(event);
|
|
221
|
+
case 'normal':
|
|
222
|
+
return this.handleNormalMode(event);
|
|
223
|
+
case 'visual':
|
|
224
|
+
return this.handleVisualMode(event);
|
|
225
|
+
case 'command':
|
|
226
|
+
return this.handleCommandMode(event);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private handleInsertMode(event: KeypressEvent): boolean {
|
|
231
|
+
if (event.name === 'escape') {
|
|
232
|
+
this.state = 'normal';
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false; // Let normal input handling take over
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private handleNormalMode(event: KeypressEvent): boolean {
|
|
239
|
+
const key = event.name;
|
|
240
|
+
|
|
241
|
+
// Mode transitions
|
|
242
|
+
if (key === 'i') { this.state = 'insert'; return true; }
|
|
243
|
+
if (key === 'a') { this.state = 'insert'; /* move cursor right */ return true; }
|
|
244
|
+
if (key === 'v') { this.state = 'visual'; return true; }
|
|
245
|
+
if (key === ':') { this.state = 'command'; this.commandBuffer = ''; return true; }
|
|
246
|
+
|
|
247
|
+
// Navigation
|
|
248
|
+
if (key === 'h') { /* move left */ return true; }
|
|
249
|
+
if (key === 'j') { /* history next */ return true; }
|
|
250
|
+
if (key === 'k') { /* history prev */ return true; }
|
|
251
|
+
if (key === 'l') { /* move right */ return true; }
|
|
252
|
+
if (key === 'w') { /* word forward */ return true; }
|
|
253
|
+
if (key === 'b') { /* word back */ return true; }
|
|
254
|
+
if (key === '0') { /* line start */ return true; }
|
|
255
|
+
if (key === '$') { /* line end */ return true; }
|
|
256
|
+
|
|
257
|
+
// Editing
|
|
258
|
+
if (key === 'x') { /* delete char */ return true; }
|
|
259
|
+
if (key === 'd') { /* start delete */ return true; }
|
|
260
|
+
if (key === 'y') { /* start yank */ return true; }
|
|
261
|
+
if (key === 'p') { /* paste */ return true; }
|
|
262
|
+
if (key === 'u') { /* undo */ return true; }
|
|
263
|
+
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private handleCommandMode(event: KeypressEvent): boolean {
|
|
268
|
+
if (event.name === 'escape') {
|
|
269
|
+
this.state = 'normal';
|
|
270
|
+
this.commandBuffer = '';
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (event.name === 'return') {
|
|
275
|
+
this.executeCommand(this.commandBuffer);
|
|
276
|
+
this.state = 'normal';
|
|
277
|
+
this.commandBuffer = '';
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.commandBuffer += event.sequence;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private executeCommand(cmd: string): void {
|
|
286
|
+
switch (cmd) {
|
|
287
|
+
case 'w': /* save */ break;
|
|
288
|
+
case 'q': /* quit */ break;
|
|
289
|
+
case 'wq': /* save and quit */ break;
|
|
290
|
+
case 'help': /* show help */ break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
getState(): VimState {
|
|
295
|
+
return this.state;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getModeIndicator(): string {
|
|
299
|
+
const indicators = {
|
|
300
|
+
insert: '-- INSERT --',
|
|
301
|
+
normal: '-- NORMAL --',
|
|
302
|
+
visual: '-- VISUAL --',
|
|
303
|
+
command: `:${this.commandBuffer}`
|
|
304
|
+
};
|
|
305
|
+
return indicators[this.state];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### File Changes
|
|
311
|
+
|
|
312
|
+
| File | Action | Description |
|
|
313
|
+
|------|--------|-------------|
|
|
314
|
+
| `src/cli/shortcuts/types.ts` | Create | Type definitions |
|
|
315
|
+
| `src/cli/shortcuts/manager.ts` | Create | Shortcut management |
|
|
316
|
+
| `src/cli/shortcuts/vim-mode.ts` | Create | Vim mode support |
|
|
317
|
+
| `src/cli/shortcuts/actions.ts` | Create | Built-in actions |
|
|
318
|
+
| `src/cli/shortcuts/index.ts` | Create | Module exports |
|
|
319
|
+
| `src/cli/index.ts` | Modify | Integrate shortcuts |
|
|
320
|
+
| `src/cli/input.ts` | Modify | Key handling |
|
|
321
|
+
|
|
322
|
+
## User Experience
|
|
323
|
+
|
|
324
|
+
### Help Display
|
|
325
|
+
```
|
|
326
|
+
User: (presses F1 or /shortcuts)
|
|
327
|
+
|
|
328
|
+
Keyboard Shortcuts:
|
|
329
|
+
┌────────────────────────────────────────────────────────────┐
|
|
330
|
+
│ Global │
|
|
331
|
+
│ Ctrl+C Cancel/abort current operation │
|
|
332
|
+
│ Ctrl+D Exit mycode │
|
|
333
|
+
│ Ctrl+L Clear screen │
|
|
334
|
+
│ F1 Show this help │
|
|
335
|
+
│ │
|
|
336
|
+
│ Input Mode │
|
|
337
|
+
│ Up/Down Navigate command history │
|
|
338
|
+
│ Ctrl+R Search history │
|
|
339
|
+
│ Tab Autocomplete commands │
|
|
340
|
+
│ Ctrl+A/E Go to start/end of line │
|
|
341
|
+
│ Meta+Enter Submit multiline input │
|
|
342
|
+
│ Ctrl+S Save current session │
|
|
343
|
+
│ Ctrl+N New session │
|
|
344
|
+
│ Ctrl+O Open session picker │
|
|
345
|
+
│ │
|
|
346
|
+
│ Permission Prompt │
|
|
347
|
+
│ 1/y Allow once │
|
|
348
|
+
│ 2 Allow for session │
|
|
349
|
+
│ 3 Always allow │
|
|
350
|
+
│ 4/n Deny │
|
|
351
|
+
└────────────────────────────────────────────────────────────┘
|
|
352
|
+
|
|
353
|
+
Vim mode: Disabled (enable in settings)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Vim Mode Indicator
|
|
357
|
+
```
|
|
358
|
+
┌─ mycode ──────────────────────────────────────────────────┐
|
|
359
|
+
│ > type your message here_ │
|
|
360
|
+
│ │
|
|
361
|
+
│ -- INSERT -- │
|
|
362
|
+
└───────────────────────────────────────────────────────────┘
|
|
363
|
+
|
|
364
|
+
(press Escape)
|
|
365
|
+
|
|
366
|
+
┌─ mycode ──────────────────────────────────────────────────┐
|
|
367
|
+
│ > type your message here │
|
|
368
|
+
│ ^ │
|
|
369
|
+
│ -- NORMAL -- │
|
|
370
|
+
└───────────────────────────────────────────────────────────┘
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### History Search (Ctrl+R)
|
|
374
|
+
```
|
|
375
|
+
(reverse-i-search)`auth': /sessions --tag authentication
|
|
376
|
+
|
|
377
|
+
Press Enter to select, Ctrl+R for next match, Esc to cancel
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Alternatives Considered
|
|
381
|
+
|
|
382
|
+
### Alternative 1: Mouse-based UI
|
|
383
|
+
Add clickable UI elements.
|
|
384
|
+
|
|
385
|
+
**Pros**: Discoverable
|
|
386
|
+
**Cons**: Requires terminal mouse support
|
|
387
|
+
**Decision**: Deferred - Can add alongside shortcuts
|
|
388
|
+
|
|
389
|
+
### Alternative 2: Command Palette
|
|
390
|
+
VS Code-style command palette.
|
|
391
|
+
|
|
392
|
+
**Pros**: Discoverable, searchable
|
|
393
|
+
**Cons**: More complex UI
|
|
394
|
+
**Decision**: Consider for future
|
|
395
|
+
|
|
396
|
+
### Alternative 3: No Vim Mode
|
|
397
|
+
Skip vim/emacs modes.
|
|
398
|
+
|
|
399
|
+
**Pros**: Simpler
|
|
400
|
+
**Cons**: Power users miss it
|
|
401
|
+
**Decision**: Rejected - Vim mode is expected
|
|
402
|
+
|
|
403
|
+
## Security Considerations
|
|
404
|
+
|
|
405
|
+
1. **Input Validation**: Validate shortcut configurations
|
|
406
|
+
2. **No Injection**: Shortcuts can't execute arbitrary commands
|
|
407
|
+
3. **Confirmation**: Dangerous actions still require confirmation
|
|
408
|
+
4. **Escape Hatch**: Always allow Ctrl+C to abort
|
|
409
|
+
|
|
410
|
+
## Testing Strategy
|
|
411
|
+
|
|
412
|
+
1. **Unit Tests**:
|
|
413
|
+
- Key normalization
|
|
414
|
+
- Binding matching
|
|
415
|
+
- Action execution
|
|
416
|
+
- Vim mode state machine
|
|
417
|
+
|
|
418
|
+
2. **Integration Tests**:
|
|
419
|
+
- Full input handling
|
|
420
|
+
- Context switching
|
|
421
|
+
- History navigation
|
|
422
|
+
|
|
423
|
+
3. **Manual Testing**:
|
|
424
|
+
- Various terminal emulators
|
|
425
|
+
- Key combinations
|
|
426
|
+
- Vim workflow
|
|
427
|
+
|
|
428
|
+
## Migration Path
|
|
429
|
+
|
|
430
|
+
1. **Phase 1**: Basic shortcuts (Ctrl+C, arrows, etc.)
|
|
431
|
+
2. **Phase 2**: History search
|
|
432
|
+
3. **Phase 3**: Vim mode
|
|
433
|
+
4. **Phase 4**: Customization
|
|
434
|
+
5. **Phase 5**: Emacs mode
|
|
435
|
+
|
|
436
|
+
No breaking changes - enhances existing input.
|
|
437
|
+
|
|
438
|
+
## References
|
|
439
|
+
|
|
440
|
+
- [readline - Node.js](https://nodejs.org/api/readline.html)
|
|
441
|
+
- [Vim Commands Cheat Sheet](https://vim.rtorr.com/)
|
|
442
|
+
- [GNU Readline](https://tiswww.case.edu/php/chet/readline/readline.html)
|
|
443
|
+
- [Inquirer.js](https://github.com/SBoudrias/Inquirer.js)
|