create-theokit 1.23.4 → 1.23.6
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 +1 -1
- package/templates/surfaces/desktop/.github/workflows/release.yml.tmpl +83 -0
- package/templates/surfaces/desktop/README-surface.md.tmpl +14 -0
- package/templates/surfaces/desktop/frontend/src/App.tsx.tmpl +82 -13
- package/templates/surfaces/desktop/src-tauri/capabilities/default.json +7 -1
- package/templates/surfaces/desktop/src-tauri/tauri.conf.json.tmpl +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Cross-platform desktop release — builds {{name}} for macOS (Apple Silicon + Intel),
|
|
2
|
+
# Linux (x64 + arm64) and Windows, then drafts a GitHub release with the installers.
|
|
3
|
+
#
|
|
4
|
+
# WHY a matrix of native runners: Tauri does NOT support cross-compiling between operating
|
|
5
|
+
# systems (macOS/Linux bundles REQUIRE their own host; only Windows-NSIS-from-Linux is a
|
|
6
|
+
# last-resort exception). Each target therefore builds on its own runner — the documented
|
|
7
|
+
# best practice: https://v2.tauri.app/distribute/pipelines/github/
|
|
8
|
+
#
|
|
9
|
+
# ⚠️ PREREQUISITE — the sidecar must be a real binary, not the dev shim.
|
|
10
|
+
# `tauri build` runs `scripts/build-sidecar.mjs`, which in DEV writes a POSIX shell shim
|
|
11
|
+
# that execs this repo's local `tsx` (absolute build-time paths, requires Node on the user's
|
|
12
|
+
# machine). That shim is NOT portable to end users, and `build-sidecar.mjs` intentionally
|
|
13
|
+
# FAILS on Windows (Tauri needs a `.exe`). Before this workflow can ship a working app you
|
|
14
|
+
# MUST compile `sidecar/sidecar.ts` to a self-contained per-platform binary at
|
|
15
|
+
# `src-tauri/binaries/theo-sidecar-<target-triple>[.exe]`. See README-surface.md § Packaging.
|
|
16
|
+
#
|
|
17
|
+
# Trigger: push to the `release` branch, or run manually (Actions → Run workflow).
|
|
18
|
+
name: release-desktop
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
push:
|
|
23
|
+
branches: [release]
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
build:
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
strategy:
|
|
30
|
+
fail-fast: false
|
|
31
|
+
matrix:
|
|
32
|
+
include:
|
|
33
|
+
- platform: macos-latest # Apple Silicon → .app / .dmg
|
|
34
|
+
args: --target aarch64-apple-darwin
|
|
35
|
+
- platform: macos-latest # Intel → .app / .dmg
|
|
36
|
+
args: --target x86_64-apple-darwin
|
|
37
|
+
- platform: ubuntu-22.04 # Linux x64 → .deb / .AppImage / .rpm
|
|
38
|
+
args: ''
|
|
39
|
+
- platform: ubuntu-22.04-arm # Linux arm64
|
|
40
|
+
args: ''
|
|
41
|
+
- platform: windows-latest # Windows → .exe (NSIS) / .msi
|
|
42
|
+
args: ''
|
|
43
|
+
|
|
44
|
+
runs-on: ${{ matrix.platform }}
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
|
|
48
|
+
- name: Install Linux system dependencies
|
|
49
|
+
if: startsWith(matrix.platform, 'ubuntu')
|
|
50
|
+
run: |
|
|
51
|
+
sudo apt-get update
|
|
52
|
+
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
|
53
|
+
|
|
54
|
+
- name: Setup Node
|
|
55
|
+
uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: lts/*
|
|
58
|
+
cache: npm
|
|
59
|
+
|
|
60
|
+
- name: Install Rust (stable)
|
|
61
|
+
uses: dtolnay/rust-toolchain@stable
|
|
62
|
+
with:
|
|
63
|
+
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
|
64
|
+
|
|
65
|
+
- name: Rust cache
|
|
66
|
+
uses: swatinem/rust-cache@v2
|
|
67
|
+
with:
|
|
68
|
+
workspaces: ./src-tauri -> target
|
|
69
|
+
|
|
70
|
+
- name: Install frontend dependencies
|
|
71
|
+
run: npm install
|
|
72
|
+
|
|
73
|
+
- name: Build + release with Tauri
|
|
74
|
+
uses: tauri-apps/tauri-action@v0
|
|
75
|
+
env:
|
|
76
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
77
|
+
with:
|
|
78
|
+
tagName: {{name}}-v__VERSION__
|
|
79
|
+
releaseName: '{{name}} v__VERSION__'
|
|
80
|
+
releaseBody: 'See the assets below to download and install this version.'
|
|
81
|
+
releaseDraft: true
|
|
82
|
+
prerelease: false
|
|
83
|
+
args: ${{ matrix.args }}
|
|
@@ -53,3 +53,17 @@ externalBin that a shell shim cannot satisfy (`build:sidecar` fails loud on win3
|
|
|
53
53
|
before building. Options: **Node SEA** (single-executable app — runs under Node, where the stream works),
|
|
54
54
|
or `bun build --compile`/`pkg` (verify the ai-sdk token stream survives your bundler first). Replace the
|
|
55
55
|
`build:sidecar` script with your compile command and keep the same output path.
|
|
56
|
+
|
|
57
|
+
### Cross-platform release (macOS · Linux · Windows)
|
|
58
|
+
|
|
59
|
+
Tauri does **not** cross-compile between operating systems — macOS/Linux bundles must be built on their own
|
|
60
|
+
host, and only Windows-NSIS-from-Linux is a fragile last resort ([docs](https://v2.tauri.app/distribute/pipelines/github/)).
|
|
61
|
+
The best practice is a **matrix of native CI runners**, so this surface ships
|
|
62
|
+
`.github/workflows/release.yml`: on push to a `release` branch (or a manual run) it builds `{{name}}` on
|
|
63
|
+
`macos-latest` (Apple Silicon **and** Intel), `ubuntu-22.04` (x64 + arm64) and `windows-latest`, then drafts
|
|
64
|
+
a GitHub release with the installers (`.dmg`/`.app`, `.deb`/`.AppImage`/`.rpm`, `.msi`/NSIS `.exe`).
|
|
65
|
+
|
|
66
|
+
Before that workflow can ship a **working** app, resolve the sidecar gap above (the CI `tauri build` runs the
|
|
67
|
+
same `build:sidecar`, which fails on the Windows runner and produces a non-portable shim on macOS/Linux). For
|
|
68
|
+
public distribution you also need **code signing** — macOS notarization (Apple Developer ID) and a Windows
|
|
69
|
+
signing certificate; see [Tauri › Distribute](https://v2.tauri.app/distribute/).
|
|
@@ -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,25 @@ 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
|
+
const appWindow = (
|
|
46
|
+
globalThis as {
|
|
47
|
+
__TAURI__?: {
|
|
48
|
+
window?: {
|
|
49
|
+
getCurrentWindow: () => {
|
|
50
|
+
minimize: () => Promise<void>
|
|
51
|
+
toggleMaximize: () => Promise<void>
|
|
52
|
+
close: () => Promise<void>
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
).__TAURI__?.window
|
|
58
|
+
const minimizeWindow = (): void => void appWindow?.getCurrentWindow().minimize()
|
|
59
|
+
const maximizeWindow = (): void => void appWindow?.getCurrentWindow().toggleMaximize()
|
|
60
|
+
const closeWindow = (): void => void appWindow?.getCurrentWindow().close()
|
|
61
|
+
|
|
43
62
|
/** The agent's opening line (from `shared/agent.ts`) — so the conversation starts warm instead of empty. */
|
|
44
63
|
const GREETING: UIMessage = {
|
|
45
64
|
id: 'greeting',
|
|
@@ -55,24 +74,30 @@ const STARTERS: QuickAction[] = [
|
|
|
55
74
|
]
|
|
56
75
|
|
|
57
76
|
// App-level layout in inline styles (the desktop bundle has no Tailwind). Colors read the theme CSS vars
|
|
58
|
-
// `@theokit/ui` sets
|
|
77
|
+
// `@theokit/ui` sets — and those vars are `oklch()` colors, so wrap them in `color-mix(in oklch, …)` for
|
|
78
|
+
// alpha (NOT `hsl(var(--x) / a)`, which is invalid CSS and silently renders transparent).
|
|
59
79
|
const styles = {
|
|
60
80
|
page: {
|
|
61
81
|
display: 'flex',
|
|
62
82
|
flexDirection: 'column',
|
|
63
83
|
height: '100vh',
|
|
64
|
-
background: '
|
|
65
|
-
color: '
|
|
84
|
+
background: 'var(--background)',
|
|
85
|
+
color: 'var(--foreground)',
|
|
66
86
|
},
|
|
67
87
|
header: {
|
|
68
88
|
display: 'flex',
|
|
69
89
|
alignItems: 'center',
|
|
70
90
|
justifyContent: 'space-between',
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
// The frameless title bar is its own DISTINCT band: the `--muted` surface reads clearly apart
|
|
92
|
+
// from the content background (adapts to light/dark), with a border to seal the strip.
|
|
93
|
+
background: 'var(--muted)',
|
|
94
|
+
borderBottom: '1px solid var(--border)',
|
|
95
|
+
// Slim title bar — thin vertical padding keeps the strip compact.
|
|
96
|
+
padding: '3px 12px',
|
|
73
97
|
},
|
|
74
98
|
brand: { display: 'inline-flex', alignItems: 'center', gap: 8 },
|
|
75
|
-
brandName: { fontWeight: 600, fontSize:
|
|
99
|
+
brandName: { fontWeight: 600, fontSize: 12, letterSpacing: '-0.01em' },
|
|
100
|
+
headerRight: { display: 'inline-flex', alignItems: 'center', gap: 2 },
|
|
76
101
|
scroll: { flex: 1, overflowY: 'auto', minHeight: 0 },
|
|
77
102
|
column: {
|
|
78
103
|
margin: '0 auto',
|
|
@@ -84,8 +109,8 @@ const styles = {
|
|
|
84
109
|
padding: 24,
|
|
85
110
|
},
|
|
86
111
|
composerBar: {
|
|
87
|
-
borderTop: '1px solid
|
|
88
|
-
background: '
|
|
112
|
+
borderTop: '1px solid color-mix(in oklch, var(--border) 60%, transparent)',
|
|
113
|
+
background: 'color-mix(in oklch, var(--background) 50%, transparent)',
|
|
89
114
|
},
|
|
90
115
|
composerInner: { margin: '0 auto', width: '100%', maxWidth: 768, padding: '16px 24px' },
|
|
91
116
|
} satisfies Record<string, CSSProperties>
|
|
@@ -115,13 +140,57 @@ export function App(): ReactElement {
|
|
|
115
140
|
|
|
116
141
|
return (
|
|
117
142
|
<TheoUIProvider>
|
|
143
|
+
{/* Window controls need :hover feedback — inline styles can't do pseudo-classes, so the button
|
|
144
|
+
appearance (incl. hover, and the classic red close) lives in this small stylesheet. */}
|
|
145
|
+
<style>{`
|
|
146
|
+
.tk-winbtn {
|
|
147
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
148
|
+
width: 20px; height: 20px; border: none; border-radius: 6px;
|
|
149
|
+
background: transparent; color: var(--muted-foreground); cursor: pointer;
|
|
150
|
+
transition: background 0.12s ease, color 0.12s ease;
|
|
151
|
+
}
|
|
152
|
+
.tk-winbtn:hover { background: color-mix(in oklch, var(--foreground) 12%, transparent); color: var(--foreground); }
|
|
153
|
+
.tk-winbtn--close:hover { background: #e5484d; color: #fff; }
|
|
154
|
+
`}</style>
|
|
118
155
|
<div style={styles.page}>
|
|
119
|
-
|
|
156
|
+
{/* Frameless title bar: `data-tauri-drag-region` makes the whole strip drag the window; the
|
|
157
|
+
background is the `--muted` band, so the bar reads as a distinct strip. Interactive
|
|
158
|
+
children (theme switcher, window buttons) still receive clicks. */}
|
|
159
|
+
<header style={styles.header} data-tauri-drag-region>
|
|
120
160
|
<span style={styles.brand}>
|
|
121
|
-
<Bot size={18} style={{ color: '
|
|
122
|
-
<span style={styles.brandName}>{{name}}
|
|
161
|
+
<Bot size={18} style={{ color: 'var(--primary)' }} aria-hidden />
|
|
162
|
+
<span style={styles.brandName}>{{name}}</span>
|
|
163
|
+
</span>
|
|
164
|
+
<span style={styles.headerRight}>
|
|
165
|
+
<ThemeSwitcher />
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
className="tk-winbtn"
|
|
169
|
+
onClick={minimizeWindow}
|
|
170
|
+
aria-label="Minimize"
|
|
171
|
+
title="Minimize"
|
|
172
|
+
>
|
|
173
|
+
<Minus size={15} />
|
|
174
|
+
</button>
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
className="tk-winbtn"
|
|
178
|
+
onClick={maximizeWindow}
|
|
179
|
+
aria-label="Maximize"
|
|
180
|
+
title="Maximize"
|
|
181
|
+
>
|
|
182
|
+
<Square size={12} />
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
className="tk-winbtn tk-winbtn--close"
|
|
187
|
+
onClick={closeWindow}
|
|
188
|
+
aria-label="Close"
|
|
189
|
+
title="Close"
|
|
190
|
+
>
|
|
191
|
+
<X size={15} />
|
|
192
|
+
</button>
|
|
123
193
|
</span>
|
|
124
|
-
<ThemeSwitcher />
|
|
125
194
|
</header>
|
|
126
195
|
|
|
127
196
|
<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",
|