symbiot 0.53.2 → 0.53.4

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 CHANGED
@@ -2,6 +2,15 @@
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.53.4
6
+
7
+ - **Updates wait for npm instead of giving up.** npm lists a new version a little before its download is there, so an update straight after a release could fail and tell you to run `npm install` in a terminal. Now Symbiot tries again every 30 seconds (up to 5 times) and the bar says it's waiting on npm; there's nothing for you to do.
8
+
9
+ ## 0.53.3
10
+
11
+ - **The window's top bar matches the look.** Symbiot now tells the browser its colour, so the bar the app window opens with is the look's own (warm grey in Pearl, near-black in Ferrofluid, cool grey in Glass) instead of your desktop's accent colour.
12
+ - **It opens maximised,** filling the screen but leaving your taskbar, rather than needing full screen.
13
+
5
14
  ## 0.53.2
6
15
 
7
16
  - **Replies you're waiting on sit on the stream.** An email your agent is waiting on a reply to is a ring on your inbox's current, where it was sent, with a thread to now; it fills when the reply is in. Point at it for who, what was asked, and Stop waiting. The header says how many you're waiting on.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "symbiot",
3
- "version": "0.53.2",
3
+ "version": "0.53.4",
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
@@ -127,7 +127,7 @@ function openApp(url) {
127
127
  return "browser tab";
128
128
  }
129
129
  const chrome = chromeBinary();
130
- if (chrome) { spawn(chrome, [`--app=${url}`, "--new-window", "--no-first-run", "--no-default-browser-check"], { detached: true, stdio: "ignore" }).unref(); return "app window"; }
130
+ if (chrome) { spawn(chrome, [`--app=${url}`, "--new-window", "--start-maximized", "--no-first-run", "--no-default-browser-check"], { detached: true, stdio: "ignore" }).unref(); return "app window"; }
131
131
  // fall back to the OS default browser (a normal tab) — still fully functional
132
132
  if (process.platform === "win32") { spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref(); return "browser tab"; }
133
133
  spawn(process.platform === "darwin" ? "open" : "xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
@@ -186,6 +186,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
186
186
  keepBrowserOpen(BROWSER_KEEP);
187
187
  const json = (res, obj) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
188
188
  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);
189
+ let UPDATING = null; // an update in flight: { target, attempt, retrying? }
189
190
  const server = createServer(async (req, res) => {
190
191
  const u = new URL(req.url, "http://127.0.0.1");
191
192
  if (req.method === "GET" && u.pathname === "/") { res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end(EMBEDDED_UI); return; }
@@ -371,7 +372,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
371
372
  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)); }
372
373
  if (u.pathname === "/api/connect" && req.method === "POST") { return json(res, await connectProvider(await readBody(req))); }
373
374
  // a sandbox (symbiot app --fresh) never offers an update: it would replace your real install
374
- 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), ...(IN_TERMUX ? { termux: true } : {}), ...(SANDBOX ? { sandbox: true } : {}) }); }
375
+ 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 } : {}) }); }
375
376
  // What's new: after an update, since the version you last saw (until you click Got it);
376
377
  // ?latest=1, what the update on offer brings, from its package on npm
377
378
  if (u.pathname === "/api/whatsnew") {
@@ -392,12 +393,22 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
392
393
  if (SANDBOX) return json(res, { error: "This is a sandbox (symbiot app --fresh): update your real Symbiot instead." });
393
394
  const { target, cmd } = updateCmd(LATEST_VERSION, VERSION);
394
395
  const inst = process.env.SYMBIOT_UPDATE_CMD || cmd;
395
- const e = track("symbiot update", inst, homedir(), (code) => {
396
- if (code !== 0) return;
396
+ // npm lists a new version a little before its download is there (a 404 for the
397
+ // .tgz): then it tries again every 30s, up to 5 times, and says so, instead of
398
+ // giving up and sending you to a terminal.
399
+ const attempt = (n) => track("symbiot update", inst, homedir(), (code) => {
400
+ if (code !== 0) {
401
+ let tail = ""; try { tail = readFileSync(join(homedir(), ".symbiot", "agent.log"), "utf8").slice(-3000); } catch {}
402
+ if (n < 5 && /E404|ETARGET|notarget|No matching version|is not in this registry/i.test(tail)) { UPDATING = { target, retrying: true, attempt: n + 1, at: Date.now() }; setTimeout(() => attempt(n + 1), 30000).unref(); }
403
+ else UPDATING = null;
404
+ return;
405
+ }
397
406
  server.close(); if (server.closeAllConnections) server.closeAllConnections();
398
407
  try { const ch = spawn(process.execPath, process.argv.slice(1), { detached: true, stdio: "ignore", env: { ...process.env, SYMBIOT_RELAUNCH: "1" } }); ch.unref(); } catch {}
399
408
  setTimeout(() => process.exit(0), 1200);
400
409
  });
410
+ UPDATING = { target, attempt: 1, at: Date.now() };
411
+ const e = attempt(1);
401
412
  return json(res, { started: true, id: e ? e.id : "", target });
402
413
  }
403
414
  if (u.pathname === "/api/quit") { res.writeHead(200); res.end("bye"); setTimeout(() => process.exit(0), 150); return; }
package/ui.mjs CHANGED
@@ -5,7 +5,7 @@
5
5
  // Kept apart from the CLI/server so UI edits can't break the backend (and vice
6
6
  // versa); test/smoke.mjs boots the real page and exercises every handler.
7
7
  export const EMBEDDED_UI = `<!doctype html><html><head><meta charset="utf8">
8
- <meta name="viewport" content="width=device-width,initial-scale=1"><title>Symbiot</title>
8
+ <meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" id="themecolor" content="#08090B"><meta name="color-scheme" content="dark light"><title>Symbiot</title>
9
9
  <style>
10
10
  @font-face{font-family:'Geist';src:url('/fonts/Geist-Variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap}
11
11
  :root{--ink:#0E1A1F;--ink2:#15262C;--ink3:#1D333A;--line:#24404A;--bone:#F4F1EA;--text:#B7C9C4;--faint:#7E9690;--green:#3DDC97;--amber:#F2A541;--green-dim:#16322D;--sans:'Geist',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
@@ -2145,6 +2145,9 @@ function heartbeat(fresh){
2145
2145
  var tried=null;try{tried=localStorage.getItem('symbiot_update_tried');}catch(e){}
2146
2146
  if(tried){
2147
2147
  if(p.version!==tried){try{localStorage.removeItem('symbiot_update_tried');}catch(e){} } // advanced → success, clear
2148
+ else if(p.retrying){ // npm hasn't finished publishing it: the app is trying again by itself
2149
+ b.className='updatebar show';b.innerHTML="Updating to "+esc(p.retrying.target)+": npm is still publishing it, so Symbiot tries again every 30 seconds (try "+p.retrying.attempt+" of 5). Nothing to do.";return;
2150
+ }
2148
2151
  else if(p.latest&&p.newer){ // still on the same version → the install didn't advance
2149
2152
  b.className='updatebar reconnect show';
2150
2153
  b.innerHTML="Auto-update to "+esc(p.latest)+" didn't take (still on "+esc(p.version)+"). Run <code>npm install -g symbiot@latest</code> in a terminal, then restart.";
@@ -2285,6 +2288,8 @@ function lqTheme(){var look=lqLookGet();var th={look:look,light:look!=='ferro',c
2285
2288
  var h=new Date().getHours();th.night=h>=22||h<6;
2286
2289
  try{if(document.createElement&&typeof getComputedStyle==='function'&&document.body&&document.body.appendChild){var a=document.createElement('span'),b=document.createElement('span');a.style.color='AccentColor';b.style.color='CanvasText';document.body.appendChild(a);document.body.appendChild(b);
2287
2290
  if(a.style.color){var ca=getComputedStyle(a).color,cb=getComputedStyle(b).color;if(ca&&ca!==cb){var m=ca.match(/[0-9.]+/g);if(m&&m.length>=3)th.accent=[m[0]/255,m[1]/255,m[2]/255];}}a.remove();b.remove();}}catch(e){}
2291
+ // the window's own bar takes the look's colour, so it reads as part of the app, not a frame round it
2292
+ var tc=document.getElementById('themecolor');if(tc&&tc.setAttribute)tc.setAttribute('content',th.forced?'Canvas':look==='pearl'?'#ECEBE8':look==='glass'?'#E9ECEE':'#08090B');
2288
2293
  var bd=document.body;if(bd&&bd.classList){bd.classList.toggle('lq-light',th.light);['ferro','glass','pearl'].forEach(function(l){bd.classList.toggle('lq-look-'+l,l===look);});bd.classList.toggle('lq-contrast',th.contrast);bd.classList.toggle('lq-solid',th.solid||th.contrast);bd.classList.toggle('lq-forced',th.forced);}
2289
2294
  LQ.theme=th;return th;}
2290
2295
  function lqSize(){var el=$('liquid');var w=(el&&el.clientWidth)||window.innerWidth||1280,h=(el&&el.clientHeight)||window.innerHeight||800;return {w:w,h:h,s:Math.max(0.55,Math.min(1.15,Math.min(w,h)/860))};}