symbiot 0.62.1 → 0.63.1
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 +10 -0
- package/README.md +1 -1
- package/agents.mjs +49 -7
- package/desktop.mjs +9 -15
- package/handover.mjs +1 -1
- package/icon.ico +0 -0
- package/icon.svg +1 -33
- package/icons/icon-ferro-small.svg +1 -0
- package/icons/icon-ferro.svg +1 -0
- package/icons/icon-glass-small.svg +1 -0
- package/icons/icon-glass.svg +1 -0
- package/icons/icon-pearl-small.svg +1 -0
- package/icons/icon-pearl.svg +1 -0
- package/package.json +8 -7
- package/server.mjs +3 -2
- package/ui.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
What each Symbiot release brought, newest first. When Approve bumps the version, it writes that release here from the tasks it approved, worded as release notes by your connected AI (or in the tasks' own words without one). The app shows the releases newer than yours under "What's new" when it offers an update, and once more after you update.
|
|
4
4
|
|
|
5
|
+
## 0.63.1
|
|
6
|
+
|
|
7
|
+
- **Fewer things put on your plate.** A permission an agent needs for your own work (building your app, using your phone over USB, recording its screen) now turns on by itself instead of showing up as a card: a tool installed in `~/.local/bin` and the marketing lane's own folder no longer count as "outside your work".
|
|
8
|
+
- **Your agent's own sensible answer is taken for you.** When an agent asks something and its recommended answer is a step it takes itself and can undo, Symbiot picks it and the agent carries on. Approvals, publishing, sending, money, accounts and other machines still come to you.
|
|
9
|
+
- **Open tasks don't just sit there.** A lane whose run ends with tasks still open starts again by itself, as long as it keeps ticking them off, and a question about one task no longer stops the rest.
|
|
10
|
+
|
|
11
|
+
## 0.63.0
|
|
12
|
+
|
|
13
|
+
- **The Android app is built again.** 0.62.1's release went out without its APK: a link to a build folder on this computer had been committed by mistake, so GitHub's build couldn't make the folder. It's gone, and the ignore rule now covers a link as well as a folder. 0.63.0 carries 0.62.1's phone fix (setup going to **Your computer**).
|
|
14
|
+
- **Symbiot's new mark.** The app's own orb, lit the way Home lights it, with an S inside in the two colours Symbiot already speaks in: the silver of agents at work above, the amber of "needs you" below. It's everywhere: the app-menu, Start-menu and dock icon (in each look: Ferrofluid's black chrome, Glass's clear droplet, Pearl's mirror), the window and tab icon (a simpler drawing at small sizes), the app's header (the old green dot is gone), setup, the Windows, Mac and Linux installers (the Mac one inside Apple's icon grid), the tray and the Mac menu bar, the Android app and its notifications, and symbiot.co.za, which now has a sharing image for links.
|
|
5
15
|
## 0.62.1
|
|
6
16
|
|
|
7
17
|
- **A phone that had Symbiot before goes to "Your computer" first.** Installing the new app over an older one kept the old app's setup, already started at "Meet Symbiot", so the phone went on to "Your AI" and asked to install Claude Code, and never offered to pair with your computer. An unfinished setup on a phone that isn't paired now opens on **Your computer** (scan the QR your computer shows, or type its address and code), once; "No computer" carries on as before. Checked on the Android emulator: 0.59.0, then 0.62.0 over it (the problem), then this over that (fixed).
|
package/README.md
CHANGED
|
@@ -248,7 +248,7 @@ are inside it (the same builds Termux installs), and it shows Symbiot full scree
|
|
|
248
248
|
with its own icon.
|
|
249
249
|
|
|
250
250
|
- **Install:** on your phone, download `symbiot-aarch64.apk` from the
|
|
251
|
-
[latest release](https://github.com/
|
|
251
|
+
[latest release](https://github.com/GAWdessey/symbiot/releases/latest)
|
|
252
252
|
(or copy `symbiot-<version>-aarch64.apk` over from a computer that built it)
|
|
253
253
|
and open it (allow your browser or file manager to install apps). Android may warn that the
|
|
254
254
|
app was built for an older version of Android. That's on purpose: it's what lets
|
package/agents.mjs
CHANGED
|
@@ -32,7 +32,7 @@ const shSingle = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
|
32
32
|
const escDq = (s) => String(s).replace(/[\\"$`]/g, "\\$&");
|
|
33
33
|
// Plain words only: in the Orca preset this passes through two shells, so no
|
|
34
34
|
// backticks or $ (escDq covers one level).
|
|
35
|
-
const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement
|
|
35
|
+
const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement its unchecked items in this repo, one at a time, urgent ones first: finish the first one completely (built, tested, ticked) before you look at the next, then carry on with the next while you can. An item that is already done in the code, or no longer applies, you tick with a one-line note saying why. Never end a run without finishing or ticking at least one item, and never stop because the list is long: another run picks up where you left off. 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, then skip that item and carry on with the next: a question never ends the run while other items can be done. Read .symbiot/ANSWERS.md first if it exists. Confirm before anything destructive.";
|
|
36
36
|
function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(migrateClaudeCmd(cfg.agentCmd)) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
|
|
37
37
|
// Save the template ("" clears it). Either way the legacy `ide` key goes.
|
|
38
38
|
function setHandoffCmd(cmd) {
|
|
@@ -235,7 +235,7 @@ function sandboxEnv(box, { token = () => (hasCmd("gh") ? sh("gh auth token").tri
|
|
|
235
235
|
// For Settings → Handoff: { on, ready, missing } (on: repo runs get it).
|
|
236
236
|
function sandboxState() { const n = sandboxNeeds(); return { on: loadConfig().agentSandbox !== false && trustFull(), ready: n.ready, missing: n.missing, ...(n.unsupported ? { unsupported: true } : {}) }; }
|
|
237
237
|
const SESSION = "session.json", RESUME_MAX = 8, RESUME_AGE = 2 * 86400000;
|
|
238
|
-
const RESUME_PROMPT = "The user has answered: read the newest entries in .symbiot/ANSWERS.md and carry on with .symbiot/TASKS.md from where you were. Same rules as before.";
|
|
238
|
+
const RESUME_PROMPT = "The user has answered: read the newest entries in .symbiot/ANSWERS.md and carry on with .symbiot/TASKS.md from where you were: finish the item you were on, then the next. Same rules as before.";
|
|
239
239
|
function sessionOf(path) { try { return JSON.parse(readSymbiot(path, SESSION)) || null; } catch { return null; } }
|
|
240
240
|
// Resume when an answer has come in since its last run, and the conversation is fresh enough
|
|
241
241
|
function resumeFor(path) {
|
|
@@ -1104,15 +1104,17 @@ function workRoots() {
|
|
|
1104
1104
|
function inWork(p, roots = workRoots()) {
|
|
1105
1105
|
if (!p) return false;
|
|
1106
1106
|
p = resolve(String(p).replace(/^~(?=\/|$)/, homedir()).replace(/[/*]+$/, "")) || "/";
|
|
1107
|
-
const
|
|
1108
|
-
if (p === drafts || p.startsWith(drafts + "/")) return true;
|
|
1107
|
+
for (const lane of [join(CONFIG_DIR, "drafts"), join(CONFIG_DIR, "marketing")]) if (p === lane || p.startsWith(lane + "/")) return true;
|
|
1109
1108
|
if (!roots.some((r) => p === r || p.startsWith(r === "/" ? "/" : r + "/"))) return false;
|
|
1110
1109
|
const rel = relative(homedir(), p);
|
|
1111
1110
|
if (!rel.startsWith("..") && rel.split("/")[0].startsWith(".")) return false; // ~/.ssh, ~/.config…
|
|
1112
1111
|
return true;
|
|
1113
1112
|
}
|
|
1114
1113
|
// The paths a rule names: Read(/x/**), Bash(find /x:*), additionalDirectories' /x.
|
|
1115
|
-
|
|
1114
|
+
// A Bash rule's own program, when it lives in a bin folder (~/.local/bin/adb), is a tool
|
|
1115
|
+
// it runs, not a place it works in: only the paths after it count.
|
|
1116
|
+
const BIN_PROGRAM = /^(Bash\(\s*)(?:~|\/)[^\s:()]*\/s?bin\/[^\s:()/]+/;
|
|
1117
|
+
const rulePaths = (r) => (String(r).replace(BIN_PROGRAM, "$1").match(/(?:~|\/)[^\s:*()"',]*/g) || []).filter((x) => x === "~" || x.length > 1);
|
|
1116
1118
|
// Whether a proposed list can be turned on without asking: { ok } or { why }.
|
|
1117
1119
|
function allowlistInWork(path) {
|
|
1118
1120
|
let p; try { p = JSON.parse(readFileSync(join(path, ".symbiot", "allowlist.proposed.json"), "utf8")).permissions || {}; } catch { return { why: "no list" }; }
|
|
@@ -1145,7 +1147,47 @@ function autoAllow(path) {
|
|
|
1145
1147
|
}
|
|
1146
1148
|
// Every run that stopped asking for its list, now: on start and every so often, so
|
|
1147
1149
|
// asks from before this version clear too.
|
|
1148
|
-
|
|
1150
|
+
// A question whose answer the agent already knows isn't the user's: when its own
|
|
1151
|
+
// recommended option is a step it takes itself (not 👤), and nothing in the question or
|
|
1152
|
+
// that option is an approval, goes out, costs money, touches an account or a machine, or
|
|
1153
|
+
// can't be undone, Symbiot picks it and the agent carries on. Everything else still comes to the user.
|
|
1154
|
+
const OUTWARD = /\b(approv\w*|publish\w*|releas\w*|push\w*|merg\w*|send\w*|sent|post\w*|repl\w*|e-?mail\w*|pay\w*|spend\w*|buy\w*|money|bill\w*|cost\w*|delet\w*|remov\w*|uninstall\w*|wip\w*|drop\w*|password\w*|2fa|token\w*|keys?|sign(?:ed|s)?[- ]?in|log(?:ged)?[- ]?in|accounts?|subscription|plan|tunnel\w*|prox\w*|ssh|vm|servers?|restart\w*|stop\w*|kill\w*|broker\w*|trad\w*|bank\w*|npm|deploy\w*|install\w*|phone|whatsapp|linkedin|github\.com)\b|\byour (?:default|settings?|profile)\b/i;
|
|
1155
|
+
function autoPick(q) {
|
|
1156
|
+
const rec = (q.options || []).filter((o) => /\(recommended\b/i.test(o));
|
|
1157
|
+
if (rec.length !== 1 || yourStep(rec[0])) return "";
|
|
1158
|
+
return OUTWARD.test(`${q.q} ${q.context || ""} ${rec[0]}`) ? "" : rec[0];
|
|
1159
|
+
}
|
|
1160
|
+
function autoDecide(path) {
|
|
1161
|
+
if (!path || runningHandoff(path)) return null;
|
|
1162
|
+
const picks = agentQuestions(path, "").questions.map((q) => ({ q: q.q, a: autoPick(q) })).filter((x) => x.a);
|
|
1163
|
+
if (!picks.length) return null;
|
|
1164
|
+
const r = answerQuestions(path, picks.map((x) => ({ q: x.q, a: `${x.a}\n(Symbiot picked your own recommendation: it's yours to do and can be undone, so the user wasn't asked.)` })), { rerun: true });
|
|
1165
|
+
return r && r.ok ? { decided: picks.length } : null;
|
|
1166
|
+
}
|
|
1167
|
+
// A lane whose run has ended with unticked tasks still in TASKS.md starts again by
|
|
1168
|
+
// itself, so open work never just sits there. Only while it's getting somewhere: after
|
|
1169
|
+
// the first continue, a lane starts again only if more tasks got ticked since the last
|
|
1170
|
+
// one, so a lane stuck on its questions doesn't loop. Parked, busy or blocked lanes are left alone.
|
|
1171
|
+
const CONTINUED = new Map(); // path -> ticked count when it was last continued
|
|
1172
|
+
const CONTINUE_AFTER = 2 * 60000;
|
|
1173
|
+
function continueLanes({ now = Date.now(), run = runHandoff, runs = loadRuns } = {}) {
|
|
1174
|
+
const out = [];
|
|
1175
|
+
for (const path of [...new Set(runs().map((r) => r.path).filter(Boolean))]) {
|
|
1176
|
+
try {
|
|
1177
|
+
if (runningHandoff(path) || isParked(path)) continue;
|
|
1178
|
+
const md = readSymbiot(path, "TASKS.md"), open = (md.match(/^\s*(?:[-*+]|\d+[.)])\s*\[ \]/gm) || []).length;
|
|
1179
|
+
if (!open) { CONTINUED.delete(path); continue; }
|
|
1180
|
+
let ended = 0; try { ended = statSync(join(path, ".symbiot", "agent.log")).mtimeMs; } catch { continue; }
|
|
1181
|
+
if (now - ended < CONTINUE_AFTER) continue;
|
|
1182
|
+
const ticked = (md.match(/^\s*(?:[-*+]|\d+[.)])\s*\[[xX]\]/gm) || []).length;
|
|
1183
|
+
if (CONTINUED.has(path) && ticked <= CONTINUED.get(path)) continue;
|
|
1184
|
+
if (blockedAgain(path, handoffCmd())) continue;
|
|
1185
|
+
const e = run(path); if (e && e.id && !e.busy && !e.blocked) { CONTINUED.set(path, ticked); out.push(path); }
|
|
1186
|
+
} catch {}
|
|
1187
|
+
}
|
|
1188
|
+
return out;
|
|
1189
|
+
}
|
|
1190
|
+
function autoAllowSweep() { const out = []; for (const r of loadRuns()) { try { const a = autoAllow(r.path); if (a && a.allowed) out.push(r.path); else if (autoDecide(r.path)) out.push(r.path); } catch {} } return out; }
|
|
1149
1191
|
// Every agent reaches the owner's knowledge folders from the start; an ops run (no repo
|
|
1150
1192
|
// of its own) their repos too, the way a person at their desk would.
|
|
1151
1193
|
function withScope(tmpl, repoPath) {
|
|
@@ -1263,4 +1305,4 @@ function agentsList() {
|
|
|
1263
1305
|
}).concat(earlierRuns());
|
|
1264
1306
|
}
|
|
1265
1307
|
|
|
1266
|
-
export { FACTS, factsOf, knownRun, withStream, workOf, HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, OPTIONS_SHOWN, IDEAS_SHOWN, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, grantRule, allowTool, claudeConnectors, withConnectors, linkedConnectors, connectorsLine, connectorsInfo, linkReach, fillHandoff, runHandoff, nothingToDo, PARKED_NOTE, parkedPaths, isParked, parkLane, agentMissing, blockedAgain, runningHandoff, loadRuns, earlierRuns, namedFiles, waitingFor, startWaiting, noteUntracked, untrackedBefore, forgetUntracked, writeTasks, droppedTasks, releaseHeldTasks, startHeldTasks, detectHandoffs, pickAgent, findOrcaCli, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, suggestionTarget, skipIdea, agentQuestions, answerQuestions, agentsList, needsOf, settleNeeds, NEEDS_FOR, readLastWords, readNeeds, parseRead, READ_FILE, autoAllow, autoAllowSweep, allowlistInWork, installAllowlist, inWork, withScope, withTrust, sandboxFor, sandboxEnv, sandboxNeeds, sandboxState, sandboxWrites, SANDBOX_DIR, resumeFor, noteSession, trustFull, GUARD_SETTINGS, isUrgent, urgentFirst, urgentUndo, urgentDone, urgentSweep, urgentState , licenceGate };
|
|
1308
|
+
export { continueLanes, autoPick, autoDecide, FACTS, factsOf, knownRun, withStream, workOf, HANDOFFS, HANDOFF_PROMPT, QUESTIONS_MAX, OPTIONS_SHOWN, IDEAS_SHOWN, shSingle, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, grantAgent, grantRule, allowTool, claudeConnectors, withConnectors, linkedConnectors, connectorsLine, connectorsInfo, linkReach, fillHandoff, runHandoff, nothingToDo, PARKED_NOTE, parkedPaths, isParked, parkLane, agentMissing, blockedAgain, runningHandoff, loadRuns, earlierRuns, namedFiles, waitingFor, startWaiting, noteUntracked, untrackedBefore, forgetUntracked, writeTasks, droppedTasks, releaseHeldTasks, startHeldTasks, detectHandoffs, pickAgent, findOrcaCli, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, track, agentChanges, parseQuestions, suggestionTarget, skipIdea, agentQuestions, answerQuestions, agentsList, needsOf, settleNeeds, NEEDS_FOR, readLastWords, readNeeds, parseRead, READ_FILE, autoAllow, autoAllowSweep, allowlistInWork, installAllowlist, inWork, withScope, withTrust, sandboxFor, sandboxEnv, sandboxNeeds, sandboxState, sandboxWrites, SANDBOX_DIR, resumeFor, noteSession, trustFull, GUARD_SETTINGS, isUrgent, urgentFirst, urgentUndo, urgentDone, urgentSweep, urgentState , licenceGate };
|
package/desktop.mjs
CHANGED
|
@@ -160,21 +160,15 @@ function autostartContent(node, script, platform = OS, path = process.env.PATH |
|
|
|
160
160
|
// new Node, a moved install), so it never points at something gone.
|
|
161
161
|
const launcherFile = (home = homedir()) => join(home, ".local", "share", "applications", "symbiot.desktop");
|
|
162
162
|
const launcherIcon = (home = homedir()) => join(home, ".local", "share", "icons", "hicolor", "scalable", "apps", "symbiot.svg");
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const base = readFileSync(ICON_SRC, "utf8"), c = LOOK_COLOURS[look];
|
|
173
|
-
if (!c) return base;
|
|
174
|
-
return base.replace('stop-color="#1B1D22"', `stop-color="${c.bg[0]}"`).replace('stop-color="#08090B"', `stop-color="${c.bg[1]}"`)
|
|
175
|
-
.replace('stop-color="#3A3D44"', `stop-color="${c.orb[0]}"`).replace('stop-color="#121317"', `stop-color="${c.orb[1]}"`).replace('stop-color="#030304"', `stop-color="${c.orb[2]}"`)
|
|
176
|
-
.replace('stop-color="#B0466E" stop-opacity="0.55"', `stop-color="${c.rim}" stop-opacity="0.45"`)
|
|
177
|
-
.replace(/stop-color="#F2A541" stop-opacity="0\.16"/, `stop-color="${c.glow}" stop-opacity="0.18"`);
|
|
163
|
+
// Symbiot's mark in each look (icons/): the Home orb as that look draws it (Ferrofluid's
|
|
164
|
+
// black chrome, Glass's clear droplet, Pearl's mirror), with the S inside in the app's two
|
|
165
|
+
// signal colours: the silver of agents at work, the amber of "needs you". small: the
|
|
166
|
+
// drawing for 16-24 px (a browser tab, the header), with no reflections and a heavier S.
|
|
167
|
+
// The app menu's icon and the window's follow the look you pick (setLauncherLook).
|
|
168
|
+
const ICONS = fileURLToPath(new URL("./icons/", import.meta.url));
|
|
169
|
+
function iconSvg(look = "ferro", { small = false } = {}) {
|
|
170
|
+
const l = ["ferro", "glass", "pearl"].includes(look) ? look : "ferro";
|
|
171
|
+
return readFileSync(join(ICONS, `icon-${l}${small ? "-small" : ""}.svg`), "utf8");
|
|
178
172
|
}
|
|
179
173
|
// The look changed: the app menu's icon follows, if the app menu has Symbiot.
|
|
180
174
|
function setLauncherLook(look, home = homedir()) {
|
package/handover.mjs
CHANGED
|
@@ -27,7 +27,7 @@ function handoverRules(lanes = [], here = "") {
|
|
|
27
27
|
}
|
|
28
28
|
// The rule for options in QUESTIONS.md: the user gets only what no agent can do,
|
|
29
29
|
// and the option says which kind it is.
|
|
30
|
-
const ONLY_YOU = "- Only what no agent can do is the user's: their body (a phone in their hand, a cable, which network they're on), their identity or secrets (signing in, a 2FA code, a token from a provider's console) or a decision that's theirs (closing an account, spending money, sending something in their name). Start such an option with `👤 You (only you: <which>):`, e.g. `👤 You (only you: your Meta token): paste it into .env`. Anything an agent could do, yours or another lane's, is never a 👤 step: do it, or hand it over. Local git work (staging, committing, pulling, pushing, in this repo or another lane's) is never a 👤 step either: do it, or hand it to that repo's lane, and ask only for an OK in QUESTIONS.md before a push, with the files it pushes. Prefer what's already linked: Symbiot's own browser (`~/.config/symbiot/browser`) is signed in to the sites the user linked under Connections, so use that session over any route that needs a new sign-in, an app, a key or their consent (a platform's API, a developer app, OAuth). Check what's linked before you write a 👤 sign-in or link step, ask them to sign in only when a real attempt found the session expired (and say so), and never offer doing an agent's job by hand. When they do have to sign in to a site, name the site's address and the place in the app: `👤 You (only you: your sign-in): sign in to <site, e.g. www.domains.co.za/client/dashboard> in the Symbiot Browser (Settings → Connections → Add a site)`, never just \"in Symbiot's browser\": Symbiot puts a Sign in button on that card, which opens the window there. An option where picking it is enough starts with `🤖 Agent:`. Tag actions only: an option that just reports what the user saw or decides (`it notified me`, `not tried yet`) gets no tag. A permission your run doesn't have (a command, a folder) is never a chore for the user. Write the rules, as narrow as the task needs, to `.symbiot/allowlist.proposed.json` (`{\"permissions\": {\"allow\": [...], \"deny\": [...], \"additionalDirectories\": [...]}}`), ask for it in QUESTIONS.md with the option `👤 You (only you: a permission): allow the list in .symbiot/allowlist.proposed.json
|
|
30
|
+
const ONLY_YOU = "- Only what no agent can do is the user's: their body (a phone in their hand, a cable, which network they're on), their identity or secrets (signing in, a 2FA code, a token from a provider's console) or a decision that's theirs (closing an account, spending money, sending something in their name). Start such an option with `👤 You (only you: <which>):`, e.g. `👤 You (only you: your Meta token): paste it into .env`. Anything an agent could do, yours or another lane's, is never a 👤 step: do it, or hand it over. Local git work (staging, committing, pulling, pushing, in this repo or another lane's) is never a 👤 step either: do it, or hand it to that repo's lane, and ask only for an OK in QUESTIONS.md before a push, with the files it pushes. Prefer what's already linked: Symbiot's own browser (`~/.config/symbiot/browser`) is signed in to the sites the user linked under Connections, so use that session over any route that needs a new sign-in, an app, a key or their consent (a platform's API, a developer app, OAuth). Check what's linked before you write a 👤 sign-in or link step, ask them to sign in only when a real attempt found the session expired (and say so), and never offer doing an agent's job by hand. When they do have to sign in to a site, name the site's address and the place in the app: `👤 You (only you: your sign-in): sign in to <site, e.g. www.domains.co.za/client/dashboard> in the Symbiot Browser (Settings → Connections → Add a site)`, never just \"in Symbiot's browser\": Symbiot puts a Sign in button on that card, which opens the window there. An option where picking it is enough starts with `🤖 Agent:`. Tag actions only: an option that just reports what the user saw or decides (`it notified me`, `not tried yet`) gets no tag. A permission your run doesn't have (a command, a folder) is never a chore for the user. Write the rules, as narrow as the task needs, to `.symbiot/allowlist.proposed.json` (`{\"permissions\": {\"allow\": [...], \"deny\": [...], \"additionalDirectories\": [...]}}`), ask for it in QUESTIONS.md with the option `👤 You (only you: a permission): allow the list in .symbiot/allowlist.proposed.json` exactly (Symbiot shows the user that option as an Allow button, with your rules in plain words, so write the question itself in plain words and never repeat the file's name in it), and stop. If it stays inside the user's work (the folders they gave Symbiot, not ~/.ssh or ~/.config), asks for nothing wide, and doesn't publish or reach another machine, Symbiot turns it on by itself and runs you again: no question reaches the user. Their knowledge folders are already yours to read. Never ask them to copy files or run claude in a terminal for it. Decide what can be undone yourself, as a trusted colleague would: the approach, names, which of two fixes, how to lay files out; pick, do it, and say what you chose and why in your last message. Ask only about what can't be undone, costs money, goes out in the user's name, or needs who they are, and then give 2–3 options with your pick first, marked (recommended). Their answer comes back into this same conversation, so you'll remember everything when it does. Symbiot's membrane (its guard) stops the few things only the user does (pushing to main, publishing, deleting outside your folder, sudo, their keys); if it stops you, ask, don't work around it. Whatever waits on the user's OK (sending, posting, deleting, paying, closing) or on a step only they can take goes in QUESTIONS.md as a question, with what they should check first in its context line, never only in your last message: they aren't asked about what your last message says.";
|
|
31
31
|
|
|
32
32
|
// What a run hands back to Symbiot itself (handback.mjs reads it): facts for
|
|
33
33
|
// memory, and emails that wait on a reply, which Symbiot then watches for; and
|
package/icon.ico
CHANGED
|
Binary file
|
package/icon.svg
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
|
2
|
-
<title>Symbiot</title>
|
|
3
|
-
<defs>
|
|
4
|
-
<radialGradient id="bg" cx="50%" cy="42%" r="70%">
|
|
5
|
-
<stop offset="0" stop-color="#1B1D22"/>
|
|
6
|
-
<stop offset="1" stop-color="#08090B"/>
|
|
7
|
-
</radialGradient>
|
|
8
|
-
<radialGradient id="orb" cx="40%" cy="34%" r="66%">
|
|
9
|
-
<stop offset="0" stop-color="#3A3D44"/>
|
|
10
|
-
<stop offset="0.45" stop-color="#121317"/>
|
|
11
|
-
<stop offset="1" stop-color="#030304"/>
|
|
12
|
-
</radialGradient>
|
|
13
|
-
<radialGradient id="rim" cx="50%" cy="50%" r="50%">
|
|
14
|
-
<stop offset="0.86" stop-color="#000" stop-opacity="0"/>
|
|
15
|
-
<stop offset="0.97" stop-color="#B0466E" stop-opacity="0.55"/>
|
|
16
|
-
<stop offset="1" stop-color="#F2A541" stop-opacity="0"/>
|
|
17
|
-
</radialGradient>
|
|
18
|
-
<radialGradient id="glow" cx="50%" cy="56%" r="50%">
|
|
19
|
-
<stop offset="0.55" stop-color="#F2A541" stop-opacity="0.16"/>
|
|
20
|
-
<stop offset="1" stop-color="#F2A541" stop-opacity="0"/>
|
|
21
|
-
</radialGradient>
|
|
22
|
-
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
|
|
23
|
-
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.85"/>
|
|
24
|
-
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/>
|
|
25
|
-
</linearGradient>
|
|
26
|
-
</defs>
|
|
27
|
-
<rect width="256" height="256" rx="58" fill="url(#bg)"/>
|
|
28
|
-
<circle cx="128" cy="136" r="96" fill="url(#glow)"/>
|
|
29
|
-
<circle cx="128" cy="128" r="78" fill="url(#orb)"/>
|
|
30
|
-
<circle cx="128" cy="128" r="78" fill="url(#rim)"/>
|
|
31
|
-
<ellipse cx="102" cy="92" rx="30" ry="17" transform="rotate(-28 102 92)" fill="url(#shine)"/>
|
|
32
|
-
<ellipse cx="160" cy="176" rx="22" ry="5" transform="rotate(-30 160 176)" fill="#FFFFFF" opacity="0.12"/>
|
|
33
|
-
</svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="22%" r="85%"><stop offset="0" stop-color="#131416"/><stop offset="1" stop-color="#08090A"/></radialGradient><clipPath id="sc"><circle cx="128" cy="128" r="84"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><radialGradient id="sfr" cx="50%" cy="50%" r="50%"><stop offset="0.55" stop-color="#2A2C31" stop-opacity="0"/><stop offset="0.9" stop-color="#2A2C31" stop-opacity="0.55"/><stop offset="1" stop-color="#6A6E76" stop-opacity="0.9"/></radialGradient><linearGradient id="sir" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#7FE3FF"/><stop offset="0.35" stop-color="#C58AF9"/><stop offset="0.7" stop-color="#F29E38"/><stop offset="1" stop-color="#7FE3FF"/></linearGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="100" y1="72" x2="156" y2="128"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.45" stop-color="#C9CFD9"/><stop offset="1" stop-color="#6F7887"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="100" y1="128" x2="156" y2="184"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><ellipse cx="128" cy="206.12" rx="65.52" ry="11.760000000000002" fill="#000000" opacity="0.55" filter="url(#sb6)"/><circle cx="128" cy="128" r="84" fill="#030303"/><circle cx="128" cy="128" r="84" fill="url(#sfr)"/><g clip-path="url(#sc)"><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="#FFFFFF" stroke-opacity="0.35" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="#F2A541" stroke-opacity="0.55" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="url(#st)" stroke-width="15" stroke-linecap="round"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="url(#sa)" stroke-width="15" stroke-linecap="round"/></g><g clip-path="url(#sc)"><rect x="62" y="58" width="50" height="24" rx="10" transform="rotate(-24 87 70)" fill="#FFFCF7" opacity="0.85" filter="url(#sb2)"/><rect x="192" y="88" width="8" height="70" rx="4" fill="#D9E6FF" opacity="0.45" filter="url(#sb2)"/><ellipse cx="128" cy="206" rx="52.08" ry="9" fill="#998066" opacity="0.35" filter="url(#sb2)"/></g><circle cx="128" cy="128" r="82.8" fill="none" stroke="url(#sir)" stroke-opacity="0.38" stroke-width="2.4"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="22%" r="85%"><stop offset="0" stop-color="#131416"/><stop offset="1" stop-color="#08090A"/></radialGradient><clipPath id="sc"><circle cx="128" cy="128" r="96"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><radialGradient id="sfr" cx="50%" cy="50%" r="50%"><stop offset="0.55" stop-color="#2A2C31" stop-opacity="0"/><stop offset="0.9" stop-color="#2A2C31" stop-opacity="0.55"/><stop offset="1" stop-color="#6A6E76" stop-opacity="0.9"/></radialGradient><linearGradient id="sir" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#7FE3FF"/><stop offset="0.35" stop-color="#C58AF9"/><stop offset="0.7" stop-color="#F29E38"/><stop offset="1" stop-color="#7FE3FF"/></linearGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="97" y1="66" x2="159" y2="128"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.45" stop-color="#C9CFD9"/><stop offset="1" stop-color="#6F7887"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="97" y1="128" x2="159" y2="190"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><ellipse cx="128" cy="217.28" rx="74.88" ry="13.440000000000001" fill="#000000" opacity="0.55" filter="url(#sb6)"/><circle cx="128" cy="128" r="96" fill="#030303"/><circle cx="128" cy="128" r="96" fill="url(#sfr)"/><g clip-path="url(#sc)"><path d="M 155.86 83.41 A 31 31 0 1 0 114.41 124.86" fill="none" stroke="#FFFFFF" stroke-opacity="0.35" stroke-width="30" stroke-linecap="round" filter="url(#sb6)"/><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="#F2A541" stroke-opacity="0.55" stroke-width="30" stroke-linecap="round" filter="url(#sb6)"/><path d="M 155.86 83.41 A 31 31 0 1 0 114.41 124.86" fill="none" stroke="url(#st)" stroke-width="22" stroke-linecap="round"/><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="url(#sa)" stroke-width="22" stroke-linecap="round"/></g><circle cx="128" cy="128" r="94" fill="none" stroke="#6A6E76" stroke-width="4"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="22%" r="85%"><stop offset="0" stop-color="#131416"/><stop offset="1" stop-color="#08090A"/></radialGradient><clipPath id="sc"><circle cx="128" cy="128" r="84"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><radialGradient id="sfr" cx="50%" cy="50%" r="50%"><stop offset="0.55" stop-color="#2A2C31" stop-opacity="0"/><stop offset="0.9" stop-color="#2A2C31" stop-opacity="0.55"/><stop offset="1" stop-color="#6A6E76" stop-opacity="0.9"/></radialGradient><linearGradient id="sir" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#7FE3FF"/><stop offset="0.35" stop-color="#C58AF9"/><stop offset="0.7" stop-color="#F29E38"/><stop offset="1" stop-color="#7FE3FF"/></linearGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="100" y1="72" x2="156" y2="128"><stop offset="0" stop-color="#FFFFFF"/><stop offset="0.45" stop-color="#C9CFD9"/><stop offset="1" stop-color="#6F7887"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="100" y1="128" x2="156" y2="184"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><ellipse cx="128" cy="206.12" rx="65.52" ry="11.760000000000002" fill="#000000" opacity="0.55" filter="url(#sb6)"/><circle cx="128" cy="128" r="84" fill="#030303"/><circle cx="128" cy="128" r="84" fill="url(#sfr)"/><g clip-path="url(#sc)"><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="#FFFFFF" stroke-opacity="0.35" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="#F2A541" stroke-opacity="0.55" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="url(#st)" stroke-width="15" stroke-linecap="round"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="url(#sa)" stroke-width="15" stroke-linecap="round"/></g><g clip-path="url(#sc)"><rect x="62" y="58" width="50" height="24" rx="10" transform="rotate(-24 87 70)" fill="#FFFCF7" opacity="0.85" filter="url(#sb2)"/><rect x="192" y="88" width="8" height="70" rx="4" fill="#D9E6FF" opacity="0.45" filter="url(#sb2)"/><ellipse cx="128" cy="206" rx="52.08" ry="9" fill="#998066" opacity="0.35" filter="url(#sb2)"/></g><circle cx="128" cy="128" r="82.8" fill="none" stroke="url(#sir)" stroke-opacity="0.38" stroke-width="2.4"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="50%" r="75%"><stop offset="0" stop-color="#EEF0F2"/><stop offset="1" stop-color="#E1E4E8"/></radialGradient><filter id="ssoft" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="22"/></filter><clipPath id="stc"><rect width="256" height="256" rx="58"/></clipPath><clipPath id="sc"><circle cx="128" cy="128" r="96"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><radialGradient id="sgl" cx="45%" cy="40%" r="60%"><stop offset="0" stop-color="#FBFEFF" stop-opacity="0.35"/><stop offset="0.75" stop-color="#FBFEFF" stop-opacity="0.2"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0.85"/></radialGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="97" y1="66" x2="159" y2="128"><stop offset="0" stop-color="#3B4656"/><stop offset="0.45" stop-color="#3B4656"/><stop offset="1" stop-color="#151A21"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="97" y1="128" x2="159" y2="190"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><g clip-path="url(#stc)" filter="url(#ssoft)" opacity="0.85"><circle cx="58" cy="70" r="62" fill="#8FC9CC"/><circle cx="206" cy="58" r="58" fill="#FAC999"/><circle cx="134" cy="214" r="64" fill="#B8C2ED"/></g><ellipse cx="128" cy="217.28" rx="74.88" ry="13.440000000000001" fill="#21211F" opacity="0.16" filter="url(#sb6)"/><g clip-path="url(#sc)"><circle cx="128" cy="128" r="96" fill="#EEF1F4"/><g filter="url(#sb12)" opacity="0.9"><circle cx="168" cy="170" r="44" fill="#8FC9CC"/><circle cx="84" cy="162" r="40" fill="#FAC999"/><circle cx="134" cy="80" r="44" fill="#B8C2ED"/></g><circle cx="128" cy="128" r="96" fill="url(#sgl)"/></g><circle cx="128" cy="128" r="95.25" fill="none" stroke="#FFFFFF" stroke-opacity="0.9" stroke-width="1.5"/><g clip-path="url(#sc)"><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="#F2A541" stroke-opacity="0.45" stroke-width="30" stroke-linecap="round" filter="url(#sb6)"/><path d="M 155.86 83.41 A 31 31 0 1 0 114.41 124.86" fill="none" stroke="url(#st)" stroke-width="22" stroke-linecap="round"/><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="url(#sa)" stroke-width="22" stroke-linecap="round"/></g><circle cx="128" cy="128" r="94" fill="none" stroke="#5C616B" stroke-opacity="0.45" stroke-width="4"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="50%" r="75%"><stop offset="0" stop-color="#EEF0F2"/><stop offset="1" stop-color="#E1E4E8"/></radialGradient><filter id="ssoft" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="22"/></filter><clipPath id="stc"><rect width="256" height="256" rx="58"/></clipPath><clipPath id="sc"><circle cx="128" cy="128" r="84"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><radialGradient id="sgl" cx="45%" cy="40%" r="60%"><stop offset="0" stop-color="#FBFEFF" stop-opacity="0.35"/><stop offset="0.75" stop-color="#FBFEFF" stop-opacity="0.2"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0.85"/></radialGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="100" y1="72" x2="156" y2="128"><stop offset="0" stop-color="#3B4656"/><stop offset="0.45" stop-color="#3B4656"/><stop offset="1" stop-color="#151A21"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="100" y1="128" x2="156" y2="184"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><g clip-path="url(#stc)" filter="url(#ssoft)" opacity="0.85"><circle cx="58" cy="70" r="62" fill="#8FC9CC"/><circle cx="206" cy="58" r="58" fill="#FAC999"/><circle cx="134" cy="214" r="64" fill="#B8C2ED"/></g><ellipse cx="128" cy="206.12" rx="65.52" ry="11.760000000000002" fill="#21211F" opacity="0.16" filter="url(#sb6)"/><g clip-path="url(#sc)"><circle cx="128" cy="128" r="84" fill="#EEF1F4"/><g filter="url(#sb12)" opacity="0.9"><circle cx="168" cy="170" r="44" fill="#8FC9CC"/><circle cx="84" cy="162" r="40" fill="#FAC999"/><circle cx="134" cy="80" r="44" fill="#B8C2ED"/></g><circle cx="128" cy="128" r="84" fill="url(#sgl)"/></g><circle cx="128" cy="128" r="83.25" fill="none" stroke="#FFFFFF" stroke-opacity="0.9" stroke-width="1.5"/><g clip-path="url(#sc)"><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="#F2A541" stroke-opacity="0.45" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="url(#st)" stroke-width="15" stroke-linecap="round"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="url(#sa)" stroke-width="15" stroke-linecap="round"/></g><g clip-path="url(#sc)"><circle cx="90" cy="82" r="7" fill="#FFFFFF" opacity="0.95" filter="url(#sb2)"/><ellipse cx="162" cy="172" rx="22" ry="10" transform="rotate(-35 162 172)" fill="#FFF7EB" opacity="0.45" filter="url(#sb6)"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="35%" r="85%"><stop offset="0" stop-color="#EDECEA"/><stop offset="1" stop-color="#E2E0DC"/></radialGradient><clipPath id="sc"><circle cx="128" cy="128" r="96"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><linearGradient id="senv" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#F5F6F7"/><stop offset="0.7" stop-color="#E2E3E6"/><stop offset="1" stop-color="#B9B6B0"/></linearGradient><radialGradient id="spe" cx="50%" cy="50%" r="50%"><stop offset="0.6" stop-color="#FFFFFF" stop-opacity="0"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0.55"/></radialGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="97" y1="66" x2="159" y2="128"><stop offset="0" stop-color="#3B4656"/><stop offset="0.45" stop-color="#3B4656"/><stop offset="1" stop-color="#151A21"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="97" y1="128" x2="159" y2="190"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><ellipse cx="128" cy="217.28" rx="74.88" ry="13.440000000000001" fill="#21211F" opacity="0.16" filter="url(#sb6)"/><circle cx="128" cy="128" r="96" fill="url(#senv)"/><circle cx="128" cy="128" r="96" fill="url(#spe)"/><g clip-path="url(#sc)"><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="#F2A541" stroke-opacity="0.45" stroke-width="30" stroke-linecap="round" filter="url(#sb6)"/><path d="M 155.86 83.41 A 31 31 0 1 0 114.41 124.86" fill="none" stroke="url(#st)" stroke-width="22" stroke-linecap="round"/><path d="M 100.14 172.59 A 31 31 0 1 0 141.59 131.14" fill="none" stroke="url(#sa)" stroke-width="22" stroke-linecap="round"/></g><circle cx="128" cy="128" r="94" fill="none" stroke="#5C616B" stroke-opacity="0.45" stroke-width="4"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"><title>Symbiot</title><defs><radialGradient id="sg" cx="50%" cy="35%" r="85%"><stop offset="0" stop-color="#EDECEA"/><stop offset="1" stop-color="#E2E0DC"/></radialGradient><clipPath id="sc"><circle cx="128" cy="128" r="84"/></clipPath><filter id="sb2" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="2.2"/></filter><filter id="sb6" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="6"/></filter><filter id="sb12" filterUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><feGaussianBlur stdDeviation="12"/></filter><linearGradient id="senv" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#F5F6F7"/><stop offset="0.62" stop-color="#E6E7E9"/><stop offset="0.86" stop-color="#A3A7AE"/><stop offset="0.93" stop-color="#6E737C"/><stop offset="1" stop-color="#9E9991"/></linearGradient><radialGradient id="spe" cx="50%" cy="50%" r="50%"><stop offset="0.6" stop-color="#FFFFFF" stop-opacity="0"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0.55"/></radialGradient><linearGradient id="st" gradientUnits="userSpaceOnUse" x1="100" y1="72" x2="156" y2="128"><stop offset="0" stop-color="#3B4656"/><stop offset="0.45" stop-color="#3B4656"/><stop offset="1" stop-color="#151A21"/></linearGradient><linearGradient id="sa" gradientUnits="userSpaceOnUse" x1="100" y1="128" x2="156" y2="184"><stop offset="0" stop-color="#FFF4E2"/><stop offset="0.45" stop-color="#F2A541"/><stop offset="1" stop-color="#7A4A10"/></linearGradient></defs><rect width="256" height="256" rx="58" fill="url(#sg)"/><ellipse cx="128" cy="206.12" rx="65.52" ry="11.760000000000002" fill="#21211F" opacity="0.16" filter="url(#sb6)"/><circle cx="128" cy="128" r="84" fill="url(#senv)"/><circle cx="128" cy="128" r="84" fill="url(#spe)"/><g clip-path="url(#sc)"><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="#F2A541" stroke-opacity="0.45" stroke-width="23" stroke-linecap="round" filter="url(#sb6)"/><path d="M 153.17 87.73 A 28 28 0 1 0 115.73 125.17" fill="none" stroke="url(#st)" stroke-width="15" stroke-linecap="round"/><path d="M 102.83 168.27 A 28 28 0 1 0 140.27 130.83" fill="none" stroke="url(#sa)" stroke-width="15" stroke-linecap="round"/></g><g clip-path="url(#sc)"><rect x="64" y="64" width="52" height="26" rx="11" transform="rotate(-22 90 77)" fill="#FFFFFF" opacity="0.75" filter="url(#sb2)"/><rect x="188" y="92" width="9" height="62" rx="4.5" fill="#FFFFFF" opacity="0.5" filter="url(#sb2)"/></g></svg>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice
|
|
3
|
+
"version": "0.63.1",
|
|
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": {
|
|
7
7
|
"symbiot": "index.mjs"
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"lang.mjs",
|
|
58
58
|
"browser.html",
|
|
59
59
|
"qr.mjs",
|
|
60
|
-
"mdns.mjs"
|
|
60
|
+
"mdns.mjs",
|
|
61
|
+
"icons"
|
|
61
62
|
],
|
|
62
63
|
"engines": {
|
|
63
64
|
"node": ">=18"
|
|
@@ -76,15 +77,15 @@
|
|
|
76
77
|
"developer-tools",
|
|
77
78
|
"cli"
|
|
78
79
|
],
|
|
79
|
-
"author": "Garth White <
|
|
80
|
+
"author": "Garth White <garthwhite507@gmail.com>",
|
|
80
81
|
"license": "Elastic-2.0",
|
|
81
82
|
"repository": {
|
|
82
83
|
"type": "git",
|
|
83
|
-
"url": "git+https://github.com/
|
|
84
|
+
"url": "git+https://github.com/GAWdessey/symbiot.git"
|
|
84
85
|
},
|
|
85
|
-
"homepage": "https://github.com/
|
|
86
|
+
"homepage": "https://github.com/GAWdessey/symbiot#readme",
|
|
86
87
|
"bugs": {
|
|
87
|
-
"url": "https://github.com/
|
|
88
|
+
"url": "https://github.com/GAWdessey/symbiot/issues"
|
|
88
89
|
},
|
|
89
90
|
"dependencies": {
|
|
90
91
|
"@anthropic-ai/sdk": "^0.128.0"
|
package/server.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { createServer } from "node:http";
|
|
|
18
18
|
import { randomBytes } from "node:crypto";
|
|
19
19
|
import { EMBEDDED_UI } from "./ui.mjs";
|
|
20
20
|
import { VERSION, LATEST_VERSION, REGISTRY, semverGt, checkLatest, CONFIG_PATH, loadConfig, saveConfig, moveSecrets, loadTasks, hasCmd, chromeBinary, CONFIG_DIR } from "./core.mjs";
|
|
21
|
-
import { shSingle, handoffCmd, setHandoffCmd, grantAgent, runHandoff, track, detectHandoffs, connectorsInfo, linkReach, answerQuestions, skipIdea, agentsList, startWaiting, parkLane, parkedPaths, autoAllowSweep, trustFull, readLastWords, sandboxState } from "./agents.mjs";
|
|
21
|
+
import { shSingle, handoffCmd, setHandoffCmd, grantAgent, runHandoff, track, detectHandoffs, connectorsInfo, linkReach, answerQuestions, skipIdea, agentsList, startWaiting, parkLane, parkedPaths, autoAllowSweep, continueLanes, trustFull, readLastWords, sandboxState } from "./agents.mjs";
|
|
22
22
|
import { PROVIDERS, resolveProvider, connectProvider, detectHardware, recommendModels, hasOllama, ollamaInstall, ensureOllama, useOllamaModel } from "./ai.mjs";
|
|
23
23
|
import { SCAN, SCAN_TIMEOUT_MS, scanRoots, scanHome, addScanRoot, removeScanRoot, buildMap, nodeDetail, repoPathMap, laneMap, setScanOptions, refreshRepos, REPO_STATE, reposState } from "./scan.mjs";
|
|
24
24
|
import { computeDrift } from "./drift.mjs";
|
|
@@ -259,7 +259,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
259
259
|
// the app's typeface ships in the package (fonts/), so it's there offline
|
|
260
260
|
// the window's own icon (its taskbar entry), the same orb as the app menu's
|
|
261
261
|
if (req.method === "GET" && (u.pathname === "/favicon.svg" || u.pathname === "/favicon.ico")) {
|
|
262
|
-
try { const b = iconSvg(u.searchParams.get("look") || loadConfig().look || "ferro"); res.writeHead(200, { "content-type": "image/svg+xml", "cache-control": "no-cache" }); res.end(b); }
|
|
262
|
+
try { const b = iconSvg(u.searchParams.get("look") || loadConfig().look || "ferro", { small: u.searchParams.get("small") === "1" }); res.writeHead(200, { "content-type": "image/svg+xml", "cache-control": "no-cache" }); res.end(b); }
|
|
263
263
|
catch { res.writeHead(404); res.end(); }
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
@@ -618,6 +618,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
618
618
|
// an agent's allow list that stays inside your work is turned on by itself, and the agent carries on
|
|
619
619
|
setTimeout(() => { try { autoAllowSweep(); } catch {} }, 4000).unref();
|
|
620
620
|
setInterval(() => { try { autoAllowSweep(); } catch {} }, 20000).unref();
|
|
621
|
+
setInterval(() => { try { continueLanes(); } catch {} }, 60000).unref(); // a lane with open tasks and no run going starts again, while it gets somewhere
|
|
621
622
|
// a finished run's last words, read by your AI for anything left to you that the patterns missed (agents.mjs readLastWords)
|
|
622
623
|
setInterval(() => { readLastWords().catch(() => {}); }, 60000).unref();
|
|
623
624
|
// knowledge folders: changed files re-read (a stat per file when nothing changed), then checked again for where two files disagree (checks.mjs)
|
package/ui.mjs
CHANGED
|
@@ -392,6 +392,7 @@ body.lq-light .thinking{background-image:linear-gradient(90deg,#4A5565 0%,#0F172
|
|
|
392
392
|
body{background:var(--ink);color:var(--text);font-family:var(--sans);font-size:14px;display:flex;flex-direction:column}
|
|
393
393
|
header{padding:16px 18px 10px;display:flex;align-items:center;gap:10px}
|
|
394
394
|
.dot{width:10px;height:10px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green)}
|
|
395
|
+
header .dot{width:22px;height:22px;border-radius:6px;background:center/contain no-repeat url('/favicon.svg?small=1');box-shadow:none}
|
|
395
396
|
.brand{font-weight:700;color:var(--bone);font-size:16px}
|
|
396
397
|
.status{margin-left:auto;font-size:12px;color:var(--faint);text-align:right;max-width:52%}
|
|
397
398
|
.tabs{display:flex;gap:6px;padding:0 14px;border-bottom:1px solid var(--line)}
|
|
@@ -1093,7 +1094,7 @@ body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
|
1093
1094
|
<div id="updatebar" class="updatebar"></div>
|
|
1094
1095
|
<div id="appbar" class="updatebar"></div>
|
|
1095
1096
|
<div id="whatsnew" class="whatsnew"></div>
|
|
1096
|
-
<header><span class="dot"></span><span class="brand">Symbiot</span><span class="ver" id="ver"></span><span id="lqlook" role="radiogroup" aria-label="Look"><button type="button" role="radio" data-look="glass" aria-checked="false">Glass</button><button type="button" role="radio" data-look="ferro" aria-checked="true">Ferrofluid</button><button type="button" role="radio" data-look="pearl" aria-checked="false">Pearl</button></span><span class="status" id="status">...</span></header>
|
|
1097
|
+
<header><span class="dot" role="img" aria-label="Symbiot"></span><span class="brand">Symbiot</span><span class="ver" id="ver"></span><span id="lqlook" role="radiogroup" aria-label="Look"><button type="button" role="radio" data-look="glass" aria-checked="false">Glass</button><button type="button" role="radio" data-look="ferro" aria-checked="true">Ferrofluid</button><button type="button" role="radio" data-look="pearl" aria-checked="false">Pearl</button></span><span class="status" id="status">...</span></header>
|
|
1097
1098
|
<div class="tabs">
|
|
1098
1099
|
<button class="tab active" data-tab="map">Map</button>
|
|
1099
1100
|
<button class="tab" data-tab="board" id="boardtab">Dashboard</button>
|
|
@@ -3652,7 +3653,7 @@ b.querySelectorAll('[data-link]').forEach(function(x){x.addEventListener('click'
|
|
|
3652
3653
|
b.querySelectorAll('[data-check]').forEach(function(x){x.addEventListener('click',function(){x.disabled=true;api('/api/links/check',{id:x.getAttribute('data-check')}).then(function(){onbLoad();});});});
|
|
3653
3654
|
b.querySelectorAll('[data-skip]').forEach(function(x){x.addEventListener('click',function(){api('/api/onboarding/set',{skip:x.getAttribute('data-skip')}).then(function(r){ONB=r;onbRender();});});});
|
|
3654
3655
|
b.querySelectorAll('[data-unskip]').forEach(function(x){x.addEventListener('click',function(){api('/api/onboarding/set',{unskip:x.getAttribute('data-unskip')}).then(function(r){ONB=r;onbRender();});});});}
|
|
3655
|
-
function lqIcon(l){var k=document.querySelector('link[rel=icon]');if(k)k.setAttribute('href','
|
|
3656
|
+
function lqIcon(l){var q='/favicon.svg?look='+encodeURIComponent(l||'ferro'),k=document.querySelector('link[rel=icon]');if(k)k.setAttribute('href',q+'&small=1');var d=document.querySelector('header .dot');if(d)d.style.backgroundImage="url('"+q+"&small=1')";document.querySelectorAll('img.onborb').forEach(function(im){im.src=q;});}
|
|
3656
3657
|
function lqInit(){var bd=document.body;if(!bd||!bd.classList)return;lqIcon(lqLookGet());lqTheme();try{i18nStart();}catch(e){}bd.classList.add('lq-liquid');
|
|
3657
3658
|
['(prefers-color-scheme: light)','(prefers-contrast: more)','(forced-colors: active)','(prefers-reduced-transparency: reduce)','(prefers-reduced-motion: reduce)'].forEach(function(q){try{var mq=window.matchMedia&&window.matchMedia(q);if(mq&&mq.addEventListener)mq.addEventListener('change',lqTheme);else if(mq&&mq.addListener)mq.addListener(lqTheme);}catch(e){}});
|
|
3658
3659
|
if(document.addEventListener){document.addEventListener('contextmenu',function(ev){var b=document.body,t=ev&&ev.target;if(!b||!b.classList||!b.classList.contains('lq-liquid'))return;if(t&&t.closest&&t.closest('input,textarea,select,[contenteditable]'))return;var sel=window.getSelection?String(window.getSelection()):'';if(sel)return;ev.preventDefault();lqBack();});
|