symbiot 0.55.0 → 0.55.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 +6 -0
- package/README.md +5 -5
- package/away.mjs +18 -12
- package/index.mjs +4 -4
- package/package.json +1 -1
- package/server.mjs +6 -2
- package/ui.mjs +12 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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.55.1
|
|
6
|
+
|
|
7
|
+
- **No more stray Symbiot windows.** 0.55.0's smoke test called Away like every other endpoint, so each full test run (yours, or an agent's in the symbiot repo) opened Away windows on your screen. Away now opens only when asked to by name, and never while windows are turned off (as in tests).
|
|
8
|
+
- **One Symbiot window, not a pile.** Opening Symbiot again (its icon, `symbiot app`) used to add a window each time. Now the newest window wins and older ones close themselves; a tab that can't close itself says Symbiot is open in a newer window, with **Use this one**. An update's restart that can't get its port bows out instead of lingering unseen.
|
|
9
|
+
- **Away is Super+`** (Super and the key above Tab). Super+S stacks windows on COSMIC, so it was the wrong key; `symbiot away --shortcut` sets up Super+` and clears the old one.
|
|
10
|
+
|
|
5
11
|
## 0.55.0
|
|
6
12
|
|
|
7
13
|
- **Away: Super+S.** Symbiot full screen while you're away from the desk: the orb at rest, the time, the agents at work and what each is doing, how many things only you can do, and how much waits on what you watch. Counts only, never anyone's words. Any key or click brings you back. On X11 with more than one screen the orb bounces across all of them; under Wayland (COSMIC, GNOME, KDE) it's one window on your screen, because no app may place windows there. `symbiot away --shortcut` sets up Super+S (on COSMIC it's done for you). Super+Esc still locks.
|
package/README.md
CHANGED
|
@@ -643,21 +643,21 @@ of this: it adapts to you.
|
|
|
643
643
|
Everything it learns stays on this computer: `adapt.json` and `mind.json` in
|
|
644
644
|
Symbiot's config folder, readable by you only.
|
|
645
645
|
|
|
646
|
-
### Away: Super
|
|
646
|
+
### Away: Super+`
|
|
647
647
|
|
|
648
|
-
Stepping away? **Super
|
|
648
|
+
Stepping away? **Super+`** (Super and the key above Tab), or `symbiot away`, puts Symbiot full screen: the orb at
|
|
649
649
|
rest, the time, and what's going on. That means the agents at work and what each is
|
|
650
650
|
doing, how many things only you can do, and how much waits on what you watch. It shows
|
|
651
651
|
counts only, never anyone's words, so it's fine on a screen across the room. Any key
|
|
652
|
-
or click (or Super
|
|
652
|
+
or click (or Super+` again) brings you back.
|
|
653
653
|
|
|
654
654
|
On one screen the orb rests in the middle. On X11 desktops with more screens it
|
|
655
655
|
bounces across all of them, one window per screen, with the orb crossing from one to
|
|
656
656
|
the next. Under Wayland (COSMIC, GNOME, KDE) no app may place a window on a screen, so
|
|
657
657
|
it's one window on the screen you're on.
|
|
658
658
|
|
|
659
|
-
`symbiot away --shortcut` sets up Super
|
|
660
|
-
|
|
659
|
+
`symbiot away --shortcut` sets up Super+`. On COSMIC that's done for you; elsewhere
|
|
660
|
+
it tells you what to bind.
|
|
661
661
|
Away isn't a lock: **Super+Esc** still locks the computer.
|
|
662
662
|
|
|
663
663
|
## Link your work
|
package/away.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// symbiot — Away: Symbiot full screen on every screen while you're away from the
|
|
2
|
-
// desk (Super
|
|
2
|
+
// desk (Super+`, or `symbiot away`): the orb at rest, the time, and what's going on
|
|
3
3
|
// (agents at work and what they're doing, how much waits on you), counts only, no
|
|
4
4
|
// one's words. One screen: the orb in the middle. More: it bounces across all of
|
|
5
5
|
// them, one kiosk window per screen, each working out where the orb is from the
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// own move-to-screen shortcut). So under Wayland it's one window, full screen where
|
|
12
12
|
// you are, the orb in the middle. Each window's profile lets Symbiot's address see the
|
|
13
13
|
// screens and go full screen without a click, so nothing asks.
|
|
14
|
-
// Any key or click, or Super
|
|
14
|
+
// Any key or click, or Super+` again, closes every window. Not a lock: Super+Esc
|
|
15
15
|
// locks the computer.
|
|
16
16
|
import { spawn, spawnSync } from "node:child_process";
|
|
17
17
|
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
@@ -65,6 +65,7 @@ function grantProfile(dir, origin) {
|
|
|
65
65
|
const placeable = (env = process.env) => (env.XDG_SESSION_TYPE || "").toLowerCase() === "x11" || (!env.WAYLAND_DISPLAY && !!env.DISPLAY);
|
|
66
66
|
function openAway(url, { chrome = chromeBinary(), scr = placeable() ? screens() : { list: [], display: "" }, now = Date.now() } = {}) {
|
|
67
67
|
if (!chrome) return { error: "Away needs Chrome or Chromium on this computer." };
|
|
68
|
+
if (process.env.SYMBIOT_NO_OPEN === "1") return { open: false, note: "Windows are off here (SYMBIOT_NO_OPEN): tests, and runs that mustn't open any." };
|
|
68
69
|
closeAway();
|
|
69
70
|
const qs = awayQueries(scr.list, now);
|
|
70
71
|
const origin = (String(url).match(/^https?:\/\/[^/?#]+/) || [""])[0];
|
|
@@ -77,31 +78,36 @@ function openAway(url, { chrome = chromeBinary(), scr = placeable() ? screens()
|
|
|
77
78
|
});
|
|
78
79
|
return { open: KIDS.length > 0, screens: Math.max(1, scr.list.length) };
|
|
79
80
|
}
|
|
80
|
-
//
|
|
81
|
+
// open: true opens, false closes, "toggle" goes the other way; anything else only
|
|
82
|
+
// says whether it's open (a call that names nothing never opens windows: the smoke
|
|
83
|
+
// test calls every endpoint, and it opened Away on the owner's screen every run).
|
|
81
84
|
function toggleAway(url, open, deps) {
|
|
82
|
-
|
|
85
|
+
if (open !== true && open !== false && open !== "toggle") return { open: awayOpen() };
|
|
86
|
+
const want = open === "toggle" ? !awayOpen() : open;
|
|
83
87
|
if (!want) { closeAway(); return { open: false }; }
|
|
84
88
|
return openAway(url, deps);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
// ---- the shortcut -------------------------------------------------------------
|
|
88
|
-
// Super
|
|
89
|
-
//
|
|
90
|
-
//
|
|
92
|
+
// Super+` (the key above Tab) runs `symbiot away`: free on COSMIC, where Super+S
|
|
93
|
+
// stacks windows. COSMIC: a custom shortcut in its config (it reloads it as it
|
|
94
|
+
// changes). Elsewhere it says how to add one.
|
|
91
95
|
const COSMIC_CUSTOM = (home = homedir()) => join(home, ".config", "cosmic", "com.system76.CosmicSettings.Shortcuts", "v1", "custom");
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
const KEY = "grave";
|
|
97
|
+
function shortcutLine(cmd) { return ` (modifiers: [Super], key: "${KEY}"): Spawn(${JSON.stringify(cmd)}),`; }
|
|
98
|
+
// The new custom file: whatever was there, minus any Super+` and any earlier one of
|
|
99
|
+
// ours (on another key), plus ours.
|
|
94
100
|
function withShortcut(text, cmd) {
|
|
95
101
|
const body = String(text || "").trim().replace(/^\{/, "").replace(/\}\s*$/, "");
|
|
96
|
-
const kept = body.split("\n").filter((l) => l.trim() && !/\(modifiers:\s*\[\s*Super\s*,?\s*\],\s*key:\s*"
|
|
102
|
+
const kept = body.split("\n").filter((l) => l.trim() && !/\(modifiers:\s*\[\s*Super\s*,?\s*\],\s*key:\s*"grave"\s*\)/.test(l) && !/Spawn\("[^"]*symbiot[^"]* away"\)/.test(l));
|
|
97
103
|
return "{\n" + [...kept.map((l) => l.replace(/\s*$/, "")), shortcutLine(cmd)].join("\n") + "\n}\n";
|
|
98
104
|
}
|
|
99
105
|
function installShortcut({ cmd, home = homedir(), desktop = process.env.XDG_CURRENT_DESKTOP || "" } = {}) {
|
|
100
|
-
if (!/cosmic/i.test(desktop)) return { manual: true, note: `Add a keyboard shortcut in your desktop's settings: Super
|
|
106
|
+
if (!/cosmic/i.test(desktop)) return { manual: true, note: `Add a keyboard shortcut in your desktop's settings: Super+\` runs ${cmd}` };
|
|
101
107
|
const f = COSMIC_CUSTOM(home);
|
|
102
108
|
let had = ""; try { had = existsSync(f) ? readFileSync(f, "utf8") : ""; } catch {}
|
|
103
109
|
try { mkdirSync(join(f, ".."), { recursive: true }); writeFileSync(f, withShortcut(had, cmd)); } catch (e) { return { error: "Couldn't write " + f + ": " + ((e && e.message) || e) }; }
|
|
104
|
-
return { ok: true, file: f, replaced: /key:\s*"
|
|
110
|
+
return { ok: true, file: f, replaced: /key:\s*"grave"/.test(had) };
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
export { placeable, grantProfile, parseMonitors, screens, awayQueries, openAway, closeAway, awayOpen, toggleAway, withShortcut, installShortcut, COSMIC_CUSTOM };
|
package/index.mjs
CHANGED
|
@@ -329,16 +329,16 @@ async function viaApp(path, body) {
|
|
|
329
329
|
} catch (e) { return { error: "Lost Symbiot's app mid-way: " + ((e && e.message) || e) }; }
|
|
330
330
|
}
|
|
331
331
|
// symbiot away: Symbiot full screen on every screen (again: closes it). --shortcut:
|
|
332
|
-
// Super
|
|
332
|
+
// Super+` runs it (on COSMIC it's set up; elsewhere it says how).
|
|
333
333
|
async function cmdAway() {
|
|
334
334
|
if (has("shortcut")) {
|
|
335
335
|
const { installShortcut } = await import("./away.mjs");
|
|
336
336
|
const r = installShortcut({ cmd: `${process.execPath} ${process.argv[1]} away` });
|
|
337
337
|
if (r.error) { console.log(c.y(r.error)); process.exitCode = 1; return; }
|
|
338
|
-
console.log(r.manual ? r.note : `${c.g("✓")} Super
|
|
338
|
+
console.log(r.manual ? r.note : `${c.g("✓")} Super+\` (the key above Tab) now opens Away. ${c.d(r.file)}`);
|
|
339
339
|
return;
|
|
340
340
|
}
|
|
341
|
-
const r = await viaApp("/api/away", {});
|
|
341
|
+
const r = await viaApp("/api/away", { open: "toggle" });
|
|
342
342
|
if (!r) { console.log(c.y("Symbiot's app isn't running.") + c.d(" Start it with symbiot app, then Away works.")); process.exitCode = 1; return; }
|
|
343
343
|
if (r.error) { console.log(c.y(r.error)); process.exitCode = 1; }
|
|
344
344
|
}
|
|
@@ -768,7 +768,7 @@ ${c.b("Usage")}
|
|
|
768
768
|
symbiot todo what's still on your plate
|
|
769
769
|
symbiot app open the visual app in your browser
|
|
770
770
|
symbiot away [--shortcut] Symbiot full screen on every screen while you're away;
|
|
771
|
-
again closes it. --shortcut: Super
|
|
771
|
+
again closes it. --shortcut: Super+\` does it
|
|
772
772
|
symbiot app --fresh [--keep] try it as someone new: a brand-new Symbiot in
|
|
773
773
|
a throwaway home, none of your data or accounts
|
|
774
774
|
symbiot drift [--fetch] what's out of sync / at risk across repos
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.1",
|
|
4
4
|
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice \u2014 Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.mjs
CHANGED
|
@@ -214,6 +214,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
214
214
|
const json = (res, obj) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
|
|
215
215
|
const screenOut =(s) => (s && s.id ? { ...s, blueprint: blueprint(s), ...(s.page ? { trusted: isTrusted(s.page.url) } : {}) } : s && s.screens ? { ...s, screens: s.screens.map(screenOut) } : s);
|
|
216
216
|
let UPDATING = null; // an update in flight: { target, attempt, retrying? }
|
|
217
|
+
let NEWEST_WIN = ""; // the Symbiot window opened last: older ones close themselves (one window, not a pile)
|
|
217
218
|
process.on("exit", () => closeAway()); // Away's windows go with the app (a quit, an update's restart)
|
|
218
219
|
const server = createServer(async (req, res) => {
|
|
219
220
|
const u = new URL(req.url, "http://127.0.0.1");
|
|
@@ -418,7 +419,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
418
419
|
if (u.pathname === "/api/run" && req.method === "POST") { const b = await readBody(req); const cmd = ["week", "standup", "todo"].includes(b.cmd) ? b.cmd : "week"; return json(res, cmd === "week" ? await runWeekly(writeup, { notify: false }) : await writeup(cmd)); }
|
|
419
420
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
420
421
|
// a sandbox (symbiot app --fresh) never offers an update: it would replace your real install
|
|
421
|
-
if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1" && !SANDBOX) await checkLatest(); return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION, newer: !SANDBOX && semverGt(LATEST_VERSION, VERSION), ...(UPDATING && UPDATING.retrying ? { retrying: UPDATING } : {}), ...(IN_TERMUX ? { termux: true } : {}), ...(SANDBOX ? { sandbox: true } : {}) }); }
|
|
422
|
+
if (u.pathname === "/api/ping") { if (u.searchParams.get("fresh") === "1" && !SANDBOX) await checkLatest(); const w = u.searchParams.get("w") || ""; if (/^[a-z0-9]{6,20}$/.test(w) && u.searchParams.get("new") === "1") NEWEST_WIN = w; return json(res, { ...(NEWEST_WIN ? { window: NEWEST_WIN } : {}), version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION, newer: !SANDBOX && semverGt(LATEST_VERSION, VERSION), ...(UPDATING && UPDATING.retrying ? { retrying: UPDATING } : {}), ...(IN_TERMUX ? { termux: true } : {}), ...(SANDBOX ? { sandbox: true } : {}) }); }
|
|
422
423
|
// What's new: after an update, since the version you last saw (until you click Got it);
|
|
423
424
|
// ?latest=1, what the update on offer brings, from its package on npm
|
|
424
425
|
if (u.pathname === "/api/whatsnew") {
|
|
@@ -473,7 +474,10 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
473
474
|
server.on("error", (e) => {
|
|
474
475
|
// Stable port busy (an older instance still exiting during an update, or a
|
|
475
476
|
// second app): retry briefly, then fall back to a random port.
|
|
476
|
-
if (e && e.code === "EADDRINUSE" && tries < 8) { tries++; setTimeout(() => { try { server.listen(PORT, "127.0.0.1"); } catch {} }, 500); }
|
|
477
|
+
if (e && e.code === "EADDRINUSE" && tries < (RELAUNCH ? 40 : 8)) { tries++; setTimeout(() => { try { server.listen(PORT, "127.0.0.1"); } catch {} }, 500); }
|
|
478
|
+
// after an update, the window only ever looks for the stable port: a copy on another
|
|
479
|
+
// port would serve nobody and linger (one did, for hours), so it bows out
|
|
480
|
+
else if (RELAUNCH) process.exit(0);
|
|
477
481
|
else { try { server.listen(0, "127.0.0.1"); } catch {} }
|
|
478
482
|
});
|
|
479
483
|
server.listen(PORT, "127.0.0.1");
|
package/ui.mjs
CHANGED
|
@@ -818,7 +818,7 @@ body.lq-light .lqatt img{border-color:rgba(21,26,33,.15)}body.lq-light .lqatt bu
|
|
|
818
818
|
#lqform.drop{outline:2px dashed rgba(242,165,65,.7);outline-offset:4px}
|
|
819
819
|
.lqmimgs{display:flex;gap:6px;margin-bottom:6px}.lqmimgs img{width:120px;max-height:90px;object-fit:cover;border-radius:8px}
|
|
820
820
|
|
|
821
|
-
/* Away (Super
|
|
821
|
+
/* Away (Super and the key above Tab): the orb, the time and what's going on, on every screen; nothing else */
|
|
822
822
|
#lqaway{display:none}
|
|
823
823
|
body.lq-away{cursor:none;overflow:hidden}
|
|
824
824
|
body.lq-away #lq{position:fixed!important;inset:0!important;width:100vw!important;height:100vh!important;display:block!important}
|
|
@@ -2280,8 +2280,17 @@ document.getElementById('autostart').addEventListener('change',saveAutostart);
|
|
|
2280
2280
|
(function(){var h='';for(var i=0;i<24;i++)h+="<option value='"+i+"'>"+(i<10?'0':'')+i+":00</option>";document.getElementById('weeklyhour').innerHTML=h;})();
|
|
2281
2281
|
var SRV_STARTED=null,srvDown=false,updBusy=false;
|
|
2282
2282
|
function ubar(){return document.getElementById('updatebar');}
|
|
2283
|
+
// One Symbiot window, not a pile: each window has an id, says so the first time it
|
|
2284
|
+
// asks, and the server remembers the newest. An older one closes itself; where the
|
|
2285
|
+
// browser won't let it (a tab you opened), it says so and can take over. Away's
|
|
2286
|
+
// windows don't count.
|
|
2287
|
+
var WIN_ID=/[?&]away=1(&|$)/.test(location.search||'')?'':Math.random().toString(36).slice(2,12),WIN_NEW=true; // read from the address: AWAY is set further down
|
|
2288
|
+
function winTakeOver(){WIN_NEW=true;var o=$('winold');if(o)o.remove();heartbeat(false);}
|
|
2289
|
+
function winOld(){try{window.close();}catch(e){}setTimeout(function(){if($('winold')||!document.body)return;var o=document.createElement('div');o.id='winold';o.className='updatebar reconnect show';o.innerHTML="Symbiot is open in a newer window. <button class='ghost' id='wintake'>Use this one</button>";document.body.appendChild(o);var t=$('wintake');if(t)t.onclick=winTakeOver;},400);}
|
|
2283
2290
|
function heartbeat(fresh){
|
|
2284
|
-
|
|
2291
|
+
var q=[];if(fresh)q.push('fresh=1');if(WIN_ID){q.push('w='+WIN_ID);if(WIN_NEW)q.push('new=1');}
|
|
2292
|
+
api('/api/ping'+(q.length?'?'+q.join('&'):'')).then(function(p){
|
|
2293
|
+
if(WIN_ID){WIN_NEW=false;if(p&&p.window&&p.window!==WIN_ID){if(!$('winold'))winOld();return;}}
|
|
2285
2294
|
var b=ubar();
|
|
2286
2295
|
var ve=document.getElementById('ver');if(ve&&p.version){ve.textContent='v'+p.version+(p.sandbox?' · sandbox':'');if(p.sandbox){ve.title='A brand-new Symbiot to try first run in (symbiot app --fresh): its own empty home, none of your data, accounts or memory. It is deleted when you quit.';ve.style.color='var(--amber)';}}
|
|
2287
2296
|
if(SRV_STARTED===null){SRV_STARTED=p.started;appBar(p);}
|
|
@@ -2817,7 +2826,7 @@ var U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,slow?0:t);gl.uniform1f(U.uD
|
|
|
2817
2826
|
gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.uContrast,th.contrast?1:0);gl.uniform1f(U.uExposure,th.night?0.82:1);
|
|
2818
2827
|
gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
2819
2828
|
|
|
2820
|
-
// ---- Away (Super
|
|
2829
|
+
// ---- Away (Super and the key above Tab) ------------------------------------------------------------
|
|
2821
2830
|
// ?away=1 from "symbiot away": this window is one of the screens. One screen: the
|
|
2822
2831
|
// orb rests in the middle. More: it bounces across their shared area (gw x gh) at
|
|
2823
2832
|
// the same pace in every window from the same start (t0), each drawing it while it's
|