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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zuzu
|
|
4
|
+
# Manages the llamafile subprocess lifecycle.
|
|
5
|
+
class LlamafileManager
|
|
6
|
+
STARTUP_TIMEOUT = 60 # seconds — models can take a while
|
|
7
|
+
SHUTDOWN_TIMEOUT = 5
|
|
8
|
+
|
|
9
|
+
attr_reader :pid
|
|
10
|
+
|
|
11
|
+
def initialize(path: Zuzu.config.llamafile_path, port: Zuzu.config.port)
|
|
12
|
+
@path = path
|
|
13
|
+
@port = port
|
|
14
|
+
@pid = nil
|
|
15
|
+
@client = LlmClient.new(port: @port)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start!
|
|
19
|
+
raise "llamafile already running (pid=#{@pid})" if running?
|
|
20
|
+
raise "llamafile not found: #{@path}" unless File.exist?(@path.to_s)
|
|
21
|
+
|
|
22
|
+
log_file = File.expand_path('llama.log', File.dirname(@path))
|
|
23
|
+
@pid = Process.spawn(
|
|
24
|
+
@path, '--server', '--port', @port.to_s, '--nobrowser',
|
|
25
|
+
out: log_file, err: log_file
|
|
26
|
+
)
|
|
27
|
+
Process.detach(@pid)
|
|
28
|
+
wait_for_ready
|
|
29
|
+
@pid
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stop!
|
|
33
|
+
return unless @pid
|
|
34
|
+
Process.kill('TERM', @pid) rescue nil
|
|
35
|
+
deadline = Time.now + SHUTDOWN_TIMEOUT
|
|
36
|
+
while Time.now < deadline
|
|
37
|
+
return (@pid = nil) unless alive?(@pid)
|
|
38
|
+
sleep 0.25
|
|
39
|
+
end
|
|
40
|
+
Process.kill('KILL', @pid) rescue nil
|
|
41
|
+
@pid = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def running?
|
|
45
|
+
@pid && alive?(@pid)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def wait_for_ready
|
|
51
|
+
deadline = Time.now + STARTUP_TIMEOUT
|
|
52
|
+
until @client.alive?
|
|
53
|
+
if Time.now > deadline
|
|
54
|
+
stop!
|
|
55
|
+
raise "llamafile failed to start within #{STARTUP_TIMEOUT}s"
|
|
56
|
+
end
|
|
57
|
+
sleep 1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def alive?(pid)
|
|
62
|
+
Process.kill(0, pid)
|
|
63
|
+
true
|
|
64
|
+
rescue Errno::ESRCH, Errno::EPERM, Errno::EINVAL
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module Zuzu
|
|
9
|
+
# HTTP client for llamafile's OpenAI-compatible API.
|
|
10
|
+
class LlmClient
|
|
11
|
+
def initialize(host: '127.0.0.1', port: Zuzu.config.port)
|
|
12
|
+
@host = host
|
|
13
|
+
@port = port
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def chat(messages, temperature: 0.1)
|
|
17
|
+
body = {
|
|
18
|
+
model: Zuzu.config.model,
|
|
19
|
+
messages: messages,
|
|
20
|
+
temperature: temperature
|
|
21
|
+
}
|
|
22
|
+
data = post_json('/v1/chat/completions', body)
|
|
23
|
+
msg = data.dig('choices', 0, 'message')
|
|
24
|
+
strip_eos(msg)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def alive?
|
|
28
|
+
uri = URI("http://#{@host}:#{@port}/v1/models")
|
|
29
|
+
Net::HTTP.get_response(uri).is_a?(Net::HTTPSuccess)
|
|
30
|
+
rescue StandardError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stream(messages, &block)
|
|
35
|
+
body = { model: Zuzu.config.model, messages: messages, stream: true }
|
|
36
|
+
uri = URI("http://#{@host}:#{@port}/v1/chat/completions")
|
|
37
|
+
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
|
|
38
|
+
req.body = JSON.generate(body)
|
|
39
|
+
|
|
40
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
|
41
|
+
http.request(req) do |response|
|
|
42
|
+
response.read_body do |chunk|
|
|
43
|
+
chunk.each_line do |line|
|
|
44
|
+
content = parse_sse(line)
|
|
45
|
+
block.call(content) if content
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def post_json(path, body)
|
|
55
|
+
uri = URI("http://#{@host}:#{@port}#{path}")
|
|
56
|
+
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
|
|
57
|
+
req.body = JSON.generate(body)
|
|
58
|
+
res = Net::HTTP.start(uri.host, uri.port, read_timeout: 120,
|
|
59
|
+
use_ssl: false) { |http| http.request(req) }
|
|
60
|
+
JSON.parse(res.body)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def strip_eos(msg)
|
|
64
|
+
return msg unless msg.is_a?(Hash) && msg['content'].is_a?(String)
|
|
65
|
+
msg['content'] = msg['content']
|
|
66
|
+
.gsub(/<\/?s>/, '')
|
|
67
|
+
.gsub(%r{\[/?INST\]}, '')
|
|
68
|
+
.strip
|
|
69
|
+
msg
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_sse(line)
|
|
73
|
+
line = line.strip
|
|
74
|
+
return nil if line.empty? || line == 'data: [DONE]'
|
|
75
|
+
return nil unless line.start_with?('data: ')
|
|
76
|
+
json = JSON.parse(line.sub('data: ', ''))
|
|
77
|
+
json.dig('choices', 0, 'delta', 'content')
|
|
78
|
+
rescue JSON::ParserError
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/zuzu/memory.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Zuzu
|
|
6
|
+
# Conversation memory backed by the messages table.
|
|
7
|
+
class Memory
|
|
8
|
+
def initialize(store = nil)
|
|
9
|
+
@store = store || Store.new
|
|
10
|
+
bootstrap
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def append(role, content)
|
|
14
|
+
@store.execute(
|
|
15
|
+
"INSERT INTO messages (role, content, created_at) VALUES (?, ?, ?)",
|
|
16
|
+
[role.to_s, content.to_s, (Time.now.to_f * 1000).to_i]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def recent(limit = 20)
|
|
21
|
+
@store.query_all(
|
|
22
|
+
"SELECT role, content FROM messages ORDER BY id DESC LIMIT ?", [limit]
|
|
23
|
+
).reverse
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear
|
|
27
|
+
@store.execute("DELETE FROM messages")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def context_for_llm(limit = 20)
|
|
31
|
+
recent(limit).map { |m| { 'role' => m['role'], 'content' => m['content'] } }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def bootstrap
|
|
37
|
+
@store.execute(<<~SQL)
|
|
38
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
role TEXT NOT NULL,
|
|
41
|
+
content TEXT NOT NULL,
|
|
42
|
+
created_at INTEGER NOT NULL
|
|
43
|
+
)
|
|
44
|
+
SQL
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/zuzu/store.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jdbc/sqlite3'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
Jdbc::SQLite3.load_driver
|
|
6
|
+
|
|
7
|
+
module Zuzu
|
|
8
|
+
# SQLite query layer used by AgentFS and Memory.
|
|
9
|
+
# One shared JDBC connection per db_path, guarded by a Mutex.
|
|
10
|
+
class Store
|
|
11
|
+
attr_reader :db_path
|
|
12
|
+
|
|
13
|
+
def initialize(db_path = Zuzu.config.db_path)
|
|
14
|
+
@db_path = db_path
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def connection
|
|
19
|
+
@connection ||= begin
|
|
20
|
+
FileUtils.mkdir_p(File.dirname(@db_path))
|
|
21
|
+
conn = Java::OrgSqlite::JDBC.new.connect(
|
|
22
|
+
"jdbc:sqlite:#{@db_path}", java.util.Properties.new
|
|
23
|
+
)
|
|
24
|
+
# Enable WAL for better concurrent read performance
|
|
25
|
+
stmt = conn.create_statement
|
|
26
|
+
stmt.execute_update("PRAGMA journal_mode=WAL")
|
|
27
|
+
stmt.close
|
|
28
|
+
conn
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def execute(sql, params = [])
|
|
33
|
+
@mutex.synchronize do
|
|
34
|
+
ps = connection.prepare_statement(sql)
|
|
35
|
+
params.each_with_index { |v, i| ps.set_object(i + 1, v) }
|
|
36
|
+
ps.execute_update
|
|
37
|
+
ensure
|
|
38
|
+
ps&.close
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def query_all(sql, params = [])
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
ps = connection.prepare_statement(sql)
|
|
45
|
+
params.each_with_index { |v, i| ps.set_object(i + 1, v) }
|
|
46
|
+
rs = ps.execute_query
|
|
47
|
+
meta = rs.meta_data
|
|
48
|
+
cols = (1..meta.column_count).map { |c| meta.get_column_name(c) }
|
|
49
|
+
rows = []
|
|
50
|
+
while rs.next
|
|
51
|
+
row = {}
|
|
52
|
+
cols.each { |col| row[col] = rs.get_object(col) }
|
|
53
|
+
rows << row
|
|
54
|
+
end
|
|
55
|
+
rows
|
|
56
|
+
ensure
|
|
57
|
+
rs&.close
|
|
58
|
+
ps&.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def query_one(sql, params = [])
|
|
63
|
+
query_all(sql, params).first
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def last_insert_id
|
|
67
|
+
@mutex.synchronize do
|
|
68
|
+
stmt = connection.create_statement
|
|
69
|
+
rs = stmt.execute_query("SELECT last_insert_rowid()")
|
|
70
|
+
rs.next ? rs.get_long(1) : nil
|
|
71
|
+
ensure
|
|
72
|
+
rs&.close
|
|
73
|
+
stmt&.close
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def close
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
@connection&.close
|
|
80
|
+
@connection = nil
|
|
81
|
+
end
|
|
82
|
+
rescue StandardError
|
|
83
|
+
# Best-effort close — don't raise during shutdown
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Zuzu
|
|
6
|
+
# Global registry of callable tools for the agent loop.
|
|
7
|
+
module ToolRegistry
|
|
8
|
+
Tool = Struct.new(:name, :description, :schema, :block, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
@tools = {}
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def register(name, description, schema, &block)
|
|
14
|
+
@tools[name.to_s] = Tool.new(
|
|
15
|
+
name: name.to_s, description: description,
|
|
16
|
+
schema: schema, block: block
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tools = @tools.values
|
|
21
|
+
def find(name) = @tools[name.to_s]
|
|
22
|
+
|
|
23
|
+
def to_openai_schema
|
|
24
|
+
tools.map do |t|
|
|
25
|
+
{ type: 'function', function: { name: t.name, description: t.description, parameters: t.schema } }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def execute(name, args, agent_fs)
|
|
30
|
+
tool = find(name) or return "Error: unknown tool '#{name}'"
|
|
31
|
+
started = Time.now.to_f
|
|
32
|
+
output = begin
|
|
33
|
+
tool.block.call(args, agent_fs)
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
"Error: #{e.message}"
|
|
36
|
+
end
|
|
37
|
+
finished = Time.now.to_f
|
|
38
|
+
agent_fs.record_tool_call(name, JSON.generate(args), output.to_s, started, finished)
|
|
39
|
+
output.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Zuzu::ToolRegistry.register(
|
|
4
|
+
'read_file', 'Read the contents of a file from the sandbox filesystem.',
|
|
5
|
+
{ type: 'object', properties: { path: { type: 'string', description: 'Absolute path' } }, required: ['path'] }
|
|
6
|
+
) { |args, fs| fs.read_file(args['path']) || "File not found: #{args['path']}" }
|
|
7
|
+
|
|
8
|
+
Zuzu::ToolRegistry.register(
|
|
9
|
+
'write_file', 'Write content to a file in the sandbox filesystem.',
|
|
10
|
+
{ type: 'object', properties: {
|
|
11
|
+
path: { type: 'string', description: 'Absolute path' },
|
|
12
|
+
content: { type: 'string', description: 'Content to write' }
|
|
13
|
+
}, required: %w[path content] }
|
|
14
|
+
) do |args, fs|
|
|
15
|
+
fs.write_file(args['path'], args['content'])
|
|
16
|
+
"Written #{args['content'].to_s.bytesize} bytes to #{args['path']}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Zuzu::ToolRegistry.register(
|
|
20
|
+
'list_directory', 'List entries in a directory.',
|
|
21
|
+
{ type: 'object', properties: { path: { type: 'string', description: 'Directory path (default "/")' } }, required: [] }
|
|
22
|
+
) do |args, fs|
|
|
23
|
+
entries = fs.list_dir(args['path'] || '/')
|
|
24
|
+
entries.empty? ? '(empty)' : entries.join("\n")
|
|
25
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Zuzu::ToolRegistry.register(
|
|
4
|
+
'run_command', 'Run a sandboxed command against AgentFS (NOT the host filesystem).',
|
|
5
|
+
{ type: 'object', properties: { command: { type: 'string', description: 'Sandboxed command' } }, required: ['command'] }
|
|
6
|
+
) do |args, fs|
|
|
7
|
+
cmd = args['command'].to_s.strip
|
|
8
|
+
parts = cmd.split(/\s+/)
|
|
9
|
+
base = parts[0]
|
|
10
|
+
|
|
11
|
+
case base
|
|
12
|
+
when 'ls'
|
|
13
|
+
path = parts[1] || '/'
|
|
14
|
+
stat = fs.stat(path)
|
|
15
|
+
if stat && stat['type'] == 'file'
|
|
16
|
+
"#{path} (file, #{stat['size']} bytes)"
|
|
17
|
+
else
|
|
18
|
+
entries = fs.list_dir(path)
|
|
19
|
+
entries.empty? ? '(empty directory)' : entries.join("\n")
|
|
20
|
+
end
|
|
21
|
+
when 'cat'
|
|
22
|
+
path = parts[1]
|
|
23
|
+
path ? (fs.read_file(path) || "File not found in AgentFS: #{path}") : 'Usage: cat <path>'
|
|
24
|
+
when 'pwd'
|
|
25
|
+
'/'
|
|
26
|
+
when 'echo'
|
|
27
|
+
parts[1..].join(' ')
|
|
28
|
+
else
|
|
29
|
+
"Error: '#{base}' is not available in the AgentFS sandbox. Supported: ls, cat, pwd, echo."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
Zuzu::ToolRegistry.register(
|
|
8
|
+
'http_get', 'Fetch a URL via HTTP GET (truncated to 8 KB).',
|
|
9
|
+
{ type: 'object', properties: { url: { type: 'string', description: 'URL to fetch' } }, required: ['url'] }
|
|
10
|
+
) do |args, _fs|
|
|
11
|
+
uri = URI.parse(args['url'].to_s)
|
|
12
|
+
raise ArgumentError, 'Only http/https supported' unless %w[http https].include?(uri.scheme)
|
|
13
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
|
|
14
|
+
open_timeout: 10, read_timeout: 15,
|
|
15
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE) { |h| h.get(uri.request_uri, 'User-Agent' => 'Zuzu/1.0') }
|
|
16
|
+
res.body.to_s.encode('UTF-8', invalid: :replace, undef: :replace).slice(0, 8192)
|
|
17
|
+
end
|
data/lib/zuzu/version.rb
ADDED
data/lib/zuzu.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Zuzu — JRuby desktop framework for local-first agentic apps.
|
|
4
|
+
#
|
|
5
|
+
# Author: Abhishek Parolkar <apac.abhi@gmail.com>
|
|
6
|
+
# License: MIT
|
|
7
|
+
# Homepage: https://github.com/parolkar/zuzu
|
|
8
|
+
|
|
9
|
+
require 'zuzu/version'
|
|
10
|
+
require 'zuzu/config'
|
|
11
|
+
require 'zuzu/store'
|
|
12
|
+
require 'zuzu/agent_fs'
|
|
13
|
+
require 'zuzu/memory'
|
|
14
|
+
require 'zuzu/llm_client'
|
|
15
|
+
require 'zuzu/llamafile_manager'
|
|
16
|
+
require 'zuzu/tool_registry'
|
|
17
|
+
require 'zuzu/tools/file_tool'
|
|
18
|
+
require 'zuzu/tools/shell_tool'
|
|
19
|
+
require 'zuzu/tools/web_tool'
|
|
20
|
+
require 'zuzu/agent'
|
|
21
|
+
require 'zuzu/channels/base'
|
|
22
|
+
require 'zuzu/channels/in_app'
|
|
23
|
+
require 'zuzu/channels/whatsapp'
|
|
24
|
+
require 'zuzu/app'
|
data/templates/app.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zuzu'
|
|
4
|
+
|
|
5
|
+
# ── Configure ────────────────────────────────────────────────────
|
|
6
|
+
# Paths are automatically expanded to absolute, so relative paths work fine.
|
|
7
|
+
Zuzu.configure do |c|
|
|
8
|
+
c.app_name = 'My Assistant'
|
|
9
|
+
c.llamafile_path = File.join(__dir__, 'models', 'your-model.llamafile')
|
|
10
|
+
c.db_path = File.join(__dir__, '.zuzu', 'zuzu.db')
|
|
11
|
+
c.port = 8080
|
|
12
|
+
# c.channels = ['whatsapp']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# ── Custom Tools ─────────────────────────────────────────────────
|
|
16
|
+
Zuzu::ToolRegistry.register(
|
|
17
|
+
'greet', 'Greet a user by name.',
|
|
18
|
+
{ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }
|
|
19
|
+
) { |args, _fs| "Hello, #{args['name']}!" }
|
|
20
|
+
|
|
21
|
+
# ── Launch ───────────────────────────────────────────────────────
|
|
22
|
+
Zuzu::App.launch!(use_llamafile: true)
|
metadata
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zuzu
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: java
|
|
6
|
+
authors:
|
|
7
|
+
- Abhishek Parolkar
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: glimmer-dsl-swt
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.30'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.30'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: jdbc-sqlite3
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.46'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.46'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: webrick
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.7'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.7'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: bigdecimal
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: logger
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
description: Zuzu is a framework for building local-first, privacy-respecting desktop
|
|
83
|
+
AI assistants using JRuby, Glimmer DSL for SWT, SQLite, and llamafile.
|
|
84
|
+
email:
|
|
85
|
+
- abhishek@parolkar.com
|
|
86
|
+
executables:
|
|
87
|
+
- zuzu
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- LICENSE
|
|
92
|
+
- README.md
|
|
93
|
+
- bin/setup
|
|
94
|
+
- bin/zuzu
|
|
95
|
+
- lib/zuzu.rb
|
|
96
|
+
- lib/zuzu/agent.rb
|
|
97
|
+
- lib/zuzu/agent_fs.rb
|
|
98
|
+
- lib/zuzu/app.rb
|
|
99
|
+
- lib/zuzu/channels/base.rb
|
|
100
|
+
- lib/zuzu/channels/in_app.rb
|
|
101
|
+
- lib/zuzu/channels/whatsapp.rb
|
|
102
|
+
- lib/zuzu/config.rb
|
|
103
|
+
- lib/zuzu/llamafile_manager.rb
|
|
104
|
+
- lib/zuzu/llm_client.rb
|
|
105
|
+
- lib/zuzu/memory.rb
|
|
106
|
+
- lib/zuzu/store.rb
|
|
107
|
+
- lib/zuzu/tool_registry.rb
|
|
108
|
+
- lib/zuzu/tools/file_tool.rb
|
|
109
|
+
- lib/zuzu/tools/shell_tool.rb
|
|
110
|
+
- lib/zuzu/tools/web_tool.rb
|
|
111
|
+
- lib/zuzu/version.rb
|
|
112
|
+
- templates/app.rb
|
|
113
|
+
homepage: https://github.com/parolkar/zuzu
|
|
114
|
+
licenses:
|
|
115
|
+
- MIT
|
|
116
|
+
metadata:
|
|
117
|
+
homepage_uri: https://github.com/parolkar/zuzu
|
|
118
|
+
source_code_uri: https://github.com/parolkar/zuzu
|
|
119
|
+
bug_tracker_uri: https://github.com/parolkar/zuzu/issues
|
|
120
|
+
rdoc_options: []
|
|
121
|
+
require_paths:
|
|
122
|
+
- lib
|
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: 3.1.0
|
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '0'
|
|
133
|
+
requirements: []
|
|
134
|
+
rubygems_version: 3.7.2
|
|
135
|
+
specification_version: 4
|
|
136
|
+
summary: Local-first agentic desktop apps with JRuby.
|
|
137
|
+
test_files: []
|