scalemax 0.1.22 → 0.1.23

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 CHANGED
@@ -113,13 +113,15 @@ 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, and selects a model available to that key. Each response is capped at 512 tokens and interactive history is bounded to the latest two turns. To choose a specific model from `scalemax models`, use:
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
- The helper is restricted to ScaleMax tasks, refuses unrelated requests, redacts ScaleMax-style secrets before requests and output, and never receives local shell or filesystem tools. Use `/help` inside the session for commands and `/quit` to exit.
122
+ Recognized setup, repair, and verification requests execute the CLI's allowlisted client operations directly. For example, `setup vs code roo extension for claude` detects or installs the official Roo Code extension, writes its ScaleMax Claude profile with the stored key, verifies the handoff, and reports each step. The language model never generates or executes shell commands; only fixed ScaleMax client detection, configuration, verification, rollback, and the official Roo Code extension installer are available to the controller.
123
+
124
+ 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
125
 
124
126
  ## Supported clients
125
127
 
@@ -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,6 +1,9 @@
1
1
  import chalk from 'chalk';
2
+ import path from 'node:path';
2
3
  import { createInterface } from 'node:readline/promises';
3
- import { createChatCompletion } from '../utils/api.js';
4
+ import { execa } from 'execa';
5
+ import { clients } from '../clients.js';
6
+ import { createChatCompletion, isAnthropicModel } from '../utils/api.js';
4
7
  import { redactApiKey } from '../utils/apiKey.js';
5
8
  import { loadApiKey } from '../utils/keyStore.js';
6
9
  import { homeDir } from '../utils/paths.js';
@@ -538,6 +541,9 @@ const CLAUDE_DESKTOP_PATTERN = /\bclaude\s+desktop\b/i;
538
541
  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
542
  const CODEX_CLI_PATTERN = /\bcodex(?:\s+cli)?\b/i;
540
543
  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;
544
+ 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)\b/i;
545
+ 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;
546
+ const ALL_CLIENTS_PATTERN = /\b(?:all|every)\b.{0,30}\b(?:client|provider|integration|app)s?\b/i;
541
547
  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
548
  const PROVIDER_IDENTITY_OVERRIDE_PATTERNS = [
543
549
  /\bkiro\b/i,
@@ -558,7 +564,8 @@ const defaultDependencies = {
558
564
  loadKey: loadApiKey,
559
565
  validateKey: validateApiKey,
560
566
  complete: createChatCompletion,
561
- write: message => console.log(message)
567
+ write: message => console.log(message),
568
+ clientCatalog: clients
562
569
  };
563
570
  export function parseHelperBotArgs(args) {
564
571
  let model;
@@ -630,9 +637,9 @@ Mandatory behavior:
630
637
 
631
638
  1. Treat the stored key, base URLs, operating system, provider scope, and model catalog as already known. Do not request them again.
632
639
  2. Never output placeholders such as YOUR-SCALEMAX-ENDPOINT or your-key when an exact known value can be used.
633
- 3. Prefer the automatic CLI workflow. For an installed supported client, tell the user to exit the Helper with /quit, run \`scalemax update\`, then run \`scalemax doctor\` to verify it.
640
+ 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
641
  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. Do not claim you executed terminal commands. The Helper has no shell tools; give the shortest exact ScaleMax command sequence when user action is required.`;
642
+ 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
643
  }
637
644
  export function helperConnectionSummary(connection) {
638
645
  return `Authenticated ${helperBillingMode(connection)} key · ${helperPlatformName(connection.platform)} · ${connection.providerScope || 'both'} scope · ${connection.baseUrl} · helper model ${connection.model}`;
@@ -685,7 +692,7 @@ export function selectHelperModel(models, requested) {
685
692
  throw new Error(`Model "${requested}" is not available to this API key. Run \`scalemax models\` to see available models.`);
686
693
  return exact.id;
687
694
  }
688
- const preferred = ['gpt-5.6-luna', 'claude-haiku-4-5-20251001', 'fable-5', 'gpt-5.6-terra', 'gpt-5.6-sol'];
695
+ 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
696
  for (const id of preferred) {
690
697
  const match = models.find(model => model.id.toLowerCase() === id);
691
698
  if (match)
@@ -731,8 +738,170 @@ For automatic configuration, exit with \`/quit\`, run \`scalemax update\`, then
731
738
  }
732
739
  return reply;
733
740
  }
741
+ const HELPER_CLIENT_PATTERNS = [
742
+ { id: 'roo-code', pattern: /\b(?:roo(?:\s+code)?|roo\s+(?:extension|plugin)|vscode\s+roo|vs\s+code\s+roo)\b/i },
743
+ { id: 'openclaw', pattern: /\bopen\s*claw\b/i },
744
+ { id: 'opencode', pattern: /\bopen\s*code\b/i },
745
+ { id: 'cherry-studio', pattern: /\bcherry\s*studio\b/i },
746
+ { id: 'api-code', pattern: /\bapi\s*code\b/i },
747
+ { id: 'trae-solo', pattern: /\btrae(?:\s+solo)?\b/i },
748
+ { id: 'cursor', pattern: /\bcursor\b/i },
749
+ { id: 'hermes', pattern: /\bhermes\b/i },
750
+ { id: 'codex', pattern: /\bcodex(?:\s+cli)?\b/i },
751
+ { id: 'claude', pattern: /\bclaude\s+code\b/i }
752
+ ];
753
+ function helperActionKind(question) {
754
+ if (HELPER_MUTATION_PATTERN.test(question))
755
+ return 'configure';
756
+ if (HELPER_VERIFICATION_PATTERN.test(question))
757
+ return 'verify';
758
+ return undefined;
759
+ }
760
+ function helperActionTarget(question) {
761
+ if (CLAUDE_DESKTOP_PATTERN.test(question))
762
+ return undefined;
763
+ if (ALL_CLIENTS_PATTERN.test(question))
764
+ return 'all';
765
+ return HELPER_CLIENT_PATTERNS.find(candidate => candidate.pattern.test(question))?.id;
766
+ }
767
+ function requestedProviderScope(question, target) {
768
+ if (/\b(?:both|openai\s*\+\s*claude|claude\s*\+\s*openai)\b/i.test(question))
769
+ return 'both';
770
+ if (/\b(?:claude|anthropic)\b/i.test(question))
771
+ return 'anthropic';
772
+ if (/\b(?:openai|chatgpt|gpt)\b/i.test(question) || target === 'codex')
773
+ return 'openai';
774
+ return undefined;
775
+ }
776
+ function actionModels(connection, requestedScope) {
777
+ const catalog = connection.modelCatalog?.length
778
+ ? connection.modelCatalog
779
+ : (connection.availableModels || []).map(id => ({ id }));
780
+ if (!requestedScope || requestedScope === 'both')
781
+ return catalog;
782
+ return catalog.filter(model => requestedScope === 'anthropic' ? isAnthropicModel(model) : !isAnthropicModel(model));
783
+ }
784
+ function hasRooCodeExtension(paths) {
785
+ return paths.some(candidate => /^rooveterinaryinc\.roo-cline-/i.test(path.basename(candidate)));
786
+ }
787
+ async function installRooCodeExtension(ctx, client, write) {
788
+ let detection = await client.detect(ctx);
789
+ if (hasRooCodeExtension(detection.paths))
790
+ return;
791
+ if (!detection.executable)
792
+ throw new Error('VS Code and the Roo Code extension were not detected. Install VS Code first, then retry this request.');
793
+ write('→ Installing the official Roo Code extension in VS Code…');
794
+ const result = await execa('code', ['--install-extension', 'RooVeterinaryInc.roo-cline', '--force'], {
795
+ env: ctx.env,
796
+ reject: false,
797
+ all: true
798
+ });
799
+ if (result.exitCode !== 0)
800
+ throw new Error(`Roo Code extension installation failed: ${result.all?.trim() || `code exited with ${result.exitCode}`}`);
801
+ detection = await client.detect(ctx);
802
+ if (!hasRooCodeExtension(detection.paths))
803
+ throw new Error('VS Code reported a successful Roo Code installation, but the extension could not be detected afterward. Restart VS Code and retry.');
804
+ write('✓ Roo Code extension installed and detected.');
805
+ }
806
+ async function runOneHelperClientAction(kind, client, connection, home, requestedScope, write) {
807
+ const models = actionModels(connection, requestedScope);
808
+ if (!models.length)
809
+ throw new Error(`The authenticated key does not expose ${requestedScope === 'anthropic' ? 'Claude' : requestedScope === 'openai' ? 'OpenAI' : 'compatible'} models for ${client.name}. No configuration was changed.`);
810
+ if (client.id === 'codex' && models.every(isAnthropicModel))
811
+ throw new Error('Codex requires an OpenAI-compatible model, but this key exposes only Claude models. No configuration was changed.');
812
+ if (client.id === 'cursor' && connection.platform !== 'darwin')
813
+ throw new Error(`Automatic Cursor configuration is currently verified on macOS only. ${helperPlatformName(connection.platform)} was not modified.`);
814
+ const ctx = {
815
+ home,
816
+ platform: connection.platform || process.platform,
817
+ env: process.env,
818
+ apiKey: connection.apiKey,
819
+ baseUrl: connection.baseUrl,
820
+ providerScope: requestedScope || connection.providerScope || 'both',
821
+ models,
822
+ interactive: false,
823
+ allowAppModification: false
824
+ };
825
+ write(`→ Detecting ${client.name}…`);
826
+ let detection = await client.detect(ctx);
827
+ if (kind === 'configure' && client.id === 'roo-code') {
828
+ await installRooCodeExtension(ctx, client, write);
829
+ detection = await client.detect(ctx);
830
+ }
831
+ if (!detection.installed)
832
+ throw new Error(`${client.name} was not detected. No configuration was changed.`);
833
+ write(`✓ ${client.name} detected.`);
834
+ if (kind === 'verify') {
835
+ if (!client.verify) {
836
+ if (!detection.configured)
837
+ throw new Error(`${client.name} is installed but its ScaleMax configuration was not detected.`);
838
+ return `✓ ${client.name} ScaleMax configuration is present.`;
839
+ }
840
+ write(`→ Verifying ${client.name}…`);
841
+ const verified = await client.verify(ctx);
842
+ return `✓ ${client.name} ${verified.message}`;
843
+ }
844
+ write(`→ Configuring ${client.name} with the authenticated stored key…`);
845
+ const configured = await client.configure(ctx);
846
+ write(`✓ ${client.name} ${configured.message}`);
847
+ if (client.verify) {
848
+ write(`→ Verifying ${client.name} configuration…`);
849
+ try {
850
+ const verified = await client.verify(ctx);
851
+ write(`✓ ${client.name} ${verified.message}`);
852
+ }
853
+ catch (error) {
854
+ if (configured.changed) {
855
+ write(`→ Verification failed; removing the incomplete ScaleMax-managed ${client.name} configuration…`);
856
+ await client.remove(ctx).catch(() => undefined);
857
+ }
858
+ throw error;
859
+ }
860
+ }
861
+ else {
862
+ detection = await client.detect(ctx);
863
+ if (!detection.configured)
864
+ throw new Error(`${client.name} configuration was written but could not be detected afterward.`);
865
+ write(`✓ ${client.name} configuration detected after write.`);
866
+ }
867
+ const activation = client.id === 'roo-code' ? ' Reload VS Code once so Roo Code imports the verified ScaleMax profile.' : '';
868
+ return `✅ Successfully completed: ${client.name} is configured and locally verified.${activation}`;
869
+ }
870
+ /** Execute only narrowly allowlisted ScaleMax client actions; never model-generated shell commands. */
871
+ export async function executeHelperAction(question, connection, home, clientCatalog = clients, write = message => console.log(message)) {
872
+ const cleanQuestion = redactHelperSecrets(question.trim(), connection.apiKey);
873
+ if (!isScaleMaxHelperQuestion(cleanQuestion))
874
+ return undefined;
875
+ const kind = helperActionKind(cleanQuestion);
876
+ const target = helperActionTarget(cleanQuestion);
877
+ if (!kind || !target)
878
+ return undefined;
879
+ const scope = requestedProviderScope(cleanQuestion, target);
880
+ const selected = target === 'all'
881
+ ? clientCatalog.filter(client => client.id !== 'cursor')
882
+ : clientCatalog.filter(client => client.id === target);
883
+ if (!selected.length)
884
+ throw new Error('The requested ScaleMax integration is not supported by this CLI build.');
885
+ write(`✓ Stored ${helperBillingMode(connection)} key is authenticated; secrets remain hidden.`);
886
+ const completed = [];
887
+ const failures = [];
888
+ for (const client of selected) {
889
+ try {
890
+ completed.push(await runOneHelperClientAction(kind, client, connection, home, scope, write));
891
+ }
892
+ catch (error) {
893
+ const message = redactHelperSecrets(error instanceof Error ? error.message : String(error), connection.apiKey);
894
+ if (target !== 'all')
895
+ throw new Error(message);
896
+ failures.push(`${client.name}: ${message}`);
897
+ }
898
+ }
899
+ if (!completed.length)
900
+ throw new Error(`No ScaleMax client action completed.\n${failures.map(item => `- ${item}`).join('\n')}`);
901
+ return `${completed.join('\n')}${failures.length ? `\n\nSkipped or failed:\n${failures.map(item => `- ${item}`).join('\n')}` : ''}`;
902
+ }
734
903
  function printSessionHelp(write) {
735
- write('Commands: /help shows this help; /status shows the authenticated non-secret connection details; /quit exits. Ask only ScaleMax installation, configuration, authentication, or troubleshooting questions.');
904
+ 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
905
  }
737
906
  export async function helperBotCommand(args = [], home = homeDir(), dependencies = {}) {
738
907
  const deps = { ...defaultDependencies, ...dependencies };
@@ -751,10 +920,15 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
751
920
  platform: process.platform,
752
921
  providerScope: validation.key?.provider_scope || 'both',
753
922
  keyMode: validation.key?.mode,
754
- availableModels: validation.models.map(item => item.id)
923
+ availableModels: validation.models.map(item => item.id),
924
+ modelCatalog: validation.models
925
+ };
926
+ const respond = async (question, history) => {
927
+ const action = await executeHelperAction(question, connection, home, deps.clientCatalog, deps.write);
928
+ return action || requestHelperReply(question, history, connection, deps.complete);
755
929
  };
756
930
  if (options.question) {
757
- deps.write(await requestHelperReply(options.question, [], connection, deps.complete));
931
+ deps.write(await respond(options.question, []));
758
932
  return;
759
933
  }
760
934
  if (!process.stdin.isTTY)
@@ -787,7 +961,7 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
787
961
  continue;
788
962
  }
789
963
  try {
790
- const reply = await requestHelperReply(input, history, connection, deps.complete);
964
+ const reply = await respond(input, history);
791
965
  deps.write(reply);
792
966
  if (reply !== SCALEMAX_HELPER_REFUSAL) {
793
967
  history.push({ role: 'user', content: redactHelperSecrets(input, key) }, { role: 'assistant', content: reply });
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\nOpen the ScaleMax-only installation, configuration, authentication, and troubleshooting assistant.');
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 Open the ScaleMax Helper
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scalemax",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Universal ScaleMax installer for AI coding clients",
5
5
  "type": "module",
6
6
  "bin": {