scalemax 0.1.42 → 0.1.44
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/dist/commands/configure.js +1 -0
- package/dist/configure/claude.js +22 -4
- package/dist/configure/templates.js +1 -1
- package/dist/install/codex.d.ts +3 -0
- package/dist/install/codex.js +142 -13
- package/dist/types/client.d.ts +1 -1
- package/dist/types/client.js +1 -0
- package/dist/utils/api.js +3 -1
- package/package.json +1 -1
package/dist/configure/claude.js
CHANGED
|
@@ -23,6 +23,14 @@ const customModelOptionDescriptionKey = 'ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTI
|
|
|
23
23
|
const fableModelId = 'fable-5';
|
|
24
24
|
const fableModelName = 'Fable 5';
|
|
25
25
|
const fableModelDescription = 'Fable 5 via ScaleMax';
|
|
26
|
+
// Claude's picker exposes ONE custom model slot (shared with Auto). fable-5 keeps
|
|
27
|
+
// it whenever the key has fable-5, so re-running `update` never silently moves an
|
|
28
|
+
// existing user onto a different model and price; fable-5.1 takes the slot only
|
|
29
|
+
// when it is the Fable model this key can reach. Either id stays callable
|
|
30
|
+
// explicitly (e.g. `/model fable-5.1`).
|
|
31
|
+
const fable51ModelId = 'fable-5.1';
|
|
32
|
+
const fable51ModelName = 'Fable 5.1';
|
|
33
|
+
const fable51ModelDescription = 'Fable 5.1 via ScaleMax';
|
|
26
34
|
const autoModelName = 'Auto';
|
|
27
35
|
const autoModelDescription = 'Auto via ScaleMax';
|
|
28
36
|
const verificationTimeoutMs = 60_000;
|
|
@@ -43,6 +51,9 @@ const managedCustomModelValues = new Set([
|
|
|
43
51
|
fableModelId,
|
|
44
52
|
fableModelName,
|
|
45
53
|
fableModelDescription,
|
|
54
|
+
fable51ModelId,
|
|
55
|
+
fable51ModelName,
|
|
56
|
+
fable51ModelDescription,
|
|
46
57
|
AUTO_MODEL_ID,
|
|
47
58
|
autoModelName,
|
|
48
59
|
autoModelDescription
|
|
@@ -57,11 +68,11 @@ function helperCommand(home, platform) {
|
|
|
57
68
|
function asRecord(value) {
|
|
58
69
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
59
70
|
}
|
|
71
|
+
function hasModelId(ctx, ...ids) {
|
|
72
|
+
return (ctx.models || []).some(model => ids.includes(String(model.id || '').trim().toLowerCase()));
|
|
73
|
+
}
|
|
60
74
|
function hasFableModel(ctx) {
|
|
61
|
-
return (ctx
|
|
62
|
-
const id = String(model.id || '').trim().toLowerCase();
|
|
63
|
-
return id === fableModelId || id === 'claude-fable-5';
|
|
64
|
-
});
|
|
75
|
+
return hasModelId(ctx, fableModelId, 'claude-fable-5');
|
|
65
76
|
}
|
|
66
77
|
function hasAutoModel(ctx) {
|
|
67
78
|
return modelIds(ctx).some(id => id.trim().toLowerCase() === AUTO_MODEL_ID);
|
|
@@ -74,6 +85,13 @@ function managedCustomModel(ctx) {
|
|
|
74
85
|
description: fableModelDescription
|
|
75
86
|
};
|
|
76
87
|
}
|
|
88
|
+
if (hasModelId(ctx, fable51ModelId)) {
|
|
89
|
+
return {
|
|
90
|
+
option: fable51ModelId,
|
|
91
|
+
name: fable51ModelName,
|
|
92
|
+
description: fable51ModelDescription
|
|
93
|
+
};
|
|
94
|
+
}
|
|
77
95
|
if (hasAutoModel(ctx)) {
|
|
78
96
|
return {
|
|
79
97
|
option: AUTO_MODEL_ID,
|
|
@@ -11,7 +11,7 @@ export function jsonConfig(client, apiKey, baseUrl, extra = {}) {
|
|
|
11
11
|
}, null, 2) + '\n';
|
|
12
12
|
}
|
|
13
13
|
export function continueConfig(apiKey, baseUrl) {
|
|
14
|
-
const models = ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review', 'deepseek-v4-flash'];
|
|
14
|
+
const models = ['gpt-6-astra', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review', 'deepseek-v4-flash'];
|
|
15
15
|
return JSON.stringify({
|
|
16
16
|
models: models.map(model => ({
|
|
17
17
|
title: model === 'deepseek-v4-flash' ? 'ScaleMax DeepSeek V4 Flash' : `ScaleMax ${model}`,
|
package/dist/install/codex.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
export declare function parseCodexSemver(text?: string): string | undefined;
|
|
2
|
+
export declare function compareCodexSemver(a: string, b: string): number;
|
|
1
3
|
export declare function isCodexExecutableAvailable(): Promise<boolean>;
|
|
2
4
|
export declare function getCodexVersion(): Promise<string | undefined>;
|
|
5
|
+
export declare function resolveLatestCodexVersion(): Promise<string | undefined>;
|
|
3
6
|
export declare function installCodexWithNpm(): Promise<{
|
|
4
7
|
ok: boolean;
|
|
5
8
|
message: string;
|
package/dist/install/codex.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { confirm, isCancel } from '@clack/prompts';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
3
5
|
import { commandExists } from '../utils/exec.js';
|
|
6
|
+
const PKG = '@openai/codex';
|
|
7
|
+
const NPMJS = 'https://registry.npmjs.org/';
|
|
8
|
+
function npmCmd() { return process.platform === 'win32' ? 'npm.cmd' : 'npm'; }
|
|
9
|
+
function specFor(ver) { return ver ? `${PKG}@${ver}` : `${PKG}@latest`; }
|
|
10
|
+
function installHint(ver) { return `npm install -g ${specFor(ver)} --prefer-online`; }
|
|
11
|
+
export function parseCodexSemver(text) {
|
|
12
|
+
const m = String(text ?? '').match(/(?<![\d.])(\d+\.\d+\.\d+)(?![\d.])/);
|
|
13
|
+
return m?.[1];
|
|
14
|
+
}
|
|
15
|
+
export function compareCodexSemver(a, b) {
|
|
16
|
+
const pa = a.replace(/^v/, '').split('.').map(n => parseInt(n, 10) || 0);
|
|
17
|
+
const pb = b.replace(/^v/, '').split('.').map(n => parseInt(n, 10) || 0);
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
20
|
+
if (d)
|
|
21
|
+
return d;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
4
25
|
export async function isCodexExecutableAvailable() { return commandExists('codex'); }
|
|
5
26
|
export async function getCodexVersion() { try {
|
|
6
27
|
return (await execa('codex', ['--version'], { reject: false })).stdout.trim();
|
|
@@ -8,32 +29,140 @@ export async function getCodexVersion() { try {
|
|
|
8
29
|
catch {
|
|
9
30
|
return undefined;
|
|
10
31
|
} }
|
|
32
|
+
async function viewLatest(useNpmjs) {
|
|
33
|
+
const args = ['view', `${PKG}@latest`, 'version', '--prefer-online'];
|
|
34
|
+
if (useNpmjs)
|
|
35
|
+
args.push(`--registry=${NPMJS}`);
|
|
36
|
+
try {
|
|
37
|
+
const res = await execa(npmCmd(), args, { reject: false, timeout: 15000 });
|
|
38
|
+
if (res.exitCode !== 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
return parseCodexSemver(res.stdout);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function resolveLatestCodexVersion() {
|
|
47
|
+
return (await viewLatest(true)) ?? (await viewLatest(false));
|
|
48
|
+
}
|
|
49
|
+
async function npmInstall(spec, registry) {
|
|
50
|
+
const args = ['install', '-g', spec, '--prefer-online'];
|
|
51
|
+
if (registry)
|
|
52
|
+
args.push(`--registry=${registry}`);
|
|
53
|
+
return execa(npmCmd(), args, { all: true, reject: false, stdio: 'pipe' });
|
|
54
|
+
}
|
|
55
|
+
async function npmGlobalBinDir() {
|
|
56
|
+
try {
|
|
57
|
+
const prefix = (await execa(npmCmd(), ['prefix', '-g'], { reject: false })).stdout.trim();
|
|
58
|
+
if (!prefix)
|
|
59
|
+
return undefined;
|
|
60
|
+
return process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function npmGlobalCodexPath(binDir) {
|
|
67
|
+
return path.join(binDir, process.platform === 'win32' ? 'codex.cmd' : 'codex');
|
|
68
|
+
}
|
|
69
|
+
async function whichCodex() {
|
|
70
|
+
try {
|
|
71
|
+
if (process.platform === 'win32') {
|
|
72
|
+
const res = await execa('where', ['codex'], { reject: false });
|
|
73
|
+
return res.stdout.trim().split(/\r?\n/)[0] || undefined;
|
|
74
|
+
}
|
|
75
|
+
const res = await execa('which', ['codex'], { reject: false });
|
|
76
|
+
return res.stdout.trim() || undefined;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function versionAt(bin) {
|
|
83
|
+
try {
|
|
84
|
+
return parseCodexSemver((await execa(bin, ['--version'], { reject: false })).stdout);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function inspectNpmCodex() {
|
|
91
|
+
const binDir = await npmGlobalBinDir();
|
|
92
|
+
if (!binDir)
|
|
93
|
+
return {};
|
|
94
|
+
const bin = npmGlobalCodexPath(binDir);
|
|
95
|
+
if (!fs.existsSync(bin))
|
|
96
|
+
return { binDir };
|
|
97
|
+
return { binDir, bin, version: await versionAt(bin) };
|
|
98
|
+
}
|
|
11
99
|
export async function installCodexWithNpm() {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
100
|
+
const target = await resolveLatestCodexVersion();
|
|
101
|
+
const spec = specFor(target);
|
|
102
|
+
const hint = installHint(target);
|
|
14
103
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
104
|
+
let res = await npmInstall(spec);
|
|
105
|
+
let inspected = await inspectNpmCodex();
|
|
106
|
+
let pathVer = parseCodexSemver(await getCodexVersion());
|
|
107
|
+
const yielded = inspected.version ?? pathVer;
|
|
108
|
+
const older = Boolean(target && yielded && compareCodexSemver(yielded, target) < 0);
|
|
109
|
+
if (res.exitCode !== 0 || older) {
|
|
110
|
+
const retry = await npmInstall(spec, NPMJS);
|
|
111
|
+
if (retry.exitCode === 0) {
|
|
112
|
+
res = retry;
|
|
113
|
+
inspected = await inspectNpmCodex();
|
|
114
|
+
pathVer = parseCodexSemver(await getCodexVersion());
|
|
115
|
+
}
|
|
116
|
+
else if (res.exitCode !== 0) {
|
|
117
|
+
return { ok: false, message: `npm install exited with ${retry.exitCode}.`, output: retry.all };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
pathVer = parseCodexSemver(await getCodexVersion());
|
|
121
|
+
if (target && pathVer && compareCodexSemver(pathVer, target) < 0) {
|
|
122
|
+
const npmVer = inspected.version;
|
|
123
|
+
if (inspected.bin && inspected.binDir && npmVer && compareCodexSemver(npmVer, target) >= 0) {
|
|
124
|
+
const shadowed = (await whichCodex()) || 'codex';
|
|
125
|
+
return { ok: true, message: `Codex ${npmVer} installed at ${inspected.bin}, but an older Codex (${pathVer}) at ${shadowed} is shadowing it on PATH. Remove the older binary or put ${inspected.binDir} first on PATH.`, output: res.all };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (res.exitCode !== 0)
|
|
129
|
+
return { ok: false, message: `npm install exited with ${res.exitCode}.`, output: res.all };
|
|
130
|
+
return { ok: true, message: 'Codex installed with npm.', output: res.all };
|
|
19
131
|
}
|
|
20
132
|
catch (err) {
|
|
21
|
-
return { ok: false, message: `Could not run
|
|
133
|
+
return { ok: false, message: `Could not run ${hint}: ${err instanceof Error ? err.message : String(err)}` };
|
|
22
134
|
}
|
|
23
135
|
}
|
|
24
136
|
export async function ensureCodexInstalled(interactive = true) {
|
|
25
|
-
if (await isCodexExecutableAvailable())
|
|
26
|
-
|
|
137
|
+
if (await isCodexExecutableAvailable()) {
|
|
138
|
+
const raw = await getCodexVersion();
|
|
139
|
+
const current = parseCodexSemver(raw);
|
|
140
|
+
const latest = await resolveLatestCodexVersion().catch(() => undefined);
|
|
141
|
+
if (!latest || !current || compareCodexSemver(current, latest) >= 0) {
|
|
142
|
+
return { ok: true, installed: false, message: `Codex detected${raw ? ` (${raw})` : ''}.` };
|
|
143
|
+
}
|
|
144
|
+
if (!interactive) {
|
|
145
|
+
return { ok: true, installed: false, message: `Codex detected (${raw ?? current}). Update available: ${latest}. Run ${installHint(latest)} to update.` };
|
|
146
|
+
}
|
|
147
|
+
const ans = await confirm({ message: `Codex ${current} is installed; update to latest ${latest}?`, initialValue: true });
|
|
148
|
+
if (isCancel(ans) || ans === false)
|
|
149
|
+
return { ok: true, installed: false, message: `Codex detected (${raw ?? current}).` };
|
|
150
|
+
const res = await installCodexWithNpm();
|
|
151
|
+
if (!res.ok)
|
|
152
|
+
return { ok: false, installed: false, message: `${res.message}\nIf this is a permission issue, install Node with nvm/fnm/volta or run: ${installHint(latest)}` };
|
|
153
|
+
return { ok: true, installed: true, message: res.message.includes('shadowing') ? res.message : 'Codex installed and detected.' };
|
|
154
|
+
}
|
|
27
155
|
if (!interactive)
|
|
28
156
|
return { ok: false, installed: false, message: 'Codex executable was not found on PATH.' };
|
|
29
|
-
const
|
|
157
|
+
const latest = await resolveLatestCodexVersion().catch(() => undefined);
|
|
158
|
+
const hint = installHint(latest);
|
|
159
|
+
const ans = await confirm({ message: `Codex CLI is not installed. Install it now with ${hint}?`, initialValue: true });
|
|
30
160
|
if (isCancel(ans) || ans === false)
|
|
31
161
|
return { ok: false, installed: false, message: 'Codex installation skipped.' };
|
|
32
162
|
const res = await installCodexWithNpm();
|
|
33
163
|
if (!res.ok)
|
|
34
|
-
return { ok: false, installed: false, message: `${res.message}
|
|
35
|
-
If this is a permission issue, install Node with nvm/fnm/volta or run: npm install -g @openai/codex@latest` };
|
|
164
|
+
return { ok: false, installed: false, message: `${res.message}\nIf this is a permission issue, install Node with nvm/fnm/volta or run: ${hint}` };
|
|
36
165
|
if (!(await isCodexExecutableAvailable()))
|
|
37
166
|
return { ok: false, installed: true, message: 'Codex install finished but codex is not on PATH. Open a new terminal or refresh PATH, then rerun npx scalemax.' };
|
|
38
|
-
return { ok: true, installed: true, message: 'Codex installed and detected.' };
|
|
167
|
+
return { ok: true, installed: true, message: res.message.includes('shadowing') ? res.message : 'Codex installed and detected.' };
|
|
39
168
|
}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type ClientId = 'codex' | 'cursor' | 'roo-code' | 'cline' | 'continue' | 'trae-solo' | 'claude' | 'opencode' | 'openclaw' | 'hermes' | 'cherry-studio' | 'api-code';
|
|
2
2
|
/** Keep this mirror aligned with shared/constants/api-key-scopes.ts. */
|
|
3
|
-
export declare const API_KEY_SCOPE_VALUES: readonly ["openai", "anthropic", "both", "deepseek-v4-flash", "glm-5.2", "deepseek-v4-pro", "auto-api", "reseller", "gemini", "grok", "qwen", "deepseek", "mimo", "local"];
|
|
3
|
+
export declare const API_KEY_SCOPE_VALUES: readonly ["openai", "anthropic", "both", "deepseek-v4-flash", "glm-5.2", "deepseek-v4-pro", "auto-api", "reseller", "gemini", "grok", "qwen", "deepseek", "mimo", "local", "voice"];
|
|
4
4
|
export type ProviderScope = (typeof API_KEY_SCOPE_VALUES)[number];
|
|
5
5
|
export declare function isProviderScope(value: unknown): value is ProviderScope;
|
|
6
6
|
export interface ClientDefinition {
|
package/dist/types/client.js
CHANGED
package/dist/utils/api.js
CHANGED
|
@@ -18,7 +18,9 @@ export const MODEL_PROBE_ENOUGH_WORKING = 8;
|
|
|
18
18
|
export const MODEL_PROBE_MAX_MODELS = 16;
|
|
19
19
|
// Keep only aliases that are no longer offered. Direct GPT-5.6 Sol is an
|
|
20
20
|
// active Codex model and must remain available to all generated clients.
|
|
21
|
-
|
|
21
|
+
// gpt-5.6-luna was removed from this list: the API revived it 2026-08-14
|
|
22
|
+
// (shared/constants/retired-models.ts), so hiding it here hid a model on sale.
|
|
23
|
+
export const RETIRED_MODEL_IDS = new Set(['fable-5-ultra']);
|
|
22
24
|
export function isRetiredModelId(id) {
|
|
23
25
|
return RETIRED_MODEL_IDS.has(id.trim());
|
|
24
26
|
}
|