zuzu 0.0.1-java
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +374 -0
- data/bin/setup +74 -0
- data/bin/zuzu +128 -0
- data/lib/zuzu/agent.rb +112 -0
- data/lib/zuzu/agent_fs.rb +224 -0
- data/lib/zuzu/app.rb +194 -0
- data/lib/zuzu/channels/base.rb +21 -0
- data/lib/zuzu/channels/in_app.rb +11 -0
- data/lib/zuzu/channels/whatsapp.rb +63 -0
- data/lib/zuzu/config.rb +51 -0
- data/lib/zuzu/llamafile_manager.rb +68 -0
- data/lib/zuzu/llm_client.rb +82 -0
- data/lib/zuzu/memory.rb +47 -0
- data/lib/zuzu/store.rb +86 -0
- data/lib/zuzu/tool_registry.rb +43 -0
- data/lib/zuzu/tools/file_tool.rb +25 -0
- data/lib/zuzu/tools/shell_tool.rb +31 -0
- data/lib/zuzu/tools/web_tool.rb +17 -0
- data/lib/zuzu/version.rb +5 -0
- data/lib/zuzu.rb +24 -0
- data/templates/app.rb +22 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ae81f9e580d8c3e0efd863e9f56b6719a9cd08a092dc6b998ab8bf703ddae000
|
|
4
|
+
data.tar.gz: 287c409a0507b53f2f313da7fb2b00466180680641b36b81cc1d23b9046654db
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 500c75552b9483fa2414a17971e3cce3d3c02f12ee0e02f63d723165e7ed1ba9c4b2f75b36d8520d4cbc52127331d11560e2bbca0eca4533e02c426e56e1e597
|
|
7
|
+
data.tar.gz: aa6f574afac309ef301b12632ee791521ee8ec287fcd4d933472c19cd7adc354e4d92c3f42871dc964ae343dde436ce0be01e6e83166ac7784f2389b9c3684db
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhishek Parolkar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Zuzu
|
|
2
|
+
|
|
3
|
+
**Local-first agentic desktop apps with JRuby.**
|
|
4
|
+
|
|
5
|
+
Zuzu is a framework for building privacy-respecting AI desktop assistants that
|
|
6
|
+
run entirely on your machine. No cloud APIs required — your data stays local.
|
|
7
|
+
|
|
8
|
+
<video src="https://raw.githubusercontent.com/parolkar/zuzu/refs/heads/main/docs/demo/zuzu_quick_demo_01.mp4" controls width="100%"></video>
|
|
9
|
+
[Quick Demo](https://raw.githubusercontent.com/parolkar/zuzu/refs/heads/main/docs/demo/zuzu_quick_demo_01.mp4)
|
|
10
|
+
|
|
11
|
+
| Component | Technology |
|
|
12
|
+
|-----------|------------|
|
|
13
|
+
| Runtime | JRuby 10.0.3.0 (Java 21+) |
|
|
14
|
+
| GUI | Glimmer DSL for SWT |
|
|
15
|
+
| Database | SQLite via JDBC |
|
|
16
|
+
| LLM | llamafile (local inference) |
|
|
17
|
+
| Packaging | Warbler (.jar / .war) |
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Prerequisites
|
|
22
|
+
|
|
23
|
+
- **macOS** or **Linux** (Windows support is planned)
|
|
24
|
+
- **Java 21+**
|
|
25
|
+
- **rbenv** (recommended) or any JRuby version manager
|
|
26
|
+
|
|
27
|
+
### One-Command Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/parolkar/zuzu.git
|
|
31
|
+
cd zuzu
|
|
32
|
+
bin/setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`bin/setup` installs Java 21, JRuby 10.0.4.0, and all gem dependencies
|
|
36
|
+
automatically. If you prefer manual setup, see [Manual Setup](#manual-setup)
|
|
37
|
+
below.
|
|
38
|
+
|
|
39
|
+
### Create Your First App
|
|
40
|
+
|
|
41
|
+
When you run `bin/zuzu new` from the source tree, it automatically detects
|
|
42
|
+
dev mode and points the scaffolded app's Gemfile at your local checkout
|
|
43
|
+
(no published gem needed).
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bin/zuzu new my_assistant
|
|
47
|
+
cd my_assistant
|
|
48
|
+
bundle install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Download a llamafile model:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mkdir -p models
|
|
55
|
+
curl -L -o models/llava-v1.5-7b-q4.llamafile \
|
|
56
|
+
https://huggingface.co/Mozilla/llava-v1.5-7b-llamafile/resolve/main/llava-v1.5-7b-q4.llamafile
|
|
57
|
+
chmod +x models/llava-v1.5-7b-q4.llamafile
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Edit `app.rb` to point at your model:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
Zuzu.configure do |c|
|
|
64
|
+
c.app_name = 'My Assistant'
|
|
65
|
+
c.llamafile_path = File.expand_path('models/llava-v1.5-7b-q4.llamafile', __dir__)
|
|
66
|
+
c.db_path = File.expand_path('.zuzu/zuzu.db', __dir__)
|
|
67
|
+
c.port = 8080
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Launch:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# macOS (SWT requires first-thread access):
|
|
75
|
+
JRUBY_OPTS="-J-XstartOnFirstThread -J--enable-native-access=ALL-UNNAMED" bundle exec ruby app.rb
|
|
76
|
+
|
|
77
|
+
# Linux:
|
|
78
|
+
JRUBY_OPTS="-J--enable-native-access=ALL-UNNAMED" bundle exec ruby app.rb
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
You'll see a native desktop window with a file browser on the left and a chat
|
|
82
|
+
interface on the right. The llamafile model starts automatically in the
|
|
83
|
+
background.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Manual Setup
|
|
88
|
+
|
|
89
|
+
If `bin/setup` doesn't suit your workflow, follow these steps.
|
|
90
|
+
|
|
91
|
+
### 1. Install Java 21+
|
|
92
|
+
|
|
93
|
+
**macOS (Homebrew):**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
brew install openjdk@21
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Add to your shell profile (`~/.zshrc` or `~/.bash_profile`):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
|
|
103
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**macOS (SDKMAN):**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
curl -s https://get.sdkman.io | bash
|
|
110
|
+
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
|
111
|
+
sdk install java 21-tem
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Ubuntu / Debian:**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
sudo apt-get update
|
|
118
|
+
sudo apt-get install openjdk-21-jdk
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Verify:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
java -version
|
|
125
|
+
# → openjdk version "21.x.x" ...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2. Install JRuby 10.0.4.0 via rbenv
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Install rbenv if needed
|
|
132
|
+
brew install rbenv ruby-build # macOS
|
|
133
|
+
# or: https://github.com/rbenv/rbenv#installation
|
|
134
|
+
|
|
135
|
+
rbenv install jruby-10.0.4.0
|
|
136
|
+
rbenv local jruby-10.0.4.0
|
|
137
|
+
ruby -v
|
|
138
|
+
# → jruby 10.0.4.0 (ruby 3.x.x) ...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. Install Gems
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
gem install bundler
|
|
145
|
+
bundle install
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Architecture
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
┌────────────────────────────────────────────────────┐
|
|
154
|
+
│ Zuzu App (SWT) │
|
|
155
|
+
│ ┌──────────┐ ┌──────────────────────────────┐ │
|
|
156
|
+
│ │ AgentFS │ │ Chat Interface │ │
|
|
157
|
+
│ │ (files) │ │ │ │
|
|
158
|
+
│ │ │ │ User: How do I ... │ │
|
|
159
|
+
│ │ + docs/ │ │ Zuzu: Let me check ... │ │
|
|
160
|
+
│ │ a.txt │ │ │ │
|
|
161
|
+
│ └──────────┘ └──────────────────────────────┘ │
|
|
162
|
+
│ ┌──────────────────────────────┐ │
|
|
163
|
+
│ │ [ Type a message... ] [Send]│ │
|
|
164
|
+
│ └──────────────────────────────┘ │
|
|
165
|
+
└──────────────────────────┬─────────────────────────┘
|
|
166
|
+
│
|
|
167
|
+
┌────────────▼────────────┐
|
|
168
|
+
│ Agent (ReAct) │
|
|
169
|
+
│ ┌─────┐ ┌─────────┐ │
|
|
170
|
+
│ │Tools│ │ Memory │ │
|
|
171
|
+
│ └──┬──┘ └────┬────┘ │
|
|
172
|
+
└──────┼─────────┼───────┘
|
|
173
|
+
│ │
|
|
174
|
+
┌──────▼─────────▼───────┐
|
|
175
|
+
│ SQLite (JDBC) │
|
|
176
|
+
│ - fs_inodes/dentries │
|
|
177
|
+
│ - messages │
|
|
178
|
+
│ - kv_store │
|
|
179
|
+
│ - tool_calls │
|
|
180
|
+
└────────────────────────┘
|
|
181
|
+
│
|
|
182
|
+
┌────────────▼────────────┐
|
|
183
|
+
│ llamafile (local LLM) │
|
|
184
|
+
│ OpenAI-compat API │
|
|
185
|
+
│ http://127.0.0.1:8080 │
|
|
186
|
+
└─────────────────────────┘
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Project Structure
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
zuzu/
|
|
193
|
+
├── lib/zuzu/
|
|
194
|
+
│ ├── config.rb # Zuzu.configure { |c| ... }
|
|
195
|
+
│ ├── store.rb # JDBC SQLite — single connection wrapper
|
|
196
|
+
│ ├── agent_fs.rb # Virtual filesystem + KV store + audit log
|
|
197
|
+
│ ├── memory.rb # Conversation history
|
|
198
|
+
│ ├── llm_client.rb # HTTP client for llamafile
|
|
199
|
+
│ ├── llamafile_manager.rb # Subprocess lifecycle
|
|
200
|
+
│ ├── tool_registry.rb # Register / execute agent tools
|
|
201
|
+
│ ├── agent.rb # ReAct loop (think → act → observe)
|
|
202
|
+
│ ├── app.rb # Glimmer SWT desktop shell
|
|
203
|
+
│ ├── tools/
|
|
204
|
+
│ │ ├── file_tool.rb # read_file, write_file, list_directory
|
|
205
|
+
│ │ ├── shell_tool.rb # run_command (allowlisted)
|
|
206
|
+
│ │ └── web_tool.rb # http_get
|
|
207
|
+
│ └── channels/
|
|
208
|
+
│ ├── base.rb # Abstract channel interface
|
|
209
|
+
│ ├── in_app.rb # Desktop GUI (default)
|
|
210
|
+
│ └── whatsapp.rb # WhatsApp Cloud API webhook
|
|
211
|
+
├── bin/
|
|
212
|
+
│ ├── zuzu # CLI: new / start / console / version
|
|
213
|
+
│ └── setup # One-command bootstrap
|
|
214
|
+
├── templates/
|
|
215
|
+
│ └── app.rb # Scaffold template for `zuzu new`
|
|
216
|
+
├── models/ # Place llamafile models here
|
|
217
|
+
├── Gemfile
|
|
218
|
+
├── zuzu.gemspec
|
|
219
|
+
├── Rakefile
|
|
220
|
+
├── warble.rb # Warbler config for .jar packaging
|
|
221
|
+
└── app.rb # Phase 1 standalone reference
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Usage Guide
|
|
227
|
+
|
|
228
|
+
### Custom Tools
|
|
229
|
+
|
|
230
|
+
Register tools that the agent can call during conversations:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
Zuzu::ToolRegistry.register(
|
|
234
|
+
'lookup_weather',
|
|
235
|
+
'Get current weather for a city.',
|
|
236
|
+
{
|
|
237
|
+
type: 'object',
|
|
238
|
+
properties: { city: { type: 'string' } },
|
|
239
|
+
required: ['city']
|
|
240
|
+
}
|
|
241
|
+
) do |args, agent_fs|
|
|
242
|
+
# Your logic here — call an API, read a file, etc.
|
|
243
|
+
"Weather in #{args['city']}: 22°C, sunny"
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The agent's ReAct loop will automatically discover and call your tools when
|
|
248
|
+
relevant to the user's question.
|
|
249
|
+
|
|
250
|
+
### WhatsApp Channel
|
|
251
|
+
|
|
252
|
+
Enable WhatsApp so users can chat with your agent via their phone:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
Zuzu.configure do |c|
|
|
256
|
+
c.channels = ['whatsapp']
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Set environment variables:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
export WHATSAPP_TOKEN="your_cloud_api_token"
|
|
264
|
+
export WHATSAPP_PHONE_ID="your_phone_number_id"
|
|
265
|
+
export WHATSAPP_PORT=9292 # optional, defaults to 9292
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The WhatsApp channel starts a WEBrick webhook server that receives messages
|
|
269
|
+
from the WhatsApp Cloud API and replies using the same agent.
|
|
270
|
+
|
|
271
|
+
### Virtual Filesystem (AgentFS)
|
|
272
|
+
|
|
273
|
+
The agent operates in a sandboxed SQLite-backed filesystem:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
store = Zuzu::Store.new
|
|
277
|
+
fs = Zuzu::AgentFS.new(store)
|
|
278
|
+
|
|
279
|
+
fs.write_file('/notes/todo.txt', 'Buy groceries')
|
|
280
|
+
fs.read_file('/notes/todo.txt') # → "Buy groceries"
|
|
281
|
+
fs.list_dir('/notes') # → ["todo.txt"]
|
|
282
|
+
fs.mkdir('/docs')
|
|
283
|
+
fs.exists?('/notes/todo.txt') # → true
|
|
284
|
+
|
|
285
|
+
# Key-value store
|
|
286
|
+
fs.kv_set('last_query', 'weather in Tokyo')
|
|
287
|
+
fs.kv_get('last_query') # → "weather in Tokyo"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Packaging as .jar
|
|
291
|
+
|
|
292
|
+
Use Warbler to create a standalone Java archive:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
gem install warbler
|
|
296
|
+
warble jar
|
|
297
|
+
java -XstartOnFirstThread -jar zuzu-app.jar # macOS
|
|
298
|
+
java -jar zuzu-app.jar # Linux
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Configuration Reference
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
Zuzu.configure do |c|
|
|
307
|
+
c.app_name = 'My Agent' # Window title
|
|
308
|
+
c.llamafile_path = 'models/llava-v1.5-7b-q4.llamafile' # Path to llamafile binary
|
|
309
|
+
c.db_path = '.zuzu/zuzu.db' # SQLite database location
|
|
310
|
+
c.port = 8080 # llamafile API port
|
|
311
|
+
c.model = 'LLaMA_CPP' # Model identifier
|
|
312
|
+
c.channels = ['whatsapp'] # Extra channels to enable
|
|
313
|
+
c.log_level = :info # :debug, :info, :warn, :error
|
|
314
|
+
c.window_width = 860 # Window width in pixels
|
|
315
|
+
c.window_height = 620 # Window height in pixels
|
|
316
|
+
end
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## CLI Reference
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
zuzu new APP_NAME Scaffold a new Zuzu application
|
|
325
|
+
zuzu start Launch the Zuzu app in the current directory
|
|
326
|
+
zuzu console Open an IRB session with Zuzu loaded
|
|
327
|
+
zuzu version Print the Zuzu version
|
|
328
|
+
zuzu help Show this message
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Troubleshooting
|
|
334
|
+
|
|
335
|
+
### macOS: "SWT main thread" error
|
|
336
|
+
|
|
337
|
+
SWT on macOS requires the main thread. Always launch with:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
JRUBY_OPTS="-J-XstartOnFirstThread -J--enable-native-access=ALL-UNNAMED" bundle exec ruby app.rb
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### llamafile won't start
|
|
344
|
+
|
|
345
|
+
- Ensure the file is executable: `chmod +x models/your-model.llamafile`
|
|
346
|
+
- Check `models/llama.log` for errors.
|
|
347
|
+
- Verify port 8080 isn't already in use: `lsof -i :8080`
|
|
348
|
+
|
|
349
|
+
### "java.lang.ClassNotFoundException: org.sqlite.JDBC"
|
|
350
|
+
|
|
351
|
+
Run `bundle install` to ensure `jdbc-sqlite3` is installed. The gem must be
|
|
352
|
+
loaded before any database access.
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Philosophy
|
|
358
|
+
|
|
359
|
+
- **Local-first.** Your data never leaves your machine.
|
|
360
|
+
- **Minimal code.** The entire framework is ~800 lines of Ruby.
|
|
361
|
+
- **No Docker.** Just Java, JRuby, and a single SQLite file.
|
|
362
|
+
- **Channels built in.** Every app gets in-app chat and optional WhatsApp.
|
|
363
|
+
- **Tools are Ruby blocks.** No YAML configs, no DSL ceremonies.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
MIT — see [LICENSE](LICENSE).
|
|
370
|
+
|
|
371
|
+
## Author(s)
|
|
372
|
+
|
|
373
|
+
**Abhishek Parolkar**
|
|
374
|
+
- GitHub: [@parolkar](https://github.com/parolkar)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bin/setup — one-command bootstrap for Zuzu development.
|
|
3
|
+
# Installs Java 21, JRuby, and gem dependencies.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
NC='\033[0m'
|
|
10
|
+
|
|
11
|
+
step() { printf "\n${GREEN}▸ %s${NC}\n" "$1"; }
|
|
12
|
+
warn() { printf "${YELLOW}⚠ %s${NC}\n" "$1"; }
|
|
13
|
+
|
|
14
|
+
JRUBY_VERSION="10.0.3.0"
|
|
15
|
+
|
|
16
|
+
# ── Java 21 ──────────────────────────────────────────────────────
|
|
17
|
+
step "Checking Java 21+"
|
|
18
|
+
if java -version 2>&1 | grep -q 'version "2[1-9]\|version "3'; then
|
|
19
|
+
echo " Java 21+ found."
|
|
20
|
+
else
|
|
21
|
+
warn "Java 21+ not found."
|
|
22
|
+
if [[ "$OSTYPE" == darwin* ]]; then
|
|
23
|
+
step "Installing Java 21 via Homebrew"
|
|
24
|
+
brew install openjdk@21
|
|
25
|
+
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 21)' >> ~/.zshrc
|
|
26
|
+
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
|
|
27
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
28
|
+
echo " Installed. Restart your shell or run: source ~/.zshrc"
|
|
29
|
+
elif [[ "$OSTYPE" == linux-gnu* ]]; then
|
|
30
|
+
step "Installing Java 21 via apt"
|
|
31
|
+
sudo apt-get update -qq && sudo apt-get install -y openjdk-21-jdk
|
|
32
|
+
else
|
|
33
|
+
echo " Please install Java 21+ manually: https://adoptium.net"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ── rbenv + JRuby ────────────────────────────────────────────────
|
|
39
|
+
step "Checking rbenv"
|
|
40
|
+
if ! command -v rbenv &>/dev/null; then
|
|
41
|
+
warn "rbenv not found. Installing..."
|
|
42
|
+
if [[ "$OSTYPE" == darwin* ]]; then
|
|
43
|
+
brew install rbenv ruby-build
|
|
44
|
+
else
|
|
45
|
+
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
|
|
46
|
+
export PATH="$HOME/.rbenv/bin:$PATH"
|
|
47
|
+
fi
|
|
48
|
+
eval "$(rbenv init - bash 2>/dev/null || rbenv init - zsh 2>/dev/null || true)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
step "Checking JRuby $JRUBY_VERSION"
|
|
52
|
+
if rbenv versions --bare 2>/dev/null | grep -q "^jruby-${JRUBY_VERSION}$"; then
|
|
53
|
+
echo " JRuby $JRUBY_VERSION already installed."
|
|
54
|
+
else
|
|
55
|
+
step "Installing JRuby $JRUBY_VERSION (this may take a few minutes)"
|
|
56
|
+
rbenv install --skip-existing "jruby-${JRUBY_VERSION}"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
rbenv local "jruby-${JRUBY_VERSION}"
|
|
60
|
+
eval "$(rbenv init - bash 2>/dev/null || rbenv init - zsh 2>/dev/null || true)"
|
|
61
|
+
|
|
62
|
+
# ── Gems ─────────────────────────────────────────────────────────
|
|
63
|
+
step "Installing gem dependencies"
|
|
64
|
+
gem install bundler --conservative
|
|
65
|
+
bundle install
|
|
66
|
+
|
|
67
|
+
# ── Done ─────────────────────────────────────────────────────────
|
|
68
|
+
step "Setup complete!"
|
|
69
|
+
echo ""
|
|
70
|
+
echo " Ruby: $(ruby -v)"
|
|
71
|
+
echo " Java: $(java -version 2>&1 | head -1)"
|
|
72
|
+
echo ""
|
|
73
|
+
echo " Next: Download a llamafile model into models/"
|
|
74
|
+
echo " Then run: bin/zuzu start"
|
data/bin/zuzu
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env jruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
5
|
+
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'zuzu'
|
|
8
|
+
|
|
9
|
+
USAGE = <<~USAGE
|
|
10
|
+
Zuzu #{Zuzu::VERSION} — local-first agentic desktop framework
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
zuzu new APP_NAME Scaffold a new Zuzu application
|
|
14
|
+
zuzu start Launch the Zuzu app in the current directory
|
|
15
|
+
zuzu package Package the app as a standalone .jar
|
|
16
|
+
zuzu console Open an IRB session with Zuzu loaded
|
|
17
|
+
zuzu version Print the Zuzu version
|
|
18
|
+
zuzu help Show this message
|
|
19
|
+
USAGE
|
|
20
|
+
|
|
21
|
+
# Detect whether we're running from the zuzu source tree (dev mode)
|
|
22
|
+
# by checking if the gemspec exists alongside this script.
|
|
23
|
+
ZUZU_ROOT = File.expand_path('..', __dir__)
|
|
24
|
+
DEV_MODE = ENV['ZUZU_DEV'] || File.exist?(File.join(ZUZU_ROOT, 'zuzu.gemspec'))
|
|
25
|
+
|
|
26
|
+
command = ARGV.shift
|
|
27
|
+
|
|
28
|
+
case command
|
|
29
|
+
when 'new'
|
|
30
|
+
app_name = ARGV.shift
|
|
31
|
+
abort 'Usage: zuzu new APP_NAME' unless app_name
|
|
32
|
+
|
|
33
|
+
app_dir = File.expand_path(app_name)
|
|
34
|
+
abort "Directory '#{app_name}' already exists." if File.exist?(app_dir)
|
|
35
|
+
|
|
36
|
+
template_src = File.expand_path('../templates/app.rb', __dir__)
|
|
37
|
+
|
|
38
|
+
FileUtils.mkdir_p(app_dir)
|
|
39
|
+
FileUtils.cp(template_src, File.join(app_dir, 'app.rb'))
|
|
40
|
+
|
|
41
|
+
# Generate Gemfile — uses local path when running from source tree,
|
|
42
|
+
# published gem when installed via `gem install zuzu`.
|
|
43
|
+
if DEV_MODE
|
|
44
|
+
zuzu_line = %(gem "zuzu", path: #{ZUZU_ROOT.inspect})
|
|
45
|
+
else
|
|
46
|
+
zuzu_line = %(gem "zuzu")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
File.write(File.join(app_dir, 'Gemfile'), <<~GEMFILE)
|
|
50
|
+
# frozen_string_literal: true
|
|
51
|
+
source "https://rubygems.org"
|
|
52
|
+
#{zuzu_line}
|
|
53
|
+
GEMFILE
|
|
54
|
+
|
|
55
|
+
puts "Created new Zuzu app: #{app_name}/"
|
|
56
|
+
puts " #{app_name}/app.rb"
|
|
57
|
+
puts " #{app_name}/Gemfile"
|
|
58
|
+
puts ''
|
|
59
|
+
if DEV_MODE
|
|
60
|
+
puts " (dev mode: Gemfile points to #{ZUZU_ROOT})"
|
|
61
|
+
puts ''
|
|
62
|
+
end
|
|
63
|
+
puts 'Next steps:'
|
|
64
|
+
puts " cd #{app_name}"
|
|
65
|
+
puts ' bundle install'
|
|
66
|
+
puts ' # Download a llamafile model into models/'
|
|
67
|
+
puts ' zuzu start'
|
|
68
|
+
|
|
69
|
+
when 'start'
|
|
70
|
+
entry = ['app.rb', 'lib/app.rb'].find { |f| File.exist?(f) }
|
|
71
|
+
abort 'No app.rb found. Run `zuzu start` from your app directory.' unless entry
|
|
72
|
+
load File.expand_path(entry)
|
|
73
|
+
|
|
74
|
+
when 'package'
|
|
75
|
+
entry = ['app.rb', 'lib/app.rb'].find { |f| File.exist?(f) }
|
|
76
|
+
abort 'No app.rb found. Run `zuzu package` from your app directory.' unless entry
|
|
77
|
+
|
|
78
|
+
# Copy warble.rb from zuzu source if not already present
|
|
79
|
+
warble_config = 'warble.rb'
|
|
80
|
+
unless File.exist?(warble_config)
|
|
81
|
+
src = File.expand_path('../warble.rb', __dir__)
|
|
82
|
+
if File.exist?(src)
|
|
83
|
+
FileUtils.cp(src, warble_config)
|
|
84
|
+
puts "Copied warble.rb from zuzu source."
|
|
85
|
+
else
|
|
86
|
+
abort 'warble.rb not found. Please add it to your app directory.'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Ensure warbler gem is available
|
|
91
|
+
warble_bin = `which warble 2>/dev/null`.strip
|
|
92
|
+
if warble_bin.empty?
|
|
93
|
+
puts 'Installing warbler gem...'
|
|
94
|
+
system('gem install warbler') || abort('Failed to install warbler.')
|
|
95
|
+
warble_bin = `which warble 2>/dev/null`.strip
|
|
96
|
+
abort 'warble binary not found after install. Try: gem install warbler' if warble_bin.empty?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
puts 'Packaging app as zuzu-app.jar ...'
|
|
100
|
+
puts ' (this may take a minute)'
|
|
101
|
+
success = Bundler.with_unbundled_env { system(warble_bin, 'jar') }
|
|
102
|
+
if success
|
|
103
|
+
puts ''
|
|
104
|
+
puts 'Done! Created: zuzu-app.jar'
|
|
105
|
+
puts ''
|
|
106
|
+
puts 'Run it with:'
|
|
107
|
+
puts ' java -XstartOnFirstThread -jar zuzu-app.jar # macOS'
|
|
108
|
+
puts ' java -jar zuzu-app.jar # Linux / Windows'
|
|
109
|
+
else
|
|
110
|
+
abort 'warble jar failed. Check output above for details.'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
when 'console'
|
|
114
|
+
require 'irb'
|
|
115
|
+
puts "Zuzu #{Zuzu::VERSION} console. Type `exit` to quit."
|
|
116
|
+
IRB.start(__FILE__)
|
|
117
|
+
|
|
118
|
+
when 'version'
|
|
119
|
+
puts "zuzu #{Zuzu::VERSION}"
|
|
120
|
+
|
|
121
|
+
when 'help', nil
|
|
122
|
+
puts USAGE
|
|
123
|
+
|
|
124
|
+
else
|
|
125
|
+
warn "Unknown command: #{command}"
|
|
126
|
+
puts USAGE
|
|
127
|
+
exit 1
|
|
128
|
+
end
|