@callstack/apex 0.4.4 → 0.4.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 (3) hide show
  1. package/README.md +6 -4
  2. package/dist/cli.js +26 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -234,10 +234,12 @@ Other launchers: `apex run opencode`, `apex run claude`, `apex run pi`, `apex ru
234
234
  Launchers pass extra arguments through unchanged (without a shell), can be overridden by explicit
235
235
  user arguments, and never run `init`. `apex run claude` sets the gateway URL, token, attribution
236
236
  flag and model aliases in the child process only, leaving your default Claude provider alone.
237
- `apex run opencode` checks which OpenCode is installed: OpenCode 1 gets `--model`; OpenCode 2,
238
- whose terminal interface has no `--model`, starts with `--standalone` and the model in
239
- `OPENCODE_CONFIG_CONTENT`, so the private server sees the model and, for a config that references
240
- the key variable, your key too.
237
+ `apex run opencode` asks the installed binary which interface it has (`--standalone` exists only on
238
+ OpenCode 2; its version number says nothing, because dev and beta builds of both generations report
239
+ `0.0.0-*`): OpenCode 1 gets `--model`; OpenCode 2, whose terminal interface has no `--model`, starts
240
+ with `--standalone` and the model in `OPENCODE_CONFIG_CONTENT`, so the private server sees the
241
+ model and, for a config that references the key variable, your key too. A binary that cannot be
242
+ asked launches with neither flag, on your own configuration, and says so.
241
243
 
242
244
  ## Safety and recovery
243
245
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { isDeepStrictEqual, parseArgs, styleText } from "node:util";
2
+ import { isDeepStrictEqual, parseArgs, promisify, styleText } from "node:util";
3
3
  import { execFile, spawn } from "node:child_process";
4
4
  import { access, lstat, mkdir, open, readFile, rename, stat, unlink } from "node:fs/promises";
5
5
  import { basename, delimiter, dirname, join, parse, resolve } from "node:path";
@@ -2602,14 +2602,15 @@ async function executable(command, env = process.env, platform = process.platfor
2602
2602
  }
2603
2603
  return null;
2604
2604
  }
2605
- function openCodeMajor(binary) {
2606
- if (!binary) return Promise.resolve(1);
2607
- return new Promise((resolve) => {
2608
- execFile(binary, ["--version"], { timeout: 1e4 }, (error, stdout) => {
2609
- const match = !error && /^(?:opencode )?v?(\d+)\.\d+\.\d+\s*$/m.exec(stdout);
2610
- resolve(match ? Number(match[1]) : 1);
2611
- });
2612
- });
2605
+ const execFileAsync = promisify(execFile);
2606
+ async function openCodeSupportsStandalone(binary) {
2607
+ if (!binary) return null;
2608
+ try {
2609
+ const { stdout, stderr } = await execFileAsync(binary, ["--help"], { timeout: 5e3 });
2610
+ return `${stdout}${stderr}`.includes("--standalone");
2611
+ } catch (error) {
2612
+ return `${error.stdout}${error.stderr}`.includes("--standalone") ? true : null;
2613
+ }
2613
2614
  }
2614
2615
  function locations(home = homedir(), env = process.env, platform = process.platform) {
2615
2616
  const config = env.XDG_CONFIG_HOME || join(home, ".config");
@@ -2851,14 +2852,20 @@ function launchToken(env, provider) {
2851
2852
  if (!token?.trim()) throw new Error(`Set ${provider.envVar} in your environment before launching with ${provider.name}.`);
2852
2853
  return token;
2853
2854
  }
2854
- function launchOptions(id, env = process.env, provider = defaultProvider(env), { openCode = 1 } = {}) {
2855
+ function launchOptions(id, env = process.env, provider = defaultProvider(env), { openCodeStandalone = false } = {}) {
2855
2856
  const { model } = provider;
2856
2857
  const token = launchToken(env, provider);
2857
2858
  const childEnv = { ...env };
2858
2859
  switch (id) {
2859
2860
  case "opencode": {
2860
2861
  const selected = `${provider.opencode}/${model}`;
2861
- if (openCode < 2) return {
2862
+ if (openCodeStandalone === null) return {
2863
+ args: [],
2864
+ env: childEnv,
2865
+ note: "(OpenCode interface unknown: no model flag added)",
2866
+ warning: "Apex CLI: Could not ask which OpenCode interface is installed, so it launches without a model flag. OpenCode 1 takes --model, OpenCode 2 takes --standalone; opencode --help says which you have."
2867
+ };
2868
+ if (!openCodeStandalone) return {
2862
2869
  args: ["--model", selected],
2863
2870
  env: childEnv
2864
2871
  };
@@ -2919,7 +2926,7 @@ function launchOptions(id, env = process.env, provider = defaultProvider(env), {
2919
2926
  }
2920
2927
  function runExpansion(id, provider, installed = {}) {
2921
2928
  const { args, note } = launchOptions(id, { [provider.envVar]: "placeholder" }, provider, installed);
2922
- return [`${id} ${args.join(" ")}`, note].filter(Boolean).join(" ");
2929
+ return [[id, ...args].join(" "), note].filter(Boolean).join(" ");
2923
2930
  }
2924
2931
  const copilotModel = (provider) => JSON.stringify({
2925
2932
  id: provider.model,
@@ -4998,10 +5005,12 @@ async function commandInit(rest) {
4998
5005
  dim("Apex CLI never reads, stores or logs the key itself.")
4999
5006
  ]);
5000
5007
  const runnable = active.map((entry) => entry.id).filter((id) => RUNNABLE.includes(id));
5001
- const installed = { openCode: await openCodeMajor(active.find((entry) => entry.id === "opencode")?.binary) };
5008
+ const opencodeBinary = active.find((entry) => entry.id === "opencode")?.binary;
5009
+ const probed = opencodeBinary ? await openCodeSupportsStandalone(opencodeBinary) : void 0;
5010
+ const installed = { openCodeStandalone: probed };
5002
5011
  const rows = runnable.map((id) => ({
5003
5012
  command: `${apex} run ${id}`,
5004
- expansion: runExpansion(id, provider, installed)
5013
+ expansion: id === "opencode" && probed === void 0 ? id : runExpansion(id, provider, installed)
5005
5014
  }));
5006
5015
  const commandWidth = columnWidth(rows.map((row) => row.command));
5007
5016
  section(runnable.length ? `Use these commands to run ${provider.model} with your selected harnesses:` : `Use ${provider.model} with your assistant:`, [
@@ -5195,7 +5204,9 @@ async function commandRun(rest) {
5195
5204
  const binary = await executable(id);
5196
5205
  if (!binary) throw new Error(`${id} was not found on PATH. Install it first, then run ${APEX} init.`);
5197
5206
  if (process.platform === "win32" && /\.(cmd|bat)$/i.test(binary)) throw new Error("Windows .cmd/.bat launchers are not executed through a shell. Use WSL or launch the configured assistant directly.");
5198
- const options = launchOptions(id, process.env, provider, { openCode: await openCodeMajor(id === "opencode" && binary) });
5207
+ const standalone = id === "opencode" ? await openCodeSupportsStandalone(binary) : false;
5208
+ const options = launchOptions(id, process.env, provider, { openCodeStandalone: standalone });
5209
+ if (options.warning) console.error(styleText("yellow", options.warning));
5199
5210
  const args = passed[0] === "--" ? passed.slice(1) : passed;
5200
5211
  const child = spawn(binary, await launchArgs(options, args), {
5201
5212
  env: options.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callstack/apex",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Detects the AI coding assistants installed on your machine and configures them to use Callstack Apex.",
5
5
  "license": "MIT",
6
6
  "keywords": [