scalemax 0.1.16 → 0.1.18
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 -2
- package/dist/bootstrap.cjs +1 -1
- package/dist/commands/configure.js +5 -4
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/login.js +1 -2
- package/dist/configure/claude.js +8 -3
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/index.js +2 -2
- package/dist/utils/api.d.ts +8 -5
- package/dist/utils/api.js +21 -10
- package/dist/utils/installerConfig.js +2 -2
- package/dist/utils/keyStore.d.ts +1 -0
- package/dist/utils/keyStore.js +4 -0
- package/dist/utils/runtime.d.ts +1 -1
- package/dist/utils/runtime.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ It detects supported AI coding clients, safely backs up existing config files, a
|
|
|
17
17
|
- A ScaleMax API key
|
|
18
18
|
- Network access to `https://api.scalemax.pro`
|
|
19
19
|
|
|
20
|
+
The CLI identifies whether the supplied key belongs to the API-credit surface or the token-pack surface. It then stores and configures the matching base URL automatically; users do not need to choose it manually.
|
|
21
|
+
|
|
20
22
|
## Installation
|
|
21
23
|
|
|
22
24
|
### One-time run with npx
|
|
@@ -114,7 +116,7 @@ For clients without a stable official config file, the installer writes a ScaleM
|
|
|
114
116
|
|
|
115
117
|
### Claude Code
|
|
116
118
|
|
|
117
|
-
ScaleMax updates `~/.claude/settings.json` with `ANTHROPIC_BASE_URL=https://api.scalemax.pro`, gateway model discovery, and a cached `apiKeyHelper` that reads a private `~/.claude/scalemax.key` file. The raw key is not stored in the settings JSON. The Anthropic base URL intentionally has no `/v1` suffix because Claude Code adds `/v1/messages` itself. The CLI preserves unrelated settings, selects a live main model from the authenticated catalog (preferring `claude-opus-4-8`), pins authenticated Haiku for lightweight background work when available, and verifies a one-token Messages request
|
|
119
|
+
ScaleMax updates `~/.claude/settings.json` with `ANTHROPIC_BASE_URL=https://api.scalemax.pro`, gateway model discovery, and a cached `apiKeyHelper` that reads a private `~/.claude/scalemax.key` file. The raw key is not stored in the settings JSON. The Anthropic base URL intentionally has no `/v1` suffix because Claude Code adds `/v1/messages` itself. The CLI preserves unrelated settings, selects a live main model from the authenticated catalog (preferring `claude-opus-4-8`), pins authenticated Haiku for lightweight background work when available, and sets `CLAUDE_CODE_DISABLE_THINKING=1` because third-party Anthropic-compatible providers do not support Claude Code's proprietary extended-thinking control envelope. It verifies a one-token Messages request and, if verification fails, removes only the ScaleMax-managed changes. Claude Code picker aliases such as `claude-opus-4-8[1m]` are normalized by the gateway.
|
|
118
120
|
|
|
119
121
|
### OpenCode
|
|
120
122
|
|
|
@@ -313,4 +315,4 @@ Yes. The CLI uses Windows-aware config paths and writes `%APPDATA%\scalemax\conf
|
|
|
313
315
|
|
|
314
316
|
### What endpoint does it configure?
|
|
315
317
|
|
|
316
|
-
|
|
318
|
+
API-credit keys use `https://api.scalemax.pro/v1` for OpenAI-compatible clients and `https://api.scalemax.pro` for Anthropic-compatible clients. Token-pack keys use `https://api.scalemax.pro/token/v1` and `https://api.scalemax.pro/token` respectively. The CLI detects the key type and configures the correct surface automatically; the two key types are intentionally not interchangeable.
|
package/dist/bootstrap.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
function cmp(a,b){a=String(a).replace(/^v/,'').split('.').map(Number);b=String(b).split('.').map(Number);for(var i=0;i<3;i++){var d=(a[i]||0)-(b[i]||0);if(d)return d;}return 0;}
|
|
4
|
-
var min=
|
|
4
|
+
var min="18.19.0";
|
|
5
5
|
if (cmp(process.versions.node,min)<0) {
|
|
6
6
|
console.error('ScaleMax requires Node.js >= '+min+'. Current: '+process.versions.node);
|
|
7
7
|
console.error('Install Node.js LTS, then rerun: npx scalemax@latest');
|
|
@@ -18,7 +18,7 @@ export async function printDetected(home = homeDir()) {
|
|
|
18
18
|
console.log('');
|
|
19
19
|
return detections;
|
|
20
20
|
}
|
|
21
|
-
async function maybeLaunchCodex(chosen, successful) {
|
|
21
|
+
async function maybeLaunchCodex(chosen, successful, baseUrl = SCALEMAX_BASE_URL) {
|
|
22
22
|
if (!chosen.includes('codex') || !successful.has('codex'))
|
|
23
23
|
return;
|
|
24
24
|
console.log(chalk.green('\nConfiguration completed.\n'));
|
|
@@ -31,7 +31,7 @@ async function maybeLaunchCodex(chosen, successful) {
|
|
|
31
31
|
const apiKey = await loadApiKey(homeDir());
|
|
32
32
|
if (!apiKey)
|
|
33
33
|
throw new Error('No API key found.');
|
|
34
|
-
const result = await codex?.launch?.({ home: homeDir(), platform: process.platform, env: process.env, apiKey, baseUrl
|
|
34
|
+
const result = await codex?.launch?.({ home: homeDir(), platform: process.platform, env: process.env, apiKey, baseUrl });
|
|
35
35
|
console.log(chalk.green(`✓ ${result?.message || 'Codex launched.'}`));
|
|
36
36
|
}
|
|
37
37
|
catch (err) {
|
|
@@ -61,6 +61,7 @@ export async function configureCommand(opts = {}) {
|
|
|
61
61
|
if (!keyInfo.models.length)
|
|
62
62
|
throw new Error(keyInfo.error || 'ScaleMax returned no models for this API key. No client configuration was changed.');
|
|
63
63
|
const providerScope = keyInfo.key?.provider_scope || 'both';
|
|
64
|
+
const selectedBaseUrl = keyInfo.baseUrl || SCALEMAX_BASE_URL;
|
|
64
65
|
const availableClients = clients.filter(c => clientAllowedForModels(c.id, keyInfo.models));
|
|
65
66
|
console.log(chalk.dim(`API key scope: ${scopeHint(providerScope)}. Clients are filtered against the authenticated model catalog.`));
|
|
66
67
|
const detections = await printDetected(home);
|
|
@@ -119,7 +120,7 @@ export async function configureCommand(opts = {}) {
|
|
|
119
120
|
}
|
|
120
121
|
const spinner = ora(`Configuring ${client.name}`).start();
|
|
121
122
|
try {
|
|
122
|
-
const ctx = { ...baseCtx, apiKey, baseUrl:
|
|
123
|
+
const ctx = { ...baseCtx, apiKey, baseUrl: selectedBaseUrl, providerScope, models: keyInfo.models, interactive: Boolean(process.stdin.isTTY), allowAppModification };
|
|
123
124
|
const res = await client.configure(ctx);
|
|
124
125
|
spinner.succeed(`${client.name} ${res.message}`);
|
|
125
126
|
if (client.verify) {
|
|
@@ -148,7 +149,7 @@ export async function configureCommand(opts = {}) {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
if (opts.promptLaunch !== false && !opts.all && process.stdin.isTTY)
|
|
151
|
-
await maybeLaunchCodex(chosen, successful);
|
|
152
|
+
await maybeLaunchCodex(chosen, successful, selectedBaseUrl);
|
|
152
153
|
if (failures.length)
|
|
153
154
|
throw new Error(`${failures.length} client configuration(s) failed:\n${failures.map(f => `- ${f}`).join('\n')}`);
|
|
154
155
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -22,7 +22,7 @@ export async function doctorCommand(home = homeDir()) {
|
|
|
22
22
|
s.succeed(`API reachable, key valid, ${models.length} models loaded`);
|
|
23
23
|
console.log(`${chalk.green('✓')} Usage and key metadata accessible through the authenticated API`);
|
|
24
24
|
const st = ora('Checking streaming (small billable request)').start();
|
|
25
|
-
const stream = await testStreaming(key, models.find(m => m.id === 'fable-5')?.id || models[0]?.id);
|
|
25
|
+
const stream = await testStreaming(key, models.find(m => m.id === 'fable-5')?.id || models[0]?.id, res.baseUrl);
|
|
26
26
|
if (stream.ok)
|
|
27
27
|
st.succeed('Streaming available');
|
|
28
28
|
else {
|
|
@@ -49,7 +49,8 @@ export async function doctorCommand(home = homeDir()) {
|
|
|
49
49
|
}
|
|
50
50
|
if (client.verify && key && models.length) {
|
|
51
51
|
try {
|
|
52
|
-
const
|
|
52
|
+
const checked = await validateApiKey(key);
|
|
53
|
+
const v = await client.verify({ home, platform: process.platform, env: process.env, apiKey: key, baseUrl: checked.baseUrl || SCALEMAX_BASE_URL, models });
|
|
53
54
|
console.log(`${chalk.green('✓')} ${client.name} ${v.message}`);
|
|
54
55
|
}
|
|
55
56
|
catch (error) {
|
package/dist/commands/login.js
CHANGED
|
@@ -3,7 +3,6 @@ import ora from 'ora';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { validateApiKey } from '../utils/api.js';
|
|
5
5
|
import { saveApiKey } from '../utils/keyStore.js';
|
|
6
|
-
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
7
6
|
import { homeDir } from '../utils/paths.js';
|
|
8
7
|
import { sanitizeApiKey } from '../utils/apiKey.js';
|
|
9
8
|
export async function loginCommand(home = homeDir()) {
|
|
@@ -20,7 +19,7 @@ export async function loginCommand(home = homeDir()) {
|
|
|
20
19
|
spinner.fail('Invalid API Key');
|
|
21
20
|
throw new Error(result.error || 'Invalid API Key');
|
|
22
21
|
}
|
|
23
|
-
await saveApiKey(sanitizedKey,
|
|
22
|
+
await saveApiKey(sanitizedKey, result.baseUrl, home);
|
|
24
23
|
spinner.succeed(`API Key verified (${result.key?.provider_scope || 'both'} provider scope)`);
|
|
25
24
|
outro(chalk.green('ScaleMax API key saved.'));
|
|
26
25
|
return sanitizedKey;
|
package/dist/configure/claude.js
CHANGED
|
@@ -13,7 +13,8 @@ const name = 'Claude Code';
|
|
|
13
13
|
const discoveryKey = 'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY';
|
|
14
14
|
const fastModelKey = 'ANTHROPIC_DEFAULT_HAIKU_MODEL';
|
|
15
15
|
const helperTtlKey = 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS';
|
|
16
|
-
const
|
|
16
|
+
const disableThinkingKey = 'CLAUDE_CODE_DISABLE_THINKING';
|
|
17
|
+
const managedEnvKeys = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_MODEL', discoveryKey, fastModelKey, helperTtlKey, disableThinkingKey];
|
|
17
18
|
function secretPath(home) { return path.join(home, '.claude', 'scalemax.key'); }
|
|
18
19
|
function helperCommand(home, platform) {
|
|
19
20
|
const file = secretPath(home);
|
|
@@ -47,7 +48,8 @@ export async function configure(ctx) {
|
|
|
47
48
|
ANTHROPIC_BASE_URL: baseUrl,
|
|
48
49
|
ANTHROPIC_MODEL: model,
|
|
49
50
|
[discoveryKey]: '1',
|
|
50
|
-
[helperTtlKey]: '900000'
|
|
51
|
+
[helperTtlKey]: '900000',
|
|
52
|
+
[disableThinkingKey]: '1'
|
|
51
53
|
};
|
|
52
54
|
delete nextEnv.ANTHROPIC_AUTH_TOKEN;
|
|
53
55
|
delete nextEnv.ANTHROPIC_API_KEY;
|
|
@@ -67,6 +69,7 @@ export async function configure(ctx) {
|
|
|
67
69
|
gatewayDiscovery: '1',
|
|
68
70
|
fastModel: quick || null,
|
|
69
71
|
helperTtl: '900000',
|
|
72
|
+
disableThinking: '1',
|
|
70
73
|
apiKeyHelper,
|
|
71
74
|
authTokenSha256: crypto.createHash('sha256').update(ctx.apiKey).digest('hex'),
|
|
72
75
|
previousEnv,
|
|
@@ -86,7 +89,7 @@ export async function verify(ctx) {
|
|
|
86
89
|
const settings = await fs.readJson(file);
|
|
87
90
|
const env = asRecord(settings?.env);
|
|
88
91
|
const model = String(env.ANTHROPIC_MODEL || '');
|
|
89
|
-
if (!modelIds(ctx).includes(model) || env.ANTHROPIC_BASE_URL !== anthropicOrigin(ctx.baseUrl) || settings.apiKeyHelper !== helperCommand(ctx.home, ctx.platform) || env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || env[discoveryKey] !== '1') {
|
|
92
|
+
if (!modelIds(ctx).includes(model) || env.ANTHROPIC_BASE_URL !== anthropicOrigin(ctx.baseUrl) || settings.apiKeyHelper !== helperCommand(ctx.home, ctx.platform) || env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || env[discoveryKey] !== '1' || env[disableThinkingKey] !== '1') {
|
|
90
93
|
throw new Error('Claude Code settings do not match the authenticated ScaleMax model catalog.');
|
|
91
94
|
}
|
|
92
95
|
await verifyClientSecret(secretPath(ctx.home), ctx);
|
|
@@ -138,6 +141,8 @@ export async function remove(ctx) {
|
|
|
138
141
|
restore(fastModelKey);
|
|
139
142
|
if (env[helperTtlKey] === managed.helperTtl)
|
|
140
143
|
restore(helperTtlKey);
|
|
144
|
+
if (env[disableThinkingKey] === managed.disableThinking)
|
|
145
|
+
restore(disableThinkingKey);
|
|
141
146
|
if (!Object.prototype.hasOwnProperty.call(env, 'ANTHROPIC_AUTH_TOKEN'))
|
|
142
147
|
restore('ANTHROPIC_AUTH_TOKEN');
|
|
143
148
|
if (!Object.prototype.hasOwnProperty.call(env, 'ANTHROPIC_API_KEY'))
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const SCALEMAX_BASE_URL = "https://api.scalemax.pro/v1";
|
|
2
|
+
export declare const SCALEMAX_TOKEN_BASE_URL = "https://api.scalemax.pro/token/v1";
|
|
2
3
|
export declare const APP_NAME = "ScaleMax";
|
|
3
4
|
export declare const GENERATED_HEADER = "ScaleMax managed configuration";
|
|
4
5
|
export declare const KEY_ENV = "SCALEMAX_API_KEY";
|
package/dist/constants.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const SCALEMAX_BASE_URL = 'https://api.scalemax.pro/v1';
|
|
2
|
+
export const SCALEMAX_TOKEN_BASE_URL = 'https://api.scalemax.pro/token/v1';
|
|
2
3
|
export const APP_NAME = 'ScaleMax';
|
|
3
4
|
export const GENERATED_HEADER = 'ScaleMax managed configuration';
|
|
4
5
|
export const KEY_ENV = 'SCALEMAX_API_KEY';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { modelsCommand } from './commands/models.js';
|
|
|
12
12
|
import { updateCommand } from './commands/update.js';
|
|
13
13
|
import { removeCommand } from './commands/remove.js';
|
|
14
14
|
import { ensureInstallerApiKey } from './utils/installerConfig.js';
|
|
15
|
-
import { isNodeSupported, nodeUpgradeHint } from './utils/runtime.js';
|
|
15
|
+
import { MIN_NODE, isNodeSupported, nodeUpgradeHint } from './utils/runtime.js';
|
|
16
16
|
import { runSystemAudit } from './utils/systemAudit.js';
|
|
17
17
|
function packageVersion() {
|
|
18
18
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -63,7 +63,7 @@ async function runInteractiveInstaller() {
|
|
|
63
63
|
}
|
|
64
64
|
async function main() {
|
|
65
65
|
if (!isNodeSupported()) {
|
|
66
|
-
console.error(chalk.red(`ScaleMax requires Node.js
|
|
66
|
+
console.error(chalk.red(`ScaleMax requires Node.js >=${MIN_NODE}. Current: ${process.versions.node}`));
|
|
67
67
|
console.error(nodeUpgradeHint());
|
|
68
68
|
process.exitCode = 1;
|
|
69
69
|
return;
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -14,15 +14,18 @@ export interface KeyMetadata {
|
|
|
14
14
|
prefix?: string;
|
|
15
15
|
label?: string;
|
|
16
16
|
}
|
|
17
|
-
export
|
|
18
|
-
export declare function filterModelsForScope(models: ModelInfo[], scope: ProviderScope): ModelInfo[];
|
|
19
|
-
export declare function validateApiKey(apiKey: string): Promise<{
|
|
17
|
+
export interface ApiValidation {
|
|
20
18
|
ok: boolean;
|
|
21
19
|
models: ModelInfo[];
|
|
22
20
|
key?: KeyMetadata;
|
|
21
|
+
baseUrl?: string;
|
|
23
22
|
error?: string;
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
surfaceMismatch?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function isAnthropicModel(model: ModelInfo): boolean;
|
|
26
|
+
export declare function filterModelsForScope(models: ModelInfo[], scope: ProviderScope): ModelInfo[];
|
|
27
|
+
export declare function validateApiKey(apiKey: string): Promise<ApiValidation>;
|
|
28
|
+
export declare function testStreaming(apiKey: string, model?: string, baseUrl?: string): Promise<{
|
|
26
29
|
ok: boolean;
|
|
27
30
|
error?: string;
|
|
28
31
|
}>;
|
package/dist/utils/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
1
|
+
import { SCALEMAX_BASE_URL, SCALEMAX_TOKEN_BASE_URL } from '../constants.js';
|
|
2
2
|
import { redactApiKey, sanitizeApiKey } from './apiKey.js';
|
|
3
3
|
export function isAnthropicModel(model) {
|
|
4
4
|
const provider = String(model.provider || model.owned_by || '').trim().toLowerCase();
|
|
@@ -34,7 +34,7 @@ async function friendlyError(res) {
|
|
|
34
34
|
function retryableStatus(status) {
|
|
35
35
|
return status === 408 || status === 429 || status >= 500;
|
|
36
36
|
}
|
|
37
|
-
async function request(path, apiKey, init = {}, retryGet = false) {
|
|
37
|
+
async function request(path, apiKey, init = {}, retryGet = false, baseUrl = SCALEMAX_BASE_URL) {
|
|
38
38
|
const headers = new Headers(init.headers);
|
|
39
39
|
if (apiKey)
|
|
40
40
|
headers.set('Authorization', `Bearer ${sanitizeApiKey(apiKey)}`);
|
|
@@ -47,7 +47,7 @@ async function request(path, apiKey, init = {}, retryGet = false) {
|
|
|
47
47
|
const timeout = AbortSignal.timeout(15000);
|
|
48
48
|
const signal = init.signal && typeof AbortSignal.any === 'function' ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
49
49
|
try {
|
|
50
|
-
const response = await fetch(`${
|
|
50
|
+
const response = await fetch(`${baseUrl}${path}`, { ...init, headers, signal });
|
|
51
51
|
if (attempt + 1 === attempts || !retryableStatus(response.status))
|
|
52
52
|
return response;
|
|
53
53
|
await response.body?.cancel().catch(() => undefined);
|
|
@@ -61,14 +61,14 @@ async function request(path, apiKey, init = {}, retryGet = false) {
|
|
|
61
61
|
}
|
|
62
62
|
throw lastError;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
async function validateOnSurface(apiKey, baseUrl) {
|
|
65
65
|
try {
|
|
66
66
|
const [authCheck, modelsRes] = await Promise.all([
|
|
67
|
-
request('/usage', apiKey, {}, true),
|
|
68
|
-
request('/models', apiKey, {}, true)
|
|
67
|
+
request('/usage', apiKey, {}, true, baseUrl),
|
|
68
|
+
request('/models', apiKey, {}, true, baseUrl)
|
|
69
69
|
]);
|
|
70
70
|
if (!authCheck.ok)
|
|
71
|
-
return { ok: false, models: [], error: redactApiKey(await friendlyError(authCheck), apiKey) };
|
|
71
|
+
return { ok: false, models: [], error: redactApiKey(await friendlyError(authCheck), apiKey), surfaceMismatch: authCheck.status === 401 };
|
|
72
72
|
const usage = await authCheck.json().catch(() => ({}));
|
|
73
73
|
const keyMeta = { provider_scope: usage?.key?.provider_scope || 'both', mode: usage?.key?.mode, prefix: usage?.key?.prefix, label: usage?.key?.label };
|
|
74
74
|
if (!modelsRes.ok)
|
|
@@ -78,17 +78,28 @@ export async function validateApiKey(apiKey) {
|
|
|
78
78
|
const models = filterModelsForScope(catalog, keyMeta.provider_scope);
|
|
79
79
|
if (!models.length)
|
|
80
80
|
return { ok: false, models: [], key: keyMeta, error: 'ScaleMax returned an empty model catalog.' };
|
|
81
|
-
return { ok: true, models, key: keyMeta };
|
|
81
|
+
return { ok: true, models, key: keyMeta, baseUrl };
|
|
82
82
|
}
|
|
83
83
|
catch (err) {
|
|
84
84
|
const message = redactApiKey(err instanceof Error ? err.message : String(err), apiKey);
|
|
85
85
|
return { ok: false, models: [], error: /abort|timeout/i.test(message) ? 'ScaleMax request timed out after 15 seconds.' : message };
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
export async function
|
|
88
|
+
export async function validateApiKey(apiKey) {
|
|
89
|
+
const credit = await validateOnSurface(apiKey, SCALEMAX_BASE_URL);
|
|
90
|
+
if (credit.ok)
|
|
91
|
+
return credit;
|
|
92
|
+
if (!credit.surfaceMismatch)
|
|
93
|
+
return credit;
|
|
94
|
+
const token = await validateOnSurface(apiKey, SCALEMAX_TOKEN_BASE_URL);
|
|
95
|
+
if (token.ok)
|
|
96
|
+
return token;
|
|
97
|
+
return credit.error && !/Invalid API key|rejected|401/i.test(credit.error) ? credit : token;
|
|
98
|
+
}
|
|
99
|
+
export async function testStreaming(apiKey, model, baseUrl = SCALEMAX_BASE_URL) {
|
|
89
100
|
const selected = model || 'fable-5';
|
|
90
101
|
try {
|
|
91
|
-
const res = await request('/chat/completions', apiKey, { method: 'POST', body: JSON.stringify({ model: selected, stream: true, max_tokens: 8, messages: [{ role: 'user', content: 'Say OK' }] }) });
|
|
102
|
+
const res = await request('/chat/completions', apiKey, { method: 'POST', body: JSON.stringify({ model: selected, stream: true, max_tokens: 8, messages: [{ role: 'user', content: 'Say OK' }] }) }, false, baseUrl);
|
|
92
103
|
if (!res.ok)
|
|
93
104
|
return { ok: false, error: redactApiKey(await friendlyError(res), apiKey) };
|
|
94
105
|
const ct = res.headers.get('content-type') || '';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { intro, outro, password, select, confirm, isCancel, cancel } from '@clack/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import { KEY_ENV
|
|
4
|
+
import { KEY_ENV } from '../constants.js';
|
|
5
5
|
import { validateApiKey } from './api.js';
|
|
6
6
|
import { loadApiKey, removeApiKey, saveApiKey } from './keyStore.js';
|
|
7
7
|
import { homeDir } from './paths.js';
|
|
@@ -25,7 +25,7 @@ export async function promptForValidatedKey(home, message = 'Enter your ScaleMax
|
|
|
25
25
|
spinner.fail('Invalid API key');
|
|
26
26
|
throw new Error(result.error || 'Invalid API key');
|
|
27
27
|
}
|
|
28
|
-
await saveApiKey(candidate,
|
|
28
|
+
await saveApiKey(candidate, result.baseUrl, home);
|
|
29
29
|
spinner.succeed('API key verified and saved');
|
|
30
30
|
return candidate;
|
|
31
31
|
}
|
package/dist/utils/keyStore.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function saveApiKey(apiKey: string, baseUrl: string, home: string): Promise<void>;
|
|
2
2
|
export declare function loadApiKey(home: string): Promise<string | undefined>;
|
|
3
|
+
export declare function loadBaseUrl(home: string): Promise<string | undefined>;
|
|
3
4
|
export declare function removeApiKey(home: string): Promise<void>;
|
package/dist/utils/keyStore.js
CHANGED
|
@@ -13,6 +13,10 @@ export async function loadApiKey(home) {
|
|
|
13
13
|
const cfg = await readJson(scaleMaxConfigPath(home), {});
|
|
14
14
|
return cfg.apiKey ? sanitizeApiKey(cfg.apiKey) : undefined;
|
|
15
15
|
}
|
|
16
|
+
export async function loadBaseUrl(home) {
|
|
17
|
+
const cfg = await readJson(scaleMaxConfigPath(home), {});
|
|
18
|
+
return cfg.baseUrl;
|
|
19
|
+
}
|
|
16
20
|
export async function removeApiKey(home) {
|
|
17
21
|
await fs.remove(scaleMaxConfigPath(home));
|
|
18
22
|
}
|
package/dist/utils/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MIN_NODE = "18.
|
|
1
|
+
export declare const MIN_NODE = "18.19.0";
|
|
2
2
|
export declare const RECOMMENDED_NODE = "22";
|
|
3
3
|
export type OsFamily = 'macos' | 'ubuntu' | 'debian' | 'fedora' | 'arch' | 'windows' | 'wsl' | 'linux' | 'unknown';
|
|
4
4
|
export type RuntimeAudit = {
|
package/dist/utils/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
3
|
import { commandExists } from './exec.js';
|
|
4
|
-
export const MIN_NODE = '18.
|
|
4
|
+
export const MIN_NODE = '18.19.0';
|
|
5
5
|
export const RECOMMENDED_NODE = '22';
|
|
6
6
|
export function compareSemver(a, b) { const pa = a.replace(/^v/, '').split('.').map(Number), pb = b.replace(/^v/, '').split('.').map(Number); for (let i = 0; i < 3; i++) {
|
|
7
7
|
const d = (pa[i] || 0) - (pb[i] || 0);
|