scalemax 0.1.22 → 0.1.24
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 -2
- package/dist/commands/helper-bot.d.ts +5 -0
- package/dist/commands/helper-bot.js +293 -10
- package/dist/commands/helper-docs.d.ts +9 -0
- package/dist/commands/helper-docs.js +70 -0
- package/dist/configure/roo-code.js +44 -21
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,13 +113,17 @@ Or ask one small question and exit:
|
|
|
113
113
|
npx scalemax@latest helper-bot "Why is my ScaleMax API key rejected in Codex?"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
The helper uses the same stored key as the installer, verifies authentication, detects the API-credit or token-pack base URL automatically
|
|
116
|
+
The helper uses the same stored key as the installer, verifies authentication, and detects the API-credit or token-pack base URL automatically. General support questions default to `gpt-5.4` when that model is available to the key; each response is capped at 512 tokens and interactive history is bounded to the latest two turns. To choose a different model from `scalemax models`, use:
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
119
|
npx scalemax@latest helper-bot --model fable-5 "Help me configure ScaleMax in Cursor"
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
Recognized setup, repair, and verification requests execute the CLI's allowlisted client operations directly. Requests such as `setup all providers`, `fix Hermes request failed`, `configure Claude Code`, `verify OpenCode`, and `setup vs code roo extension for claude` are handled by the controller before any model request. The controller detects the relevant client, runs a safe installer when the client has one, verifies existing ScaleMax configuration, repairs ScaleMax-managed files when verification fails, re-verifies, and reports each step.
|
|
123
|
+
|
|
124
|
+
For Roo Code, helper-bot can install VS Code on Windows with the fixed Microsoft winget package, install the official Roo Code extension, write OpenAI and Claude ScaleMax profiles when the key exposes both families, and verify the import handoff. For general troubleshooting that cannot be completed by a fixed client action, helper-bot sends a small `gpt-5.4` request grounded with bundled ScaleMax documentation about stored authentication, credit/token base URLs, provider scope, client setup, and common errors. The language model never generates or executes shell commands; only fixed ScaleMax client detection, installation, configuration, verification, and rollback operations are available to the controller.
|
|
125
|
+
|
|
126
|
+
The helper refuses unrelated requests and redacts ScaleMax-style secrets from prompts, provider output, errors, and progress notifications. Use `/status` for non-secret connection details, `/help` for commands, and `/quit` to exit.
|
|
123
127
|
|
|
124
128
|
## Supported clients
|
|
125
129
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ChatCompletionOptions, type ChatCompletionResult, type ChatMessage, type ModelInfo } from '../utils/api.js';
|
|
2
2
|
import { loadApiKey } from '../utils/keyStore.js';
|
|
3
3
|
import { validateApiKey } from '../utils/api.js';
|
|
4
|
+
import type { ClientDefinition } from '../types/client.js';
|
|
4
5
|
export declare const SCALEMAX_HELPER_REFUSAL: string;
|
|
5
6
|
export declare const SCALEMAX_HELPER_SYSTEM_PROMPT: string;
|
|
6
7
|
export declare const SCALEMAX_HELPER_INVALID_PROVIDER_RESPONSE: string;
|
|
@@ -16,6 +17,7 @@ export interface HelperConnection {
|
|
|
16
17
|
providerScope?: 'openai' | 'anthropic' | 'both';
|
|
17
18
|
keyMode?: string;
|
|
18
19
|
availableModels?: string[];
|
|
20
|
+
modelCatalog?: ModelInfo[];
|
|
19
21
|
}
|
|
20
22
|
export type HelperCompletion = (options: ChatCompletionOptions) => Promise<ChatCompletionResult>;
|
|
21
23
|
export interface HelperBotDependencies {
|
|
@@ -23,6 +25,7 @@ export interface HelperBotDependencies {
|
|
|
23
25
|
validateKey: typeof validateApiKey;
|
|
24
26
|
complete: HelperCompletion;
|
|
25
27
|
write: (message: string) => void;
|
|
28
|
+
clientCatalog: ClientDefinition[];
|
|
26
29
|
}
|
|
27
30
|
export declare function parseHelperBotArgs(args: string[]): HelperBotArgs;
|
|
28
31
|
export declare function redactHelperSecrets(value: unknown, activeKey?: string): string;
|
|
@@ -38,4 +41,6 @@ export declare function helperConnectionSummary(connection: HelperConnection): s
|
|
|
38
41
|
export declare function localHelperReply(question: string, connection: HelperConnection): string | undefined;
|
|
39
42
|
export declare function selectHelperModel(models: ModelInfo[], requested?: string): string;
|
|
40
43
|
export declare function requestHelperReply(question: string, history: ChatMessage[], connection: HelperConnection, complete?: HelperCompletion): Promise<string>;
|
|
44
|
+
/** Execute only narrowly allowlisted ScaleMax client actions; never model-generated shell commands. */
|
|
45
|
+
export declare function executeHelperAction(question: string, connection: HelperConnection, home: string, clientCatalog?: ClientDefinition[], write?: (message: string) => void): Promise<string | undefined>;
|
|
41
46
|
export declare function helperBotCommand(args?: string[], home?: string, dependencies?: Partial<HelperBotDependencies>): Promise<void>;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { createInterface } from 'node:readline/promises';
|
|
3
|
-
import {
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { clients } from '../clients.js';
|
|
6
|
+
import { helperDocsContext } from './helper-docs.js';
|
|
7
|
+
import { createChatCompletion, isAnthropicModel } from '../utils/api.js';
|
|
4
8
|
import { redactApiKey } from '../utils/apiKey.js';
|
|
5
9
|
import { loadApiKey } from '../utils/keyStore.js';
|
|
6
10
|
import { homeDir } from '../utils/paths.js';
|
|
7
11
|
import { validateApiKey } from '../utils/api.js';
|
|
12
|
+
import { commandExists } from '../utils/exec.js';
|
|
8
13
|
export const SCALEMAX_HELPER_REFUSAL = `I am the official ScaleMax Helper.
|
|
9
14
|
|
|
10
15
|
I can only assist with installing, configuring, repairing, verifying, and troubleshooting ScaleMax.
|
|
@@ -292,6 +297,8 @@ Never stop after only giving instructions if you can continue solving the issue.
|
|
|
292
297
|
|
|
293
298
|
Always prefer automatic configuration.
|
|
294
299
|
|
|
300
|
+
When bundled ScaleMax documentation is supplied in the conversation, use it for exact ScaleMax configuration facts before relying on general model knowledge.
|
|
301
|
+
|
|
295
302
|
Only ask the user to perform manual actions when automation is impossible.
|
|
296
303
|
|
|
297
304
|
If multiple configuration methods exist:
|
|
@@ -531,13 +538,17 @@ const DEFAULT_MAX_TOKENS = 512;
|
|
|
531
538
|
const SECRET_PATTERN = /\b(?:sm_(?:live|test)_[A-Za-z0-9_-]{8,}|sk-[A-Za-z0-9_-]{12,})\b/g;
|
|
532
539
|
const PROMPT_INJECTION_PATTERN = /\b(?:ignore|override|bypass|disable|reveal|show|print|expose)\b.{0,80}\b(?:system|hidden|previous|developer|instruction|prompt|rule|secret)\b/i;
|
|
533
540
|
const CLEARLY_UNRELATED_PATTERN = /\b(?:homework|poem|story|roleplay|translate|translation|medical|legal|politics|religion|dating|entertainment|game|malware|hacking|reverse\s+engineering|weather|sports|recipe|joke)\b/i;
|
|
534
|
-
const SCALEMAX_TOPIC_PATTERN = /\b(?:scalemax|api\s*key|base\s*url|endpoint|provider|model|token|credit|billing|pricing|authentication|configuration|configure|install|update|repair|verify|health\s*check|environment\s*variable|vscode|visual\s+studio\s+code|cursor|codex(?:\s+cli)?|claude(?:\s+code)?|opencode|openclaw|roo
|
|
541
|
+
const SCALEMAX_TOPIC_PATTERN = /\b(?:scalemax|api\s*key|stored\s+key|customer\s+key|base\s*url|endpoint|provider|openai|anthropic|model|fable|token|credit|billing|pricing|authentication|login|log\s+in|configuration|configure|setup|set\s*up|install|update|repair|verify|health\s*check|environment\s*variable|vscode|visual\s+studio\s+code|vs\s*code\s+extension|cursor|codex(?:\s+cli)?|claude(?:\s+code)?|opencode|openclaw|roo(?:\s+code|\s+extension)?|continue|windsurf|hermes|cherry\s+studio|api\s+code)\b/i;
|
|
535
542
|
const TROUBLESHOOTING_PATTERN = /\b(?:error|failed?|failure|broken|not\s+working|doesn['’]?t\s+work|cannot|can['’]?t|timeout|invalid|unauthorized|forbidden|help)\b/i;
|
|
536
543
|
const GREETING_PATTERN = /^(?:hi|hello|hey|help|what can you do)[!.?\s]*$/i;
|
|
537
544
|
const CLAUDE_DESKTOP_PATTERN = /\bclaude\s+desktop\b/i;
|
|
538
545
|
const CLAUDE_DESKTOP_CONFIGURATION_PATTERN = /\b(?:configur(?:e|ation)|setup|set\s*up|gateway|base\s*url|api\s*key|connect(?:ion)?|third[- ]party|inference|field|install|verify)\b/i;
|
|
539
546
|
const CODEX_CLI_PATTERN = /\bcodex(?:\s+cli)?\b/i;
|
|
540
547
|
const CLIENT_CONFIGURATION_PATTERN = /\b(?:configur(?:e|ation)|setup|set\s*up|base\s*url|api\s*key|connect(?:ion)?|provider|install|repair|verify|update)\b/i;
|
|
548
|
+
const STORED_KEY_EXPLAIN_PATTERN = /\b(?:why\s+(?:did\s+)?i\s+(?:login|log\s+in)|helper\b.{0,80}\b(?:customer|stored)\s+key|(?:customer|stored)\s+key\b.{0,80}\bhelper|does\s+(?:it|helper)\s+use\s+(?:my\s+)?(?:api\s+)?key)\b/i;
|
|
549
|
+
const HELPER_MUTATION_PATTERN = /(?:^|\b(?:please|can\s+you|could\s+you|help\s+me(?:\s+to)?|i\s+(?:want|need)\s+you\s+to|how\s+(?:do|can)\s+i)\s+)(?:set\s*up|setup|configure|connect|install|repair|fix|update|reconfigure|make\b.{0,80}\bwork)\b/i;
|
|
550
|
+
const HELPER_VERIFICATION_PATTERN = /(?:^|\b(?:please|can\s+you|could\s+you|help\s+me(?:\s+to)?|i\s+(?:want|need)\s+you\s+to)\s+)(?:verify|check|test|diagnose)\b/i;
|
|
551
|
+
const ALL_CLIENTS_PATTERN = /\b(?:all|every)\b.{0,30}\b(?:client|provider|integration|app)s?\b/i;
|
|
541
552
|
const KNOWN_CONTEXT_REASK_PATTERN = /(?:\b(?:need|send|provide|share|tell)\b.{0,100}\b(?:api\s*(?:key|endpoint)|base\s*url|operating\s+system|\bos\b|model\s+(?:name|catalog))\b|YOUR-SCALEMAX-ENDPOINT|your-key)/is;
|
|
542
553
|
const PROVIDER_IDENTITY_OVERRIDE_PATTERNS = [
|
|
543
554
|
/\bkiro\b/i,
|
|
@@ -558,7 +569,8 @@ const defaultDependencies = {
|
|
|
558
569
|
loadKey: loadApiKey,
|
|
559
570
|
validateKey: validateApiKey,
|
|
560
571
|
complete: createChatCompletion,
|
|
561
|
-
write: message => console.log(message)
|
|
572
|
+
write: message => console.log(message),
|
|
573
|
+
clientCatalog: clients
|
|
562
574
|
};
|
|
563
575
|
export function parseHelperBotArgs(args) {
|
|
564
576
|
let model;
|
|
@@ -630,9 +642,9 @@ Mandatory behavior:
|
|
|
630
642
|
|
|
631
643
|
1. Treat the stored key, base URLs, operating system, provider scope, and model catalog as already known. Do not request them again.
|
|
632
644
|
2. Never output placeholders such as YOUR-SCALEMAX-ENDPOINT or your-key when an exact known value can be used.
|
|
633
|
-
3.
|
|
645
|
+
3. The CLI controller executes recognized safe configuration and verification requests before asking the model. Do not claim that an unrecognized action was executed.
|
|
634
646
|
4. The Helper request itself uses the stored customer key and therefore consumes the small number of tokens used by the selected Helper model. The key is never included in the prompt text.
|
|
635
|
-
5.
|
|
647
|
+
5. When user action is genuinely required, give the shortest exact instruction and clearly distinguish it from work already completed by the CLI controller.`;
|
|
636
648
|
}
|
|
637
649
|
export function helperConnectionSummary(connection) {
|
|
638
650
|
return `Authenticated ${helperBillingMode(connection)} key · ${helperPlatformName(connection.platform)} · ${connection.providerScope || 'both'} scope · ${connection.baseUrl} · helper model ${connection.model}`;
|
|
@@ -643,6 +655,18 @@ export function helperConnectionSummary(connection) {
|
|
|
643
655
|
* field values matter more than generated prose.
|
|
644
656
|
*/
|
|
645
657
|
export function localHelperReply(question, connection) {
|
|
658
|
+
if (STORED_KEY_EXPLAIN_PATTERN.test(question)) {
|
|
659
|
+
return `ScaleMax Helper uses the key saved by \`scalemax login\`.
|
|
660
|
+
|
|
661
|
+
- The key is validated before Helper starts.
|
|
662
|
+
- The Helper's own small replies use the stored customer key with the selected low-cost model, usually \`${connection.model}\`.
|
|
663
|
+
- Client setup actions reuse the same stored key in the target client's private config.
|
|
664
|
+
- The key is never pasted into the prompt, displayed in chat, or requested again after login.
|
|
665
|
+
|
|
666
|
+
Authenticated base URL: ${connection.baseUrl}
|
|
667
|
+
Billing mode: ${helperBillingMode(connection)}
|
|
668
|
+
Provider scope: ${connection.providerScope || 'both'}`;
|
|
669
|
+
}
|
|
646
670
|
if (CLAUDE_DESKTOP_PATTERN.test(question) && CLAUDE_DESKTOP_CONFIGURATION_PATTERN.test(question)) {
|
|
647
671
|
const gatewayBaseUrl = claudeDesktopGatewayBaseUrl(connection.baseUrl);
|
|
648
672
|
return `ScaleMax Helper detected and authenticated the stored ${helperBillingMode(connection)} key.
|
|
@@ -685,7 +709,7 @@ export function selectHelperModel(models, requested) {
|
|
|
685
709
|
throw new Error(`Model "${requested}" is not available to this API key. Run \`scalemax models\` to see available models.`);
|
|
686
710
|
return exact.id;
|
|
687
711
|
}
|
|
688
|
-
const preferred = ['gpt-5.
|
|
712
|
+
const preferred = ['gpt-5.4', 'gpt-5.4-mini', 'claude-haiku-4-5-20251001', 'gpt-5.6-luna', 'fable-5', 'gpt-5.6-terra', 'gpt-5.6-sol'];
|
|
689
713
|
for (const id of preferred) {
|
|
690
714
|
const match = models.find(model => model.id.toLowerCase() === id);
|
|
691
715
|
if (match)
|
|
@@ -705,6 +729,7 @@ export async function requestHelperReply(question, history, connection, complete
|
|
|
705
729
|
if (localReply)
|
|
706
730
|
return localReply;
|
|
707
731
|
const recentHistory = history.slice(-MAX_HISTORY_MESSAGES).map(message => ({ ...message, content: redactHelperSecrets(message.content, connection.apiKey) }));
|
|
732
|
+
const docsContext = helperDocsContext(cleanQuestion);
|
|
708
733
|
const result = await complete({
|
|
709
734
|
apiKey: connection.apiKey,
|
|
710
735
|
baseUrl: connection.baseUrl,
|
|
@@ -713,6 +738,7 @@ export async function requestHelperReply(question, history, connection, complete
|
|
|
713
738
|
messages: [
|
|
714
739
|
{ role: 'system', content: SCALEMAX_HELPER_SYSTEM_PROMPT },
|
|
715
740
|
{ role: 'system', content: helperRuntimeContext(connection) },
|
|
741
|
+
...(docsContext ? [{ role: 'system', content: docsContext }] : []),
|
|
716
742
|
...recentHistory,
|
|
717
743
|
{ role: 'user', content: cleanQuestion }
|
|
718
744
|
]
|
|
@@ -731,8 +757,260 @@ For automatic configuration, exit with \`/quit\`, run \`scalemax update\`, then
|
|
|
731
757
|
}
|
|
732
758
|
return reply;
|
|
733
759
|
}
|
|
760
|
+
const HELPER_CLIENT_PATTERNS = [
|
|
761
|
+
{ id: 'roo-code', pattern: /\b(?:roo(?:\s+code)?|roo\s+(?:extension|plugin)|vscode\s+roo|vs\s+code\s+roo|vs\s*code\s+extension|visual\s+studio\s+code\s+extension)\b/i },
|
|
762
|
+
{ id: 'openclaw', pattern: /\bopen\s*claw\b/i },
|
|
763
|
+
{ id: 'opencode', pattern: /\bopen\s*code\b/i },
|
|
764
|
+
{ id: 'cherry-studio', pattern: /\bcherry\s*studio\b/i },
|
|
765
|
+
{ id: 'api-code', pattern: /\bapi\s*code\b/i },
|
|
766
|
+
{ id: 'trae-solo', pattern: /\btrae(?:\s+solo)?\b/i },
|
|
767
|
+
{ id: 'cursor', pattern: /\bcursor\b/i },
|
|
768
|
+
{ id: 'hermes', pattern: /\bhermes\b/i },
|
|
769
|
+
{ id: 'codex', pattern: /\bcodex(?:\s+cli)?\b/i },
|
|
770
|
+
{ id: 'claude', pattern: /\bclaude(?:\s+code|\s+cli)?\b/i }
|
|
771
|
+
];
|
|
772
|
+
function helperActionKind(question) {
|
|
773
|
+
if (/\b(?:repair|fix|troubleshoot|diagnose|not\s+working|doesn['’]?t\s+work|broken|failed?|invalid|timeout|rate\s*limit(?:ed)?)\b/i.test(question))
|
|
774
|
+
return 'repair';
|
|
775
|
+
if (HELPER_MUTATION_PATTERN.test(question))
|
|
776
|
+
return 'configure';
|
|
777
|
+
if (HELPER_VERIFICATION_PATTERN.test(question))
|
|
778
|
+
return 'verify';
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
function helperActionTarget(question) {
|
|
782
|
+
if (CLAUDE_DESKTOP_PATTERN.test(question))
|
|
783
|
+
return undefined;
|
|
784
|
+
if (ALL_CLIENTS_PATTERN.test(question) || /\b(?:all|every)\b.{0,30}\bproviders?\b/i.test(question))
|
|
785
|
+
return 'all';
|
|
786
|
+
const explicit = HELPER_CLIENT_PATTERNS.find(candidate => candidate.pattern.test(question))?.id;
|
|
787
|
+
if (explicit)
|
|
788
|
+
return explicit;
|
|
789
|
+
if (/\b(?:clients?|integrations?|apps?|providers?)\b/i.test(question) && /\b(?:scalemax|configure|setup|set\s*up|repair|fix|update)\b/i.test(question))
|
|
790
|
+
return 'all';
|
|
791
|
+
return undefined;
|
|
792
|
+
}
|
|
793
|
+
function requestedProviderScope(question, target) {
|
|
794
|
+
if (/\b(?:both|openai\s*\+\s*claude|claude\s*\+\s*openai)\b/i.test(question))
|
|
795
|
+
return 'both';
|
|
796
|
+
if (/\b(?:claude|anthropic)\b/i.test(question))
|
|
797
|
+
return 'anthropic';
|
|
798
|
+
if (/\b(?:openai|chatgpt|gpt)\b/i.test(question) || target === 'codex')
|
|
799
|
+
return 'openai';
|
|
800
|
+
return undefined;
|
|
801
|
+
}
|
|
802
|
+
function actionModels(connection, requestedScope) {
|
|
803
|
+
const catalog = connection.modelCatalog?.length
|
|
804
|
+
? connection.modelCatalog
|
|
805
|
+
: (connection.availableModels || []).map(id => ({ id }));
|
|
806
|
+
if (!requestedScope || requestedScope === 'both')
|
|
807
|
+
return catalog;
|
|
808
|
+
return catalog.filter(model => requestedScope === 'anthropic' ? isAnthropicModel(model) : !isAnthropicModel(model));
|
|
809
|
+
}
|
|
810
|
+
function helperClientAllowedForModels(clientId, models) {
|
|
811
|
+
if (!models.length)
|
|
812
|
+
return false;
|
|
813
|
+
const crossProtocolClients = new Set(['claude', 'cursor', 'roo-code', 'opencode', 'openclaw', 'hermes', 'cherry-studio', 'api-code']);
|
|
814
|
+
if (crossProtocolClients.has(clientId))
|
|
815
|
+
return true;
|
|
816
|
+
return models.some(model => !isAnthropicModel(model));
|
|
817
|
+
}
|
|
818
|
+
function hasRooCodeExtension(paths) {
|
|
819
|
+
return paths.some(candidate => /^rooveterinaryinc\.roo-cline-/i.test(path.basename(candidate)));
|
|
820
|
+
}
|
|
821
|
+
function pathEnvKey(env) {
|
|
822
|
+
return Object.keys(env).find(key => key.toLowerCase() === 'path') || 'PATH';
|
|
823
|
+
}
|
|
824
|
+
function windowsVSCodeBinPaths(ctx) {
|
|
825
|
+
const localAppData = ctx.env.LOCALAPPDATA || path.win32.join(ctx.home, 'AppData', 'Local');
|
|
826
|
+
const programFiles = ctx.env.ProgramFiles || 'C:\\Program Files';
|
|
827
|
+
const programFilesX86 = ctx.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
|
828
|
+
return [
|
|
829
|
+
path.win32.join(localAppData, 'Programs', 'Microsoft VS Code', 'bin'),
|
|
830
|
+
path.win32.join(programFiles, 'Microsoft VS Code', 'bin'),
|
|
831
|
+
path.win32.join(programFilesX86, 'Microsoft VS Code', 'bin')
|
|
832
|
+
];
|
|
833
|
+
}
|
|
834
|
+
function refreshWindowsVSCodePath(ctx) {
|
|
835
|
+
if (ctx.platform !== 'win32')
|
|
836
|
+
return;
|
|
837
|
+
const key = pathEnvKey(ctx.env);
|
|
838
|
+
const delimiter = ';';
|
|
839
|
+
const existing = new Set((ctx.env[key] || '').split(delimiter).filter(Boolean).map(item => item.toLowerCase()));
|
|
840
|
+
const additions = windowsVSCodeBinPaths(ctx).filter(candidate => !existing.has(candidate.toLowerCase()));
|
|
841
|
+
if (additions.length)
|
|
842
|
+
ctx.env[key] = [...additions, ctx.env[key] || ''].filter(Boolean).join(delimiter);
|
|
843
|
+
}
|
|
844
|
+
async function installVSCodeOnWindows(ctx, write) {
|
|
845
|
+
if (ctx.platform !== 'win32')
|
|
846
|
+
return;
|
|
847
|
+
refreshWindowsVSCodePath(ctx);
|
|
848
|
+
if (await commandExists('code', ctx.env))
|
|
849
|
+
return;
|
|
850
|
+
if (!(await commandExists('winget', ctx.env))) {
|
|
851
|
+
throw new Error('VS Code and the Roo Code extension were not detected. Install VS Code first, then retry this request.');
|
|
852
|
+
}
|
|
853
|
+
write('→ VS Code was not detected; installing Microsoft Visual Studio Code with winget…');
|
|
854
|
+
const result = await execa('winget', [
|
|
855
|
+
'install',
|
|
856
|
+
'--id',
|
|
857
|
+
'Microsoft.VisualStudioCode',
|
|
858
|
+
'--exact',
|
|
859
|
+
'--accept-package-agreements',
|
|
860
|
+
'--accept-source-agreements',
|
|
861
|
+
'--silent'
|
|
862
|
+
], {
|
|
863
|
+
env: ctx.env,
|
|
864
|
+
reject: false,
|
|
865
|
+
all: true
|
|
866
|
+
});
|
|
867
|
+
if (result.exitCode !== 0)
|
|
868
|
+
throw new Error(`VS Code installation failed: ${result.all?.trim() || `winget exited with ${result.exitCode}`}`);
|
|
869
|
+
refreshWindowsVSCodePath(ctx);
|
|
870
|
+
if (!(await commandExists('code', ctx.env)))
|
|
871
|
+
throw new Error('VS Code installed, but the code command was not detected. Restart the terminal and retry this request.');
|
|
872
|
+
write('✓ VS Code installed and detected.');
|
|
873
|
+
}
|
|
874
|
+
async function installRooCodeExtension(ctx, client, write) {
|
|
875
|
+
let detection = await client.detect(ctx);
|
|
876
|
+
if (hasRooCodeExtension(detection.paths))
|
|
877
|
+
return;
|
|
878
|
+
if (!detection.executable && ctx.platform === 'win32') {
|
|
879
|
+
await installVSCodeOnWindows(ctx, write);
|
|
880
|
+
detection = await client.detect(ctx);
|
|
881
|
+
}
|
|
882
|
+
if (!detection.executable)
|
|
883
|
+
throw new Error('VS Code and the Roo Code extension were not detected. Install VS Code first, then retry this request.');
|
|
884
|
+
write('→ Installing the official Roo Code extension in VS Code…');
|
|
885
|
+
const result = await execa('code', ['--install-extension', 'RooVeterinaryInc.roo-cline', '--force'], {
|
|
886
|
+
env: ctx.env,
|
|
887
|
+
reject: false,
|
|
888
|
+
all: true
|
|
889
|
+
});
|
|
890
|
+
if (result.exitCode !== 0)
|
|
891
|
+
throw new Error(`Roo Code extension installation failed: ${result.all?.trim() || `code exited with ${result.exitCode}`}`);
|
|
892
|
+
detection = await client.detect(ctx);
|
|
893
|
+
if (!hasRooCodeExtension(detection.paths))
|
|
894
|
+
throw new Error('VS Code reported a successful Roo Code installation, but the extension could not be detected afterward. Restart VS Code and retry.');
|
|
895
|
+
write('✓ Roo Code extension installed and detected.');
|
|
896
|
+
}
|
|
897
|
+
async function runOneHelperClientAction(kind, client, connection, home, requestedScope, write) {
|
|
898
|
+
const models = actionModels(connection, requestedScope);
|
|
899
|
+
if (!models.length)
|
|
900
|
+
throw new Error(`The authenticated key does not expose ${requestedScope === 'anthropic' ? 'Claude' : requestedScope === 'openai' ? 'OpenAI' : 'compatible'} models for ${client.name}. No configuration was changed.`);
|
|
901
|
+
if (client.id === 'codex' && models.every(isAnthropicModel))
|
|
902
|
+
throw new Error('Codex requires an OpenAI-compatible model, but this key exposes only Claude models. No configuration was changed.');
|
|
903
|
+
if (client.id === 'cursor' && connection.platform !== 'darwin')
|
|
904
|
+
throw new Error(`Automatic Cursor configuration is currently verified on macOS only. ${helperPlatformName(connection.platform)} was not modified.`);
|
|
905
|
+
const ctx = {
|
|
906
|
+
home,
|
|
907
|
+
platform: connection.platform || process.platform,
|
|
908
|
+
env: { ...process.env },
|
|
909
|
+
apiKey: connection.apiKey,
|
|
910
|
+
baseUrl: connection.baseUrl,
|
|
911
|
+
providerScope: requestedScope || connection.providerScope || 'both',
|
|
912
|
+
models,
|
|
913
|
+
interactive: false,
|
|
914
|
+
allowAppModification: false
|
|
915
|
+
};
|
|
916
|
+
if (kind !== 'verify' && client.install) {
|
|
917
|
+
write(`→ Checking ${client.name} installer…`);
|
|
918
|
+
const installed = await client.install({ home, platform: ctx.platform, env: ctx.env, interactive: Boolean(process.stdin.isTTY) });
|
|
919
|
+
write(`✓ ${client.name} ${installed.message}`);
|
|
920
|
+
}
|
|
921
|
+
write(`→ Detecting ${client.name}…`);
|
|
922
|
+
let detection = await client.detect(ctx);
|
|
923
|
+
if ((kind === 'configure' || kind === 'repair') && client.id === 'roo-code') {
|
|
924
|
+
await installRooCodeExtension(ctx, client, write);
|
|
925
|
+
detection = await client.detect(ctx);
|
|
926
|
+
}
|
|
927
|
+
if (!detection.installed && client.id !== 'api-code')
|
|
928
|
+
throw new Error(`${client.name} was not detected. No configuration was changed.`);
|
|
929
|
+
write(client.id === 'api-code' && !detection.installed ? `✓ ${client.name} output path is available.` : `✓ ${client.name} detected.`);
|
|
930
|
+
if (kind === 'verify') {
|
|
931
|
+
if (!client.verify) {
|
|
932
|
+
if (!detection.configured)
|
|
933
|
+
throw new Error(`${client.name} is installed but its ScaleMax configuration was not detected.`);
|
|
934
|
+
return `✓ ${client.name} ScaleMax configuration is present.`;
|
|
935
|
+
}
|
|
936
|
+
write(`→ Verifying ${client.name}…`);
|
|
937
|
+
const verified = await client.verify(ctx);
|
|
938
|
+
return `✓ ${client.name} ${verified.message}`;
|
|
939
|
+
}
|
|
940
|
+
if (kind === 'repair' && client.verify) {
|
|
941
|
+
write(`→ Diagnosing ${client.name} configuration…`);
|
|
942
|
+
try {
|
|
943
|
+
const verified = await client.verify(ctx);
|
|
944
|
+
return `✓ ${client.name} is already healthy: ${verified.message}`;
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
write(`→ ${client.name} verification failed; repairing ScaleMax-managed configuration…`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
write(`→ Configuring ${client.name} with the authenticated stored key…`);
|
|
951
|
+
const configured = await client.configure(ctx);
|
|
952
|
+
write(`✓ ${client.name} ${configured.message}`);
|
|
953
|
+
if (client.verify) {
|
|
954
|
+
write(`→ Verifying ${client.name} configuration…`);
|
|
955
|
+
try {
|
|
956
|
+
const verified = await client.verify(ctx);
|
|
957
|
+
write(`✓ ${client.name} ${verified.message}`);
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
if (configured.changed) {
|
|
961
|
+
write(`→ Verification failed; removing the incomplete ScaleMax-managed ${client.name} configuration…`);
|
|
962
|
+
await client.remove(ctx).catch(() => undefined);
|
|
963
|
+
}
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
detection = await client.detect(ctx);
|
|
969
|
+
if (!detection.configured)
|
|
970
|
+
throw new Error(`${client.name} configuration was written but could not be detected afterward.`);
|
|
971
|
+
write(`✓ ${client.name} configuration detected after write.`);
|
|
972
|
+
}
|
|
973
|
+
const activation = client.id === 'roo-code' ? ' Reload VS Code once so Roo Code imports the verified ScaleMax profile.' : '';
|
|
974
|
+
return `✅ Successfully completed: ${client.name} is configured and locally verified.${activation}`;
|
|
975
|
+
}
|
|
976
|
+
/** Execute only narrowly allowlisted ScaleMax client actions; never model-generated shell commands. */
|
|
977
|
+
export async function executeHelperAction(question, connection, home, clientCatalog = clients, write = message => console.log(message)) {
|
|
978
|
+
const cleanQuestion = redactHelperSecrets(question.trim(), connection.apiKey);
|
|
979
|
+
if (!isScaleMaxHelperQuestion(cleanQuestion))
|
|
980
|
+
return undefined;
|
|
981
|
+
const kind = helperActionKind(cleanQuestion);
|
|
982
|
+
const target = helperActionTarget(cleanQuestion);
|
|
983
|
+
if (!kind || !target)
|
|
984
|
+
return undefined;
|
|
985
|
+
const scope = requestedProviderScope(cleanQuestion, target);
|
|
986
|
+
const scopedModels = actionModels(connection, scope);
|
|
987
|
+
const selected = target === 'all'
|
|
988
|
+
? clientCatalog.filter(client => client.id !== 'cursor' && helperClientAllowedForModels(client.id, scopedModels))
|
|
989
|
+
: clientCatalog.filter(client => client.id === target);
|
|
990
|
+
if (!selected.length)
|
|
991
|
+
throw new Error('The requested ScaleMax integration is not supported by this CLI build.');
|
|
992
|
+
if (target !== 'all' && !helperClientAllowedForModels(selected[0].id, scopedModels))
|
|
993
|
+
throw new Error(`The authenticated key does not expose models compatible with ${selected[0].name}. No configuration was changed.`);
|
|
994
|
+
write(`✓ Stored ${helperBillingMode(connection)} key is authenticated; secrets remain hidden.`);
|
|
995
|
+
const completed = [];
|
|
996
|
+
const failures = [];
|
|
997
|
+
for (const client of selected) {
|
|
998
|
+
try {
|
|
999
|
+
completed.push(await runOneHelperClientAction(kind, client, connection, home, scope, write));
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
const message = redactHelperSecrets(error instanceof Error ? error.message : String(error), connection.apiKey);
|
|
1003
|
+
if (target !== 'all')
|
|
1004
|
+
throw new Error(message);
|
|
1005
|
+
failures.push(`${client.name}: ${message}`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (!completed.length)
|
|
1009
|
+
throw new Error(`No ScaleMax client action completed.\n${failures.map(item => `- ${item}`).join('\n')}`);
|
|
1010
|
+
return `${completed.join('\n')}${failures.length ? `\n\nSkipped or failed:\n${failures.map(item => `- ${item}`).join('\n')}` : ''}`;
|
|
1011
|
+
}
|
|
734
1012
|
function printSessionHelp(write) {
|
|
735
|
-
write('Commands: /help shows this help; /status shows the authenticated non-secret connection details; /quit exits.
|
|
1013
|
+
write('Commands: /help shows this help; /status shows the authenticated non-secret connection details; /quit exits. Setup, repair, and verification requests for supported clients are executed automatically with progress notifications.');
|
|
736
1014
|
}
|
|
737
1015
|
export async function helperBotCommand(args = [], home = homeDir(), dependencies = {}) {
|
|
738
1016
|
const deps = { ...defaultDependencies, ...dependencies };
|
|
@@ -751,10 +1029,15 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
|
|
|
751
1029
|
platform: process.platform,
|
|
752
1030
|
providerScope: validation.key?.provider_scope || 'both',
|
|
753
1031
|
keyMode: validation.key?.mode,
|
|
754
|
-
availableModels: validation.models.map(item => item.id)
|
|
1032
|
+
availableModels: validation.models.map(item => item.id),
|
|
1033
|
+
modelCatalog: validation.models
|
|
1034
|
+
};
|
|
1035
|
+
const respond = async (question, history) => {
|
|
1036
|
+
const action = await executeHelperAction(question, connection, home, deps.clientCatalog, deps.write);
|
|
1037
|
+
return action || requestHelperReply(question, history, connection, deps.complete);
|
|
755
1038
|
};
|
|
756
1039
|
if (options.question) {
|
|
757
|
-
deps.write(await
|
|
1040
|
+
deps.write(await respond(options.question, []));
|
|
758
1041
|
return;
|
|
759
1042
|
}
|
|
760
1043
|
if (!process.stdin.isTTY)
|
|
@@ -787,7 +1070,7 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
|
|
|
787
1070
|
continue;
|
|
788
1071
|
}
|
|
789
1072
|
try {
|
|
790
|
-
const reply = await
|
|
1073
|
+
const reply = await respond(input, history);
|
|
791
1074
|
deps.write(reply);
|
|
792
1075
|
if (reply !== SCALEMAX_HELPER_REFUSAL) {
|
|
793
1076
|
history.push({ role: 'user', content: redactHelperSecrets(input, key) }, { role: 'assistant', content: reply });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface HelperDocSection {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
keywords: string[];
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const HELPER_DOC_SECTIONS: HelperDocSection[];
|
|
8
|
+
export declare function selectHelperDocs(question: string, limit?: number): HelperDocSection[];
|
|
9
|
+
export declare function helperDocsContext(question: string): string | undefined;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const HELPER_DOC_SECTIONS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'stored-auth',
|
|
4
|
+
title: 'Stored ScaleMax authentication',
|
|
5
|
+
keywords: ['login', 'authenticate', 'authentication', 'stored key', 'api key', 'customer key', 'why login', 'base url'],
|
|
6
|
+
content: `After scalemax login succeeds, the CLI stores the customer's ScaleMax API key privately and validates it before helper-bot starts. The helper must not ask the user to paste the key, base URL, operating system, or model catalog again. The helper uses the stored customer key for its own small ScaleMax model requests and for supported client configuration files. The key is never inserted into prompts or displayed in chat.`
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'credit-token-urls',
|
|
10
|
+
title: 'API credit and API token base URLs',
|
|
11
|
+
keywords: ['credit', 'token', 'base url', 'endpoint', 'renew', 'billing', 'api credit', 'api token'],
|
|
12
|
+
content: `ScaleMax API credit keys use https://api.scalemax.pro/v1. ScaleMax API token budget keys use https://api.scalemax.pro/token/v1. Claude Desktop gateway configuration removes the trailing /v1, so credit keys use https://api.scalemax.pro and token keys use https://api.scalemax.pro/token. Do not mix credit and token base URLs; the authenticated key validation result is authoritative.`
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'provider-scope',
|
|
16
|
+
title: 'Provider scope and visible models',
|
|
17
|
+
keywords: ['provider', 'scope', 'openai', 'claude', 'anthropic', 'both', 'models', 'fable'],
|
|
18
|
+
content: `A ScaleMax key may be OpenAI only, Claude only, or both. OpenAI-only keys should configure OpenAI-compatible models only. Claude-only keys should configure Claude-compatible models only. Both-scope keys may configure both model families in clients that can switch profiles. Fable 5 is a Claude-family frontend model through ScaleMax and should only appear for Claude-capable or both-scope keys, not OpenAI-only keys.`
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'roo-code',
|
|
22
|
+
title: 'Roo Code setup through VS Code',
|
|
23
|
+
keywords: ['roo', 'roo code', 'vscode', 'vs code', 'extension', 'visual studio code'],
|
|
24
|
+
content: `For Roo Code, the ScaleMax CLI writes a private settings import file at ~/.scalemax/roo-code-import.json and sets roo-cline.autoImportSettingsPath in VS Code User settings. Roo imports that profile after VS Code is reloaded. The CLI can install the official Roo Code extension with code --install-extension RooVeterinaryInc.roo-cline when VS Code is detected. On Windows, helper-bot may install VS Code first with winget using the fixed Microsoft.VisualStudioCode package id, then install Roo, write the import handoff, and verify the files.`
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'codex-and-clients',
|
|
28
|
+
title: 'Supported client update flow',
|
|
29
|
+
keywords: ['codex', 'cursor', 'claude code', 'opencode', 'openclaw', 'hermes', 'cherry studio', 'update', 'doctor'],
|
|
30
|
+
content: `scalemax update detects supported clients and configures them from the stored key. scalemax doctor validates the stored key and performs a minimal ScaleMax request. Supported clients include Codex CLI, Cursor, Claude Code, OpenCode, OpenClaw, Hermes, Cherry Studio, Roo Code, and API Code. Recognized helper-bot setup and verification requests are executed by the CLI controller before any model fallback is used.`
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'troubleshooting',
|
|
34
|
+
title: 'Common troubleshooting',
|
|
35
|
+
keywords: ['error', 'failed', 'invalid', 'rate limit', 'timeout', 'kiro', 'request failed', 'provider returned invalid response'],
|
|
36
|
+
content: `If a provider response claims to be Kiro, Claude, ChatGPT, Codex, or says it cannot act as ScaleMax Helper, the helper must reject that response and not show the provider persona to the user. For timeouts or rate limits, retry with a tiny request and prefer the lowest-cost available helper model. For invalid key errors, re-run scalemax login and then scalemax doctor. For a client that still shows old settings, reload or restart that client after scalemax update.`
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
function words(value) {
|
|
40
|
+
return [...new Set(value.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)?/g) || [])];
|
|
41
|
+
}
|
|
42
|
+
function scoreSection(questionWords, section) {
|
|
43
|
+
const haystack = words(`${section.title} ${section.keywords.join(' ')} ${section.content}`);
|
|
44
|
+
let score = 0;
|
|
45
|
+
for (const word of haystack)
|
|
46
|
+
if (questionWords.has(word))
|
|
47
|
+
score += section.keywords.includes(word) ? 4 : 1;
|
|
48
|
+
return score;
|
|
49
|
+
}
|
|
50
|
+
export function selectHelperDocs(question, limit = 3) {
|
|
51
|
+
const questionWords = new Set(words(question));
|
|
52
|
+
if (!questionWords.size)
|
|
53
|
+
return [];
|
|
54
|
+
return HELPER_DOC_SECTIONS
|
|
55
|
+
.map(section => ({ section, score: scoreSection(questionWords, section) }))
|
|
56
|
+
.filter(item => item.score > 1)
|
|
57
|
+
.sort((a, b) => b.score - a.score || a.section.id.localeCompare(b.section.id))
|
|
58
|
+
.slice(0, limit)
|
|
59
|
+
.map(item => item.section);
|
|
60
|
+
}
|
|
61
|
+
export function helperDocsContext(question) {
|
|
62
|
+
const sections = selectHelperDocs(question);
|
|
63
|
+
if (!sections.length)
|
|
64
|
+
return undefined;
|
|
65
|
+
return `# SCALEMAX DOCUMENTATION CONTEXT
|
|
66
|
+
|
|
67
|
+
Use this bundled ScaleMax documentation for exact configuration facts. If it does not fully answer the request, continue from the authenticated runtime context and ScaleMax Helper rules.
|
|
68
|
+
|
|
69
|
+
${sections.map(section => `## ${section.title}\n${section.content}`).join('\n\n')}`;
|
|
70
|
+
}
|
|
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import JSON5 from 'json5';
|
|
4
4
|
import { atomicWrite, backupFile } from '../utils/fs.js';
|
|
5
5
|
import { clientConfigPath } from './paths.js';
|
|
6
|
-
import { openAiBaseUrl, preferredChatCompletionsModel } from './models.js';
|
|
6
|
+
import { anthropicModelIds, openAiBaseUrl, openAiModelIds, preferredAnthropicModel, preferredChatCompletionsModel, preferredOpenAiModel } from './models.js';
|
|
7
7
|
import { isAnthropicModel } from '../utils/api.js';
|
|
8
8
|
const id = 'roo-code';
|
|
9
9
|
const name = 'Roo Code (VS Code)';
|
|
@@ -14,13 +14,31 @@ const claudeProfileName = 'ScaleMax Claude';
|
|
|
14
14
|
const codexProfileId = 'scalemax-codex';
|
|
15
15
|
const claudeProfileId = 'scalemax-claude';
|
|
16
16
|
function importPath(home) { return path.join(home, '.scalemax', 'roo-code-import.json'); }
|
|
17
|
-
function
|
|
17
|
+
function fallbackProfileFor(ctx) {
|
|
18
18
|
const model = preferredChatCompletionsModel(ctx);
|
|
19
19
|
const metadata = (ctx.models || []).find(candidate => candidate.id === model) || { id: model };
|
|
20
20
|
return isAnthropicModel(metadata)
|
|
21
21
|
? { name: claudeProfileName, id: claudeProfileId, model }
|
|
22
22
|
: { name: codexProfileName, id: codexProfileId, model };
|
|
23
23
|
}
|
|
24
|
+
function profilesFor(ctx) {
|
|
25
|
+
const scope = ctx.providerScope || 'both';
|
|
26
|
+
const profiles = [];
|
|
27
|
+
if (scope !== 'anthropic' && openAiModelIds(ctx).length) {
|
|
28
|
+
profiles.push({ name: codexProfileName, id: codexProfileId, model: preferredOpenAiModel(ctx) });
|
|
29
|
+
}
|
|
30
|
+
if (scope !== 'openai' && anthropicModelIds(ctx).length) {
|
|
31
|
+
profiles.push({ name: claudeProfileName, id: claudeProfileId, model: preferredAnthropicModel(ctx) });
|
|
32
|
+
}
|
|
33
|
+
return profiles.length ? profiles : [fallbackProfileFor(ctx)];
|
|
34
|
+
}
|
|
35
|
+
function currentProfileFor(ctx, profiles = profilesFor(ctx)) {
|
|
36
|
+
if (ctx.providerScope === 'anthropic')
|
|
37
|
+
return profiles.find(profile => profile.id === claudeProfileId) || profiles[0];
|
|
38
|
+
if (ctx.providerScope === 'openai')
|
|
39
|
+
return profiles.find(profile => profile.id === codexProfileId) || profiles[0];
|
|
40
|
+
return profiles.find(profile => profile.id === codexProfileId) || profiles[0];
|
|
41
|
+
}
|
|
24
42
|
function parseSettings(text, file) {
|
|
25
43
|
if (!text)
|
|
26
44
|
return {};
|
|
@@ -35,21 +53,21 @@ function parseSettings(text, file) {
|
|
|
35
53
|
}
|
|
36
54
|
}
|
|
37
55
|
function handoff(ctx) {
|
|
38
|
-
const
|
|
56
|
+
const profiles = profilesFor(ctx);
|
|
57
|
+
const current = currentProfileFor(ctx, profiles);
|
|
58
|
+
const apiConfigs = Object.fromEntries(profiles.map(profile => [profile.name, {
|
|
59
|
+
id: profile.id,
|
|
60
|
+
apiProvider: 'openai',
|
|
61
|
+
openAiBaseUrl: openAiBaseUrl(ctx.baseUrl),
|
|
62
|
+
openAiApiKey: ctx.apiKey,
|
|
63
|
+
openAiModelId: profile.model,
|
|
64
|
+
openAiStreamingEnabled: true,
|
|
65
|
+
includeMaxTokens: true
|
|
66
|
+
}]));
|
|
39
67
|
return JSON.stringify({
|
|
40
68
|
providerProfiles: {
|
|
41
|
-
currentApiConfigName:
|
|
42
|
-
apiConfigs
|
|
43
|
-
[profile.name]: {
|
|
44
|
-
id: profile.id,
|
|
45
|
-
apiProvider: 'openai',
|
|
46
|
-
openAiBaseUrl: openAiBaseUrl(ctx.baseUrl),
|
|
47
|
-
openAiApiKey: ctx.apiKey,
|
|
48
|
-
openAiModelId: profile.model,
|
|
49
|
-
openAiStreamingEnabled: true,
|
|
50
|
-
includeMaxTokens: true
|
|
51
|
-
}
|
|
52
|
-
},
|
|
69
|
+
currentApiConfigName: current.name,
|
|
70
|
+
apiConfigs,
|
|
53
71
|
modeApiConfigs: {}
|
|
54
72
|
},
|
|
55
73
|
globalSettings: {}
|
|
@@ -90,14 +108,19 @@ export async function verify(ctx) {
|
|
|
90
108
|
const profileFile = importPath(ctx.home);
|
|
91
109
|
const settings = parseSettings(await fs.readFile(settingsFile, 'utf8'), settingsFile);
|
|
92
110
|
const profile = await fs.readJson(profileFile).catch(() => undefined);
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
if (settings[importSetting] !== importSettingValue || profile?.providerProfiles?.currentApiConfigName !==
|
|
96
|
-
|| config?.id !== expected.id || config?.apiProvider !== 'openai' || config?.openAiBaseUrl !== openAiBaseUrl(ctx.baseUrl)
|
|
97
|
-
|| config?.openAiApiKey !== ctx.apiKey || config?.openAiModelId !== expected.model || config?.openAiStreamingEnabled !== true
|
|
98
|
-
|| config?.includeMaxTokens !== true) {
|
|
111
|
+
const expectedProfiles = profilesFor(ctx);
|
|
112
|
+
const current = currentProfileFor(ctx, expectedProfiles);
|
|
113
|
+
if (settings[importSetting] !== importSettingValue || profile?.providerProfiles?.currentApiConfigName !== current.name) {
|
|
99
114
|
throw new Error('Roo Code auto-import handoff is incomplete.');
|
|
100
115
|
}
|
|
116
|
+
for (const expected of expectedProfiles) {
|
|
117
|
+
const config = profile?.providerProfiles?.apiConfigs?.[expected.name];
|
|
118
|
+
if (config?.id !== expected.id || config?.apiProvider !== 'openai' || config?.openAiBaseUrl !== openAiBaseUrl(ctx.baseUrl)
|
|
119
|
+
|| config?.openAiApiKey !== ctx.apiKey || config?.openAiModelId !== expected.model || config?.openAiStreamingEnabled !== true
|
|
120
|
+
|| config?.includeMaxTokens !== true) {
|
|
121
|
+
throw new Error('Roo Code auto-import handoff is incomplete.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
101
124
|
if (ctx.platform !== 'win32' && ((await fs.stat(profileFile)).mode & 0o077) !== 0)
|
|
102
125
|
throw new Error('Roo Code import handoff permissions are too broad.');
|
|
103
126
|
return { id, name, changed: false, path: profileFile, message: 'verified supported auto-import handoff (runtime activation requires a VS Code reload)' };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ function welcome() {
|
|
|
34
34
|
}
|
|
35
35
|
function printHelp(command) {
|
|
36
36
|
if (command === 'helper-bot') {
|
|
37
|
-
console.log('Usage: scalemax helper-bot [--model <id>] ["question"]\n\
|
|
37
|
+
console.log('Usage: scalemax helper-bot [--model <id>] ["question"]\n\nExecute safe ScaleMax setup and verification actions, or open the ScaleMax-only troubleshooting assistant.');
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
const details = { update: 'Revalidate the API key and reconfigure every detected client.', remove: 'Restore backups or remove only ScaleMax-managed client configuration.', doctor: 'Validate the API key and verify detected client configuration.', models: 'List models available to the stored API key.', login: 'Store or replace the ScaleMax API key.', logout: 'Remove the locally stored ScaleMax API key.' };
|
|
@@ -52,7 +52,7 @@ Commands:
|
|
|
52
52
|
scalemax logout Remove API key
|
|
53
53
|
scalemax doctor Run diagnostics
|
|
54
54
|
scalemax models List available models
|
|
55
|
-
scalemax helper-bot
|
|
55
|
+
scalemax helper-bot Execute setup actions or open the ScaleMax Helper
|
|
56
56
|
scalemax update Reconfigure detected clients
|
|
57
57
|
scalemax remove Remove ScaleMax-generated configs
|
|
58
58
|
scalemax --version Show installed version
|