@phantomcodes/cli 1.2.4 → 1.2.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 +7 -5
- package/dist/agents/command.mjs +11 -3
- package/dist/cli/run.mjs +16 -10
- package/dist/cli/screens.mjs +9 -4
- package/dist/core/helpers.mjs +1 -1
- package/dist/core/pick.mjs +5 -3
- package/dist/core/version.mjs +1 -1
- package/dist/setup/agents.mjs +54 -1
- package/dist/setup/command.mjs +1 -1
- package/dist/setup/screen.mjs +1 -1
- package/dist/setup/shared.mjs +4 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -62,8 +62,8 @@ The Phantom CLI detects supported agents, previews the configuration changes, th
|
|
|
62
62
|
applying them. It connects their model calls to Phantom AI and wires up the saved
|
|
63
63
|
key. Restart your agent, or open a new terminal, to use the connection.
|
|
64
64
|
|
|
65
|
-
Codex, Claude Code, opencode, Continue, Goose, Aider, GitHub Copilot CLI
|
|
66
|
-
support saved setup. Cursor gets instructions for its settings screen; pi gets
|
|
65
|
+
Codex, Claude Code, opencode, Continue, Goose, Aider, GitHub Copilot CLI, Cline
|
|
66
|
+
and Gemini CLI support saved setup. Cursor gets instructions for its settings screen; pi gets
|
|
67
67
|
the agent skill.
|
|
68
68
|
|
|
69
69
|
Preview without changing files with `phantom setup --dry-run`. The Phantom CLI keeps backups;
|
|
@@ -79,9 +79,11 @@ phantom workspace status
|
|
|
79
79
|
phantom codex
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Both setup options require **Node 24+** and your coding agent installed. Connect,
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
Both setup options require **Node 24+** and your coding agent installed. Connect, then
|
|
83
|
+
launch **Codex** (`phantom codex`), **Claude Code** (`phantom claude`), **pi**
|
|
84
|
+
(`phantom pi`), or **any other compatible agent**. Each runs phantom-1 unless you
|
|
85
|
+
pick a model (`--model` after `--`), and phantom-1 runs on free models until you add
|
|
86
|
+
credit. Free calls leave out web search, which costs money on any model.
|
|
85
87
|
|
|
86
88
|
Login opens a device approval page and saves a key privately. Funding opens
|
|
87
89
|
checkout; check your workspace balance after payment. Agent runs use temporary
|
package/dist/agents/command.mjs
CHANGED
|
@@ -6,7 +6,10 @@ import { die, NO_KEY } from "../core/errors.mjs";
|
|
|
6
6
|
import { DEFAULT_BASE_URL, httpsOnly, request } from "../core/request.mjs";
|
|
7
7
|
import { keyId, readNamedKey, resolveApiKey } from "../keys/saved.mjs";
|
|
8
8
|
import { workspaceLogin } from "../workspace/command.mjs";
|
|
9
|
-
|
|
9
|
+
/** The model an agent runs when the user names none: phantom-1, which runs free on a key with no credit. */
|
|
10
|
+
const DEFAULT_MODEL = 'phantom-1';
|
|
11
|
+
/** phantom-1 as Claude Code names it: its picker keeps only ids that say "claude", so Phantom lists them as `claude-code/<id>`. */
|
|
12
|
+
const CLAUDE_DEFAULT_MODEL = `claude-code/${DEFAULT_MODEL}`;
|
|
10
13
|
/** The key's spend and balance, or null when the answer has no numbers, such as a sign-in page that came back with status 200. */
|
|
11
14
|
async function keyStatus(apiKey, baseUrl) {
|
|
12
15
|
const s = await request('GET', '/key/balance', apiKey, undefined, baseUrl);
|
|
@@ -23,11 +26,16 @@ export const AGENTS = {
|
|
|
23
26
|
// --settings outranks settings.json, so an env block or apiKeyHelper
|
|
24
27
|
// `phantom setup` wrote can't send Claude Code past the proxy. The helper
|
|
25
28
|
// reads the token from the environment, so it stays off the command line.
|
|
29
|
+
// It runs with this phantom's own Node: the curl installer puts none on PATH.
|
|
30
|
+
const helper = `${JSON.stringify(process.execPath)} -p "process.env.PHANTOM_AGENT_TOKEN || ''"`;
|
|
31
|
+
// phantom-1 unless the user picks a model. It goes in the environment, which
|
|
32
|
+
// Claude Code ranks below --model and below a model in its settings file.
|
|
33
|
+
const model = hasModel(args) || env.ANTHROPIC_MODEL ? {} : { ANTHROPIC_MODEL: CLAUDE_DEFAULT_MODEL };
|
|
26
34
|
const vars = { ANTHROPIC_BASE_URL: proxy, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1', ENABLE_TOOL_SEARCH: 'true' };
|
|
27
35
|
const { ANTHROPIC_AUTH_TOKEN: _auth, ...rest } = env;
|
|
28
36
|
return {
|
|
29
|
-
args: ['--settings', JSON.stringify({ apiKeyHelper:
|
|
30
|
-
env: { ...rest, ANTHROPIC_BASE_URL: proxy, ANTHROPIC_API_KEY: token, PHANTOM_AGENT_TOKEN: token },
|
|
37
|
+
args: ['--settings', JSON.stringify({ apiKeyHelper: helper, env: vars }), ...args],
|
|
38
|
+
env: { ...rest, ...model, ANTHROPIC_BASE_URL: proxy, ANTHROPIC_API_KEY: token, PHANTOM_AGENT_TOKEN: token },
|
|
31
39
|
};
|
|
32
40
|
},
|
|
33
41
|
},
|
package/dist/cli/run.mjs
CHANGED
|
@@ -21,16 +21,21 @@ import { LEGACY_SETUP_HINT, legacySetupFound } from "../setup/legacy.mjs";
|
|
|
21
21
|
* @param node The Node version it runs on; one too old to reject forged
|
|
22
22
|
* eMudhra certificates gets a warning on stderr first.
|
|
23
23
|
*/
|
|
24
|
+
/** Retired flags, accepted so the command can say what replaced them, and never suggested for a typo. */
|
|
25
|
+
const RETIRED_FLAGS = ['pay', 'mpp'];
|
|
26
|
+
/** The flags every command takes, and the retired ones. */
|
|
27
|
+
const EVERY_COMMAND_FLAGS = ['table', 'json', 'help', 'all', 'version', 'mcp', ...RETIRED_FLAGS];
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* such as --yse
|
|
28
|
-
*
|
|
29
|
+
* The flags a command reads: each one its help names, plus those of the
|
|
30
|
+
* command its subcommand names (`workspace fund` reads `fund`'s), plus
|
|
31
|
+
* {@link EVERY_COMMAND_FLAGS}. A typo such as --yse, or a flag only another
|
|
32
|
+
* command takes (balance --key), is refused instead of quietly ignored, which
|
|
33
|
+
* in CI would otherwise drop the flag without a word.
|
|
29
34
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
function flagsOf(command, subcommand) {
|
|
36
|
+
const helps = [command.help, COMMANDS.find((c) => c.name === subcommand)?.help ?? ''].join('\n');
|
|
37
|
+
return new Set([...[...helps.matchAll(/--([a-z][a-z0-9-]*)/g)].map((m) => m[1]), ...EVERY_COMMAND_FLAGS]);
|
|
38
|
+
}
|
|
34
39
|
export async function run(argv = process.argv.slice(2), env = process.env, io = {
|
|
35
40
|
stdout: (s) => process.stdout.write(s),
|
|
36
41
|
stderr: (s) => process.stderr.write(s),
|
|
@@ -92,10 +97,11 @@ export async function run(argv = process.argv.slice(2), env = process.env, io =
|
|
|
92
97
|
const ownFlags = parseFlags(flagArgs.includes('--') ? flagArgs.slice(0, flagArgs.indexOf('--')) : flagArgs);
|
|
93
98
|
// claude, codex and pi hand their arguments to the agent, so they check their own.
|
|
94
99
|
const passesArgs = command.handler === COMMANDS.find((c) => c.name === 'claude')?.handler;
|
|
100
|
+
const known = flagsOf(command, subcommand);
|
|
95
101
|
for (const name of passesArgs ? [] : Object.keys(ownFlags)) {
|
|
96
|
-
if (
|
|
102
|
+
if (known.has(name))
|
|
97
103
|
continue;
|
|
98
|
-
const guess = closestCommand(name, [...
|
|
104
|
+
const guess = closestCommand(name, [...known].filter((flag) => !RETIRED_FLAGS.includes(flag)));
|
|
99
105
|
die(`Unknown flag --${name} for phantom ${argv[0]}.${guess ? ` Did you mean --${guess}?` : ''} See phantom ${argv[0]} --help.`, 'unknown_flag');
|
|
100
106
|
}
|
|
101
107
|
// A person at a terminal reads each command's screen; a pipe (scripts, agents) or --json gets JSON.
|
package/dist/cli/screens.mjs
CHANGED
|
@@ -109,15 +109,19 @@ export function spendScreen(s, style = {}) {
|
|
|
109
109
|
lines.push(...(s.scope === 'workspace' ? next(p, 'Pick the models phantom-1 runs:', 'phantom lineup') : next(p, 'Every key in the workspace:', 'phantom spend --workspace')));
|
|
110
110
|
return lines.join('\n');
|
|
111
111
|
}
|
|
112
|
+
/** `text` cut to `width` characters, ending in … when it was longer. */
|
|
113
|
+
const clip = (text, width) => (text.length > width ? `${text.slice(0, width - 1)}…` : text);
|
|
112
114
|
/** The keys, one row each: name, the start of the key, what it spent, and its limit. */
|
|
113
115
|
function keyRows(p, keys) {
|
|
114
116
|
if (!keys.length)
|
|
115
117
|
return [` ${p.dim('No keys yet.')}`];
|
|
116
118
|
return keys.map((k) => {
|
|
117
119
|
const limit = k.limit_usd != null ? `limit ${usd(k.limit_usd)}` : k.budget_usd != null ? `${usd(k.budget_usd)} every ${plural(k.period_days ?? 30, 'day')}` : 'no limit';
|
|
118
|
-
|
|
120
|
+
// A revoked or rotated key gets a hollow mark and the word off at the end, so the columns stay in line.
|
|
121
|
+
const off = k.is_active === false;
|
|
122
|
+
const mark = off ? p.dim('○') : p.lime('●');
|
|
119
123
|
// The id's first 8 characters: enough for --id, which also takes the name.
|
|
120
|
-
return ` ${mark} ${(k.name ?? 'Key'
|
|
124
|
+
return ` ${mark} ${clip(k.name ?? 'Key', 20).padEnd(21)}${p.dim((k.id ?? '').slice(0, 8).padEnd(10))}${p.dim((k.prefix ?? '').slice(0, 18).padEnd(20))}${usd(k.spent_usd ?? 0).padStart(8)} ${p.dim(limit)}${off ? ` ${p.dim('off')}` : ''}`;
|
|
121
125
|
});
|
|
122
126
|
}
|
|
123
127
|
/** `phantom workspace`: the shared balance, each key, and the last few charges. */
|
|
@@ -161,7 +165,7 @@ export function routeScreen(r, style = {}) {
|
|
|
161
165
|
const lines = ['', ...caption(p, w, 'ROUTING', p.dim('how model "auto" picks for this key'))];
|
|
162
166
|
const policy = r.route_policy;
|
|
163
167
|
if (!policy) {
|
|
164
|
-
lines.push(' No route policy, so
|
|
168
|
+
lines.push(' No route policy, so model "auto" runs phantom-1, which picks a model for each request.');
|
|
165
169
|
lines.push(...next(p, 'Make one:', 'phantom route set --models a,b,c'));
|
|
166
170
|
return lines.join('\n');
|
|
167
171
|
}
|
|
@@ -317,7 +321,8 @@ export function savedKeysScreen(keys, style = {}) {
|
|
|
317
321
|
lines.push(` ${p.dim('None saved yet.')}`);
|
|
318
322
|
for (const k of keys) {
|
|
319
323
|
const credit = k.credit_balance_usd === undefined ? '' : usd(k.credit_balance_usd).padStart(10);
|
|
320
|
-
|
|
324
|
+
const off = k.active === false;
|
|
325
|
+
lines.push(` ${off ? p.dim('○') : p.lime('●')} ${clip(k.name, 20).padEnd(21)}${p.dim(k.id.slice(0, 12).padEnd(14))}${credit}${off ? ` ${p.dim('off')}` : ''}`);
|
|
321
326
|
}
|
|
322
327
|
lines.push(...next(p, 'Save another:', 'phantom keys save <name>'));
|
|
323
328
|
return lines.join('\n');
|
package/dist/core/helpers.mjs
CHANGED
|
@@ -12,7 +12,7 @@ export function pickIo(input) {
|
|
|
12
12
|
if (!(io.keys ? io.isTTY : canPick(env, io.isTTY)))
|
|
13
13
|
return null;
|
|
14
14
|
// A command started from the menu goes back to it on Esc; one typed on its own quits.
|
|
15
|
-
return { write: io.stderr, env, keys: io.keys, esc: io.fromMenu ? 'back' : '
|
|
15
|
+
return { write: io.stderr, env, keys: io.keys, esc: io.fromMenu ? 'back' : 'cancel', columns: process.stderr.columns };
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Whether a command should draw its screen rather than print JSON: with
|
package/dist/core/pick.mjs
CHANGED
|
@@ -96,6 +96,8 @@ function block(write) {
|
|
|
96
96
|
}
|
|
97
97
|
/** The question line: a lime ? and the question in bold, then the answer once there is one. */
|
|
98
98
|
const asked = (p, question, answer = '') => ` ${p.lime('?')} ${p.bold(question)}${answer ? ` ${p.cyan(answer)}` : ''}`;
|
|
99
|
+
/** The answer a question shows after Esc: back, quit, or cancelled. */
|
|
100
|
+
const escAnswer = (esc) => (esc === 'quit' ? 'quit' : esc === 'cancel' ? 'cancelled' : 'back');
|
|
99
101
|
/** The hint under a picker: which keys do what. */
|
|
100
102
|
const keysHint = (p, words) => ` ${p.dim(words)}`;
|
|
101
103
|
/**
|
|
@@ -129,7 +131,7 @@ export async function choose(io, question, choices, start = 0) {
|
|
|
129
131
|
return choices[at].value;
|
|
130
132
|
}
|
|
131
133
|
else if (key === 'escape' || key === 'q' || key === 'end') {
|
|
132
|
-
draw([asked(p, question, io.esc
|
|
134
|
+
draw([asked(p, question, escAnswer(io.esc))]);
|
|
133
135
|
return null;
|
|
134
136
|
}
|
|
135
137
|
else if (key === 'ctrl-c') {
|
|
@@ -190,7 +192,7 @@ export async function checklist(io, question, items) {
|
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
else if (key === 'escape' || key === 'q' || key === 'end') {
|
|
193
|
-
draw([asked(p, question, io.esc
|
|
195
|
+
draw([asked(p, question, escAnswer(io.esc))]);
|
|
194
196
|
return null;
|
|
195
197
|
}
|
|
196
198
|
else if (key === 'ctrl-c') {
|
|
@@ -270,7 +272,7 @@ export async function askText(io, question, opts = {}) {
|
|
|
270
272
|
problem = why;
|
|
271
273
|
}
|
|
272
274
|
else if (key === 'escape' || key === 'end') {
|
|
273
|
-
draw([asked(p, question, io.esc
|
|
275
|
+
draw([asked(p, question, escAnswer(io.esc))]);
|
|
274
276
|
return null;
|
|
275
277
|
}
|
|
276
278
|
else if (key === 'ctrl-c') {
|
package/dist/core/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This release of phantom. `npm version` keeps it equal to the version in package.json. */
|
|
2
|
-
export const VERSION = '1.2.
|
|
2
|
+
export const VERSION = '1.2.5';
|
package/dist/setup/agents.mjs
CHANGED
|
@@ -88,7 +88,8 @@ export function claudePlan(env, opts) {
|
|
|
88
88
|
ANTHROPIC_API_KEY: '',
|
|
89
89
|
ENABLE_TOOL_SEARCH: 'true',
|
|
90
90
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1',
|
|
91
|
-
|
|
91
|
+
// phantom-1 unless a model is named, as Codex gets: it runs free until there is credit.
|
|
92
|
+
ANTHROPIC_MODEL: opts.model ?? 'claude-code/phantom-1',
|
|
92
93
|
};
|
|
93
94
|
const changes = [[['apiKeyHelper'], `${phantomCommand(opts.onPath ?? onPath)} key show`], ...Object.entries(set).map(([k, v]) => [['env', k], v])];
|
|
94
95
|
// A token outranks apiKeyHelper, and would send a non-Phantom key.
|
|
@@ -311,6 +312,44 @@ export function clineProvider(ctx, off) {
|
|
|
311
312
|
},
|
|
312
313
|
};
|
|
313
314
|
}
|
|
315
|
+
// ── Gemini CLI ───────────────────────────────────────────────────────────────
|
|
316
|
+
/** Gemini CLI's folder: `.gemini` in `$GEMINI_CLI_HOME`, else in the home folder. */
|
|
317
|
+
export function geminiDir(env, home) {
|
|
318
|
+
return path.join(env.GEMINI_CLI_HOME?.trim() ? env.GEMINI_CLI_HOME : home, '.gemini');
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Gemini CLI speaks Google's API, which Phantom AI serves at `/gemini-cli`.
|
|
322
|
+
* The shell block sets GOOGLE_GEMINI_BASE_URL to it and GEMINI_API_KEY to
|
|
323
|
+
* the Phantom key; `settings.json` in Gemini CLI's folder picks API key
|
|
324
|
+
* sign-in (`security.auth.selectedType`) and the model (`model.name`).
|
|
325
|
+
* `--provider off` takes out a phantom model; the sign-in choice stays, and
|
|
326
|
+
* `phantom setup --restore` puts the file back as it was.
|
|
327
|
+
*/
|
|
328
|
+
export function geminiProvider(ctx, off) {
|
|
329
|
+
const file = path.join(geminiDir(ctx.env, ctx.home), 'settings.json');
|
|
330
|
+
const text = readText(file);
|
|
331
|
+
const settings = jsonObject(file, text);
|
|
332
|
+
if (off) {
|
|
333
|
+
const ours = typeof settings.model?.name === 'string' && settings.model.name.startsWith('phantom-');
|
|
334
|
+
const alone = Object.keys(settings.model ?? {}).length === 1;
|
|
335
|
+
return offResult('Gemini CLI', file, text, ours ? jsonEdit(file, text, [[alone ? ['model'] : ['model', 'name'], undefined]]) : text);
|
|
336
|
+
}
|
|
337
|
+
const model = ctx.model ?? SETUP_MODEL;
|
|
338
|
+
const changes = [
|
|
339
|
+
[['security', 'auth', 'selectedType'], 'gemini-api-key'],
|
|
340
|
+
[['model', 'name'], model],
|
|
341
|
+
];
|
|
342
|
+
return {
|
|
343
|
+
edits: [{ file, after: jsonEdit(file, text, changes) }],
|
|
344
|
+
result: {
|
|
345
|
+
settings: file,
|
|
346
|
+
on: true,
|
|
347
|
+
base_url: agentBaseUrl(ctx.env, 'gemini-cli'),
|
|
348
|
+
model,
|
|
349
|
+
warnings: ['Gemini CLI reads its key from the shell; open a new terminal before running it. Other programs that read GEMINI_API_KEY get the Phantom key too'],
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
314
353
|
// ── Registry ─────────────────────────────────────────────────────────────────
|
|
315
354
|
/**
|
|
316
355
|
* The agents `phantom setup` knows, in the order setup runs them. Adding an agent
|
|
@@ -418,6 +457,20 @@ export const SETUP_AGENTS = {
|
|
|
418
457
|
home: '.cline',
|
|
419
458
|
provider: clineProvider,
|
|
420
459
|
},
|
|
460
|
+
/** Gemini CLI. Its address and key come from the shell block; settings.json picks the sign-in and model. */
|
|
461
|
+
gemini: {
|
|
462
|
+
label: 'Gemini CLI',
|
|
463
|
+
/** ~/.gemini, or under $GEMINI_CLI_HOME */
|
|
464
|
+
home: '.gemini',
|
|
465
|
+
detect: (env, home) => existsSync(geminiDir(env, home)),
|
|
466
|
+
/** ~/.agents/skills, which Gemini CLI reads as well as ~/.gemini/skills */
|
|
467
|
+
skills: '.agents/skills',
|
|
468
|
+
provider: geminiProvider,
|
|
469
|
+
shellEnv: (ctx) => ({
|
|
470
|
+
GOOGLE_GEMINI_BASE_URL: agentBaseUrl(ctx.env, 'gemini-cli'),
|
|
471
|
+
GEMINI_API_KEY: '$PHANTOM_API_KEY',
|
|
472
|
+
}),
|
|
473
|
+
},
|
|
421
474
|
};
|
|
422
475
|
export const SETUP_NAMES = Object.keys(SETUP_AGENTS);
|
|
423
476
|
/** `a, b or c`. */
|
package/dist/setup/command.mjs
CHANGED
|
@@ -168,7 +168,7 @@ export async function setupCommand(input) {
|
|
|
168
168
|
// At a terminal: a choice instead of typing y or n, with a look at every line first if wanted.
|
|
169
169
|
if (pick && !io.ask) {
|
|
170
170
|
for (;;) {
|
|
171
|
-
const answer = await choose({ ...pick, esc: ticks || io.fromMenu ? 'back' : '
|
|
171
|
+
const answer = await choose({ ...pick, esc: ticks || io.fromMenu ? 'back' : 'cancel' }, 'Make these changes?', [
|
|
172
172
|
{ label: 'Make these changes', value: 'yes' },
|
|
173
173
|
{ label: 'Show every line first', value: 'diff' },
|
|
174
174
|
{ label: 'Cancel', hint: 'change nothing', value: 'no' },
|
package/dist/setup/screen.mjs
CHANGED
|
@@ -208,7 +208,7 @@ function doneSummary(agent, a, files, skipped, p) {
|
|
|
208
208
|
return files ? 'skill installed.' : p.dim('already set up.');
|
|
209
209
|
}
|
|
210
210
|
/** The command that starts each agent that runs in a terminal. Editors (Cursor, Continue, Cline) are opened, not run. */
|
|
211
|
-
const START = { claude: 'claude', codex: 'codex', pi: 'pi', opencode: 'opencode', goose: 'goose', aider: 'aider', copilot: 'copilot' };
|
|
211
|
+
const START = { claude: 'claude', codex: 'codex', pi: 'pi', opencode: 'opencode', goose: 'goose', aider: 'aider', copilot: 'copilot', gemini: 'gemini' };
|
|
212
212
|
/**
|
|
213
213
|
* The one thing to do after setup, in corner brackets: run the agent. When an
|
|
214
214
|
* agent reads its key from the shell (Codex, Goose and others), the step says
|
package/dist/setup/shared.mjs
CHANGED
|
@@ -11,13 +11,14 @@ export function onPath(bin) {
|
|
|
11
11
|
export const SETUP_MODEL = 'phantom-1';
|
|
12
12
|
/**
|
|
13
13
|
* The address Phantom AI serves a coding agent at, from PHANTOM_BASE_URL
|
|
14
|
-
* without its `/v1`: `/claude-code` (Claude Code adds `/v1` itself),
|
|
15
|
-
* `/
|
|
14
|
+
* without its `/v1`: `/claude-code` (Claude Code adds `/v1` itself),
|
|
15
|
+
* `/gemini-cli` (Gemini CLI adds Google's `/v1beta`), or `/codex/v1`,
|
|
16
|
+
* `/cursor/v1` and `/coding-agent/v1` for the others.
|
|
16
17
|
* @throws {@link CliError} when PHANTOM_BASE_URL is not https ({@link httpsOnly}).
|
|
17
18
|
*/
|
|
18
19
|
export function agentBaseUrl(env, agent) {
|
|
19
20
|
const origin = httpsOnly(env.PHANTOM_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, '').replace(/\/v1$/, '');
|
|
20
|
-
return agent === 'claude-code' ? `${origin}
|
|
21
|
+
return agent === 'claude-code' || agent === 'gemini-cli' ? `${origin}/${agent}` : `${origin}/${agent}/v1`;
|
|
21
22
|
}
|
|
22
23
|
/** The end of every line phantom adds to a TOML, YAML or shell file. */
|
|
23
24
|
export const SETUP_MARK = '# phantom setup';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phantomcodes/cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "AI inference for your coding agents. Route models, control spending, and fund your workspace.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"phantom",
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
"license": "SEE LICENSE IN LICENSE",
|
|
67
67
|
"author": "Harbor Financial LLC",
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@types/node": "^24.19.
|
|
69
|
+
"@types/node": "^24.19.2",
|
|
70
70
|
"@vitest/coverage-v8": "^5.0.3",
|
|
71
71
|
"typescript": "^6.0.3",
|
|
72
|
-
"vitest": "^5.0.
|
|
72
|
+
"vitest": "^5.0.5"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|