@bivy/bivy 0.16.1-staging.3 → 0.16.1-staging.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/bin/bivy.mjs CHANGED
@@ -39,6 +39,8 @@ import { renderManagedBlock, upsertManagedBlock, removeManagedBlock, rcFileForSh
39
39
  import { removeInstallAndState } from "./uninstall-paths.mjs";
40
40
  import { findAvailablePort, reconcilePort } from "./port-picker.mjs";
41
41
  import { resolveAttachSessionId } from "./attach-session-id.mjs";
42
+ import { detectInstallKind as classifyInstallKind } from "./install-kind.mjs";
43
+ import { hasConfiguredService as configuredServiceExists } from "./service-state.mjs";
42
44
 
43
45
  const selfScript = fileURLToPath(import.meta.url);
44
46
  const __dirname = path.dirname(selfScript);
@@ -69,14 +71,10 @@ process.env.BIVY_DATA_DIR = appDir;
69
71
  // background service can point at repoRoot:
70
72
  // - "git" dev checkout (has .git)
71
73
  // - "npx" ephemeral `npx bivy` run (repoRoot under an npm _npx cache)
72
- // - "npm-global" `npm i -g @bivy/bivy` (repoRoot's parent dir is node_modules)
74
+ // - "npm-global" `npm i -g @bivy/bivy` (repoRoot is below node_modules/@bivy)
73
75
  // - "packaged" install.sh tarball tree (user-owned, self-preserving)
74
76
  function detectInstallKind() {
75
- if (fs.existsSync(path.join(repoRoot, ".git"))) return "git";
76
- const inNodeModules = path.basename(path.dirname(repoRoot)) === "node_modules";
77
- if (inNodeModules && /[\\/]_npx[\\/]/.test(repoRoot)) return "npx";
78
- if (inNodeModules) return "npm-global";
79
- return "packaged";
77
+ return classifyInstallKind(repoRoot);
80
78
  }
81
79
  const cliConfigPath = path.join(appDir, "cli.json");
82
80
  const canonicalConfigPath = path.join(appDir, "config.yaml");
@@ -3254,6 +3252,10 @@ async function reconcileNodePort(config) {
3254
3252
  // reloads/relaunches). Otherwise a plain restart. Returns true if the service
3255
3253
  // was (re)started. Used by `bivy restart` and `bivy update` — the paths that
3256
3254
  // previously trusted the saved port verbatim.
3255
+ function hasConfiguredService(config) {
3256
+ return configuredServiceExists(config, servicePaths().file);
3257
+ }
3258
+
3257
3259
  async function restartServiceReconciled(config) {
3258
3260
  const { kind, file } = servicePaths();
3259
3261
  if (!fs.existsSync(file)) return restartService();
@@ -4442,7 +4444,7 @@ async function runUpdate(args = []) {
4442
4444
  await ensureKnownAgents();
4443
4445
  const config = loadConfig();
4444
4446
  await waitForIdleSessions(config, { skip: skipWait });
4445
- if (config.service && (await restartServiceReconciled(config))) {
4447
+ if (hasConfiguredService(config) && (await restartServiceReconciled(config))) {
4446
4448
  if (await verifyNodeCameUp(config)) {
4447
4449
  console.log(c.green("Updated and restarted the background service."));
4448
4450
  } else {
@@ -4474,7 +4476,7 @@ async function runUpdate(args = []) {
4474
4476
  });
4475
4477
  // install.sh restarts the service itself; verify the node actually came up
4476
4478
  // rather than trusting the installer's exit code over a crash-looping node.
4477
- if (code === 0 && config.service && !(await verifyNodeCameUp(config))) {
4479
+ if (code === 0 && hasConfiguredService(config) && !(await verifyNodeCameUp(config))) {
4478
4480
  reportNodeDidNotStart();
4479
4481
  process.exit(1);
4480
4482
  }
@@ -4490,7 +4492,7 @@ async function runUpdate(args = []) {
4490
4492
  await ensureKnownAgents();
4491
4493
  const config = loadConfig();
4492
4494
  await waitForIdleSessions(config, { skip: skipWait });
4493
- if (config.service && (await restartServiceReconciled(config))) {
4495
+ if (hasConfiguredService(config) && (await restartServiceReconciled(config))) {
4494
4496
  if (await verifyNodeCameUp(config)) {
4495
4497
  console.log(c.green("Updated and restarted the background service."));
4496
4498
  } else {
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ /** Classify a Bivy package root without assuming unscoped npm package layout. */
7
+ export function detectInstallKind(repoRoot, existsSync = fs.existsSync) {
8
+ if (existsSync(path.join(repoRoot, ".git"))) return "git";
9
+
10
+ // npm installs an unscoped package at node_modules/name and a scoped package
11
+ // at node_modules/@scope/name. Bivy uses the latter layout.
12
+ const parent = path.dirname(repoRoot);
13
+ const grandparent = path.dirname(parent);
14
+ const inNodeModules = path.basename(parent) === "node_modules"
15
+ || (path.basename(parent).startsWith("@") && path.basename(grandparent) === "node_modules");
16
+
17
+ if (inNodeModules && /[\\/]_npx[\\/]/.test(repoRoot)) return "npx";
18
+ if (inNodeModules) return "npm-global";
19
+ return "packaged";
20
+ }
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import fs from "node:fs";
4
+
5
+ /**
6
+ * Whether an install has a background service that an update must restart.
7
+ * The on-disk unit/plist is authoritative; cli.json.service is only a hint and
8
+ * may be absent on migrated installs.
9
+ */
10
+ export function hasConfiguredService(config, serviceFile, existsSync = fs.existsSync) {
11
+ return Boolean(config?.service) || existsSync(serviceFile);
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.16.1-staging.3",
3
+ "version": "0.16.1-staging.4",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",