create-theokit 1.23.3 → 1.23.5

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.23.3",
3
+ "version": "1.23.5",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -1 +1,7 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#6366f1"/><text x="50%" y="55%" font-family="system-ui" font-size="20" font-weight="bold" fill="white" text-anchor="middle" dominant-baseline="middle">T</text></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024" role="img" aria-label="Theo">
2
+ <rect x="0" y="0" width="1024" height="1024" rx="205" ry="205" fill="#431A7A"/>
3
+ <g fill="#ffffff" fill-rule="evenodd" transform="translate(512,512) scale(0.66) translate(-512,-512)">
4
+ <path d="M193.9,195.9 L180.9,198.9 L162.0,209.9 L149.0,224.9 L142.0,240.9 L140.0,250.9 L140.0,369.7 L142.0,379.7 L155.0,403.6 L163.0,411.6 L180.9,421.6 L193.9,424.6 L354.7,425.6 L366.7,430.6 L376.7,442.6 L379.7,452.6 L379.7,777.1 L384.7,793.1 L390.7,803.1 L411.6,821.1 L434.6,828.1 L585.4,828.1 L595.4,826.1 L608.4,821.1 L627.3,805.1 L635.3,792.1 L640.3,773.1 L639.3,475.5 L631.3,458.6 L623.4,450.6 L608.4,442.6 L503.5,439.6 L503.5,304.8 L411.6,203.9 L403.6,198.9 L392.7,195.9 Z"/>
5
+ <path d="M629.3,195.9 L612.4,202.9 L519.5,304.8 L519.5,424.6 L830.1,424.6 L848.0,419.6 L861.0,411.6 L875.0,395.7 L880.0,385.7 L884.0,369.7 L884.0,250.9 L879.0,232.9 L869.0,216.9 L857.0,205.9 L843.1,198.9 L829.1,195.9 Z"/>
6
+ </g>
7
+ </svg>
Binary file
@@ -13,7 +13,7 @@ import {
13
13
  } from '@theokit/ui'
14
14
  import { Button } from '@usetheo/ui'
15
15
  import { createTauriChannelSource, type TauriCore } from '@theokit/tauri'
16
- import { Bot, Plus, Sparkles } from 'lucide-react'
16
+ import { Bot, Minus, Plus, Sparkles, Square, X } from 'lucide-react'
17
17
  import { ChannelTransport, useAgent } from 'theokit/client'
18
18
 
19
19
  import { AGENT } from '../../shared/agent'
@@ -40,6 +40,26 @@ if (tauri === undefined) {
40
40
  }
41
41
  const transport = new ChannelTransport({ source: createTauriChannelSource(tauri.core) })
42
42
 
43
+ // The window is frameless (`decorations: false` in tauri.conf.json) so the OS title bar can't clash with
44
+ // the app background — the app owns its top bar. These drive the native window from our custom controls.
45
+ // (The drag + these calls need `core:window:*` permissions in `src-tauri/capabilities/default.json`.)
46
+ const appWindow = (
47
+ globalThis as {
48
+ __TAURI__?: {
49
+ window?: {
50
+ getCurrentWindow: () => {
51
+ minimize: () => Promise<void>
52
+ toggleMaximize: () => Promise<void>
53
+ close: () => Promise<void>
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ).__TAURI__?.window
59
+ const minimizeWindow = (): void => void appWindow?.getCurrentWindow().minimize()
60
+ const maximizeWindow = (): void => void appWindow?.getCurrentWindow().toggleMaximize()
61
+ const closeWindow = (): void => void appWindow?.getCurrentWindow().close()
62
+
43
63
  /** The agent's opening line (from `shared/agent.ts`) — so the conversation starts warm instead of empty. */
44
64
  const GREETING: UIMessage = {
45
65
  id: 'greeting',
@@ -68,11 +88,27 @@ const styles = {
68
88
  display: 'flex',
69
89
  alignItems: 'center',
70
90
  justifyContent: 'space-between',
91
+ // A faint gray wash (4% of the text color over the background) makes the frameless title bar
92
+ // read as a distinct strip in BOTH themes, without breaking the seamless feel.
93
+ background: 'hsl(var(--foreground) / 0.04)',
71
94
  borderBottom: '1px solid hsl(var(--border) / 0.6)',
72
95
  padding: '8px 16px',
73
96
  },
74
97
  brand: { display: 'inline-flex', alignItems: 'center', gap: 8 },
75
98
  brandName: { fontWeight: 600, fontSize: 13, letterSpacing: '-0.01em' },
99
+ headerRight: { display: 'inline-flex', alignItems: 'center', gap: 4 },
100
+ winBtn: {
101
+ display: 'inline-flex',
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ width: 28,
105
+ height: 28,
106
+ border: 'none',
107
+ borderRadius: 6,
108
+ background: 'transparent',
109
+ color: 'hsl(var(--muted-foreground))',
110
+ cursor: 'pointer',
111
+ },
76
112
  scroll: { flex: 1, overflowY: 'auto', minHeight: 0 },
77
113
  column: {
78
114
  margin: '0 auto',
@@ -116,12 +152,44 @@ export function App(): ReactElement {
116
152
  return (
117
153
  <TheoUIProvider>
118
154
  <div style={styles.page}>
119
- <header style={styles.header}>
155
+ {/* Frameless title bar: `data-tauri-drag-region` makes the whole strip drag the window; the
156
+ background is the app background (with a faint wash), so the bar reads as one with the
157
+ content. Interactive children (theme switcher, window buttons) still receive clicks. */}
158
+ <header style={styles.header} data-tauri-drag-region>
120
159
  <span style={styles.brand}>
121
160
  <Bot size={18} style={{ color: 'hsl(var(--primary))' }} aria-hidden />
122
161
  <span style={styles.brandName}>{{name}} — TheoKit agent</span>
123
162
  </span>
124
- <ThemeSwitcher />
163
+ <span style={styles.headerRight}>
164
+ <ThemeSwitcher />
165
+ <button
166
+ type="button"
167
+ style={styles.winBtn}
168
+ onClick={minimizeWindow}
169
+ aria-label="Minimize"
170
+ title="Minimize"
171
+ >
172
+ <Minus size={16} />
173
+ </button>
174
+ <button
175
+ type="button"
176
+ style={styles.winBtn}
177
+ onClick={maximizeWindow}
178
+ aria-label="Maximize"
179
+ title="Maximize"
180
+ >
181
+ <Square size={13} />
182
+ </button>
183
+ <button
184
+ type="button"
185
+ style={styles.winBtn}
186
+ onClick={closeWindow}
187
+ aria-label="Close"
188
+ title="Close"
189
+ >
190
+ <X size={16} />
191
+ </button>
192
+ </span>
125
193
  </header>
126
194
 
127
195
  <div style={styles.scroll}>
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "$schema": "../gen/schemas/desktop-schema.json",
3
3
  "identifier": "default",
4
- "description": "Allow the webview to run the agent turn + settle HITL via the sidecar.",
4
+ "description": "Allow the webview to run the agent turn + settle HITL via the sidecar, and drive the frameless window (drag + minimize/maximize/close).",
5
5
  "windows": ["main"],
6
6
  "permissions": [
7
7
  "core:default",
8
+ "core:window:allow-start-dragging",
9
+ "core:window:allow-minimize",
10
+ "core:window:allow-maximize",
11
+ "core:window:allow-unmaximize",
12
+ "core:window:allow-toggle-maximize",
13
+ "core:window:allow-close",
8
14
  "shell:allow-spawn",
9
15
  {
10
16
  "identifier": "shell:allow-execute",
@@ -29,6 +29,9 @@ async fn run_turn(
29
29
  .shell()
30
30
  .sidecar("theo-sidecar")
31
31
  .map_err(|e| e.to_string())?
32
+ // Forward this process's environment (the provider key — OPENROUTER_API_KEY /
33
+ // ANTHROPIC_API_KEY — lives here) so the sidecar can authenticate.
34
+ .envs(std::env::vars())
32
35
  .args([message])
33
36
  .spawn()
34
37
  .map_err(|e| e.to_string())?;
@@ -41,18 +44,22 @@ async fn run_turn(
41
44
  *guard = Some(child);
42
45
  }
43
46
 
44
- tauri::async_runtime::spawn(async move {
45
- while let Some(event) = rx.recv().await {
46
- match event {
47
- CommandEvent::Stdout(bytes) => {
48
- let line = String::from_utf8_lossy(&bytes).to_string();
49
- let _ = on_chunk.send(line);
50
- }
51
- CommandEvent::Terminated(_) => break,
52
- _ => {}
47
+ // AWAIT the stream inline — do NOT spawn a detached task and return early. The webview's
48
+ // `ChannelTransport` calls `invoke('run_turn').then(onClose)`, and `onClose` CLOSES the stream
49
+ // (further chunks are dropped). If `run_turn` resolves before the sidecar's chunks are delivered
50
+ // over the Channel, every chunk lands after the stream is closed → the agent renders no reply.
51
+ // Resolving only after the sidecar terminates makes `onClose` fire AFTER the last chunk. HITL is
52
+ // unaffected: `approve` writes to the child's stdin via shared state while this loop awaits.
53
+ while let Some(event) = rx.recv().await {
54
+ match event {
55
+ CommandEvent::Stdout(bytes) => {
56
+ let line = String::from_utf8_lossy(&bytes).to_string();
57
+ let _ = on_chunk.send(line);
53
58
  }
59
+ CommandEvent::Terminated(_) => break,
60
+ _ => {}
54
61
  }
55
- });
62
+ }
56
63
  Ok(())
57
64
  }
58
65
 
@@ -16,7 +16,8 @@
16
16
  "label": "main",
17
17
  "title": "{{name}} — TheoKit desktop",
18
18
  "width": 900,
19
- "height": 680
19
+ "height": 680,
20
+ "decorations": false
20
21
  }
21
22
  ],
22
23
  "security": {