baro-ai 0.118.6 → 0.118.7

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/bin/baro CHANGED
@@ -3,9 +3,21 @@
3
3
  // shebang, and a #!/bin/sh bin makes the Windows shim invoke sh.exe (absent on
4
4
  // Windows). A node shebang yields correct cmd/ps1/sh shims on every platform.
5
5
  import { spawnSync } from "node:child_process"
6
- import { existsSync } from "node:fs"
6
+ import { existsSync, readFileSync } from "node:fs"
7
7
  import { homedir } from "node:os"
8
8
  import path from "node:path"
9
+ import { fileURLToPath } from "node:url"
10
+
11
+ import { selfUpdate } from "./self-update.mjs"
12
+
13
+ const argv = process.argv.slice(2)
14
+ try {
15
+ const packageJson = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json")
16
+ const packageVersion = JSON.parse(readFileSync(packageJson, "utf8")).version
17
+ await selfUpdate({ packageVersion, argv })
18
+ } catch {
19
+ // An update that cannot be attempted never stops baro from starting.
20
+ }
9
21
 
10
22
  const bin = path.join(
11
23
  homedir(),
@@ -19,7 +31,7 @@ if (!existsSync(bin)) {
19
31
  process.exit(1)
20
32
  }
21
33
 
22
- const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" })
34
+ const result = spawnSync(bin, argv, { stdio: "inherit" })
23
35
  if (result.error) {
24
36
  console.error(`baro: failed to launch binary: ${result.error.message}`)
25
37
  process.exit(1)
@@ -0,0 +1,122 @@
1
+ // A hand-started baro brings itself up to date before it runs. The runner
2
+ // already did this for services; a person running `baro` stayed on whatever
3
+ // they had installed until they noticed the banner.
4
+ import { spawnSync } from "node:child_process"
5
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
6
+ import { homedir } from "node:os"
7
+ import path from "node:path"
8
+
9
+ const CACHE_TTL_MS = 3 * 3600_000
10
+ const FETCH_TIMEOUT_MS = 2500
11
+ const QUICK_COMMANDS = new Set(["--version", "-V", "--help", "-h", "connect"])
12
+
13
+ export function semverLt(a, b) {
14
+ const parts = (v) => String(v).split(".").map((x) => Number.parseInt(x, 10) || 0)
15
+ const [pa, pb] = [parts(a), parts(b)]
16
+ for (let i = 0; i < 3; i++) {
17
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) < (pb[i] ?? 0)
18
+ }
19
+ return false
20
+ }
21
+
22
+ /**
23
+ * Why this invocation must not update, or null when it may. Automation keeps
24
+ * the version it was given; the runner updates itself between runs; and the
25
+ * staged bundles are shared, so replacing them under a live run breaks it.
26
+ */
27
+ export function skipReason({ argv, env, isTTY, liveRuns }) {
28
+ if (env.BARO_NO_SELF_UPDATE === "1") return "BARO_NO_SELF_UPDATE=1"
29
+ if (env.CI) return "CI"
30
+ if (!isTTY) return "not a terminal"
31
+ if (argv.includes("--headless")) return "headless"
32
+ if (argv.some((arg) => QUICK_COMMANDS.has(arg))) return "quick command"
33
+ if (liveRuns > 0) return "another baro run is active"
34
+ return null
35
+ }
36
+
37
+ function baroHome() {
38
+ return path.join(homedir(), ".baro")
39
+ }
40
+
41
+ function readJson(file) {
42
+ try {
43
+ return JSON.parse(readFileSync(file, "utf8"))
44
+ } catch {
45
+ return null
46
+ }
47
+ }
48
+
49
+ /** The version the binary and its bundles are actually staged at. */
50
+ export function installedVersion(packageVersion) {
51
+ return readJson(path.join(baroHome(), "bin", "bundle-version.json"))?.version ?? packageVersion
52
+ }
53
+
54
+ export function liveRunCount() {
55
+ try {
56
+ const dir = path.join(baroHome(), "live")
57
+ return readdirSync(dir).filter((name) => {
58
+ const pid = readJson(path.join(dir, name))?.pid
59
+ if (!Number.isInteger(pid)) return false
60
+ try {
61
+ process.kill(pid, 0)
62
+ return true
63
+ } catch (error) {
64
+ return error.code === "EPERM"
65
+ }
66
+ }).length
67
+ } catch {
68
+ return 0
69
+ }
70
+ }
71
+
72
+ export async function latestVersion(now = Date.now()) {
73
+ const cachePath = path.join(baroHome(), "update-check.json")
74
+ const cached = readJson(cachePath)
75
+ if (cached?.latest && cached.checkedAt && now - cached.checkedAt < CACHE_TTL_MS) {
76
+ return cached.latest
77
+ }
78
+ try {
79
+ const response = await fetch("https://registry.npmjs.org/baro-ai/latest", {
80
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
81
+ })
82
+ const latest = (await response.json())?.version
83
+ if (typeof latest !== "string") return null
84
+ try {
85
+ mkdirSync(baroHome(), { recursive: true })
86
+ writeFileSync(cachePath, JSON.stringify({ latest, checkedAt: now }))
87
+ } catch {
88
+ // A read-only home only costs the next start another lookup.
89
+ }
90
+ return latest
91
+ } catch {
92
+ return null
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Update when a newer baro is published. Never throws and never blocks a run
98
+ * for long: offline, a failed install or a root-owned global prefix all fall
99
+ * through to the version already installed.
100
+ */
101
+ export async function selfUpdate({ packageVersion, argv, env = process.env, isTTY = process.stdin.isTTY }) {
102
+ const reason = skipReason({ argv, env, isTTY: Boolean(isTTY), liveRuns: 0 })
103
+ if (reason) return { updated: false, reason }
104
+ const current = installedVersion(packageVersion)
105
+ const latest = await latestVersion()
106
+ if (!latest || !semverLt(current, latest)) return { updated: false, reason: "up to date" }
107
+ if (liveRunCount() > 0) {
108
+ console.error(`baro ${latest} is available; not updating while another baro run is active.`)
109
+ return { updated: false, reason: "another baro run is active" }
110
+ }
111
+ console.error(`Updating baro ${current} → ${latest}…`)
112
+ const result = spawnSync("npm", ["install", "-g", `baro-ai@${latest}`], {
113
+ stdio: ["ignore", "ignore", "inherit"],
114
+ shell: process.platform === "win32",
115
+ })
116
+ if (result.status !== 0 || installedVersion(packageVersion) !== latest) {
117
+ console.error(`Could not update baro automatically. Update with: npm i -g baro-ai@latest`)
118
+ return { updated: false, reason: "install failed" }
119
+ }
120
+ console.error(`baro ${latest} installed.`)
121
+ return { updated: true, reason: null }
122
+ }
package/dist/runner.mjs CHANGED
@@ -3857,7 +3857,7 @@ var url = process.env.CONTROL_URL ?? "wss://api.baro.jigjoy.ai";
3857
3857
  var token = process.env.RUNNER_TOKEN;
3858
3858
  var httpBase = canonicalControlHttpOrigin(url);
3859
3859
  var credsPath = join3(homedir2(), ".baro", "credentials.json");
3860
- var VERSION = true ? "0.118.6" : "0.0.0-dev";
3860
+ var VERSION = true ? "0.118.7" : "0.0.0-dev";
3861
3861
  var RUNNER_VERSION = VERSION;
3862
3862
  var selfUpdateEnabled = VERSION !== "0.0.0-dev" && process.env.BARO_RUN_ONCE !== "1" && process.env.BARO_NO_SELF_UPDATE !== "1";
3863
3863
  var RUNNER_PROTOCOL_VERSION = 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baro-ai",
3
- "version": "0.118.6",
3
+ "version": "0.118.7",
4
4
  "description": "Autonomous parallel coding - plan and execute with AI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -96,8 +96,17 @@ async function main() {
96
96
 
97
97
  fs.mkdirSync(BARO_HOME, { recursive: true })
98
98
 
99
- // Clean up old binary
100
- try { fs.unlinkSync(binaryPath) } catch {}
99
+ // Clean up old binary. Windows cannot delete a running executable but can
100
+ // rename it, so an update started from baro itself moves it aside.
101
+ try {
102
+ fs.unlinkSync(binaryPath)
103
+ } catch (err) {
104
+ if (err.code !== "ENOENT") {
105
+ const aside = `${binaryPath}.old`
106
+ try { fs.unlinkSync(aside) } catch {}
107
+ try { fs.renameSync(binaryPath, aside) } catch {}
108
+ }
109
+ }
101
110
 
102
111
  try {
103
112
  await download(url, binaryPath)