create-theokit 1.13.0 → 1.14.1

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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -15,8 +15,8 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "theokit": "^0.43.0",
19
- "@theokit/agents": "^0.42.0",
18
+ "theokit": "^0.43.1",
19
+ "@theokit/agents": "^0.43.0",
20
20
  "@theokit/sdk": "^4.0.1",
21
21
  "@theokit/ui": "^1.0.0",
22
22
  "@usetheo/ui": "^0.26.0",
@@ -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 · /clear resets · esc cancels.`,
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, or quits when idle (the Claude Code affordance).
73
- useInput((_input, key) => {
74
- if (!key.escape) return
75
- if (streaming) agent.abort()
76
- else exit()
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', '/clear resets · esc cancels a turn or quits']}
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="Enter sends · Alt+Enter newline · /clear resets · esc cancels"
109
- commands={[{ name: 'clear', description: 'clear the conversation' }]}
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
- render(<App />)
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 })