@ebowwa/coder 0.7.64 → 0.7.66
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/index.js +36233 -32
- package/dist/interfaces/ui/terminal/cli/index.js +34318 -158
- package/dist/interfaces/ui/terminal/native/README.md +53 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
- package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/interfaces/ui/terminal/native/index.js +43 -0
- package/dist/interfaces/ui/terminal/native/index.node +0 -0
- package/dist/interfaces/ui/terminal/native/package.json +34 -0
- package/dist/native/README.md +53 -0
- package/dist/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/native/claude_code_native.dylib +0 -0
- package/dist/native/index.d.ts +0 -480
- package/dist/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.js +43 -1625
- package/dist/native/index.node +0 -0
- package/dist/native/package.json +34 -0
- package/native/index.darwin-arm64.node +0 -0
- package/native/index.js +33 -19
- package/package.json +3 -2
- package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
- package/packages/src/core/agent-loop/compaction.ts +6 -2
- package/packages/src/core/agent-loop/index.ts +2 -0
- package/packages/src/core/agent-loop/loop-state.ts +1 -1
- package/packages/src/core/agent-loop/turn-executor.ts +4 -0
- package/packages/src/core/agent-loop/types.ts +4 -0
- package/packages/src/core/api-client-impl.ts +377 -176
- package/packages/src/core/cognitive-security/hooks.ts +2 -1
- package/packages/src/core/config/todo +7 -0
- package/packages/src/core/context/__tests__/integration.test.ts +334 -0
- package/packages/src/core/context/compaction.ts +170 -0
- package/packages/src/core/context/constants.ts +58 -0
- package/packages/src/core/context/extraction.ts +85 -0
- package/packages/src/core/context/index.ts +66 -0
- package/packages/src/core/context/summarization.ts +251 -0
- package/packages/src/core/context/token-estimation.ts +98 -0
- package/packages/src/core/context/types.ts +59 -0
- package/packages/src/core/models.ts +81 -4
- package/packages/src/core/normalizers/todo +5 -1
- package/packages/src/core/providers/README.md +230 -0
- package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
- package/packages/src/core/providers/index.ts +419 -0
- package/packages/src/core/providers/types.ts +132 -0
- package/packages/src/core/retry.ts +10 -0
- package/packages/src/ecosystem/tools/index.ts +174 -0
- package/packages/src/index.ts +23 -2
- package/packages/src/interfaces/ui/index.ts +17 -20
- package/packages/src/interfaces/ui/spinner.ts +2 -2
- package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
- package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
- package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
- package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
- package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
- package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
- package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
- package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +402 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
- package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
- package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
- package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
- package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
- package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
- package/packages/src/native/index.ts +404 -27
- package/packages/src/native/tui_v2_types.ts +39 -0
- package/packages/src/teammates/coordination.test.ts +279 -0
- package/packages/src/teammates/coordination.ts +646 -0
- package/packages/src/teammates/index.ts +95 -25
- package/packages/src/teammates/integration.test.ts +272 -0
- package/packages/src/teammates/runner.test.ts +235 -0
- package/packages/src/teammates/runner.ts +750 -0
- package/packages/src/teammates/schemas.ts +673 -0
- package/packages/src/types/index.ts +1 -0
- package/packages/src/core/context-compaction.ts +0 -578
- package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
- package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
- package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
- package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
- package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
- package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
- package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
- package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
- package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
- package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
- package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
- package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
- package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
- package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
- package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
- package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
- package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
- package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
- package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
- package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
- package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
- package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
- package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
- package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
/** @jsx React.createElement */
|
|
2
|
-
/**
|
|
3
|
-
* Help Panel Component
|
|
4
|
-
* Displays contextual help with tabbed sections
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useState, useEffect } from "react";
|
|
8
|
-
import { Box, Text, useStdout } from "ink";
|
|
9
|
-
import chalk from "chalk";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Help section definition
|
|
13
|
-
*/
|
|
14
|
-
export interface HelpSection {
|
|
15
|
-
id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
content: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Help panel props
|
|
22
|
-
*/
|
|
23
|
-
export interface HelpPanelProps {
|
|
24
|
-
activeSection?: string;
|
|
25
|
-
onClose?: () => void;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* General help section - basic usage
|
|
30
|
-
*/
|
|
31
|
-
const GENERAL_HELP: string = `
|
|
32
|
-
${chalk.bold.cyan("Claude understands your codebase, makes edits with your permission,")}
|
|
33
|
-
${chalk.bold.cyan("and executes commands — right from your terminal.")}
|
|
34
|
-
|
|
35
|
-
${chalk.gray("─").repeat(60)}
|
|
36
|
-
|
|
37
|
-
${chalk.yellow("Input Features:")}
|
|
38
|
-
${chalk.green("@")} <path> ${chalk.gray("Reference files or directories")}
|
|
39
|
-
${chalk.green("&")} ${chalk.gray("Run command in background")}
|
|
40
|
-
${chalk.green("#")} <memory> ${chalk.gray("Add to persistent memory")}
|
|
41
|
-
|
|
42
|
-
${chalk.yellow("Keyboard Shortcuts:")}
|
|
43
|
-
${chalk.cyan("ctrl + o")} ${chalk.gray("Toggle verbose output")}
|
|
44
|
-
${chalk.cyan("meta + o")} ${chalk.gray("Toggle fast mode")}
|
|
45
|
-
${chalk.cyan("shift + ⏎")} ${chalk.gray("Insert newline")}
|
|
46
|
-
${chalk.cyan("ctrl + c")} ${chalk.gray("Exit session")}
|
|
47
|
-
${chalk.cyan("ctrl + b")} ${chalk.gray("Background task menu")}
|
|
48
|
-
|
|
49
|
-
${chalk.gray("─").repeat(60)}
|
|
50
|
-
|
|
51
|
-
${chalk.dim("For more help:")} ${chalk.blue.underline("https://code.claude.com/docs/en/overview")}
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Commands help section
|
|
56
|
-
*/
|
|
57
|
-
const COMMANDS_HELP: string = `
|
|
58
|
-
${chalk.bold.yellow("Session Commands:")}
|
|
59
|
-
${chalk.green("/help")}, ${chalk.green("/?")} Show this help
|
|
60
|
-
${chalk.green("/exit")}, ${chalk.green("/q")} Exit the session
|
|
61
|
-
${chalk.green("/new")} Start a fresh session
|
|
62
|
-
${chalk.green("/clear")} Clear conversation history
|
|
63
|
-
${chalk.green("/status")} Show session status
|
|
64
|
-
${chalk.green("/cost")} Show total cost
|
|
65
|
-
|
|
66
|
-
${chalk.bold.yellow("Model Commands:")}
|
|
67
|
-
${chalk.green("/model")} <name> Switch model
|
|
68
|
-
${chalk.green("/models")} List available models
|
|
69
|
-
${chalk.green("/tools")} List available tools
|
|
70
|
-
|
|
71
|
-
${chalk.bold.yellow("Context Commands:")}
|
|
72
|
-
${chalk.green("/compact")} Force context compaction
|
|
73
|
-
${chalk.green("/export")} [fmt] Export session (json/md/jsonl)
|
|
74
|
-
|
|
75
|
-
${chalk.bold.yellow("Session Management:")}
|
|
76
|
-
${chalk.green("/resume")} [id] Resume session
|
|
77
|
-
${chalk.green("/sessions")} List recent sessions
|
|
78
|
-
`;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Checkpoint commands help section
|
|
82
|
-
*/
|
|
83
|
-
const CHECKPOINTS_HELP: string = `
|
|
84
|
-
${chalk.bold.magenta("Checkpoint Commands:")}
|
|
85
|
-
${chalk.gray("Save and restore conversation + code state")}
|
|
86
|
-
|
|
87
|
-
${chalk.green("/checkpoint")} <label> Save checkpoint
|
|
88
|
-
${chalk.green("/checkpoints")} List saved checkpoints
|
|
89
|
-
${chalk.green("/restore")} <id> Restore checkpoint
|
|
90
|
-
${chalk.green("/restore-chat")} <id> Restore chat only
|
|
91
|
-
${chalk.green("/undo")} Go back to previous state
|
|
92
|
-
${chalk.green("/redo")} Go forward
|
|
93
|
-
${chalk.green("/cps-status")} Navigation status
|
|
94
|
-
|
|
95
|
-
${chalk.gray("─").repeat(60)}
|
|
96
|
-
|
|
97
|
-
${chalk.dim("Checkpoints capture:")}
|
|
98
|
-
${chalk.gray("• Conversation history")}
|
|
99
|
-
${chalk.gray("• File snapshots")}
|
|
100
|
-
${chalk.gray("• Git state")}
|
|
101
|
-
${chalk.gray("• Cost/tokens used")}
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Custom commands help section
|
|
106
|
-
*/
|
|
107
|
-
const CUSTOM_HELP: string = `
|
|
108
|
-
${chalk.bold.blue("Custom Commands & Skills:")}
|
|
109
|
-
${chalk.gray("Extend Coder with your own commands")}
|
|
110
|
-
|
|
111
|
-
${chalk.yellow("Skills:")}
|
|
112
|
-
${chalk.green("/commit")} Create a git commit
|
|
113
|
-
${chalk.green("/review-pr")} Review a pull request
|
|
114
|
-
${chalk.green("/git")} Git workflow helper
|
|
115
|
-
${chalk.green("/mcp-builder")} Build MCP servers
|
|
116
|
-
|
|
117
|
-
${chalk.yellow("Configuration:")}
|
|
118
|
-
${chalk.cyan("~/.claude/CLAUDE.md")} ${chalk.gray("Global instructions")}
|
|
119
|
-
${chalk.cyan(".claude/CLAUDE.md")} ${chalk.gray("Project instructions")}
|
|
120
|
-
${chalk.cyan("~/.claude.json")} ${chalk.gray("MCP servers config")}
|
|
121
|
-
${chalk.cyan("~/.claude/settings.json")} ${chalk.gray("Hooks & permissions")}
|
|
122
|
-
${chalk.cyan("~/.claude/keybindings.json")} ${chalk.gray("Custom keybindings")}
|
|
123
|
-
|
|
124
|
-
${chalk.gray("─").repeat(60)}
|
|
125
|
-
|
|
126
|
-
${chalk.dim("Create custom skills in:")} ${chalk.cyan("~/.claude/skills/")}
|
|
127
|
-
`;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Keybindings help section
|
|
131
|
-
*/
|
|
132
|
-
const KEYBINDINGS_HELP: string = `
|
|
133
|
-
${chalk.bold.green("Keyboard Shortcuts")}
|
|
134
|
-
|
|
135
|
-
${chalk.yellow("Input Editing:")}
|
|
136
|
-
${chalk.cyan("← →")} Move cursor
|
|
137
|
-
${chalk.cyan("Home / End")} Jump to start/end
|
|
138
|
-
${chalk.cyan("ctrl + a")} Jump to start
|
|
139
|
-
${chalk.cyan("ctrl + e")} Jump to end
|
|
140
|
-
${chalk.cyan("ctrl + u")} Clear line
|
|
141
|
-
${chalk.cyan("ctrl + w")} Delete word
|
|
142
|
-
|
|
143
|
-
${chalk.yellow("Navigation:")}
|
|
144
|
-
${chalk.cyan("Page Up / Down")} Scroll messages
|
|
145
|
-
${chalk.cyan("shift + ↑ ↓")} Scroll one line
|
|
146
|
-
${chalk.cyan("tab")} Cycle help sections
|
|
147
|
-
${chalk.cyan("← →")} Cycle sections (in help)
|
|
148
|
-
|
|
149
|
-
${chalk.yellow("Session:")}
|
|
150
|
-
${chalk.cyan("ctrl + c")} Exit session
|
|
151
|
-
${chalk.cyan("ctrl + d")} Exit (alternative)
|
|
152
|
-
${chalk.cyan("ctrl + b")} Background tasks
|
|
153
|
-
|
|
154
|
-
${chalk.gray("─").repeat(60)}
|
|
155
|
-
|
|
156
|
-
${chalk.dim("Customize:")} ${chalk.green("/keybindings")}
|
|
157
|
-
`;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* All help sections
|
|
161
|
-
*/
|
|
162
|
-
const HELP_SECTIONS: HelpSection[] = [
|
|
163
|
-
{ id: "general", title: "general", content: GENERAL_HELP },
|
|
164
|
-
{ id: "commands", title: "commands", content: COMMANDS_HELP },
|
|
165
|
-
{ id: "checkpoints", title: "checkpoints", content: CHECKPOINTS_HELP },
|
|
166
|
-
{ id: "custom", title: "custom-commands", content: CUSTOM_HELP },
|
|
167
|
-
{ id: "keybindings", title: "keybindings", content: KEYBINDINGS_HELP },
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Help Panel Component
|
|
172
|
-
*/
|
|
173
|
-
export function HelpPanel({ activeSection = "general", onClose }: HelpPanelProps) {
|
|
174
|
-
const [currentSection, setCurrentSection] = useState(0);
|
|
175
|
-
const { stdout } = useStdout();
|
|
176
|
-
|
|
177
|
-
// Find initial section index
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
const index = HELP_SECTIONS.findIndex(s => s.id === activeSection);
|
|
180
|
-
if (index >= 0) {
|
|
181
|
-
setCurrentSection(index);
|
|
182
|
-
}
|
|
183
|
-
}, [activeSection]);
|
|
184
|
-
|
|
185
|
-
const section = HELP_SECTIONS[currentSection];
|
|
186
|
-
if (!section) return null;
|
|
187
|
-
|
|
188
|
-
// Build tab bar
|
|
189
|
-
const tabBar = HELP_SECTIONS.map((s, i) => {
|
|
190
|
-
const isActive = i === currentSection;
|
|
191
|
-
const label = s.title;
|
|
192
|
-
if (isActive) {
|
|
193
|
-
return chalk.bgCyan.black(` ${label} `);
|
|
194
|
-
}
|
|
195
|
-
return chalk.dim(` ${label} `);
|
|
196
|
-
}).join(chalk.gray("│"));
|
|
197
|
-
|
|
198
|
-
// Navigation hint
|
|
199
|
-
const navHint = chalk.dim("←/→ or tab to cycle");
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<Box flexDirection="column" width={stdout.columns || 80}>
|
|
203
|
-
{/* Header */}
|
|
204
|
-
<Box>
|
|
205
|
-
<Text>
|
|
206
|
-
{chalk.gray("─").repeat(3)}
|
|
207
|
-
{chalk.bold.cyan(" Coder ")}
|
|
208
|
-
{chalk.gray("v0.2.0 ─ ")}
|
|
209
|
-
{tabBar}
|
|
210
|
-
{chalk.gray(" ─ ")}
|
|
211
|
-
{navHint}
|
|
212
|
-
{chalk.gray("─".repeat(Math.max(0, (stdout.columns || 80) - 60)))}
|
|
213
|
-
</Text>
|
|
214
|
-
</Box>
|
|
215
|
-
|
|
216
|
-
{/* Content */}
|
|
217
|
-
<Box flexDirection="column" paddingX={1}>
|
|
218
|
-
<Text>{section.content}</Text>
|
|
219
|
-
</Box>
|
|
220
|
-
|
|
221
|
-
{/* Footer */}
|
|
222
|
-
<Box>
|
|
223
|
-
<Text dimColor>
|
|
224
|
-
{chalk.gray("─").repeat(Math.min((stdout.columns || 80), 80))}
|
|
225
|
-
</Text>
|
|
226
|
-
</Box>
|
|
227
|
-
</Box>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Get all help sections
|
|
233
|
-
*/
|
|
234
|
-
export function getHelpSections(): HelpSection[] {
|
|
235
|
-
return HELP_SECTIONS;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Get help text for a specific section (for non-interactive display)
|
|
240
|
-
*/
|
|
241
|
-
export function getHelpText(section?: string): string {
|
|
242
|
-
if (section) {
|
|
243
|
-
const s = HELP_SECTIONS.find(s => s.id === section || s.title === section);
|
|
244
|
-
if (s) return s.content;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Return all sections combined
|
|
248
|
-
return HELP_SECTIONS.map(s => s.content).join("\n");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Get compact help text (single line per command)
|
|
253
|
-
*/
|
|
254
|
-
export function getCompactHelpText(): string {
|
|
255
|
-
return `
|
|
256
|
-
Commands: /help /exit /new /clear /compact /model /models /tools /cost /status /resume
|
|
257
|
-
Checkpoints: /checkpoint /checkpoints /restore /undo /redo /cps-status
|
|
258
|
-
Export: /export [json|md|jsonl]
|
|
259
|
-
`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export default HelpPanel;
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/** @jsx React.createElement */
|
|
2
|
-
/** @jsxFrag React.Fragment */
|
|
3
|
-
/**
|
|
4
|
-
* Centralized Input Management System
|
|
5
|
-
*
|
|
6
|
-
* Single source of truth for keyboard input across all TUI components.
|
|
7
|
-
* Solves the problem of multiple components fighting over stdin.
|
|
8
|
-
*
|
|
9
|
-
* Architecture:
|
|
10
|
-
* - InputProvider wraps the entire TUI
|
|
11
|
-
* - Components register/unregister as input handlers
|
|
12
|
-
* - Focus system ensures only ONE handler receives input
|
|
13
|
-
* - Priority system for modals/overlays
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import React, { createContext, useContext, useState, useCallback, useRef, useEffect } from "react";
|
|
17
|
-
import type { NativeKeyEvent } from "../../../../native/index.js";
|
|
18
|
-
|
|
19
|
-
// Re-export NativeKeyEvent for convenience
|
|
20
|
-
export type { NativeKeyEvent } from "../../../../native/index.js";
|
|
21
|
-
|
|
22
|
-
// ============================================
|
|
23
|
-
// TYPES
|
|
24
|
-
// ============================================
|
|
25
|
-
|
|
26
|
-
export type InputHandler = (event: NativeKeyEvent) => boolean;
|
|
27
|
-
|
|
28
|
-
export interface InputHandlerOptions {
|
|
29
|
-
/** Unique ID for this handler */
|
|
30
|
-
id: string;
|
|
31
|
-
/** Priority (higher = receives input first) */
|
|
32
|
-
priority?: number;
|
|
33
|
-
/** Handler function - return true to consume, false to pass through */
|
|
34
|
-
handler: InputHandler;
|
|
35
|
-
/** Whether this handler is currently active */
|
|
36
|
-
isActive?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface InputContextValue {
|
|
40
|
-
/** Register an input handler */
|
|
41
|
-
register: (options: InputHandlerOptions) => () => void;
|
|
42
|
-
/** Set focus to a specific handler */
|
|
43
|
-
focus: (handlerId: string) => void;
|
|
44
|
-
/** Get currently focused handler ID */
|
|
45
|
-
focusedId: string | null;
|
|
46
|
-
/** Dispatch a key event to handlers */
|
|
47
|
-
dispatch: (event: NativeKeyEvent) => boolean;
|
|
48
|
-
/** Whether input is currently blocked */
|
|
49
|
-
isBlocked: boolean;
|
|
50
|
-
/** Block/unblock input (for loading states) */
|
|
51
|
-
setBlocked: (blocked: boolean) => void;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================
|
|
55
|
-
// CONTEXT
|
|
56
|
-
// ============================================
|
|
57
|
-
|
|
58
|
-
const InputContext = createContext<InputContextValue | null>(null);
|
|
59
|
-
|
|
60
|
-
export function useInputContext(): InputContextValue {
|
|
61
|
-
const ctx = useContext(InputContext);
|
|
62
|
-
if (!ctx) {
|
|
63
|
-
throw new Error("useInputContext must be used within InputProvider");
|
|
64
|
-
}
|
|
65
|
-
return ctx;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ============================================
|
|
69
|
-
// PROVIDER
|
|
70
|
-
// ============================================
|
|
71
|
-
|
|
72
|
-
export interface InputProviderProps {
|
|
73
|
-
children: React.ReactNode;
|
|
74
|
-
/** Initial blocked state */
|
|
75
|
-
initialBlocked?: boolean;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function InputProvider({ children, initialBlocked = false }: InputProviderProps) {
|
|
79
|
-
const handlersRef = useRef<Map<string, InputHandlerOptions>>(new Map());
|
|
80
|
-
const [focusedId, setFocusedId] = useState<string | null>(null);
|
|
81
|
-
const [isBlocked, setIsBlocked] = useState(initialBlocked);
|
|
82
|
-
|
|
83
|
-
const register = useCallback((options: InputHandlerOptions) => {
|
|
84
|
-
const { id } = options;
|
|
85
|
-
handlersRef.current.set(id, options);
|
|
86
|
-
|
|
87
|
-
// If no focus set, focus this handler
|
|
88
|
-
if (!focusedId) {
|
|
89
|
-
setFocusedId(id);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Return unregister function
|
|
93
|
-
return () => {
|
|
94
|
-
handlersRef.current.delete(id);
|
|
95
|
-
if (focusedId === id) {
|
|
96
|
-
// Focus next available handler (highest priority)
|
|
97
|
-
const remaining = Array.from(handlersRef.current.values())
|
|
98
|
-
.filter(h => h.isActive !== false)
|
|
99
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
100
|
-
setFocusedId(remaining[0]?.id ?? null);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
}, [focusedId]);
|
|
104
|
-
|
|
105
|
-
const focus = useCallback((handlerId: string) => {
|
|
106
|
-
if (handlersRef.current.has(handlerId)) {
|
|
107
|
-
setFocusedId(handlerId);
|
|
108
|
-
}
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
const dispatch = useCallback((event: NativeKeyEvent): boolean => {
|
|
112
|
-
if (isBlocked) return false;
|
|
113
|
-
|
|
114
|
-
// Get all active handlers sorted by priority (highest first)
|
|
115
|
-
const activeHandlers = Array.from(handlersRef.current.values())
|
|
116
|
-
.filter(h => h.isActive !== false)
|
|
117
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
118
|
-
|
|
119
|
-
// First, try the focused handler
|
|
120
|
-
if (focusedId) {
|
|
121
|
-
const focused = handlersRef.current.get(focusedId);
|
|
122
|
-
if (focused?.isActive !== false && focused?.handler) {
|
|
123
|
-
const consumed = focused.handler(event);
|
|
124
|
-
if (consumed) return true;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Then try other handlers by priority
|
|
129
|
-
for (const h of activeHandlers) {
|
|
130
|
-
if (h.id === focusedId) continue; // Already tried
|
|
131
|
-
const consumed = h.handler(event);
|
|
132
|
-
if (consumed) return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return false;
|
|
136
|
-
}, [isBlocked, focusedId]);
|
|
137
|
-
|
|
138
|
-
const setBlocked = useCallback((blocked: boolean) => {
|
|
139
|
-
setIsBlocked(blocked);
|
|
140
|
-
}, []);
|
|
141
|
-
|
|
142
|
-
const value: InputContextValue = {
|
|
143
|
-
register,
|
|
144
|
-
focus,
|
|
145
|
-
focusedId,
|
|
146
|
-
dispatch,
|
|
147
|
-
isBlocked,
|
|
148
|
-
setBlocked,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<InputContext.Provider value={value}>
|
|
153
|
-
{children}
|
|
154
|
-
</InputContext.Provider>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ============================================
|
|
159
|
-
// CONVENIENCE HOOKS
|
|
160
|
-
// ============================================
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Hook to register an input handler with the centralized system
|
|
164
|
-
* Automatically unregisters on unmount
|
|
165
|
-
*/
|
|
166
|
-
export function useInputRegistration(
|
|
167
|
-
id: string,
|
|
168
|
-
handler: InputHandler,
|
|
169
|
-
options: { priority?: number; isActive?: boolean } = {}
|
|
170
|
-
) {
|
|
171
|
-
const { register } = useInputContext();
|
|
172
|
-
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
return register({
|
|
175
|
-
id,
|
|
176
|
-
handler,
|
|
177
|
-
priority: options.priority,
|
|
178
|
-
isActive: options.isActive,
|
|
179
|
-
});
|
|
180
|
-
}, [id, handler, options.priority, options.isActive, register]);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Alias for convenience
|
|
184
|
-
export const useInputHandler = useInputRegistration;
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Hook to receive focus for a handler
|
|
188
|
-
*/
|
|
189
|
-
export function useInputFocus(handlerId: string) {
|
|
190
|
-
const { focus, focusedId } = useInputContext();
|
|
191
|
-
const isFocused = focusedId === handlerId;
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
isFocused,
|
|
195
|
-
focus: () => focus(handlerId),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Hook for components that need to block input
|
|
201
|
-
*/
|
|
202
|
-
export function useInputBlock() {
|
|
203
|
-
const { isBlocked, setBlocked } = useInputContext();
|
|
204
|
-
return { isBlocked, setBlocked };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ============================================
|
|
208
|
-
// PRIORITY CONSTANTS
|
|
209
|
-
// ============================================
|
|
210
|
-
|
|
211
|
-
export const InputPriority = {
|
|
212
|
-
/** Normal components */
|
|
213
|
-
DEFAULT: 0,
|
|
214
|
-
/** Focused input fields */
|
|
215
|
-
INPUT: 10,
|
|
216
|
-
/** Selectable lists */
|
|
217
|
-
LIST: 20,
|
|
218
|
-
/** Sidebar */
|
|
219
|
-
SIDEBAR: 30,
|
|
220
|
-
/** Modal dialogs */
|
|
221
|
-
MODAL: 100,
|
|
222
|
-
/** Toast notifications (can dismiss) */
|
|
223
|
-
TOAST: 110,
|
|
224
|
-
/** System-level (Ctrl+C, etc.) */
|
|
225
|
-
SYSTEM: 1000,
|
|
226
|
-
} as const;
|
|
227
|
-
|
|
228
|
-
// ============================================
|
|
229
|
-
// EXPORTS
|
|
230
|
-
// ============================================
|
|
231
|
-
|
|
232
|
-
export default InputContext;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/** @jsx React.createElement */
|
|
2
|
-
/** @jsxFrag React.Fragment */
|
|
3
|
-
/**
|
|
4
|
-
* Input Field Component - Simple plain text input
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useSyncExternalStore } from "react";
|
|
8
|
-
import { Text } from "ink";
|
|
9
|
-
import type { InputFieldProps } from "./types.js";
|
|
10
|
-
|
|
11
|
-
// Global input display state - bypasses React batching
|
|
12
|
-
let globalInputValue = "";
|
|
13
|
-
let globalCursorPos = 0;
|
|
14
|
-
const listeners = new Set<() => void>();
|
|
15
|
-
|
|
16
|
-
export function setGlobalInput(value: string, cursor: number) {
|
|
17
|
-
globalInputValue = value;
|
|
18
|
-
globalCursorPos = cursor;
|
|
19
|
-
listeners.forEach((listener) => listener());
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getGlobalInput() {
|
|
23
|
-
return { value: globalInputValue, cursorPos: globalCursorPos };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function subscribe(callback: () => void) {
|
|
27
|
-
listeners.add(callback);
|
|
28
|
-
return () => listeners.delete(callback);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getSnapshot() {
|
|
32
|
-
return { value: globalInputValue, cursorPos: globalCursorPos };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Simple input field - just text, no box
|
|
37
|
-
*/
|
|
38
|
-
export function InputField({ placeholder, isActive }: Omit<InputFieldProps, "value" | "cursorPos">) {
|
|
39
|
-
const { value, cursorPos } = useSyncExternalStore(subscribe, getSnapshot);
|
|
40
|
-
|
|
41
|
-
const beforeCursor = value.slice(0, cursorPos);
|
|
42
|
-
const afterCursor = value.slice(cursorPos);
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<Text>
|
|
46
|
-
<Text bold color={isActive ? "cyan" : "gray"}>You: </Text>
|
|
47
|
-
{value.length > 0 ? (
|
|
48
|
-
<>
|
|
49
|
-
{beforeCursor}
|
|
50
|
-
<Text backgroundColor="cyan" color="black">
|
|
51
|
-
{cursorPos < value.length ? value[cursorPos] : " "}
|
|
52
|
-
</Text>
|
|
53
|
-
{afterCursor}
|
|
54
|
-
</>
|
|
55
|
-
) : (
|
|
56
|
-
<Text dimColor>{placeholder}</Text>
|
|
57
|
-
)}
|
|
58
|
-
</Text>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export default InputField;
|