balladeer 1.0.5 → 1.0.6

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/dist/cli.js CHANGED
File without changes
package/dist/install.js CHANGED
@@ -23,9 +23,10 @@ function commandOnPath(name, env) {
23
23
  }
24
24
  return undefined;
25
25
  }
26
- function execute(command, args, env) {
26
+ function execute(command, args, env, cwd) {
27
27
  return execFileSync(command, args, {
28
28
  env,
29
+ ...(cwd ? { cwd } : {}),
29
30
  encoding: "utf8",
30
31
  stdio: ["ignore", "pipe", "pipe"],
31
32
  timeout: 120_000,
@@ -33,14 +34,14 @@ function execute(command, args, env) {
33
34
  }).trim();
34
35
  }
35
36
  /** Inspect package ownership before executing a command from PATH. */
36
- function packageVersion(command, env) {
37
+ function packageVersion(command, env, cwd) {
37
38
  let entry = realpathSync(command);
38
39
  if (basename(entry) === "volta-shim") {
39
40
  const volta = commandOnPath("volta", env);
40
41
  if (!volta)
41
42
  return undefined;
42
43
  try {
43
- entry = realpathSync(execute(volta, ["which", "balladeer"], env));
44
+ entry = realpathSync(execute(volta, ["which", "balladeer"], env, cwd));
44
45
  }
45
46
  catch {
46
47
  return undefined;
@@ -121,25 +122,114 @@ function shellProfiles(env, bin) {
121
122
  return { path, text, previous: old };
122
123
  });
123
124
  }
125
+ function atLeastCurrent(version) {
126
+ if (!/^\d+\.\d+\.\d+$/.test(version))
127
+ return false;
128
+ const actual = version.split(".").map(Number), expected = CLI_VERSION.split(".").map(Number);
129
+ for (let i = 0; i < 3; i++) {
130
+ if (actual[i] !== expected[i])
131
+ return actual[i] > expected[i];
132
+ }
133
+ return true;
134
+ }
135
+ /** npm exec may put the actual Node-image bin ahead of Volta's registered shim. */
136
+ function installWithVolta(env) {
137
+ const home = env.VOLTA_HOME ?? (env.HOME ? join(env.HOME, ".volta") : undefined);
138
+ const volta = commandOnPath("volta", env);
139
+ if (!home || !isAbsolute(home) || !volta)
140
+ return undefined;
141
+ const bin = join(home, "bin"), shim = join(bin, "balladeer");
142
+ const nodeShim = join(bin, "node");
143
+ // Merely having Volta somewhere on PATH must not select it for another toolchain.
144
+ const onPath = (env.PATH ?? "")
145
+ .split(delimiter)
146
+ .some((part) => resolve(part || ".") === resolve(bin));
147
+ const managedNode = existsSync(nodeShim) && basename(realpathSync(nodeShim)) === "volta-shim";
148
+ const imageDirectory = join(home, "tools/image/node");
149
+ const imageRoot = existsSync(imageDirectory)
150
+ ? realpathSync(imageDirectory)
151
+ : resolve(imageDirectory);
152
+ const selectedNode = commandOnPath("node", env);
153
+ const actualNode = selectedNode ? realpathSync(selectedNode) : undefined;
154
+ const activeToolchain = actualNode !== undefined &&
155
+ ((managedNode && actualNode === realpathSync(nodeShim)) ||
156
+ actualNode.startsWith(imageRoot + "/"));
157
+ if (!onPath || !managedNode || !activeToolchain)
158
+ return undefined;
159
+ const bootstrap = env.npm_command === "exec";
160
+ const parentEnv = {
161
+ ...env,
162
+ PATH: (env.PATH ?? "")
163
+ .split(delimiter)
164
+ .filter((part) => {
165
+ const full = resolve(part || ".");
166
+ if (full.split(/[\\/]/).includes("node_modules"))
167
+ return false;
168
+ return !(bootstrap &&
169
+ (existsSync(full) ? realpathSync(full) : full).startsWith(imageRoot + "/") &&
170
+ basename(full) === "bin");
171
+ })
172
+ .join(delimiter),
173
+ };
174
+ const selected = commandOnPath("balladeer", parentEnv);
175
+ if (selected && resolve(selected) !== resolve(shim))
176
+ throw new InstallError(`Another command precedes Volta at ${selected}. Resolve that PATH conflict before installing; nothing was overwritten.`);
177
+ if (existsSync(shim) && basename(realpathSync(shim)) !== "volta-shim")
178
+ throw new InstallError("Another command owns Volta's balladeer path. Nothing was overwritten.");
179
+ const neutral = mkdtempSync(join(tmpdir(), "balladeer-volta-install-"));
180
+ const installEnv = { ...parentEnv, npm_config_cache: join(neutral, "npm-cache") };
181
+ try {
182
+ let version = existsSync(shim) ? packageVersion(shim, installEnv, neutral) : undefined;
183
+ if (existsSync(shim) && version === undefined)
184
+ throw new InstallError("Volta's registered Balladeer package cannot be verified. Repair the toolchain before pairing.");
185
+ const alreadyCurrent = version !== undefined && atLeastCurrent(version);
186
+ if (!alreadyCurrent) {
187
+ try {
188
+ execute(volta, ["install", `balladeer@${CLI_VERSION}`], installEnv, neutral);
189
+ }
190
+ catch {
191
+ throw new InstallError("Volta could not install the exact Balladeer release. Check network and toolchain write access, then repeat npx -y balladeer@latest install. No pairing has started.");
192
+ }
193
+ version = existsSync(shim) ? packageVersion(shim, installEnv, neutral) : undefined;
194
+ if (version !== CLI_VERSION)
195
+ throw new InstallError("Volta finished, but its registered default is not the expected Balladeer release. No pairing has started.");
196
+ }
197
+ // Verify both the global default and what an ordinary fresh child in the
198
+ // caller's directory sees. A project override is never mistaken for a default.
199
+ if (!version ||
200
+ execute(shim, ["--version"], installEnv, neutral) !== version ||
201
+ commandOnPath("balladeer", parentEnv) !== shim ||
202
+ execute(shim, ["--version"], parentEnv) !== version)
203
+ throw new InstallError("Volta's default was checked, but this terminal or project selects a different Balladeer. Resolve its PATH or project tool pin before pairing.");
204
+ const shell = execute("/bin/sh", ["-c", "command -v balladeer; balladeer --version"], parentEnv).split("\n");
205
+ if (shell[0] !== shim || shell[1] !== version)
206
+ throw new InstallError("The fresh terminal PATH does not select the verified Volta command. No pairing has started.");
207
+ env.PATH = parentEnv.PATH;
208
+ return {
209
+ status: alreadyCurrent ? "current" : "installed",
210
+ command: shim,
211
+ version,
212
+ restartRequired: false,
213
+ };
214
+ }
215
+ finally {
216
+ rmSync(neutral, { recursive: true, force: true });
217
+ }
218
+ }
124
219
  export function ensureInstalled(options) {
125
220
  const env = options.environment;
126
221
  const platform = options.platform ?? process.platform;
127
222
  if (platform === "win32")
128
223
  throw new InstallError("Automatic command installation currently supports macOS and Linux. On Windows, use npm install --global balladeer@latest, then verify balladeer --version. No files or pairing were changed.");
224
+ const managed = installWithVolta(env);
225
+ if (managed)
226
+ return managed;
129
227
  const current = commandOnPath("balladeer", env);
130
228
  if (current) {
131
229
  const version = packageVersion(current, env);
132
230
  if (version === undefined)
133
231
  throw new InstallError(`Another command already owns ${current}. Keep it intact and resolve that command conflict before installing Balladeer.`);
134
- const newer = /^\d+\.\d+\.\d+$/.test(version) &&
135
- version
136
- .split(".")
137
- .map(Number)
138
- .some((part, index, parts) => part > Number(CLI_VERSION.split(".")[index]) &&
139
- parts
140
- .slice(0, index)
141
- .every((value, before) => value === Number(CLI_VERSION.split(".")[before])));
142
- if (version === CLI_VERSION || newer) {
232
+ if (atLeastCurrent(version)) {
143
233
  if (execute(current, ["--version"], env) !== version)
144
234
  throw new InstallError("The installed Balladeer command does not run the expected version. Repair the installation before pairing.");
145
235
  return { status: "current", command: current, restartRequired: false, version };
package/dist/wire.d.ts CHANGED
@@ -5,10 +5,10 @@
5
5
  * runtime dependency at all: Node 22 builtins and global fetch, nothing else.
6
6
  * A contract test compares the scope list below against the server's.
7
7
  */
8
- export declare const CLI_VERSION = "1.0.5";
8
+ export declare const CLI_VERSION = "1.0.6";
9
9
  export declare const CLI_INVOCATION = "npx -y balladeer@latest";
10
10
  export declare const CLIENT_HEADER = "x-balladeer-client";
11
- export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.5";
11
+ export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.6";
12
12
  export declare const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
13
13
  export type DelegatedScope = "repository:enroll" | "agent:issue" | "ci:connect" | "workspace:invite" | "candidate:propose";
14
14
  export declare const DELEGATED_SCOPES: readonly DelegatedScope[];
package/dist/wire.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * runtime dependency at all: Node 22 builtins and global fetch, nothing else.
6
6
  * A contract test compares the scope list below against the server's.
7
7
  */
8
- export const CLI_VERSION = "1.0.5";
8
+ export const CLI_VERSION = "1.0.6";
9
9
  export const CLI_INVOCATION = "npx -y balladeer@latest";
10
10
  export const CLIENT_HEADER = "x-balladeer-client";
11
11
  export const CLIENT_HEADER_VALUE = `balladeer/${CLI_VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balladeer",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Set up Balladeer from your terminal, or from a coding agent's.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,