symbiot 0.22.0 → 0.24.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 +77 -6
- 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
|
|
@@ -636,12 +638,16 @@ function detectHandoffs() {
|
|
|
636
638
|
// Orca IDE (any OS): register the repo + open a terminal tab. Two variants —
|
|
637
639
|
// one that just opens the repo (use Orca's own agent, e.g. GPT), and one that
|
|
638
640
|
// runs Claude in the tab for Claude users. Not Claude-only.
|
|
641
|
+
// `<orca> open` first: it launches Orca AND blocks until the runtime is
|
|
642
|
+
// reachable, so this works even when Orca is closed (its CLI can't talk to a
|
|
643
|
+
// dead app). Use the full orca-ide path, never bare `orca` (that's a different
|
|
644
|
+
// tool on PATH). If Orca is already up, `open` returns fast.
|
|
639
645
|
const orca = findOrcaCli();
|
|
640
646
|
if (orca) {
|
|
641
647
|
const q = JSON.stringify(orca);
|
|
642
648
|
agents.unshift(
|
|
643
|
-
{ label: "Orca IDE — open repo (use your Orca agent)", tmpl: `${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --focus`, kind: "agent" },
|
|
644
|
-
{ label: "Orca IDE — run Claude in a tab", tmpl: `${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --command "claude {prompt}" --focus`, kind: "agent" },
|
|
649
|
+
{ label: "Orca IDE — open repo (use your Orca agent)", tmpl: `${q} open; ${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --focus`, kind: "agent" },
|
|
650
|
+
{ label: "Orca IDE — run Claude in a tab", tmpl: `${q} open; ${q} repo add --path {dir}; ${q} terminal create --worktree path:{dir} --command "claude {prompt}" --focus`, kind: "agent" },
|
|
645
651
|
);
|
|
646
652
|
}
|
|
647
653
|
return { agents, editors };
|
|
@@ -649,7 +655,20 @@ function detectHandoffs() {
|
|
|
649
655
|
const shSingle = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
650
656
|
const escDq = (s) => String(s).replace(/[\\"$`]/g, "\\$&");
|
|
651
657
|
const HANDOFF_PROMPT = "Read .symbiot/TASKS.md and implement the unchecked items in this repo. Confirm before anything destructive.";
|
|
652
|
-
function handoffCmd() { const cfg = loadConfig(); return cfg.agentCmd || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
|
|
658
|
+
function handoffCmd() { const cfg = loadConfig(); return migrateOrcaCmd(cfg.agentCmd) || (cfg.ide ? `${cfg.ide} {dir}` : ""); } // ide = legacy
|
|
659
|
+
// An Orca command saved before we learned its CLI needs a running app lacks the
|
|
660
|
+
// `open` step and silently fails when Orca is closed. Patch it in place: insert
|
|
661
|
+
// `<orca> open; ` before the first `repo add`. Idempotent (skips if already there).
|
|
662
|
+
function migrateOrcaCmd(cmd) {
|
|
663
|
+
if (!cmd || typeof cmd !== "string") return cmd;
|
|
664
|
+
if (!/orca-ide/.test(cmd) || !/\brepo add\b/.test(cmd)) return cmd;
|
|
665
|
+
if (/orca-ide("|')?\s+open\b|\bopen;\s/.test(cmd)) return cmd; // already migrated
|
|
666
|
+
const m = cmd.match(/^(\S+|"[^"]*"|'[^']*')\s+repo add/); // the orca binary token
|
|
667
|
+
const bin = m ? m[1] : "";
|
|
668
|
+
const patched = bin ? cmd.replace(/(\S+|"[^"]*"|'[^']*')\s+repo add/, `${bin} open; $1 repo add`) : cmd;
|
|
669
|
+
if (patched !== cmd) { try { const cfg = loadConfig(); if (cfg.agentCmd === cmd) { cfg.agentCmd = patched; saveConfig(cfg); } } catch {} }
|
|
670
|
+
return patched;
|
|
671
|
+
}
|
|
653
672
|
// Run a shell command as a tracked, logged background job that shows up live in
|
|
654
673
|
// the Agents tab. Shared by the agent handoff and the local-model setup.
|
|
655
674
|
function track(name, cmd, cwd, onExit) {
|
|
@@ -1268,6 +1287,10 @@ select,input{width:100%;background:var(--ink);border:1px solid var(--line);borde
|
|
|
1268
1287
|
select:focus,input:focus{outline:none;border-color:var(--green)}
|
|
1269
1288
|
a{color:var(--green);cursor:pointer}.hidden{display:none}
|
|
1270
1289
|
.note{font-size:12px;margin-top:10px}.ok{color:var(--green)}.err{color:var(--amber)}
|
|
1290
|
+
.updatebar{display:none;align-items:center;gap:12px;padding:9px 16px;font-size:13px;font-weight:600;background:var(--green);color:var(--ink)}
|
|
1291
|
+
.updatebar.show{display:flex}
|
|
1292
|
+
.updatebar.reconnect{background:var(--amber)}
|
|
1293
|
+
.updatebar button{font:inherit;font-weight:700;border:0;border-radius:8px;padding:5px 12px;background:var(--ink);color:var(--bone);cursor:pointer}
|
|
1271
1294
|
footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
1272
1295
|
.profile{font-size:13px;margin-bottom:8px;line-height:1.5}
|
|
1273
1296
|
.profile b{color:var(--bone)}
|
|
@@ -1332,6 +1355,7 @@ footer{padding:10px 18px;border-top:1px solid var(--line);display:flex}
|
|
|
1332
1355
|
.lg i{width:10px;height:10px;border-radius:50%;display:inline-block}
|
|
1333
1356
|
@media(max-width:760px){.maprow{flex-direction:column}.detail{width:auto;max-height:none}}
|
|
1334
1357
|
</style></head><body>
|
|
1358
|
+
<div id="updatebar" class="updatebar"></div>
|
|
1335
1359
|
<header><span class="dot"></span><span class="brand">Symbiot</span><span class="status" id="status">...</span></header>
|
|
1336
1360
|
<div class="tabs">
|
|
1337
1361
|
<button class="tab active" data-tab="map">Map</button>
|
|
@@ -1631,6 +1655,24 @@ document.getElementById('addtask').addEventListener('click',addTaskUI);
|
|
|
1631
1655
|
document.getElementById('pushtasks').addEventListener('click',pushTasksUI);
|
|
1632
1656
|
document.getElementById('agentcmd').addEventListener('change',saveAgent);
|
|
1633
1657
|
document.getElementById('newtask').addEventListener('keydown',function(e){if(e.key==='Enter')addTaskUI();});
|
|
1658
|
+
var SRV_STARTED=null,srvDown=false,updBusy=false;
|
|
1659
|
+
function ubar(){return document.getElementById('updatebar');}
|
|
1660
|
+
function heartbeat(){
|
|
1661
|
+
api('/api/ping').then(function(p){
|
|
1662
|
+
var b=ubar();
|
|
1663
|
+
if(SRV_STARTED===null){SRV_STARTED=p.started;}
|
|
1664
|
+
else if(p.started!==SRV_STARTED){location.reload();return;}
|
|
1665
|
+
if(srvDown){location.reload();return;}
|
|
1666
|
+
if(updBusy)return;
|
|
1667
|
+
if(p.latest&&p.version&&p.latest!==p.version){
|
|
1668
|
+
b.className='updatebar show';
|
|
1669
|
+
b.innerHTML="A new Symbiot ("+esc(p.latest)+") is available — you're on "+esc(p.version)+". <button id='doupd'>Update & restart</button>";
|
|
1670
|
+
var btn=document.getElementById('doupd');if(btn)btn.onclick=doUpdate;
|
|
1671
|
+
}else{b.className='updatebar';}
|
|
1672
|
+
}).catch(function(){srvDown=true;var b=ubar();b.className='updatebar reconnect show';b.textContent='Reconnecting to Symbiot…';});
|
|
1673
|
+
}
|
|
1674
|
+
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',{});}
|
|
1675
|
+
setInterval(heartbeat,4000);heartbeat();
|
|
1634
1676
|
initGraphEvents();syncP();refresh();loadMap();loadAgentCfg();loadScanRoots();
|
|
1635
1677
|
</script></body></html>`;
|
|
1636
1678
|
|
|
@@ -1673,7 +1715,13 @@ function openApp(url) {
|
|
|
1673
1715
|
}
|
|
1674
1716
|
async function cmdApp() {
|
|
1675
1717
|
SERVING = true;
|
|
1676
|
-
const
|
|
1718
|
+
const SERVER_STARTED = Date.now();
|
|
1719
|
+
// Stable token + port so the URL survives a restart — the open tab can
|
|
1720
|
+
// reconnect and auto-reload itself instead of you closing and reopening it.
|
|
1721
|
+
const cfg0 = loadConfig();
|
|
1722
|
+
let TOKEN = cfg0.appToken;
|
|
1723
|
+
if (!TOKEN) { TOKEN = randomBytes(16).toString("hex"); try { saveConfig({ ...loadConfig(), appToken: TOKEN }); } catch {} }
|
|
1724
|
+
const PORT = Number(process.env.SYMBIOT_PORT || cfg0.appPort) || 7391;
|
|
1677
1725
|
const json = (res, obj) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
|
|
1678
1726
|
const server = createServer(async (req, res) => {
|
|
1679
1727
|
const u = new URL(req.url, "http://127.0.0.1");
|
|
@@ -1714,17 +1762,40 @@ async function cmdApp() {
|
|
|
1714
1762
|
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
1763
|
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
1764
|
if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
|
|
1765
|
+
if (u.pathname === "/api/ping") return json(res, { version: VERSION, started: SERVER_STARTED, latest: LATEST_VERSION });
|
|
1766
|
+
if (u.pathname === "/api/update" && req.method === "POST") {
|
|
1767
|
+
// Install the newest symbiot, then relaunch this same app (same port +
|
|
1768
|
+
// token => same URL) and exit. The open page's heartbeat reconnects and
|
|
1769
|
+
// reloads to the new version. No terminal, no reopening the window.
|
|
1770
|
+
const inst = process.platform === "win32" ? "npm i -g symbiot@latest" : "npm install -g symbiot@latest";
|
|
1771
|
+
const e = track("symbiot update", inst, homedir(), (code) => {
|
|
1772
|
+
if (code !== 0) return;
|
|
1773
|
+
try { const ch = spawn(process.execPath, process.argv.slice(1), { detached: true, stdio: "ignore", env: process.env }); ch.unref(); } catch {}
|
|
1774
|
+
setTimeout(() => process.exit(0), 1200);
|
|
1775
|
+
});
|
|
1776
|
+
return json(res, { started: true, id: e ? e.id : "" });
|
|
1777
|
+
}
|
|
1717
1778
|
if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
|
|
1718
1779
|
} catch (e) { res.writeHead(500, { "content-type": "application/json" }); res.end(JSON.stringify({ error: String((e && e.message) || e) })); return; }
|
|
1719
1780
|
res.writeHead(404); res.end("not found");
|
|
1720
1781
|
});
|
|
1721
|
-
|
|
1782
|
+
let announced = false, tries = 0, opened = false;
|
|
1783
|
+
server.on("listening", () => {
|
|
1784
|
+
if (announced) return; announced = true;
|
|
1722
1785
|
const url = `http://127.0.0.1:${server.address().port}/?t=${TOKEN}`;
|
|
1723
|
-
const how = openApp(url);
|
|
1786
|
+
const how = opened ? "" : openApp(url); opened = true; // only pop a window the first time
|
|
1724
1787
|
console.log(`\n${c.g("●")} ${c.b("Symbiot")} is running at ${c.b(url)}`);
|
|
1725
1788
|
console.log(how ? c.d(` Opened in a ${how}.`) : c.d(" Open that URL in your browser."));
|
|
1726
1789
|
console.log(c.d(" Leave this running; press Ctrl+C to stop (or click Quit in the window)."));
|
|
1727
1790
|
});
|
|
1791
|
+
server.on("error", (e) => {
|
|
1792
|
+
// Stable port busy (an older instance still exiting during an update, or a
|
|
1793
|
+
// second app): retry briefly, then fall back to a random port.
|
|
1794
|
+
if (e && e.code === "EADDRINUSE" && tries < 8) { tries++; setTimeout(() => { try { server.listen(PORT, "127.0.0.1"); } catch {} }, 500); }
|
|
1795
|
+
else { try { server.listen(0, "127.0.0.1"); } catch {} }
|
|
1796
|
+
});
|
|
1797
|
+
server.listen(PORT, "127.0.0.1");
|
|
1798
|
+
checkLatest(); setInterval(checkLatest, 10 * 60 * 1000).unref(); // background update check
|
|
1728
1799
|
}
|
|
1729
1800
|
|
|
1730
1801
|
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.24.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": {
|