symbio 0.0.0-stage → 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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +201 -3
- package/bin/symbio.js +217 -0
- package/dist/server/api.js +611 -0
- package/dist/server/assets.js +154 -0
- package/dist/server/bus.js +9 -0
- package/dist/server/connections.js +227 -0
- package/dist/server/costs.js +175 -0
- package/dist/server/docs.js +97 -0
- package/dist/server/folder-picker.js +121 -0
- package/dist/server/git.js +38 -0
- package/dist/server/guard.js +135 -0
- package/dist/server/housekeeping.js +119 -0
- package/dist/server/index.js +133 -0
- package/dist/server/inspect.js +136 -0
- package/dist/server/mcp.js +74 -0
- package/dist/server/opencode/client.js +152 -0
- package/dist/server/opencode/runtime.js +326 -0
- package/dist/server/origin.js +15 -0
- package/dist/server/plan.js +162 -0
- package/dist/server/prompts.js +265 -0
- package/dist/server/providers.js +131 -0
- package/dist/server/remote.js +271 -0
- package/dist/server/secrets.js +105 -0
- package/dist/server/ssh.js +146 -0
- package/dist/server/store.js +167 -0
- package/dist/server/team.js +1440 -0
- package/dist/server/tools.js +409 -0
- package/dist/server/ui-token.js +29 -0
- package/dist/server/updates.js +55 -0
- package/dist/server/workspaces.js +133 -0
- package/dist/web/assets/index-Cbd1sDvV.css +1 -0
- package/dist/web/assets/index-DrIslUUT.js +52 -0
- package/dist/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/web/index.html +14 -0
- package/package.json +73 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to Symbio. The format follows [Keep a Changelog](https://keepachangelog.com/), and
|
|
4
|
+
versions follow [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.2.0]
|
|
9
|
+
|
|
10
|
+
- Installable with `npm install -g symbio`; brings its own OpenCode (pinned to 2.1.x).
|
|
11
|
+
- Commands: `sym` (start in the background and open), `sym stop`, `sym status`, `sym open`,
|
|
12
|
+
`sym logs`, `sym doctor`, `sym run`.
|
|
13
|
+
- The projects page points to Settings → Model providers when no provider is connected.
|
|
14
|
+
|
|
15
|
+
- Costs include sub-agents (OpenCode's own subagents such as "explore"), under their own model.
|
|
16
|
+
- Daily housekeeping: backups of Symbio's state (last 7 days), a check for work that exists only on
|
|
17
|
+
this machine and for helpers' build outputs; the human and core are told, core can push and clean
|
|
18
|
+
with the new `clean_build_outputs` tool, and Project settings → Storage & backups shows it all.
|
|
19
|
+
|
|
20
|
+
- Long chats load in pages: the latest messages first, earlier ones when scrolling up.
|
|
21
|
+
- The loop guard only counts conversation (not task or asset traffic), is configurable per project
|
|
22
|
+
(Settings → Team) and tells the human when it blocks.
|
|
23
|
+
- Usage per agent and model, including cache reads, earlier sessions and stopped agents, with an
|
|
24
|
+
estimated cost from editable model prices (Settings → Model prices).
|
|
25
|
+
- Rename helpers; the old name keeps working.
|
|
26
|
+
- Free shared assets are granted automatically; core only decides on contention.
|
|
27
|
+
- Fresh sessions for long-running agents (button, or automatically after each merged task), with
|
|
28
|
+
a handoff note; earlier sessions stay readable.
|
|
29
|
+
- Leftover helper folders of removed projects are cleaned up at startup.
|
|
30
|
+
- After an update, each project's coordinator is told what changed for the agents (a note, it
|
|
31
|
+
isn't woken up); the human sees the same list in Messages.
|
|
32
|
+
|
|
33
|
+
## [0.1.0]
|
|
34
|
+
|
|
35
|
+
First version.
|
|
36
|
+
|
|
37
|
+
- Projects from any folder: inspection, git setup, onboarding of existing folders, project docs in
|
|
38
|
+
the project or private.
|
|
39
|
+
- core (lead agent) that scopes, plans, delegates, reviews and merges; helpers in their own git
|
|
40
|
+
worktrees; stop and restart helpers.
|
|
41
|
+
- Messages between agents that wake the recipient; shared assets with core as gatekeeper.
|
|
42
|
+
- Plan approval and an animated kanban; *Needs you* inbox for questions, approvals and
|
|
43
|
+
permission requests; pause and resume.
|
|
44
|
+
- GitHub and SSH connections with an encrypted vault and optional per-action approval.
|
|
45
|
+
- Model providers managed from Settings; allowed models per project; per-agent model switching;
|
|
46
|
+
automatic model fallback.
|
|
47
|
+
- Supervisor, watchdog, memory recycling and tool re-registration for reliability.
|
|
48
|
+
- Safety rules for agents: no killing processes by name, no reading SSH keys or secrets, installs
|
|
49
|
+
need approval.
|
|
50
|
+
- Security: a UI key on every API request, protection against cross-site requests and DNS
|
|
51
|
+
rebinding, OpenCode's password kept out of agent shells, hardened GitHub push/pull, SSH host key
|
|
52
|
+
pinning, approvals stored outside the project, symlink-safe paths, debug routes off by default.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LinKzy
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,201 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Symbio
|
|
2
|
+
|
|
3
|
+
**Run a team of AI agents on your project, from your browser.**
|
|
4
|
+
|
|
5
|
+
Symbio is a local web app that coordinates several [OpenCode](https://opencode.ai) agents working on
|
|
6
|
+
the same folder. You talk to one lead agent, **core**. It works out the scope with you, proposes a
|
|
7
|
+
plan, and hands the work to helper agents, each in its own copy of the project. The agents message
|
|
8
|
+
each other directly, ask you when they need a decision, and you watch it all happen on a live board.
|
|
9
|
+
|
|
10
|
+
It isn't tied to software. A project is just a folder and a goal: code, a book, research, a data
|
|
11
|
+
cleanup, a set of documents. Symbio provides the machinery (messaging, isolation, coordination,
|
|
12
|
+
safety); the agents decide how to organize the work.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
> **Built with AI.** Symbio was designed and written in collaboration with AI coding agents, and
|
|
17
|
+
> it's meant to be worked on the same way. The documentation for agents (and for humans who want the
|
|
18
|
+
> details) lives in [`docs/`](docs/README.md); [`AGENTS.md`](AGENTS.md) is the entry point for a coding
|
|
19
|
+
> agent opening this repository.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## A quick tour
|
|
24
|
+
|
|
25
|
+
These screenshots come from a real session. The project is a pocket travel guide, *Lisbon on a
|
|
26
|
+
Budget*, run on free models: the conversations, plan and messages were produced by the agents, not
|
|
27
|
+
mocked.
|
|
28
|
+
|
|
29
|
+
**1. Start from a folder.** Pick an empty folder for something new, or an existing one for core to
|
|
30
|
+
study first. Symbio can set up version history so helpers can work side by side.
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
**2. Approve the plan.** After asking a couple of questions, core proposes a plan with
|
|
35
|
+
dependencies and declares the shared assets. Nothing starts until you approve.
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
**3. core delegates.** It creates a helper for the first task and gives it a precise brief. The
|
|
40
|
+
helper works on its own branch; the shared `notes` folder is held by it until it's done.
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
**4. Follow the work.** The board moves by itself as helpers pick up, finish and hand in tasks.
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
**5. See every message.** Requests, asset grants, plan updates and Symbio's own notices, all in one
|
|
49
|
+
place. Here Symbio was restarted mid-task, and it told the helper to pick up where it left off.
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
## What it does
|
|
54
|
+
|
|
55
|
+
- **One conversation, a whole team.** You chat with core. It asks what you want, studies an
|
|
56
|
+
existing folder before touching it, and proposes a plan of tasks for you to approve.
|
|
57
|
+
- **Helpers that work in parallel without collisions.** Each helper gets its own git worktree and
|
|
58
|
+
branch. core reviews and merges their work. Helpers are only created when work can really run in
|
|
59
|
+
parallel; one helper is the default.
|
|
60
|
+
- **Agents talk to each other.** Messages go straight from agent to agent through Symbio, and
|
|
61
|
+
delivering a message wakes the recipient. No copy-pasting between terminals.
|
|
62
|
+
- **Shared assets with a gatekeeper.** Anything several agents could change (a file pattern, a
|
|
63
|
+
database, a spreadsheet) can be declared a *shared asset*. Agents request it from core, wait for
|
|
64
|
+
the grant and release it when done. Changes to an asset an agent wasn't granted are flagged at
|
|
65
|
+
merge.
|
|
66
|
+
- **A live plan.** The Plan tab is an animated kanban that moves by itself as helpers pick up
|
|
67
|
+
tasks, ask for review and get merged.
|
|
68
|
+
- **You stay in control.** Questions, plan approvals, permission requests (like installing
|
|
69
|
+
software) and deploy approvals collect in a **Needs you** inbox. Pause one team or everything at
|
|
70
|
+
once.
|
|
71
|
+
- **Connections without exposing secrets.** Give agents a GitHub account or an SSH server. They
|
|
72
|
+
ask Symbio to push, call the API or deploy, and Symbio does it with credentials the agents never
|
|
73
|
+
see. Each connection can require your approval for every action.
|
|
74
|
+
- **Resilient.** A supervisor restarts OpenCode if it crashes, a watchdog unsticks hung agents, and
|
|
75
|
+
when a model fails (quota, outage) the agent switches to the next allowed model and carries on.
|
|
76
|
+
- **Safety rails.** Agents can't kill processes by name (which would take down your terminals and
|
|
77
|
+
editors), can't read your SSH keys or Symbio's secret store, and must ask before installing
|
|
78
|
+
software.
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
- **Node.js 22+**
|
|
83
|
+
- **Git** (recommended): it isolates helpers from each other. Without git, helpers share the folder
|
|
84
|
+
and rely on shared-asset grants alone.
|
|
85
|
+
- A model provider: OpenCode's own, or any you connect in Symbio's **Settings → Model providers**
|
|
86
|
+
(API key or browser sign-in).
|
|
87
|
+
|
|
88
|
+
Symbio brings its own copy of [OpenCode](https://opencode.ai), pinned to a version it's tested
|
|
89
|
+
with, and runs it privately. It shares your OpenCode configuration and provider logins, so
|
|
90
|
+
providers you already use in OpenCode show up in Symbio too.
|
|
91
|
+
|
|
92
|
+
Symbio has been developed and used on **Windows 11**. It's written to be cross-platform (macOS and
|
|
93
|
+
Linux are expected to work), but those haven't been tested yet. Reports are welcome.
|
|
94
|
+
|
|
95
|
+
## Getting started
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
npm install -g symbio
|
|
99
|
+
sym # starts Symbio in the background and opens it in your browser
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Symbio opens its page with a private key in the link (it also prints the link). The browser
|
|
103
|
+
remembers the key, so after that `http://127.0.0.1:4310` is enough. The key stops other programs
|
|
104
|
+
on your computer, including the agents, from using Symbio's API behind your back.
|
|
105
|
+
|
|
106
|
+
| Command | |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `sym` | Start Symbio in the background and open it (or just open it, if it's running). Closing the terminal doesn't stop it. |
|
|
109
|
+
| `sym stop` | Stop Symbio. Agents that were working pick up where they left off on the next start. |
|
|
110
|
+
| `sym status` | Whether it's running, OpenCode's state, and each project's agents. |
|
|
111
|
+
| `sym open` | Open Symbio in the browser. |
|
|
112
|
+
| `sym logs` | The last lines of the log (`~/.symbio/logs/symbio.log`). |
|
|
113
|
+
| `sym doctor` | Check Node.js, git, OpenCode, the port and model providers. |
|
|
114
|
+
| `sym run` | Run in the foreground, with the log in the terminal. |
|
|
115
|
+
|
|
116
|
+
`symbio` works too, as the long name of `sym`. To run from a clone instead: `npm install`,
|
|
117
|
+
`npm run build`, then `npm link` once to get the commands.
|
|
118
|
+
|
|
119
|
+
## How to use it
|
|
120
|
+
|
|
121
|
+
1. **Connect your models.** Open **Settings → Model providers** and check that the providers you
|
|
122
|
+
use are there. Add one with an API key or a browser sign-in, and remove the ones you don't use
|
|
123
|
+
so the model list stays short.
|
|
124
|
+
2. **Create a project.** On the Projects page, click **New project**:
|
|
125
|
+
- **Folder:** browse for or paste a folder. An empty folder starts a new project; an existing
|
|
126
|
+
one is studied first. Symbio offers to set up git if the folder doesn't have it.
|
|
127
|
+
- **Setup:** choose where the project documentation goes (in the project, or private), and which
|
|
128
|
+
connections (GitHub, servers) the agents may use.
|
|
129
|
+
- **Models:** pick the models agents may use, and the one core runs on (the crown).
|
|
130
|
+
3. **Talk to core.** Describe what you want. core asks questions, proposes a plan, and waits for
|
|
131
|
+
your approval in the **Plan** tab (it shows a *Review* badge).
|
|
132
|
+
4. **Watch and steer.** Helpers appear in the sidebar as core creates them. Click any agent to read
|
|
133
|
+
its conversation or message it directly, change its model from its header, or stop and restart
|
|
134
|
+
it. The **Messages** tab shows all traffic between agents.
|
|
135
|
+
5. **Answer what needs you.** Questions, approvals and permission requests show up in **Needs you**
|
|
136
|
+
(top right) and in the relevant chat.
|
|
137
|
+
|
|
138
|
+
### Words used in the app
|
|
139
|
+
|
|
140
|
+
| Term | Meaning |
|
|
141
|
+
|---|---|
|
|
142
|
+
| **core** | The project's lead agent. It plans, delegates, reviews and merges; it coordinates rather than doing the work itself. |
|
|
143
|
+
| **Helper** | An agent core creates for a piece of work. It works in its own git worktree (branch `symbio/<name>`). |
|
|
144
|
+
| **Shared asset** | Something more than one agent could change. Access goes through core: request, grant, release. |
|
|
145
|
+
| **Connection** | A GitHub account or SSH server agents can use through Symbio, without ever seeing its credentials. |
|
|
146
|
+
| **Allowed models** | The models a project's agents may run on. If one fails, agents fall back to the next. |
|
|
147
|
+
|
|
148
|
+
## Where your data lives
|
|
149
|
+
|
|
150
|
+
| Location | What |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `~/.symbio/config.json` | The list of projects |
|
|
153
|
+
| `~/.symbio/connections.json` | Connection settings (no secrets) |
|
|
154
|
+
| `~/.symbio/secrets.enc` | Connection secrets, encrypted with a key kept in your OS credential store |
|
|
155
|
+
| `~/.symbio/worktrees/` | Helpers' working copies |
|
|
156
|
+
| `~/.symbio/backups/` | Daily backups of Symbio's state (the last 7 days) |
|
|
157
|
+
| `<project>/.symbio/` | Project state: agents, plan, message log, shared assets (git-ignored automatically) |
|
|
158
|
+
| `<project>/AGENTS.md`, `<project>/docs/` | Project documentation for agents (unless you chose *private*) |
|
|
159
|
+
| OpenCode's database | The conversations themselves. Every agent is an OpenCode session, so you can also open one with `opencode -s <session-id>`. |
|
|
160
|
+
|
|
161
|
+
Symbio never deletes your project files. Removing a project from Symbio only forgets it, and can
|
|
162
|
+
optionally delete its `.symbio/` folder.
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
All optional, set as environment variables:
|
|
167
|
+
|
|
168
|
+
| Variable | Default | Purpose |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `SYMBIO_PORT` | `4310` | Port for the UI and API (bound to 127.0.0.1 only) |
|
|
171
|
+
| `SYMBIO_HOME` | `~/.symbio` | Where Symbio keeps its global data |
|
|
172
|
+
| `SYMBIO_NO_OPEN` | | Set to `1` to not open the browser on start (use the link Symbio prints) |
|
|
173
|
+
| `SYMBIO_DEBUG` | | Set to `1` to enable the read-only OpenCode debug routes (for development) |
|
|
174
|
+
| `SYMBIO_OPENCODE_BIN` | bundled | Path to an `opencode` executable to use instead of the bundled one |
|
|
175
|
+
| `SYMBIO_STUCK_MINUTES` | `10` | Silence before the watchdog interrupts a working agent |
|
|
176
|
+
| `SYMBIO_RECYCLE_MB` | `1500` | Restart OpenCode above this memory use once every agent is idle |
|
|
177
|
+
| `SYMBIO_RECYCLE_HARD_MB` | `3000` | Restart OpenCode above this memory use regardless (agents resume) |
|
|
178
|
+
|
|
179
|
+
## Status
|
|
180
|
+
|
|
181
|
+
Symbio is young (version 0.1) and changing quickly. The core workflow works end to end: projects,
|
|
182
|
+
core and helpers, messaging, plan and kanban, shared assets, connections, providers, and recovery.
|
|
183
|
+
Planned next: npm packaging with first-run checks, an agent graph, and screenshots for agents so
|
|
184
|
+
they can see what they build. See [docs/roadmap.md](docs/roadmap.md).
|
|
185
|
+
|
|
186
|
+
## Documentation
|
|
187
|
+
|
|
188
|
+
- [docs/](docs/README.md): architecture, the agent protocol, the OpenCode API notes, development
|
|
189
|
+
and security. Written for both people and coding agents.
|
|
190
|
+
- [AGENTS.md](AGENTS.md): the short entry point for an AI agent working on this repository.
|
|
191
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md): how to set up, make changes and open a pull request.
|
|
192
|
+
- [SECURITY.md](SECURITY.md): how to report a vulnerability.
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Contributions are welcome, whether you write the code yourself or with an AI agent. Start with
|
|
197
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
[MIT](LICENSE) © LinKzy
|
package/bin/symbio.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// symbio / sym — start, stop and inspect the local Symbio server.
|
|
3
|
+
//
|
|
4
|
+
// sym start in the background (or just open it, if it's running) and open the browser
|
|
5
|
+
// sym stop stop it (agents are left as they are and pick up on the next start)
|
|
6
|
+
// sym status is it running? where? which version?
|
|
7
|
+
// sym open open it in the browser
|
|
8
|
+
// sym logs the last lines of its log
|
|
9
|
+
// sym doctor check that everything Symbio needs is in place
|
|
10
|
+
// sym run run in the foreground (for debugging)
|
|
11
|
+
|
|
12
|
+
import { execFileSync, exec, spawn } from "node:child_process";
|
|
13
|
+
import { existsSync, mkdirSync, openSync, readFileSync } from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
|
|
18
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
|
+
const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
|
|
20
|
+
const PORT = Number(process.env.SYMBIO_PORT ?? 4310);
|
|
21
|
+
const HOME = process.env.SYMBIO_HOME ?? path.join(os.homedir(), ".symbio");
|
|
22
|
+
const LOG = path.join(HOME, "logs", "symbio.log");
|
|
23
|
+
const BASE = `http://127.0.0.1:${PORT}`;
|
|
24
|
+
const server = path.join(root, "dist", "server", "index.js");
|
|
25
|
+
|
|
26
|
+
const token = () => {
|
|
27
|
+
try {
|
|
28
|
+
return readFileSync(path.join(HOME, "ui-token"), "utf8").trim();
|
|
29
|
+
} catch {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const link = () => `${BASE}/?token=${token()}`;
|
|
34
|
+
|
|
35
|
+
async function api(method, p) {
|
|
36
|
+
const res = await fetch(BASE + p, { method, headers: { "x-symbio-token": token() }, signal: AbortSignal.timeout(3000) });
|
|
37
|
+
return { status: res.status, body: await res.json().catch(() => null) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** "running" (ours), "other" (something else on the port) or "stopped". */
|
|
41
|
+
async function state() {
|
|
42
|
+
try {
|
|
43
|
+
const r = await api("GET", "/api/runtime");
|
|
44
|
+
if (r.status === 200 || (r.status === 401 && r.body?.code === "locked")) return "running";
|
|
45
|
+
return "other";
|
|
46
|
+
} catch {
|
|
47
|
+
return "stopped";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function openBrowser(url) {
|
|
52
|
+
if (process.env.SYMBIO_NO_OPEN === "1") return;
|
|
53
|
+
const cmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
54
|
+
exec(cmd, () => {});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
58
|
+
|
|
59
|
+
async function start() {
|
|
60
|
+
const s = await state();
|
|
61
|
+
if (s === "running") {
|
|
62
|
+
console.log(`Symbio is running: ${link()}`);
|
|
63
|
+
openBrowser(link());
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (s === "other") {
|
|
67
|
+
console.error(`Port ${PORT} is used by another program. Start Symbio on another port: SYMBIO_PORT=4320 sym`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (!existsSync(server)) {
|
|
71
|
+
console.error(`Symbio isn't built (${server} is missing). Run "npm run build" in ${root}.`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
mkdirSync(path.dirname(LOG), { recursive: true });
|
|
75
|
+
const out = openSync(LOG, "a");
|
|
76
|
+
// Detached, so closing this terminal doesn't stop Symbio (or the agents' work).
|
|
77
|
+
const child = spawn(process.execPath, [server], {
|
|
78
|
+
detached: true,
|
|
79
|
+
stdio: ["ignore", out, out],
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
env: { ...process.env, SYMBIO_NO_OPEN: "1" },
|
|
82
|
+
});
|
|
83
|
+
child.unref();
|
|
84
|
+
process.stdout.write("Starting Symbio");
|
|
85
|
+
for (let i = 0; i < 60; i++) {
|
|
86
|
+
await sleep(1000);
|
|
87
|
+
if ((await state()) === "running") {
|
|
88
|
+
console.log(`\nSymbio is running: ${link()}\nStop it with "sym stop". Log: ${LOG}`);
|
|
89
|
+
openBrowser(link());
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(".");
|
|
93
|
+
}
|
|
94
|
+
console.error(`\nSymbio didn't come up within a minute. See the log: ${LOG}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function stop() {
|
|
99
|
+
if ((await state()) !== "running") return console.log("Symbio isn't running.");
|
|
100
|
+
await api("POST", "/api/shutdown").catch(() => {});
|
|
101
|
+
for (let i = 0; i < 30; i++) {
|
|
102
|
+
await sleep(500);
|
|
103
|
+
if ((await state()) === "stopped") return console.log("Symbio stopped. Agents that were working continue on the next start.");
|
|
104
|
+
}
|
|
105
|
+
console.error("Symbio didn't stop within 15 seconds.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function status() {
|
|
110
|
+
const s = await state();
|
|
111
|
+
console.log(`Symbio ${pkg.version} — ${s === "running" ? `running at ${BASE}` : s === "other" ? `port ${PORT} is used by another program` : "not running"}`);
|
|
112
|
+
if (s !== "running") return;
|
|
113
|
+
const runtime = (await api("GET", "/api/runtime")).body;
|
|
114
|
+
const projects = (await api("GET", "/api/projects")).body ?? [];
|
|
115
|
+
console.log(`OpenCode ${runtime?.version ?? "?"} (${runtime?.state ?? "?"}${runtime?.memoryMB ? `, ${runtime.memoryMB} MB` : ""})`);
|
|
116
|
+
for (const p of projects) {
|
|
117
|
+
const working = p.agents.filter((a) => a.status === "busy").length;
|
|
118
|
+
console.log(`- ${p.name}: ${p.agents.filter((a) => a.status !== "stopped").length} agents${working ? `, ${working} working` : ""}${p.paused ? " (paused)" : ""}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function logs() {
|
|
123
|
+
if (!existsSync(LOG)) return console.log(`No log yet (${LOG}).`);
|
|
124
|
+
const lines = readFileSync(LOG, "utf8").split(/\r?\n/);
|
|
125
|
+
console.log(lines.slice(-60).join("\n").replace(/token=[\w-]+/g, "token=…"));
|
|
126
|
+
console.log(`\n(${LOG})`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function doctor() {
|
|
130
|
+
let ok = true;
|
|
131
|
+
const check = (pass, label, fix) => {
|
|
132
|
+
console.log(`${pass ? "✔" : "✘"} ${label}${!pass && fix ? `\n → ${fix}` : ""}`);
|
|
133
|
+
if (!pass) ok = false;
|
|
134
|
+
};
|
|
135
|
+
const [major] = process.versions.node.split(".").map(Number);
|
|
136
|
+
check(major >= 22, `Node.js ${process.versions.node}`, "Symbio needs Node.js 22 or newer: https://nodejs.org");
|
|
137
|
+
let gitVersion = "";
|
|
138
|
+
try {
|
|
139
|
+
gitVersion = execFileSync("git", ["--version"], { encoding: "utf8" }).trim();
|
|
140
|
+
} catch {}
|
|
141
|
+
check(!!gitVersion, gitVersion || "git", "Install git (https://git-scm.com) so helpers can work in parallel without conflicts.");
|
|
142
|
+
let oc = "";
|
|
143
|
+
try {
|
|
144
|
+
const { resolveOpencode } = await import(pathToFileURL(path.join(root, "dist", "server", "opencode", "runtime.js")).href);
|
|
145
|
+
const { command, args, shell } = resolveOpencode();
|
|
146
|
+
oc = `${execFileSync(command, [...args, "--version"], { encoding: "utf8", shell, timeout: 20_000 }).trim()} (${command})`;
|
|
147
|
+
} catch {}
|
|
148
|
+
check(!!oc, oc ? `OpenCode ${oc}` : "OpenCode", "Reinstall Symbio (npm i -g symbio): it brings its own OpenCode.");
|
|
149
|
+
const s = await state();
|
|
150
|
+
check(s !== "other", s === "running" ? `Symbio running at ${BASE}` : s === "stopped" ? `Port ${PORT} free` : `Port ${PORT}`, `Another program uses port ${PORT}; use SYMBIO_PORT.`);
|
|
151
|
+
if (s === "running") {
|
|
152
|
+
const providers = (await api("GET", "/api/providers").catch(() => ({ body: null }))).body;
|
|
153
|
+
const n = providers?.connected?.length ?? 0;
|
|
154
|
+
check(n > 0, `${n} model provider(s) connected`, "Open Symbio → Settings → Model providers and connect one.");
|
|
155
|
+
}
|
|
156
|
+
console.log(ok ? "\nAll good." : "\nFix the items marked ✘.");
|
|
157
|
+
if (!ok) process.exitCode = 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function help() {
|
|
161
|
+
console.log(`Symbio ${pkg.version} — run a team of AI agents on your project, from your browser.
|
|
162
|
+
|
|
163
|
+
Usage: sym [command]
|
|
164
|
+
|
|
165
|
+
(none), start start Symbio in the background and open it in the browser
|
|
166
|
+
stop stop Symbio
|
|
167
|
+
status show whether it's running, and its projects
|
|
168
|
+
open open Symbio in the browser
|
|
169
|
+
logs show the last lines of the log
|
|
170
|
+
doctor check that everything Symbio needs is in place
|
|
171
|
+
run run in the foreground (logs in this terminal)
|
|
172
|
+
--version print the version
|
|
173
|
+
|
|
174
|
+
Environment: SYMBIO_PORT (default 4310), SYMBIO_HOME (default ~/.symbio), SYMBIO_NO_OPEN=1.`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const cmd = process.argv[2] ?? "start";
|
|
178
|
+
switch (cmd) {
|
|
179
|
+
case "start":
|
|
180
|
+
await start();
|
|
181
|
+
break;
|
|
182
|
+
case "stop":
|
|
183
|
+
await stop();
|
|
184
|
+
break;
|
|
185
|
+
case "status":
|
|
186
|
+
await status();
|
|
187
|
+
break;
|
|
188
|
+
case "open":
|
|
189
|
+
if ((await state()) === "running") {
|
|
190
|
+
console.log(link());
|
|
191
|
+
openBrowser(link());
|
|
192
|
+
} else console.log('Symbio isn\'t running. Start it with "sym".');
|
|
193
|
+
break;
|
|
194
|
+
case "logs":
|
|
195
|
+
logs();
|
|
196
|
+
break;
|
|
197
|
+
case "doctor":
|
|
198
|
+
await doctor();
|
|
199
|
+
break;
|
|
200
|
+
case "run":
|
|
201
|
+
await import(pathToFileURL(server).href);
|
|
202
|
+
break;
|
|
203
|
+
case "-v":
|
|
204
|
+
case "--version":
|
|
205
|
+
case "version":
|
|
206
|
+
console.log(pkg.version);
|
|
207
|
+
break;
|
|
208
|
+
case "-h":
|
|
209
|
+
case "--help":
|
|
210
|
+
case "help":
|
|
211
|
+
help();
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
console.error(`Unknown command "${cmd}".\n`);
|
|
215
|
+
help();
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|