zephira 0.1.0

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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +36 -0
  4. data/Dockerfile +39 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +145 -0
  7. data/README.md +230 -0
  8. data/Rakefile +12 -0
  9. data/exe/zephira +6 -0
  10. data/lib/zephira/agent/status.rb +20 -0
  11. data/lib/zephira/agent.rb +312 -0
  12. data/lib/zephira/backends/open_ai_compatible.rb +74 -0
  13. data/lib/zephira/backends.rb +17 -0
  14. data/lib/zephira/cli.rb +41 -0
  15. data/lib/zephira/commands/about.rb +27 -0
  16. data/lib/zephira/commands/bye.rb +22 -0
  17. data/lib/zephira/commands/clear.rb +32 -0
  18. data/lib/zephira/commands/compact.rb +22 -0
  19. data/lib/zephira/commands/help.rb +22 -0
  20. data/lib/zephira/commands/history.rb +25 -0
  21. data/lib/zephira/commands/model.rb +51 -0
  22. data/lib/zephira/commands.rb +31 -0
  23. data/lib/zephira/completions/file_names.rb +22 -0
  24. data/lib/zephira/completions/slash_commands.rb +17 -0
  25. data/lib/zephira/completions.rb +22 -0
  26. data/lib/zephira/config.rb +17 -0
  27. data/lib/zephira/formatter.rb +68 -0
  28. data/lib/zephira/history.rb +117 -0
  29. data/lib/zephira/logger.rb +46 -0
  30. data/lib/zephira/models/base_model.rb +143 -0
  31. data/lib/zephira/models/chat_gpt41.rb +15 -0
  32. data/lib/zephira/models/chat_gpt41_mini.rb +15 -0
  33. data/lib/zephira/models/claude_35_sonnet.rb +15 -0
  34. data/lib/zephira/models/gpt_5_4.rb +15 -0
  35. data/lib/zephira/models/gpt_5_5.rb +15 -0
  36. data/lib/zephira/models/gpt_o4_mini.rb +15 -0
  37. data/lib/zephira/models/llama4.rb +15 -0
  38. data/lib/zephira/models.rb +19 -0
  39. data/lib/zephira/sandbox.rb +193 -0
  40. data/lib/zephira/tokens.rb +16 -0
  41. data/lib/zephira/tools/base_tool.rb +105 -0
  42. data/lib/zephira/tools/code_search.rb +135 -0
  43. data/lib/zephira/tools/delete_file.rb +47 -0
  44. data/lib/zephira/tools/http_request.rb +112 -0
  45. data/lib/zephira/tools/list_directory.rb +57 -0
  46. data/lib/zephira/tools/memory_delete.rb +39 -0
  47. data/lib/zephira/tools/memory_list.rb +37 -0
  48. data/lib/zephira/tools/memory_read.rb +43 -0
  49. data/lib/zephira/tools/memory_store.rb +51 -0
  50. data/lib/zephira/tools/memory_write.rb +39 -0
  51. data/lib/zephira/tools/read_file.rb +90 -0
  52. data/lib/zephira/tools/shell.rb +82 -0
  53. data/lib/zephira/tools/update_file.rb +46 -0
  54. data/lib/zephira/tools/web_search.rb +113 -0
  55. data/lib/zephira/tools.rb +70 -0
  56. data/lib/zephira/version.rb +5 -0
  57. data/lib/zephira.rb +24 -0
  58. data/license.txt +21 -0
  59. data/standard.yml +3 -0
  60. metadata +243 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1c50d16206adc052c5236333defc539edcd65a533e975794f781427f7364fd10
4
+ data.tar.gz: 69fecd0054fba2316e0aaa02ef529742c5a9de2258091eedada942df3c9f8425
5
+ SHA512:
6
+ metadata.gz: 20b9495103c802b063d71f197e55f248c0eebc7f1b07d74ca0e41ebd0219a450976e7b246dec0c7d72cdcbbe659b68f8db54443c224d9291abf24df6472fbb40
7
+ data.tar.gz: 859aa00aa284fc405df4f7079cdf322640228986f141ab1a0142743af6c83faffd65ea68a9ba09a4fac1b64be497feaadb81cfbc79b4cb88eec5f9aa0e4841a7
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Zephira Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - Concurrent execution of read-only tool calls within a single model turn (`web_search`, `read_file`, `code_search`, `list_directory`, `memory_read`, `memory_list`). Mutating tools still run sequentially in declared order.
7
+ - `Tools::BaseTool.read_only?` class method — tools opt in by overriding it. Default is `false`.
8
+ - `Zephira::Tokens` module — single token-estimator shared by `History` and `Agent`.
9
+ - `Zephira::Agent::Status` extracted to its own file.
10
+ - `Zephira::Tools::MemoryStore` shared helper backing the four memory tools.
11
+ - Per-model `backend` override on `BaseModel` so future provider classes can be added without forking the inference loop.
12
+
13
+ ### Changed
14
+ - Renamed `/models` slash command to `/model`.
15
+ - `Agent#run_loop` decomposed into focused helpers (`print_intro`, `render_status_bar`, `process_user_message`, `run_inference_with_spinner`, etc.).
16
+ - Cached `uname -a` and `pwd` at agent init instead of shelling out per request.
17
+ - Inference loop uses iteration instead of recursion for tool-call chains; max-iter guards become trivial to add.
18
+ - Memory tools now load YAML via `YAML.safe_load_file`.
19
+ - Backend `agent` is passed per call rather than held as an ivar; rescue paths nil-guard the agent.
20
+ - Debug toggle moved from raw `ENV["DEBUG"]` to `Config.read("ZEPHIRA_DEBUG")` for consistency.
21
+ - `code_search` tool uses POSIX `command -v` instead of `which`.
22
+ - Spinner elapsed-time updates now go through the public `TTY::Spinner#update` API instead of reaching into private `@tokens` state.
23
+ - `update_file` now allows empty content (declarative `allow_empty: true` is now the single source of truth).
24
+
25
+ ### Fixed
26
+ - JSON parse failures on tool arguments now log via `agent.logger.error` with the raw payload instead of being silently swallowed.
27
+
28
+ ### Tests
29
+ - Added thread-safe stdout/stderr silencer in `spec_helper` (`:show_output` opt-out).
30
+ - Added default `Config.read` stub in `spec_helper` (`:real_config` opt-out).
31
+ - Broadened error-path coverage for file tools and `Agent::Status`.
32
+ - Suite: 422 examples, 0 failures, ~96% line coverage.
33
+
34
+ ## 0.1.0
35
+
36
+ In the beginning...
data/Dockerfile ADDED
@@ -0,0 +1,39 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ruby:3.4-slim AS deps
4
+
5
+ WORKDIR /build
6
+
7
+ RUN apt-get update -qq && \
8
+ apt-get install -y --no-install-recommends \
9
+ build-essential \
10
+ git \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ COPY Gemfile Gemfile.lock zephira.gemspec ./
14
+ COPY lib/zephira/version.rb lib/zephira/version.rb
15
+
16
+ ENV BUNDLE_WITHOUT="development:test"
17
+
18
+ RUN bundle install --jobs 4
19
+
20
+ COPY . .
21
+ RUN gem build zephira.gemspec
22
+
23
+
24
+ FROM ruby:3.4-slim AS runtime
25
+
26
+ RUN apt-get update -qq && \
27
+ apt-get install -y --no-install-recommends \
28
+ libreadline-dev \
29
+ && rm -rf /var/lib/apt/lists/*
30
+
31
+ COPY --from=deps /usr/local/bundle /usr/local/bundle
32
+ COPY --from=deps /build/zephira-*.gem /tmp/
33
+
34
+ RUN gem install --local --no-document /tmp/zephira-*.gem && \
35
+ rm -f /tmp/zephira-*.gem
36
+
37
+ WORKDIR /workspace
38
+
39
+ CMD ["zephira"]
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,145 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zephira (0.1.0)
5
+ faraday (~> 2.0)
6
+ tty-cursor (~> 0.7)
7
+ tty-screen (~> 0.8)
8
+ tty-spinner (~> 0.9)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.9.0)
14
+ public_suffix (>= 2.0.2, < 8.0)
15
+ ast (2.4.3)
16
+ bigdecimal (4.1.2)
17
+ crack (1.0.1)
18
+ bigdecimal
19
+ rexml
20
+ date (3.5.1)
21
+ diff-lcs (1.6.2)
22
+ docile (1.4.1)
23
+ erb (6.0.3)
24
+ fakefs (3.2.1)
25
+ faraday (2.14.1)
26
+ faraday-net_http (>= 2.0, < 3.5)
27
+ json
28
+ logger
29
+ faraday-net_http (3.4.2)
30
+ net-http (~> 0.5)
31
+ hashdiff (1.2.1)
32
+ io-console (0.8.2)
33
+ irb (1.17.0)
34
+ pp (>= 0.6.0)
35
+ prism (>= 1.3.0)
36
+ rdoc (>= 4.0.0)
37
+ reline (>= 0.4.2)
38
+ json (2.19.3)
39
+ language_server-protocol (3.17.0.5)
40
+ lint_roller (1.1.0)
41
+ logger (1.7.0)
42
+ net-http (0.9.1)
43
+ uri (>= 0.11.1)
44
+ parallel (1.28.0)
45
+ parser (3.3.11.1)
46
+ ast (~> 2.4.1)
47
+ racc
48
+ pp (0.6.3)
49
+ prettyprint
50
+ prettyprint (0.2.0)
51
+ prism (1.9.0)
52
+ psych (5.3.1)
53
+ date
54
+ stringio
55
+ public_suffix (7.0.5)
56
+ racc (1.8.1)
57
+ rainbow (3.1.1)
58
+ rdoc (7.2.0)
59
+ erb
60
+ psych (>= 4.0.0)
61
+ tsort
62
+ regexp_parser (2.12.0)
63
+ reline (0.6.3)
64
+ io-console (~> 0.5)
65
+ rexml (3.4.4)
66
+ rspec (3.13.2)
67
+ rspec-core (~> 3.13.0)
68
+ rspec-expectations (~> 3.13.0)
69
+ rspec-mocks (~> 3.13.0)
70
+ rspec-core (3.13.6)
71
+ rspec-support (~> 3.13.0)
72
+ rspec-expectations (3.13.5)
73
+ diff-lcs (>= 1.2.0, < 2.0)
74
+ rspec-support (~> 3.13.0)
75
+ rspec-mocks (3.13.8)
76
+ diff-lcs (>= 1.2.0, < 2.0)
77
+ rspec-support (~> 3.13.0)
78
+ rspec-support (3.13.7)
79
+ rubocop (1.84.2)
80
+ json (~> 2.3)
81
+ language_server-protocol (~> 3.17.0.2)
82
+ lint_roller (~> 1.1.0)
83
+ parallel (~> 1.10)
84
+ parser (>= 3.3.0.2)
85
+ rainbow (>= 2.2.2, < 4.0)
86
+ regexp_parser (>= 2.9.3, < 3.0)
87
+ rubocop-ast (>= 1.49.0, < 2.0)
88
+ ruby-progressbar (~> 1.7)
89
+ unicode-display_width (>= 2.4.0, < 4.0)
90
+ rubocop-ast (1.49.1)
91
+ parser (>= 3.3.7.2)
92
+ prism (~> 1.7)
93
+ rubocop-performance (1.26.1)
94
+ lint_roller (~> 1.1)
95
+ rubocop (>= 1.75.0, < 2.0)
96
+ rubocop-ast (>= 1.47.1, < 2.0)
97
+ ruby-progressbar (1.13.0)
98
+ simplecov (0.22.0)
99
+ docile (~> 1.1)
100
+ simplecov-html (~> 0.11)
101
+ simplecov_json_formatter (~> 0.1)
102
+ simplecov-html (0.13.2)
103
+ simplecov_json_formatter (0.1.4)
104
+ standard (1.54.0)
105
+ language_server-protocol (~> 3.17.0.2)
106
+ lint_roller (~> 1.0)
107
+ rubocop (~> 1.84.0)
108
+ standard-custom (~> 1.0.0)
109
+ standard-performance (~> 1.8)
110
+ standard-custom (1.0.2)
111
+ lint_roller (~> 1.0)
112
+ rubocop (~> 1.50)
113
+ standard-performance (1.9.0)
114
+ lint_roller (~> 1.1)
115
+ rubocop-performance (~> 1.26.0)
116
+ stringio (3.2.0)
117
+ tsort (0.2.0)
118
+ tty-cursor (0.7.1)
119
+ tty-screen (0.8.2)
120
+ tty-spinner (0.9.3)
121
+ tty-cursor (~> 0.7)
122
+ unicode-display_width (3.2.0)
123
+ unicode-emoji (~> 4.1)
124
+ unicode-emoji (4.2.0)
125
+ uri (1.1.1)
126
+ webmock (3.26.2)
127
+ addressable (>= 2.8.0)
128
+ crack (>= 0.3.2)
129
+ hashdiff (>= 0.4.0, < 2.0.0)
130
+
131
+ PLATFORMS
132
+ arm64-darwin-24
133
+ ruby
134
+
135
+ DEPENDENCIES
136
+ fakefs (~> 3.2)
137
+ irb (~> 1.17)
138
+ rspec (~> 3.13.2)
139
+ simplecov (~> 0.22)
140
+ standard (~> 1.54)
141
+ webmock (~> 3.0)
142
+ zephira!
143
+
144
+ BUNDLED WITH
145
+ 2.6.9
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Zephira
2
+
3
+ Zephira is a command-line AI coding assistant written in Ruby.
4
+
5
+ It runs in your terminal, keeps per-project conversation history, calls a pluggable set of tools, and executes inside a Docker sandbox by default so the agent cannot touch the host system unless you opt out. The codebase is small, plugin-based, and intended to be read end-to-end.
6
+
7
+ ## Features
8
+
9
+ - Interactive terminal chat loop with per-session token-budget tracking and automatic history compaction
10
+ - Built-in slash commands: `/help`, `/about`, `/model`, `/history`, `/compact`, `/clear`, `/bye`
11
+ - Plugin-style tool system — drop a file in `lib/zephira/tools/` and it is auto-loaded:
12
+ - file I/O: `read_file`, `update_file`, `delete_file`, `list_directory`
13
+ - search: `code_search` (ripgrep-backed), `web_search` (Brave Search API)
14
+ - execution: `shell`, `http_request`
15
+ - persistent memory: `memory_read`, `memory_write`, `memory_list`, `memory_delete`
16
+ - Concurrent execution of read-only tool calls in a single turn (mutating tools still run sequentially in declared order)
17
+ - Pluggable model + backend layer — register a new model by dropping a file in `lib/zephira/models/`; backends bind per model class
18
+ - OpenAI-compatible backend out of the box; structured to add provider-specific backends without forking the core loop
19
+ - Docker sandbox enabled by default; `--dangerously-skip-sandbox` to opt out
20
+ - Persistent session log + conversation history under `.zephira/` in each project
21
+ - ~95% line coverage on a focused RSpec suite
22
+
23
+ ## Installation
24
+
25
+ Requirements:
26
+
27
+ - Ruby 3.2+
28
+ - Bundler
29
+ - Docker, if you want sandboxed execution
30
+
31
+ Install from RubyGems:
32
+
33
+ ```sh
34
+ gem install zephira
35
+ ```
36
+
37
+ Or install locally for development:
38
+
39
+ ```sh
40
+ git clone https://github.com/aarongough/zephira.git
41
+ cd zephira
42
+ bundle install
43
+ ```
44
+
45
+ ## Quick start
46
+
47
+ Start Zephira in the current project directory:
48
+
49
+ ```sh
50
+ zephira
51
+ ```
52
+
53
+ Show CLI help:
54
+
55
+ ```sh
56
+ zephira --help
57
+ ```
58
+
59
+ Print the installed version:
60
+
61
+ ```sh
62
+ zephira --version
63
+ ```
64
+
65
+ To run without Docker sandboxing:
66
+
67
+ ```sh
68
+ zephira --dangerously-skip-sandbox
69
+ ```
70
+
71
+ Warning: skipping the sandbox gives the agent direct access to your real filesystem.
72
+
73
+ ## Configuration
74
+
75
+ Zephira reads configuration from:
76
+
77
+ - environment variables
78
+ - `.zephira.yml` in the current project
79
+ - `~/.zephira.yml` in your home directory
80
+
81
+ Environment variables take precedence.
82
+
83
+ Example configuration:
84
+
85
+ ```yaml
86
+ ZEPHIRA_API_KEY: "your_api_key_here"
87
+ ZEPHIRA_MODEL: "gpt-4.1-mini"
88
+ ZEPHIRA_BASE_URL: "https://api.openai.com/v1"
89
+ ZEPHIRA_BACKEND: "openai_compatible"
90
+ ZEPHIRA_BASE_IMAGE: "ruby:3.4-slim"
91
+ ZEPHIRA_BRAVE_SEARCH_API_KEY: "your_brave_api_key_here"
92
+ ```
93
+
94
+ ## Supported configuration keys
95
+
96
+ - `ZEPHIRA_API_KEY` — API key for the selected LLM backend
97
+ - `ZEPHIRA_MODEL` — model name to use
98
+ - `ZEPHIRA_BASE_URL` — base URL for OpenAI-compatible APIs
99
+ - `ZEPHIRA_BACKEND` — backend adapter identifier
100
+ - `ZEPHIRA_BASE_IMAGE` — base Docker image for sandbox execution
101
+ - `ZEPHIRA_BRAVE_SEARCH_API_KEY` — required for the web search tool
102
+ - `ZEPHIRA_SANDBOX` — internal/advanced flag to disable sandboxing
103
+
104
+ ## Sandbox behavior
105
+
106
+ By default, Zephira attempts to run inside Docker for safer execution.
107
+
108
+ When sandboxing is enabled, Zephira re-executes itself inside a container and mounts your current project into `/workspace`. This gives the agent access to the project while helping isolate it from the host system.
109
+
110
+ If Docker is unavailable, Zephira exits with an error and explains how to proceed.
111
+
112
+ You can bypass sandboxing with:
113
+
114
+ ```sh
115
+ zephira --dangerously-skip-sandbox
116
+ ```
117
+
118
+ Use that only if you understand the risks.
119
+
120
+ ## Interactive commands
121
+
122
+ Inside Zephira, you can use slash commands:
123
+
124
+ - `/help` — show available commands
125
+ - `/about` — show project information
126
+ - `/model` — list available models
127
+ - `/model set MODEL_NAME` — switch models for the current session
128
+ - `/history` — print conversation history
129
+ - `/compact` — manually compact the conversation history
130
+ - `/clear` — clear the screen
131
+ - `/bye` — exit the session
132
+
133
+ ## Available models
134
+
135
+ This repository currently includes model definitions for:
136
+
137
+ - `gpt-4.1`
138
+ - `gpt-4.1-mini`
139
+ - `gpt-5.4`
140
+ - `gpt-5.5`
141
+ - `gpt-o4-mini`
142
+ - `claude-3.5-sonnet`
143
+ - `llama4`
144
+
145
+ The exact names available in the running app are determined by the model classes in `lib/zephira/models`.
146
+
147
+ ## Built-in tools
148
+
149
+ Zephira includes tools for:
150
+
151
+ - `read_file`
152
+ - `update_file`
153
+ - `delete_file`
154
+ - `list_directory`
155
+ - `code_search`
156
+ - `shell`
157
+ - `http_request`
158
+ - `web_search`
159
+ - `memory_write`
160
+ - `memory_read`
161
+ - `memory_list`
162
+ - `memory_delete`
163
+
164
+ These tools allow the agent to inspect the project, modify files, run commands, query APIs, and maintain lightweight persistent memory.
165
+
166
+ ## Project structure
167
+
168
+ ```text
169
+ exe/ Executable entrypoint
170
+ lib/zephira/ Core application code
171
+ lib/zephira/models/ Model definitions
172
+ lib/zephira/tools/ Tool implementations
173
+ lib/zephira/commands/ Slash commands
174
+ lib/zephira/completions/ Readline completions
175
+ spec/ Test suite
176
+ Dockerfile Sandbox/runtime image
177
+ ```
178
+
179
+ ## Development
180
+
181
+ Install dependencies:
182
+
183
+ ```sh
184
+ bundle install
185
+ ```
186
+
187
+ Run the test suite:
188
+
189
+ ```sh
190
+ bundle exec rspec
191
+ ```
192
+
193
+ Run linting:
194
+
195
+ ```sh
196
+ bundle exec standardrb --fix
197
+ ```
198
+
199
+ ## Design goals
200
+
201
+ Zephira favors:
202
+
203
+ - clarity over complexity
204
+ - small, readable components
205
+ - hackability and extension
206
+ - realistic terminal-first workflows
207
+ - sandboxed local agent execution
208
+
209
+ It is best thought of as a lightweight coding assistant and a learning-oriented agent framework, not a full enterprise platform.
210
+
211
+ ## Logging and history
212
+
213
+ Zephira stores session information under `.zephira/` in the current project directory, including logs and conversation history. This makes sessions project-local and easy to inspect.
214
+
215
+ ## License
216
+
217
+ Released under the MIT License.
218
+
219
+ See:
220
+
221
+ ```text
222
+ license.txt
223
+ ```
224
+
225
+ ## Author
226
+
227
+ Aaron Gough
228
+
229
+ Project home:
230
+ https://github.com/aarongough/zephira
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ # use Standard Ruby style guide for linting
9
+ require "standard/rake"
10
+
11
+ task lint: :standard
12
+ task default: %i[spec lint]
data/exe/zephira ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "zephira"
5
+
6
+ Zephira::CLI.new(ARGV)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zephira
4
+ class Agent
5
+ class Status
6
+ def initialize(agent)
7
+ @agent = agent
8
+ end
9
+
10
+ def verbose(msg)
11
+ return unless @agent.verbose
12
+ @agent.update_status(msg)
13
+ end
14
+
15
+ def warn(msg)
16
+ @agent.update_status(Formatter.color(:dark_red, msg))
17
+ end
18
+ end
19
+ end
20
+ end