tokenmix 1.4.16 → 1.5.0
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 +4 -0
- package/dist/agents/aider.js +7 -14
- package/dist/agents/claude.js +4 -22
- package/dist/agents/cline.js +7 -13
- package/dist/agents/codex.js +19 -30
- package/dist/agents/continue.js +7 -12
- package/dist/agents/goose.js +4 -10
- package/dist/agents/helpers.js +36 -0
- package/dist/agents/kilo.js +8 -14
- package/dist/agents/opencode.js +7 -27
- package/dist/agents/openhands.js +6 -11
- package/dist/agents/qwen.js +6 -23
- package/dist/agents/registry.js +0 -3
- package/dist/agents/roo.js +7 -12
- package/dist/api/client.js +8 -5
- package/dist/commands/agent-runner.js +1 -2
- package/dist/commands/balance.js +8 -5
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/logout.js +2 -1
- package/dist/commands/topup.js +3 -3
- package/dist/config/store.js +8 -0
- package/dist/config/urls.js +3 -0
- package/dist/i18n/messages.js +6 -6
- package/dist/program.js +5 -20
- package/dist/utils/prompt.js +1 -10
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# TokenMix CLI
|
|
2
2
|
|
|
3
|
+
[](https://github.com/TokenMixAi/tokenmix-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/tokenmix)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
3
7
|
Zero-config CLI to use any open-source coding agent with [TokenMix](https://tokenmix.ai) as the unified LLM backend.
|
|
4
8
|
|
|
5
9
|
One account, one balance, 160+ models routed automatically across Claude / GPT / Gemini / DeepSeek / Qwen / Moonshot / ...
|
package/dist/agents/aider.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { commandExists, run
|
|
1
|
+
import { commandExists, run } from '../utils/exec.js';
|
|
2
|
+
import { probeVersion } from './helpers.js';
|
|
3
|
+
import { v1Url, DEFAULT_MODEL } from '../config/store.js';
|
|
2
4
|
import { t } from '../i18n/index.js';
|
|
3
5
|
const AIDER_BIN = 'aider';
|
|
4
6
|
async function installCheck() {
|
|
@@ -18,13 +20,7 @@ async function installCheck() {
|
|
|
18
20
|
hint: t('aider.hintNotInstalled', { cmd: installCmd }),
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
const v = await captureRun(AIDER_BIN, ['--version']);
|
|
23
|
-
return { installed: true, version: v.stdout.trim() };
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return { installed: true };
|
|
27
|
-
}
|
|
23
|
+
return probeVersion(AIDER_BIN);
|
|
28
24
|
}
|
|
29
25
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
30
26
|
// Aider reads OPENAI_API_KEY and OPENAI_API_BASE. We pass via env at launch
|
|
@@ -32,13 +28,10 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
32
28
|
return {
|
|
33
29
|
envVars: {
|
|
34
30
|
OPENAI_API_KEY: apiKey,
|
|
35
|
-
OPENAI_API_BASE:
|
|
31
|
+
OPENAI_API_BASE: v1Url(baseUrl),
|
|
36
32
|
TOKENMIX_DEFAULT_MODEL: defaultModel,
|
|
37
33
|
},
|
|
38
|
-
notes: [
|
|
39
|
-
t('aider.noteUsing'),
|
|
40
|
-
t('aider.noteModel', { model: defaultModel }),
|
|
41
|
-
],
|
|
34
|
+
notes: [t('aider.noteUsing'), t('aider.noteModel', { model: defaultModel })],
|
|
42
35
|
};
|
|
43
36
|
}
|
|
44
37
|
// Aider lets you pick a model with --model OR one of its built-in alias flags
|
|
@@ -67,7 +60,7 @@ async function launch(args, env) {
|
|
|
67
60
|
// Inject our default --model only if the user didn't already pick a model.
|
|
68
61
|
const finalArgs = userSelectedModel(args)
|
|
69
62
|
? args
|
|
70
|
-
: ['--model', `openai/${env.TOKENMIX_DEFAULT_MODEL ??
|
|
63
|
+
: ['--model', `openai/${env.TOKENMIX_DEFAULT_MODEL ?? DEFAULT_MODEL}`, ...args];
|
|
71
64
|
await run(AIDER_BIN, finalArgs, { env });
|
|
72
65
|
}
|
|
73
66
|
export const AiderAgent = {
|
package/dist/agents/claude.js
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
-
import {
|
|
4
|
+
import { run } from '../utils/exec.js';
|
|
5
|
+
import { npmInstallCheck, npmInstallGlobal } from './helpers.js';
|
|
5
6
|
import { t } from '../i18n/index.js';
|
|
6
7
|
const CLAUDE_BIN = 'claude';
|
|
7
8
|
const CLAUDE_NPM_PACKAGE = '@anthropic-ai/claude-code';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!bin) {
|
|
11
|
-
const cmd = `npm install -g ${CLAUDE_NPM_PACKAGE}`;
|
|
12
|
-
return {
|
|
13
|
-
installed: false,
|
|
14
|
-
hint: t('install.willInstallVia', { cmd }),
|
|
15
|
-
installCmd: cmd,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
const v = await captureRun(CLAUDE_BIN, ['--version']);
|
|
20
|
-
return { installed: true, version: v.stdout.trim() };
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return { installed: true };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async function install() {
|
|
27
|
-
await run('npm', ['install', '-g', CLAUDE_NPM_PACKAGE]);
|
|
28
|
-
}
|
|
9
|
+
const installCheck = () => npmInstallCheck(CLAUDE_BIN, CLAUDE_NPM_PACKAGE);
|
|
10
|
+
const install = () => npmInstallGlobal(CLAUDE_NPM_PACKAGE);
|
|
29
11
|
async function configure(apiKey, baseUrl, _defaultModel) {
|
|
30
12
|
// Claude Code reads:
|
|
31
13
|
// - env: ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY
|
package/dist/agents/cline.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vscodeConfigOnlyCheck } from './helpers.js';
|
|
2
|
+
import { v1Url } from '../config/store.js';
|
|
2
3
|
import { t } from '../i18n/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// may be copying the config for another machine.
|
|
8
|
-
const code = await commandExists('code');
|
|
9
|
-
return {
|
|
10
|
-
installed: true,
|
|
11
|
-
hint: code ? t('cline.hintMarketplace') : t('cline.hintNoVscode'),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
4
|
+
// Cline is a config-only agent (VSCode extension): the CLI can't install the
|
|
5
|
+
// extension, so installCheck always reports installed and configure() prints the
|
|
6
|
+
// settings to paste (the user may even be copying the config for another machine).
|
|
7
|
+
const installCheck = () => vscodeConfigOnlyCheck('cline.hintMarketplace', 'cline.hintNoVscode');
|
|
14
8
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
15
9
|
// Cline is a VSCode extension configured through its settings panel
|
|
16
10
|
// (API Provider → "OpenAI Compatible"). Unlike Kilo it exposes no documented
|
|
@@ -22,7 +16,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
22
16
|
t('cline.noteConfigWith'),
|
|
23
17
|
'',
|
|
24
18
|
` Provider: OpenAI Compatible`,
|
|
25
|
-
` Base URL: ${baseUrl}
|
|
19
|
+
` Base URL: ${v1Url(baseUrl)}`,
|
|
26
20
|
` API Key: ${apiKey}`,
|
|
27
21
|
` Model ID: ${defaultModel}`,
|
|
28
22
|
'',
|
package/dist/agents/codex.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { run } from '../utils/exec.js';
|
|
2
|
+
import { npmInstallCheck, npmInstallGlobal } from './helpers.js';
|
|
3
|
+
import { v1Url, DEFAULT_MODEL } from '../config/store.js';
|
|
2
4
|
import { t } from '../i18n/index.js';
|
|
3
5
|
const CODEX_BIN = 'codex';
|
|
4
6
|
const CODEX_NPM_PACKAGE = '@openai/codex';
|
|
@@ -8,27 +10,8 @@ const PROVIDER_ID = 'tokenmix';
|
|
|
8
10
|
// Env var Codex reads for the bearer token (the provider's `env_key`). We set it
|
|
9
11
|
// at launch from the user's TokenMix key — nothing is written to ~/.codex.
|
|
10
12
|
const KEY_ENV = 'TOKENMIX_API_KEY';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!bin) {
|
|
14
|
-
const cmd = `npm install -g ${CODEX_NPM_PACKAGE}`;
|
|
15
|
-
return {
|
|
16
|
-
installed: false,
|
|
17
|
-
hint: t('install.willInstallVia', { cmd }),
|
|
18
|
-
installCmd: cmd,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const v = await captureRun(CODEX_BIN, ['--version']);
|
|
23
|
-
return { installed: true, version: v.stdout.trim() };
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return { installed: true };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async function install() {
|
|
30
|
-
await run('npm', ['install', '-g', CODEX_NPM_PACKAGE]);
|
|
31
|
-
}
|
|
13
|
+
const installCheck = () => npmInstallCheck(CODEX_BIN, CODEX_NPM_PACKAGE);
|
|
14
|
+
const install = () => npmInstallGlobal(CODEX_NPM_PACKAGE);
|
|
32
15
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
33
16
|
// We do NOT write ~/.codex/config.toml. Codex accepts a whole custom provider
|
|
34
17
|
// via `--config` overrides at launch, and reads the key from the env var named
|
|
@@ -37,7 +20,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
37
20
|
return {
|
|
38
21
|
envVars: {
|
|
39
22
|
[KEY_ENV]: apiKey,
|
|
40
|
-
TOKENMIX_BASE_URL:
|
|
23
|
+
TOKENMIX_BASE_URL: v1Url(baseUrl),
|
|
41
24
|
},
|
|
42
25
|
notes: [t('codex.noteUsing'), t('codex.noteModel', { model: defaultModel })],
|
|
43
26
|
};
|
|
@@ -48,12 +31,18 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
48
31
|
// Responses API specifically for Codex clients (POST /v1/responses).
|
|
49
32
|
export function providerOverrides(baseUrl, model) {
|
|
50
33
|
return [
|
|
51
|
-
'--config',
|
|
52
|
-
|
|
53
|
-
'--config',
|
|
54
|
-
|
|
55
|
-
'--config',
|
|
56
|
-
|
|
34
|
+
'--config',
|
|
35
|
+
`model_provider="${PROVIDER_ID}"`,
|
|
36
|
+
'--config',
|
|
37
|
+
`model="${model}"`,
|
|
38
|
+
'--config',
|
|
39
|
+
`model_providers.${PROVIDER_ID}.name="TokenMix"`,
|
|
40
|
+
'--config',
|
|
41
|
+
`model_providers.${PROVIDER_ID}.base_url="${baseUrl}"`,
|
|
42
|
+
'--config',
|
|
43
|
+
`model_providers.${PROVIDER_ID}.env_key="${KEY_ENV}"`,
|
|
44
|
+
'--config',
|
|
45
|
+
`model_providers.${PROVIDER_ID}.wire_api="responses"`,
|
|
57
46
|
];
|
|
58
47
|
}
|
|
59
48
|
async function launch(args, env) {
|
|
@@ -64,7 +53,7 @@ async function launch(args, env) {
|
|
|
64
53
|
await run(CODEX_BIN, args, { env });
|
|
65
54
|
return;
|
|
66
55
|
}
|
|
67
|
-
const model = env.TOKENMIX_DEFAULT_MODEL ??
|
|
56
|
+
const model = env.TOKENMIX_DEFAULT_MODEL ?? DEFAULT_MODEL;
|
|
68
57
|
// Our overrides go first so user-supplied args (e.g. `--config model=...`) win.
|
|
69
58
|
await run(CODEX_BIN, [...providerOverrides(baseUrl, model), ...args], { env });
|
|
70
59
|
}
|
package/dist/agents/continue.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vscodeConfigOnlyCheck } from './helpers.js';
|
|
2
|
+
import { v1Url } from '../config/store.js';
|
|
2
3
|
import { t } from '../i18n/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const code = await commandExists('code');
|
|
8
|
-
return {
|
|
9
|
-
installed: true,
|
|
10
|
-
hint: code ? t('continue.hintMarketplace') : t('continue.hintNoVscode'),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
4
|
+
// Continue is a config-only agent (VSCode/JetBrains extension): the CLI can't
|
|
5
|
+
// install the extension, so installCheck always reports installed and configure()
|
|
6
|
+
// prints the config to paste into ~/.continue/config.yaml.
|
|
7
|
+
const installCheck = () => vscodeConfigOnlyCheck('continue.hintMarketplace', 'continue.hintNoVscode');
|
|
13
8
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
14
9
|
// Continue is driven by ~/.continue/config.yaml (verified schema: top-level
|
|
15
10
|
// name/version/schema + a `models:` list; each model needs name/provider/model,
|
|
@@ -24,7 +19,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
24
19
|
' - name: TokenMix',
|
|
25
20
|
' provider: openai',
|
|
26
21
|
` model: ${defaultModel}`,
|
|
27
|
-
` apiBase: ${baseUrl}
|
|
22
|
+
` apiBase: ${v1Url(baseUrl)}`,
|
|
28
23
|
` apiKey: ${apiKey}`,
|
|
29
24
|
].join('\n');
|
|
30
25
|
return {
|
package/dist/agents/goose.js
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
import { commandExists, run
|
|
1
|
+
import { commandExists, run } from '../utils/exec.js';
|
|
2
|
+
import { probeVersion } from './helpers.js';
|
|
2
3
|
import { t } from '../i18n/index.js';
|
|
3
4
|
const GOOSE_BIN = 'goose';
|
|
4
5
|
// Goose ships as a Rust binary via an official install script. We do NOT auto-run
|
|
5
6
|
// a `curl | bash`; we print it so the user installs it deliberately.
|
|
6
7
|
const GOOSE_INSTALL = 'curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash';
|
|
7
8
|
async function installCheck() {
|
|
8
|
-
|
|
9
|
-
if (!bin) {
|
|
9
|
+
if (!(await commandExists(GOOSE_BIN))) {
|
|
10
10
|
return {
|
|
11
11
|
installed: false,
|
|
12
12
|
hint: t('goose.hintInstall', { cmd: GOOSE_INSTALL }),
|
|
13
13
|
installCmd: GOOSE_INSTALL,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
const v = await captureRun(GOOSE_BIN, ['--version']);
|
|
18
|
-
return { installed: true, version: v.stdout.trim() };
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return { installed: true };
|
|
22
|
-
}
|
|
16
|
+
return probeVersion(GOOSE_BIN);
|
|
23
17
|
}
|
|
24
18
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
25
19
|
// Goose reads GOOSE_PROVIDER/GOOSE_MODEL + OPENAI_HOST/OPENAI_API_KEY. NOTE:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { commandExists, run, captureRun } from '../utils/exec.js';
|
|
2
|
+
import { t } from '../i18n/index.js';
|
|
3
|
+
// Shared install-check / install helpers, so each agent descriptor stays a thin
|
|
4
|
+
// declaration instead of repeating the same boilerplate.
|
|
5
|
+
// Best-effort version probe for an installed agent binary. `<bin> --version` is
|
|
6
|
+
// advisory, so a missing or unparseable version still reports installed.
|
|
7
|
+
export async function probeVersion(bin) {
|
|
8
|
+
try {
|
|
9
|
+
const v = await captureRun(bin, ['--version']);
|
|
10
|
+
return { installed: true, version: v.stdout.trim() };
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { installed: true };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Install-check for an agent shipped as a global npm package: present → probe
|
|
17
|
+
// version; absent → offer the `npm install -g <pkg>` command.
|
|
18
|
+
export async function npmInstallCheck(bin, npmPackage) {
|
|
19
|
+
if (!(await commandExists(bin))) {
|
|
20
|
+
const cmd = `npm install -g ${npmPackage}`;
|
|
21
|
+
return { installed: false, hint: t('install.willInstallVia', { cmd }), installCmd: cmd };
|
|
22
|
+
}
|
|
23
|
+
return probeVersion(bin);
|
|
24
|
+
}
|
|
25
|
+
// Install a global npm package (the auto-install path for npm-based agents).
|
|
26
|
+
export async function npmInstallGlobal(npmPackage) {
|
|
27
|
+
await run('npm', ['install', '-g', npmPackage]);
|
|
28
|
+
}
|
|
29
|
+
// Install-check for config-only VSCode-extension agents (Kilo / Cline / Roo /
|
|
30
|
+
// Continue): the CLI can't install the extension, so it's always "installed";
|
|
31
|
+
// the hint nudges toward installing VSCode + the extension when `code` isn't on
|
|
32
|
+
// PATH (vs. the marketplace hint when it is).
|
|
33
|
+
export async function vscodeConfigOnlyCheck(marketplaceHint, noVscodeHint) {
|
|
34
|
+
const code = await commandExists('code');
|
|
35
|
+
return { installed: true, hint: code ? t(marketplaceHint) : t(noVscodeHint) };
|
|
36
|
+
}
|
package/dist/agents/kilo.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vscodeConfigOnlyCheck } from './helpers.js';
|
|
2
|
+
import { v1Url } from '../config/store.js';
|
|
2
3
|
import { t } from '../i18n/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// may be copying the config for another machine.
|
|
8
|
-
const code = await commandExists('code');
|
|
9
|
-
return {
|
|
10
|
-
installed: true,
|
|
11
|
-
hint: code ? t('kilo.hintMarketplace') : t('kilo.hintNoVscode'),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
4
|
+
// Kilo Code is a config-only agent (VSCode extension): the CLI can't install the
|
|
5
|
+
// extension, so installCheck always reports installed and configure() prints the
|
|
6
|
+
// snippet (the user may even be copying the config for another machine).
|
|
7
|
+
const installCheck = () => vscodeConfigOnlyCheck('kilo.hintMarketplace', 'kilo.hintNoVscode');
|
|
14
8
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
15
9
|
// Kilo Code is a VSCode extension; there is no CLI launcher.
|
|
16
10
|
// We print the configuration values for the user to paste into Kilo settings.
|
|
@@ -20,7 +14,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
20
14
|
t('kilo.noteConfigWith'),
|
|
21
15
|
'',
|
|
22
16
|
` Provider: OpenAI Compatible`,
|
|
23
|
-
` Base URL: ${baseUrl}
|
|
17
|
+
` Base URL: ${v1Url(baseUrl)}`,
|
|
24
18
|
` API Key: ${apiKey}`,
|
|
25
19
|
` Default Model: ${defaultModel}`,
|
|
26
20
|
'',
|
|
@@ -28,7 +22,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
28
22
|
'',
|
|
29
23
|
JSON.stringify({
|
|
30
24
|
provider: 'openai-compatible',
|
|
31
|
-
openAiBaseUrl:
|
|
25
|
+
openAiBaseUrl: v1Url(baseUrl),
|
|
32
26
|
openAiApiKey: apiKey,
|
|
33
27
|
defaultModelId: defaultModel,
|
|
34
28
|
}, null, 2),
|
package/dist/agents/opencode.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
-
import {
|
|
4
|
+
import { run } from '../utils/exec.js';
|
|
5
|
+
import { npmInstallCheck, npmInstallGlobal } from './helpers.js';
|
|
6
|
+
import { v1Url } from '../config/store.js';
|
|
5
7
|
import { t } from '../i18n/index.js';
|
|
6
8
|
const OPENCODE_BIN = 'opencode';
|
|
7
9
|
const OPENCODE_NPM_PACKAGE = 'opencode-ai';
|
|
@@ -14,27 +16,8 @@ function configPath() {
|
|
|
14
16
|
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
15
17
|
return path.join(xdgHome, 'opencode', 'opencode.json');
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!bin) {
|
|
20
|
-
const cmd = `npm install -g ${OPENCODE_NPM_PACKAGE}`;
|
|
21
|
-
return {
|
|
22
|
-
installed: false,
|
|
23
|
-
hint: t('install.willInstallVia', { cmd }),
|
|
24
|
-
installCmd: cmd,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const v = await captureRun(OPENCODE_BIN, ['--version']);
|
|
29
|
-
return { installed: true, version: v.stdout.trim() };
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return { installed: true };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function install() {
|
|
36
|
-
await run('npm', ['install', '-g', OPENCODE_NPM_PACKAGE]);
|
|
37
|
-
}
|
|
19
|
+
const installCheck = () => npmInstallCheck(OPENCODE_BIN, OPENCODE_NPM_PACKAGE);
|
|
20
|
+
const install = () => npmInstallGlobal(OPENCODE_NPM_PACKAGE);
|
|
38
21
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
39
22
|
const filePath = configPath();
|
|
40
23
|
let existing = {};
|
|
@@ -51,7 +34,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
51
34
|
npm: '@ai-sdk/openai-compatible',
|
|
52
35
|
name: 'TokenMix',
|
|
53
36
|
options: {
|
|
54
|
-
baseURL:
|
|
37
|
+
baseURL: v1Url(baseUrl),
|
|
55
38
|
apiKey,
|
|
56
39
|
},
|
|
57
40
|
// Listed models populate /connect picker; users can still type any tokenmix short_id.
|
|
@@ -72,10 +55,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
72
55
|
await fs.writeFile(filePath, JSON.stringify(next, null, 2));
|
|
73
56
|
return {
|
|
74
57
|
configPath: filePath,
|
|
75
|
-
notes: [
|
|
76
|
-
t('opencode.noteModel', { model: defaultModel }),
|
|
77
|
-
t('opencode.noteSwitch'),
|
|
78
|
-
],
|
|
58
|
+
notes: [t('opencode.noteModel', { model: defaultModel }), t('opencode.noteSwitch')],
|
|
79
59
|
};
|
|
80
60
|
}
|
|
81
61
|
async function launch(args) {
|
package/dist/agents/openhands.js
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import { commandExists, run
|
|
1
|
+
import { commandExists, run } from '../utils/exec.js';
|
|
2
|
+
import { probeVersion } from './helpers.js';
|
|
3
|
+
import { v1Url } from '../config/store.js';
|
|
2
4
|
import { t } from '../i18n/index.js';
|
|
3
5
|
const OPENHANDS_BIN = 'openhands';
|
|
4
6
|
// OpenHands needs Python 3.12+; `uv` pulls a matching Python automatically. We
|
|
5
7
|
// print this rather than auto-running it (it installs a toolchain + Python).
|
|
6
8
|
const OPENHANDS_INSTALL = 'uv tool install openhands --python 3.12';
|
|
7
9
|
async function installCheck() {
|
|
8
|
-
|
|
9
|
-
if (!bin) {
|
|
10
|
+
if (!(await commandExists(OPENHANDS_BIN))) {
|
|
10
11
|
return {
|
|
11
12
|
installed: false,
|
|
12
13
|
hint: t('openhands.hintInstall', { cmd: OPENHANDS_INSTALL }),
|
|
13
14
|
installCmd: OPENHANDS_INSTALL,
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
const v = await captureRun(OPENHANDS_BIN, ['--version']);
|
|
18
|
-
return { installed: true, version: v.stdout.trim() };
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return { installed: true };
|
|
22
|
-
}
|
|
17
|
+
return probeVersion(OPENHANDS_BIN);
|
|
23
18
|
}
|
|
24
19
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
25
20
|
// OpenHands reads LLM_API_KEY/LLM_MODEL/LLM_BASE_URL but ONLY when launched with
|
|
@@ -29,7 +24,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
29
24
|
envVars: {
|
|
30
25
|
LLM_API_KEY: apiKey,
|
|
31
26
|
LLM_MODEL: `openai/${defaultModel}`,
|
|
32
|
-
LLM_BASE_URL:
|
|
27
|
+
LLM_BASE_URL: v1Url(baseUrl),
|
|
33
28
|
OPENHANDS_SUPPRESS_BANNER: '1',
|
|
34
29
|
},
|
|
35
30
|
notes: [t('openhands.noteUsing'), t('openhands.noteModel', { model: defaultModel })],
|
package/dist/agents/qwen.js
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { run } from '../utils/exec.js';
|
|
2
|
+
import { npmInstallCheck, npmInstallGlobal } from './helpers.js';
|
|
3
|
+
import { v1Url } from '../config/store.js';
|
|
2
4
|
import { t } from '../i18n/index.js';
|
|
3
5
|
const QWEN_BIN = 'qwen';
|
|
4
6
|
const QWEN_NPM_PACKAGE = '@qwen-code/qwen-code';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!bin) {
|
|
8
|
-
const cmd = `npm install -g ${QWEN_NPM_PACKAGE}`;
|
|
9
|
-
return {
|
|
10
|
-
installed: false,
|
|
11
|
-
hint: t('install.willInstallVia', { cmd }),
|
|
12
|
-
installCmd: cmd,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const v = await captureRun(QWEN_BIN, ['--version']);
|
|
17
|
-
return { installed: true, version: v.stdout.trim() };
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return { installed: true };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function install() {
|
|
24
|
-
await run('npm', ['install', '-g', `${QWEN_NPM_PACKAGE}@latest`]);
|
|
25
|
-
}
|
|
7
|
+
const installCheck = () => npmInstallCheck(QWEN_BIN, QWEN_NPM_PACKAGE);
|
|
8
|
+
const install = () => npmInstallGlobal(`${QWEN_NPM_PACKAGE}@latest`);
|
|
26
9
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
27
10
|
// Qwen Code (a Gemini CLI fork) reads OPENAI_API_KEY / OPENAI_BASE_URL /
|
|
28
11
|
// OPENAI_MODEL when launched with `--auth-type openai` — VERIFIED end-to-end
|
|
@@ -31,7 +14,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
31
14
|
return {
|
|
32
15
|
envVars: {
|
|
33
16
|
OPENAI_API_KEY: apiKey,
|
|
34
|
-
OPENAI_BASE_URL:
|
|
17
|
+
OPENAI_BASE_URL: v1Url(baseUrl),
|
|
35
18
|
OPENAI_MODEL: defaultModel,
|
|
36
19
|
},
|
|
37
20
|
notes: [t('qwen.noteUsing'), t('qwen.noteModel', { model: defaultModel })],
|
package/dist/agents/registry.js
CHANGED
package/dist/agents/roo.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vscodeConfigOnlyCheck } from './helpers.js';
|
|
2
|
+
import { v1Url } from '../config/store.js';
|
|
2
3
|
import { t } from '../i18n/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const code = await commandExists('code');
|
|
8
|
-
return {
|
|
9
|
-
installed: true,
|
|
10
|
-
hint: code ? t('roo.hintMarketplace') : t('roo.hintNoVscode'),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
4
|
+
// Roo Code is a config-only agent (VSCode extension, a Cline fork): the CLI can't
|
|
5
|
+
// install the extension, so installCheck always reports installed and configure()
|
|
6
|
+
// prints the settings (the user may also be copying the config for another machine).
|
|
7
|
+
const installCheck = () => vscodeConfigOnlyCheck('roo.hintMarketplace', 'roo.hintNoVscode');
|
|
13
8
|
async function configure(apiKey, baseUrl, defaultModel) {
|
|
14
9
|
// Roo Code is configured through its settings panel (API Provider →
|
|
15
10
|
// "OpenAI Compatible"), exactly like Cline. No documented settings.json
|
|
@@ -20,7 +15,7 @@ async function configure(apiKey, baseUrl, defaultModel) {
|
|
|
20
15
|
t('roo.noteConfigWith'),
|
|
21
16
|
'',
|
|
22
17
|
` Provider: OpenAI Compatible`,
|
|
23
|
-
` Base URL: ${baseUrl}
|
|
18
|
+
` Base URL: ${v1Url(baseUrl)}`,
|
|
24
19
|
` API Key: ${apiKey}`,
|
|
25
20
|
` Model ID: ${defaultModel}`,
|
|
26
21
|
'',
|
package/dist/api/client.js
CHANGED
|
@@ -12,14 +12,17 @@ export function unwrap(resp) {
|
|
|
12
12
|
if (body && typeof body.code === 'number' && body.code !== 0) {
|
|
13
13
|
throw new ApiError(0, body.message || 'API error');
|
|
14
14
|
}
|
|
15
|
-
return
|
|
15
|
+
// Standard success envelope → return the inner `data`. A body with no envelope
|
|
16
|
+
// (no `data` field — e.g. a raw array) is passed straight through: a deliberate
|
|
17
|
+
// fallback for endpoints that don't wrap their payload (locked by a unit test).
|
|
18
|
+
if (body && body.data != null)
|
|
19
|
+
return body.data;
|
|
20
|
+
return body;
|
|
16
21
|
}
|
|
17
22
|
function handleAxios(err) {
|
|
18
23
|
const e = err;
|
|
19
24
|
if (e.response) {
|
|
20
|
-
const msg = e.response.data?.message ||
|
|
21
|
-
e.response.data?.error?.message ||
|
|
22
|
-
e.message;
|
|
25
|
+
const msg = e.response.data?.message || e.response.data?.error?.message || e.message;
|
|
23
26
|
throw new ApiError(e.response.status, msg);
|
|
24
27
|
}
|
|
25
28
|
throw new ApiError(0, `Could not reach the TokenMix API (${e.message || 'network error'}). Check your internet connection or proxy.`);
|
|
@@ -56,7 +59,7 @@ export async function listPublicModels(cfg) {
|
|
|
56
59
|
timeout: REQUEST_TIMEOUT_MS,
|
|
57
60
|
}));
|
|
58
61
|
const list = unwrap(r);
|
|
59
|
-
return Array.isArray(list) ? list : list?.list ?? [];
|
|
62
|
+
return Array.isArray(list) ? list : (list?.list ?? []);
|
|
60
63
|
}
|
|
61
64
|
catch (err) {
|
|
62
65
|
handleAxios(err);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
|
-
import { readConfig, apiBaseUrl } from '../config/store.js';
|
|
2
|
+
import { readConfig, apiBaseUrl, DEFAULT_MODEL } from '../config/store.js';
|
|
3
3
|
import { confirm } from '../utils/prompt.js';
|
|
4
4
|
import { AGENTS } from '../agents/registry.js';
|
|
5
5
|
import { t } from '../i18n/index.js';
|
|
6
|
-
const DEFAULT_MODEL = 'claude-sonnet-4.6';
|
|
7
6
|
// Major version of the running Node (e.g. 22 from "v22.9.0"). Gates agents whose
|
|
8
7
|
// binary needs a newer Node than this process (Codex / Qwen Code require 22).
|
|
9
8
|
export function nodeMajor() {
|
package/dist/commands/balance.js
CHANGED
|
@@ -3,9 +3,8 @@ import { logger } from '../utils/logger.js';
|
|
|
3
3
|
import { openOrHint } from '../utils/browser.js';
|
|
4
4
|
import { readConfig, apiBaseUrl } from '../config/store.js';
|
|
5
5
|
import { fetchWallet } from '../api/client.js';
|
|
6
|
+
import { DASHBOARD_URL, DASHBOARD_CREDITS_URL } from '../config/urls.js';
|
|
6
7
|
import { t } from '../i18n/index.js';
|
|
7
|
-
const DASHBOARD_URL = 'https://tokenmix.ai/dashboard';
|
|
8
|
-
const TOPUP_URL = 'https://tokenmix.ai/dashboard/credits';
|
|
9
8
|
// micro-USD → display string. Mirrors the platform / plugin: 2 decimals for
|
|
10
9
|
// amounts >= $1 (trailing zeros trimmed), more precision for sub-dollar amounts.
|
|
11
10
|
export function formatUSD(microUsd) {
|
|
@@ -32,12 +31,16 @@ export async function balanceCommand() {
|
|
|
32
31
|
console.log(` ${t('balance.reservedLabel')}: $${formatUSD(w.frozen)}`);
|
|
33
32
|
}
|
|
34
33
|
console.log();
|
|
35
|
-
logger.dim(t('balance.topupAt', { url:
|
|
34
|
+
logger.dim(t('balance.topupAt', { url: DASHBOARD_CREDITS_URL }));
|
|
36
35
|
console.log();
|
|
37
36
|
}
|
|
38
|
-
catch {
|
|
39
|
-
//
|
|
37
|
+
catch (err) {
|
|
38
|
+
// Couldn't fetch the wallet (network failure, or an invalid/expired key).
|
|
39
|
+
// Surface the reason — consistent with doctor/login — then fall back to the
|
|
40
|
+
// dashboard so the user isn't stuck.
|
|
40
41
|
logger.warn(t('balance.fetchFailed'));
|
|
42
|
+
if (err instanceof Error && err.message)
|
|
43
|
+
logger.dim(err.message);
|
|
41
44
|
logger.step(t('balance.opening', { url: DASHBOARD_URL }));
|
|
42
45
|
await openOrHint(DASHBOARD_URL);
|
|
43
46
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -40,6 +40,15 @@ export async function doctorCommand() {
|
|
|
40
40
|
console.log(chalk.bold(t('doctor.agentStatus')));
|
|
41
41
|
for (const a of AGENTS) {
|
|
42
42
|
const r = await a.installCheck();
|
|
43
|
+
// Config-only agents (VSCode extensions) have no binary of their own to install,
|
|
44
|
+
// so installCheck always reports installed:true — labeling them "installed" is
|
|
45
|
+
// misleading. Show their install mode instead, matching `tokenmix list`.
|
|
46
|
+
if (a.installMode === 'manual-vscode') {
|
|
47
|
+
logger.success(`${a.displayName.padEnd(14)} ${chalk.dim(t('list.modeManualVscode'))}`);
|
|
48
|
+
if (r.hint)
|
|
49
|
+
console.log(` ${chalk.dim(r.hint)}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
43
52
|
if (r.installed) {
|
|
44
53
|
logger.success(`${a.displayName.padEnd(14)} ${t('doctor.installed')}${r.version ? ` (${chalk.dim(r.version)})` : ''}`);
|
|
45
54
|
if (r.hint)
|
package/dist/commands/logout.js
CHANGED
|
@@ -14,7 +14,8 @@ export async function logoutCommand() {
|
|
|
14
14
|
try {
|
|
15
15
|
const r = await agent.cleanup();
|
|
16
16
|
if (r.reverted) {
|
|
17
|
-
logger.success(t('logout.reverted', { name: agent.displayName }) +
|
|
17
|
+
logger.success(t('logout.reverted', { name: agent.displayName }) +
|
|
18
|
+
(r.configPath ? ` (${r.configPath})` : ''));
|
|
18
19
|
if (r.note)
|
|
19
20
|
logger.dim(` ${r.note}`);
|
|
20
21
|
}
|
package/dist/commands/topup.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
2
|
import { openOrHint } from '../utils/browser.js';
|
|
3
|
+
import { DASHBOARD_CREDITS_URL } from '../config/urls.js';
|
|
3
4
|
import { t } from '../i18n/index.js';
|
|
4
|
-
const TOPUP_URL = 'https://tokenmix.ai/dashboard/credits';
|
|
5
5
|
export async function topupCommand() {
|
|
6
|
-
logger.step(t('topup.opening', { url:
|
|
7
|
-
await openOrHint(
|
|
6
|
+
logger.step(t('topup.opening', { url: DASHBOARD_CREDITS_URL }));
|
|
7
|
+
await openOrHint(DASHBOARD_CREDITS_URL);
|
|
8
8
|
}
|
package/dist/config/store.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { configDir, configFile } from './paths.js';
|
|
3
3
|
export const DEFAULT_API_BASE = 'https://api.tokenmix.ai';
|
|
4
|
+
// The model agents default to when the user hasn't chosen one (overridable via
|
|
5
|
+
// the TOKENMIX_DEFAULT_MODEL env var or stored config). Single source of truth.
|
|
6
|
+
export const DEFAULT_MODEL = 'claude-sonnet-4.6';
|
|
7
|
+
// Append the OpenAI-compatible `/v1` suffix to a base URL, tolerating a trailing
|
|
8
|
+
// slash so `https://host/` doesn't yield `https://host//v1`.
|
|
9
|
+
export function v1Url(baseUrl) {
|
|
10
|
+
return `${baseUrl.replace(/\/+$/, '')}/v1`;
|
|
11
|
+
}
|
|
4
12
|
export async function readConfig() {
|
|
5
13
|
try {
|
|
6
14
|
const raw = await fs.readFile(configFile(), 'utf-8');
|
package/dist/i18n/messages.js
CHANGED
|
@@ -102,7 +102,7 @@ export const en = {
|
|
|
102
102
|
// install hints
|
|
103
103
|
'install.willInstallVia': 'Will install via: {cmd}',
|
|
104
104
|
'aider.hintNeedPython': 'Aider requires Python 3. Install Python 3 from https://python.org/downloads, then come back and run `tokenmix aider` again.',
|
|
105
|
-
'aider.hintNotInstalled': 'Aider is not installed. Run this in another terminal:\n {cmd}
|
|
105
|
+
'aider.hintNotInstalled': 'Aider is not installed. Run this in another terminal:\n {cmd}',
|
|
106
106
|
// opencode configure notes
|
|
107
107
|
'opencode.noteModel': 'Default model set to tokenmix/{model}',
|
|
108
108
|
'opencode.noteSwitch': 'To switch models, run `tokenmix models` or use `/connect` inside OpenCode.',
|
|
@@ -279,7 +279,7 @@ export const zh = {
|
|
|
279
279
|
// install hints
|
|
280
280
|
'install.willInstallVia': '将自动安装:{cmd}',
|
|
281
281
|
'aider.hintNeedPython': 'Aider 需要 Python 3。请从 https://python.org/downloads 安装 Python 3,然后重新运行 `tokenmix aider`。',
|
|
282
|
-
'aider.hintNotInstalled': 'Aider 尚未安装。请在另一个终端运行:\n {cmd}
|
|
282
|
+
'aider.hintNotInstalled': 'Aider 尚未安装。请在另一个终端运行:\n {cmd}',
|
|
283
283
|
// opencode configure notes
|
|
284
284
|
'opencode.noteModel': '默认模型已设为 tokenmix/{model}',
|
|
285
285
|
'opencode.noteSwitch': '切换模型:运行 `tokenmix models`,或在 OpenCode 内使用 `/connect`。',
|
|
@@ -439,7 +439,7 @@ export const ja = {
|
|
|
439
439
|
'desc.openhands': 'All-Hands-AI/OpenHands — 自律型コーディング agent(OpenAI 互換)',
|
|
440
440
|
'install.willInstallVia': '次の方法でインストールします:{cmd}',
|
|
441
441
|
'aider.hintNeedPython': 'Aider には Python 3 が必要です。https://python.org/downloads から Python 3 をインストールし、再度 `tokenmix aider` を実行してください。',
|
|
442
|
-
'aider.hintNotInstalled': 'Aider はインストールされていません。別のターミナルで次を実行してください:\n {cmd}
|
|
442
|
+
'aider.hintNotInstalled': 'Aider はインストールされていません。別のターミナルで次を実行してください:\n {cmd}',
|
|
443
443
|
'opencode.noteModel': 'デフォルトモデルを tokenmix/{model} に設定しました',
|
|
444
444
|
'opencode.noteSwitch': 'モデルを切り替えるには `tokenmix models` を実行するか、OpenCode 内で `/connect` を使用してください。',
|
|
445
445
|
'claude.noteModels': 'tokenmix 経由で利用できる Claude モデル:claude-opus-4.7、claude-sonnet-4.6、claude-haiku-4.5',
|
|
@@ -594,7 +594,7 @@ export const ko = {
|
|
|
594
594
|
'desc.openhands': 'All-Hands-AI/OpenHands — 자율 코딩 agent (OpenAI 호환)',
|
|
595
595
|
'install.willInstallVia': '다음 방법으로 설치합니다: {cmd}',
|
|
596
596
|
'aider.hintNeedPython': 'Aider에는 Python 3가 필요합니다. https://python.org/downloads 에서 Python 3를 설치한 뒤 다시 `tokenmix aider`를 실행하세요.',
|
|
597
|
-
'aider.hintNotInstalled': 'Aider가 설치되어 있지 않습니다. 다른 터미널에서 다음을 실행하세요:\n {cmd}
|
|
597
|
+
'aider.hintNotInstalled': 'Aider가 설치되어 있지 않습니다. 다른 터미널에서 다음을 실행하세요:\n {cmd}',
|
|
598
598
|
'opencode.noteModel': '기본 모델을 tokenmix/{model}(으)로 설정했습니다',
|
|
599
599
|
'opencode.noteSwitch': '모델을 바꾸려면 `tokenmix models`를 실행하거나 OpenCode 안에서 `/connect`를 사용하세요.',
|
|
600
600
|
'claude.noteModels': 'tokenmix를 통해 사용할 수 있는 Claude 모델: claude-opus-4.7, claude-sonnet-4.6, claude-haiku-4.5',
|
|
@@ -749,7 +749,7 @@ export const es = {
|
|
|
749
749
|
'desc.openhands': 'All-Hands-AI/OpenHands — agente de programación autónomo (compatible con OpenAI)',
|
|
750
750
|
'install.willInstallVia': 'Se instalará mediante: {cmd}',
|
|
751
751
|
'aider.hintNeedPython': 'Aider requiere Python 3. Instala Python 3 desde https://python.org/downloads y vuelve a ejecutar `tokenmix aider`.',
|
|
752
|
-
'aider.hintNotInstalled': 'Aider no está instalado. Ejecuta esto en otra terminal:\n {cmd}
|
|
752
|
+
'aider.hintNotInstalled': 'Aider no está instalado. Ejecuta esto en otra terminal:\n {cmd}',
|
|
753
753
|
'opencode.noteModel': 'Modelo predeterminado establecido en tokenmix/{model}',
|
|
754
754
|
'opencode.noteSwitch': 'Para cambiar de modelo, ejecuta `tokenmix models` o usa `/connect` dentro de OpenCode.',
|
|
755
755
|
'claude.noteModels': 'Modelos de Claude disponibles vía tokenmix: claude-opus-4.7, claude-sonnet-4.6, claude-haiku-4.5',
|
|
@@ -904,7 +904,7 @@ export const fr = {
|
|
|
904
904
|
'desc.openhands': 'All-Hands-AI/OpenHands — agent de codage autonome (compatible OpenAI)',
|
|
905
905
|
'install.willInstallVia': 'Sera installé via : {cmd}',
|
|
906
906
|
'aider.hintNeedPython': 'Aider nécessite Python 3. Installez Python 3 depuis https://python.org/downloads, puis relancez `tokenmix aider`.',
|
|
907
|
-
'aider.hintNotInstalled': 'Aider n’est pas installé. Exécutez ceci dans un autre terminal :\n {cmd}
|
|
907
|
+
'aider.hintNotInstalled': 'Aider n’est pas installé. Exécutez ceci dans un autre terminal :\n {cmd}',
|
|
908
908
|
'opencode.noteModel': 'Modèle par défaut défini sur tokenmix/{model}',
|
|
909
909
|
'opencode.noteSwitch': 'Pour changer de modèle, exécutez `tokenmix models` ou utilisez `/connect` dans OpenCode.',
|
|
910
910
|
'claude.noteModels': 'Modèles Claude disponibles via tokenmix : claude-opus-4.7, claude-sonnet-4.6, claude-haiku-4.5',
|
package/dist/program.js
CHANGED
|
@@ -33,32 +33,17 @@ export function buildProgram(deps = {}) {
|
|
|
33
33
|
.option('-p, --paste', t('cmd.loginPaste'))
|
|
34
34
|
.option('-u, --url <baseUrl>', t('cmd.loginUrl'))
|
|
35
35
|
.action(loginCommand);
|
|
36
|
-
program
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.action(logoutCommand);
|
|
40
|
-
program
|
|
41
|
-
.command('balance')
|
|
42
|
-
.description(t('cmd.balance'))
|
|
43
|
-
.action(balanceCommand);
|
|
44
|
-
program
|
|
45
|
-
.command('topup')
|
|
46
|
-
.description(t('cmd.topup'))
|
|
47
|
-
.action(topupCommand);
|
|
36
|
+
program.command('logout').description(t('cmd.logout')).action(logoutCommand);
|
|
37
|
+
program.command('balance').description(t('cmd.balance')).action(balanceCommand);
|
|
38
|
+
program.command('topup').description(t('cmd.topup')).action(topupCommand);
|
|
48
39
|
program
|
|
49
40
|
.command('models')
|
|
50
41
|
.description(t('cmd.models'))
|
|
51
42
|
.option('-t, --type <type>', t('cmd.modelsType'))
|
|
52
43
|
.option('-s, --search <keyword>', t('cmd.modelsSearch'))
|
|
53
44
|
.action(modelsCommand);
|
|
54
|
-
program
|
|
55
|
-
|
|
56
|
-
.description(t('cmd.list'))
|
|
57
|
-
.action(listCommand);
|
|
58
|
-
program
|
|
59
|
-
.command('doctor')
|
|
60
|
-
.description(t('cmd.doctor'))
|
|
61
|
-
.action(doctorCommand);
|
|
45
|
+
program.command('list').description(t('cmd.list')).action(listCommand);
|
|
46
|
+
program.command('doctor').description(t('cmd.doctor')).action(doctorCommand);
|
|
62
47
|
// Register one subcommand per supported agent (opencode, claude, aider, kilo, ...).
|
|
63
48
|
registerAgentCommands(program, deps.runAgent);
|
|
64
49
|
return program;
|
package/dist/utils/prompt.js
CHANGED
|
@@ -5,7 +5,7 @@ export async function promptApiKey() {
|
|
|
5
5
|
type: 'password',
|
|
6
6
|
name: 'apiKey',
|
|
7
7
|
message: t('prompt.pasteKey'),
|
|
8
|
-
validate: (v) => v && v.startsWith('sk-tm-') ? true : t('login.keyMustStart'),
|
|
8
|
+
validate: (v) => (v && v.startsWith('sk-tm-') ? true : t('login.keyMustStart')),
|
|
9
9
|
});
|
|
10
10
|
const key = r.apiKey?.trim();
|
|
11
11
|
return key || null;
|
|
@@ -25,12 +25,3 @@ export async function confirm(message, initial = true) {
|
|
|
25
25
|
});
|
|
26
26
|
return Boolean(r.ok);
|
|
27
27
|
}
|
|
28
|
-
export async function select(message, choices) {
|
|
29
|
-
const r = await prompts({
|
|
30
|
-
type: 'select',
|
|
31
|
-
name: 'value',
|
|
32
|
-
message,
|
|
33
|
-
choices,
|
|
34
|
-
});
|
|
35
|
-
return r.value ?? null;
|
|
36
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmix",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Zero-config CLI to use any open-source coding agent with TokenMix as the unified LLM backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"dev": "tsx src/cli.ts",
|
|
12
12
|
"start": "node bin/tokenmix.js",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
|
+
"lint": "eslint .",
|
|
15
|
+
"lint:fix": "eslint . --fix",
|
|
16
|
+
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
17
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
14
18
|
"test": "vitest run",
|
|
15
19
|
"test:watch": "vitest",
|
|
16
20
|
"prepublishOnly": "pnpm build"
|
|
@@ -36,11 +40,15 @@
|
|
|
36
40
|
"openhands",
|
|
37
41
|
"coding-agent"
|
|
38
42
|
],
|
|
43
|
+
"author": "TokenMix",
|
|
39
44
|
"license": "MIT",
|
|
40
45
|
"homepage": "https://tokenmix.ai",
|
|
41
46
|
"repository": {
|
|
42
47
|
"type": "git",
|
|
43
|
-
"url": "https://github.com/
|
|
48
|
+
"url": "https://github.com/TokenMixAi/tokenmix-cli.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/TokenMixAi/tokenmix-cli/issues"
|
|
44
52
|
},
|
|
45
53
|
"files": [
|
|
46
54
|
"bin",
|
|
@@ -59,12 +67,18 @@
|
|
|
59
67
|
"which": "^5.0.0"
|
|
60
68
|
},
|
|
61
69
|
"devDependencies": {
|
|
70
|
+
"@eslint/js": "^10.0.1",
|
|
62
71
|
"@types/fs-extra": "^11.0.4",
|
|
63
72
|
"@types/node": "^22.9.0",
|
|
64
73
|
"@types/prompts": "^2.4.9",
|
|
65
74
|
"@types/which": "^3.0.4",
|
|
75
|
+
"eslint": "^10.4.1",
|
|
76
|
+
"eslint-config-prettier": "^10.1.8",
|
|
77
|
+
"globals": "^17.6.0",
|
|
78
|
+
"prettier": "^3.8.3",
|
|
66
79
|
"tsx": "^4.19.2",
|
|
67
80
|
"typescript": "^5.6.3",
|
|
81
|
+
"typescript-eslint": "^8.60.0",
|
|
68
82
|
"vitest": "^4.1.7"
|
|
69
83
|
},
|
|
70
84
|
"pnpm": {
|