balladeer 0.0.3 → 0.0.5

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.
Files changed (2) hide show
  1. package/bin/balladeer.js +49 -4
  2. package/package.json +1 -1
package/bin/balladeer.js CHANGED
@@ -10,9 +10,9 @@
10
10
  * Flags: --yes/-y skip the prompt; --download-only fetch + verify, print the path.
11
11
  */
12
12
  const { createHash } = require("node:crypto");
13
- const { mkdtempSync, writeFileSync, rmSync } = require("node:fs");
13
+ const { existsSync, mkdtempSync, writeFileSync, rmSync } = require("node:fs");
14
14
  const { tmpdir } = require("node:os");
15
- const { join } = require("node:path");
15
+ const { join, sep } = require("node:path");
16
16
  const { spawnSync } = require("node:child_process");
17
17
  const readline = require("node:readline");
18
18
 
@@ -82,10 +82,34 @@ async function main() {
82
82
  return;
83
83
  }
84
84
 
85
+ // Volta, two traps (both hit live on a Volta machine): (1) it prepends its real-node bin dir
86
+ // to a child tool's PATH, so a bare `npm` here is the RAW npm that installs into the node
87
+ // image's globals — while the user's `balladeer` stays Volta's shim serving THIS bootstrapper
88
+ // (split-brain: every run is the installer again); (2) it stamps _VOLTA_TOOL_RECURSION into a
89
+ // shimmed tool's env, and the shim npm passes straight through to the raw npm when it sees it,
90
+ // so merely routing through $VOLTA_HOME/bin/npm changes nothing. Fix: spawn Volta's npm shim
91
+ // WITH the recursion guard scrubbed, so its global-install interception updates the package
92
+ // image the user's shim actually serves.
93
+ const childEnv = { ...process.env };
94
+ delete childEnv._VOLTA_TOOL_RECURSION;
95
+ // Volta detection cannot rely on env: inside a shimmed tool VOLTA_HOME is absent (the env
96
+ // carries only _VOLTA_TOOL_RECURSION). Our own node binary IS Volta's
97
+ // (<volta>/tools/image/node/<v>/bin/node), so derive the root from execPath, honoring
98
+ // VOLTA_HOME when it is set (custom install locations).
99
+ const voltaMarker = `${sep}.volta${sep}`;
100
+ const voltaRoot =
101
+ process.env.VOLTA_HOME ??
102
+ (process.execPath.includes(voltaMarker)
103
+ ? process.execPath.slice(0, process.execPath.indexOf(voltaMarker) + voltaMarker.length - 1)
104
+ : null);
105
+ const voltaBin = voltaRoot ? join(voltaRoot, "bin") : null;
106
+ const voltaNpm = voltaBin ? join(voltaBin, "npm") : null;
107
+ const npmCmd = voltaNpm && existsSync(voltaNpm) ? voltaNpm : "npm";
85
108
  // --ignore-scripts: the client has no install hooks, and this keeps a hostile tarball from
86
109
  // running code at install time (the same flag the script installer passes).
87
- const r = spawnSync("npm", ["install", "-g", "--ignore-scripts", file], {
110
+ const r = spawnSync(npmCmd, ["install", "-g", "--ignore-scripts", file], {
88
111
  stdio: "inherit",
112
+ env: childEnv,
89
113
  shell: process.platform === "win32",
90
114
  });
91
115
  try {
@@ -95,6 +119,27 @@ async function main() {
95
119
  fail(`error: npm install -g failed (exit ${r.status ?? "?"}). Fix the npm error above and re-run \`balladeer\`.`, r.status ?? 1);
96
120
  }
97
121
 
122
+ // Never report success on npm's exit code alone: prove the world changed — AS THE USER'S SHELL
123
+ // SEES IT. Probe the Volta shim by absolute path when Volta is present (this process's PATH has
124
+ // Volta's image dirs prepended, so a bare probe can hit a copy the user's shell never resolves),
125
+ // with the recursion guard scrubbed so the shim re-resolves instead of echoing this bootstrapper.
126
+ const voltaShim = voltaBin ? join(voltaBin, "balladeer") : null;
127
+ const probeCmd = voltaShim && existsSync(voltaShim) ? voltaShim : "balladeer";
128
+ const probe = spawnSync(probeCmd, ["--version"], {
129
+ encoding: "utf8",
130
+ env: childEnv,
131
+ shell: process.platform === "win32",
132
+ });
133
+ const probedVersion = (probe.stdout ?? "").trim();
134
+ if (probe.status !== 0 || !/^\d+\.\d+\.\d+/.test(probedVersion)) {
135
+ fail(
136
+ `error: npm reported success, but \`balladeer\` on your PATH is not the client yet.\n` +
137
+ `Your npm setup may install globals somewhere this shell does not look, or a version\n` +
138
+ `manager may be serving a stale copy. Try a fresh shell and \`balladeer --version\`,\n` +
139
+ `or use the script installer:\n\n curl -fsSL ${BASE} | sh\n`,
140
+ );
141
+ }
142
+
98
143
  // Plain `balladeer login` (the client defaults to hosted central). BASE is the INSTALL host
99
144
  // (get.balladeer.ai), an alias that exists only for downloading — teaching it as --server
100
145
  // would store it as the machine's permanent server URL. A custom BALLADEER_INSTALL_BASE
@@ -105,7 +150,7 @@ async function main() {
105
150
  ? "balladeer login"
106
151
  : "balladeer login --server <your central server URL>";
107
152
  console.error(
108
- `\n✓ Balladeer client installed.\n\n` +
153
+ `\n✓ Balladeer client installed (${probedVersion}).\n\n` +
109
154
  ` next ${pairHint} # approve in your browser; no token to copy\n` +
110
155
  ` then your coding agent gains get_context / consult / propose_decision\n` +
111
156
  ` agent or let your agent finish the wiring: paste into it -> Read ${BASE}/agent and follow it.\n\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balladeer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Installer for the Balladeer client (decision memory for engineering teams and their AI agents): downloads the sha256-verified client from get.balladeer.ai and installs it globally.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://balladeer.ai",