symbiot 0.33.0 → 0.34.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/README.md +110 -79
- package/index.mjs +39 -18
- package/package.json +2 -2
- package/ui.mjs +9 -4
package/README.md
CHANGED
|
@@ -12,17 +12,17 @@ symbiot your last 7 days, written up (same as: symbiot week)
|
|
|
12
12
|
symbiot standup yesterday + today, for standup
|
|
13
13
|
symbiot todo what's still on your plate
|
|
14
14
|
symbiot app open the visual app in your browser
|
|
15
|
-
symbiot drift what's out of sync / at risk across your repos
|
|
16
15
|
symbiot push write tasks into each repo for your coding agent
|
|
17
|
-
symbiot
|
|
18
|
-
symbiot models recommend AI models for your hardware
|
|
19
|
-
symbiot setup-local set up a free local model (Ollama), one command
|
|
16
|
+
symbiot drift what's out of sync / at risk across your repos
|
|
20
17
|
symbiot login connect it to an AI (once)
|
|
21
18
|
symbiot whoami show how it's connected
|
|
22
19
|
symbiot logout forget saved credentials
|
|
23
20
|
symbiot help
|
|
24
21
|
```
|
|
25
22
|
|
|
23
|
+
A few newer extras (email, CI checks, local-model setup) are
|
|
24
|
+
[experimental](#experimental).
|
|
25
|
+
|
|
26
26
|
## Install
|
|
27
27
|
|
|
28
28
|
```bash
|
|
@@ -54,7 +54,7 @@ Tabs:
|
|
|
54
54
|
**no AI key**; reviews and suggestions use your chosen model. While the scan runs
|
|
55
55
|
the Map shows its progress.
|
|
56
56
|
- **Drift** — the [`symbiot drift`](#whats-out-of-sync-symbiot-drift) report, with
|
|
57
|
-
"fetch latest" and "check CI"
|
|
57
|
+
a "fetch latest" toggle (and an [experimental](#experimental) "check CI").
|
|
58
58
|
- **Week / Standup / Todo** — the write-ups (these use your chosen AI).
|
|
59
59
|
- **Tasks** — a checklist, grouped by kind (Fixes, Tests & CI, Docs, …). Filter by
|
|
60
60
|
type or repo, then **Send to repos** to hand just those to your agent (see
|
|
@@ -71,9 +71,9 @@ Tabs:
|
|
|
71
71
|
did** — files changed and commits made, read from git, whichever agent it was.
|
|
72
72
|
When an agent leaves **questions, options or ideas** for you, they show up on its
|
|
73
73
|
block (see [Questions from your agent](#questions-from-your-agent)).
|
|
74
|
-
- **Settings** — your AI, the folders to scan
|
|
75
|
-
[
|
|
76
|
-
|
|
74
|
+
- **Settings** — your AI, the folders to scan and your agent command, plus the
|
|
75
|
+
[experimental](#experimental) email, model recommendations and one-click local
|
|
76
|
+
model.
|
|
77
77
|
|
|
78
78
|
**Stays current by itself.** The app checks npm for a newer Symbiot every couple of
|
|
79
79
|
minutes (and whenever you come back to the window); the version you're on is shown
|
|
@@ -82,7 +82,10 @@ version, relaunches the app on the same address, and the open window reloads its
|
|
|
82
82
|
onto it. Restart `symbiot app` yourself and the open window reconnects the same way.
|
|
83
83
|
|
|
84
84
|
The app listens on port **7391** so its address survives restarts; set
|
|
85
|
-
`SYMBIOT_PORT` to use another
|
|
85
|
+
`SYMBIOT_PORT` to use another. **One app at a time:** running `symbiot app` while
|
|
86
|
+
it's already running just opens the existing window instead of starting a second
|
|
87
|
+
copy (`SYMBIOT_FORCE_NEW=1` starts one anyway). If something else holds the port,
|
|
88
|
+
it falls back to a free one.
|
|
86
89
|
It works on **Linux, macOS and Windows**; with no Chromium-family browser to open
|
|
87
90
|
the chrome-less window, it uses your default browser.
|
|
88
91
|
|
|
@@ -130,7 +133,7 @@ a template — `{dir}` is the repo path, `{prompt}` is the instruction to read
|
|
|
130
133
|
`TASKS.md` — so it works with any agent or editor:
|
|
131
134
|
|
|
132
135
|
```
|
|
133
|
-
claude -p "{prompt}" --permission-mode acceptEdits
|
|
136
|
+
claude -p "{prompt}" --permission-mode acceptEdits --allowedTools "Bash(npm test:*)" "Bash(node:*)"
|
|
134
137
|
aider --message "{prompt}" --yes
|
|
135
138
|
code {dir}
|
|
136
139
|
```
|
|
@@ -139,12 +142,16 @@ Settings shows **one-click presets** for what's installed on your machine. A
|
|
|
139
142
|
preset only fills in the command box; the saved command is what runs.
|
|
140
143
|
|
|
141
144
|
- **Agents** (all make changes, ready for your review): Claude Code, Codex
|
|
142
|
-
(OpenAI), Aider, Cursor agent, Gemini CLI.
|
|
145
|
+
(OpenAI), Aider, Cursor agent, Gemini CLI. The Claude preset may also run
|
|
146
|
+
`npm test` and `node`, so it can check its own work; a command saved from the
|
|
147
|
+
older preset is upgraded automatically, and an edited one is left alone.
|
|
143
148
|
- **Orca IDE** (any OS): opens the repo in Orca — either just the repo, to use
|
|
144
149
|
Orca's own agent, or with Claude running in a new tab. It launches Orca if it's
|
|
145
150
|
closed and waits for it to be ready first.
|
|
146
|
-
- **Editors:** VS Code, Cursor, Windsurf, Zed, Sublime
|
|
147
|
-
(on macOS, also found as `.app` bundles when the CLI
|
|
151
|
+
- **Editors — opens only, no review:** VS Code, Cursor, Windsurf, Zed, Sublime
|
|
152
|
+
Text, IntelliJ IDEA, Neovim (on macOS, also found as `.app` bundles when the CLI
|
|
153
|
+
isn't on your PATH). They just open the repo. Nothing comes back for review unless
|
|
154
|
+
you run an agent there yourself, and Neovim needs a terminal to open in.
|
|
148
155
|
|
|
149
156
|
Each run is logged to `.symbiot/agent.log` in the repo and shown live in the
|
|
150
157
|
**Agents** tab. The command is saved as `agentCmd` in
|
|
@@ -184,7 +191,6 @@ committed.
|
|
|
184
191
|
```bash
|
|
185
192
|
symbiot drift # local git facts only — fast, no network
|
|
186
193
|
symbiot drift --fetch # fetch from origin first, so "behind" is current
|
|
187
|
-
symbiot drift --ci # also check GitHub Actions state (needs gh)
|
|
188
194
|
```
|
|
189
195
|
|
|
190
196
|
A **deterministic** report of what's out of sync, stuck, or at risk across your
|
|
@@ -195,10 +201,9 @@ not new work), **uncommitted work**, **behind upstream**, **multiple worktrees**
|
|
|
195
201
|
branch** (the "merged but main didn't move" trap — split into *likely never
|
|
196
202
|
landed*, when files the PR added are missing from main, and *probably re-done*,
|
|
197
203
|
when they're all there), and — if you configure it — **production running code
|
|
198
|
-
that isn't on your default branch**.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
too: without a remote it compares against your local default branch.
|
|
204
|
+
that isn't on your default branch**. Works on local-only repos too: without a
|
|
205
|
+
remote it compares against your local default branch. (CI checks with `--ci` are
|
|
206
|
+
[experimental](#ci-status-symbiot-drift---ci).)
|
|
202
207
|
|
|
203
208
|
For the production check, add a per-repo deploy command to
|
|
204
209
|
`~/.config/symbiot/deploys.json` (read only from your own config, never from a
|
|
@@ -206,31 +211,6 @@ repo), keyed by repo path or folder name, that prints the deployed commit sha:
|
|
|
206
211
|
`{ "/path/to/repo": "ssh prod cat ~/app/.deployed-sha" }`. Also the **Drift** tab
|
|
207
212
|
in the app.
|
|
208
213
|
|
|
209
|
-
## Which model? Ask your machine
|
|
210
|
-
|
|
211
|
-
```bash
|
|
212
|
-
symbiot models
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Reads your RAM / CPU / GPU and recommends **local models by tier** (min / med / max,
|
|
216
|
-
marking which fit your RAM) to run free & private via [Ollama](https://ollama.com),
|
|
217
|
-
plus **paid** options (Claude / OpenAI / Gemini, cheap → top). Also available as
|
|
218
|
-
a button in the app's Settings.
|
|
219
|
-
|
|
220
|
-
### A free local model in one command: `symbiot setup-local`
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
symbiot setup-local # the best model for your RAM
|
|
224
|
-
symbiot setup-local --model llama3.2:3b # or pick one
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Starts Ollama if it isn't running, downloads the model (with progress), and
|
|
228
|
-
switches Symbiot over to it — free, private, nothing leaves your machine. If
|
|
229
|
-
Ollama isn't installed yet it prints the one-line install for your OS (Homebrew on
|
|
230
|
-
macOS, winget on Windows, the official script on Linux) and you re-run it after.
|
|
231
|
-
Also the **Set up a free local model** button in Settings, where the download shows
|
|
232
|
-
in the Agents tab and Symbiot switches over when it finishes.
|
|
233
|
-
|
|
234
214
|
## Connect it (once)
|
|
235
215
|
|
|
236
216
|
Pick the AI you want it to write with:
|
|
@@ -270,34 +250,6 @@ It summarises the commits **you** authored — matching all your identities in e
|
|
|
270
250
|
repo (per-repo and global email, your GitHub noreply address, your name), and
|
|
271
251
|
counting everyone if that filter would drop almost all of an active repo's history.
|
|
272
252
|
|
|
273
|
-
## Your email, without an API: `symbiot mail`
|
|
274
|
-
|
|
275
|
-
```bash
|
|
276
|
-
symbiot mail # what mail it can read, and what you sent this week
|
|
277
|
-
symbiot mail --on # use it in week / standup (--off to stop)
|
|
278
|
-
symbiot mail --add ~/Takeout/Mail/All\ mail.mbox # an export, or any mail folder
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Much of a week's work happens over email, so Week and Standup can include **what
|
|
282
|
-
you sent**. There's no Gmail or Outlook API, no OAuth, no app to register and no
|
|
283
|
-
password. Symbiot reads the mail a desktop client already keeps on your computer,
|
|
284
|
-
so anyone can link theirs the same way:
|
|
285
|
-
|
|
286
|
-
- **Thunderbird** (Linux, macOS, Windows, including Snap and Flatpak installs),
|
|
287
|
-
**Apple Mail**, **Evolution**, **KMail**, and **mutt/neomutt** or any
|
|
288
|
-
`~/Maildir` / `~/mail` are found automatically. Symbiot only looks at their
|
|
289
|
-
**Sent** folders.
|
|
290
|
-
- **Webmail only?** Add an export. Google Takeout gives you an `.mbox`, and Symbiot
|
|
291
|
-
keeps just the messages with Gmail's *Sent* label (or from one of your
|
|
292
|
-
addresses). You can also point it at any `.mbox` file or a folder of `.eml`
|
|
293
|
-
files.
|
|
294
|
-
|
|
295
|
-
It reads **headers only** (date, recipients, subject), never a message body. It's
|
|
296
|
-
**off until you switch it on**, in Settings (*Email*) or with `symbiot mail --on`.
|
|
297
|
-
**Preview** shows exactly what a write-up would see. On macOS, reading Apple Mail
|
|
298
|
-
needs Full Disk Access for your terminal. Outlook for Windows keeps mail in `.pst`
|
|
299
|
-
files, which Symbiot can't read, so export to `.mbox` or use Thunderbird.
|
|
300
|
-
|
|
301
253
|
## Where it looks
|
|
302
254
|
|
|
303
255
|
By default Symbiot scans your **home folder**. To point it at where your work
|
|
@@ -327,7 +279,7 @@ instead of hanging. The CLI shows scan progress on one line as it goes.
|
|
|
327
279
|
|
|
328
280
|
- **Reads:** your local git — commit messages and changed-file names, plus
|
|
329
281
|
`TODO`/`FIXME` markers and uncommitted changes for `todo`. If you switch
|
|
330
|
-
[email](#your-email-without-an-api-symbiot-mail) on, it also reads the headers
|
|
282
|
+
[email](#your-sent-email-without-an-api-symbiot-mail) on, it also reads the headers
|
|
331
283
|
of mail you sent (subject, recipients, date). All local.
|
|
332
284
|
- **Sends to the AI:** commit messages and dates, changed-file **names**, the
|
|
333
285
|
folder structure, `TODO`/`FIXME` lines, and (for a repo review) an excerpt of
|
|
@@ -341,6 +293,66 @@ instead of hanging. The CLI shows scan progress on one line as it goes.
|
|
|
341
293
|
- **Runs only what you set:** the agent command is yours, and deploy commands are
|
|
342
294
|
read only from your own `~/.config/symbiot/`, never from a repo.
|
|
343
295
|
|
|
296
|
+
## Experimental
|
|
297
|
+
|
|
298
|
+
These work and are tested, but are newer or depend on things Symbiot can't fully
|
|
299
|
+
check, so they may change. Everything above works without them.
|
|
300
|
+
|
|
301
|
+
### Your sent email, without an API: `symbiot mail`
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
symbiot mail # what mail it can read, and what you sent this week
|
|
305
|
+
symbiot mail --on # use it in week / standup (--off to stop)
|
|
306
|
+
symbiot mail --add ~/Takeout/Mail/All\ mail.mbox # an export, or any mail folder
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Week and Standup can include **what you sent**. It needs no Gmail or Outlook API,
|
|
310
|
+
OAuth, app registration or password: Symbiot reads the mail a desktop client
|
|
311
|
+
already keeps on your computer, so anyone can link theirs the same way.
|
|
312
|
+
**Thunderbird** (including Snap and Flatpak), **Apple Mail**, **Evolution**,
|
|
313
|
+
**KMail**, and **mutt/neomutt** or any `~/Maildir` / `~/mail` are found
|
|
314
|
+
automatically, and only their **Sent** folders are read. Webmail only? Add an
|
|
315
|
+
export: Google Takeout gives you an `.mbox`, and Symbiot keeps just the messages
|
|
316
|
+
with Gmail's *Sent* label (or from one of your addresses). Any `.mbox` file or
|
|
317
|
+
folder of `.eml` files works too.
|
|
318
|
+
|
|
319
|
+
It reads **headers only** (date, recipients, subject), never a message body, and
|
|
320
|
+
it's **off until you switch it on** in Settings (*Email*) or with
|
|
321
|
+
`symbiot mail --on`. **Preview** shows exactly what a write-up would see. Why it's
|
|
322
|
+
experimental: the tests cover mbox and Maildir, but it hasn't been tried against
|
|
323
|
+
real Apple Mail, Evolution or KMail stores. On macOS, reading Apple Mail needs Full
|
|
324
|
+
Disk Access for your terminal. Outlook for Windows (`.pst`) isn't supported:
|
|
325
|
+
export to `.mbox` or use Thunderbird.
|
|
326
|
+
|
|
327
|
+
### CI status: `symbiot drift --ci`
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
symbiot drift --ci # also check GitHub Actions state (needs gh)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Adds CI to the drift report, telling a real failing run apart from **CI that isn't
|
|
334
|
+
running at all** (jobs never started, usually a billing or spending limit) and
|
|
335
|
+
quoting GitHub's reason when it can. Also the **check CI** toggle in the Drift tab.
|
|
336
|
+
Why it's experimental: it needs the GitHub CLI (`gh`), only covers GitHub Actions,
|
|
337
|
+
and spotting "not running" is a heuristic.
|
|
338
|
+
|
|
339
|
+
### Local models: `symbiot models` and `symbiot setup-local`
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
symbiot models # recommend models for this machine
|
|
343
|
+
symbiot setup-local # the best local model for your RAM
|
|
344
|
+
symbiot setup-local --model llama3.2:3b # or pick one
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`models` reads your RAM / CPU / GPU and recommends **local models by tier** (min /
|
|
348
|
+
med / max, marking which fit your RAM) to run free and private via
|
|
349
|
+
[Ollama](https://ollama.com), plus **paid** options (Claude / OpenAI / Gemini,
|
|
350
|
+
cheap → top). `setup-local` starts Ollama if it isn't running, downloads the model
|
|
351
|
+
with progress, and switches Symbiot over to it. If Ollama isn't installed it prints
|
|
352
|
+
the one-line install for your OS and you re-run it after. Both are buttons in
|
|
353
|
+
Settings, where the download shows in the Agents tab. You can always pick a model
|
|
354
|
+
yourself with `symbiot login`.
|
|
355
|
+
|
|
344
356
|
## Config
|
|
345
357
|
|
|
346
358
|
Everything lives in `~/.config/symbiot/`: `config.json` (your AI, `scanRoots`,
|
|
@@ -353,6 +365,7 @@ conventions every repo review must respect, alongside each repo's own
|
|
|
353
365
|
`claude-haiku-4-5`, `gemini-1.5-pro`).
|
|
354
366
|
- `SYMBIOT_SCAN_TIMEOUT` — the scan time limit, in seconds (default 60).
|
|
355
367
|
- `SYMBIOT_PORT` — the app's port (default 7391).
|
|
368
|
+
- `SYMBIOT_FORCE_NEW=1` — start a second app even if one is already running.
|
|
356
369
|
- `symbiot whoami` shows the active provider, model, and where the credential
|
|
357
370
|
came from. `symbiot logout` forgets saved credentials.
|
|
358
371
|
|
|
@@ -362,26 +375,44 @@ conventions every repo review must respect, alongside each repo's own
|
|
|
362
375
|
npm test
|
|
363
376
|
```
|
|
364
377
|
|
|
365
|
-
Runs
|
|
378
|
+
Runs five suites, all against throwaway repos, an isolated `HOME` and (for the
|
|
379
|
+
update check) a fake npm registry:
|
|
366
380
|
|
|
367
381
|
- `test/load.mjs` — the shipped files parse and load: `node --check` on each
|
|
368
382
|
module, `ui.mjs` imported on its own with its page's JavaScript parsed, every
|
|
369
383
|
local import listed in package.json `"files"`, and the `bin` entry point shipped.
|
|
370
384
|
- `test/fixtures.mjs` — accuracy fixtures: the facts Symbiot collects (identity
|
|
371
385
|
matching, stale checkouts, worktrees, drift) and the `symbiot drift` report as
|
|
372
|
-
printed, the scan time limit, the agent handoff,
|
|
386
|
+
printed, the scan time limit, the agent handoff, review → send back → approve →
|
|
387
|
+
ship (including a repo that gitignores `.symbiot/`), agent questions, mail
|
|
388
|
+
ingestion (`symbiot mail` end to end), and the update command.
|
|
373
389
|
- `test/smoke.mjs` — boots `symbiot app`, runs the page's own JavaScript against
|
|
374
390
|
a fake DOM (every tab, every button), hits every endpoint the UI calls with the
|
|
375
|
-
method the UI uses,
|
|
376
|
-
the
|
|
391
|
+
method the UI uses, checks POST-only endpoints refuse GET and `/api` needs the
|
|
392
|
+
token, and checks the update banner can't loop.
|
|
393
|
+
- `test/app.mjs` — the app's lifecycle: a second `symbiot app` reuses the running
|
|
394
|
+
one (and `SYMBIOT_FORCE_NEW` / a foreign server on the port don't), and the
|
|
395
|
+
update check only offers a higher npm version.
|
|
377
396
|
- `test/install.mjs` — runs the CLI through a bin symlink, then `npm pack` +
|
|
378
397
|
global install into a temp prefix: `symbiot help`, every shipped file present,
|
|
379
398
|
and the installed `symbiot app` serving its page. Needs npm registry access for
|
|
380
399
|
dependencies; `SYMBIOT_SKIP_INSTALL_TEST=1` skips that part.
|
|
381
400
|
|
|
382
|
-
`SYMBIOT_NO_OPEN=1` stops `symbiot app` opening a window (the tests set it)
|
|
383
|
-
|
|
384
|
-
|
|
401
|
+
`SYMBIOT_NO_OPEN=1` stops `symbiot app` opening a window (the tests set it), and
|
|
402
|
+
`SYMBIOT_REGISTRY` points its update check at another registry. CI
|
|
403
|
+
(`.github/workflows/ci.yml`) runs the whole suite on every pull request.
|
|
404
|
+
|
|
405
|
+
**Releasing.** Bump the version in the pull request, merge it into `main`, then tag
|
|
406
|
+
`main`'s commit:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
git checkout main && git pull
|
|
410
|
+
git tag v$(node -p "require('./package.json').version") && git push origin --tags
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`.github/workflows/publish.yml` publishes from that tag only if it's on `main`,
|
|
414
|
+
matches `package.json`, and the full suite passes. A tag on any other branch is
|
|
415
|
+
refused, so what's on npm is always what's on `main`.
|
|
385
416
|
|
|
386
417
|
---
|
|
387
418
|
|
package/index.mjs
CHANGED
|
@@ -64,7 +64,17 @@ let LATEST_VERSION = ""; // newest symbiot on npm, checked in the background
|
|
|
64
64
|
// a.b.c numeric compare: only a HIGHER npm version is an update (a local build
|
|
65
65
|
// ahead of npm must not be offered a "newer" older one)
|
|
66
66
|
function semverGt(a, b) { const p = (v) => String(v || "").replace(/^v/, "").split(/[.-]/).slice(0, 3).map((n) => parseInt(n, 10) || 0); const x = p(a), y = p(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] > y[i]; return false; }
|
|
67
|
-
|
|
67
|
+
// SYMBIOT_REGISTRY points the check at another registry (the tests use a fake one)
|
|
68
|
+
const REGISTRY = (process.env.SYMBIOT_REGISTRY || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
69
|
+
async function checkLatest() { try { const r = await fetch(REGISTRY + "/symbiot"); if (!r.ok) return; const j = await r.json(); LATEST_VERSION = (j["dist-tags"] && j["dist-tags"].latest) || ""; } catch {} }
|
|
70
|
+
// The in-app update installs the EXACT newest version (not the `latest` tag, which
|
|
71
|
+
// npm's cache/propagation can resolve stale — that caused an update loop where the
|
|
72
|
+
// install kept re-fetching the same old version). --prefer-online skips a stale
|
|
73
|
+
// cached packument.
|
|
74
|
+
function updateCmd(latest, current, platform = process.platform) {
|
|
75
|
+
const target = latest && semverGt(latest, current) ? latest : "latest";
|
|
76
|
+
return { target, cmd: (platform === "win32" ? "npm i -g " : "npm install -g ") + "symbiot@" + target + " --prefer-online" };
|
|
77
|
+
}
|
|
68
78
|
|
|
69
79
|
// ---- colour + spinner ------------------------------------------------------
|
|
70
80
|
const c = PLAIN
|
|
@@ -753,7 +763,7 @@ const escDq = (s) => String(s).replace(/[\\"$`]/g, "\\$&");
|
|
|
753
763
|
// Plain words only: in the Orca preset this passes through two shells, so no
|
|
754
764
|
// backticks or $ (escDq covers one level).
|
|
755
765
|
const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement the unchecked items in this repo. Tick each item [x] in that file as you finish it and leave your changes uncommitted, so they can be reviewed and approved. If you need a decision, or have ideas or options for the user, write them to .symbiot/QUESTIONS.md as TASKS.md explains, and read .symbiot/ANSWERS.md first if it exists. Confirm before anything destructive.";
|
|
756
|
-
function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(cfg.agentCmd) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
|
|
766
|
+
function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(migrateClaudeCmd(cfg.agentCmd)) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
|
|
757
767
|
// Save the template ("" clears it). Either way the legacy `ide` key goes.
|
|
758
768
|
function setHandoffCmd(cmd) {
|
|
759
769
|
const cfg = loadConfig(); const v = String(cmd || "").trim();
|
|
@@ -772,8 +782,13 @@ const IDE_LIST = [["code", "VS Code", "Visual Studio Code"], ["cursor", "Cursor"
|
|
|
772
782
|
// [cmd, label, template]. Only agents that leave changes to review: a handoff
|
|
773
783
|
// runs unattended, so a "plan only" run can't ask anything and leaves nothing to
|
|
774
784
|
// approve — that preset was dropped (a saved one still runs as-is).
|
|
785
|
+
// Claude: acceptEdits alone still blocks every shell command, so a run could
|
|
786
|
+
// never run its own tests and had to leave test work unticked. Allow just the
|
|
787
|
+
// test runner and node.
|
|
788
|
+
const CLAUDE_CMD = 'claude -p "{prompt}" --permission-mode acceptEdits --allowedTools "Bash(npm test:*)" "Bash(node:*)"';
|
|
789
|
+
const CLAUDE_CMD_OLD = 'claude -p "{prompt}" --permission-mode acceptEdits'; // ≤0.33
|
|
775
790
|
const AGENT_LIST = [
|
|
776
|
-
["claude", "Claude Code — make changes",
|
|
791
|
+
["claude", "Claude Code — make changes", CLAUDE_CMD],
|
|
777
792
|
["codex", "Codex (OpenAI/GPT) — make changes", 'codex exec --full-auto "{prompt}"'],
|
|
778
793
|
["aider", "Aider — make changes", 'aider --message "{prompt}" --yes'],
|
|
779
794
|
["gemini", "Gemini — make changes", 'gemini --yolo -p "{prompt}"'],
|
|
@@ -835,6 +850,13 @@ function migrateOrcaCmd(cmd) {
|
|
|
835
850
|
if (rebuilt !== cmd) { try { const cfg = loadConfig(); if (cfg.agentCmd === cmd) { cfg.agentCmd = rebuilt; saveConfig(cfg); } } catch {} }
|
|
836
851
|
return rebuilt;
|
|
837
852
|
}
|
|
853
|
+
// Upgrade a saved Claude preset from ≤0.33 to the current one. Exact match only:
|
|
854
|
+
// a command the user edited is theirs and runs as-is.
|
|
855
|
+
function migrateClaudeCmd(cmd) {
|
|
856
|
+
if (typeof cmd !== "string" || cmd.trim() !== CLAUDE_CMD_OLD) return cmd;
|
|
857
|
+
try { const cfg = loadConfig(); if (cfg.agentCmd === cmd) { cfg.agentCmd = CLAUDE_CMD; saveConfig(cfg); } } catch {}
|
|
858
|
+
return CLAUDE_CMD;
|
|
859
|
+
}
|
|
838
860
|
// Cross-platform detection for the presets (and gh / the app's browser).
|
|
839
861
|
// "Is this command available?": command -v on posix, where on win.
|
|
840
862
|
function hasCmd(cmd) { try { return !!sh(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`).trim(); } catch { return false; } }
|
|
@@ -1789,7 +1811,7 @@ async function cmdApp() {
|
|
|
1789
1811
|
if (r && r.ok) {
|
|
1790
1812
|
const p = await r.json().catch(() => ({}));
|
|
1791
1813
|
if (p && p.version) {
|
|
1792
|
-
const url = `http://127.0.0.1:${PORT}/?t=${TOKEN}`; const how = openApp(url);
|
|
1814
|
+
const url = `http://127.0.0.1:${PORT}/?t=${TOKEN}`; const how = process.env.SYMBIOT_NO_OPEN === "1" ? "" : openApp(url);
|
|
1793
1815
|
console.log(`\n${c.g("●")} ${c.b("Symbiot")} is already running (v${p.version}) at ${c.b(url)}`);
|
|
1794
1816
|
console.log(how ? c.d(` Opened the existing window (a ${how}).`) : c.d(" Open that URL in your browser."));
|
|
1795
1817
|
console.log(c.d(" (Not starting a second copy. Set SYMBIOT_FORCE_NEW=1 to force one.)"));
|
|
@@ -1852,14 +1874,10 @@ async function cmdApp() {
|
|
|
1852
1874
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1853
1875
|
if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1") await checkLatest(); return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION, newer: semverGt(LATEST_VERSION, VERSION) }); }
|
|
1854
1876
|
if (u.pathname === "/api/update" && req.method === "POST") {
|
|
1855
|
-
// Install the
|
|
1856
|
-
//
|
|
1857
|
-
//
|
|
1858
|
-
|
|
1859
|
-
// a stale cached packument. The page's heartbeat reconnects and reloads.
|
|
1860
|
-
const target = LATEST_VERSION && semverGt(LATEST_VERSION, VERSION) ? LATEST_VERSION : "latest";
|
|
1861
|
-
const pkg = "symbiot@" + target;
|
|
1862
|
-
const inst = (process.platform === "win32" ? "npm i -g " : "npm install -g ") + pkg + " --prefer-online";
|
|
1877
|
+
// Install the exact newest version (see updateCmd), then relaunch this
|
|
1878
|
+
// same app (same port+token => same URL) and exit. The page's heartbeat
|
|
1879
|
+
// reconnects and reloads.
|
|
1880
|
+
const { target, cmd: inst } = updateCmd(LATEST_VERSION, VERSION);
|
|
1863
1881
|
const e = track("symbiot update", inst, homedir(), (code) => {
|
|
1864
1882
|
if (code !== 0) return;
|
|
1865
1883
|
try { const ch = spawn(process.execPath, process.argv.slice(1), { detached: true, stdio: "ignore", env: process.env }); ch.unref(); } catch {}
|
|
@@ -1896,7 +1914,7 @@ function cmdMail() {
|
|
|
1896
1914
|
const add = flag("add", null);
|
|
1897
1915
|
if (add) { const r = setMail({ add }); if (r.error) console.log(c.y(r.error)); }
|
|
1898
1916
|
const m = mailState();
|
|
1899
|
-
console.log(`\n${c.g("●")} ${c.b("Symbiot mail")} ${c.d("· " + (m.enabled ? "on — what you sent feeds week and standup" : "off"))}\n`);
|
|
1917
|
+
console.log(`\n${c.g("●")} ${c.b("Symbiot mail")} ${c.d("(experimental) · " + (m.enabled ? "on — what you sent feeds week and standup" : "off"))}\n`);
|
|
1900
1918
|
const srcs = [...m.detected.map((d) => [d.path, d.kind]), ...m.sources.map((p) => [p, "added"])];
|
|
1901
1919
|
if (!srcs.length) console.log(c.y("No mail found on this computer.") + c.d(" Use a desktop mail app (Thunderbird, Apple Mail, Evolution…), or add an export: symbiot mail --add ~/Takeout/Mail/All.mbox"));
|
|
1902
1920
|
for (const [p, k] of srcs) console.log(` ${c.g("·")} ${p} ${c.d(k)}`);
|
|
@@ -1915,16 +1933,19 @@ ${c.b("Usage")}
|
|
|
1915
1933
|
symbiot standup yesterday + today, for standup
|
|
1916
1934
|
symbiot todo what's still on your plate
|
|
1917
1935
|
symbiot app open the visual app in your browser
|
|
1918
|
-
symbiot drift
|
|
1936
|
+
symbiot drift [--fetch] what's out of sync / at risk across repos
|
|
1919
1937
|
symbiot push [--open] write tasks into each repo (and run your agent)
|
|
1920
|
-
symbiot mail [--on|--off] use the mail you sent in write-ups (local, no API)
|
|
1921
|
-
symbiot models recommend AI models for your hardware
|
|
1922
|
-
symbiot setup-local [--model X] install/run a free local model (Ollama)
|
|
1923
1938
|
symbiot login connect it to an AI (once)
|
|
1924
1939
|
symbiot whoami show how it's connected
|
|
1925
1940
|
symbiot logout forget saved credentials
|
|
1926
1941
|
symbiot help
|
|
1927
1942
|
|
|
1943
|
+
${c.b("Experimental")}
|
|
1944
|
+
symbiot drift --ci also check GitHub Actions (needs gh)
|
|
1945
|
+
symbiot mail [--on|--off] use the mail you sent in write-ups (local, no API)
|
|
1946
|
+
symbiot models recommend AI models for your hardware
|
|
1947
|
+
symbiot setup-local [--model X] install/run a free local model (Ollama)
|
|
1948
|
+
|
|
1928
1949
|
${c.b("Options")}
|
|
1929
1950
|
--dir <path> where your repos are (default: ${homedir()})
|
|
1930
1951
|
--since <days> window for 'week' (default 7)
|
|
@@ -1969,4 +1990,4 @@ const isMain = (() => {
|
|
|
1969
1990
|
})();
|
|
1970
1991
|
if (isMain) main();
|
|
1971
1992
|
|
|
1972
|
-
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, buildTasksMd, taskType, EMBEDDED_UI, orcaHandoffCmd, migrateOrcaCmd, fillHandoff, handoffCmd, setHandoffCmd, ORCA_CLAUDE_CMD, HANDOFF_PROMPT, shipChanges, syncTasks, pendingReview, approveRepo, sendBack, pushTasks, semverGt, parseQuestions, agentQuestions };
|
|
1993
|
+
export { authorship, repoState, readmeInfo, repoShape, houseRules, findAllRepos, buildMap, reportFooter, detectHardware, recommendModels, computeDrift, driftRepo, gitDefaultBranch, buildTasksMd, taskType, EMBEDDED_UI, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, fillHandoff, handoffCmd, setHandoffCmd, ORCA_CLAUDE_CMD, CLAUDE_CMD, HANDOFF_PROMPT, shipChanges, syncTasks, pendingReview, approveRepo, sendBack, pushTasks, semverGt, updateCmd, parseQuestions, agentQuestions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"@anthropic-ai/sdk": "^0.128.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"test": "node test/load.mjs && node test/fixtures.mjs && node test/smoke.mjs && node test/install.mjs"
|
|
47
|
+
"test": "node test/load.mjs && node test/fixtures.mjs && node test/smoke.mjs && node test/app.mjs && node test/install.mjs"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/ui.mjs
CHANGED
|
@@ -183,7 +183,7 @@ label.check input{width:auto}
|
|
|
183
183
|
<section id="panel-drift" class="hidden">
|
|
184
184
|
<div class="row"><span class="muted">What's out of sync, stuck or at risk across your repos — local git facts.</span>
|
|
185
185
|
<label class="muted" style="margin-left:auto"><input type="checkbox" id="driftfetch"> fetch latest</label>
|
|
186
|
-
<label class="muted"><input type="checkbox" id="driftci"> check CI (needs gh)</label>
|
|
186
|
+
<label class="muted"><input type="checkbox" id="driftci"> check CI (needs gh, experimental)</label>
|
|
187
187
|
<button class="ghost" id="driftrun">Rescan</button></div>
|
|
188
188
|
<div id="driftout"></div>
|
|
189
189
|
</section>
|
|
@@ -216,15 +216,16 @@ label.check input{width:auto}
|
|
|
216
216
|
<div id="agentpresets" style="margin-top:8px"></div>
|
|
217
217
|
</div>
|
|
218
218
|
<div style="margin-top:20px;border-top:1px solid var(--line);padding-top:16px">
|
|
219
|
-
<label>Email — add what you sent to Week and Standup</label>
|
|
219
|
+
<label>Email — add what you sent to Week and Standup <span class="muted">(experimental)</span></label>
|
|
220
220
|
<label class="check"><input type="checkbox" id="mailon"> Use my sent email (subjects & recipients only)</label>
|
|
221
221
|
<div id="mailsources"></div>
|
|
222
222
|
<div class="row" style="margin-top:6px"><input id="newmail" placeholder="add a mail folder or .mbox file (e.g. a Google Takeout export)" style="flex:1"><button class="ghost" id="addmail">Add</button><button class="ghost" id="mailpreview">Preview</button></div>
|
|
223
223
|
<input id="mailaddrs" type="text" placeholder="your email addresses, comma-separated (only needed for a whole-mailbox export)">
|
|
224
|
-
<div class="note muted" id="mailnote">No API, no OAuth, no password: Symbiot reads the mail your desktop mail app (Thunderbird, Apple Mail, Evolution, mutt…) already keeps on this computer, or an exported .mbox — so anyone can link theirs. Headers only, never a message body; off until you tick it.</div>
|
|
224
|
+
<div class="note muted" id="mailnote">No API, no OAuth, no password: Symbiot reads the mail your desktop mail app (Thunderbird, Apple Mail, Evolution, mutt…) already keeps on this computer, or an exported .mbox — so anyone can link theirs. Headers only, never a message body; off until you tick it. Experimental: tested with mbox and Maildir, not yet on real Apple Mail, Evolution or KMail stores.</div>
|
|
225
225
|
<div id="mailout"></div>
|
|
226
226
|
</div>
|
|
227
227
|
<div style="margin-top:20px;border-top:1px solid var(--line);padding-top:16px">
|
|
228
|
+
<label>Local models <span class="muted">(experimental)</span></label>
|
|
228
229
|
<button class="ghost" id="recbtn">Recommend models for my machine</button>
|
|
229
230
|
<button class="ghost" id="setuplocal" style="margin-left:8px">Set up a free local model</button>
|
|
230
231
|
<div id="setupout" class="note muted" style="margin-top:10px"></div>
|
|
@@ -489,7 +490,11 @@ function loadAgentCfg(){api('/api/agentcfg').then(function(d){document.getElemen
|
|
|
489
490
|
var chips=[];(d.agents||[]).forEach(function(a){chips.push(a);});(d.editors||[]).forEach(function(e){chips.push(e);});
|
|
490
491
|
var box=document.getElementById('agentpresets');
|
|
491
492
|
if(!chips.length){box.innerHTML="<span class='muted' style='font-size:12px'>Nothing detected on PATH — type your own command above.</span>";return;}
|
|
492
|
-
|
|
493
|
+
// Agents leave changes for review; an editor preset only opens the repo, so say so.
|
|
494
|
+
var na=(d.agents||[]).length;function chip(c,i){return "<button class='ghost preset' data-i='"+i+"' style='padding:4px 10px;font-size:12px;margin:5px 5px 0 0'>"+esc(c.label)+"</button>";}
|
|
495
|
+
var h="<span class='muted' style='font-size:12px'>Detected — click to use:</span><br>"+chips.slice(0,na).map(chip).join("");
|
|
496
|
+
if(chips.length>na)h+="<div class='muted' style='font-size:12px;margin-top:8px'>Editors — opens only, no review:</div>"+chips.slice(na).map(function(c,i){return chip(c,na+i);}).join("");
|
|
497
|
+
box.innerHTML=h;
|
|
493
498
|
box.querySelectorAll('.preset').forEach(function(btn){btn.addEventListener('click',function(){document.getElementById('agentcmd').value=chips[+btn.getAttribute('data-i')].tmpl;saveAgent();});});});}
|
|
494
499
|
function saveAgent(){api('/api/agentcmd',{cmd:document.getElementById('agentcmd').value});}
|
|
495
500
|
function loadScanRoots(){api('/api/scanroots').then(function(d){var box=document.getElementById('scanroots');var roots=d.effective||[];
|