scalemax 0.1.50 → 0.1.51

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.
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import fs from 'fs-extra';
3
3
  import { parseDocument } from 'yaml';
4
4
  import { atomicWrite, backupFile, captureFileSnapshots, restoreFileSnapshots, snapshotRollback } from '../utils/fs.js';
5
- import { clientConfigPath } from './paths.js';
5
+ import { clientConfigPath, hermesHome } from './paths.js';
6
6
  import { isScaleMaxApiUrl, isScaleMaxManagedText } from './common.js';
7
7
  import { anthropicModelIds, anthropicOrigin, deepSeekChatModelIds, modelIdKey, modelSupportsTools, openAiBaseUrl, openAiResponsesModelIds, preferredAnthropicModel, preferredOpenAiResponsesModel } from './models.js';
8
8
  const id = 'hermes';
@@ -27,7 +27,7 @@ function exactChatLabel(ids) {
27
27
  function asRecord(value) {
28
28
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
29
29
  }
30
- function envPath(home) { return path.join(home, '.hermes', '.env'); }
30
+ function envPath(ctx) { return path.join(hermesHome(ctx.home, ctx.platform, ctx.env), '.env'); }
31
31
  function managedEnv(text, apiKey) {
32
32
  const kept = text.split(/\r?\n/).filter(line => line.trim() !== envMarker && !line.startsWith(`${envKey}=`));
33
33
  while (kept.length && !kept[kept.length - 1])
@@ -73,8 +73,8 @@ async function removeLegacyArtifact(ctx) {
73
73
  return true;
74
74
  }
75
75
  export async function configure(ctx) {
76
- const file = clientConfigPath(id, ctx.home, ctx.platform);
77
- const secrets = envPath(ctx.home);
76
+ const file = clientConfigPath(id, ctx.home, ctx.platform, ctx.env);
77
+ const secrets = envPath(ctx);
78
78
  const legacy = path.join(ctx.home, '.hermes', 'config', 'scalemax.json');
79
79
  const snapshots = await captureFileSnapshots([file, secrets, legacy]);
80
80
  await fs.ensureDir(path.dirname(file), 0o700);
@@ -127,7 +127,12 @@ export async function configure(ctx) {
127
127
  doc.set('custom_providers', providers);
128
128
  const currentModel = asRecord(data.model);
129
129
  const currentProvider = String(currentModel.provider || '');
130
- const activatesScaleMax = !currentProvider || currentProvider.startsWith('custom:scalemax');
130
+ // "auto" is Hermes's own unchosen default: the native Windows installer
131
+ // writes provider "auto" with an OpenRouter base_url into a fresh
132
+ // config.yaml, which left ScaleMax listed but never active ("No inference
133
+ // provider is configured yet"). A provider the user picked is left alone.
134
+ const unchosen = !currentProvider || currentProvider === 'auto';
135
+ const activatesScaleMax = unchosen || currentProvider.startsWith('custom:scalemax');
131
136
  if (activatesScaleMax) {
132
137
  const keepClaude = currentProvider === `custom:${claudeProviderName}` || claudeIds.includes(String(currentModel.default || ''));
133
138
  const keepDeepSeek = currentProvider === `custom:${deepSeekProviderName}`
@@ -142,7 +147,10 @@ export async function configure(ctx) {
142
147
  const activeModel = activeName === codexProviderName
143
148
  ? preferredOpenAiResponsesModel(ctx)
144
149
  : activeName === chatProviderName ? deepSeekIds[0] : preferredAnthropicModel(ctx);
145
- doc.set('model', { ...currentModel, provider: `custom:${activeName}`, default: activeModel, max_tokens: 4096 });
150
+ // The custom provider carries its own base_url; a leftover model.base_url
151
+ // from the "auto" template would point Hermes back at OpenRouter.
152
+ const { base_url: _unchosenBaseUrl, ...keptModel } = currentModel;
153
+ doc.set('model', { ...(unchosen ? keptModel : currentModel), provider: `custom:${activeName}`, default: activeModel, max_tokens: 4096 });
146
154
  }
147
155
  const nextConfig = String(doc);
148
156
  const nextEnv = managedEnv(originalEnv, ctx.apiKey);
@@ -184,8 +192,8 @@ export async function configure(ctx) {
184
192
  };
185
193
  }
186
194
  export async function verify(ctx) {
187
- const file = clientConfigPath(id, ctx.home, ctx.platform);
188
- const secrets = envPath(ctx.home);
195
+ const file = clientConfigPath(id, ctx.home, ctx.platform, ctx.env);
196
+ const secrets = envPath(ctx);
189
197
  const doc = parseYaml(await fs.readFile(file, 'utf8'));
190
198
  const data = asRecord(doc.toJS());
191
199
  const providers = Array.isArray(data.custom_providers) ? data.custom_providers : [];
@@ -215,8 +223,8 @@ export async function verify(ctx) {
215
223
  return { id, name, changed: false, path: file, message: `verified ${codexIds.length} Codex/OpenAI, ${deepSeekIds.length} ${chatLabel}, and ${claudeIds.length} Claude models` };
216
224
  }
217
225
  export async function remove(ctx) {
218
- const file = clientConfigPath(id, ctx.home, ctx.platform);
219
- const secrets = envPath(ctx.home);
226
+ const file = clientConfigPath(id, ctx.home, ctx.platform, ctx.env);
227
+ const secrets = envPath(ctx);
220
228
  const currentEnv = await fs.readFile(secrets, 'utf8').catch(() => '');
221
229
  if (!(await fs.pathExists(file))) {
222
230
  const removedLegacy = await removeLegacyArtifact(ctx);
@@ -14,6 +14,7 @@ const preferredClaude = [
14
14
  'claude-opus-4-7',
15
15
  'claude-opus-4-6',
16
16
  'claude-sonnet-4-5',
17
+ 'claude-haiku-5-5',
17
18
  'claude-haiku-4-5-20251001'
18
19
  ];
19
20
  /** Codex runs only on these (plus Auto on an Auto key). */
@@ -2,5 +2,12 @@ import { ClientId } from '../types/client.js';
2
2
  export declare function codexDir(home: string): string;
3
3
  export declare function codexConfigPath(home: string): string;
4
4
  export declare function codexAuthPath(home: string): string;
5
+ /**
6
+ * Hermes Agent's home: HERMES_HOME when set, else its platform default.
7
+ * Native Windows Hermes keeps config.yaml and .env in %LOCALAPPDATA%\\hermes
8
+ * and never reads ~/.hermes (hermes_constants._get_platform_default_hermes_home),
9
+ * so writing ~/.hermes there left Hermes with no ScaleMax provider.
10
+ */
11
+ export declare function hermesHome(home: string, platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
5
12
  export declare function clientConfigPath(id: ClientId, home: string, platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
6
13
  export declare function installHints(id: ClientId, home: string, platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): string[];
@@ -3,6 +3,20 @@ import { configRoot } from '../utils/paths.js';
3
3
  export function codexDir(home) { return path.join(home, '.codex'); }
4
4
  export function codexConfigPath(home) { return path.join(codexDir(home), 'config.toml'); }
5
5
  export function codexAuthPath(home) { return path.join(codexDir(home), 'auth.json'); }
6
+ /**
7
+ * Hermes Agent's home: HERMES_HOME when set, else its platform default.
8
+ * Native Windows Hermes keeps config.yaml and .env in %LOCALAPPDATA%\\hermes
9
+ * and never reads ~/.hermes (hermes_constants._get_platform_default_hermes_home),
10
+ * so writing ~/.hermes there left Hermes with no ScaleMax provider.
11
+ */
12
+ export function hermesHome(home, platform, env = process.env) {
13
+ const explicit = env.HERMES_HOME?.trim();
14
+ if (explicit)
15
+ return explicit;
16
+ if (platform === 'win32')
17
+ return path.join(env.LOCALAPPDATA?.trim() || path.join(home, 'AppData', 'Local'), 'hermes');
18
+ return path.join(home, '.hermes');
19
+ }
6
20
  export function clientConfigPath(id, home, platform, env = process.env) {
7
21
  const app = configRoot(home, platform, env);
8
22
  const linux = env.XDG_CONFIG_HOME || path.join(home, '.config');
@@ -23,7 +37,7 @@ export function clientConfigPath(id, home, platform, env = process.env) {
23
37
  case 'claude': return path.join(home, '.claude', 'settings.json');
24
38
  case 'opencode': return path.join(linux, 'opencode', 'opencode.json');
25
39
  case 'openclaw': return path.join(home, '.openclaw', 'openclaw.json');
26
- case 'hermes': return path.join(home, '.hermes', 'config.yaml');
40
+ case 'hermes': return path.join(hermesHome(home, platform, env), 'config.yaml');
27
41
  case 'cherry-studio': return path.join(home, '.scalemax', 'cherry-studio.setup.json');
28
42
  case 'api-code': return path.join(home, '.scalemax', 'api-code.examples.json');
29
43
  }
@@ -41,7 +55,7 @@ export function installHints(id, home, platform, env = process.env) {
41
55
  case 'claude': return [path.join(home, '.claude')];
42
56
  case 'opencode': return [path.join(linux, 'opencode')];
43
57
  case 'openclaw': return [path.join(home, '.openclaw'), path.join(linux, 'openclaw')];
44
- case 'hermes': return [path.join(home, '.hermes')];
58
+ case 'hermes': return [...new Set([hermesHome(home, platform, env), path.join(home, '.hermes')])];
45
59
  case 'cherry-studio': return [platform === 'darwin' ? '/Applications/Cherry Studio.app' : path.join(app, 'Cherry Studio'), path.join(linux, 'CherryStudio')];
46
60
  case 'api-code': return [path.join(home, '.scalemax')];
47
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scalemax",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "description": "Universal ScaleMax installer for AI coding clients",
5
5
  "type": "module",
6
6
  "bin": {