@boxcompute/cli 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -61,9 +61,11 @@ Deploy the server release with both Sandbox API v1 and v2 before publishing CLI
61
61
  contract. After that, clients need only upgrade the package:
62
62
 
63
63
  ```sh
64
- npm install --global @boxcompute/cli@latest
64
+ bxc update
65
65
  ```
66
66
 
67
- The installation refreshes untouched managed skills before the client's next
68
- agent session. The new CLI uses v2 for multi-instance sandboxes and gives a
69
- server-first upgrade message if it reaches an older deployment.
67
+ `bxc up` is the short alias. If the automatic update cannot invoke npm, use
68
+ `npm install --global @boxcompute/cli@latest` manually. Updating preserves the
69
+ saved BoxCompute credential and refreshes untouched managed skills before the
70
+ client's next agent session. The new CLI uses v2 for multi-instance sandboxes
71
+ and gives a server-first upgrade message if it reaches an older deployment.
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
2
3
  import { type Connection } from "./config.js";
3
4
  import { detectHarnesses, installSkill, readSkill, removeSkill, syncManagedSkills } from "./skill.js";
4
5
  type Io = {
@@ -12,6 +13,7 @@ export type CliDependencies = {
12
13
  now?: () => number;
13
14
  sleep?: (milliseconds: number) => Promise<void>;
14
15
  openBrowser?: (url: string) => void;
16
+ installUpdate?: (version: string) => Promise<void>;
15
17
  loadConnection?: (env: NodeJS.ProcessEnv) => Promise<Connection>;
16
18
  loadSavedUrl?: (env: NodeJS.ProcessEnv) => Promise<string | null>;
17
19
  saveConnection?: (url: string, token: string, env: NodeJS.ProcessEnv) => Promise<void>;
@@ -22,6 +24,18 @@ export type CliDependencies = {
22
24
  readSkill?: typeof readSkill;
23
25
  syncManagedSkills?: typeof syncManagedSkills;
24
26
  };
27
+ type BrowserRuntime = {
28
+ env?: NodeJS.ProcessEnv;
29
+ kernelRelease?: string;
30
+ spawn?: typeof spawn;
31
+ system?: NodeJS.Platform;
32
+ };
33
+ export declare function browserLaunch(url: string, runtime?: Pick<BrowserRuntime, "env" | "kernelRelease" | "system">): {
34
+ command: string;
35
+ args: string[];
36
+ detached: boolean;
37
+ };
38
+ export declare function openBrowser(url: string, runtime?: BrowserRuntime): void;
25
39
  export declare function runCli(argv: string[], supplied?: CliDependencies): Promise<number>;
26
40
  export declare function formatCliError(error: unknown): string;
27
41
  export {};
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  import { readFileSync, realpathSync } from "node:fs";
4
- import { hostname, platform } from "node:os";
4
+ import { hostname, platform, release } from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { BoxComputeClient, BoxComputeHttpError, publicRequest, } from "./client.js";
7
7
  import { clearConnection, loadConnection, loadSavedUrl, saveConnection, } from "./config.js";
@@ -15,6 +15,7 @@ Usage: bxc [options] [command]
15
15
  Commands:
16
16
 
17
17
  version Print the version number and exit
18
+ update [alias: up] Update the CLI to the latest npm release
18
19
  login Log in through BoxCompute in your browser
19
20
  logout Revoke and remove the saved CLI credential
20
21
  auth [alias: login] Authentication commands
@@ -48,6 +49,7 @@ Login options:
48
49
  Examples:
49
50
 
50
51
  $ bxc login
52
+ $ bxc update
51
53
  $ bxc skill detect
52
54
  $ bxc skill install
53
55
  $ bxc workspaces
@@ -105,13 +107,104 @@ Supported harnesses:
105
107
  class UsageError extends Error {
106
108
  constructor(message) { super(message); this.name = "UsageError"; }
107
109
  }
108
- function browser(url) {
109
- const system = platform();
110
- const command = system === "darwin" ? "open" : system === "win32" ? "cmd" : "xdg-open";
111
- const args = system === "win32" ? ["/c", "start", "", url] : [url];
112
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
113
- child.on("error", () => undefined);
114
- child.unref();
110
+ export function browserLaunch(url, runtime = {}) {
111
+ const system = runtime.system ?? platform();
112
+ const env = runtime.env ?? process.env;
113
+ const kernelRelease = runtime.kernelRelease ?? release();
114
+ const isWsl = system === "linux" && Boolean(env.WSL_DISTRO_NAME || env.WSL_INTEROP || /microsoft/i.test(kernelRelease));
115
+ if (isWsl)
116
+ return { command: "explorer.exe", args: [url], detached: false };
117
+ if (system === "darwin")
118
+ return { command: "open", args: [url], detached: true };
119
+ if (system === "win32")
120
+ return { command: "cmd", args: ["/c", "start", "", url], detached: true };
121
+ return { command: "xdg-open", args: [url], detached: true };
122
+ }
123
+ export function openBrowser(url, runtime = {}) {
124
+ const launch = browserLaunch(url, runtime);
125
+ try {
126
+ const child = (runtime.spawn ?? spawn)(launch.command, launch.args, {
127
+ detached: launch.detached,
128
+ stdio: "ignore",
129
+ });
130
+ child.on("error", () => undefined);
131
+ child.unref();
132
+ }
133
+ catch {
134
+ // The approval URL is always printed before this best-effort launch. Keep
135
+ // polling so headless shells and restricted WSL interop can authenticate.
136
+ }
137
+ }
138
+ function releaseVersion(value) {
139
+ if (typeof value !== "string")
140
+ return null;
141
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value);
142
+ if (!match)
143
+ return null;
144
+ const parts = match.slice(1).map(Number);
145
+ return parts.every(Number.isSafeInteger) ? parts : null;
146
+ }
147
+ function compareReleaseVersions(left, right) {
148
+ for (let index = 0; index < left.length; index += 1) {
149
+ if (left[index] !== right[index])
150
+ return left[index] - right[index];
151
+ }
152
+ return 0;
153
+ }
154
+ async function installCliUpdate(version) {
155
+ const executable = platform() === "win32" ? "npm.cmd" : "npm";
156
+ await new Promise((resolve, reject) => {
157
+ let child;
158
+ try {
159
+ child = spawn(executable, ["install", "--global", `@boxcompute/cli@${version}`], {
160
+ stdio: ["ignore", "ignore", "inherit"],
161
+ });
162
+ }
163
+ catch (error) {
164
+ reject(error);
165
+ return;
166
+ }
167
+ child.once("error", reject);
168
+ child.once("exit", (code) => {
169
+ if (code === 0)
170
+ resolve();
171
+ else
172
+ reject(new Error(`npm exited with code ${code ?? "unknown"}`));
173
+ });
174
+ });
175
+ }
176
+ async function updateCli(dependencies) {
177
+ let response;
178
+ try {
179
+ response = await dependencies.fetch("https://registry.npmjs.org/%40boxcompute%2Fcli/latest", {
180
+ headers: { accept: "application/json" },
181
+ });
182
+ }
183
+ catch (error) {
184
+ throw new Error(`Could not check npm for updates: ${error?.message ?? String(error)}`);
185
+ }
186
+ if (!response.ok)
187
+ throw new Error(`Could not check npm for updates (HTTP ${response.status})`);
188
+ const latest = (await response.json()).version;
189
+ const currentParts = releaseVersion(CLI_VERSION);
190
+ const latestParts = releaseVersion(latest);
191
+ if (!currentParts || !latestParts || typeof latest !== "string") {
192
+ throw new Error("npm returned an invalid BoxCompute CLI version");
193
+ }
194
+ if (compareReleaseVersions(latestParts, currentParts) <= 0) {
195
+ emit(dependencies.io, dependencies.json, { updated: false, version: CLI_VERSION }, `BoxCompute CLI is already up to date (${CLI_VERSION}).\n`);
196
+ return 0;
197
+ }
198
+ write(dependencies.io.stderr, `Updating BoxCompute CLI from ${CLI_VERSION} to ${latest}…\n`);
199
+ try {
200
+ await dependencies.install(latest);
201
+ }
202
+ catch (error) {
203
+ throw new Error(`Could not install @boxcompute/cli@${latest}: ${error?.message ?? String(error)}. ` +
204
+ `Run \`npm install --global @boxcompute/cli@${latest}\` manually.`);
205
+ }
206
+ emit(dependencies.io, dependencies.json, { updated: true, previousVersion: CLI_VERSION, version: latest }, `Updated BoxCompute CLI to ${latest}.\n`);
207
+ return 0;
115
208
  }
116
209
  const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
117
210
  const write = (stream, value) => { stream.write(value); };
@@ -251,7 +344,8 @@ export async function runCli(argv, supplied = {}) {
251
344
  const fetchImpl = supplied.fetch ?? fetch;
252
345
  const now = supplied.now ?? Date.now;
253
346
  const sleep = supplied.sleep ?? delay;
254
- const openBrowser = supplied.openBrowser ?? browser;
347
+ const openBrowserImpl = supplied.openBrowser ?? openBrowser;
348
+ const installUpdate = supplied.installUpdate ?? installCliUpdate;
255
349
  const load = supplied.loadConnection ?? loadConnection;
256
350
  const savedUrl = supplied.loadSavedUrl ?? loadSavedUrl;
257
351
  const save = supplied.saveConnection ?? saveConnection;
@@ -277,6 +371,8 @@ export async function runCli(argv, supplied = {}) {
277
371
  let command = args.shift();
278
372
  if (command === "login")
279
373
  command = "auth";
374
+ if (command === "up")
375
+ command = "update";
280
376
  if (command === "skills")
281
377
  command = "skill";
282
378
  if (command === "list" || command === "ls")
@@ -323,7 +419,12 @@ export async function runCli(argv, supplied = {}) {
323
419
  emit(io, json, { authenticated: false }, "BoxCompute CLI credential revoked and removed.\n");
324
420
  return 0;
325
421
  }
326
- return authenticate(args, { env, io, json, fetch: fetchImpl, now, sleep, openBrowser, loadSavedUrl: savedUrl, saveConnection: save });
422
+ return authenticate(args, { env, io, json, fetch: fetchImpl, now, sleep, openBrowser: openBrowserImpl, loadSavedUrl: savedUrl, saveConnection: save });
423
+ }
424
+ if (command === "update") {
425
+ if (args.length)
426
+ throw new UsageError("update takes no options");
427
+ return updateCli({ fetch: fetchImpl, install: installUpdate, io, json });
327
428
  }
328
429
  if (command === "skill") {
329
430
  let action = args.shift();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcompute/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Connect local AI agents to BoxCompute sandboxes",
5
5
  "keywords": [
6
6
  "boxcompute",