create-theokit 1.2.2 → 1.2.4

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.2.2",
3
+ "version": "1.2.4",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "theokit": "^0.30.1",
19
- "@theokit/agents": "^0.35.1",
19
+ "@theokit/agents": "^0.35.2",
20
20
  "@theokit/sdk": "^2.13.0",
21
21
  "@theokit/ui": "^1.0.0",
22
22
  "@usetheo/ui": "^0.14.0",
@@ -1,21 +1,35 @@
1
1
  import { Box, Text, useApp, useInput } from 'ink'
2
- import { useState, type ReactElement } from 'react'
3
- import { ChatThread } from '@theokit/tui'
2
+ import { type ReactElement } from 'react'
3
+ import {
4
+ AgentStreaming,
5
+ AppStatusBar,
6
+ ChatComposer,
7
+ ChatThread,
8
+ TheoTUIProvider,
9
+ useTurnElapsed,
10
+ WelcomeBanner,
11
+ } from '@theokit/tui'
4
12
  import { uiMessagesToChatThread } from '@theokit/tui/ai-sdk'
5
13
  import { InProcessTransport, useAgent } from 'theokit/client'
6
- import { streamAgentTurnInProcess } from 'theokit/server'
14
+ import { streamAgentTurnInProcess } from 'theokit/server/agent'
7
15
 
8
16
  import * as chatAgent from '../agents/chat.js'
9
17
 
10
- /** Resolve the provider key from the environment (OpenRouter / Anthropic). */
11
- const apiKey = (): string => process.env.OPENROUTER_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? ''
18
+ /** Shown in the status bar — keep in sync with the model in `agents/chat.ts`. */
19
+ const MODEL = 'openai/gpt-4o-mini'
20
+ const CWD = process.cwd()
21
+
22
+ /** Resolve the provider key from the environment (OpenRouter / Anthropic / OpenAI). */
23
+ const apiKey = (): string =>
24
+ process.env.OPENROUTER_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY ?? ''
12
25
 
13
26
  /**
14
- * M46 — the terminal surface renders with `@theokit/tui`. `useAgent` (M41) drives an `InProcessTransport`
15
- * whose runner binds the in-process seam (`streamAgentTurnInProcess`) — the SAME hook the web surface uses.
16
- * The unified client's `UIMessage[]` is projected onto `<ChatThread>` via the `@theokit/tui/ai-sdk` adapter
17
- * (`uiMessagesToChatThread`), so the terminal shows the exact same conversation the web/desktop surfaces do,
18
- * rendered with the terminal-native primitives instead of a hand-rolled `<Text>` concat.
27
+ * M46 — the terminal surface, composed from `@theokit/tui` the way a Claude Code / OpenCode / Codex CLI is:
28
+ * a `WelcomeBanner` header, a scrolling `<ChatThread>`, a live `<AgentStreaming>` indicator, the Claude-Code
29
+ * bordered `<ChatComposer>` (slash-commands, `@` file mentions, Alt+Enter newline — all shipped), and a
30
+ * persistent `<AppStatusBar>` footer (model · cwd · state). The agent is driven by the UNIFIED `useAgent`
31
+ * hook (M41) over the in-process seam — its `UIMessage[]` projected onto `<ChatThread>` by the
32
+ * `@theokit/tui/ai-sdk` adapter. We compose shipped primitives: no hand-rolled input, no bespoke reader.
19
33
  */
20
34
  const transport = new InProcessTransport({
21
35
  run: (input) => streamAgentTurnInProcess(chatAgent, apiKey(), input),
@@ -23,39 +37,53 @@ const transport = new InProcessTransport({
23
37
 
24
38
  export function App(): ReactElement {
25
39
  const agent = useAgent<{ message: string }>(transport)
26
- const [input, setInput] = useState('')
40
+ const streaming = agent.status === 'streaming'
41
+ const elapsed = useTurnElapsed(streaming)
27
42
  const { exit } = useApp()
28
43
 
29
- useInput((char, key) => {
30
- if (key.escape) {
31
- exit()
32
- } else if (key.return) {
33
- if (input.trim().length > 0) agent.send({ message: input })
34
- setInput('')
35
- } else if (key.backspace || key.delete) {
36
- setInput((s) => s.slice(0, -1))
37
- } else if (char && !key.ctrl && !key.meta) {
38
- setInput((s) => s + char)
39
- }
44
+ // Esc cancels a running turn, or quits when idle (the Claude Code affordance).
45
+ useInput((_input, key) => {
46
+ if (!key.escape) return
47
+ if (streaming) agent.abort()
48
+ else exit()
40
49
  })
41
50
 
42
- // The unified client's UIMessage[] → this package's ChatThreadMessage[] (the ai-sdk adapter, Step A).
43
- const messages = uiMessagesToChatThread(agent.messages)
51
+ const handleSubmit = (text: string): void => {
52
+ const trimmed = text.trim()
53
+ if (trimmed.length === 0) return
54
+ if (trimmed === '/clear') {
55
+ agent.reset()
56
+ return
57
+ }
58
+ agent.send({ message: trimmed })
59
+ }
44
60
 
45
61
  return (
46
- <Box flexDirection="column" padding={1}>
47
- <ChatThread
48
- messages={messages}
49
- header={
50
- <Text color="cyan">◆ {{name}} — TheoKit agent (terminal). Type a message + Enter · Esc to quit.</Text>
51
- }
52
- />
53
- {agent.status === 'streaming' ? <Text color="yellow">…thinking</Text> : null}
54
- {agent.error ? <Text color="red">⚠ {agent.error.message}</Text> : null}
55
- <Box marginTop={1}>
56
- <Text color="green">› </Text>
57
- <Text>{input}</Text>
62
+ <TheoTUIProvider theme={{ base: 'dark', override: { accent: 'cyan' } }}>
63
+ <Box flexDirection="column">
64
+ <WelcomeBanner
65
+ name="{{name}}"
66
+ tagline="Your agent, in the terminal — built with TheoKit."
67
+ hints={['Enter sends · Alt+Enter for a newline', '/clear resets · esc cancels a turn or quits']}
68
+ >
69
+ <Text dimColor>cwd: {CWD}</Text>
70
+ </WelcomeBanner>
71
+
72
+ <ChatThread messages={uiMessagesToChatThread(agent.messages)} />
73
+
74
+ {streaming ? <AgentStreaming thought="Thinking…" elapsedSeconds={elapsed} showCancelHint /> : null}
75
+ {agent.error ? <Text color="red">⚠ {agent.error.message}</Text> : null}
76
+
77
+ <ChatComposer
78
+ placeholder="Ask your agent… (Enter sends · / commands · @ files)"
79
+ bordered
80
+ hint="Enter sends · Alt+Enter newline · /clear resets · esc cancels"
81
+ commands={[{ name: 'clear', description: 'clear the conversation' }]}
82
+ onSubmit={handleSubmit}
83
+ />
84
+
85
+ <AppStatusBar model={MODEL} cwd={CWD} state={streaming ? 'streaming' : 'idle'} />
58
86
  </Box>
59
- </Box>
87
+ </TheoTUIProvider>
60
88
  )
61
89
  }