ohwow 0.1.13 → 0.2.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.
- package/LICENSE +17 -17
- package/README.md +120 -166
- package/dist/api.d.ts +91 -0
- package/dist/api.js +2 -0
- package/dist/index.d.ts +1 -90
- package/dist/index.js +1754 -590
- package/dist/mcp-server/index.js +10 -49
- package/dist/migrations/001-data-plane-tables.sql +1 -1
- package/dist/migrations/002-agents-table.sql +1 -1
- package/dist/migrations/017-openclaw-call-logs.sql +15 -0
- package/dist/migrations/038-peer-queue-tracking.sql +5 -0
- package/dist/migrations/039-task-delegation.sql +5 -0
- package/dist/migrations/040-self-improvement-tables.sql +109 -0
- package/dist/migrations/041-session-metadata.sql +3 -0
- package/dist/migrations/042-memory-sync.sql +45 -0
- package/dist/migrations/043-multi-connection.sql +18 -0
- package/dist/migrations/044-multi-connection-fixes.sql +32 -0
- package/dist/migrations/045-task-state.sql +21 -0
- package/dist/migrations/046-state-changelog.sql +18 -0
- package/dist/migrations/047-outbound-queue.sql +9 -0
- package/dist/migrations/048-tasks-column-alignment.sql +37 -0
- package/dist/migrations/049-agents-column-alignment.sql +25 -0
- package/dist/migrations/050-workflows-trigger-alignment.sql +25 -0
- package/dist/migrations/051-execution-engine-tables.sql +132 -0
- package/dist/migrations/052-a2a-rate-limit-alignment.sql +8 -0
- package/dist/migrations/053-llm-cache.sql +20 -0
- package/dist/migrations/054-task-checkpoints.sql +13 -0
- package/dist/migrations/055-resource-usage.sql +12 -0
- package/dist/migrations/056-sandbox-tables.sql +35 -0
- package/dist/web/assets/{index-DSojxPLI.css → index-1Wga6dwZ.css} +1 -1
- package/dist/web/assets/{index-D6DkPUkA.js → index-BHeLTE3N.js} +26 -26
- package/dist/web/index.html +2 -2
- package/package.json +59 -14
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2026 Jesus David Oñoro Delgado
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Change License: Apache License, Version 2.0
|
|
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:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,88 +1,35 @@
|
|
|
1
1
|
# ohwow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/ohwow)
|
|
5
|
+
[](https://github.com/ohwow-fun/ohwow/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/ohwow)
|
|
7
|
+
[](https://discord.gg/WUGnGqceeY)
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
**Your AI team that runs on your laptop. Agents that learn, message customers, browse the web, and control your desktop. Costs $0.**
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="docs/demo.gif" alt="ohwow demo" width="800">
|
|
13
|
+
</p>
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
ohwow is an open source AI business operating system. A team of AI agents that live on your laptop, handle your CRM, message your customers on WhatsApp, automate your workflows, browse the web, and now control your desktop. All running locally. All free. No cloud required.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
npm install ohwow -g
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### Requirements
|
|
16
|
-
|
|
17
|
-
- Node.js 20+
|
|
18
|
-
- [Ollama](https://ollama.com) for local models
|
|
19
|
-
- Optional: Anthropic API key (for Claude models)
|
|
20
|
-
- Optional: Playwright browsers (`npx playwright install chromium`) for browser automation
|
|
21
|
-
- Optional: C++ compiler may be needed on some platforms for `better-sqlite3`
|
|
22
|
-
|
|
23
|
-
### Launch
|
|
17
|
+
## Quick Start
|
|
24
18
|
|
|
25
19
|
```bash
|
|
20
|
+
npm install ohwow -g
|
|
26
21
|
ohwow
|
|
27
22
|
```
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
A setup wizard appears in your terminal. Point it at your Ollama instance to get started. To connect to ohwow.fun cloud, enter your license key (from the ohwow.fun dashboard, under Settings > License).
|
|
32
|
-
|
|
33
|
-
Config is saved to `~/.ohwow/config.json`. You only do this once.
|
|
34
|
-
|
|
35
|
-
## What Happens at Startup
|
|
36
|
-
|
|
37
|
-
Once configured, the runtime:
|
|
24
|
+
A setup wizard walks you through connecting to [Ollama](https://ollama.com), picking a model, and selecting agents for your business type. Running in under 5 minutes.
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
4. Starts the execution engine, orchestrator, and model router
|
|
43
|
-
5. Connects messaging channels (WhatsApp, Telegram) if configured
|
|
44
|
-
6. Starts the scheduler, proactive engine, and trigger evaluator
|
|
45
|
-
7. Launches the HTTP server (default port 7700)
|
|
46
|
-
8. Connects to the ohwow.fun control plane (enterprise)
|
|
47
|
-
9. Opens a WebSocket for real-time updates
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="docs/onboarding.gif" alt="ohwow onboarding" width="800">
|
|
28
|
+
</p>
|
|
48
29
|
|
|
49
|
-
|
|
30
|
+
## Talk to it like a person
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| Command | What it does |
|
|
54
|
-
|---|---|
|
|
55
|
-
| `ohwow` | Start the TUI (default) |
|
|
56
|
-
| `ohwow --daemon` | Start daemon in foreground (for systemd/launchd/Docker) |
|
|
57
|
-
| `ohwow stop` | Stop the daemon |
|
|
58
|
-
| `ohwow status` | Check daemon status (PID and port) |
|
|
59
|
-
| `ohwow logs` | Tail daemon logs |
|
|
60
|
-
| `ohwow restart` | Restart the daemon |
|
|
61
|
-
|
|
62
|
-
## TUI
|
|
63
|
-
|
|
64
|
-
The terminal UI opens into a chat interface with tab navigation. Use arrow keys or tab to switch between:
|
|
65
|
-
|
|
66
|
-
- **Dashboard** — overview of your workspace
|
|
67
|
-
- **Agents** — manage agent configs, memory, and capabilities
|
|
68
|
-
- **Tasks** — view and manage running/completed tasks
|
|
69
|
-
- **Approvals** — review pending items before execution
|
|
70
|
-
- **Activity** — live feed of everything happening
|
|
71
|
-
- **Automations** — webhook-based automation triggers
|
|
72
|
-
- **Contacts** — CRM with leads, customers, partners
|
|
73
|
-
- **Settings** — config, connections, license
|
|
74
|
-
|
|
75
|
-
Everything you see in the web dashboard is also here, running locally.
|
|
76
|
-
|
|
77
|
-
## Web UI
|
|
78
|
-
|
|
79
|
-
The runtime serves a built-in React app at `http://localhost:7700`. Same capabilities as the TUI. Useful if you prefer a graphical interface or want to share access with your team on the local network. Override the port with the `OHWOW_PORT` env var.
|
|
80
|
-
|
|
81
|
-
## Orchestrator
|
|
82
|
-
|
|
83
|
-
The orchestrator is a conversational assistant built into the runtime with 40+ tools. Open the Chat tab in the TUI, or use the web UI.
|
|
84
|
-
|
|
85
|
-
You can talk to it naturally:
|
|
32
|
+
The orchestrator is a conversational assistant with 150+ tools. Open the Chat tab and speak naturally:
|
|
86
33
|
|
|
87
34
|
| What you say | What happens |
|
|
88
35
|
|---|---|
|
|
@@ -90,135 +37,142 @@ You can talk to it naturally:
|
|
|
90
37
|
| "What failed today?" | Lists recent failed tasks with details |
|
|
91
38
|
| "Schedule outreach every weekday at 9am" | Creates a cron schedule for the agent |
|
|
92
39
|
| "Send a WhatsApp to the team: launching Friday" | Sends the message through your connected WhatsApp |
|
|
40
|
+
| "Open Figma and export the hero banner as PNG" | Takes over your desktop, opens the app, clicks through menus, saves the file |
|
|
93
41
|
| "Plan out researching 5 new leads this week" | Creates a multi-step plan with agent assignments, waits for your approval |
|
|
94
42
|
| "Show me the business pulse" | Returns task stats, contact pipeline, costs, and streaks |
|
|
95
43
|
| "Create a project for the website redesign" | Creates a project with a Kanban board |
|
|
96
|
-
| "Move that task to review" | Moves a task between board columns |
|
|
97
|
-
|
|
98
|
-
The orchestrator covers: agents, tasks, projects, CRM (contacts, pipeline, events), scheduling, messaging (WhatsApp + Telegram), A2A connections, goal planning, deep research, analytics, and workflows. It can also switch your TUI tabs if you ask ("go to approvals").
|
|
99
44
|
|
|
100
45
|
## Features
|
|
101
46
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- **Speech-to-Text**: Voicebox (Whisper via local FastAPI server), WhisperLocal (via Ollama), or WhisperAPI (OpenAI cloud fallback)
|
|
119
|
-
- **Text-to-Speech**: VoiceboxTTS (local), Piper (local), or OpenAI TTS (cloud fallback)
|
|
120
|
-
|
|
121
|
-
### A2A Protocol
|
|
122
|
-
|
|
123
|
-
Connect to external agents using the [A2A protocol](https://google.github.io/A2A/) over JSON-RPC 2.0. Each agent publishes a card at `/.well-known/agent-card.json` describing its capabilities. Trust levels (`read_only`, `execute`, `autonomous`, `admin`) control what external agents can do. Scopes cover tasks, agents, results, and file access. Managed from the A2A tab or through the orchestrator.
|
|
124
|
-
|
|
125
|
-
### Scheduling and Proactive Engine
|
|
126
|
-
|
|
127
|
-
Set agents or workflows to run on cron schedules. Create schedules through conversation ("schedule the analyst every Monday at 8am") or from the Schedules tab.
|
|
128
|
-
|
|
129
|
-
The proactive engine runs every 30 minutes and checks for overdue tasks, aging approvals, and idle agents. It generates nudges (suggestions, not auto-executions) so nothing falls through the cracks.
|
|
130
|
-
|
|
131
|
-
### Goal Planning and Approvals
|
|
132
|
-
|
|
133
|
-
For complex goals, the orchestrator breaks them into multi-step plans with agent assignments and dependencies. Plans start as drafts. You review the steps, approve or reject, and track execution from the Plans tab. Rejected tasks can retry with your feedback included.
|
|
134
|
-
|
|
135
|
-
### Projects and CRM
|
|
136
|
-
|
|
137
|
-
Organize tasks into projects with Kanban boards (backlog, todo, in progress, review, done). The built-in CRM tracks contacts (leads, customers, partners), logs events (calls, emails, meetings), and gives you pipeline analytics. All stored locally.
|
|
138
|
-
|
|
139
|
-
### Automations and Triggers
|
|
140
|
-
|
|
141
|
-
Webhook-based automations that fire on external events. Configure field mapping to extract data from incoming payloads and route it to agents or workflows. Managed from the Automations tab.
|
|
47
|
+
| Category | What you get |
|
|
48
|
+
|---|---|
|
|
49
|
+
| AI Agents | 48 pre-built agents across 6 business types, persistent memory, RAG retrieval |
|
|
50
|
+
| Orchestrator | 150+ tools, natural language chat interface for everything |
|
|
51
|
+
| Desktop Control | Full macOS desktop automation: mouse, keyboard, screen capture. Your agents operate any app on your computer. (macOS only) |
|
|
52
|
+
| Messaging | WhatsApp + Telegram built in, auto-routing to agents |
|
|
53
|
+
| Browser | Local Chromium automation (navigate, click, fill, screenshot, extract) |
|
|
54
|
+
| Voice | Local STT/TTS (Whisper, Piper) with cloud fallbacks |
|
|
55
|
+
| CRM | Contacts, pipeline, events, analytics, all stored locally |
|
|
56
|
+
| Scheduling | Cron schedules + proactive nudge engine every 30 minutes |
|
|
57
|
+
| Workflows | DAG-based multi-agent execution graphs with conditions |
|
|
58
|
+
| Multidevice | Zero-config mDNS mesh, automatic task routing across machines |
|
|
59
|
+
| MCP | External tool integration via Model Context Protocol |
|
|
60
|
+
| A2A | Google Agent-to-Agent protocol over JSON-RPC 2.0 |
|
|
61
|
+
| Sandbox | Isolated JavaScript execution (no fs/net/process access) |
|
|
142
62
|
|
|
143
|
-
|
|
63
|
+
## Desktop Control
|
|
144
64
|
|
|
145
|
-
|
|
65
|
+
Your agents aren't trapped inside a chat window. They can use your computer.
|
|
146
66
|
|
|
147
|
-
|
|
67
|
+
ohwow agents see your screen, move the mouse, type on the keyboard, and operate any macOS app. Not through brittle scripts or accessibility hacks. They look at what's on screen, reason about what to do next, and act. The same way you would, but without getting distracted.
|
|
148
68
|
|
|
149
|
-
|
|
69
|
+
**How it works:** the agent takes a screenshot, analyzes it with a vision model, decides the next action (click, type, scroll, key press), executes it, then takes another screenshot to see the result. This perception/reasoning/action loop repeats until the task is done.
|
|
150
70
|
|
|
151
|
-
|
|
71
|
+
```
|
|
72
|
+
You: "Fill out the Q1 expense report in Google Sheets using last month's receipts folder"
|
|
73
|
+
|
|
74
|
+
ohwow:
|
|
75
|
+
1. Opens Finder, navigates to the receipts folder
|
|
76
|
+
2. Opens Google Sheets in Chrome
|
|
77
|
+
3. Reads each receipt, types the amounts into the right cells
|
|
78
|
+
4. Double-checks totals
|
|
79
|
+
5. Done. You were making coffee the whole time.
|
|
80
|
+
```
|
|
152
81
|
|
|
153
|
-
|
|
82
|
+
**Safety first.** Desktop control requires your explicit permission before activating. You can stop the agent at any point. Dangerous actions (typing in Terminal, changing system settings) trigger additional approval. An emergency stop halts everything instantly.
|
|
154
83
|
|
|
155
|
-
|
|
84
|
+
**Works with Dispatch.** Connect to [ohwow.fun](https://ohwow.fun) and trigger desktop tasks from your phone. Start a task on the train, your MacBook at home does the work. Check progress from the dashboard.
|
|
156
85
|
|
|
157
|
-
|
|
86
|
+
> Desktop control currently supports macOS only. All other features (agents, chat, browser automation, voice, CRM, scheduling, MCP) work on macOS, Linux, and Windows. See [Windows Setup Guide](docs/windows-setup.md) for details.
|
|
158
87
|
|
|
159
|
-
|
|
88
|
+
## Who is this for?
|
|
160
89
|
|
|
161
|
-
|
|
90
|
+
- **Solo founders** automating marketing, outreach, and ops with zero budget
|
|
91
|
+
- **Busy operators** who want an AI team that handles the repetitive screen work while they focus on growth
|
|
92
|
+
- **Small teams** that need agents coordinating across devices
|
|
93
|
+
- **Agencies** managing operations for multiple clients
|
|
94
|
+
- **Privacy-conscious businesses** that need AI automation without cloud lock-in
|
|
95
|
+
- **Developers** building custom agents with the orchestrator, MCP tools, and desktop control APIs
|
|
162
96
|
|
|
163
|
-
|
|
97
|
+
## Why ohwow?
|
|
164
98
|
|
|
165
|
-
|
|
99
|
+
| | Zapier | n8n | Make | OpenClaw | **ohwow** |
|
|
100
|
+
|---|---|---|---|---|---|
|
|
101
|
+
| Runs on your machine | No | Self-host option | No | Yes | **Yes, always** |
|
|
102
|
+
| Desktop control | No | No | No | Yes | **Yes, with safety guards** |
|
|
103
|
+
| AI agents with persistent memory | No | Yes (LangChain nodes) | No | Partial | **Yes, agents learn over time** |
|
|
104
|
+
| Cost to start | $20+/mo | Free (self-host) | $10+/mo | Free | **$0 with Ollama** |
|
|
105
|
+
| Pre-built business agents | No | No | No | No | **48 agents, 6 biz types** |
|
|
106
|
+
| WhatsApp + Telegram | Plugin | Plugin | Plugin | No | **Built in** |
|
|
107
|
+
| CRM + contacts | No | No | No | No | **Built in** |
|
|
108
|
+
| Multi-device mesh | No | No | No | No | **Zero-config** |
|
|
109
|
+
| Browser automation | No | Partial | No | Yes | **Built in (Chromium)** |
|
|
110
|
+
| Workflow DAGs | Yes | Yes | Yes | No | **Yes, with conditions + parallel** |
|
|
111
|
+
| Phone dispatch | No | No | No | No | **Yes, via ohwow.fun** |
|
|
112
|
+
| Your data stays local | No | Optional | No | Yes | **Always** |
|
|
166
113
|
|
|
167
|
-
|
|
114
|
+
> Comparison as of March 2026. We love what OpenClaw, n8n, and others are building. ohwow focuses on a different problem: a complete AI business runtime, not just a single agent or workflow tool.
|
|
168
115
|
|
|
169
|
-
|
|
116
|
+
## Self-hosting
|
|
170
117
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
118
|
+
| Command | What it does |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `ohwow` | Start the TUI (default) |
|
|
121
|
+
| `ohwow --daemon` | Start daemon in foreground (for systemd/launchd/Docker) |
|
|
122
|
+
| `ohwow stop` | Stop the daemon |
|
|
123
|
+
| `ohwow status` | Check daemon status (PID and port) |
|
|
124
|
+
| `ohwow logs` | Tail daemon logs |
|
|
125
|
+
| `ohwow restart` | Restart the daemon |
|
|
177
126
|
|
|
178
|
-
|
|
179
|
-
- Prompts and system instructions
|
|
180
|
-
- Agent outputs and full conversations
|
|
181
|
-
- Long-term agent memory
|
|
182
|
-
- CRM contacts and activity history
|
|
183
|
-
- WhatsApp and Telegram message history
|
|
184
|
-
- Browser session data and screenshots
|
|
127
|
+
The runtime serves a web UI at `http://localhost:7700`. Override the port with `OHWOW_PORT`.
|
|
185
128
|
|
|
186
|
-
|
|
129
|
+
Docker:
|
|
187
130
|
|
|
188
|
-
|
|
131
|
+
```bash
|
|
132
|
+
docker run -d --name ohwow -p 7700:7700 -v ~/.ohwow:/root/.ohwow ohwow
|
|
133
|
+
```
|
|
189
134
|
|
|
190
|
-
|
|
135
|
+
## Cloud
|
|
191
136
|
|
|
192
|
-
|
|
137
|
+
Connect to [ohwow.fun](https://ohwow.fun) for cloud features on top of the free local runtime:
|
|
193
138
|
|
|
194
|
-
|
|
139
|
+
- **Desktop dispatch** from your phone. Assign tasks to your MacBook while you're out.
|
|
140
|
+
- **OAuth integrations** (Gmail, Slack, and more)
|
|
141
|
+
- **Cloud task dispatch** from the web dashboard or mobile
|
|
142
|
+
- **AI site generator** with hosting and custom domains
|
|
143
|
+
- **Webhook relay** for external services
|
|
144
|
+
- **Fleet management** across devices
|
|
195
145
|
|
|
196
|
-
|
|
197
|
-
ohwow --daemon
|
|
198
|
-
```
|
|
146
|
+
All local features work without cloud. See [pricing](https://ohwow.fun/pricing) for plans starting at $29/mo.
|
|
199
147
|
|
|
200
|
-
|
|
148
|
+
## Requirements
|
|
201
149
|
|
|
202
|
-
|
|
150
|
+
- Node.js 20+
|
|
151
|
+
- [Ollama](https://ollama.com) for local models
|
|
152
|
+
- Optional: Anthropic API key (for Claude models)
|
|
153
|
+
- Optional: Playwright browsers (`npx playwright install chromium`) for browser automation
|
|
154
|
+
- Optional: macOS Accessibility permission for desktop control (macOS only)
|
|
155
|
+
- Optional: Visual Studio C++ Build Tools on Windows for `better-sqlite3` native module
|
|
203
156
|
|
|
204
|
-
|
|
157
|
+
## Troubleshooting
|
|
205
158
|
|
|
206
|
-
|
|
|
207
|
-
|
|
208
|
-
| `
|
|
209
|
-
|
|
|
210
|
-
|
|
|
211
|
-
|
|
|
159
|
+
| Problem | Solution |
|
|
160
|
+
|---------|----------|
|
|
161
|
+
| `better-sqlite3` build fails | Install build tools: `xcode-select --install` (macOS), `sudo apt install build-essential python3` (Linux), or Visual Studio C++ Build Tools (Windows) |
|
|
162
|
+
| Ollama not detected | Ensure Ollama is running (`ollama serve`) and accessible at `http://localhost:11434` |
|
|
163
|
+
| Port 7700 in use | Set `OHWOW_PORT=7701` or any free port |
|
|
164
|
+
| WhatsApp QR expired | Restart ohwow and scan the new QR within 60 seconds |
|
|
165
|
+
| `EACCES` on global install | Use `npm install ohwow -g --prefix ~/.npm-global` or fix npm permissions |
|
|
212
166
|
|
|
213
|
-
##
|
|
167
|
+
## Community
|
|
214
168
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
169
|
+
- [Discord](https://discord.gg/WUGnGqceeY)
|
|
170
|
+
- [Contributing](./CONTRIBUTING.md)
|
|
171
|
+
- [Architecture](./ARCHITECTURE.md)
|
|
172
|
+
- [Security](./SECURITY.md)
|
|
173
|
+
- [Governance](./GOVERNANCE.md)
|
|
174
|
+
- Email: ogsus@ohwow.fun
|
|
221
175
|
|
|
222
176
|
## License
|
|
223
177
|
|
|
224
|
-
|
|
178
|
+
[MIT](./LICENSE)
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DatabaseAdapter Interface (Runtime Copy)
|
|
5
|
+
*
|
|
6
|
+
* Abstracts the query builder pattern used by Supabase's PostgREST client.
|
|
7
|
+
* Both the Supabase adapter (cloud) and SQLite adapter (local runtime)
|
|
8
|
+
* implement this interface, allowing all agent services to work with either backend.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the SupabaseClient chaining API: .from(table).select().eq().single()
|
|
11
|
+
*/
|
|
12
|
+
interface DbResult<T> {
|
|
13
|
+
data: T | null;
|
|
14
|
+
error: DbError | null;
|
|
15
|
+
count?: number | null;
|
|
16
|
+
}
|
|
17
|
+
interface DbError {
|
|
18
|
+
message: string;
|
|
19
|
+
code?: string;
|
|
20
|
+
details?: string;
|
|
21
|
+
hint?: string;
|
|
22
|
+
}
|
|
23
|
+
interface FilterBuilder<T> {
|
|
24
|
+
eq(column: string, value: unknown): FilterBuilder<T>;
|
|
25
|
+
neq(column: string, value: unknown): FilterBuilder<T>;
|
|
26
|
+
gt(column: string, value: unknown): FilterBuilder<T>;
|
|
27
|
+
gte(column: string, value: unknown): FilterBuilder<T>;
|
|
28
|
+
lt(column: string, value: unknown): FilterBuilder<T>;
|
|
29
|
+
lte(column: string, value: unknown): FilterBuilder<T>;
|
|
30
|
+
in(column: string, values: unknown[]): FilterBuilder<T>;
|
|
31
|
+
is(column: string, value: null | boolean): FilterBuilder<T>;
|
|
32
|
+
or(filters: string, options?: {
|
|
33
|
+
foreignTable?: string;
|
|
34
|
+
}): FilterBuilder<T>;
|
|
35
|
+
not(column: string, operator: string, value: unknown): FilterBuilder<T>;
|
|
36
|
+
order(column: string, options?: {
|
|
37
|
+
ascending?: boolean;
|
|
38
|
+
}): FilterBuilder<T>;
|
|
39
|
+
limit(count: number): FilterBuilder<T>;
|
|
40
|
+
range(from: number, to: number): FilterBuilder<T>;
|
|
41
|
+
single(): PromiseLike<DbResult<T>>;
|
|
42
|
+
maybeSingle(): PromiseLike<DbResult<T | null>>;
|
|
43
|
+
then<TResult1 = DbResult<T[]>, TResult2 = never>(onfulfilled?: ((value: DbResult<T[]>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
44
|
+
}
|
|
45
|
+
type SelectBuilder<T> = FilterBuilder<T>;
|
|
46
|
+
interface InsertBuilder<T> {
|
|
47
|
+
select(columns?: string): FilterBuilder<T>;
|
|
48
|
+
then<TResult1 = DbResult<null>, TResult2 = never>(onfulfilled?: ((value: DbResult<null>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
49
|
+
}
|
|
50
|
+
interface UpdateBuilder<T> extends FilterBuilder<T> {
|
|
51
|
+
eq(column: string, value: unknown): UpdateBuilder<T>;
|
|
52
|
+
neq(column: string, value: unknown): UpdateBuilder<T>;
|
|
53
|
+
select(columns?: string): FilterBuilder<T>;
|
|
54
|
+
}
|
|
55
|
+
type DeleteBuilder<T> = FilterBuilder<T>;
|
|
56
|
+
interface TableBuilder<T = Record<string, unknown>> {
|
|
57
|
+
select(columns?: string, options?: {
|
|
58
|
+
count?: 'exact';
|
|
59
|
+
head?: boolean;
|
|
60
|
+
}): SelectBuilder<T>;
|
|
61
|
+
insert(data: Partial<T> | Partial<T>[]): InsertBuilder<T>;
|
|
62
|
+
update(data: Partial<T>): UpdateBuilder<T>;
|
|
63
|
+
delete(): DeleteBuilder<T>;
|
|
64
|
+
}
|
|
65
|
+
interface DatabaseAdapter {
|
|
66
|
+
from<T = Record<string, unknown>>(table: string): TableBuilder<T>;
|
|
67
|
+
rpc<T = unknown>(fn: string, params?: Record<string, unknown>): PromiseLike<DbResult<T>>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* SQLite Adapter
|
|
72
|
+
*
|
|
73
|
+
* Implements the DatabaseAdapter interface using better-sqlite3.
|
|
74
|
+
* Translates the SupabaseClient-shaped query builder API into SQL queries
|
|
75
|
+
* against a local SQLite database.
|
|
76
|
+
*
|
|
77
|
+
* Used by the local runtime for local data plane storage.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Map of registered RPC functions for the SQLite adapter.
|
|
82
|
+
* In Supabase, .rpc() calls server-side Postgres functions.
|
|
83
|
+
* In SQLite, we register JS functions that perform the same logic.
|
|
84
|
+
*/
|
|
85
|
+
type RpcHandler = (params: Record<string, unknown>) => unknown;
|
|
86
|
+
interface SqliteAdapterOptions {
|
|
87
|
+
rpcHandlers?: Record<string, RpcHandler>;
|
|
88
|
+
}
|
|
89
|
+
declare function createSqliteAdapter(db: Database.Database, options?: SqliteAdapterOptions): DatabaseAdapter;
|
|
90
|
+
|
|
91
|
+
export { type SqliteAdapterOptions, createSqliteAdapter };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
|
+
function F(s,e){return{message:s,code:e}}function q(s,e){return{data:s,error:null,count:e}}function f(s,e){return{data:null,error:F(s,e)}}function A(s){let e=s.split(","),i=[],o=[];for(let p of e){let c=p.trim().split(".");if(c.length<3)continue;let l=c[0],t=c[1],r=c.slice(2).join(".");switch(t){case"eq":i.push(`${l} = ?`),o.push(r);break;case"neq":i.push(`${l} != ?`),o.push(r);break;case"gt":i.push(`${l} > ?`),o.push(r);break;case"gte":i.push(`${l} >= ?`),o.push(r);break;case"lt":i.push(`${l} < ?`),o.push(r);break;case"lte":i.push(`${l} <= ?`),o.push(r);break;case"is":r==="null"?i.push(`${l} IS NULL`):r==="true"?i.push(`${l} = 1`):r==="false"&&i.push(`${l} = 0`);break;case"ilike":i.push(`${l} LIKE ? COLLATE NOCASE`),o.push(r.replace(/%25/g,"%"));break;case"like":i.push(`${l} LIKE ?`),o.push(r);break;case"in":{let n=r.replace(/^\(/,"").replace(/\)$/,"").split(",");i.push(`${l} IN (${n.map(()=>"?").join(",")})`),o.push(...n)}break;default:i.push(`${l} ${t} ?`),o.push(r)}}return{sql:`(${i.join(" OR ")})`,params:o}}function O(s){if(s.conditions.length===0)return{sql:"",params:[]};let e=[],i=[];for(let o of s.conditions)e.push(o.sql),i.push(...o.params);return{sql:` WHERE ${e.join(" AND ")}`,params:i}}function B(s){return s.orderClauses.length===0?"":` ORDER BY ${s.orderClauses.join(", ")}`}function C(s){if(s.rangeFrom!==void 0&&s.rangeTo!==void 0)return` LIMIT ${s.rangeTo-s.rangeFrom+1} OFFSET ${s.rangeFrom}`;if(s.limitValue!==void 0){let e=s.offsetValue?` OFFSET ${s.offsetValue}`:"";return` LIMIT ${s.limitValue}${e}`}return""}function S(s){if(!s)return s;let e={...s};for(let[i,o]of Object.entries(e))if(typeof o=="string"&&(o.startsWith("{")&&o.endsWith("}")||o.startsWith("[")&&o.endsWith("]")))try{e[i]=JSON.parse(o)}catch{}return e}function y(s,e,i,o,p,c,l){let t={eq(r,n){return e.conditions.push({sql:`${r} = ?`,params:[n]}),t},neq(r,n){return e.conditions.push({sql:`${r} != ?`,params:[n]}),t},gt(r,n){return e.conditions.push({sql:`${r} > ?`,params:[n]}),t},gte(r,n){return e.conditions.push({sql:`${r} >= ?`,params:[n]}),t},lt(r,n){return e.conditions.push({sql:`${r} < ?`,params:[n]}),t},lte(r,n){return e.conditions.push({sql:`${r} <= ?`,params:[n]}),t},in(r,n){if(n.length===0)e.conditions.push({sql:"1 = 0",params:[]});else{let a=n.map(()=>"?").join(", ");e.conditions.push({sql:`${r} IN (${a})`,params:n})}return t},is(r,n){return n===null?e.conditions.push({sql:`${r} IS NULL`,params:[]}):e.conditions.push({sql:`${r} = ?`,params:[n?1:0]}),t},or(r){let n=A(r);return e.conditions.push(n),t},not(r,n,a){switch(n){case"eq":e.conditions.push({sql:`${r} != ?`,params:[a]});break;case"is":a===null&&e.conditions.push({sql:`${r} IS NOT NULL`,params:[]});break;default:e.conditions.push({sql:`NOT (${r} ${n} ?)`,params:[a]})}return t},order(r,n){let a=n?.ascending===!1?"DESC":"ASC";return e.orderClauses.push(`${r} ${a}`),t},limit(r){return e.limitValue=r,t},range(r,n){return e.rangeFrom=r,e.rangeTo=n,t},single(){return{then(r,n){try{let a=w(s,e,i,o,p,c,l),u=a.data;if(!u||Array.isArray(u)&&u.length===0){let d=f("Row not found","PGRST116");return Promise.resolve(d).then(r,n)}if(Array.isArray(u)&&u.length>1){let d=f("Multiple rows returned","PGRST116");return Promise.resolve(d).then(r,n)}let m=Array.isArray(u)?u[0]:u;return Promise.resolve(q(m,a.count)).then(r,n)}catch(a){let u=f(a.message);return n?Promise.resolve(u).then(r,n):Promise.resolve(u).then(r)}}}},maybeSingle(){return{then(r,n){try{let a=w(s,e,i,o,p,c,l),u=a.data;if(!u||Array.isArray(u)&&u.length===0)return Promise.resolve(q(null,a.count)).then(r,n);let m=Array.isArray(u)?u[0]:u;return Promise.resolve(q(m,a.count)).then(r,n)}catch(a){let u=f(a.message);return Promise.resolve(u).then(r,n)}}}},then(r,n){try{let a=w(s,e,i,o,p,c,l);return Promise.resolve(a).then(r,n)}catch(a){let u=f(a.message);return Promise.resolve(u).then(r,n)}}};return t}function w(s,e,i,o,p,c,l){let t=O(e),r=B(e),n=C(e);switch(i){case"select":{let a=o||"*";if(p?.head===!0&&p?.count==="exact"){let g=`SELECT COUNT(*) as count FROM ${e.table}${t.sql}`,h=s.prepare(g).get(...t.params);return{data:[],error:null,count:h.count}}let m=`SELECT ${a} FROM ${e.table}${t.sql}${r}${n}`,d=s.prepare(m).all(...t.params);d=d.map(g=>S(g));let T=null;if(p?.count==="exact"){let g=`SELECT COUNT(*) as count FROM ${e.table}${t.sql}`;T=s.prepare(g).get(...t.params).count}return{data:d,error:null,count:T}}case"insert":{let a=Array.isArray(c)?c:[c],u=[];for(let m of a){let d=m,T=Object.keys(d),g=T.map(R=>{let k=d[R];return k!==null&&typeof k=="object"?JSON.stringify(k):k}),h=T.map(()=>"?").join(", "),$=`INSERT INTO ${e.table} (${T.join(", ")}) VALUES (${h})`;s.prepare($).run(...g);let b=s.prepare("SELECT last_insert_rowid() as id").get(),E=s.prepare(`SELECT * FROM ${e.table} WHERE rowid = ?`).get(b.id);E&&u.push(S(E))}return{data:u,error:null}}case"update":{let a=l,u=Object.keys(a),m=u.map($=>`${$} = ?`).join(", "),d=u.map($=>{let b=a[$];return b!==null&&typeof b=="object"?JSON.stringify(b):b}),T=`UPDATE ${e.table} SET ${m}${t.sql}`;s.prepare(T).run(...d,...t.params);let g=`SELECT * FROM ${e.table}${t.sql}${r}${n}`,h=s.prepare(g).all(...t.params);return h=h.map($=>S($)),{data:h,error:null}}case"delete":{let a=`DELETE FROM ${e.table}${t.sql}`;return s.prepare(a).run(...t.params),{data:[],error:null}}}}function P(s,e){let i=e?.rpcHandlers??{};return{from(o){return{select(p,c){return y(s,{table:o,conditions:[],orderClauses:[]},"select",p,c)},insert(p){let c={table:o,conditions:[],orderClauses:[]};return{select(t){return y(s,c,"insert",t,void 0,p)},then(t,r){try{let n=w(s,c,"insert",void 0,void 0,p);return Promise.resolve({data:null,error:n.error}).then(t,r)}catch(n){let a=f(n.message);return Promise.resolve(a).then(t,r)}}}},update(p){let c={table:o,conditions:[],orderClauses:[]},t=y(s,c,"update",void 0,void 0,void 0,p);return t.select=r=>y(s,c,"update",r,void 0,void 0,p),t},delete(){return y(s,{table:o,conditions:[],orderClauses:[]},"delete")}}},rpc(o,p){let c=i[o];if(!c)return Promise.resolve(f(`RPC function '${o}' not registered`));try{let l=c(p??{});return Promise.resolve(q(l))}catch(l){return Promise.resolve(f(l.message))}}}}export{P as createSqliteAdapter};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,91 +1,2 @@
|
|
|
1
|
-
import Database from 'better-sqlite3';
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
* DatabaseAdapter Interface (Runtime Copy)
|
|
5
|
-
*
|
|
6
|
-
* Abstracts the query builder pattern used by Supabase's PostgREST client.
|
|
7
|
-
* Both the Supabase adapter (cloud) and SQLite adapter (enterprise runtime)
|
|
8
|
-
* implement this interface, allowing all agent services to work with either backend.
|
|
9
|
-
*
|
|
10
|
-
* Mirrors the SupabaseClient chaining API: .from(table).select().eq().single()
|
|
11
|
-
*/
|
|
12
|
-
interface DbResult<T> {
|
|
13
|
-
data: T | null;
|
|
14
|
-
error: DbError | null;
|
|
15
|
-
count?: number | null;
|
|
16
|
-
}
|
|
17
|
-
interface DbError {
|
|
18
|
-
message: string;
|
|
19
|
-
code?: string;
|
|
20
|
-
details?: string;
|
|
21
|
-
hint?: string;
|
|
22
|
-
}
|
|
23
|
-
interface FilterBuilder<T> {
|
|
24
|
-
eq(column: string, value: unknown): FilterBuilder<T>;
|
|
25
|
-
neq(column: string, value: unknown): FilterBuilder<T>;
|
|
26
|
-
gt(column: string, value: unknown): FilterBuilder<T>;
|
|
27
|
-
gte(column: string, value: unknown): FilterBuilder<T>;
|
|
28
|
-
lt(column: string, value: unknown): FilterBuilder<T>;
|
|
29
|
-
lte(column: string, value: unknown): FilterBuilder<T>;
|
|
30
|
-
in(column: string, values: unknown[]): FilterBuilder<T>;
|
|
31
|
-
is(column: string, value: null | boolean): FilterBuilder<T>;
|
|
32
|
-
or(filters: string, options?: {
|
|
33
|
-
foreignTable?: string;
|
|
34
|
-
}): FilterBuilder<T>;
|
|
35
|
-
not(column: string, operator: string, value: unknown): FilterBuilder<T>;
|
|
36
|
-
order(column: string, options?: {
|
|
37
|
-
ascending?: boolean;
|
|
38
|
-
}): FilterBuilder<T>;
|
|
39
|
-
limit(count: number): FilterBuilder<T>;
|
|
40
|
-
range(from: number, to: number): FilterBuilder<T>;
|
|
41
|
-
single(): PromiseLike<DbResult<T>>;
|
|
42
|
-
maybeSingle(): PromiseLike<DbResult<T | null>>;
|
|
43
|
-
then<TResult1 = DbResult<T[]>, TResult2 = never>(onfulfilled?: ((value: DbResult<T[]>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
44
|
-
}
|
|
45
|
-
type SelectBuilder<T> = FilterBuilder<T>;
|
|
46
|
-
interface InsertBuilder<T> {
|
|
47
|
-
select(columns?: string): FilterBuilder<T>;
|
|
48
|
-
then<TResult1 = DbResult<null>, TResult2 = never>(onfulfilled?: ((value: DbResult<null>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
49
|
-
}
|
|
50
|
-
interface UpdateBuilder<T> extends FilterBuilder<T> {
|
|
51
|
-
eq(column: string, value: unknown): UpdateBuilder<T>;
|
|
52
|
-
neq(column: string, value: unknown): UpdateBuilder<T>;
|
|
53
|
-
select(columns?: string): FilterBuilder<T>;
|
|
54
|
-
}
|
|
55
|
-
type DeleteBuilder<T> = FilterBuilder<T>;
|
|
56
|
-
interface TableBuilder<T = Record<string, unknown>> {
|
|
57
|
-
select(columns?: string, options?: {
|
|
58
|
-
count?: 'exact';
|
|
59
|
-
head?: boolean;
|
|
60
|
-
}): SelectBuilder<T>;
|
|
61
|
-
insert(data: Partial<T> | Partial<T>[]): InsertBuilder<T>;
|
|
62
|
-
update(data: Partial<T>): UpdateBuilder<T>;
|
|
63
|
-
delete(): DeleteBuilder<T>;
|
|
64
|
-
}
|
|
65
|
-
interface DatabaseAdapter {
|
|
66
|
-
from<T = Record<string, unknown>>(table: string): TableBuilder<T>;
|
|
67
|
-
rpc<T = unknown>(fn: string, params?: Record<string, unknown>): PromiseLike<DbResult<T>>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* SQLite Adapter
|
|
72
|
-
*
|
|
73
|
-
* Implements the DatabaseAdapter interface using better-sqlite3.
|
|
74
|
-
* Translates the SupabaseClient-shaped query builder API into SQL queries
|
|
75
|
-
* against a local SQLite database.
|
|
76
|
-
*
|
|
77
|
-
* Used by the enterprise runtime for local data plane storage.
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Map of registered RPC functions for the SQLite adapter.
|
|
82
|
-
* In Supabase, .rpc() calls server-side Postgres functions.
|
|
83
|
-
* In SQLite, we register JS functions that perform the same logic.
|
|
84
|
-
*/
|
|
85
|
-
type RpcHandler = (params: Record<string, unknown>) => unknown;
|
|
86
|
-
interface SqliteAdapterOptions {
|
|
87
|
-
rpcHandlers?: Record<string, RpcHandler>;
|
|
88
|
-
}
|
|
89
|
-
declare function createSqliteAdapter(db: Database.Database, options?: SqliteAdapterOptions): DatabaseAdapter;
|
|
90
|
-
|
|
91
|
-
export { type SqliteAdapterOptions, createSqliteAdapter };
|
|
2
|
+
export { }
|