symbiot 0.57.0 → 0.57.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 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.57.1
6
+
7
+ - **Windows is tested on every change.** A Windows job runs on GitHub with each pull request: Symbiot has to install, start, open into setup, find your Claude subscription and uninstall cleanly there, or the change doesn't go in. The whole suite runs there too and reports what isn't Windows-ready yet.
8
+ - **Your Claude subscription on Windows.** Claude Code installs as `claude.cmd` on Windows, which Symbiot didn't find, so it fell back to asking for a key. And Symbiot's instructions now reach Claude Code in a file instead of on the command line, which Windows caps at about 8,000 characters.
9
+ - **`symbiot --version` and `symbiot --help` work.** Both ran the weekly write-up instead.
10
+
5
11
  ## 0.57.0
6
12
 
7
13
  - **Setup, before anything else.** A new Symbiot opens straight into setup: what it is (it watches, it works, it asks), your AI (your Claude subscription, found by itself), your work (your projects, found while you read), your coding agent (one click), every app you use (each connected, or marked "I don't use it"; you can't go on until they're all decided), and an optional folder of your documents. Then a short "finding your way" and Home. Where you are is kept, so closing it resumes there; Settings → Setup → Run setup again walks it again.
package/ai.mjs CHANGED
@@ -4,7 +4,7 @@ import Anthropic from "@anthropic-ai/sdk";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
  import { homedir, totalmem, cpus as oscpus } from "node:os";
6
6
  import { join } from "node:path";
7
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
7
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
8
8
  import { loadConfig, saveConfig, sh, hasCmd, CONFIG_DIR } from "./core.mjs";
9
9
 
10
10
  const MAX_TOKENS = 1600;
@@ -26,13 +26,33 @@ const PROVIDERS = {
26
26
  // agents. Whether it's there and signed in: `claude auth status` (a quarter of a
27
27
  // second), kept 5 minutes. SYMBIOT_CLAUDE_CMD replaces the command (the tests' stand-in).
28
28
  const CLAUDE_BIN = () => process.env.SYMBIOT_CLAUDE_CMD || "claude";
29
+ // How to run it. Windows: npm installs `claude` as claude.cmd, which Node can only
30
+ // start through cmd.exe (a .exe, from Claude's own installer, it starts directly), so
31
+ // it's found with `where` and its arguments are quoted for cmd.exe.
32
+ const winQuote = (a) => '"' + String(a).replace(/"/g, '""') + '"';
33
+ function claudeCommand(platform = process.platform) {
34
+ const bin = CLAUDE_BIN();
35
+ if (platform !== "win32") return { file: bin, shell: false };
36
+ if (/\.exe$/i.test(bin)) return { file: bin, shell: false };
37
+ if (/\.(cmd|bat)$/i.test(bin)) return { file: bin, shell: true };
38
+ try {
39
+ const hits = String(spawnSync("where", [bin], { encoding: "utf8", timeout: 5000 }).stdout || "").split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
40
+ const exe = hits.find((h) => /\.exe$/i.test(h)); if (exe) return { file: exe, shell: false };
41
+ const cmd = hits.find((h) => /\.(cmd|bat)$/i.test(h)); if (cmd) return { file: cmd, shell: true };
42
+ } catch {}
43
+ return { file: bin, shell: true };
44
+ }
45
+ function claudeSpawn(args, opts = {}, sync = false) {
46
+ const c = claudeCommand(), run = sync ? spawnSync : spawn;
47
+ return c.shell ? run([c.file, ...args].map(winQuote).join(" "), [], { ...opts, shell: true, windowsHide: true }) : run(c.file, args, { ...opts, windowsHide: true });
48
+ }
29
49
  let CLAUDE_SEEN = null;
30
50
  function claudeState(fresh = false) {
31
51
  if (!fresh && CLAUDE_SEEN && Date.now() - CLAUDE_SEEN.at < 300000) return CLAUDE_SEEN;
32
52
  let installed = !!process.env.SYMBIOT_CLAUDE_CMD || hasCmd("claude"), signedIn = false;
33
53
  if (installed) {
34
54
  try {
35
- const r = spawnSync(CLAUDE_BIN(), ["auth", "status"], { encoding: "utf8", timeout: 8000, env: process.env });
55
+ const r = claudeSpawn(["auth", "status"], { encoding: "utf8", timeout: 8000, env: process.env }, true);
36
56
  if (r.error && r.error.code === "ENOENT") installed = false;
37
57
  else { const j = JSON.parse(String(r.stdout || "").trim() || "{}"); signedIn = !!j.loggedIn && j.authMethod !== "apiKey"; }
38
58
  } catch {}
@@ -83,7 +103,8 @@ function resolveProvider() {
83
103
 
84
104
  // ---- model calls (one per provider, same in/out) --------------------------
85
105
  // Claude Code, headless, on your subscription: Symbiot's own instructions as the
86
- // system prompt, the prompt on stdin, no tools (Read only, for screenshots you
106
+ // system prompt (from a file: Windows' command line holds only ~8,000 characters,
107
+ // and they run longer), the prompt on stdin, no tools (Read only, for screenshots you
87
108
  // attached), none of your settings, plugins or servers, no session kept. Not --bare:
88
109
  // that ignores the subscription sign-in.
89
110
  function callClaude(r, system, prompt, images = []) {
@@ -93,15 +114,18 @@ function callClaude(r, system, prompt, images = []) {
93
114
  try { const dir = join(CONFIG_DIR, "uploads"); mkdirSync(dir, { recursive: true, mode: 0o700 }); const f = join(dir, `${Date.now()}-${shots.length}.${/png/.test(i.mime) ? "png" : /webp/.test(i.mime) ? "webp" : /gif/.test(i.mime) ? "gif" : "jpg"}`); writeFileSync(f, Buffer.from(i.data, "base64"), { mode: 0o600 }); shots.push(f); } catch {}
94
115
  }
95
116
  const input = shots.length ? `${prompt}\n\nScreenshots they attached (look at them with your Read tool): ${shots.join(", ")}` : prompt;
96
- const args = ["-p", "--output-format", "json", "--system-prompt", system, "--tools", shots.length ? "Read" : "", "--setting-sources", "",
117
+ let sysFile = ""; try { mkdirSync(CONFIG_DIR, { recursive: true }); sysFile = join(CONFIG_DIR, `.system-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`); writeFileSync(sysFile, system, { mode: 0o600 }); } catch { sysFile = ""; }
118
+ const drop = () => { if (sysFile) { try { unlinkSync(sysFile); } catch {} } };
119
+ const args = ["-p", "--output-format", "json", ...(sysFile ? ["--system-prompt-file", sysFile] : ["--system-prompt", system]), "--tools", shots.length ? "Read" : "", "--setting-sources", "",
97
120
  "--strict-mcp-config", "--no-session-persistence", "--disable-slash-commands", ...(r.model ? ["--model", r.model] : [])];
98
121
  return new Promise((resolve, reject) => {
99
122
  let out = "", err = "", done = false;
100
- let ch; try { ch = spawn(CLAUDE_BIN(), args, { cwd: CONFIG_DIR, stdio: ["pipe", "pipe", "pipe"], env: process.env }); } catch (e) { reject(e); return; }
123
+ let ch; try { ch = claudeSpawn(args, { cwd: CONFIG_DIR, stdio: ["pipe", "pipe", "pipe"], env: process.env }); } catch (e) { drop(); reject(e); return; }
101
124
  const t = setTimeout(() => { if (!done) { done = true; try { ch.kill("SIGTERM"); } catch {} reject(new Error("Claude Code didn't answer within 3 minutes")); } }, 180000);
102
125
  ch.stdout.on("data", (d) => { out += d; }); ch.stderr.on("data", (d) => { err += d; });
103
- ch.on("error", (e) => { if (!done) { done = true; clearTimeout(t); reject(e); } });
126
+ ch.on("error", (e) => { drop(); if (!done) { done = true; clearTimeout(t); reject(e); } });
104
127
  ch.on("close", () => {
128
+ drop();
105
129
  if (done) return; done = true; clearTimeout(t);
106
130
  let j = null; try { j = JSON.parse(out.trim().split("\n").filter(Boolean).pop() || "{}"); } catch {}
107
131
  if (j && j.is_error === false && typeof j.result === "string") { resolve(j.result.trim()); return; }
@@ -284,4 +308,4 @@ async function connectProvider(b) {
284
308
  return saveConfig(cfg) ? { ok: true, message: `Connected: ${PROVIDERS[provider].label} · ${model}` } : { ok: false, message: "Couldn't write the config file." };
285
309
  }
286
310
 
287
- export { PROVIDERS, AI_UI, resolveProvider, write, validate, connectProvider, detectHardware, recommendModels, hasOllama, ollamaInstall, ensureOllama, useOllamaModel , claudeState, claudeReady, claudeHelp };
311
+ export { PROVIDERS, AI_UI, resolveProvider, write, validate, connectProvider, detectHardware, recommendModels, hasOllama, ollamaInstall, ensureOllama, useOllamaModel , claudeState, claudeReady, claudeHelp , claudeCommand };
package/index.mjs CHANGED
@@ -49,7 +49,8 @@ import { knowledgeState, addKnowledgeFolder, removeKnowledgeFolder, indexKnowled
49
49
 
50
50
  // ---- tiny arg parse --------------------------------------------------------
51
51
  const argv = process.argv.slice(2);
52
- const cmd = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "week").toLowerCase();
52
+ // a first word that's a flag means the default (week), except asking for help or the version
53
+ const cmd = (argv[0] && (!argv[0].startsWith("-") || ["--help", "-h", "--version", "-v"].includes(argv[0])) ? argv[0] : "week").toLowerCase();
53
54
  const flag = (name, def) => {
54
55
  const i = argv.indexOf(`--${name}`);
55
56
  return i >= 0 && argv[i + 1] ? argv[i + 1] : def;
@@ -812,6 +813,7 @@ ${c.b("Usage")}
812
813
  symbiot standup yesterday + today, for standup
813
814
  symbiot todo what's still on your plate
814
815
  symbiot app open the visual app in your browser
816
+ symbiot --version which Symbiot this is
815
817
  symbiot open start Symbiot in the background and open its window
816
818
  (what its app-menu icon does)
817
819
  symbiot uninstall [--keep-data] remove Symbiot and everything it added
@@ -868,6 +870,7 @@ ${c.d("local Ollama model).")}`;
868
870
  // ---- main -----------------------------------------------------------------
869
871
  async function main() {
870
872
  if (cmd === "help" || cmd === "--help" || cmd === "-h") { console.log(HELP); return; }
873
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") { console.log(VERSION); return; }
871
874
  if (cmd === "login" || cmd === "auth") return cmdLogin();
872
875
  if (cmd === "logout") return cmdLogout();
873
876
  if (cmd === "whoami" || cmd === "status") return cmdWhoami();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "symbiot",
3
- "version": "0.57.0",
3
+ "version": "0.57.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": {
@@ -80,7 +80,7 @@
80
80
  "@anthropic-ai/sdk": "^0.128.0"
81
81
  },
82
82
  "scripts": {
83
- "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/onboarding.mjs && node test/marketing.mjs && node test/paths.mjs && node test/smoke.mjs && node test/app.mjs && node test/install.mjs",
83
+ "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/onboarding.mjs && node test/windows.mjs && node test/marketing.mjs && node test/paths.mjs && node test/smoke.mjs && node test/app.mjs && node test/install.mjs",
84
84
  "lint": "eslint .",
85
85
  "postinstall": "node postinstall.mjs"
86
86
  },