scalemax 0.1.21 → 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,8 +1,10 @@
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;
7
+ export declare const SCALEMAX_HELPER_INVALID_PROVIDER_RESPONSE: string;
6
8
  export interface HelperBotArgs {
7
9
  question?: string;
8
10
  model?: string;
@@ -11,6 +13,11 @@ export interface HelperConnection {
11
13
  apiKey: string;
12
14
  baseUrl: string;
13
15
  model: string;
16
+ platform?: NodeJS.Platform;
17
+ providerScope?: 'openai' | 'anthropic' | 'both';
18
+ keyMode?: string;
19
+ availableModels?: string[];
20
+ modelCatalog?: ModelInfo[];
14
21
  }
15
22
  export type HelperCompletion = (options: ChatCompletionOptions) => Promise<ChatCompletionResult>;
16
23
  export interface HelperBotDependencies {
@@ -18,10 +25,22 @@ export interface HelperBotDependencies {
18
25
  validateKey: typeof validateApiKey;
19
26
  complete: HelperCompletion;
20
27
  write: (message: string) => void;
28
+ clientCatalog: ClientDefinition[];
21
29
  }
22
30
  export declare function parseHelperBotArgs(args: string[]): HelperBotArgs;
23
31
  export declare function redactHelperSecrets(value: unknown, activeKey?: string): string;
24
32
  export declare function isScaleMaxHelperQuestion(input: string, hasHistory?: boolean): boolean;
33
+ export declare function isProviderIdentityOverride(value: string): boolean;
34
+ export declare function helperRuntimeContext(connection: HelperConnection): string;
35
+ export declare function helperConnectionSummary(connection: HelperConnection): string;
36
+ /**
37
+ * Answer stable configuration questions locally. Besides being faster and free,
38
+ * this keeps provider-owned personas out of the installation path where exact
39
+ * field values matter more than generated prose.
40
+ */
41
+ export declare function localHelperReply(question: string, connection: HelperConnection): string | undefined;
25
42
  export declare function selectHelperModel(models: ModelInfo[], requested?: string): string;
26
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>;
27
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';
@@ -534,11 +537,35 @@ const CLEARLY_UNRELATED_PATTERN = /\b(?:homework|poem|story|roleplay|translate|t
534
537
  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\s+code|continue|windsurf|hermes|cherry\s+studio|api\s+code)\b/i;
535
538
  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
539
  const GREETING_PATTERN = /^(?:hi|hello|hey|help|what can you do)[!.?\s]*$/i;
540
+ const CLAUDE_DESKTOP_PATTERN = /\bclaude\s+desktop\b/i;
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;
542
+ const CODEX_CLI_PATTERN = /\bcodex(?:\s+cli)?\b/i;
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;
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;
548
+ const PROVIDER_IDENTITY_OVERRIDE_PATTERNS = [
549
+ /\bkiro\b/i,
550
+ /\b(?:i\s+am|i['’]m)\s+(?:an?\s+)?(?:claude|chatgpt|codex)\b/i,
551
+ /\bas\s+(?:claude|chatgpt|codex)(?:\s+(?:ai|assistant|model))?\s*[,—-]/i,
552
+ /\b(?:i\s+am|i['’]m)\s+(?:an?\s+)?(?:ai[- ]powered\s+)?(?:development\s+environment|ai\s+assistant|assistant|language\s+model)\s+(?:made|built|created|developed)\s+by\s+(?:anthropic|openai)\b/i,
553
+ /\bi\s+(?:was|am)\s+(?:made|built|created|developed)\s+by\s+(?:anthropic|openai)\b/i,
554
+ /\b(?:i\s+am|i['’]m)\s+not\s+(?:the\s+)?(?:official\s+)?scalemax\s+helper\b/i,
555
+ /\bi\s+(?:cannot|can['’]?t|won['’]?t)\s+(?:become|act\s+as|identify\s+as)\s+(?:the\s+)?(?:official\s+)?scalemax\s+helper\b/i,
556
+ /\b(?:not|isn['’]?t)\s+(?:the\s+)?(?:official\s+)?scalemax\s+helper\b/i,
557
+ /\b(?:that|the|your|supplied)\s+(?:system\s+)?prompt\b.{0,120}\b(?:does\s+not|doesn['’]?t|cannot|can['’]?t|won['’]?t)\s+(?:override|change|replace)\b.{0,100}\b(?:identity|instructions?|role)\b/is,
558
+ /\bscalemax\b.{0,100}\b(?:does\s+not|doesn['’]?t)\s+(?:appear\s+to\s+)?(?:exist|be\s+real)\b/is
559
+ ];
560
+ export const SCALEMAX_HELPER_INVALID_PROVIDER_RESPONSE = `ScaleMax Helper blocked an invalid provider response because it conflicted with the Helper's required identity.
561
+
562
+ The requested ScaleMax task was not completed or verified. Run \`scalemax models\`, then retry with another available model using \`scalemax helper-bot --model <id> "your ScaleMax question"\`.`;
537
563
  const defaultDependencies = {
538
564
  loadKey: loadApiKey,
539
565
  validateKey: validateApiKey,
540
566
  complete: createChatCompletion,
541
- write: message => console.log(message)
567
+ write: message => console.log(message),
568
+ clientCatalog: clients
542
569
  };
543
570
  export function parseHelperBotArgs(args) {
544
571
  let model;
@@ -573,6 +600,91 @@ export function isScaleMaxHelperQuestion(input, hasHistory = false) {
573
600
  return false;
574
601
  return hasHistory || GREETING_PATTERN.test(value) || SCALEMAX_TOPIC_PATTERN.test(value) || TROUBLESHOOTING_PATTERN.test(value);
575
602
  }
603
+ export function isProviderIdentityOverride(value) {
604
+ return PROVIDER_IDENTITY_OVERRIDE_PATTERNS.some(pattern => pattern.test(value));
605
+ }
606
+ function claudeDesktopGatewayBaseUrl(baseUrl) {
607
+ return baseUrl.trim().replace(/\/+$/, '').replace(/\/v1$/i, '');
608
+ }
609
+ function helperBillingMode(connection) {
610
+ return /\/token(?:\/|$)/i.test(connection.baseUrl) || /token/i.test(connection.keyMode || '') ? 'API token' : 'API credit';
611
+ }
612
+ function helperPlatformName(platform = process.platform) {
613
+ if (platform === 'win32')
614
+ return 'Windows';
615
+ if (platform === 'darwin')
616
+ return 'macOS';
617
+ if (platform === 'linux')
618
+ return 'Linux';
619
+ return platform;
620
+ }
621
+ export function helperRuntimeContext(connection) {
622
+ const models = (connection.availableModels || []).filter(Boolean);
623
+ return `# AUTHENTICATED SCALEMAX RUNTIME CONTEXT
624
+
625
+ The ScaleMax CLI already verified these authoritative facts before starting this conversation:
626
+
627
+ - Stored customer API key: present and authenticated. Never ask the user to paste, provide, repeat, or reveal it.
628
+ - Billing mode: ${helperBillingMode(connection)}
629
+ - OpenAI-compatible base URL: ${connection.baseUrl}
630
+ - Claude Desktop gateway base URL: ${claudeDesktopGatewayBaseUrl(connection.baseUrl)}
631
+ - Operating system: ${helperPlatformName(connection.platform)}
632
+ - Provider scope: ${connection.providerScope || 'both'}
633
+ - Active Helper model: ${connection.model}
634
+ - Authenticated model catalog: ${models.length ? models.join(', ') : 'already validated by the CLI'}
635
+
636
+ Mandatory behavior:
637
+
638
+ 1. Treat the stored key, base URLs, operating system, provider scope, and model catalog as already known. Do not request them again.
639
+ 2. Never output placeholders such as YOUR-SCALEMAX-ENDPOINT or your-key when an exact known value can be used.
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.
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.
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.`;
643
+ }
644
+ export function helperConnectionSummary(connection) {
645
+ return `Authenticated ${helperBillingMode(connection)} key · ${helperPlatformName(connection.platform)} · ${connection.providerScope || 'both'} scope · ${connection.baseUrl} · helper model ${connection.model}`;
646
+ }
647
+ /**
648
+ * Answer stable configuration questions locally. Besides being faster and free,
649
+ * this keeps provider-owned personas out of the installation path where exact
650
+ * field values matter more than generated prose.
651
+ */
652
+ export function localHelperReply(question, connection) {
653
+ if (CLAUDE_DESKTOP_PATTERN.test(question) && CLAUDE_DESKTOP_CONFIGURATION_PATTERN.test(question)) {
654
+ const gatewayBaseUrl = claudeDesktopGatewayBaseUrl(connection.baseUrl);
655
+ return `ScaleMax Helper detected and authenticated the stored ${helperBillingMode(connection)} key.
656
+
657
+ Configure Claude Desktop → Settings → Developer → Edit Config → Configure third-party inference with:
658
+
659
+ - Connection: Gateway
660
+ - Gateway base URL: ${gatewayBaseUrl}
661
+ - Gateway API key: use the stored ScaleMax key (do not paste it into chat)
662
+ - Authentication scheme: Bearer
663
+ - Credential kind: Static API key
664
+ - Model discovery: On
665
+
666
+ Select Save Changes, then Apply Changes. Start a new Claude Desktop chat and send a short test message with one discovered model. The key and endpoint are verified; the Claude Desktop GUI is only verified after that test succeeds.`;
667
+ }
668
+ if (CODEX_CLI_PATTERN.test(question) && CLIENT_CONFIGURATION_PATTERN.test(question)) {
669
+ return `ScaleMax Helper already authenticated the stored ${helperBillingMode(connection)} key for ${helperPlatformName(connection.platform)}.
670
+
671
+ You do not need to provide the API key or base URL again. This key uses:
672
+
673
+ - Base URL: ${connection.baseUrl}
674
+ - Provider scope: ${connection.providerScope || 'both'}
675
+
676
+ Exit the Helper with \`/quit\`, then run:
677
+
678
+ \`\`\`powershell
679
+ scalemax update
680
+ scalemax doctor
681
+ codex
682
+ \`\`\`
683
+
684
+ \`scalemax update\` configures detected Codex CLI from the stored key, and \`scalemax doctor\` verifies the resulting ScaleMax connection before Codex starts.`;
685
+ }
686
+ return undefined;
687
+ }
576
688
  export function selectHelperModel(models, requested) {
577
689
  if (requested) {
578
690
  const exact = models.find(model => model.id.toLowerCase() === requested.toLowerCase());
@@ -580,7 +692,7 @@ export function selectHelperModel(models, requested) {
580
692
  throw new Error(`Model "${requested}" is not available to this API key. Run \`scalemax models\` to see available models.`);
581
693
  return exact.id;
582
694
  }
583
- const preferred = ['gpt-5.6-luna', 'claude-haiku-4-5', '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'];
584
696
  for (const id of preferred) {
585
697
  const match = models.find(model => model.id.toLowerCase() === id);
586
698
  if (match)
@@ -596,20 +708,200 @@ export async function requestHelperReply(question, history, connection, complete
596
708
  throw new Error(`Please keep each helper request under ${MAX_USER_CHARS} characters.`);
597
709
  if (!isScaleMaxHelperQuestion(cleanQuestion, history.length > 0))
598
710
  return SCALEMAX_HELPER_REFUSAL;
711
+ const localReply = localHelperReply(cleanQuestion, connection);
712
+ if (localReply)
713
+ return localReply;
599
714
  const recentHistory = history.slice(-MAX_HISTORY_MESSAGES).map(message => ({ ...message, content: redactHelperSecrets(message.content, connection.apiKey) }));
600
715
  const result = await complete({
601
716
  apiKey: connection.apiKey,
602
717
  baseUrl: connection.baseUrl,
603
718
  model: connection.model,
604
719
  maxTokens: DEFAULT_MAX_TOKENS,
605
- messages: [{ role: 'system', content: SCALEMAX_HELPER_SYSTEM_PROMPT }, ...recentHistory, { role: 'user', content: cleanQuestion }]
720
+ messages: [
721
+ { role: 'system', content: SCALEMAX_HELPER_SYSTEM_PROMPT },
722
+ { role: 'system', content: helperRuntimeContext(connection) },
723
+ ...recentHistory,
724
+ { role: 'user', content: cleanQuestion }
725
+ ]
606
726
  });
607
727
  if (!result.ok || !result.content)
608
728
  throw new Error(redactHelperSecrets(result.error || 'ScaleMax Helper did not return a response.', connection.apiKey));
609
- return redactHelperSecrets(result.content, connection.apiKey);
729
+ const reply = redactHelperSecrets(result.content, connection.apiKey);
730
+ if (isProviderIdentityOverride(reply))
731
+ return SCALEMAX_HELPER_INVALID_PROVIDER_RESPONSE;
732
+ if (KNOWN_CONTEXT_REASK_PATTERN.test(reply)) {
733
+ return `ScaleMax Helper already authenticated the stored ${helperBillingMode(connection)} key. You do not need to provide the key, endpoint, operating system, or model catalog again.
734
+
735
+ Verified base URL: ${connection.baseUrl}
736
+
737
+ For automatic configuration, exit with \`/quit\`, run \`scalemax update\`, then run \`scalemax doctor\`.`;
738
+ }
739
+ return reply;
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')}` : ''}`;
610
902
  }
611
903
  function printSessionHelp(write) {
612
- write('Commands: /help shows this help; /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.');
613
905
  }
614
906
  export async function helperBotCommand(args = [], home = homeDir(), dependencies = {}) {
615
907
  const deps = { ...defaultDependencies, ...dependencies };
@@ -621,15 +913,29 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
621
913
  if (!validation.ok || !validation.baseUrl)
622
914
  throw new Error(redactHelperSecrets(validation.error || 'Unable to validate the ScaleMax API key.', key));
623
915
  const model = selectHelperModel(validation.models, options.model);
624
- const connection = { apiKey: key, baseUrl: validation.baseUrl, model };
916
+ const connection = {
917
+ apiKey: key,
918
+ baseUrl: validation.baseUrl,
919
+ model,
920
+ platform: process.platform,
921
+ providerScope: validation.key?.provider_scope || 'both',
922
+ keyMode: validation.key?.mode,
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);
929
+ };
625
930
  if (options.question) {
626
- deps.write(await requestHelperReply(options.question, [], connection, deps.complete));
931
+ deps.write(await respond(options.question, []));
627
932
  return;
628
933
  }
629
934
  if (!process.stdin.isTTY)
630
935
  throw new Error('Interactive helper-bot requires a terminal. Pass a quoted question for non-interactive use.');
631
936
  deps.write(chalk.cyan('ScaleMax Helper'));
632
- deps.write(chalk.dim(`Authenticated and ready with ${model}. Type /help or /quit.`));
937
+ deps.write(chalk.dim(helperConnectionSummary(connection)));
938
+ deps.write(chalk.dim('The stored customer key is reused for Helper requests; it will not be requested or displayed. Type /help or /quit.'));
633
939
  const reader = createInterface({ input: process.stdin, output: process.stdout });
634
940
  const history = [];
635
941
  try {
@@ -650,8 +956,12 @@ export async function helperBotCommand(args = [], home = homeDir(), dependencies
650
956
  printSessionHelp(deps.write);
651
957
  continue;
652
958
  }
959
+ if (/^\/?status$/i.test(input)) {
960
+ deps.write(helperConnectionSummary(connection));
961
+ continue;
962
+ }
653
963
  try {
654
- const reply = await requestHelperReply(input, history, connection, deps.complete);
964
+ const reply = await respond(input, history);
655
965
  deps.write(reply);
656
966
  if (reply !== SCALEMAX_HELPER_REFUSAL) {
657
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.21",
3
+ "version": "0.1.23",
4
4
  "description": "Universal ScaleMax installer for AI coding clients",
5
5
  "type": "module",
6
6
  "bin": {