scalemax 0.1.31 → 0.1.33
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 +6 -1
- package/dist/clients.js +3 -0
- package/dist/commands/configure.js +15 -9
- package/dist/commands/helper-bot.js +34 -15
- package/dist/configure/cline.d.ts +29 -0
- package/dist/configure/cline.js +581 -0
- package/dist/configure/continue.js +20 -18
- package/dist/configure/hermes.js +28 -12
- package/dist/configure/models.d.ts +23 -1
- package/dist/configure/models.js +31 -11
- package/dist/configure/openclaw.js +53 -29
- package/dist/configure/paths.js +9 -0
- package/dist/configure/roo-code.js +71 -24
- package/dist/configure/trae-solo.js +3 -1
- package/dist/detect/cline.d.ts +2 -0
- package/dist/detect/cline.js +62 -0
- package/dist/detect/common.js +1 -1
- package/dist/types/client.d.ts +12 -2
- package/dist/utils/api.d.ts +10 -1
- package/dist/utils/api.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ The helper refuses unrelated requests and redacts ScaleMax-style secrets from pr
|
|
|
136
136
|
- Codex
|
|
137
137
|
- Cursor
|
|
138
138
|
- Roo Code (VS Code)
|
|
139
|
+
- Cline (CLI and VS Code)
|
|
139
140
|
- Continue (VS Code)
|
|
140
141
|
- TRAE SOLO
|
|
141
142
|
- Claude Code
|
|
@@ -161,7 +162,7 @@ ScaleMax merges a named `scalemax` provider into `~/.config/opencode/opencode.js
|
|
|
161
162
|
|
|
162
163
|
### OpenClaw
|
|
163
164
|
|
|
164
|
-
ScaleMax partitions OpenClaw models into native `openai-responses` (`scalemax-codex`, `/v1`), OpenAI-compatible `openai-completions` (`scalemax-deepseek`, `/v1`), and `anthropic-messages` (`scalemax-claude`, origin) providers. `deepseek-v4-flash` is always isolated in the Chat Completions provider and never advertised through Responses. Its agent flags come from authenticated catalog discovery:
|
|
165
|
+
ScaleMax partitions OpenClaw models into native `openai-responses` (`scalemax-codex`, `/v1`), OpenAI-compatible `openai-completions` (`scalemax-deepseek`, `/v1`), and `anthropic-messages` (`scalemax-claude`, origin) providers. `deepseek-v4-flash` is always isolated in the Chat Completions provider and never advertised through Responses. Its agent flags come only from authenticated catalog discovery: tools, forced `tool_choice`, reasoning, reasoning effort, streamed usage, and `reasoning_content` replay are each enabled only when the exact model advertises that capability. In particular, a tool-capable route is never forced to replay reasoning content unless `capabilities.reasoning_replay=true`; a fallback route remains explicitly tool-free. The API key and compatibility User-Agent are OpenClaw file-backed SecretRefs stored in private `0600` files under `~/.openclaw/secrets/`; neither value is embedded in the main provider JSON. Migration removes the obsolete mixed provider, preserves the active model family, and scrubs plaintext credentials from generated ScaleMax agent registries even when they contain an older key. Output is capped at 4096 tokens, private-network routing is denied for the public gateway, and unrelated providers and agent settings are preserved.
|
|
165
166
|
|
|
166
167
|
### Hermes
|
|
167
168
|
|
|
@@ -171,6 +172,10 @@ ScaleMax creates family-specific Hermes providers in `~/.hermes/config.yaml` and
|
|
|
171
172
|
|
|
172
173
|
Roo Code stores provider profiles and secrets in VS Code extension storage, so ScaleMax does not mutate its private database. Instead, the CLI writes a private `0600` Roo settings-import file at `~/.scalemax/roo-code-import.json` and points the supported `roo-cline.autoImportSettingsPath` VS Code setting at it. Codex, Claude, and tool-capable DeepSeek profiles remain separate. DeepSeek is offered only when authenticated discovery reports `capabilities.tools=true`; otherwise Roo fails closed and points the user to Continue Chat/Edit. Reload VS Code once so Roo imports supported profiles into its SecretStorage. CLI verification intentionally reports the handoff as verified, not the extension runtime, until that reload occurs.
|
|
173
174
|
|
|
175
|
+
### Cline (CLI and VS Code)
|
|
176
|
+
|
|
177
|
+
ScaleMax supports the verified Cline CLI `3.0.47` and VS Code extension `4.0.12` formats. The CLI surface uses Cline's built-in `openai-native` provider and Responses protocol. The extension uses its built-in OpenAI-compatible provider. Both receive the raw ScaleMax key and the matching credit or token base URL; ScaleMax never writes a custom `Authorization` or `x-api-key` header because Cline generates `Authorization: Bearer …` itself. Configuration is attempted only while Cline and VS Code are stopped, preserves unrelated state, uses private atomic files with backups and rollback, and fails closed on unknown versions or malformed/symlinked state.
|
|
178
|
+
|
|
174
179
|
### Continue (VS Code)
|
|
175
180
|
|
|
176
181
|
Continue is the universal VS Code Chat/Edit route for `deepseek-v4-flash`. The CLI installs the official `Continue.continue` extension when VS Code is available, merges an OpenAI-compatible model into `~/.continue/config.yaml`, sets `useResponsesApi: false`, and keeps the authenticated ScaleMax key in private `~/.continue/.env` rather than the YAML. Existing Continue models and environment entries are preserved. Reload VS Code and select **ScaleMax DeepSeek V4 Flash**. For agent workflows, prefer Roo/OpenClaw/Hermes when the authenticated catalog reports `capabilities.tools=true`.
|
package/dist/clients.js
CHANGED
|
@@ -4,6 +4,8 @@ import * as cursorDetect from './detect/cursor.js';
|
|
|
4
4
|
import * as cursorConfig from './configure/cursor.js';
|
|
5
5
|
import * as rooCodeDetect from './detect/roo-code.js';
|
|
6
6
|
import * as rooCodeConfig from './configure/roo-code.js';
|
|
7
|
+
import * as clineDetect from './detect/cline.js';
|
|
8
|
+
import * as clineConfig from './configure/cline.js';
|
|
7
9
|
import * as continueDetect from './detect/continue.js';
|
|
8
10
|
import * as continueConfig from './configure/continue.js';
|
|
9
11
|
import * as traeSoloDetect from './detect/trae-solo.js';
|
|
@@ -24,6 +26,7 @@ export const clients = [
|
|
|
24
26
|
{ id: 'codex', name: 'Codex', detect: codexDetect.detect, install: codexConfig.install, configure: codexConfig.configure, verify: codexConfig.verify, launch: codexConfig.launch, remove: codexConfig.remove },
|
|
25
27
|
{ id: 'cursor', name: 'Cursor', detect: cursorDetect.detect, configure: cursorConfig.configure, verify: cursorConfig.verify, remove: cursorConfig.remove },
|
|
26
28
|
{ id: 'roo-code', name: 'Roo Code (VS Code)', detect: rooCodeDetect.detect, configure: rooCodeConfig.configure, verify: rooCodeConfig.verify, remove: rooCodeConfig.remove },
|
|
29
|
+
{ id: 'cline', name: 'Cline', detect: clineDetect.detect, install: clineConfig.install, configure: clineConfig.configure, verify: clineConfig.verify, remove: clineConfig.remove },
|
|
27
30
|
{ id: 'continue', name: 'Continue (VS Code)', detect: continueDetect.detect, install: continueConfig.install, configure: continueConfig.configure, verify: continueConfig.verify, remove: continueConfig.remove },
|
|
28
31
|
{ id: 'trae-solo', name: 'TRAE SOLO', detect: traeSoloDetect.detect, configure: traeSoloConfig.configure, remove: traeSoloConfig.remove },
|
|
29
32
|
{ id: 'claude', name: 'Claude Code', detect: claudeDetect.detect, configure: claudeConfig.configure, verify: claudeConfig.verify, remove: claudeConfig.remove },
|
|
@@ -4,9 +4,9 @@ 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, isAnthropicModel } from '../utils/api.js';
|
|
7
|
+
import { validateApiKey, isAnthropicModel, isModelRouteAvailable } from '../utils/api.js';
|
|
8
8
|
import { homeDir } from '../utils/paths.js';
|
|
9
|
-
import {
|
|
9
|
+
import { isExactChatCompletionsModelId } from '../configure/models.js';
|
|
10
10
|
export async function detectAll(home = homeDir()) {
|
|
11
11
|
const ctx = { home, platform: process.platform, env: process.env };
|
|
12
12
|
return Promise.all(clients.map(c => c.detect(ctx)));
|
|
@@ -41,23 +41,27 @@ async function maybeLaunchCodex(chosen, successful, baseUrl = SCALEMAX_BASE_URL)
|
|
|
41
41
|
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');
|
|
42
42
|
}
|
|
43
43
|
export function clientAllowedForModels(clientId, models) {
|
|
44
|
+
models = models.filter(isModelRouteAvailable);
|
|
44
45
|
if (!models.length)
|
|
45
46
|
return false;
|
|
46
47
|
// These clients use either a native family-specific adapter or the gateway's
|
|
47
48
|
// normalized OpenAI-compatible Chat Completions route, which supports both
|
|
48
49
|
// authenticated model families. Codex remains Responses-only; TRAE uses
|
|
49
50
|
// OpenAI-compatible Chat Completions.
|
|
50
|
-
const crossProtocolClients = new Set(['cursor', 'roo-code', 'opencode', 'openclaw', 'hermes', 'cherry-studio', 'api-code']);
|
|
51
|
+
const crossProtocolClients = new Set(['cursor', 'roo-code', 'cline', 'opencode', 'openclaw', 'hermes', 'cherry-studio', 'api-code']);
|
|
51
52
|
if (clientId === 'continue')
|
|
52
|
-
return models.some(model =>
|
|
53
|
+
return models.some(model => isExactChatCompletionsModelId(model.id));
|
|
53
54
|
if (clientId === 'claude')
|
|
54
55
|
return models.some(isAnthropicModel);
|
|
55
56
|
if (clientId === 'codex')
|
|
56
|
-
return models.some(model => !isAnthropicModel(model) && !
|
|
57
|
-
if (clientId === 'hermes' && models.every(model =>
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
return models.some(model => !isAnthropicModel(model) && !isExactChatCompletionsModelId(model.id));
|
|
58
|
+
if (clientId === 'hermes' && models.every(model => isExactChatCompletionsModelId(model.id)))
|
|
59
|
+
return models.some(model => model.capabilities?.tools === true);
|
|
60
|
+
// Roo is an agent, not a text-only chat surface. Its task loop depends on
|
|
61
|
+
// native function calls for every model family, so offer it only when the
|
|
62
|
+
// authenticated catalog confirms at least one viable route.
|
|
63
|
+
if (clientId === 'roo-code' || clientId === 'cline')
|
|
64
|
+
return models.some(model => model.capabilities?.tools === true);
|
|
61
65
|
if (crossProtocolClients.has(clientId))
|
|
62
66
|
return true;
|
|
63
67
|
return models.some(model => !isAnthropicModel(model));
|
|
@@ -65,6 +69,8 @@ export function clientAllowedForModels(clientId, models) {
|
|
|
65
69
|
function scopeHint(scope) {
|
|
66
70
|
if (scope === 'deepseek-v4-flash')
|
|
67
71
|
return 'DeepSeek V4 Flash only';
|
|
72
|
+
if (scope === 'glm-5.2')
|
|
73
|
+
return 'GLM 5.2 only';
|
|
68
74
|
return scope === 'both' ? 'OpenAI + Claude' : scope === 'anthropic' ? 'Claude only' : 'OpenAI only';
|
|
69
75
|
}
|
|
70
76
|
export async function configureCommand(opts = {}) {
|
|
@@ -4,13 +4,14 @@ import { createInterface } from 'node:readline/promises';
|
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { clients } from '../clients.js';
|
|
6
6
|
import { helperDocsContext } from './helper-docs.js';
|
|
7
|
-
import { createChatCompletion, isAnthropicModel } from '../utils/api.js';
|
|
7
|
+
import { createChatCompletion, filterModelsForScope, isAnthropicModel } from '../utils/api.js';
|
|
8
8
|
import { redactApiKey } from '../utils/apiKey.js';
|
|
9
9
|
import { loadApiKey } from '../utils/keyStore.js';
|
|
10
10
|
import { homeDir } from '../utils/paths.js';
|
|
11
11
|
import { validateApiKey } from '../utils/api.js';
|
|
12
12
|
import { commandExists } from '../utils/exec.js';
|
|
13
13
|
import { installClaudeGithubTools } from '../configure/claude-tools.js';
|
|
14
|
+
import { isExactChatCompletionsModelId } from '../configure/models.js';
|
|
14
15
|
export const SCALEMAX_HELPER_REFUSAL = `I am the official ScaleMax Helper.
|
|
15
16
|
|
|
16
17
|
I can only assist with installing, configuring, repairing, verifying, and troubleshooting ScaleMax.
|
|
@@ -762,6 +763,7 @@ For automatic configuration, exit with \`/quit\`, run \`scalemax update\`, then
|
|
|
762
763
|
const HELPER_CLIENT_PATTERNS = [
|
|
763
764
|
{ id: 'continue', pattern: /\b(?:continue(?:\.dev)?|vscode\s+continue|vs\s+code\s+continue)\b/i },
|
|
764
765
|
{ id: 'roo-code', pattern: /\b(?:roo(?:\s+code)?|roo\s+(?:extension|plugin)|vscode\s+roo|vs\s+code\s+roo)\b/i },
|
|
766
|
+
{ id: 'cline', pattern: /\bcline(?:\s+(?:extension|plugin|cli))?\b/i },
|
|
765
767
|
{ id: 'openclaw', pattern: /\bopen\s*claw\b/i },
|
|
766
768
|
{ id: 'opencode', pattern: /\bopen\s*code\b/i },
|
|
767
769
|
{ id: 'cherry-studio', pattern: /\bcherry\s*studio\b/i },
|
|
@@ -800,6 +802,8 @@ function helperActionTarget(question) {
|
|
|
800
802
|
return undefined;
|
|
801
803
|
}
|
|
802
804
|
function requestedProviderScope(question, target) {
|
|
805
|
+
if (/\bglm[-\s]*5[._\s]*2\b/i.test(question))
|
|
806
|
+
return 'glm-5.2';
|
|
803
807
|
if (/\bdeepseek(?:[-\s]+v4[-\s]+flash)?\b/i.test(question))
|
|
804
808
|
return 'deepseek-v4-flash';
|
|
805
809
|
if (/\b(?:both|openai\s*\+\s*claude|claude\s*\+\s*openai)\b/i.test(question))
|
|
@@ -811,35 +815,48 @@ function requestedProviderScope(question, target) {
|
|
|
811
815
|
return undefined;
|
|
812
816
|
}
|
|
813
817
|
function actionModels(connection, requestedScope) {
|
|
814
|
-
const
|
|
818
|
+
const rawCatalog = connection.modelCatalog?.length
|
|
815
819
|
? connection.modelCatalog
|
|
816
820
|
: (connection.availableModels || []).map(id => ({ id }));
|
|
821
|
+
const entitlement = connection.providerScope || 'both';
|
|
822
|
+
// Always begin with the key's verified entitlement. Natural-language intent
|
|
823
|
+
// must never widen an exact-model key into an OpenAI/Claude family key.
|
|
824
|
+
const catalog = filterModelsForScope(rawCatalog, entitlement);
|
|
817
825
|
if (!requestedScope)
|
|
818
826
|
return catalog;
|
|
819
|
-
|
|
820
|
-
|
|
827
|
+
const exactEntitlement = entitlement === 'deepseek-v4-flash' || entitlement === 'glm-5.2';
|
|
828
|
+
if (exactEntitlement && requestedScope !== entitlement)
|
|
829
|
+
return [];
|
|
830
|
+
if (requestedScope === 'deepseek-v4-flash' || requestedScope === 'glm-5.2') {
|
|
831
|
+
return requestedScope === entitlement
|
|
832
|
+
? catalog.filter(model => model.id.trim().toLowerCase() === requestedScope)
|
|
833
|
+
: [];
|
|
821
834
|
}
|
|
822
835
|
if (requestedScope === 'both') {
|
|
823
|
-
return catalog.filter(model => model.id
|
|
836
|
+
return catalog.filter(model => !isExactChatCompletionsModelId(model.id));
|
|
824
837
|
}
|
|
825
838
|
return catalog.filter(model => requestedScope === 'anthropic'
|
|
826
839
|
? isAnthropicModel(model)
|
|
827
|
-
: !isAnthropicModel(model) && model.id
|
|
840
|
+
: !isAnthropicModel(model) && !isExactChatCompletionsModelId(model.id));
|
|
828
841
|
}
|
|
829
842
|
function helperClientAllowedForModels(clientId, models) {
|
|
830
843
|
if (!models.length)
|
|
831
844
|
return false;
|
|
832
|
-
const crossProtocolClients = new Set(['cursor', 'roo-code', 'opencode', 'openclaw', 'hermes', 'cherry-studio', 'api-code']);
|
|
833
|
-
const
|
|
845
|
+
const crossProtocolClients = new Set(['cursor', 'roo-code', 'cline', 'opencode', 'openclaw', 'hermes', 'cherry-studio', 'api-code']);
|
|
846
|
+
const onlyExactChat = models.every(model => isExactChatCompletionsModelId(model.id));
|
|
834
847
|
if (clientId === 'continue')
|
|
835
|
-
return
|
|
848
|
+
return onlyExactChat;
|
|
836
849
|
if (clientId === 'claude')
|
|
837
850
|
return models.some(isAnthropicModel);
|
|
838
|
-
if (
|
|
851
|
+
if (clientId === 'codex' && onlyExactChat)
|
|
839
852
|
return false;
|
|
853
|
+
if (clientId === 'hermes' && onlyExactChat)
|
|
854
|
+
return models.some(model => model.capabilities?.tools === true);
|
|
855
|
+
if (clientId === 'roo-code' || clientId === 'cline')
|
|
856
|
+
return models.some(model => model.capabilities?.tools === true);
|
|
840
857
|
if (crossProtocolClients.has(clientId))
|
|
841
858
|
return true;
|
|
842
|
-
return models.some(model => !isAnthropicModel(model) && model.id
|
|
859
|
+
return models.some(model => !isAnthropicModel(model) && !isExactChatCompletionsModelId(model.id));
|
|
843
860
|
}
|
|
844
861
|
function hasRooCodeExtension(paths) {
|
|
845
862
|
return paths.some(candidate => /^rooveterinaryinc\.roo-cline-/i.test(path.basename(candidate)));
|
|
@@ -923,8 +940,8 @@ async function installRooCodeExtension(ctx, client, write) {
|
|
|
923
940
|
async function runOneHelperClientAction(kind, client, connection, home, requestedScope, write) {
|
|
924
941
|
const models = actionModels(connection, requestedScope);
|
|
925
942
|
if (!models.length)
|
|
926
|
-
throw new Error(`The authenticated key does not expose ${requestedScope === 'anthropic' ? 'Claude' : requestedScope === 'openai' ? 'OpenAI' : requestedScope === 'deepseek-v4-flash' ? 'DeepSeek V4 Flash' : 'compatible'} models for ${client.name}. No configuration was changed.`);
|
|
927
|
-
if (client.id === 'codex' && models.every(model => isAnthropicModel(model) || model.id
|
|
943
|
+
throw new Error(`The authenticated key does not expose ${requestedScope === 'anthropic' ? 'Claude' : requestedScope === 'openai' ? 'OpenAI' : requestedScope === 'deepseek-v4-flash' ? 'DeepSeek V4 Flash' : requestedScope === 'glm-5.2' ? 'GLM 5.2' : 'compatible'} models for ${client.name}. No configuration was changed.`);
|
|
944
|
+
if (client.id === 'codex' && models.every(model => isAnthropicModel(model) || isExactChatCompletionsModelId(model.id)))
|
|
928
945
|
throw new Error('Codex requires a Responses-capable model, but this key exposes only Claude or Chat-Completions-only models. No configuration was changed.');
|
|
929
946
|
if (client.id === 'cursor' && connection.platform !== 'darwin')
|
|
930
947
|
throw new Error(`Automatic Cursor configuration is currently verified on macOS only. ${helperPlatformName(connection.platform)} was not modified.`);
|
|
@@ -934,7 +951,7 @@ async function runOneHelperClientAction(kind, client, connection, home, requeste
|
|
|
934
951
|
env: { ...process.env },
|
|
935
952
|
apiKey: connection.apiKey,
|
|
936
953
|
baseUrl: connection.baseUrl,
|
|
937
|
-
providerScope:
|
|
954
|
+
providerScope: connection.providerScope || 'both',
|
|
938
955
|
models,
|
|
939
956
|
interactive: false,
|
|
940
957
|
allowAppModification: false
|
|
@@ -998,7 +1015,9 @@ async function runOneHelperClientAction(kind, client, connection, home, requeste
|
|
|
998
1015
|
}
|
|
999
1016
|
const activation = client.id === 'roo-code'
|
|
1000
1017
|
? ' Reload VS Code once so Roo Code imports the verified ScaleMax profile.'
|
|
1001
|
-
: client.id === '
|
|
1018
|
+
: client.id === 'cline'
|
|
1019
|
+
? ' Restart Cline; reload VS Code too when using the extension.'
|
|
1020
|
+
: client.id === 'continue' ? ' Reload VS Code once so Continue loads the verified ScaleMax profile.' : '';
|
|
1002
1021
|
return `✅ Successfully completed: ${client.name} is configured and locally verified.${activation}`;
|
|
1003
1022
|
}
|
|
1004
1023
|
/** Execute only narrowly allowlisted ScaleMax client actions; never model-generated shell commands. */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ConfigureContext, ConfigureResult, DetectContext, InstallContext } from '../types/client.js';
|
|
2
|
+
export interface ClineExtensionSurface {
|
|
3
|
+
path: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
supported: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ClineSurfaces {
|
|
8
|
+
cliInstalled: boolean;
|
|
9
|
+
cliVersion?: string;
|
|
10
|
+
cliSupported: boolean;
|
|
11
|
+
extensions: ClineExtensionSurface[];
|
|
12
|
+
extensionInstalled: boolean;
|
|
13
|
+
extensionSupported: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function clineExtensionStatePaths(ctx: Pick<DetectContext, 'home' | 'env'>): {
|
|
16
|
+
globalStateFile: string;
|
|
17
|
+
secretsFile: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function inspectClineSurfaces(ctx: DetectContext): Promise<ClineSurfaces>;
|
|
20
|
+
export declare function clineProcessConflicts(output: string, platform: NodeJS.Platform): {
|
|
21
|
+
cli: boolean;
|
|
22
|
+
extension: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function install(ctx: InstallContext): Promise<ConfigureResult>;
|
|
25
|
+
export declare function configure(ctx: ConfigureContext): Promise<ConfigureResult>;
|
|
26
|
+
export declare function verify(ctx: ConfigureContext): Promise<ConfigureResult>;
|
|
27
|
+
export declare function isManagedCliStore(value: unknown): boolean;
|
|
28
|
+
export declare function isManagedExtensionState(globalState: unknown, secrets: unknown): boolean;
|
|
29
|
+
export declare function remove(ctx: ConfigureContext): Promise<ConfigureResult>;
|