symbiot 0.22.0 → 0.23.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/index.mjs +57 -3
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -57,6 +57,8 @@ const PLAIN = has("plain") || !process.stdout.isTTY;
|
|
|
57
57
|
let SERVING = false; // set while `symbiot app` runs — silences the CLI spinner
|
|
58
58
|
let LAST_MAP = null; // cached graph so node clicks don't rescan
|
|
59
59
|
const HANDOFFS = []; // live registry of agents Symbiot has handed work to
|
|
60
|
+
let LATEST_VERSION = ""; // newest symbiot on npm, checked in the background
|
|
61
|
+
async function checkLatest() { try { const r = await fetch("https://registry.npmjs.org/symbiot"); if (!r.ok) return; const j = await r.json(); LATEST_VERSION = (j["dist-tags"] && j["dist-tags"].latest) || ""; } catch {} }
|
|
60
62
|
|
|
61
63
|
// ---- colour + spinner ------------------------------------------------------
|
|
62
64
|
const c = PLAIN
|
|
@@ -1268,6 +1270,10 @@ select,input{width:100%;background:var(--ink);border:1px solid var(--line);borde
|
|
|
1268
1270
|
select:focus,input:focus{outline:none;border-color:var(--green)}
|
|
1269
1271
|
a{color:var(--green);cursor:pointer}.hidden{display:none}
|
|
1270
1272
|
.note{font-size:12px;margin-top:10px}.ok{color:var(--green)}.err{color:var(--amber)}
|
|
1273
|
+
.updatebar{display:none;align-items:center;gap:12px;padding:9px 16px;font-size:13px;font-weight:600;background:var(--green);color:var(--ink)}
|
|
1274
|
+
.updatebar.show{display:flex}
|
|
1275
|
+
.updatebar.reconnect{background:var(--amber)}
|
|
1276
|
+
.updatebar button{font:inherit;font-weight:700;border:0;border-radius:8px;padding:5px 12px;background:var(--ink);color:var(--bone);cursor:pointer}
|
|
1271
1277
|
footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
1272
1278
|
.profile{font-size:13px;margin-bottom:8px;line-height:1.5}
|
|
1273
1279
|
.profile b{color:var(--bone)}
|
|
@@ -1332,6 +1338,7 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
1332
1338
|
.lg i{width:10px;height:10px;border-radius:50%;display:inline-block}
|
|
1333
1339
|
@media(max-width:760px){.maprow{flex-direction:column}.detail{width:auto;max-height:none}}
|
|
1334
1340
|
</style></head><body>
|
|
1341
|
+
<div id="updatebar" class="updatebar"></div>
|
|
1335
1342
|
<header><span class="dot"></span><span class="brand">Symbiot</span><span class="status" id="status">...</span></header>
|
|
1336
1343
|
<div class="tabs">
|
|
1337
1344
|
<button class="tab active" data-tab="map">Map</button>
|
|
@@ -1631,6 +1638,24 @@ document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
|
1631
1638
|
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
1632
1639
|
document.getElementById('agentcmd').addEventListener('change',saveAgent);
|
|
1633
1640
|
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
1641
|
+
var SRV_STARTED=null,srvDown=false,updBusy=false;
|
|
1642
|
+
function ubar(){return document.getElementById('updatebar');}
|
|
1643
|
+
function heartbeat(){
|
|
1644
|
+
api('/api/ping').then(function(p){
|
|
1645
|
+
var b=ubar();
|
|
1646
|
+
if(SRV_STARTED===null){SRV_STARTED=p.started;}
|
|
1647
|
+
else if(p.started!==SRV_STARTED){location.reload();return;}
|
|
1648
|
+
if(srvDown){location.reload();return;}
|
|
1649
|
+
if(updBusy)return;
|
|
1650
|
+
if(p.latest&&p.version&&p.latest!==p.version){
|
|
1651
|
+
b.className='updatebar show';
|
|
1652
|
+
b.innerHTML="A new Symbiot ("+esc(p.latest)+") is available — you're on "+esc(p.version)+". <button id='doupd'>Update & restart</button>";
|
|
1653
|
+
var btn=document.getElementById('doupd');if(btn)btn.onclick=doUpdate;
|
|
1654
|
+
}else{b.className='updatebar';}
|
|
1655
|
+
}).catch(function(){srvDown=true;var b=ubar();b.className='updatebar reconnect show';b.textContent='Reconnecting to Symbiot…';});
|
|
1656
|
+
}
|
|
1657
|
+
function doUpdate(){updBusy=true;var b=ubar();b.className='updatebar show';b.textContent='Updating & restarting… this page will reload itself when it is back.';api('/api/update',{});}
|
|
1658
|
+
setInterval(heartbeat,4000);heartbeat();
|
|
1634
1659
|
initGraphEvents();syncP();refresh();loadMap();loadAgentCfg();loadScanRoots();
|
|
1635
1660
|
</script></body></html>`;
|
|
1636
1661
|
|
|
@@ -1673,7 +1698,13 @@ function openApp(url) {
|
|
|
1673
1698
|
}
|
|
1674
1699
|
async function cmdApp() {
|
|
1675
1700
|
SERVING = true;
|
|
1676
|
-
const
|
|
1701
|
+
const SERVER_STARTED = Date.now();
|
|
1702
|
+
// Stable token + port so the URL survives a restart — the open tab can
|
|
1703
|
+
// reconnect and auto-reload itself instead of you closing and reopening it.
|
|
1704
|
+
const cfg0 = loadConfig();
|
|
1705
|
+
let TOKEN = cfg0.appToken;
|
|
1706
|
+
if (!TOKEN) { TOKEN = randomBytes(16).toString("hex"); try { saveConfig({ ...loadConfig(), appToken: TOKEN }); } catch {} }
|
|
1707
|
+
const PORT = Number(process.env.SYMBIOT_PORT || cfg0.appPort) || 7391;
|
|
1677
1708
|
const json = (res, obj) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
|
|
1678
1709
|
const server = createServer(async (req, res) => {
|
|
1679
1710
|
const u = new URL(req.url, "http://127.0.0.1");
|
|
@@ -1714,17 +1745,40 @@ async function cmdApp() {
|
|
|
1714
1745
|
if (u.pathname === "/api/agents") return json(res, HANDOFFS.map((e) => { let tail = ""; try { tail = readFileSync(e.log, "utf8").slice(-1200); } catch {} return { id: e.id, name: e.name, path: e.path, status: e.status, elapsed: (e.endedAt || Date.now()) - e.startedAt, exitCode: e.exitCode, tail }; }));
|
|
1715
1746
|
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, await produce(cmd)); }
|
|
1716
1747
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1748
|
+
if (u.pathname === "/api/ping") return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION });
|
|
1749
|
+
if (u.pathname === "/api/update" && req.method === "POST") {
|
|
1750
|
+
// Install the newest symbiot, then relaunch this same app (same port +
|
|
1751
|
+
// token => same URL) and exit. The open page's heartbeat reconnects and
|
|
1752
|
+
// reloads to the new version. No terminal, no reopening the window.
|
|
1753
|
+
const inst = process.platform === "win32" ? "npm i -g symbiot@latest" : "npm install -g symbiot@latest";
|
|
1754
|
+
const e = track("symbiot update", inst, homedir(), (code) => {
|
|
1755
|
+
if (code !== 0) return;
|
|
1756
|
+
try { const ch = spawn(process.execPath, process.argv.slice(1), { detached: true, stdio: "ignore", env: process.env }); ch.unref(); } catch {}
|
|
1757
|
+
setTimeout(() => process.exit(0), 1200);
|
|
1758
|
+
});
|
|
1759
|
+
return json(res, { started: true, id: e ? e.id : "" });
|
|
1760
|
+
}
|
|
1717
1761
|
if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
|
|
1718
1762
|
} catch (e) { res.writeHead(500, { "content-type": "application/json" }); res.end(JSON.stringify({ error: String((e && e.message) || e) })); return; }
|
|
1719
1763
|
res.writeHead(404); res.end("not found");
|
|
1720
1764
|
});
|
|
1721
|
-
|
|
1765
|
+
let announced = false, tries = 0, opened = false;
|
|
1766
|
+
server.on("listening", () => {
|
|
1767
|
+
if (announced) return; announced = true;
|
|
1722
1768
|
const url = `http://127.0.0.1:${server.address().port}/?t=${TOKEN}`;
|
|
1723
|
-
const how = openApp(url);
|
|
1769
|
+
const how = opened ? "" : openApp(url); opened = true; // only pop a window the first time
|
|
1724
1770
|
console.log(`\n${c.g("●")} ${c.b("Symbiot")} is running at ${c.b(url)}`);
|
|
1725
1771
|
console.log(how ? c.d(` Opened in a ${how}.`) : c.d(" Open that URL in your browser."));
|
|
1726
1772
|
console.log(c.d(" Leave this running; press Ctrl+C to stop (or click Quit in the window)."));
|
|
1727
1773
|
});
|
|
1774
|
+
server.on("error", (e) => {
|
|
1775
|
+
// Stable port busy (an older instance still exiting during an update, or a
|
|
1776
|
+
// second app): retry briefly, then fall back to a random port.
|
|
1777
|
+
if (e && e.code === "EADDRINUSE" && tries < 8) { tries++; setTimeout(() => { try { server.listen(PORT, "127.0.0.1"); } catch {} }, 500); }
|
|
1778
|
+
else { try { server.listen(0, "127.0.0.1"); } catch {} }
|
|
1779
|
+
});
|
|
1780
|
+
server.listen(PORT, "127.0.0.1");
|
|
1781
|
+
checkLatest(); setInterval(checkLatest, 10 * 60 * 1000).unref(); // background update check
|
|
1728
1782
|
}
|
|
1729
1783
|
|
|
1730
1784
|
const HELP = `${c.b("symbiot")} — your week, written from your real work.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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": {
|