@callstack/apex 0.4.3 → 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.
- package/README.md +6 -0
- package/dist/cli.js +71 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -234,6 +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` 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.
|
|
237
243
|
|
|
238
244
|
## Safety and recovery
|
|
239
245
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { isDeepStrictEqual, parseArgs, styleText } from "node:util";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { isDeepStrictEqual, parseArgs, promisify, styleText } from "node:util";
|
|
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";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -2602,6 +2602,16 @@ async function executable(command, env = process.env, platform = process.platfor
|
|
|
2602
2602
|
}
|
|
2603
2603
|
return null;
|
|
2604
2604
|
}
|
|
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
|
+
}
|
|
2614
|
+
}
|
|
2605
2615
|
function locations(home = homedir(), env = process.env, platform = process.platform) {
|
|
2606
2616
|
const config = env.XDG_CONFIG_HOME || join(home, ".config");
|
|
2607
2617
|
const userData = platform === "darwin" ? join(home, "Library", "Application Support") : platform === "win32" ? env.APPDATA || join(home, "AppData", "Roaming") : config;
|
|
@@ -2837,16 +2847,40 @@ async function planAssistant(assistant, provider = PROVIDERS.vercel) {
|
|
|
2837
2847
|
}
|
|
2838
2848
|
return changes;
|
|
2839
2849
|
}
|
|
2840
|
-
function
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
|
|
2850
|
+
function launchToken(env, provider) {
|
|
2851
|
+
const token = env[provider.envVar];
|
|
2852
|
+
if (!token?.trim()) throw new Error(`Set ${provider.envVar} in your environment before launching with ${provider.name}.`);
|
|
2853
|
+
return token;
|
|
2854
|
+
}
|
|
2855
|
+
function launchOptions(id, env = process.env, provider = defaultProvider(env), { openCodeStandalone = false } = {}) {
|
|
2856
|
+
const { model } = provider;
|
|
2857
|
+
const token = launchToken(env, provider);
|
|
2844
2858
|
const childEnv = { ...env };
|
|
2845
2859
|
switch (id) {
|
|
2846
|
-
case "opencode":
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2860
|
+
case "opencode": {
|
|
2861
|
+
const selected = `${provider.opencode}/${model}`;
|
|
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 {
|
|
2869
|
+
args: ["--model", selected],
|
|
2870
|
+
env: childEnv
|
|
2871
|
+
};
|
|
2872
|
+
const inline = env.OPENCODE_CONFIG_CONTENT?.trim() ? parseJson(env.OPENCODE_CONFIG_CONTENT, "OPENCODE_CONFIG_CONTENT") : {};
|
|
2873
|
+
childEnv.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
|
2874
|
+
...inline,
|
|
2875
|
+
model: selected
|
|
2876
|
+
});
|
|
2877
|
+
return {
|
|
2878
|
+
args: ["--standalone"],
|
|
2879
|
+
env: childEnv,
|
|
2880
|
+
afterCommand: true,
|
|
2881
|
+
note: "(OPENCODE_CONFIG_CONTENT model set)"
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2850
2884
|
case "codex": return {
|
|
2851
2885
|
args: ["--profile", provider.codex],
|
|
2852
2886
|
env: childEnv
|
|
@@ -2884,15 +2918,15 @@ function launchOptions(id, env = process.env, provider = defaultProvider(env)) {
|
|
|
2884
2918
|
});
|
|
2885
2919
|
return {
|
|
2886
2920
|
args: ["--model", model],
|
|
2887
|
-
env: childEnv
|
|
2921
|
+
env: childEnv,
|
|
2922
|
+
note: "(ANTHROPIC_* gateway environment set)"
|
|
2888
2923
|
};
|
|
2889
2924
|
default: throw new Error(`Cannot launch ${id}. Use the editor's model selector.`);
|
|
2890
2925
|
}
|
|
2891
2926
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
return [`${id} ${args.join(" ")}`, RUN_NOTE[id]].filter(Boolean).join(" ");
|
|
2927
|
+
function runExpansion(id, provider, installed = {}) {
|
|
2928
|
+
const { args, note } = launchOptions(id, { [provider.envVar]: "placeholder" }, provider, installed);
|
|
2929
|
+
return [[id, ...args].join(" "), note].filter(Boolean).join(" ");
|
|
2896
2930
|
}
|
|
2897
2931
|
const copilotModel = (provider) => JSON.stringify({
|
|
2898
2932
|
id: provider.model,
|
|
@@ -4796,6 +4830,7 @@ async function planAll(assistants, options, provider) {
|
|
|
4796
4830
|
name: NAMES[assistant.id],
|
|
4797
4831
|
detected: assistant.detected,
|
|
4798
4832
|
evidence: assistant.evidence || null,
|
|
4833
|
+
binary: assistant.binary ?? null,
|
|
4799
4834
|
manual: MANUAL_IDS.includes(assistant.id),
|
|
4800
4835
|
error: null,
|
|
4801
4836
|
files: [],
|
|
@@ -4970,9 +5005,12 @@ async function commandInit(rest) {
|
|
|
4970
5005
|
dim("Apex CLI never reads, stores or logs the key itself.")
|
|
4971
5006
|
]);
|
|
4972
5007
|
const runnable = active.map((entry) => entry.id).filter((id) => RUNNABLE.includes(id));
|
|
5008
|
+
const opencodeBinary = active.find((entry) => entry.id === "opencode")?.binary;
|
|
5009
|
+
const probed = opencodeBinary ? await openCodeSupportsStandalone(opencodeBinary) : void 0;
|
|
5010
|
+
const installed = { openCodeStandalone: probed };
|
|
4973
5011
|
const rows = runnable.map((id) => ({
|
|
4974
5012
|
command: `${apex} run ${id}`,
|
|
4975
|
-
expansion: runExpansion(id, provider)
|
|
5013
|
+
expansion: id === "opencode" && probed === void 0 ? id : runExpansion(id, provider, installed)
|
|
4976
5014
|
}));
|
|
4977
5015
|
const commandWidth = columnWidth(rows.map((row) => row.command));
|
|
4978
5016
|
section(runnable.length ? `Use these commands to run ${provider.model} with your selected harnesses:` : `Use ${provider.model} with your assistant:`, [
|
|
@@ -5139,6 +5177,17 @@ function takeProvider(args) {
|
|
|
5139
5177
|
rest: args
|
|
5140
5178
|
};
|
|
5141
5179
|
}
|
|
5180
|
+
const isDirectory = (path) => stat(path).then((info) => info.isDirectory(), () => false);
|
|
5181
|
+
async function launchArgs(options, args) {
|
|
5182
|
+
const [first, ...rest] = args;
|
|
5183
|
+
if (options.afterCommand && ["run", "mini"].includes(first)) return [
|
|
5184
|
+
first,
|
|
5185
|
+
...options.args,
|
|
5186
|
+
...rest
|
|
5187
|
+
];
|
|
5188
|
+
if (options.afterCommand && first && !first.startsWith("-") && !await isDirectory(first)) return args;
|
|
5189
|
+
return [...options.args, ...args];
|
|
5190
|
+
}
|
|
5142
5191
|
async function commandRun(rest) {
|
|
5143
5192
|
const before = takeProvider(rest);
|
|
5144
5193
|
const [id, ...after] = before.rest;
|
|
@@ -5150,12 +5199,16 @@ async function commandRun(rest) {
|
|
|
5150
5199
|
const chosen = before.value ?? behind.value;
|
|
5151
5200
|
if (chosen !== null && !PROVIDER_IDS.includes(chosen)) throw new Error(`Unknown --provider. Choose from: ${PROVIDER_IDS.join(", ")}`);
|
|
5152
5201
|
const passed = behind.rest;
|
|
5153
|
-
const
|
|
5202
|
+
const provider = chosen === null ? defaultProvider() : PROVIDERS[chosen];
|
|
5203
|
+
launchToken(process.env, provider);
|
|
5154
5204
|
const binary = await executable(id);
|
|
5155
5205
|
if (!binary) throw new Error(`${id} was not found on PATH. Install it first, then run ${APEX} init.`);
|
|
5156
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.");
|
|
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));
|
|
5157
5210
|
const args = passed[0] === "--" ? passed.slice(1) : passed;
|
|
5158
|
-
const child = spawn(binary,
|
|
5211
|
+
const child = spawn(binary, await launchArgs(options, args), {
|
|
5159
5212
|
env: options.env,
|
|
5160
5213
|
stdio: "inherit",
|
|
5161
5214
|
shell: false
|