@callstack/apex 0.4.1 → 0.4.3
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 +67 -42
- package/dist/cli.js +362 -150
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# Apex CLI
|
|
2
2
|
|
|
3
3
|
Configures the AI coding assistants on your machine to use
|
|
4
|
-
[Callstack Apex](https://apex.callstack.com/) (`callstack/
|
|
4
|
+
[Callstack Apex](https://apex.callstack.com/) through Vercel AI Gateway (`callstack/apex`) or the
|
|
5
|
+
Callstack portal (`callstack/Apex`).
|
|
5
6
|
|
|
6
|
-
`apex init` detects the assistants on your machine, lets you pick
|
|
7
|
-
shows every change it wants to make, and only then writes. Nothing
|
|
7
|
+
`apex init` asks which gateway serves Apex, detects the assistants on your machine, lets you pick
|
|
8
|
+
which ones to configure, shows every change it wants to make, and only then writes. Nothing
|
|
9
|
+
changes until you say so.
|
|
10
|
+
|
|
11
|
+
## Gateways
|
|
12
|
+
|
|
13
|
+
| Gateway | `--provider` | Model | Key |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| Vercel AI Gateway | `vercel` | `callstack/apex` | `AI_GATEWAY_API_KEY` |
|
|
16
|
+
| Callstack portal | `callstack` | `callstack/Apex` | `CALLSTACK_AUTH_TOKEN` |
|
|
17
|
+
|
|
18
|
+
`apex init` asks which one to use. Without `--provider`, the default is the gateway whose key is
|
|
19
|
+
already set in your shell, and Vercel AI Gateway when neither or both are. `apex detect` and
|
|
20
|
+
`apex run` pick the gateway the same way, so `apex run codex` launches whatever you set up last as
|
|
21
|
+
long as only that key is exported. Each gateway gets its own provider entry in every config, so
|
|
22
|
+
switching gateways adds a provider next to the old one instead of rewriting it.
|
|
8
23
|
|
|
9
24
|
## Install
|
|
10
25
|
|
|
@@ -30,25 +45,26 @@ undo your configs, so run `apex undo` first if you want them back.
|
|
|
30
45
|
| `apex init --apply` | Writes the planned changes without asking | Yes |
|
|
31
46
|
| `apex undo` | Reverts the most recent Apex CLI setup, with the same preview and confirmation | Only after you confirm |
|
|
32
47
|
| `apex undo --list` | Shows recorded setups and which are already undone (`--json` prints the journal) | No |
|
|
33
|
-
| `apex run <assistant> [-- <args>]` | Launches the assistant with the gateway, model and credentials in the child environment | No |
|
|
48
|
+
| `apex run [--provider <id>] <assistant> [-- <args>]` | Launches the assistant with the gateway, model and credentials in the child environment | No |
|
|
34
49
|
| `apex completion <zsh\|bash\|fish>` | Prints a shell completion script | No |
|
|
35
50
|
|
|
36
51
|
Each command accepts only its own flags, and `--help` and the shell completions list exactly those:
|
|
37
52
|
|
|
38
53
|
| Command | Flags |
|
|
39
54
|
| --- | --- |
|
|
40
|
-
| `init` | `--assistants <ids>`, `--no-interactive`, `--apply`, `--no-diff`, `--json`, `--help` |
|
|
41
|
-
| `detect` | `--json`, `--help` |
|
|
55
|
+
| `init` | `--assistants <ids>`, `--provider <id>`, `--no-interactive`, `--apply`, `--no-diff`, `--json`, `--help` |
|
|
56
|
+
| `detect` | `--provider <id>`, `--json`, `--help` |
|
|
57
|
+
| `run` | `--provider <id>`, right before or after the assistant |
|
|
42
58
|
| `undo` | `--no-interactive`, `--apply`, `--no-diff`, `--json`, `--list`, `--help` |
|
|
43
59
|
|
|
44
60
|
`apex init --wat` fails fast, and so does a flag in the wrong command
|
|
45
61
|
(`apex detect --no-diff`). Only `--apply` writes, so there is no "preview *and* write" to argue
|
|
46
62
|
about. `--assistants <ids>` is a comma list of `opencode`, `codex`,
|
|
47
|
-
`claude`, `pi`, `cursor`, `copilot`, `ai-sdk`.
|
|
63
|
+
`claude`, `pi`, `cursor`, `copilot`, `ai-sdk`. `--provider <id>` is `vercel` or `callstack`.
|
|
48
64
|
|
|
49
65
|
Non-interactive callers (`--no-interactive`, `--json`, pipes, CI) never get a prompt and never
|
|
50
66
|
have files changed unless they also pass `--apply`. `--json` prints a machine-readable plan with
|
|
51
|
-
`mode`, `applied`, `appliedPaths`, per-file `changes` and `nextSteps` instead of the human UI. If a
|
|
67
|
+
`mode`, `provider`, `model`, `envVar`, `applied`, `appliedPaths`, per-file `changes` and `nextSteps` instead of the human UI. If a
|
|
52
68
|
write fails halfway through a batch, the payload still reports what already landed plus the `error`,
|
|
53
69
|
and the exit code is 1.
|
|
54
70
|
|
|
@@ -64,15 +80,15 @@ apex completion fish > ~/.config/fish/completions/apex.fish
|
|
|
64
80
|
|
|
65
81
|
```
|
|
66
82
|
┌────────────────────────────────────────────────────────────────────────────┐
|
|
67
|
-
│ Apex CLI · Configure
|
|
83
|
+
│ Apex CLI · Configure Apex for your favorite harness │
|
|
68
84
|
│ │
|
|
69
85
|
│ Nothing is written until you confirm. │
|
|
70
86
|
└────────────────────────────────────────────────────────────────────────────┘
|
|
71
87
|
│
|
|
72
|
-
|
|
73
|
-
│
|
|
88
|
+
◇ Which gateway should your assistants use for Apex?
|
|
89
|
+
│ Vercel AI Gateway
|
|
74
90
|
│
|
|
75
|
-
◇ Set up callstack/
|
|
91
|
+
◇ Set up callstack/apex for which assistants? (space toggles, enter confirms)
|
|
76
92
|
│ OpenCode, Codex
|
|
77
93
|
|
|
78
94
|
|
|
@@ -87,15 +103,15 @@ apex completion fish > ~/.config/fish/completions/apex.fish
|
|
|
87
103
|
- "theme": "dark"
|
|
88
104
|
+ "theme": "dark",
|
|
89
105
|
+ "provider": {
|
|
90
|
-
+ "
|
|
106
|
+
+ "apex-vercel": { … }
|
|
91
107
|
+ }
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
Create ~/.codex/
|
|
110
|
+
Create ~/.codex/apex_vercel.config.toml
|
|
95
111
|
--- /dev/null
|
|
96
|
-
+++ ~/.codex/
|
|
112
|
+
+++ ~/.codex/apex_vercel.config.toml
|
|
97
113
|
@@ -0,0 +1,10 @@
|
|
98
|
-
+model_provider = "
|
|
114
|
+
+model_provider = "apex_vercel"
|
|
99
115
|
|
|
100
116
|
Existing files get a .apex-backup-<id> copy first. (dimmed)
|
|
101
117
|
│
|
|
@@ -105,24 +121,25 @@ apex completion fish > ~/.config/fish/completions/apex.fish
|
|
|
105
121
|
|
|
106
122
|
Environment
|
|
107
123
|
|
|
108
|
-
✓
|
|
124
|
+
✓ AI_GATEWAY_API_KEY is set in this shell
|
|
109
125
|
Apex CLI never reads, stores or logs the key itself.
|
|
110
126
|
|
|
111
127
|
|
|
112
|
-
Use these commands to run callstack/
|
|
128
|
+
Use these commands to run callstack/apex with your selected harnesses:
|
|
113
129
|
|
|
114
|
-
apex run opencode opencode --model
|
|
115
|
-
apex run codex codex --profile
|
|
130
|
+
apex run opencode opencode --model apex-vercel/callstack/apex (dimmed)
|
|
131
|
+
apex run codex codex --profile apex_vercel
|
|
116
132
|
|
|
117
|
-
...or pick "callstack/
|
|
118
|
-
For more instructions, visit:
|
|
119
|
-
https://app.notion.com/p/callstack/Apex-how-to-use-it-36d5d027c0f880e99d03d1c37a77382f
|
|
133
|
+
...or pick "callstack/apex" from the UI when setting up manually.
|
|
134
|
+
For more instructions, visit: https://vercel.com/docs/ai-gateway/coding-agents
|
|
120
135
|
|
|
121
136
|
If you want to undo the changes, run apex undo
|
|
122
137
|
```
|
|
123
|
-
`Environment` only shows the `export
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
`Environment` only shows the `export AI_GATEWAY_API_KEY=…` line (and, for Vercel AI Gateway, where to
|
|
139
|
+
create a key) while the variable is missing; once it is set you get a green
|
|
140
|
+
`✓ AI_GATEWAY_API_KEY is set in this shell` instead. The Callstack portal shows `CALLSTACK_AUTH_TOKEN`. The closing block shows
|
|
141
|
+
what each `apex run` expands to, links the gateway's guide (Vercel's coding-agents docs or the Callstack portal guide; the URL is never
|
|
142
|
+
folded, so it stays clickable), and
|
|
126
143
|
mentions `apex undo` only when something was actually written.
|
|
127
144
|
|
|
128
145
|
The diff is the change list: it only shows the lines that actually move, paths are shortened to
|
|
@@ -143,15 +160,17 @@ their own backup first, so an undo is itself reversible. Apex CLI records each b
|
|
|
143
160
|
|
|
144
161
|
## Integrations
|
|
145
162
|
|
|
163
|
+
Names below are Vercel AI Gateway / Callstack portal.
|
|
164
|
+
|
|
146
165
|
| Assistant | Configuration | Behavior |
|
|
147
166
|
| --- | --- | --- |
|
|
148
|
-
| OpenCode | `~/.config/opencode/opencode.json` or `.jsonc` | Adds `provider.callstack.ai`, OpenAI-compatible transport, model and environment-key reference; preserves the default model. A config already in OpenCode 2's `providers` format gets the v2 entry instead, and you add the key with `/connect`. |
|
|
149
|
-
| Codex 0.134.0+ | `~/.codex/callstack_ai.config.toml` | Adds a self-contained Responses API provider/profile; leaves base config and default model untouched. |
|
|
150
|
-
| Claude Code | `~/.claude/settings.json` | Merges attribution flag only; use `apex run claude` for gateway credentials and model selection. |
|
|
151
|
-
| Pi | `~/.pi/agent/models.json` and `settings.json` | Adds `providers.callstack` with Chat Completions, Apex and `$CALLSTACK_AUTH_TOKEN`; saves `medium` as Apex's per-model thinking default for direct launches. Preserves other models and their defaults. Requires a Pi version supporting `$VAR` key interpolation and `modelThinkingLevels`. |
|
|
152
|
-
| Cursor | Guided setup | Prints endpoint, API-key and custom-model steps; does not modify private editor storage. |
|
|
167
|
+
| OpenCode | `~/.config/opencode/opencode.json` or `.jsonc` | Adds `provider.apex-vercel` / `provider.callstack.ai`, OpenAI-compatible transport, model and environment-key reference; preserves the default model. A config already in OpenCode 2's `providers` format gets the v2 entry instead, and you add the key with `/connect`. |
|
|
168
|
+
| Codex 0.134.0+ | `~/.codex/apex_vercel.config.toml` / `callstack_ai.config.toml` | Adds a self-contained Responses API provider/profile (Vercel's Codex endpoint `/codex/v1`); leaves base config and default model untouched. |
|
|
169
|
+
| Claude Code | `~/.claude/settings.json` | Merges attribution flag only; use `apex run claude` for gateway credentials and model selection (Vercel's Claude Code endpoint `/claude-code`). |
|
|
170
|
+
| Pi | `~/.pi/agent/models.json` and `settings.json` | Adds `providers.apex-vercel` / `providers.callstack` with Chat Completions, Apex and `$AI_GATEWAY_API_KEY` / `$CALLSTACK_AUTH_TOKEN`; saves `medium` as Apex's per-model thinking default for direct launches. Preserves other models and their defaults. Requires a Pi version supporting `$VAR` key interpolation and `modelThinkingLevels`. |
|
|
171
|
+
| Cursor | Guided setup | Prints endpoint (Vercel's Cursor endpoint `/cursor/v1`), API-key and custom-model steps; does not modify private editor storage. |
|
|
153
172
|
| VS Code / Copilot | Guided setup | Detects VS Code, not whether Copilot is installed; prints custom-endpoint steps and model JSON, retaining the editor-generated secret reference. |
|
|
154
|
-
| Vercel AI SDK / Eve | Guided setup | Detected from the `package.json` in the directory you run Apex CLI from (`ai`, `@ai-sdk/openai` or `eve`); prints the connector snippet, which reads the key from `CALLSTACK_AUTH_TOKEN
|
|
173
|
+
| Vercel AI SDK / Eve | Guided setup | Detected from the `package.json` in the directory you run Apex CLI from (`ai`, `@ai-sdk/openai` or `eve`); prints the connector snippet, which reads the key from `AI_GATEWAY_API_KEY` or `CALLSTACK_AUTH_TOKEN` (Vercel AI Gateway uses its general `/v1` endpoint here, since an app is not a coding agent). Nothing is written into the project. |
|
|
155
174
|
|
|
156
175
|
Apex configs declare tool calling, image input, and the supported
|
|
157
176
|
`none`/`low`/`medium`/`xhigh` reasoning efforts (default `medium`) where supported.
|
|
@@ -163,8 +182,8 @@ reserve.
|
|
|
163
182
|
Pi's per-model default applies to fresh direct launches and model selection; `apex run pi` also
|
|
164
183
|
selects `medium` explicitly. Resumed Pi sessions retain their saved thinking level.
|
|
165
184
|
|
|
166
|
-
The
|
|
167
|
-
|
|
185
|
+
The model ID and key depend on the gateway you pick; the `medium` default and `off` → `none`
|
|
186
|
+
translation are Apex-specific.
|
|
168
187
|
|
|
169
188
|
| Harness | Context field | Output field | Official reference |
|
|
170
189
|
| --- | --- | --- | --- |
|
|
@@ -197,16 +216,20 @@ avoid shell interpolation; launch the configured assistant directly or use WSL.
|
|
|
197
216
|
|
|
198
217
|
## Authentication
|
|
199
218
|
|
|
200
|
-
Provide `
|
|
201
|
-
|
|
202
|
-
|
|
219
|
+
Provide `AI_GATEWAY_API_KEY` (Vercel AI Gateway) or `CALLSTACK_AUTH_TOKEN` (Callstack portal)
|
|
220
|
+
through your shell or a secret manager. Create a Vercel key on the AI Gateway page of your Vercel
|
|
221
|
+
dashboard. Apex CLI never prompts for, logs, stores or validates the key, and never writes it to a
|
|
222
|
+
config file; configs only reference it. A hidden prompt keeps it out of your shell history:
|
|
203
223
|
|
|
204
224
|
```bash
|
|
205
|
-
read -r -s -p '
|
|
206
|
-
export
|
|
225
|
+
read -r -s -p 'AI Gateway API key: ' AI_GATEWAY_API_KEY; echo
|
|
226
|
+
export AI_GATEWAY_API_KEY
|
|
207
227
|
apex run codex
|
|
208
228
|
```
|
|
209
229
|
|
|
230
|
+
With both keys exported, `apex run` uses Vercel AI Gateway; pass `--provider callstack` for the
|
|
231
|
+
portal: `apex run --provider callstack codex`.
|
|
232
|
+
|
|
210
233
|
Other launchers: `apex run opencode`, `apex run claude`, `apex run pi`, `apex run codex -- --help`.
|
|
211
234
|
Launchers pass extra arguments through unchanged (without a shell), can be overridden by explicit
|
|
212
235
|
user arguments, and never run `init`. `apex run claude` sets the gateway URL, token, attribution
|
|
@@ -221,7 +244,7 @@ Apex CLI will not break what you already have.
|
|
|
221
244
|
- It copies a file next to itself before it edits that file, so you can always put the old one back.
|
|
222
245
|
- It keeps your comments, your other settings, and your default model.
|
|
223
246
|
- It never asks for your API key, writes it down, or sends it anywhere. You keep it in
|
|
224
|
-
`CALLSTACK_AUTH_TOKEN`, and the CLI prints `<redacted>` wherever a secret would show.
|
|
247
|
+
`AI_GATEWAY_API_KEY` or `CALLSTACK_AUTH_TOKEN`, and the CLI prints `<redacted>` wherever a secret would show.
|
|
225
248
|
- If a config file is broken, set up another way, or a link to somewhere else, Apex CLI stops and
|
|
226
249
|
tells you. It does not overwrite it.
|
|
227
250
|
- `apex undo` gives your old setup back. It only touches files that still match what Apex CLI wrote.
|
|
@@ -235,10 +258,10 @@ How that works underneath:
|
|
|
235
258
|
then land in your dotfiles repo, so add `*.apex-backup-*` to its `.gitignore`.
|
|
236
259
|
- The dedicated Codex Apex profile's owned keys are upgraded rather than rejected as conflicts;
|
|
237
260
|
the base `config.toml` and other profiles remain untouched.
|
|
238
|
-
- JSON/JSONC edits preserve comments and unrelated keys. The
|
|
261
|
+
- JSON/JSONC edits preserve comments and unrelated keys. The gateway endpoint is set to the
|
|
239
262
|
documented one. A key the assistant already has (in OpenCode's own key store, or in Pi's
|
|
240
263
|
`models.json`, where Apex CLI 0.2 put them) is kept, so upgrading never leaves an assistant
|
|
241
|
-
without a key; otherwise the config references `CALLSTACK_AUTH_TOKEN`.
|
|
264
|
+
without a key; otherwise the config references `AI_GATEWAY_API_KEY` or `CALLSTACK_AUTH_TOKEN`.
|
|
242
265
|
- Writes use same-directory temporary files plus rename with mode `0600` (new directories `0700`),
|
|
243
266
|
and abort if the file changed on disk between planning and saving. This is not a lock or a
|
|
244
267
|
multi-file transaction: a later I/O failure can leave earlier writes applied. `apex undo`
|
|
@@ -255,6 +278,8 @@ How that works underneath:
|
|
|
255
278
|
(separate profile files for 0.134.0+; provider is placed in that profile).
|
|
256
279
|
- OpenCode v1 configuration: `https://dev.opencode.ai/docs/config/`
|
|
257
280
|
(JSONC and `{env:VARIABLE}` support).
|
|
281
|
+
- Vercel AI Gateway for coding agents: `https://vercel.com/docs/ai-gateway/coding-agents`
|
|
282
|
+
(per-agent endpoints; Apex is listed as `callstack/apex`).
|
|
258
283
|
- Pi custom models: `https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/models.md`
|
|
259
284
|
(`$VARIABLE` key interpolation; bare uppercase names are literals in current docs).
|
|
260
285
|
|
package/dist/cli.js
CHANGED
|
@@ -2451,9 +2451,50 @@ async function writeChange(change) {
|
|
|
2451
2451
|
|
|
2452
2452
|
//#endregion
|
|
2453
2453
|
//#region src/assistants.js
|
|
2454
|
-
const
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2454
|
+
const VERCEL = "https://ai-gateway.vercel.sh";
|
|
2455
|
+
const PROVIDERS = {
|
|
2456
|
+
vercel: {
|
|
2457
|
+
id: "vercel",
|
|
2458
|
+
name: "Vercel AI Gateway",
|
|
2459
|
+
label: "Vercel AI Gateway",
|
|
2460
|
+
model: "callstack/apex",
|
|
2461
|
+
envVar: "AI_GATEWAY_API_KEY",
|
|
2462
|
+
keyName: "your Vercel AI Gateway API key",
|
|
2463
|
+
keysUrl: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway&title=AI+Gateway",
|
|
2464
|
+
guideUrl: "https://vercel.com/docs/ai-gateway/coding-agents",
|
|
2465
|
+
baseUrl: `${VERCEL}/coding-agent/v1`,
|
|
2466
|
+
codexUrl: `${VERCEL}/codex/v1`,
|
|
2467
|
+
cursorUrl: `${VERCEL}/cursor/v1`,
|
|
2468
|
+
anthropicUrl: `${VERCEL}/claude-code`,
|
|
2469
|
+
appUrl: `${VERCEL}/v1`,
|
|
2470
|
+
opencode: "apex-vercel",
|
|
2471
|
+
pi: "apex-vercel",
|
|
2472
|
+
codex: "apex_vercel"
|
|
2473
|
+
},
|
|
2474
|
+
callstack: {
|
|
2475
|
+
id: "callstack",
|
|
2476
|
+
name: "Callstack portal",
|
|
2477
|
+
label: "callstack.ai",
|
|
2478
|
+
model: "callstack/Apex",
|
|
2479
|
+
envVar: "CALLSTACK_AUTH_TOKEN",
|
|
2480
|
+
keyName: "your callstack.ai key",
|
|
2481
|
+
keysUrl: null,
|
|
2482
|
+
guideUrl: "https://app.notion.com/p/callstack/Apex-how-to-use-it-36d5d027c0f880e99d03d1c37a77382f",
|
|
2483
|
+
baseUrl: "https://api.callstack.ai/v1",
|
|
2484
|
+
codexUrl: "https://api.callstack.ai/v1",
|
|
2485
|
+
cursorUrl: "https://api.callstack.ai/v1",
|
|
2486
|
+
anthropicUrl: "https://api.callstack.ai",
|
|
2487
|
+
appUrl: "https://api.callstack.ai/v1",
|
|
2488
|
+
opencode: "callstack.ai",
|
|
2489
|
+
pi: "callstack",
|
|
2490
|
+
codex: "callstack_ai"
|
|
2491
|
+
}
|
|
2492
|
+
};
|
|
2493
|
+
const PROVIDER_IDS = Object.keys(PROVIDERS);
|
|
2494
|
+
function defaultProvider(env = process.env) {
|
|
2495
|
+
const keyed = PROVIDER_IDS.filter((id) => env[PROVIDERS[id].envVar]?.trim());
|
|
2496
|
+
return PROVIDERS[keyed.length === 1 ? keyed[0] : "vercel"];
|
|
2497
|
+
}
|
|
2457
2498
|
const IDS = [
|
|
2458
2499
|
"opencode",
|
|
2459
2500
|
"codex",
|
|
@@ -2502,30 +2543,25 @@ const OPENCODE_MODEL = {
|
|
|
2502
2543
|
options: { reasoningEffort: "medium" },
|
|
2503
2544
|
variants: Object.fromEntries(EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
|
2504
2545
|
};
|
|
2505
|
-
const
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
settings: { reasoningEffort:
|
|
2521
|
-
|
|
2522
|
-
id: effort,
|
|
2523
|
-
settings: { reasoningEffort: effort }
|
|
2524
|
-
}))
|
|
2525
|
-
} }
|
|
2546
|
+
const OPENCODE_V2_PACKAGE = "@opencode/ai/providers/openai-compatible";
|
|
2547
|
+
const OPENCODE_V2_MODEL = {
|
|
2548
|
+
name: "Apex",
|
|
2549
|
+
capabilities: {
|
|
2550
|
+
tools: true,
|
|
2551
|
+
input: ["text", "image"],
|
|
2552
|
+
output: ["text"]
|
|
2553
|
+
},
|
|
2554
|
+
limit: {
|
|
2555
|
+
context: CONTEXT_WINDOW,
|
|
2556
|
+
output: MAX_OUTPUT
|
|
2557
|
+
},
|
|
2558
|
+
settings: { reasoningEffort: "medium" },
|
|
2559
|
+
variants: EFFORTS.map((effort) => ({
|
|
2560
|
+
id: effort,
|
|
2561
|
+
settings: { reasoningEffort: effort }
|
|
2562
|
+
}))
|
|
2526
2563
|
};
|
|
2527
2564
|
const PI_MODEL = {
|
|
2528
|
-
id: MODEL,
|
|
2529
2565
|
reasoning: true,
|
|
2530
2566
|
input: ["text", "image"],
|
|
2531
2567
|
thinkingLevelMap: {
|
|
@@ -2632,16 +2668,17 @@ async function detect(options = {}) {
|
|
|
2632
2668
|
};
|
|
2633
2669
|
}));
|
|
2634
2670
|
}
|
|
2635
|
-
async function openCodeHasKey(authFile) {
|
|
2671
|
+
async function openCodeHasKey(authFile, providerId) {
|
|
2636
2672
|
if (!authFile) return false;
|
|
2637
2673
|
try {
|
|
2638
|
-
return Object.hasOwn(JSON.parse(await readConfig(authFile) ?? "{}"),
|
|
2674
|
+
return Object.hasOwn(JSON.parse(await readConfig(authFile) ?? "{}"), providerId);
|
|
2639
2675
|
} catch {
|
|
2640
2676
|
return false;
|
|
2641
2677
|
}
|
|
2642
2678
|
}
|
|
2643
2679
|
const PI_PLACEHOLDER = "XXX";
|
|
2644
|
-
async function planAssistant(assistant) {
|
|
2680
|
+
async function planAssistant(assistant, provider = PROVIDERS.vercel) {
|
|
2681
|
+
const { model, envVar } = provider;
|
|
2645
2682
|
const changes = [];
|
|
2646
2683
|
const json = async (filename, updates) => {
|
|
2647
2684
|
const path = join(assistant.directory, filename);
|
|
@@ -2660,124 +2697,131 @@ async function planAssistant(assistant) {
|
|
|
2660
2697
|
const hasJsonc = await exists(join(assistant.directory, "opencode.jsonc"));
|
|
2661
2698
|
if (hasJson && hasJsonc) throw new Error("Both opencode.json and opencode.jsonc exist. Consolidate them before init.");
|
|
2662
2699
|
let v2 = false;
|
|
2663
|
-
const
|
|
2700
|
+
const id = provider.opencode;
|
|
2701
|
+
const storedKey = await openCodeHasKey(assistant.authFile, id);
|
|
2664
2702
|
await json(hasJsonc ? "opencode.jsonc" : "opencode.json", (existing) => {
|
|
2665
2703
|
v2 = Object.hasOwn(existing, "providers");
|
|
2666
2704
|
if (v2) return [
|
|
2667
2705
|
[[
|
|
2668
2706
|
"providers",
|
|
2669
|
-
|
|
2707
|
+
id,
|
|
2670
2708
|
"name"
|
|
2671
|
-
],
|
|
2709
|
+
], provider.label],
|
|
2672
2710
|
[[
|
|
2673
2711
|
"providers",
|
|
2674
|
-
|
|
2712
|
+
id,
|
|
2675
2713
|
"package"
|
|
2676
|
-
],
|
|
2714
|
+
], OPENCODE_V2_PACKAGE],
|
|
2677
2715
|
[[
|
|
2678
2716
|
"providers",
|
|
2679
|
-
|
|
2717
|
+
id,
|
|
2680
2718
|
"settings",
|
|
2681
2719
|
"baseURL"
|
|
2682
|
-
],
|
|
2683
|
-
...Object.entries(
|
|
2720
|
+
], provider.baseUrl],
|
|
2721
|
+
...Object.entries(OPENCODE_V2_MODEL).map(([key, value]) => [[
|
|
2684
2722
|
"providers",
|
|
2685
|
-
|
|
2723
|
+
id,
|
|
2686
2724
|
"models",
|
|
2687
|
-
|
|
2725
|
+
model,
|
|
2688
2726
|
key
|
|
2689
2727
|
], key === "settings" ? {
|
|
2690
|
-
...existing.providers?.[
|
|
2728
|
+
...existing.providers?.[id]?.models?.[model]?.settings,
|
|
2691
2729
|
...value
|
|
2692
2730
|
} : value])
|
|
2693
2731
|
];
|
|
2694
2732
|
return [
|
|
2695
2733
|
[[
|
|
2696
2734
|
"provider",
|
|
2697
|
-
|
|
2735
|
+
id,
|
|
2698
2736
|
"npm"
|
|
2699
2737
|
], "@ai-sdk/openai-compatible"],
|
|
2700
2738
|
[[
|
|
2701
2739
|
"provider",
|
|
2702
|
-
|
|
2740
|
+
id,
|
|
2703
2741
|
"name"
|
|
2704
|
-
],
|
|
2742
|
+
], provider.label],
|
|
2705
2743
|
[[
|
|
2706
2744
|
"provider",
|
|
2707
|
-
|
|
2745
|
+
id,
|
|
2708
2746
|
"options",
|
|
2709
2747
|
"baseURL"
|
|
2710
|
-
],
|
|
2711
|
-
...storedKey || existing.provider?.[
|
|
2748
|
+
], provider.baseUrl],
|
|
2749
|
+
...storedKey || existing.provider?.[id]?.options?.apiKey ? [] : [[[
|
|
2712
2750
|
"provider",
|
|
2713
|
-
|
|
2751
|
+
id,
|
|
2714
2752
|
"options",
|
|
2715
2753
|
"apiKey"
|
|
2716
|
-
],
|
|
2754
|
+
], `{env:${envVar}}`]],
|
|
2717
2755
|
...Object.entries(OPENCODE_MODEL).map(([key, value]) => [[
|
|
2718
2756
|
"provider",
|
|
2719
|
-
|
|
2757
|
+
id,
|
|
2720
2758
|
"models",
|
|
2721
|
-
|
|
2759
|
+
model,
|
|
2722
2760
|
key
|
|
2723
2761
|
], key === "options" ? {
|
|
2724
|
-
...existing.provider?.[
|
|
2762
|
+
...existing.provider?.[id]?.models?.[model]?.options,
|
|
2725
2763
|
...value
|
|
2726
2764
|
} : value])
|
|
2727
2765
|
];
|
|
2728
2766
|
});
|
|
2729
|
-
if (v2) changes[0].steps = [
|
|
2767
|
+
if (v2) changes[0].steps = [`OpenCode 2 stores provider keys itself: run /connect in OpenCode, pick ${provider.label} → "Manually enter API Key", and paste ${provider.keyName}.`];
|
|
2730
2768
|
break;
|
|
2731
2769
|
}
|
|
2732
|
-
case "pi":
|
|
2770
|
+
case "pi": {
|
|
2771
|
+
const id = provider.pi;
|
|
2772
|
+
const entry = {
|
|
2773
|
+
id: model,
|
|
2774
|
+
...PI_MODEL
|
|
2775
|
+
};
|
|
2733
2776
|
await json("models.json", (existing) => {
|
|
2734
|
-
const models = existing.providers?.
|
|
2735
|
-
const key = existing.providers?.
|
|
2736
|
-
if (!Array.isArray(models) || models.some((
|
|
2777
|
+
const models = existing.providers?.[id]?.models ?? [];
|
|
2778
|
+
const key = existing.providers?.[id]?.apiKey;
|
|
2779
|
+
if (!Array.isArray(models) || models.some((item) => !item || typeof item.id !== "string")) throw new Error("Expected Pi models to be an array of model objects.");
|
|
2737
2780
|
return [
|
|
2738
2781
|
[[
|
|
2739
2782
|
"providers",
|
|
2740
|
-
|
|
2783
|
+
id,
|
|
2741
2784
|
"baseUrl"
|
|
2742
|
-
],
|
|
2785
|
+
], provider.baseUrl],
|
|
2743
2786
|
[[
|
|
2744
2787
|
"providers",
|
|
2745
|
-
|
|
2788
|
+
id,
|
|
2746
2789
|
"api"
|
|
2747
2790
|
], "openai-completions"],
|
|
2748
2791
|
...key && key !== PI_PLACEHOLDER ? [] : [[[
|
|
2749
2792
|
"providers",
|
|
2750
|
-
|
|
2793
|
+
id,
|
|
2751
2794
|
"apiKey"
|
|
2752
|
-
],
|
|
2795
|
+
], `$${envVar}`]],
|
|
2753
2796
|
[[
|
|
2754
2797
|
"providers",
|
|
2755
|
-
|
|
2798
|
+
id,
|
|
2756
2799
|
"models"
|
|
2757
|
-
], models.some((
|
|
2758
|
-
...
|
|
2759
|
-
...
|
|
2760
|
-
...
|
|
2761
|
-
} :
|
|
2800
|
+
], models.some((item) => item.id === model) ? models.map((item) => item.id === model ? {
|
|
2801
|
+
...item,
|
|
2802
|
+
...entry,
|
|
2803
|
+
...item.compat ? { compat: Object.fromEntries(Object.entries(item.compat).filter(([key]) => key !== "reasoningEffortMap")) } : {}
|
|
2804
|
+
} : item) : [...models, entry]]
|
|
2762
2805
|
];
|
|
2763
2806
|
});
|
|
2764
|
-
await json("settings.json", [[["modelThinkingLevels",
|
|
2807
|
+
await json("settings.json", [[["modelThinkingLevels", `${id}/${model}`], "medium"]]);
|
|
2765
2808
|
break;
|
|
2809
|
+
}
|
|
2766
2810
|
case "claude":
|
|
2767
2811
|
await json("settings.json", [[["env", "CLAUDE_CODE_ATTRIBUTION_HEADER"], "0"]]);
|
|
2768
2812
|
break;
|
|
2769
2813
|
case "codex": {
|
|
2770
|
-
const path = join(assistant.directory,
|
|
2814
|
+
const path = join(assistant.directory, `${provider.codex}.config.toml`);
|
|
2771
2815
|
const before = await readConfig(path);
|
|
2772
2816
|
const after = updateToml(before, {
|
|
2773
|
-
model_provider:
|
|
2774
|
-
model
|
|
2817
|
+
model_provider: provider.codex,
|
|
2818
|
+
model,
|
|
2775
2819
|
model_context_window: CONTEXT_WINDOW,
|
|
2776
2820
|
model_reasoning_effort: "medium",
|
|
2777
|
-
model_providers: {
|
|
2778
|
-
name:
|
|
2779
|
-
base_url:
|
|
2780
|
-
env_key:
|
|
2821
|
+
model_providers: { [provider.codex]: {
|
|
2822
|
+
name: provider.label,
|
|
2823
|
+
base_url: provider.codexUrl,
|
|
2824
|
+
env_key: envVar,
|
|
2781
2825
|
wire_api: "responses",
|
|
2782
2826
|
requires_openai_auth: false
|
|
2783
2827
|
} }
|
|
@@ -2793,25 +2837,26 @@ async function planAssistant(assistant) {
|
|
|
2793
2837
|
}
|
|
2794
2838
|
return changes;
|
|
2795
2839
|
}
|
|
2796
|
-
function launchOptions(id, env = process.env) {
|
|
2797
|
-
const
|
|
2798
|
-
|
|
2840
|
+
function launchOptions(id, env = process.env, provider = defaultProvider(env)) {
|
|
2841
|
+
const { model, envVar } = provider;
|
|
2842
|
+
const token = env[envVar];
|
|
2843
|
+
if (!token?.trim()) throw new Error(`Set ${envVar} in your environment before launching with ${provider.name}.`);
|
|
2799
2844
|
const childEnv = { ...env };
|
|
2800
2845
|
switch (id) {
|
|
2801
2846
|
case "opencode": return {
|
|
2802
|
-
args: ["--model",
|
|
2847
|
+
args: ["--model", `${provider.opencode}/${model}`],
|
|
2803
2848
|
env: childEnv
|
|
2804
2849
|
};
|
|
2805
2850
|
case "codex": return {
|
|
2806
|
-
args: ["--profile",
|
|
2851
|
+
args: ["--profile", provider.codex],
|
|
2807
2852
|
env: childEnv
|
|
2808
2853
|
};
|
|
2809
2854
|
case "pi": return {
|
|
2810
2855
|
args: [
|
|
2811
2856
|
"--provider",
|
|
2812
|
-
|
|
2857
|
+
provider.pi,
|
|
2813
2858
|
"--model",
|
|
2814
|
-
|
|
2859
|
+
model,
|
|
2815
2860
|
"--thinking",
|
|
2816
2861
|
"medium"
|
|
2817
2862
|
],
|
|
@@ -2826,33 +2871,33 @@ function launchOptions(id, env = process.env) {
|
|
|
2826
2871
|
"CLAUDE_CODE_USE_FOUNDRY"
|
|
2827
2872
|
]) delete childEnv[key];
|
|
2828
2873
|
Object.assign(childEnv, {
|
|
2829
|
-
ANTHROPIC_BASE_URL:
|
|
2874
|
+
ANTHROPIC_BASE_URL: provider.anthropicUrl,
|
|
2830
2875
|
ANTHROPIC_AUTH_TOKEN: token,
|
|
2831
|
-
ANTHROPIC_MODEL:
|
|
2876
|
+
ANTHROPIC_MODEL: model,
|
|
2832
2877
|
CLAUDE_CODE_ATTRIBUTION_HEADER: "0",
|
|
2833
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL:
|
|
2834
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL:
|
|
2835
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL:
|
|
2836
|
-
CLAUDE_CODE_SUBAGENT_MODEL:
|
|
2878
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
|
2879
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
|
2880
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
|
|
2881
|
+
CLAUDE_CODE_SUBAGENT_MODEL: model,
|
|
2837
2882
|
CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(CONTEXT_WINDOW),
|
|
2838
2883
|
CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(MAX_OUTPUT)
|
|
2839
2884
|
});
|
|
2840
2885
|
return {
|
|
2841
|
-
args: ["--model",
|
|
2886
|
+
args: ["--model", model],
|
|
2842
2887
|
env: childEnv
|
|
2843
2888
|
};
|
|
2844
2889
|
default: throw new Error(`Cannot launch ${id}. Use the editor's model selector.`);
|
|
2845
2890
|
}
|
|
2846
2891
|
}
|
|
2847
2892
|
const RUN_NOTE = { claude: "(ANTHROPIC_* gateway environment set)" };
|
|
2848
|
-
function runExpansion(id) {
|
|
2849
|
-
const { args } = launchOptions(id, {
|
|
2893
|
+
function runExpansion(id, provider) {
|
|
2894
|
+
const { args } = launchOptions(id, { [provider.envVar]: "placeholder" }, provider);
|
|
2850
2895
|
return [`${id} ${args.join(" ")}`, RUN_NOTE[id]].filter(Boolean).join(" ");
|
|
2851
2896
|
}
|
|
2852
|
-
const
|
|
2853
|
-
id:
|
|
2897
|
+
const copilotModel = (provider) => JSON.stringify({
|
|
2898
|
+
id: provider.model,
|
|
2854
2899
|
name: "Apex",
|
|
2855
|
-
url:
|
|
2900
|
+
url: provider.baseUrl,
|
|
2856
2901
|
toolCalling: true,
|
|
2857
2902
|
vision: true,
|
|
2858
2903
|
thinking: true,
|
|
@@ -2861,32 +2906,39 @@ const COPILOT_MODEL = JSON.stringify({
|
|
|
2861
2906
|
supportsReasoningEffort: EFFORTS,
|
|
2862
2907
|
reasoningEffortFormat: "chat-completions"
|
|
2863
2908
|
}, null, 2);
|
|
2864
|
-
const
|
|
2909
|
+
const aiSdkSnippet = (provider) => [
|
|
2865
2910
|
"import { createOpenAI } from '@ai-sdk/openai';",
|
|
2866
2911
|
"",
|
|
2867
2912
|
"const apex = createOpenAI({",
|
|
2868
2913
|
" name: 'callstack',",
|
|
2869
|
-
` baseURL: '${
|
|
2870
|
-
|
|
2914
|
+
` baseURL: '${provider.appUrl}',`,
|
|
2915
|
+
` apiKey: process.env.${provider.envVar},`,
|
|
2871
2916
|
"});",
|
|
2872
2917
|
"const requestOptions = {",
|
|
2873
|
-
` model: apex('${
|
|
2918
|
+
` model: apex('${provider.model}'),`,
|
|
2874
2919
|
` maxOutputTokens: ${MAX_OUTPUT},`,
|
|
2875
2920
|
"};",
|
|
2876
2921
|
"// Pass requestOptions with your prompt to generateText / streamText.",
|
|
2877
2922
|
`// Reasoning effort (${EFFORTS.join(", ")}):`,
|
|
2878
2923
|
"// providerOptions: { callstack: { reasoningEffort: 'medium' } }"
|
|
2879
2924
|
];
|
|
2880
|
-
const
|
|
2881
|
-
cursor
|
|
2882
|
-
copilot
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2925
|
+
const MANUAL_IDS = [
|
|
2926
|
+
"cursor",
|
|
2927
|
+
"copilot",
|
|
2928
|
+
"ai-sdk"
|
|
2929
|
+
];
|
|
2930
|
+
function manualSteps(id, provider) {
|
|
2931
|
+
switch (id) {
|
|
2932
|
+
case "cursor": return [`Cursor: Settings → Models → API Keys → OpenAI API Key. Enter ${provider.keyName}, override the base URL with ${provider.cursorUrl}, add and enable ${provider.model}, then select it in Agent.`, "Cursor has no way to declare what a custom model can do (tool calling, vision, reasoning); it infers that itself."];
|
|
2933
|
+
case "copilot": return [
|
|
2934
|
+
`Installation of Copilot itself is not verified: Copilot → model selector → Manage Models → Add Models → Custom Endpoint → name ${provider.label} → enter ${provider.keyName} → Chat Completions.`,
|
|
2935
|
+
"Keep the generated apiKey secret reference; add this object to its models array:",
|
|
2936
|
+
...copilotModel(provider).split("\n")
|
|
2937
|
+
];
|
|
2938
|
+
case "ai-sdk": return [`Apex CLI never writes your key into a project. Keep it in ${provider.envVar} (for example in a .env that git ignores) and create the provider with:`, ...aiSdkSnippet(provider)];
|
|
2939
|
+
default: throw new Error(`${id} is set up automatically.`);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2890
2942
|
|
|
2891
2943
|
//#endregion
|
|
2892
2944
|
//#region node_modules/fast-string-truncated-width/dist/utils.js
|
|
@@ -3585,6 +3637,32 @@ var a = class extends y {
|
|
|
3585
3637
|
});
|
|
3586
3638
|
}
|
|
3587
3639
|
};
|
|
3640
|
+
let n$1 = class n extends y {
|
|
3641
|
+
options;
|
|
3642
|
+
cursor = 0;
|
|
3643
|
+
get _selectedValue() {
|
|
3644
|
+
return this.options[this.cursor];
|
|
3645
|
+
}
|
|
3646
|
+
changeValue() {
|
|
3647
|
+
const e = this._selectedValue;
|
|
3648
|
+
this.value = e === void 0 ? void 0 : e.value;
|
|
3649
|
+
}
|
|
3650
|
+
constructor(e) {
|
|
3651
|
+
super(e, false), this.options = e.options;
|
|
3652
|
+
const o = this.options.findIndex(({ value: s }) => s === e.initialValue), t = o === -1 ? 0 : o;
|
|
3653
|
+
this.cursor = this.options[t]?.disabled ? findCursor(t, 1, this.options) : t, this.changeValue(), this.on("cursor", (s) => {
|
|
3654
|
+
switch (s) {
|
|
3655
|
+
case "left":
|
|
3656
|
+
case "up":
|
|
3657
|
+
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
3658
|
+
break;
|
|
3659
|
+
case "down":
|
|
3660
|
+
case "right": this.cursor = findCursor(this.cursor, 1, this.options);
|
|
3661
|
+
}
|
|
3662
|
+
this.changeValue();
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
};
|
|
3588
3666
|
|
|
3589
3667
|
//#endregion
|
|
3590
3668
|
//#region node_modules/@clack/prompts/dist/index.mjs
|
|
@@ -3816,6 +3894,63 @@ const u = {
|
|
|
3816
3894
|
block: unicodeOr("█", "#")
|
|
3817
3895
|
};
|
|
3818
3896
|
const SELECT_INSTRUCTIONS = [`${styleText("dim", "↑/↓")} to navigate`, `${styleText("dim", "Enter:")} confirm`];
|
|
3897
|
+
const c = (t, o) => t.includes(`
|
|
3898
|
+
`) ? t.split(`
|
|
3899
|
+
`).map((d) => o(d)).join(`
|
|
3900
|
+
`) : o(t);
|
|
3901
|
+
const select = (t) => {
|
|
3902
|
+
const o = (n, m) => {
|
|
3903
|
+
if (n === void 0) return "";
|
|
3904
|
+
const s = n.label ?? String(n.value);
|
|
3905
|
+
switch (m) {
|
|
3906
|
+
case "disabled": return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
|
|
3907
|
+
case "selected": return `${c(s, (i) => styleText("dim", i))}`;
|
|
3908
|
+
case "active": return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
|
|
3909
|
+
case "cancelled": return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
|
|
3910
|
+
default: return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
|
|
3911
|
+
}
|
|
3912
|
+
}, d = t.showInstructions ?? true;
|
|
3913
|
+
return new n$1({
|
|
3914
|
+
options: t.options,
|
|
3915
|
+
signal: t.signal,
|
|
3916
|
+
input: t.input,
|
|
3917
|
+
output: t.output,
|
|
3918
|
+
initialValue: t.initialValue,
|
|
3919
|
+
render() {
|
|
3920
|
+
const n = t.withGuide ?? settings.withGuide, m = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t.output, t.message, s, m), u = `${n ? `${styleText("gray", S_BAR)}
|
|
3921
|
+
` : ""}${i}
|
|
3922
|
+
`;
|
|
3923
|
+
switch (this.state) {
|
|
3924
|
+
case "submit": {
|
|
3925
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
3926
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r)}`;
|
|
3927
|
+
}
|
|
3928
|
+
case "cancel": {
|
|
3929
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
3930
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r)}${n ? `
|
|
3931
|
+
${styleText("gray", S_BAR)}` : ""}`;
|
|
3932
|
+
}
|
|
3933
|
+
default: {
|
|
3934
|
+
const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
|
|
3935
|
+
`).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
|
|
3936
|
+
`), b = p.length + 1;
|
|
3937
|
+
return `${u}${r}${limitOptions({
|
|
3938
|
+
output: t.output,
|
|
3939
|
+
cursor: this.cursor,
|
|
3940
|
+
options: this.options,
|
|
3941
|
+
maxItems: t.maxItems,
|
|
3942
|
+
columnPadding: r.length,
|
|
3943
|
+
rowPadding: a + b,
|
|
3944
|
+
style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
|
|
3945
|
+
}).join(`
|
|
3946
|
+
${r}`)}
|
|
3947
|
+
${f}
|
|
3948
|
+
`;
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}).prompt();
|
|
3953
|
+
};
|
|
3819
3954
|
const i = `${styleText("gray", S_BAR)} `;
|
|
3820
3955
|
|
|
3821
3956
|
//#endregion
|
|
@@ -4072,7 +4207,8 @@ const pad = (text, column) => {
|
|
|
4072
4207
|
const value = String(text);
|
|
4073
4208
|
return value + " ".repeat(Math.max(1, column - visible(value).length));
|
|
4074
4209
|
};
|
|
4075
|
-
const
|
|
4210
|
+
const MODELS = [...new Set(Object.values(PROVIDERS).map((provider) => provider.model))].map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
4211
|
+
const MODEL_PATTERN = new RegExp(`(?<![\\w/.-])(?:${MODELS.join("|")})(?![\\w-])`, "g");
|
|
4076
4212
|
const model = (text) => String(text).replace(MODEL_PATTERN, (match) => green(match));
|
|
4077
4213
|
const STATUS_COLUMN = columnWidth(Object.values(STATUS).map((status) => status.word));
|
|
4078
4214
|
function clip(text, limit) {
|
|
@@ -4176,9 +4312,21 @@ function bullet(marker, message) {
|
|
|
4176
4312
|
const success = (message) => bullet(green("✔"), message);
|
|
4177
4313
|
const warn = (message) => bullet(yellow("!"), message);
|
|
4178
4314
|
const error = (message) => bullet(red("✖"), message);
|
|
4179
|
-
async function
|
|
4315
|
+
async function selectProvider(options, initial) {
|
|
4316
|
+
const chosen = await select({
|
|
4317
|
+
message: "Which gateway should your assistants use for Apex?",
|
|
4318
|
+
options,
|
|
4319
|
+
initialValue: initial
|
|
4320
|
+
});
|
|
4321
|
+
if (isCancel(chosen)) {
|
|
4322
|
+
cancel("Cancelled. Nothing was written.");
|
|
4323
|
+
return false;
|
|
4324
|
+
}
|
|
4325
|
+
return chosen;
|
|
4326
|
+
}
|
|
4327
|
+
async function selectAssistants(options, initial, modelId) {
|
|
4180
4328
|
const chosen = await multiselect({
|
|
4181
|
-
message: model(`Set up ${
|
|
4329
|
+
message: model(`Set up ${modelId} for which assistants? ${dim("(space toggles, enter confirms)")}`),
|
|
4182
4330
|
options,
|
|
4183
4331
|
initialValues: initial,
|
|
4184
4332
|
required: false,
|
|
@@ -4387,6 +4535,12 @@ const FLAGS = {
|
|
|
4387
4535
|
hint: "assistant ids",
|
|
4388
4536
|
text: `comma list of: ${IDS.join(", ")} (skips the picker)`
|
|
4389
4537
|
},
|
|
4538
|
+
provider: {
|
|
4539
|
+
type: "string",
|
|
4540
|
+
usage: "--provider <id>",
|
|
4541
|
+
hint: "gateway serving Apex",
|
|
4542
|
+
text: `${PROVIDER_IDS.join(" or ")}; defaults to the one whose key is set, else vercel (skips the question)`
|
|
4543
|
+
},
|
|
4390
4544
|
"no-interactive": {
|
|
4391
4545
|
type: "boolean",
|
|
4392
4546
|
usage: "--no-interactive",
|
|
@@ -4431,6 +4585,7 @@ const COMMANDS = {
|
|
|
4431
4585
|
summary: "configure assistants",
|
|
4432
4586
|
flags: [
|
|
4433
4587
|
"assistants",
|
|
4588
|
+
"provider",
|
|
4434
4589
|
"no-interactive",
|
|
4435
4590
|
"apply",
|
|
4436
4591
|
"no-diff",
|
|
@@ -4439,9 +4594,13 @@ const COMMANDS = {
|
|
|
4439
4594
|
]
|
|
4440
4595
|
},
|
|
4441
4596
|
detect: {
|
|
4442
|
-
args: "[--json]",
|
|
4597
|
+
args: "[--provider <id>] [--json]",
|
|
4443
4598
|
summary: "show what Apex CLI can see on this machine",
|
|
4444
|
-
flags: [
|
|
4599
|
+
flags: [
|
|
4600
|
+
"provider",
|
|
4601
|
+
"json",
|
|
4602
|
+
"help"
|
|
4603
|
+
]
|
|
4445
4604
|
},
|
|
4446
4605
|
undo: {
|
|
4447
4606
|
args: "[--list] [options]",
|
|
@@ -4456,7 +4615,7 @@ const COMMANDS = {
|
|
|
4456
4615
|
]
|
|
4457
4616
|
},
|
|
4458
4617
|
run: {
|
|
4459
|
-
args:
|
|
4618
|
+
args: `[--provider <id>] <${RUNNABLE.join("|")}> [-- <args>]`,
|
|
4460
4619
|
summary: "launch an assistant with the gateway set",
|
|
4461
4620
|
flags: []
|
|
4462
4621
|
},
|
|
@@ -4520,12 +4679,13 @@ const exampleRows = [
|
|
|
4520
4679
|
["apex init", "pick assistants, review, confirm"],
|
|
4521
4680
|
["apex init --assistants codex,pi", "preview two tools (agent and CI friendly)"],
|
|
4522
4681
|
["apex init --assistants codex,pi --apply", "write those two without asking"],
|
|
4682
|
+
["apex init --provider callstack", "use the Callstack portal instead of Vercel AI Gateway"],
|
|
4523
4683
|
["apex undo", "preview the reversal"],
|
|
4524
4684
|
["apex undo --apply", "reverse the last setup without asking"],
|
|
4525
4685
|
["apex detect --json", "machine-readable inventory"]
|
|
4526
4686
|
];
|
|
4527
4687
|
const environmentRows = [
|
|
4528
|
-
[
|
|
4688
|
+
...Object.values(PROVIDERS).map((provider) => [provider.envVar, `${provider.keyName}, needed by apex run and by every assistant you configure for ${provider.name}`]),
|
|
4529
4689
|
["Config directories", "XDG_CONFIG_HOME, CODEX_HOME, CLAUDE_CONFIG_DIR, PI_CODING_AGENT_DIR"],
|
|
4530
4690
|
["Undo journal", "APEX_STATE_DIR, XDG_STATE_HOME (default ~/.local/state/apex)"]
|
|
4531
4691
|
];
|
|
@@ -4574,7 +4734,7 @@ function helpText() {
|
|
|
4574
4734
|
""
|
|
4575
4735
|
];
|
|
4576
4736
|
return [
|
|
4577
|
-
...wrap(`Apex CLI: point your coding assistants at ${
|
|
4737
|
+
...wrap(`Apex CLI: point your coding assistants at Apex via ${Object.values(PROVIDERS).map((provider) => provider.name).join(" or the ")}`, width$1).map((line) => bold(model(line))),
|
|
4578
4738
|
"",
|
|
4579
4739
|
...section("Usage", usageRows.map(([lead, text]) => [
|
|
4580
4740
|
lead,
|
|
@@ -4614,9 +4774,11 @@ function readFlags(rest, command) {
|
|
|
4614
4774
|
const selected = values.assistants === void 0 ? null : [...new Set(values.assistants.split(",").map((id) => id.trim()))];
|
|
4615
4775
|
if (selected?.includes("")) throw new Error("--assistants needs at least one id.");
|
|
4616
4776
|
if (selected?.some((id) => !IDS.includes(id))) throw new Error(`Unknown assistant in --assistants. Choose from: ${IDS.join(", ")}`);
|
|
4777
|
+
if (values.provider !== void 0 && !PROVIDER_IDS.includes(values.provider)) throw new Error(`Unknown --provider. Choose from: ${PROVIDER_IDS.join(", ")}`);
|
|
4617
4778
|
const json = Boolean(values.json);
|
|
4618
4779
|
return {
|
|
4619
4780
|
selected,
|
|
4781
|
+
provider: values.provider === void 0 ? null : PROVIDERS[values.provider],
|
|
4620
4782
|
showDiff: !values["no-diff"],
|
|
4621
4783
|
json,
|
|
4622
4784
|
help: Boolean(values.help),
|
|
@@ -4627,7 +4789,7 @@ function readFlags(rest, command) {
|
|
|
4627
4789
|
})
|
|
4628
4790
|
};
|
|
4629
4791
|
}
|
|
4630
|
-
async function planAll(assistants, options) {
|
|
4792
|
+
async function planAll(assistants, options, provider) {
|
|
4631
4793
|
return Promise.all(assistants.map(async (assistant) => {
|
|
4632
4794
|
const base = {
|
|
4633
4795
|
id: assistant.id,
|
|
@@ -4640,7 +4802,7 @@ async function planAll(assistants, options) {
|
|
|
4640
4802
|
steps: []
|
|
4641
4803
|
};
|
|
4642
4804
|
try {
|
|
4643
|
-
base.files = (await planAssistant(assistant)).map((change) => describeFile(change, options));
|
|
4805
|
+
base.files = (await planAssistant(assistant, provider)).map((change) => describeFile(change, options));
|
|
4644
4806
|
base.steps = base.files.flatMap((file) => file.change.steps ?? []);
|
|
4645
4807
|
} catch (error) {
|
|
4646
4808
|
base.error = error.message;
|
|
@@ -4648,7 +4810,7 @@ async function planAll(assistants, options) {
|
|
|
4648
4810
|
return base;
|
|
4649
4811
|
}));
|
|
4650
4812
|
}
|
|
4651
|
-
const token = () => process.env.
|
|
4813
|
+
const token = (provider) => process.env[provider.envVar]?.trim() || void 0;
|
|
4652
4814
|
const stamp = (iso) => iso.replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
|
|
4653
4815
|
const pendingOf = (entry) => entry.files.filter((file) => file.status !== "unchanged");
|
|
4654
4816
|
async function applyAll(items, apply) {
|
|
@@ -4681,6 +4843,11 @@ const jsonAssistant = (entry, secret) => ({
|
|
|
4681
4843
|
...entry.steps.length ? { steps: entry.steps } : {},
|
|
4682
4844
|
files: entry.files.map((file) => jsonFile(file, secret))
|
|
4683
4845
|
});
|
|
4846
|
+
const providerOptions = () => Object.values(PROVIDERS).map((provider) => ({
|
|
4847
|
+
value: provider.id,
|
|
4848
|
+
label: provider.name,
|
|
4849
|
+
hint: `${provider.model}, key in ${provider.envVar}${token(provider) ? " (set)" : ""}`
|
|
4850
|
+
}));
|
|
4684
4851
|
function pickerOptions(entries) {
|
|
4685
4852
|
return entries.map((entry) => {
|
|
4686
4853
|
const pending = pendingOf(entry).length;
|
|
@@ -4697,24 +4864,29 @@ async function commandInit(rest) {
|
|
|
4697
4864
|
printHelp();
|
|
4698
4865
|
return;
|
|
4699
4866
|
}
|
|
4700
|
-
const
|
|
4867
|
+
const journalFile = journalPath();
|
|
4868
|
+
if (flags.mode !== "preview") await readJournal(journalFile);
|
|
4869
|
+
const batch = randomUUID();
|
|
4870
|
+
const asking = !flags.json && flags.mode === "prompt";
|
|
4871
|
+
let provider = flags.provider ?? defaultProvider();
|
|
4872
|
+
if (!flags.json) intro("Apex CLI · Configure Apex for your favorite harness", MODE_NOTE[flags.mode]);
|
|
4873
|
+
if (asking && !flags.provider) {
|
|
4874
|
+
const picked = await selectProvider(providerOptions(), provider.id);
|
|
4875
|
+
if (picked === false) return;
|
|
4876
|
+
provider = PROVIDERS[picked];
|
|
4877
|
+
}
|
|
4878
|
+
const secret = token(provider);
|
|
4701
4879
|
const options = {
|
|
4702
4880
|
secret,
|
|
4703
4881
|
showDiff: flags.showDiff && !flags.json
|
|
4704
4882
|
};
|
|
4705
|
-
const
|
|
4706
|
-
if (flags.mode !== "preview") await readJournal(journalFile);
|
|
4707
|
-
const batch = randomUUID();
|
|
4708
|
-
const entries = await planAll(await detect(), options);
|
|
4883
|
+
const entries = await planAll(await detect(), options, provider);
|
|
4709
4884
|
let ids = flags.selected ?? entries.filter((entry) => entry.detected).map((entry) => entry.id);
|
|
4710
|
-
if (!flags.
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
if (picked === false) return;
|
|
4716
|
-
ids = picked;
|
|
4717
|
-
}
|
|
4885
|
+
if (asking && !flags.selected) {
|
|
4886
|
+
const defaults = entries.filter((entry) => entry.detected && !entry.manual && !entry.error && pendingOf(entry).length).map((entry) => entry.id);
|
|
4887
|
+
const picked = await selectAssistants(pickerOptions(entries), defaults, provider.model);
|
|
4888
|
+
if (picked === false) return;
|
|
4889
|
+
ids = picked;
|
|
4718
4890
|
}
|
|
4719
4891
|
const chosen = ids.map((id) => entries.find((entry) => entry.id === id));
|
|
4720
4892
|
const blocked = chosen.filter((entry) => entry.error);
|
|
@@ -4735,8 +4907,9 @@ async function commandInit(rest) {
|
|
|
4735
4907
|
console.log(JSON.stringify({
|
|
4736
4908
|
command: "init",
|
|
4737
4909
|
mode: flags.mode,
|
|
4738
|
-
|
|
4739
|
-
|
|
4910
|
+
provider: provider.id,
|
|
4911
|
+
model: provider.model,
|
|
4912
|
+
endpoint: provider.baseUrl,
|
|
4740
4913
|
applied: applied.length,
|
|
4741
4914
|
appliedPaths: applied,
|
|
4742
4915
|
...failure ? { error: failure.message } : {},
|
|
@@ -4748,7 +4921,7 @@ async function commandInit(rest) {
|
|
|
4748
4921
|
assistants: chosen.map((entry) => jsonAssistant(entry, secret)),
|
|
4749
4922
|
journal: journalFile,
|
|
4750
4923
|
undo: `${APEX} undo`,
|
|
4751
|
-
envVar:
|
|
4924
|
+
envVar: provider.envVar,
|
|
4752
4925
|
nextSteps: active.filter((entry) => RUNNABLE.includes(entry.id)).map((entry) => `${APEX} run ${entry.id}`)
|
|
4753
4926
|
}, null, 2));
|
|
4754
4927
|
if (blocked.length || failure) process.exitCode = 1;
|
|
@@ -4758,7 +4931,10 @@ async function commandInit(rest) {
|
|
|
4758
4931
|
outro(`No assistant selected, so nothing was written. Pick one with --assistants or run ${APEX} init interactively.`);
|
|
4759
4932
|
return;
|
|
4760
4933
|
}
|
|
4761
|
-
if (flags.mode !== "prompt")
|
|
4934
|
+
if (flags.mode !== "prompt") {
|
|
4935
|
+
prose(`${gray("gateway:")} ${provider.name}`);
|
|
4936
|
+
prose(`${gray("setup for:")} ${chosen.map((entry) => entry.name + (entry.detected ? "" : " (not detected)")).join(", ")}`);
|
|
4937
|
+
}
|
|
4762
4938
|
if (blocked.length) {
|
|
4763
4939
|
plain("");
|
|
4764
4940
|
for (const entry of blocked) error(`${entry.name}: ${shortPath(entry.error)}`);
|
|
@@ -4786,23 +4962,24 @@ async function commandInit(rest) {
|
|
|
4786
4962
|
}
|
|
4787
4963
|
const apex = APEX !== "apex" && flags.mode === "prompt" && await installApex() ? "apex" : APEX;
|
|
4788
4964
|
for (const entry of active.filter((entry) => entry.steps.length)) section(`Finish setting up ${entry.name}`, entry.steps);
|
|
4789
|
-
for (const entry of manual) section(`Manual setup for ${entry.name}: Apex CLI writes nothing`,
|
|
4965
|
+
for (const entry of manual) section(`Manual setup for ${entry.name}: Apex CLI writes nothing`, manualSteps(entry.id, provider));
|
|
4790
4966
|
section("Environment", [
|
|
4791
|
-
...secret ? [] : [
|
|
4792
|
-
secret ? `${green("✓")}
|
|
4967
|
+
...secret ? [] : [`export ${provider.envVar}=<${provider.keyName.replace(/^your /, "")}>`],
|
|
4968
|
+
secret ? `${green("✓")} ${provider.envVar} is set in this shell` : `${yellow("!")} ${provider.envVar} is not set here yet`,
|
|
4969
|
+
...!secret && provider.keysUrl ? [`Create a key at ${link(provider.keysUrl)}`] : [],
|
|
4793
4970
|
dim("Apex CLI never reads, stores or logs the key itself.")
|
|
4794
4971
|
]);
|
|
4795
4972
|
const runnable = active.map((entry) => entry.id).filter((id) => RUNNABLE.includes(id));
|
|
4796
4973
|
const rows = runnable.map((id) => ({
|
|
4797
4974
|
command: `${apex} run ${id}`,
|
|
4798
|
-
expansion: runExpansion(id)
|
|
4975
|
+
expansion: runExpansion(id, provider)
|
|
4799
4976
|
}));
|
|
4800
4977
|
const commandWidth = columnWidth(rows.map((row) => row.command));
|
|
4801
|
-
section(runnable.length ? `Use these commands to run ${
|
|
4978
|
+
section(runnable.length ? `Use these commands to run ${provider.model} with your selected harnesses:` : `Use ${provider.model} with your assistant:`, [
|
|
4802
4979
|
...rows.flatMap((row) => wrap(row.expansion, width() - 3 - commandWidth).map((part, index) => `${pad(index ? "" : row.command, commandWidth)}${dim(part)}`)),
|
|
4803
4980
|
"",
|
|
4804
|
-
`...or pick "${
|
|
4805
|
-
`For more instructions, visit: ${link(
|
|
4981
|
+
`...or pick "${provider.model}" from the UI when setting up manually.`,
|
|
4982
|
+
`For more instructions, visit: ${link(provider.guideUrl)}`,
|
|
4806
4983
|
...applied ? ["", `If you want to undo the changes, run ${bold(`${apex} undo`)}`] : [],
|
|
4807
4984
|
...apex === "apex" ? [] : ["", dim("For the short apex command: npm install -g @callstack/apex")]
|
|
4808
4985
|
]);
|
|
@@ -4813,16 +4990,17 @@ async function commandDetect(rest) {
|
|
|
4813
4990
|
printHelp();
|
|
4814
4991
|
return;
|
|
4815
4992
|
}
|
|
4816
|
-
const
|
|
4993
|
+
const provider = flags.provider ?? defaultProvider();
|
|
4994
|
+
const secret = token(provider);
|
|
4817
4995
|
const entries = await planAll(await detect(), {
|
|
4818
4996
|
secret,
|
|
4819
4997
|
showDiff: false
|
|
4820
|
-
});
|
|
4998
|
+
}, provider);
|
|
4821
4999
|
if (flags.json) {
|
|
4822
5000
|
console.log(JSON.stringify(entries.map((entry) => jsonAssistant(entry, secret)), null, 2));
|
|
4823
5001
|
return;
|
|
4824
5002
|
}
|
|
4825
|
-
intro(
|
|
5003
|
+
intro(`Apex CLI · What this machine has for ${provider.name}`, "Read only: nothing is created, edited or removed.");
|
|
4826
5004
|
const rows = entries.map((entry) => {
|
|
4827
5005
|
const pending = pendingOf(entry).length;
|
|
4828
5006
|
return {
|
|
@@ -4848,7 +5026,7 @@ async function commandUndo(rest) {
|
|
|
4848
5026
|
printHelp();
|
|
4849
5027
|
return;
|
|
4850
5028
|
}
|
|
4851
|
-
const secret = token();
|
|
5029
|
+
const secret = token(defaultProvider());
|
|
4852
5030
|
const options = {
|
|
4853
5031
|
secret,
|
|
4854
5032
|
showDiff: flags.showDiff && !flags.json
|
|
@@ -4946,10 +5124,33 @@ async function commandUndo(rest) {
|
|
|
4946
5124
|
}
|
|
4947
5125
|
})).applied) outro("Undone. Configs are back to how they were. Restart any open assistant to reload them.");
|
|
4948
5126
|
}
|
|
5127
|
+
function takeProvider(args) {
|
|
5128
|
+
const [first, second] = args;
|
|
5129
|
+
if (first === "--provider") return {
|
|
5130
|
+
value: second ?? "",
|
|
5131
|
+
rest: args.slice(2)
|
|
5132
|
+
};
|
|
5133
|
+
if (first?.startsWith("--provider=")) return {
|
|
5134
|
+
value: first.slice(11),
|
|
5135
|
+
rest: args.slice(1)
|
|
5136
|
+
};
|
|
5137
|
+
return {
|
|
5138
|
+
value: null,
|
|
5139
|
+
rest: args
|
|
5140
|
+
};
|
|
5141
|
+
}
|
|
4949
5142
|
async function commandRun(rest) {
|
|
4950
|
-
const
|
|
5143
|
+
const before = takeProvider(rest);
|
|
5144
|
+
const [id, ...after] = before.rest;
|
|
4951
5145
|
if (!RUNNABLE.includes(id)) throw new Error(`apex run needs one of: ${RUNNABLE.join(", ")}. See ${APEX} --help.`);
|
|
4952
|
-
const
|
|
5146
|
+
const behind = before.value === null ? takeProvider(after) : {
|
|
5147
|
+
value: null,
|
|
5148
|
+
rest: after
|
|
5149
|
+
};
|
|
5150
|
+
const chosen = before.value ?? behind.value;
|
|
5151
|
+
if (chosen !== null && !PROVIDER_IDS.includes(chosen)) throw new Error(`Unknown --provider. Choose from: ${PROVIDER_IDS.join(", ")}`);
|
|
5152
|
+
const passed = behind.rest;
|
|
5153
|
+
const options = launchOptions(id, process.env, chosen === null ? defaultProvider() : PROVIDERS[chosen]);
|
|
4953
5154
|
const binary = await executable(id);
|
|
4954
5155
|
if (!binary) throw new Error(`${id} was not found on PATH. Install it first, then run ${APEX} init.`);
|
|
4955
5156
|
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.");
|
|
@@ -4972,7 +5173,8 @@ const completionsFor = (command) => COMMANDS[command].flags.map((flag) => ({
|
|
|
4972
5173
|
long: `--${flag}`,
|
|
4973
5174
|
short: FLAGS[flag].short ? `-${FLAGS[flag].short}` : null,
|
|
4974
5175
|
hint: FLAGS[flag].hint,
|
|
4975
|
-
...command === "init" && flag === "assistants" ? { values: IDS } : {}
|
|
5176
|
+
...command === "init" && flag === "assistants" ? { values: IDS } : {},
|
|
5177
|
+
...flag === "provider" ? { values: PROVIDER_IDS } : {}
|
|
4976
5178
|
}));
|
|
4977
5179
|
const spellings = (entry) => [entry.long, entry.short].filter(Boolean);
|
|
4978
5180
|
function completionScript(shell) {
|
|
@@ -4980,15 +5182,18 @@ function completionScript(shell) {
|
|
|
4980
5182
|
const words = (list) => list.join(" ");
|
|
4981
5183
|
if (shell === "zsh") return `#compdef apex
|
|
4982
5184
|
_apex() {
|
|
4983
|
-
local -a commands assistants runnable${taking.length ? " " : ""}${words(taking.map((name) => `${name}_flags`))}
|
|
5185
|
+
local -a commands assistants providers runnable${taking.length ? " " : ""}${words(taking.map((name) => `${name}_flags`))}
|
|
4984
5186
|
commands=(${words([...Object.entries(COMMANDS).map(([name, command]) => `'${name}:${command.summary}'`), ...GLOBALS.map(([flag, text]) => `'${flag}:${text}'`)])})
|
|
4985
5187
|
assistants=(${words(IDS)})
|
|
5188
|
+
providers=(${words(PROVIDER_IDS)})
|
|
4986
5189
|
runnable=(${words(RUNNABLE)})
|
|
4987
5190
|
${taking.map((name) => ` ${name}_flags=(${words(completionsFor(name).flatMap((entry) => spellings(entry).map((word) => `'${word}:${entry.hint}'`)))})`).join("\n")}
|
|
4988
5191
|
if (( CURRENT == 2 )); then
|
|
4989
5192
|
_describe -t commands 'apex command' commands
|
|
4990
5193
|
elif [[ "\${words[CURRENT - 1]}" == --assistants ]]; then
|
|
4991
5194
|
_describe -t assistants 'assistant' assistants
|
|
5195
|
+
elif [[ "\${words[CURRENT - 1]}" == --provider ]]; then
|
|
5196
|
+
_describe -t providers 'gateway' providers
|
|
4992
5197
|
else
|
|
4993
5198
|
case "\${words[2]}" in
|
|
4994
5199
|
${taking.map((name) => ` ${name}) _describe -t flags 'option' ${name}_flags ;;`).join("\n")}
|
|
@@ -5003,6 +5208,7 @@ if [[ "\${funcstack[1]}" == _apex ]]; then _apex "$@"; else compdef _apex apex;
|
|
|
5003
5208
|
if (shell === "bash") return `_apex_completions() {
|
|
5004
5209
|
local sub="\${COMP_WORDS[1]}" prev="\${COMP_WORDS[COMP_CWORD - 1]}" current="\${COMP_WORDS[COMP_CWORD]}"
|
|
5005
5210
|
if [[ "$prev" == --assistants ]]; then COMPREPLY=( $(compgen -W "${words(IDS)}" -- "$current") ); return; fi
|
|
5211
|
+
if [[ "$prev" == --provider ]]; then COMPREPLY=( $(compgen -W "${words(PROVIDER_IDS)}" -- "$current") ); return; fi
|
|
5006
5212
|
if (( COMP_CWORD == 1 )); then
|
|
5007
5213
|
COMPREPLY=( $(compgen -W "${words([...Object.keys(COMMANDS), ...GLOBALS.map(([flag]) => flag)])}" -- "$current") )
|
|
5008
5214
|
return
|
|
@@ -5021,6 +5227,12 @@ complete -F _apex_completions apex
|
|
|
5021
5227
|
...GLOBALS.map(([flag, hint]) => `complete -c apex -f -n "__fish_use_subcommand" -l ${flag.slice(2)} -d '${hint}'`),
|
|
5022
5228
|
...taking.flatMap((name) => completionsFor(name).map((entry) => fish(`__fish_seen_subcommand_from ${name}`, entry))),
|
|
5023
5229
|
...RUNNABLE.map((id) => `complete -c apex -f -n "__fish_seen_subcommand_from run" -a ${id} -d 'assistant'`),
|
|
5230
|
+
fish("__fish_seen_subcommand_from run", {
|
|
5231
|
+
long: "--provider",
|
|
5232
|
+
short: null,
|
|
5233
|
+
hint: FLAGS.provider.hint,
|
|
5234
|
+
values: PROVIDER_IDS
|
|
5235
|
+
}),
|
|
5024
5236
|
...SHELLS.map((name) => `complete -c apex -f -n "__fish_seen_subcommand_from completion" -a ${name} -d 'shell'`)
|
|
5025
5237
|
].join("\n") + "\n";
|
|
5026
5238
|
}
|