create-theokit 1.13.0 → 1.14.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/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Box, Text, useApp, useInput } from 'ink'
|
|
2
|
-
import { type ReactElement } from 'react'
|
|
2
|
+
import { type ReactElement, useState } from 'react'
|
|
3
3
|
import {
|
|
4
4
|
AgentStreaming,
|
|
5
5
|
AgentTimeline,
|
|
6
6
|
AppStatusBar,
|
|
7
7
|
ChatComposer,
|
|
8
|
+
DEFAULT_COMPOSER_SHORTCUTS,
|
|
9
|
+
KeyboardHelp,
|
|
8
10
|
messagesToAgentEvents,
|
|
9
11
|
readTurnUsage,
|
|
10
12
|
TheoTUIProvider,
|
|
@@ -29,7 +31,7 @@ const GREETING: UIMessageLike = {
|
|
|
29
31
|
parts: [
|
|
30
32
|
{
|
|
31
33
|
type: 'text',
|
|
32
|
-
text: `${AGENT.greeting} Type below and press Enter · /
|
|
34
|
+
text: `${AGENT.greeting} Type below and press Enter · / for commands · @ to mention a file · ? for help.`,
|
|
33
35
|
},
|
|
34
36
|
],
|
|
35
37
|
}
|
|
@@ -57,6 +59,10 @@ export function App(): ReactElement {
|
|
|
57
59
|
const streaming = agent.status === 'streaming'
|
|
58
60
|
const elapsed = useTurnElapsed(streaming)
|
|
59
61
|
const { exit } = useApp()
|
|
62
|
+
const [showHelp, setShowHelp] = useState(false)
|
|
63
|
+
// Ctrl+C is a two-step quit (Claude Code): the first press arms the exit + shows a hint, the second
|
|
64
|
+
// quits. Any other key disarms. `render(<App/>, { exitOnCtrlC: false })` in main.tsx hands us Ctrl+C.
|
|
65
|
+
const [exitArmed, setExitArmed] = useState(false)
|
|
60
66
|
|
|
61
67
|
// The store owns the conversation (M46) — prepend the warm greeting and project to timeline events.
|
|
62
68
|
const events = messagesToAgentEvents([GREETING, ...agent.thread])
|
|
@@ -69,11 +75,24 @@ export function App(): ReactElement {
|
|
|
69
75
|
const sessionCost = usages.reduce((sum, u) => sum + (u.cost ?? 0), 0)
|
|
70
76
|
const tokens = lastUsage ? { used: lastUsage.inputTokens, limit: AGENT.contextWindow } : undefined
|
|
71
77
|
|
|
72
|
-
// Esc cancels a running turn
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
78
|
+
// Global keys. Esc cancels a running turn / closes the help panel. Ctrl+C cancels a turn, else arms →
|
|
79
|
+
// quits on a second press. The ChatComposer owns the editing keys (/, @, !, ?, history ↑↓, emacs chords).
|
|
80
|
+
useInput((input, key) => {
|
|
81
|
+
if (key.escape) {
|
|
82
|
+
if (showHelp) setShowHelp(false)
|
|
83
|
+
else if (streaming) agent.abort()
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (key.ctrl && input === 'c') {
|
|
87
|
+
if (streaming) {
|
|
88
|
+
agent.abort()
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
if (exitArmed) exit()
|
|
92
|
+
else setExitArmed(true)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
if (exitArmed) setExitArmed(false)
|
|
77
96
|
})
|
|
78
97
|
|
|
79
98
|
const handleSubmit = (text: string): void => {
|
|
@@ -83,6 +102,10 @@ export function App(): ReactElement {
|
|
|
83
102
|
agent.reset()
|
|
84
103
|
return
|
|
85
104
|
}
|
|
105
|
+
if (trimmed === '/help') {
|
|
106
|
+
setShowHelp((h) => !h)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
86
109
|
agent.send({ message: trimmed })
|
|
87
110
|
}
|
|
88
111
|
|
|
@@ -92,7 +115,7 @@ export function App(): ReactElement {
|
|
|
92
115
|
<WelcomeBanner
|
|
93
116
|
name="{{name}}"
|
|
94
117
|
tagline="Your agent, in the terminal — built with TheoKit."
|
|
95
|
-
hints={['Enter sends · Alt+Enter for a newline', '/
|
|
118
|
+
hints={['Enter sends · Alt+Enter for a newline', '/ commands · @ files · ? help · ctrl+c quits']}
|
|
96
119
|
>
|
|
97
120
|
<Text dimColor>cwd: {CWD}</Text>
|
|
98
121
|
</WelcomeBanner>
|
|
@@ -102,11 +125,21 @@ export function App(): ReactElement {
|
|
|
102
125
|
{streaming ? <AgentStreaming thought="Thinking…" elapsedSeconds={elapsed} showCancelHint /> : null}
|
|
103
126
|
{agent.error ? <Text color="red">⚠ {agent.error.message}</Text> : null}
|
|
104
127
|
|
|
128
|
+
{showHelp ? <KeyboardHelp shortcuts={DEFAULT_COMPOSER_SHORTCUTS} /> : null}
|
|
129
|
+
|
|
105
130
|
<ChatComposer
|
|
106
|
-
placeholder="Ask your agent… (Enter sends · / commands · @ files)"
|
|
131
|
+
placeholder="Ask your agent… (Enter sends · / commands · @ files · ? help)"
|
|
107
132
|
bordered
|
|
108
|
-
hint=
|
|
109
|
-
|
|
133
|
+
hint={
|
|
134
|
+
exitArmed
|
|
135
|
+
? 'Press Ctrl+C again to quit'
|
|
136
|
+
: 'Enter sends · Alt+Enter newline · / commands · @ files · ? help · esc cancels'
|
|
137
|
+
}
|
|
138
|
+
commands={[
|
|
139
|
+
{ name: 'clear', description: 'clear the conversation' },
|
|
140
|
+
{ name: 'help', description: 'toggle the keyboard shortcuts panel' },
|
|
141
|
+
]}
|
|
142
|
+
onHelpToggle={() => setShowHelp((h) => !h)}
|
|
110
143
|
onSubmit={handleSubmit}
|
|
111
144
|
/>
|
|
112
145
|
|
|
@@ -11,4 +11,6 @@ if (typeof process.loadEnvFile === 'function') {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// `exitOnCtrlC: false` so the App owns Ctrl+C — a single press cancels a running turn, a double press
|
|
15
|
+
// quits (the Claude Code affordance). Without this, Ink would hard-exit on the first Ctrl+C.
|
|
16
|
+
render(<App />, { exitOnCtrlC: false })
|