scalemax 0.1.5 → 0.1.7
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 -3
- package/dist/clients.js +12 -15
- package/dist/commands/configure.js +22 -5
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/login.js +6 -4
- package/dist/configure/api-code.js +15 -0
- package/dist/configure/cherry-studio.js +15 -0
- package/dist/configure/claude.js +13 -3
- package/dist/configure/codex.js +2 -2
- package/dist/configure/opencode.js +21 -2
- package/dist/configure/paths.js +7 -11
- package/dist/configure/templates.js +1 -1
- package/dist/configure/trae-solo.js +15 -0
- package/dist/detect/api-code.js +2 -0
- package/dist/detect/cherry-studio.js +2 -0
- package/dist/detect/common.js +12 -30
- package/dist/detect/trae-solo.js +2 -0
- package/dist/types/client.d.ts +2 -1
- package/dist/utils/api.d.ts +8 -0
- package/dist/utils/api.js +11 -5
- package/dist/utils/apiKey.d.ts +1 -0
- package/dist/utils/apiKey.js +8 -0
- package/dist/utils/keyStore.js +4 -3
- package/package.json +6 -6
- package/dist/configure/cline.d.ts +0 -3
- package/dist/configure/cline.js +0 -13
- package/dist/configure/continue.js +0 -12
- package/dist/configure/roo.d.ts +0 -3
- package/dist/configure/roo.js +0 -13
- package/dist/configure/vscode.js +0 -13
- package/dist/configure/windsurf.js +0 -13
- package/dist/detect/cline.js +0 -2
- package/dist/detect/continue.js +0 -2
- package/dist/detect/roo.js +0 -2
- package/dist/detect/vscode.d.ts +0 -2
- package/dist/detect/vscode.js +0 -2
- package/dist/detect/windsurf.d.ts +0 -2
- package/dist/detect/windsurf.js +0 -2
- /package/dist/configure/{continue.d.ts → api-code.d.ts} +0 -0
- /package/dist/configure/{vscode.d.ts → cherry-studio.d.ts} +0 -0
- /package/dist/configure/{windsurf.d.ts → trae-solo.d.ts} +0 -0
- /package/dist/detect/{cline.d.ts → api-code.d.ts} +0 -0
- /package/dist/detect/{continue.d.ts → cherry-studio.d.ts} +0 -0
- /package/dist/detect/{roo.d.ts → trae-solo.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -98,15 +98,19 @@ SCALEMAX_API_KEY=sm_live_xxx scalemax models
|
|
|
98
98
|
|
|
99
99
|
## Supported clients
|
|
100
100
|
|
|
101
|
+
`npx scalemax` now focuses on the supported non-VS-Code client set:
|
|
102
|
+
|
|
101
103
|
- Codex
|
|
102
104
|
- Cursor
|
|
105
|
+
- TRAE SOLO
|
|
103
106
|
- Claude Code
|
|
104
107
|
- OpenCode
|
|
105
108
|
- OpenClaw
|
|
106
109
|
- Hermes
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
+
- Cherry Studio
|
|
111
|
+
- API Code
|
|
112
|
+
|
|
113
|
+
For clients without a stable official config file, the installer writes a ScaleMax setup guide under `~/.scalemax/` instead of modifying private app storage.
|
|
110
114
|
|
|
111
115
|
## Examples
|
|
112
116
|
|
package/dist/clients.js
CHANGED
|
@@ -2,31 +2,28 @@ import * as codexDetect from './detect/codex.js';
|
|
|
2
2
|
import * as codexConfig from './configure/codex.js';
|
|
3
3
|
import * as cursorDetect from './detect/cursor.js';
|
|
4
4
|
import * as cursorConfig from './configure/cursor.js';
|
|
5
|
+
import * as traeSoloDetect from './detect/trae-solo.js';
|
|
6
|
+
import * as traeSoloConfig from './configure/trae-solo.js';
|
|
7
|
+
import * as claudeDetect from './detect/claude.js';
|
|
8
|
+
import * as claudeConfig from './configure/claude.js';
|
|
5
9
|
import * as opencodeDetect from './detect/opencode.js';
|
|
6
10
|
import * as opencodeConfig from './configure/opencode.js';
|
|
7
11
|
import * as openclawDetect from './detect/openclaw.js';
|
|
8
12
|
import * as openclawConfig from './configure/openclaw.js';
|
|
9
13
|
import * as hermesDetect from './detect/hermes.js';
|
|
10
14
|
import * as hermesConfig from './configure/hermes.js';
|
|
11
|
-
import * as
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
14
|
-
import * as
|
|
15
|
-
import * as rooDetect from './detect/roo.js';
|
|
16
|
-
import * as rooConfig from './configure/roo.js';
|
|
17
|
-
import * as clineDetect from './detect/cline.js';
|
|
18
|
-
import * as clineConfig from './configure/cline.js';
|
|
19
|
-
import * as windsurfDetect from './detect/windsurf.js';
|
|
20
|
-
import * as windsurfConfig from './configure/windsurf.js';
|
|
15
|
+
import * as cherryStudioDetect from './detect/cherry-studio.js';
|
|
16
|
+
import * as cherryStudioConfig from './configure/cherry-studio.js';
|
|
17
|
+
import * as apiCodeDetect from './detect/api-code.js';
|
|
18
|
+
import * as apiCodeConfig from './configure/api-code.js';
|
|
21
19
|
export const clients = [
|
|
22
20
|
{ id: 'codex', name: 'Codex', detect: codexDetect.detect, install: codexConfig.install, configure: codexConfig.configure, verify: codexConfig.verify, launch: codexConfig.launch, remove: codexConfig.remove },
|
|
23
21
|
{ id: 'cursor', name: 'Cursor', detect: cursorDetect.detect, configure: cursorConfig.configure, remove: cursorConfig.remove },
|
|
22
|
+
{ id: 'trae-solo', name: 'TRAE SOLO', detect: traeSoloDetect.detect, configure: traeSoloConfig.configure, remove: traeSoloConfig.remove },
|
|
23
|
+
{ id: 'claude', name: 'Claude Code', detect: claudeDetect.detect, configure: claudeConfig.configure, remove: claudeConfig.remove },
|
|
24
24
|
{ id: 'opencode', name: 'OpenCode', detect: opencodeDetect.detect, configure: opencodeConfig.configure, remove: opencodeConfig.remove },
|
|
25
25
|
{ id: 'openclaw', name: 'OpenClaw', detect: openclawDetect.detect, configure: openclawConfig.configure, remove: openclawConfig.remove },
|
|
26
26
|
{ id: 'hermes', name: 'Hermes', detect: hermesDetect.detect, configure: hermesConfig.configure, remove: hermesConfig.remove },
|
|
27
|
-
{ id: '
|
|
28
|
-
{ id: '
|
|
29
|
-
{ id: 'cline', name: 'Cline (VS Code)', detect: clineDetect.detect, configure: clineConfig.configure, remove: clineConfig.remove },
|
|
30
|
-
{ id: 'continue', name: 'Continue', detect: continueDetect.detect, configure: continueConfig.configure, remove: continueConfig.remove },
|
|
31
|
-
{ id: 'windsurf', name: 'Windsurf', detect: windsurfDetect.detect, configure: windsurfConfig.configure, remove: windsurfConfig.remove }
|
|
27
|
+
{ id: 'cherry-studio', name: 'Cherry Studio', detect: cherryStudioDetect.detect, configure: cherryStudioConfig.configure, remove: cherryStudioConfig.remove },
|
|
28
|
+
{ id: 'api-code', name: 'API Code', detect: apiCodeDetect.detect, configure: apiCodeConfig.configure, remove: apiCodeConfig.remove }
|
|
32
29
|
];
|
|
@@ -4,6 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import { clients } from '../clients.js';
|
|
5
5
|
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
6
6
|
import { loadApiKey } from '../utils/keyStore.js';
|
|
7
|
+
import { validateApiKey } from '../utils/api.js';
|
|
7
8
|
import { homeDir } from '../utils/paths.js';
|
|
8
9
|
export async function detectAll(home = homeDir()) {
|
|
9
10
|
const ctx = { home, platform: process.platform, env: process.env };
|
|
@@ -38,33 +39,49 @@ async function maybeLaunchCodex(chosen, successful) {
|
|
|
38
39
|
}
|
|
39
40
|
console.log('\nIf Codex asks for authentication,\nit should already be configured to use ScaleMax.\n\nIf you still see the official login screen,\nthe configuration is incomplete or Codex rejected the generated auth/config files.\n');
|
|
40
41
|
}
|
|
42
|
+
function clientAllowedForScope(clientId, scope) {
|
|
43
|
+
if (scope === 'both')
|
|
44
|
+
return true;
|
|
45
|
+
if (clientId === 'claude')
|
|
46
|
+
return scope === 'anthropic';
|
|
47
|
+
if (clientId === 'api-code')
|
|
48
|
+
return true;
|
|
49
|
+
return scope === 'openai';
|
|
50
|
+
}
|
|
51
|
+
function scopeHint(scope) {
|
|
52
|
+
return scope === 'both' ? 'OpenAI + Claude' : scope === 'anthropic' ? 'Claude only' : 'OpenAI only';
|
|
53
|
+
}
|
|
41
54
|
export async function configureCommand(opts = {}) {
|
|
42
55
|
const home = opts.home || homeDir();
|
|
43
56
|
const apiKey = await loadApiKey(home);
|
|
44
57
|
if (!apiKey)
|
|
45
58
|
throw new Error('No ScaleMax API key found. Run `scalemax login` first.');
|
|
59
|
+
const keyInfo = await validateApiKey(apiKey);
|
|
60
|
+
const providerScope = keyInfo.key?.provider_scope || 'both';
|
|
61
|
+
const availableClients = clients.filter(c => clientAllowedForScope(c.id, providerScope));
|
|
62
|
+
console.log(chalk.dim(`API key scope: ${scopeHint(providerScope)}. Incompatible clients are hidden.`));
|
|
46
63
|
const detections = await printDetected(home);
|
|
47
64
|
const detectedIds = new Set(detections.filter(d => d.installed).map(d => d.id));
|
|
48
65
|
let chosen;
|
|
49
66
|
if (opts.all)
|
|
50
|
-
chosen =
|
|
67
|
+
chosen = availableClients.filter(c => detectedIds.has(c.id)).map(c => c.id);
|
|
51
68
|
else {
|
|
52
69
|
const action = await select({ message: 'Configure', options: [
|
|
53
|
-
...
|
|
70
|
+
...availableClients.map((c, idx) => ({ value: c.id, label: `${idx + 1}. ${c.name}${detectedIds.has(c.id) ? '' : ' (not detected)'}` })),
|
|
54
71
|
{ value: 'all', label: 'A. Configure ALL detected clients' }
|
|
55
72
|
] });
|
|
56
73
|
if (isCancel(action)) {
|
|
57
74
|
cancel('Configuration cancelled.');
|
|
58
75
|
return;
|
|
59
76
|
}
|
|
60
|
-
chosen = action === 'all' ?
|
|
77
|
+
chosen = action === 'all' ? availableClients.filter(c => detectedIds.has(c.id)).map(c => c.id) : [action];
|
|
61
78
|
}
|
|
62
79
|
if (chosen.length === 0) {
|
|
63
80
|
console.log(chalk.yellow('No detected clients to configure.'));
|
|
64
81
|
return;
|
|
65
82
|
}
|
|
66
83
|
const successful = new Set();
|
|
67
|
-
for (const client of
|
|
84
|
+
for (const client of availableClients.filter(c => chosen.includes(c.id))) {
|
|
68
85
|
const baseCtx = { home, platform: process.platform, env: process.env };
|
|
69
86
|
if (client.install) {
|
|
70
87
|
const spinner = ora(`Checking ${client.name}`).start();
|
|
@@ -79,7 +96,7 @@ export async function configureCommand(opts = {}) {
|
|
|
79
96
|
}
|
|
80
97
|
const spinner = ora(`Configuring ${client.name}`).start();
|
|
81
98
|
try {
|
|
82
|
-
const ctx = { ...baseCtx, apiKey, baseUrl: SCALEMAX_BASE_URL };
|
|
99
|
+
const ctx = { ...baseCtx, apiKey, baseUrl: SCALEMAX_BASE_URL, providerScope };
|
|
83
100
|
const res = await client.configure(ctx);
|
|
84
101
|
spinner.succeed(`${client.name} ${res.message}`);
|
|
85
102
|
if (client.verify) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function doctorCommand(home = homeDir()) {
|
|
|
24
24
|
failures++;
|
|
25
25
|
}
|
|
26
26
|
const st = ora('Checking streaming').start();
|
|
27
|
-
const stream = await testStreaming(key, res.models.find(m => m.id === '
|
|
27
|
+
const stream = await testStreaming(key, res.models.find(m => m.id === 'fable-5')?.id || res.models[0]?.id);
|
|
28
28
|
if (stream.ok)
|
|
29
29
|
st.succeed('Streaming available');
|
|
30
30
|
else {
|
package/dist/commands/login.js
CHANGED
|
@@ -5,6 +5,7 @@ import { validateApiKey } from '../utils/api.js';
|
|
|
5
5
|
import { saveApiKey } from '../utils/keyStore.js';
|
|
6
6
|
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
7
7
|
import { homeDir } from '../utils/paths.js';
|
|
8
|
+
import { sanitizeApiKey } from '../utils/apiKey.js';
|
|
8
9
|
export async function loginCommand(home = homeDir()) {
|
|
9
10
|
intro(chalk.cyan('ScaleMax Login'));
|
|
10
11
|
const key = await password({ message: 'Paste your ScaleMax API Key:', mask: '•', validate(value) { return value.trim().length < 12 ? 'Enter a valid ScaleMax API key.' : undefined; } });
|
|
@@ -12,14 +13,15 @@ export async function loginCommand(home = homeDir()) {
|
|
|
12
13
|
cancel('Login cancelled.');
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
16
|
+
const sanitizedKey = sanitizeApiKey(key);
|
|
15
17
|
const spinner = ora('Verifying API key').start();
|
|
16
|
-
const result = await validateApiKey(
|
|
18
|
+
const result = await validateApiKey(sanitizedKey);
|
|
17
19
|
if (!result.ok) {
|
|
18
20
|
spinner.fail('Invalid API Key');
|
|
19
21
|
throw new Error(result.error || 'Invalid API Key');
|
|
20
22
|
}
|
|
21
|
-
await saveApiKey(
|
|
22
|
-
spinner.succeed(
|
|
23
|
+
await saveApiKey(sanitizedKey, SCALEMAX_BASE_URL, home);
|
|
24
|
+
spinner.succeed(`API Key verified (${result.key?.provider_scope || 'both'} provider scope)`);
|
|
23
25
|
outro(chalk.green('ScaleMax API key saved.'));
|
|
24
|
-
return
|
|
26
|
+
return sanitizedKey;
|
|
25
27
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { clientConfigPath } from './paths.js';
|
|
2
|
+
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
+
import { jsonConfig } from './templates.js';
|
|
4
|
+
const id = 'api-code';
|
|
5
|
+
const name = 'API Code';
|
|
6
|
+
export async function configure(ctx) {
|
|
7
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
+
const content = jsonConfig(name, ctx.apiKey, ctx.baseUrl, { "environment": { "SCALEMAX_API_KEY": "your ScaleMax API key", "SCALEMAX_BASE_URL": "https://api.scalemax.pro/v1" }, "node": "import OpenAI from 'openai';\nconst client = new OpenAI({ apiKey: process.env.SCALEMAX_API_KEY, baseURL: 'https://api.scalemax.pro/v1' });", "python": "from openai import OpenAI\nclient = OpenAI(api_key='...', base_url='https://api.scalemax.pro/v1')" });
|
|
9
|
+
const result = await writeManagedConfig(ctx, id, name, file, content);
|
|
10
|
+
return { ...result, message: 'API code examples written' };
|
|
11
|
+
}
|
|
12
|
+
export async function remove(ctx) {
|
|
13
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
14
|
+
return removeManagedConfig(ctx, id, name, file);
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { clientConfigPath } from './paths.js';
|
|
2
|
+
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
+
import { jsonConfig } from './templates.js';
|
|
4
|
+
const id = 'cherry-studio';
|
|
5
|
+
const name = 'Cherry Studio';
|
|
6
|
+
export async function configure(ctx) {
|
|
7
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
+
const content = jsonConfig(name, ctx.apiKey, ctx.baseUrl, { "officialSetup": ["Settings > Model Service / Model Providers", "Provider type: OpenAI or OpenAI Compatible", "API host/base URL: https://api.scalemax.pro/v1", "Add ScaleMax model IDs or fetch from /v1/models"], "recommendedModel": "fable-5" });
|
|
9
|
+
const result = await writeManagedConfig(ctx, id, name, file, content);
|
|
10
|
+
return { ...result, message: 'setup guide written; enter these values in Cherry Studio model provider settings' };
|
|
11
|
+
}
|
|
12
|
+
export async function remove(ctx) {
|
|
13
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
14
|
+
return removeManagedConfig(ctx, id, name, file);
|
|
15
|
+
}
|
package/dist/configure/claude.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { clientConfigPath } from './paths.js';
|
|
2
2
|
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
-
import { jsonConfig } from './templates.js';
|
|
4
3
|
const id = 'claude';
|
|
5
4
|
const name = 'Claude Code';
|
|
6
5
|
export async function configure(ctx) {
|
|
7
6
|
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
-
const content =
|
|
9
|
-
|
|
7
|
+
const content = JSON.stringify({
|
|
8
|
+
env: {
|
|
9
|
+
ANTHROPIC_AUTH_TOKEN: ctx.apiKey,
|
|
10
|
+
ANTHROPIC_BASE_URL: ctx.baseUrl,
|
|
11
|
+
ANTHROPIC_MODEL: 'fable-5'
|
|
12
|
+
},
|
|
13
|
+
scalemax: {
|
|
14
|
+
managedBy: 'ScaleMax CLI',
|
|
15
|
+
note: 'Claude Code uses Anthropic Messages, not OpenAI Chat Completions. This file stores ScaleMax values, but Claude Code requires a ScaleMax Anthropic-compatible endpoint before it can work reliably.'
|
|
16
|
+
}
|
|
17
|
+
}, null, 2) + '\n';
|
|
18
|
+
const result = await writeManagedConfig(ctx, id, name, file, content);
|
|
19
|
+
return { ...result, message: 'settings written; Claude Code requires Anthropic-compatible ScaleMax endpoint' };
|
|
10
20
|
}
|
|
11
21
|
export async function remove(ctx) {
|
|
12
22
|
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
package/dist/configure/codex.js
CHANGED
|
@@ -8,9 +8,9 @@ import { commandExists } from '../utils/exec.js';
|
|
|
8
8
|
const id = 'codex';
|
|
9
9
|
const name = 'Codex';
|
|
10
10
|
const provider = 'scalemax';
|
|
11
|
-
const model = '
|
|
11
|
+
const model = 'fable-5';
|
|
12
12
|
function codexConfig(baseUrl) {
|
|
13
|
-
return `# ${GENERATED_HEADER}\nmodel = "${model}"\nmodel_provider = "${provider}"\ndisable_response_storage = true\n# Recommended ScaleMax Codex models: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex.\n# Provider-internal gpt-5.3-codex-spark is routed behind the public gpt-5.3-codex Codex slug.\n\n[model_providers.${provider}]\nname = "ScaleMax"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nrequires_openai_auth = true\n`;
|
|
13
|
+
return `# ${GENERATED_HEADER}\nmodel = "${model}"\nmodel_provider = "${provider}"\ndisable_response_storage = true\n# Recommended ScaleMax Codex models: fable-5, fable-5-ultra, gpt-5.6-luna, gpt-5.6-terra, gpt-5.6-sol, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex.\n# Provider-internal gpt-5.3-codex-spark is routed behind the public gpt-5.3-codex Codex slug.\n\n[model_providers.${provider}]\nname = "ScaleMax"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nrequires_openai_auth = true\n`;
|
|
14
14
|
}
|
|
15
15
|
function codexAuth(apiKey) {
|
|
16
16
|
return JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2) + '\n';
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { clientConfigPath } from './paths.js';
|
|
2
2
|
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
-
import { jsonConfig } from './templates.js';
|
|
4
3
|
const id = 'opencode';
|
|
5
4
|
const name = 'OpenCode';
|
|
6
5
|
export async function configure(ctx) {
|
|
7
6
|
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
-
const content =
|
|
7
|
+
const content = JSON.stringify({
|
|
8
|
+
$schema: 'https://opencode.ai/config.json',
|
|
9
|
+
provider: {
|
|
10
|
+
scalemax: {
|
|
11
|
+
npm: '@ai-sdk/openai-compatible',
|
|
12
|
+
name: 'ScaleMax',
|
|
13
|
+
options: { baseURL: ctx.baseUrl, apiKey: ctx.apiKey },
|
|
14
|
+
models: {
|
|
15
|
+
'fable-5': { name: 'Fable 5' },
|
|
16
|
+
'fable-5-ultra': { name: 'Fable 5 Ultra' },
|
|
17
|
+
'gpt-5.6-luna': { name: 'GPT-5.6 Luna' },
|
|
18
|
+
'gpt-5.6-terra': { name: 'GPT-5.6 Terra' },
|
|
19
|
+
'gpt-5.6-sol': { name: 'GPT-5.6 Sol' },
|
|
20
|
+
'gpt-5.4': { name: 'GPT-5.4' },
|
|
21
|
+
'gpt-5.4-mini': { name: 'GPT-5.4 Mini' },
|
|
22
|
+
'gpt-5.3-codex': { name: 'GPT-5.3 Codex' },
|
|
23
|
+
'codex-auto-review': { name: 'Codex Auto Review' }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}, null, 2) + '\n';
|
|
9
28
|
return writeManagedConfig(ctx, id, name, file, content);
|
|
10
29
|
}
|
|
11
30
|
export async function remove(ctx) {
|
package/dist/configure/paths.js
CHANGED
|
@@ -9,15 +9,13 @@ export function clientConfigPath(id, home, platform, env = process.env) {
|
|
|
9
9
|
switch (id) {
|
|
10
10
|
case 'codex': return codexConfigPath(home);
|
|
11
11
|
case 'cursor': return platform === 'win32' ? path.join(app, 'Cursor', 'User', 'settings.json') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json') : path.join(linux, 'Cursor', 'User', 'settings.json');
|
|
12
|
+
case 'trae-solo': return path.join(home, '.scalemax', 'trae-solo.setup.json');
|
|
12
13
|
case 'claude': return path.join(home, '.claude', 'settings.json');
|
|
13
|
-
case 'opencode': return path.join(linux, 'opencode', '
|
|
14
|
+
case 'opencode': return path.join(linux, 'opencode', 'opencode.json');
|
|
14
15
|
case 'openclaw': return path.join(home, '.openclaw', 'config.json');
|
|
15
16
|
case 'hermes': return path.join(home, '.hermes', 'config', 'scalemax.json');
|
|
16
|
-
case '
|
|
17
|
-
case '
|
|
18
|
-
case 'cline': return path.join(home, '.scalemax', 'cline-vscode.manual.json');
|
|
19
|
-
case 'vscode': return platform === 'win32' ? path.join(app, 'Code', 'User', 'settings.json') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json') : path.join(linux, 'Code', 'User', 'settings.json');
|
|
20
|
-
case 'windsurf': return platform === 'win32' ? path.join(app, 'Windsurf', 'User', 'settings.json') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'Windsurf', 'User', 'settings.json') : path.join(linux, 'Windsurf', 'User', 'settings.json');
|
|
17
|
+
case 'cherry-studio': return path.join(home, '.scalemax', 'cherry-studio.setup.json');
|
|
18
|
+
case 'api-code': return path.join(home, '.scalemax', 'api-code.examples.json');
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
21
|
export function installHints(id, home, platform, env = process.env) {
|
|
@@ -26,14 +24,12 @@ export function installHints(id, home, platform, env = process.env) {
|
|
|
26
24
|
switch (id) {
|
|
27
25
|
case 'codex': return [codexDir(home), path.join(linux, 'codex')];
|
|
28
26
|
case 'cursor': return [platform === 'darwin' ? '/Applications/Cursor.app' : path.join(app, 'Cursor'), path.join(linux, 'Cursor')];
|
|
27
|
+
case 'trae-solo': return [platform === 'darwin' ? '/Applications/Trae.app' : path.join(app, 'Trae'), path.join(linux, 'Trae')];
|
|
29
28
|
case 'claude': return [path.join(home, '.claude')];
|
|
30
29
|
case 'opencode': return [path.join(linux, 'opencode')];
|
|
31
30
|
case 'openclaw': return [path.join(home, '.openclaw'), path.join(linux, 'openclaw')];
|
|
32
31
|
case 'hermes': return [path.join(home, '.hermes')];
|
|
33
|
-
case '
|
|
34
|
-
case '
|
|
35
|
-
case 'cline': return [];
|
|
36
|
-
case 'vscode': return [platform === 'darwin' ? '/Applications/Visual Studio Code.app' : path.join(app, 'Code'), path.join(linux, 'Code')];
|
|
37
|
-
case 'windsurf': return [platform === 'darwin' ? '/Applications/Windsurf.app' : path.join(app, 'Windsurf'), path.join(linux, 'Windsurf')];
|
|
32
|
+
case 'cherry-studio': return [platform === 'darwin' ? '/Applications/Cherry Studio.app' : path.join(app, 'Cherry Studio'), path.join(linux, 'CherryStudio')];
|
|
33
|
+
case 'api-code': return [path.join(home, '.scalemax')];
|
|
38
34
|
}
|
|
39
35
|
}
|
|
@@ -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.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review'];
|
|
14
|
+
const models = ['fable-5', 'fable-5-ultra', 'gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review'];
|
|
15
15
|
return JSON.stringify({
|
|
16
16
|
models: models.map(model => ({
|
|
17
17
|
title: `ScaleMax ${model}`,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { clientConfigPath } from './paths.js';
|
|
2
|
+
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
+
import { jsonConfig } from './templates.js';
|
|
4
|
+
const id = 'trae-solo';
|
|
5
|
+
const name = 'TRAE SOLO';
|
|
6
|
+
export async function configure(ctx) {
|
|
7
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
+
const content = jsonConfig(name, ctx.apiKey, ctx.baseUrl, { "officialSetup": ["Settings > Models > Add Model", "API format: OpenAI Chat Completions", "Request URL: https://api.scalemax.pro/v1/chat/completions or base URL https://api.scalemax.pro/v1", "Model ID: fable-5 or another ScaleMax model"], "recommendedModel": "fable-5" });
|
|
9
|
+
const result = await writeManagedConfig(ctx, id, name, file, content);
|
|
10
|
+
return { ...result, message: 'setup guide written; enter these values in TRAE SOLO custom model settings' };
|
|
11
|
+
}
|
|
12
|
+
export async function remove(ctx) {
|
|
13
|
+
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
14
|
+
return removeManagedConfig(ctx, id, name, file);
|
|
15
|
+
}
|
package/dist/detect/common.js
CHANGED
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { commandExists } from '../utils/exec.js';
|
|
3
3
|
import { clientConfigPath, installHints } from '../configure/paths.js';
|
|
4
|
-
function vscodeExtensionRoots(ctx) {
|
|
5
|
-
const app = ctx.env.APPDATA || '';
|
|
6
|
-
const xdg = ctx.env.XDG_DATA_HOME || pathJoin(ctx.home, '.local', 'share');
|
|
7
|
-
if (ctx.platform === 'win32')
|
|
8
|
-
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(app, 'Code', 'User', 'globalStorage')];
|
|
9
|
-
if (ctx.platform === 'darwin')
|
|
10
|
-
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(ctx.home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage')];
|
|
11
|
-
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(xdg, 'Code', 'User', 'globalStorage')];
|
|
12
|
-
}
|
|
13
|
-
function pathJoin(...parts) { return parts.filter(Boolean).join('/'); }
|
|
14
|
-
async function detectVsCodeExtension(ctx, patterns) {
|
|
15
|
-
const hits = [];
|
|
16
|
-
for (const root of vscodeExtensionRoots(ctx)) {
|
|
17
|
-
const entries = await fs.readdir(root).catch(() => []);
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
const lower = entry.toLowerCase();
|
|
20
|
-
if (patterns.some(pattern => lower.includes(pattern)))
|
|
21
|
-
hits.push(`${root}/${entry}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return hits;
|
|
25
|
-
}
|
|
26
4
|
const commands = {
|
|
27
|
-
codex: ['codex'],
|
|
5
|
+
codex: ['codex'],
|
|
6
|
+
cursor: ['cursor'],
|
|
7
|
+
'trae-solo': ['trae', 'trae-solo'],
|
|
8
|
+
claude: ['claude'],
|
|
9
|
+
opencode: ['opencode'],
|
|
10
|
+
openclaw: ['openclaw'],
|
|
11
|
+
hermes: ['hermes'],
|
|
12
|
+
'cherry-studio': ['cherry-studio', 'cherrystudio'],
|
|
13
|
+
'api-code': []
|
|
28
14
|
};
|
|
29
15
|
export async function detectClient(ctx, id, name) {
|
|
30
16
|
const paths = [clientConfigPath(id, ctx.home, ctx.platform, ctx.env), ...installHints(id, ctx.home, ctx.platform, ctx.env)];
|
|
@@ -36,11 +22,7 @@ export async function detectClient(ctx, id, name) {
|
|
|
36
22
|
for (const cmd of commands[id])
|
|
37
23
|
if (await commandExists(cmd))
|
|
38
24
|
cmdHits.push(`command:${cmd}`);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
? await detectVsCodeExtension(ctx, ['saoudrizwan.claude-dev', 'cline.cline'])
|
|
43
|
-
: [];
|
|
44
|
-
const hits = [...cmdHits, ...pathHits, ...extensionHits];
|
|
45
|
-
return { id, name, installed: hits.length > 0, reason: hits.length ? undefined : 'not detected', paths: hits };
|
|
25
|
+
const hits = [...cmdHits, ...pathHits];
|
|
26
|
+
const alwaysAvailable = id === 'api-code';
|
|
27
|
+
return { id, name, installed: hits.length > 0 || alwaysAvailable, reason: hits.length || alwaysAvailable ? undefined : 'not detected', paths: hits };
|
|
46
28
|
}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type ClientId = 'codex' | 'cursor' | '
|
|
1
|
+
export type ClientId = 'codex' | 'cursor' | 'trae-solo' | 'claude' | 'opencode' | 'openclaw' | 'hermes' | 'cherry-studio' | 'api-code';
|
|
2
|
+
export type ProviderScope = 'openai' | 'anthropic' | 'both';
|
|
2
3
|
export interface ClientDefinition {
|
|
3
4
|
id: ClientId;
|
|
4
5
|
name: string;
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
export type ProviderScope = 'openai' | 'anthropic' | 'both';
|
|
1
2
|
export interface ModelInfo {
|
|
2
3
|
id: string;
|
|
3
4
|
object?: string;
|
|
4
5
|
owned_by?: string;
|
|
5
6
|
display_name?: string;
|
|
6
7
|
}
|
|
8
|
+
export interface KeyMetadata {
|
|
9
|
+
provider_scope: ProviderScope;
|
|
10
|
+
mode?: string;
|
|
11
|
+
prefix?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
7
14
|
export declare function validateApiKey(apiKey: string): Promise<{
|
|
8
15
|
ok: boolean;
|
|
9
16
|
models: ModelInfo[];
|
|
17
|
+
key?: KeyMetadata;
|
|
10
18
|
error?: string;
|
|
11
19
|
}>;
|
|
12
20
|
export declare function testStreaming(apiKey: string, model?: string): Promise<{
|
package/dist/utils/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
2
|
+
import { sanitizeApiKey } from './apiKey.js';
|
|
2
3
|
async function friendlyError(res) {
|
|
3
4
|
let body = '';
|
|
4
5
|
try {
|
|
@@ -22,7 +23,7 @@ async function friendlyError(res) {
|
|
|
22
23
|
async function request(path, apiKey, init = {}) {
|
|
23
24
|
const headers = new Headers(init.headers);
|
|
24
25
|
if (apiKey)
|
|
25
|
-
headers.set('Authorization', `Bearer ${apiKey}`);
|
|
26
|
+
headers.set('Authorization', `Bearer ${sanitizeApiKey(apiKey)}`);
|
|
26
27
|
headers.set('Cache-Control', 'no-cache');
|
|
27
28
|
if (!headers.has('Content-Type') && init.body)
|
|
28
29
|
headers.set('Content-Type', 'application/json');
|
|
@@ -30,18 +31,23 @@ async function request(path, apiKey, init = {}) {
|
|
|
30
31
|
}
|
|
31
32
|
export async function validateApiKey(apiKey) {
|
|
32
33
|
try {
|
|
33
|
-
const
|
|
34
|
+
const authCheck = await request('/usage', apiKey);
|
|
35
|
+
if (!authCheck.ok)
|
|
36
|
+
return { ok: false, models: [], error: await friendlyError(authCheck) };
|
|
37
|
+
const usage = await authCheck.json().catch(() => ({}));
|
|
38
|
+
const keyMeta = { provider_scope: usage?.key?.provider_scope || 'both', mode: usage?.key?.mode, prefix: usage?.key?.prefix, label: usage?.key?.label };
|
|
39
|
+
const res = await request('/models');
|
|
34
40
|
if (!res.ok)
|
|
35
|
-
return { ok:
|
|
41
|
+
return { ok: true, models: [], key: keyMeta, error: await friendlyError(res) };
|
|
36
42
|
const data = await res.json();
|
|
37
|
-
return { ok: Array.isArray(data.data)
|
|
43
|
+
return { ok: true, models: Array.isArray(data.data) ? data.data : [], key: keyMeta };
|
|
38
44
|
}
|
|
39
45
|
catch (err) {
|
|
40
46
|
return { ok: false, models: [], error: err instanceof Error ? err.message : String(err) };
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
export async function testStreaming(apiKey, model) {
|
|
44
|
-
const selected = model || '
|
|
50
|
+
const selected = model || 'fable-5';
|
|
45
51
|
try {
|
|
46
52
|
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' }] }) });
|
|
47
53
|
if (!res.ok)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeApiKey(input: unknown): string;
|
package/dist/utils/keyStore.js
CHANGED
|
@@ -2,15 +2,16 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import { KEY_ENV } from '../constants.js';
|
|
3
3
|
import { scaleMaxConfigPath, scaleMaxDir } from './paths.js';
|
|
4
4
|
import { readJson, writeJsonAtomic } from './fs.js';
|
|
5
|
+
import { sanitizeApiKey } from './apiKey.js';
|
|
5
6
|
export async function saveApiKey(apiKey, baseUrl, home) {
|
|
6
7
|
await fs.ensureDir(scaleMaxDir(home), 0o700);
|
|
7
|
-
await writeJsonAtomic(scaleMaxConfigPath(home), { apiKey, baseUrl, updatedAt: new Date().toISOString() }, 0o600);
|
|
8
|
+
await writeJsonAtomic(scaleMaxConfigPath(home), { apiKey: sanitizeApiKey(apiKey), baseUrl, updatedAt: new Date().toISOString() }, 0o600);
|
|
8
9
|
}
|
|
9
10
|
export async function loadApiKey(home) {
|
|
10
11
|
if (process.env[KEY_ENV])
|
|
11
|
-
return process.env[KEY_ENV];
|
|
12
|
+
return sanitizeApiKey(process.env[KEY_ENV]);
|
|
12
13
|
const cfg = await readJson(scaleMaxConfigPath(home), {});
|
|
13
|
-
return cfg.apiKey;
|
|
14
|
+
return cfg.apiKey ? sanitizeApiKey(cfg.apiKey) : undefined;
|
|
14
15
|
}
|
|
15
16
|
export async function removeApiKey(home) {
|
|
16
17
|
await fs.remove(scaleMaxConfigPath(home));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scalemax",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Universal ScaleMax installer for AI coding clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"installer",
|
|
52
52
|
"codex",
|
|
53
53
|
"cursor",
|
|
54
|
+
"trae-solo",
|
|
55
|
+
"claude-code",
|
|
54
56
|
"opencode",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"cline",
|
|
59
|
-
"windsurf",
|
|
57
|
+
"openclaw",
|
|
58
|
+
"hermes",
|
|
59
|
+
"cherry-studio",
|
|
60
60
|
"openai-compatible"
|
|
61
61
|
],
|
|
62
62
|
"license": "MIT",
|
package/dist/configure/cline.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const id = 'cline';
|
|
2
|
-
const name = 'Cline (VS Code)';
|
|
3
|
-
export async function configure(ctx) {
|
|
4
|
-
return {
|
|
5
|
-
id,
|
|
6
|
-
name,
|
|
7
|
-
changed: false,
|
|
8
|
-
message: `detected. Open the extension settings and select API Provider: OpenAI Compatible; Base URL: ${ctx.baseUrl}; API Key: your ScaleMax key; Model ID: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, or codex-auto-review. Official docs: https://docs.cline.bot/provider-config/openai-compatible`,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export async function remove(_ctx) {
|
|
12
|
-
return { id, name, changed: false, message: 'Roo/Cline settings are stored inside VS Code extension state. Remove or edit them from the extension settings UI.' };
|
|
13
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { clientConfigPath } from './paths.js';
|
|
2
|
-
import { writeManagedConfig, removeManagedConfig } from './common.js';
|
|
3
|
-
import { continueConfig } from './templates.js';
|
|
4
|
-
const id = 'continue';
|
|
5
|
-
const name = 'Continue';
|
|
6
|
-
export async function configure(ctx) {
|
|
7
|
-
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
-
return writeManagedConfig(ctx, id, name, file, continueConfig(ctx.apiKey, ctx.baseUrl));
|
|
9
|
-
}
|
|
10
|
-
export async function remove(ctx) {
|
|
11
|
-
return removeManagedConfig(ctx, id, name, clientConfigPath(id, ctx.home, ctx.platform));
|
|
12
|
-
}
|
package/dist/configure/roo.d.ts
DELETED
package/dist/configure/roo.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const id = 'roo';
|
|
2
|
-
const name = 'Roo Code (VS Code)';
|
|
3
|
-
export async function configure(ctx) {
|
|
4
|
-
return {
|
|
5
|
-
id,
|
|
6
|
-
name,
|
|
7
|
-
changed: false,
|
|
8
|
-
message: `detected. Open the extension settings and select API Provider: OpenAI Compatible; Base URL: ${ctx.baseUrl}; API Key: your ScaleMax key; Model ID: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, or codex-auto-review. Official docs: https://docs.roocode.com/providers/openai-compatible`,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export async function remove(_ctx) {
|
|
12
|
-
return { id, name, changed: false, message: 'Roo/Cline settings are stored inside VS Code extension state. Remove or edit them from the extension settings UI.' };
|
|
13
|
-
}
|
package/dist/configure/vscode.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { clientConfigPath } from './paths.js';
|
|
2
|
-
import { removeManagedConfig, writeManagedConfig } from './common.js';
|
|
3
|
-
import { continueConfig } from './templates.js';
|
|
4
|
-
const id = 'vscode';
|
|
5
|
-
const name = 'Continue Extension (VS Code)';
|
|
6
|
-
export async function configure(ctx) {
|
|
7
|
-
const file = clientConfigPath('continue', ctx.home, ctx.platform);
|
|
8
|
-
return writeManagedConfig(ctx, id, name, file, continueConfig(ctx.apiKey, ctx.baseUrl));
|
|
9
|
-
}
|
|
10
|
-
export async function remove(ctx) {
|
|
11
|
-
const file = clientConfigPath('continue', ctx.home, ctx.platform);
|
|
12
|
-
return removeManagedConfig(ctx, id, name, file);
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { clientConfigPath } from './paths.js';
|
|
2
|
-
import { removeManagedConfig, writeMergedJsonConfig } from './common.js';
|
|
3
|
-
import { editorSettings } from './templates.js';
|
|
4
|
-
const id = 'windsurf';
|
|
5
|
-
const name = 'Windsurf';
|
|
6
|
-
export async function configure(ctx) {
|
|
7
|
-
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
8
|
-
return writeMergedJsonConfig(ctx, id, name, file, existing => editorSettings(name, ctx.apiKey, ctx.baseUrl, existing));
|
|
9
|
-
}
|
|
10
|
-
export async function remove(ctx) {
|
|
11
|
-
const file = clientConfigPath(id, ctx.home, ctx.platform);
|
|
12
|
-
return removeManagedConfig(ctx, id, name, file);
|
|
13
|
-
}
|
package/dist/detect/cline.js
DELETED
package/dist/detect/continue.js
DELETED
package/dist/detect/roo.js
DELETED
package/dist/detect/vscode.d.ts
DELETED
package/dist/detect/vscode.js
DELETED
package/dist/detect/windsurf.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|