symbiot 0.55.3 → 0.56.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/CHANGELOG.md +6 -0
- package/README.md +8 -0
- package/away.mjs +12 -2
- package/desktop.mjs +48 -1
- package/icon.svg +33 -0
- package/index.mjs +46 -4
- package/package.json +7 -4
- package/postinstall.mjs +12 -0
- package/server.mjs +13 -4
- package/ui.mjs +2 -1
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.56.0
|
|
6
|
+
|
|
7
|
+
- **Symbiot in your app menu, no terminal.** On Linux, `npm install -g symbiot` puts Symbiot in your app menu with its own icon, the orb. Click it and Symbiot starts in the background and opens its window; click it again and the window comes up. You never need a terminal to run it. Its window shows the orb in your taskbar too, and the menu entry keeps itself pointing at the installed Symbiot (a new Node, a moved install).
|
|
8
|
+
- **`symbiot uninstall`** removes Symbiot and everything it added: the menu entry and icon, start at login, the Away shortcut, and its data (`--keep-data` keeps your settings, tasks and memory). Claude Code and its sign-in stay.
|
|
9
|
+
- `symbiot open` does what the icon does, from a terminal.
|
|
10
|
+
|
|
5
11
|
## 0.55.3
|
|
6
12
|
|
|
7
13
|
- **Your Claude subscription, no API key.** With Claude Code signed in to your Claude account (Pro or Max), Symbiot now answers through it: chats, write-ups, posts, screenshots. Nothing to connect, no separate bill. It comes first when you haven't picked an AI, ahead of an old saved key. `symbiot login` and Settings → Your AI offer it first; if Claude Code isn't signed in yet, they tell you to run `claude` once and sign in. Before, a fresh install sent you to `symbiot login`, which only asked for an API key.
|
package/README.md
CHANGED
|
@@ -43,6 +43,14 @@ npm install -g symbiot # or run without installing: npx symbiot week
|
|
|
43
43
|
With Claude Code signed in to your Claude account, that's all: Symbiot uses your
|
|
44
44
|
subscription, no API key. See [Connect it (once)](#connect-it-once) for the other options.
|
|
45
45
|
|
|
46
|
+
On Linux, installing puts **Symbiot in your app menu**, with its icon. Open it from
|
|
47
|
+
there: it starts in the background (no terminal) and opens its window, or brings the
|
|
48
|
+
window up if it's already running. You don't need a terminal again; `symbiot open`
|
|
49
|
+
does the same from one.
|
|
50
|
+
|
|
51
|
+
To remove it and everything it added (menu entry, icon, start at login, shortcut,
|
|
52
|
+
its data): `symbiot uninstall` (`--keep-data` keeps your settings, tasks and memory).
|
|
53
|
+
|
|
46
54
|
## Prefer a window? `symbiot app`
|
|
47
55
|
|
|
48
56
|
```bash
|
package/away.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
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
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
17
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { CONFIG_DIR, chromeBinary } from "./core.mjs";
|
|
@@ -110,4 +110,14 @@ function installShortcut({ cmd, home = homedir(), desktop = process.env.XDG_CURR
|
|
|
110
110
|
return { ok: true, file: f, replaced: /key:\s*"grave"/.test(had) };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// Uninstalling: our Super+` line comes out of COSMIC's custom shortcuts; the file
|
|
114
|
+
// goes if nothing else was in it. True when there was one.
|
|
115
|
+
function removeShortcut(home = homedir()) {
|
|
116
|
+
const f = COSMIC_CUSTOM(home); let had = ""; try { had = readFileSync(f, "utf8"); } catch { return false; }
|
|
117
|
+
const lines = had.split("\n"), kept = lines.filter((l) => !/Spawn\("[^"]*symbiot[^"]* away"\)/.test(l));
|
|
118
|
+
if (kept.length === lines.length) return false;
|
|
119
|
+
const rest = kept.join("\n");
|
|
120
|
+
try { if (!rest.replace(/[{}\s]/g, "")) unlinkSync(f); else writeFileSync(f, rest); } catch {}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
export { removeShortcut, placeable, grantProfile, parseMonitors, screens, awayQueries, openAway, closeAway, awayOpen, toggleAway, withShortcut, installShortcut, COSMIC_CUSTOM };
|
package/desktop.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// start-at-login launches that server in the background (no window) when you
|
|
5
5
|
// log in, so the write-up still happens on days you never open Symbiot.
|
|
6
6
|
// `symbiot app` then finds that running copy and opens its window.
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { spawn } from "node:child_process";
|
|
8
9
|
import { homedir } from "node:os";
|
|
9
10
|
import { join, dirname } from "node:path";
|
|
@@ -144,6 +145,52 @@ function autostartContent(node, script, platform = OS, path = process.env.PATH |
|
|
|
144
145
|
return ["[Desktop Entry]", "Type=Application", "Name=Symbiot", "Comment=Symbiot in the background: the weekly write-up and its notification",
|
|
145
146
|
`Exec=env SYMBIOT_NO_OPEN=1 ${q("PATH=" + path)} ${q(node)} ${q(script)} app`, "Terminal=false", "NoDisplay=true", "X-GNOME-Autostart-enabled=true", ""].join("\n");
|
|
146
147
|
}
|
|
148
|
+
// ---- the app launcher ---------------------------------------------------------
|
|
149
|
+
// Symbiot in your app menu, with its icon: click it and Symbiot starts in the
|
|
150
|
+
// background (no terminal) and opens its window, or brings the window up if it's
|
|
151
|
+
// running (`symbiot open`). Linux (XDG): an entry in ~/.local/share/applications and
|
|
152
|
+
// the icon in your icon theme. `npm install -g` writes it (postinstall.mjs), and each
|
|
153
|
+
// start of an installed Symbiot writes it again if it's missing or out of date (a
|
|
154
|
+
// new Node, a moved install), so it never points at something gone.
|
|
155
|
+
const launcherFile = (home = homedir()) => join(home, ".local", "share", "applications", "symbiot.desktop");
|
|
156
|
+
const launcherIcon = (home = homedir()) => join(home, ".local", "share", "icons", "hicolor", "scalable", "apps", "symbiot.svg");
|
|
157
|
+
const ICON_SRC = fileURLToPath(new URL("./icon.svg", import.meta.url));
|
|
158
|
+
function launcherContent(node, script, path = process.env.PATH || "") {
|
|
159
|
+
const q = (s) => '"' + String(s).replace(/(["`$\\])/g, "\\$1") + '"';
|
|
160
|
+
return ["[Desktop Entry]", "Type=Application", "Name=Symbiot", "GenericName=Assistant",
|
|
161
|
+
"Comment=Your work, your agents and what needs you, in one place",
|
|
162
|
+
`Exec=env ${q("PATH=" + path)} ${q(node)} ${q(script)} open`, "Icon=symbiot", "Terminal=false",
|
|
163
|
+
"Categories=Office;Utility;Development;", "Keywords=assistant;agents;tasks;week;standup;", "StartupNotify=true",
|
|
164
|
+
// the app's window is Chrome's --app window on Symbiot's address: this groups it under this icon
|
|
165
|
+
"StartupWMClass=chrome-127.0.0.1__-Default", ""].join("\n");
|
|
166
|
+
}
|
|
167
|
+
// The PATH the launcher starts it with: yours, without the throwaway folders npm adds
|
|
168
|
+
// while it installs (…/node_modules/.bin), each once, and Node's own folder in it.
|
|
169
|
+
function launcherPath(path, node) {
|
|
170
|
+
const seen = new Set(), out = [];
|
|
171
|
+
for (const d of [...String(path || "").split(":"), dirname(node)]) if (d && !/node_modules[\\/]\.bin|node-gyp-bin/.test(d) && !seen.has(d)) { seen.add(d); out.push(d); }
|
|
172
|
+
return out.join(":");
|
|
173
|
+
}
|
|
174
|
+
// Writes the entry and icon when they're missing or differ. { written, file } or { skipped }.
|
|
175
|
+
function installLauncher({ node = process.execPath, script, home = homedir(), platform = OS, path = process.env.PATH || "" } = {}) {
|
|
176
|
+
if (platform !== "linux" || !script) return { skipped: true };
|
|
177
|
+
const file = launcherFile(home), icon = launcherIcon(home), want = launcherContent(node, script, launcherPath(path, node));
|
|
178
|
+
let wrote = false;
|
|
179
|
+
try {
|
|
180
|
+
let had = ""; try { had = readFileSync(file, "utf8"); } catch {}
|
|
181
|
+
if (had !== want) { mkdirSync(join(file, ".."), { recursive: true }); writeFileSync(file, want, { mode: 0o644 }); wrote = true; }
|
|
182
|
+
let hadIcon = ""; try { hadIcon = readFileSync(icon, "utf8"); } catch {}
|
|
183
|
+
const svg = readFileSync(ICON_SRC, "utf8");
|
|
184
|
+
if (hadIcon !== svg) { mkdirSync(join(icon, ".."), { recursive: true }); writeFileSync(icon, svg, { mode: 0o644 }); wrote = true; }
|
|
185
|
+
} catch (e) { return { error: (e && e.message) || String(e) }; }
|
|
186
|
+
if (wrote) { try { spawn("update-desktop-database", [join(file, "..")], { stdio: "ignore", detached: true }).on("error", () => {}).unref(); } catch {} }
|
|
187
|
+
return { written: wrote, file };
|
|
188
|
+
}
|
|
189
|
+
function removeLauncher(home = homedir()) {
|
|
190
|
+
const gone = [];
|
|
191
|
+
for (const f of [launcherFile(home), launcherIcon(home)]) { try { if (existsSync(f)) { unlinkSync(f); gone.push(f); } } catch {} }
|
|
192
|
+
return gone;
|
|
193
|
+
}
|
|
147
194
|
function autostartState() { const file = autostartFile(); return { on: existsSync(file), file, ...(OS === "android-app" ? { phone: "app" } : OS === "android" ? { phone: "termux" } : {}) }; }
|
|
148
195
|
// `script` is index.mjs's real path. From npx that's a cache folder that goes
|
|
149
196
|
// away, so it has to be installed for this to keep working.
|
|
@@ -159,4 +206,4 @@ function setAutostart(on, script) {
|
|
|
159
206
|
} catch (e) { return { ...autostartState(), error: String((e && e.message) || e) }; }
|
|
160
207
|
}
|
|
161
208
|
|
|
162
|
-
export { weeklyCfg, lastSlot, weeklyDue, setWeekly, weeklyState, latestWeek, runWeekly, startWeekly, notifyCmd, desktopNotify, autostartFile, autostartContent, autostartState, setAutostart };
|
|
209
|
+
export { launcherPath, launcherFile, launcherIcon, launcherContent, installLauncher, removeLauncher, weeklyCfg, lastSlot, weeklyDue, setWeekly, weeklyState, latestWeek, runWeekly, startWeekly, notifyCmd, desktopNotify, autostartFile, autostartContent, autostartState, setAutostart };
|
package/icon.svg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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>
|
package/index.mjs
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
// tasks.mjs (tasks, review and Approve), agents.mjs (handing work to an agent),
|
|
24
24
|
// drift.mjs, server.mjs (`symbiot app`'s server) and ui.mjs (its page).
|
|
25
25
|
|
|
26
|
-
import { spawn } from "node:child_process";
|
|
26
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
27
27
|
import { homedir } from "node:os";
|
|
28
28
|
import { join, basename } from "node:path";
|
|
29
|
-
import { realpathSync, readFileSync } from "node:fs";
|
|
29
|
+
import { realpathSync, readFileSync, mkdirSync, openSync, rmSync, existsSync } from "node:fs";
|
|
30
30
|
import { createInterface } from "node:readline";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import { EMBEDDED_UI } from "./ui.mjs";
|
|
33
|
-
import { VERSION, CONFIG_PATH, loadConfig, saveConfig, repoState, semverGt } from "./core.mjs";
|
|
33
|
+
import { VERSION, CONFIG_PATH, CONFIG_DIR, loadConfig, saveConfig, repoState, semverGt } from "./core.mjs";
|
|
34
34
|
import { HANDOFF_PROMPT, CLAUDE_CMD, ORCA_CLAUDE_CMD, handoffCmd, setHandoffCmd, fillHandoff, runHandoff, orcaHandoffCmd, migrateOrcaCmd, migrateClaudeCmd, parseQuestions, agentQuestions } from "./agents.mjs";
|
|
35
35
|
import { AI_UI, PROVIDERS, resolveProvider, validate, detectHardware, recommendModels, hasOllama, ollamaInstall, ensureOllama, useOllamaModel, claudeState, connectProvider } from "./ai.mjs";
|
|
36
36
|
import { setScanOptions, scanBase, authorship, readmeInfo, repoShape, houseRules, reportFooter, findAllRepos, buildMap } from "./scan.mjs";
|
|
@@ -42,7 +42,7 @@ import { mapPage, wholePage, pressRegion, typeRegion, scrollPage, signIn, isTrus
|
|
|
42
42
|
import { watchState, addWatch, removeWatch, seenWatch, newsSince, markNews, checkWatch, setBrief, draftReply, watchBoard, boardLine, boardChat, boardTalk, clearBoardChat } from "./watch.mjs";
|
|
43
43
|
import { PORT as PHONE_PORT, phoneState, pairComputer, pollComputer, forgetComputer } from "./phone.mjs";
|
|
44
44
|
import { KIND_LABEL as POST_KIND, PATHS as POST_PATHS, draftPosts, postsState, postLog, approvePost, editPost, skipPost, voiceFromLinkedIn, openUrl, addMedia as addPostMedia, removeMedia as removePostMedia, mediaDir as postMediaDir, pictureOfPage, clipOfPage } from "./post.mjs";
|
|
45
|
-
import { startApp, updateCmd, isAppRunningWeekly } from "./server.mjs";
|
|
45
|
+
import { startApp, updateCmd, isAppRunningWeekly, askRunningApp, openApp } from "./server.mjs";
|
|
46
46
|
import { listReports, readReport } from "./reports.mjs";
|
|
47
47
|
import { runSandbox } from "./sandbox.mjs";
|
|
48
48
|
import { knowledgeState, addKnowledgeFolder, removeKnowledgeFolder, indexKnowledge, searchKnowledge, waitingOn, ownerOf, myName, setMyName, itemLine, caseLine } from "./knowledge.mjs";
|
|
@@ -350,6 +350,43 @@ async function cmdAway() {
|
|
|
350
350
|
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; }
|
|
351
351
|
if (r.error) { console.log(c.y(r.error)); process.exitCode = 1; }
|
|
352
352
|
}
|
|
353
|
+
// symbiot open: what the app-menu icon runs. Running: its window comes up. Not
|
|
354
|
+
// running: it starts in the background (no terminal; what it prints goes to
|
|
355
|
+
// ~/.config/symbiot/app.log) and opens its own window.
|
|
356
|
+
async function cmdOpen() {
|
|
357
|
+
const cfg = loadConfig(), port = Number(process.env.SYMBIOT_PORT || cfg.appPort) || 7391;
|
|
358
|
+
const p = cfg.appToken ? await askRunningApp("ping", { port, token: cfg.appToken, ms: 1500 }) : null;
|
|
359
|
+
if (p && p.version) { openApp(`http://127.0.0.1:${port}/?t=${cfg.appToken}`); return; }
|
|
360
|
+
let fd = "ignore"; try { mkdirSync(CONFIG_DIR, { recursive: true }); fd = openSync(join(CONFIG_DIR, "app.log"), "a"); } catch {}
|
|
361
|
+
const env = { ...process.env }; delete env.SYMBIOT_NO_OPEN;
|
|
362
|
+
spawn(process.execPath, [realpathSync(fileURLToPath(import.meta.url)), "app"], { detached: true, stdio: ["ignore", fd, fd], env }).unref();
|
|
363
|
+
}
|
|
364
|
+
// symbiot uninstall: everything Symbiot put on this computer, then the program.
|
|
365
|
+
// --keep-data keeps ~/.config/symbiot (settings, tasks, memory); --yes doesn't ask.
|
|
366
|
+
async function cmdUninstall() {
|
|
367
|
+
const keep = has("keep-data");
|
|
368
|
+
console.log(c.b("Remove Symbiot from this computer") + "\n");
|
|
369
|
+
console.log(" · its app-menu entry and icon, start at login, the Super+` shortcut");
|
|
370
|
+
console.log(keep ? " · " + c.d("keeps your data in " + CONFIG_DIR) : " · everything it knows: settings, tasks, memory, drafts, screenshots (" + CONFIG_DIR + ")");
|
|
371
|
+
if (!has("keep-program")) console.log(" · the program itself (npm uninstall -g symbiot)");
|
|
372
|
+
console.log(c.d(" Claude Code and its sign-in stay. Folders it made inside your projects (.symbiot/) stay too."));
|
|
373
|
+
if (!has("yes")) {
|
|
374
|
+
if (!process.stdin.isTTY) { console.log("\n" + c.y("Run it again with --yes to go ahead.")); process.exitCode = 1; return; }
|
|
375
|
+
if (((await ask("\nType yes to remove it: ")) || "").trim().toLowerCase() !== "yes") { console.log("Nothing removed."); return; }
|
|
376
|
+
}
|
|
377
|
+
const { removeLauncher, setAutostart } = await import("./desktop.mjs");
|
|
378
|
+
const { removeShortcut } = await import("./away.mjs");
|
|
379
|
+
await askRunningApp("quit", { ms: 2000 });
|
|
380
|
+
try { setAutostart(false); } catch {}
|
|
381
|
+
removeLauncher(); removeShortcut();
|
|
382
|
+
try { rmSync(join(homedir(), ".symbiot"), { recursive: true, force: true }); } catch {}
|
|
383
|
+
if (!keep) { try { rmSync(CONFIG_DIR, { recursive: true, force: true }); } catch {} }
|
|
384
|
+
console.log(c.g("✓ ") + "Removed what Symbiot added" + (keep ? ", your data kept." : ", and its data."));
|
|
385
|
+
if (has("keep-program")) return;
|
|
386
|
+
const r = spawnSync("npm", ["uninstall", "-g", "symbiot"], { stdio: "inherit", shell: process.platform === "win32" });
|
|
387
|
+
console.log(r.status === 0 ? c.g("✓ ") + "Symbiot is uninstalled." : c.y("Couldn't remove the program: run npm uninstall -g symbiot"));
|
|
388
|
+
if (existsSync(CONFIG_DIR) && !keep) console.log(c.d("(Something wrote to " + CONFIG_DIR + " meanwhile; delete it if you like.)"));
|
|
389
|
+
}
|
|
353
390
|
async function cmdScreens() {
|
|
354
391
|
const [sub = "list", a1, a2, a3] = argv.slice(1).filter((x, i, all) => !x.startsWith("--") && all[i - 1] !== "--name");
|
|
355
392
|
const out = (x) => { console.log(JSON.stringify(x, null, 2)); if (x && x.error) process.exitCode = 1; };
|
|
@@ -775,6 +812,9 @@ ${c.b("Usage")}
|
|
|
775
812
|
symbiot standup yesterday + today, for standup
|
|
776
813
|
symbiot todo what's still on your plate
|
|
777
814
|
symbiot app open the visual app in your browser
|
|
815
|
+
symbiot open start Symbiot in the background and open its window
|
|
816
|
+
(what its app-menu icon does)
|
|
817
|
+
symbiot uninstall [--keep-data] remove Symbiot and everything it added
|
|
778
818
|
symbiot away [--shortcut] Symbiot full screen on every screen while you're away;
|
|
779
819
|
again closes it. --shortcut: Super+\` does it
|
|
780
820
|
symbiot app --fresh [--keep] try it as someone new: a brand-new Symbiot in
|
|
@@ -833,6 +873,8 @@ async function main() {
|
|
|
833
873
|
if (cmd === "whoami" || cmd === "status") return cmdWhoami();
|
|
834
874
|
if (cmd === "app" || cmd === "ui") return cmdApp();
|
|
835
875
|
if (cmd === "away") return cmdAway();
|
|
876
|
+
if (cmd === "open") return cmdOpen();
|
|
877
|
+
if (cmd === "uninstall") return cmdUninstall();
|
|
836
878
|
if (cmd === "models" || cmd === "hardware") return cmdModels();
|
|
837
879
|
if (cmd === "setup-local" || cmd === "setup-ollama") return cmdSetupLocal();
|
|
838
880
|
if (cmd === "drift") return cmdDrift();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.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 \u2014 Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
"mapknn.mjs",
|
|
45
45
|
"guard.mjs",
|
|
46
46
|
"away.mjs",
|
|
47
|
-
"marketing.mjs"
|
|
47
|
+
"marketing.mjs",
|
|
48
|
+
"postinstall.mjs",
|
|
49
|
+
"icon.svg"
|
|
48
50
|
],
|
|
49
51
|
"engines": {
|
|
50
52
|
"node": ">=18"
|
|
@@ -77,8 +79,9 @@
|
|
|
77
79
|
"@anthropic-ai/sdk": "^0.128.0"
|
|
78
80
|
},
|
|
79
81
|
"scripts": {
|
|
80
|
-
"test": "node test/load.mjs && node test/fixtures.mjs && node test/links.mjs && node test/post.mjs && node test/peek.mjs && node test/mind.mjs && node test/knowledge.mjs && node test/checks.mjs &&node test/lanes.mjs && node test/handback.mjs && node test/autoallow.mjs && node test/guard.mjs && node test/reports.mjs && node test/sandbox.mjs && node test/adapt.mjs && node test/work.mjs && node test/mapknn.mjs && node test/away.mjs && node test/subscription.mjs && node test/marketing.mjs && node test/paths.mjs && node test/smoke.mjs && node test/app.mjs && node test/install.mjs",
|
|
81
|
-
"lint": "eslint ."
|
|
82
|
+
"test": "node test/load.mjs && node test/fixtures.mjs && node test/links.mjs && node test/post.mjs && node test/peek.mjs && node test/mind.mjs && node test/knowledge.mjs && node test/checks.mjs &&node test/lanes.mjs && node test/handback.mjs && node test/autoallow.mjs && node test/guard.mjs && node test/reports.mjs && node test/sandbox.mjs && node test/adapt.mjs && node test/work.mjs && node test/mapknn.mjs && node test/away.mjs && node test/subscription.mjs && node test/launcher.mjs && node test/marketing.mjs && node test/paths.mjs && node test/smoke.mjs && node test/app.mjs && node test/install.mjs",
|
|
83
|
+
"lint": "eslint .",
|
|
84
|
+
"postinstall": "node postinstall.mjs"
|
|
82
85
|
},
|
|
83
86
|
"devDependencies": {
|
|
84
87
|
"eslint": "^9.39.5",
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// npm install -g symbiot runs this: Symbiot in your app menu, with its icon, so you
|
|
2
|
+
// never need a terminal to start it (desktop.mjs installLauncher). Only for a global
|
|
3
|
+
// install, and it never fails the install.
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
try {
|
|
7
|
+
if (process.env.npm_config_global === "true" && !process.env.SYMBIOT_NO_LAUNCHER) {
|
|
8
|
+
const { installLauncher } = await import("./desktop.mjs");
|
|
9
|
+
const r = installLauncher({ script: realpathSync(fileURLToPath(new URL("./index.mjs", import.meta.url))) });
|
|
10
|
+
if (r.file) console.log("Symbiot is in your app menu: open it from there, no terminal needed.");
|
|
11
|
+
}
|
|
12
|
+
} catch {}
|
package/server.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { addTask, toggleTask, removeTask, restoreTask, syncTasks, taskType, push
|
|
|
22
22
|
import { repoReview, repoSuggest, folderSuggest, taskChat, clearTaskChat, mailState, setMail, sentMail, produce, releaseNotes } from "./writeups.mjs";
|
|
23
23
|
import { loadScreens, screenImage, captureScreen, splitScreen, listMonitors, allowScreenshots, importScreen, setRegions, renameScreen, removeScreen, blueprint, clickRegion } from "./screens.mjs";
|
|
24
24
|
import { mapPage, wholePage, pressRegion, typeRegion, scrollPage, signIn, keepBrowserOpen, isTrusted, trustedSites, trustSite, untrustSite } from "./headless.mjs";
|
|
25
|
-
import { weeklyState, setWeekly, runWeekly, startWeekly, autostartState, setAutostart } from "./desktop.mjs";
|
|
25
|
+
import { weeklyState, setWeekly, runWeekly, startWeekly, autostartState, setAutostart, installLauncher } from "./desktop.mjs";
|
|
26
26
|
import { markNews, newsSince, watchState, addWatch, setEvery, removeWatch, clearNews, seenWatch, checkWatch, startWatches, setBrief, draftReply, openChat, watchBoard, boardChat, clearBoardChat } from "./watch.mjs";
|
|
27
27
|
import { linksState, linkSite, checkLink, unlinkSite } from "./links.mjs";
|
|
28
28
|
import { testWeeks, testInstalls, postsState, draftPosts, approvePost, editPost, skipPost, voiceFromLinkedIn, addMedia, removeMedia, mediaFile, mediaDir, pictureOfPage, clipOfPage, openUrl } from "./post.mjs";
|
|
@@ -204,7 +204,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
204
204
|
if (p && p.version) {
|
|
205
205
|
const url = `http://127.0.0.1:${PORT}/?t=${TOKEN}`; const how = process.env.SYMBIOT_NO_OPEN === "1" ? "" : openApp(url);
|
|
206
206
|
console.log(`\n${c.g("●")} ${c.b("Symbiot")} is already running (v${p.version}) at ${c.b(url)}`);
|
|
207
|
-
console.log(how ? c.d(` Opened the existing window (a ${how}).`) : c.d(" Open that URL in your browser."));
|
|
207
|
+
console.log(how ? c.d(` Opened the existing window (${/^[aeiou]/.test(how) ? "an" : "a"} ${how}).`) : c.d(" Open that URL in your browser."));
|
|
208
208
|
console.log(c.d(" (Not starting a second copy. Set SYMBIOT_FORCE_NEW=1 to force one.)"));
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
@@ -214,6 +214,9 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
214
214
|
keepBrowserOpen(BROWSER_KEEP);
|
|
215
215
|
const json = (res, obj) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); };
|
|
216
216
|
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);
|
|
217
|
+
// an installed Symbiot keeps its app-menu entry pointing at itself (a new Node, a moved
|
|
218
|
+
// install); not a copy run from a checkout or a test
|
|
219
|
+
if (/[\\/]node_modules[\\/]symbiot[\\/]/.test(bin || "") && !SANDBOX) { try { installLauncher({ script: bin }); } catch {} }
|
|
217
220
|
let UPDATING = null; // an update in flight: { target, attempt, retrying? }
|
|
218
221
|
let NEWEST_WIN = ""; // the Symbiot window opened last: older ones close themselves (one window, not a pile)
|
|
219
222
|
process.on("exit", () => closeAway()); // Away's windows go with the app (a quit, an update's restart)
|
|
@@ -221,6 +224,12 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
221
224
|
const u = new URL(req.url, "http://127.0.0.1");
|
|
222
225
|
if (req.method === "GET" && u.pathname === "/") { res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end(EMBEDDED_UI); return; }
|
|
223
226
|
// the app's typeface ships in the package (fonts/), so it's there offline
|
|
227
|
+
// the window's own icon (its taskbar entry), the same orb as the app menu's
|
|
228
|
+
if (req.method === "GET" && (u.pathname === "/favicon.svg" || u.pathname === "/favicon.ico")) {
|
|
229
|
+
try { const b = readFileSync(fileURLToPath(new URL("./icon.svg", import.meta.url))); res.writeHead(200, { "content-type": "image/svg+xml", "cache-control": "public, max-age=86400" }); res.end(b); }
|
|
230
|
+
catch { res.writeHead(404); res.end(); }
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
224
233
|
if (req.method === "GET" && u.pathname === "/fonts/Geist-Variable.woff2") {
|
|
225
234
|
try { const b = readFileSync(fileURLToPath(new URL("./fonts/Geist-Variable.woff2", import.meta.url))); res.writeHead(200, { "content-type": "font/woff2", "cache-control": "public, max-age=31536000, immutable" }); res.end(b); }
|
|
226
235
|
catch { res.writeHead(404); res.end(); }
|
|
@@ -477,7 +486,7 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
477
486
|
const url = `http://127.0.0.1:${server.address().port}/?t=${TOKEN}`;
|
|
478
487
|
const how = opened || RELAUNCH || process.env.SYMBIOT_NO_OPEN === "1" ? "" : openApp(url); opened = true; // only pop a window the first time (never in tests, never after an update)
|
|
479
488
|
console.log(`\n${c.g("●")} ${c.b("Symbiot")} is running at ${c.b(url)}`);
|
|
480
|
-
console.log(RELAUNCH ? c.d(" Restarted after an update; the open window reloads itself.") : how ? c.d(` Opened in a ${how}.`) : c.d(" Open that URL in your browser."));
|
|
489
|
+
console.log(RELAUNCH ? c.d(" Restarted after an update; the open window reloads itself.") : how ? c.d(` Opened in ${/^[aeiou]/.test(how) ? "an" : "a"} ${how}.`) : c.d(" Open that URL in your browser."));
|
|
481
490
|
console.log(c.d(" Leave this running; press Ctrl+C to stop (or click the X in the window's top corner)."));
|
|
482
491
|
});
|
|
483
492
|
server.on("error", (e) => {
|
|
@@ -508,4 +517,4 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
508
517
|
setInterval(() => { try { awaitTick(); } catch {} }, 60000).unref(); // a reply an agent's email waits on: found, and handed on
|
|
509
518
|
}
|
|
510
519
|
|
|
511
|
-
export { updateCmd, changesSince, whatsNew, tarFile, registryChangelog, BROWSER_KEEP, startApp, askRunningApp, isAppRunningWeekly };
|
|
520
|
+
export { updateCmd, changesSince, whatsNew, tarFile, registryChangelog, BROWSER_KEEP, startApp, askRunningApp, isAppRunningWeekly, openApp };
|
package/ui.mjs
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
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"><meta name="theme-color" id="themecolor" content="#08090B"
|
|
8
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" id="themecolor" content="#08090B">
|
|
9
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="color-scheme" content="dark light"><title>Symbiot</title>
|
|
9
10
|
<style>
|
|
10
11
|
@font-face{font-family:'Geist';src:url('/fonts/Geist-Variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap}
|
|
11
12
|
: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}
|