scalemax 0.1.24 → 0.1.25

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
@@ -148,7 +148,7 @@ ScaleMax configures the official Codex custom-provider interface in `~/.codex/co
148
148
 
149
149
  ### Claude Code
150
150
 
151
- ScaleMax updates `~/.claude/settings.json` with `ANTHROPIC_BASE_URL=https://api.scalemax.pro`, gateway model discovery, and a cached `apiKeyHelper` that reads a private `~/.claude/scalemax.key` file. The raw key is not stored in the settings JSON. The Anthropic base URL intentionally has no `/v1` suffix because Claude Code adds `/v1/messages` itself. The CLI preserves unrelated settings, selects a live main model from the authenticated catalog (preferring `claude-opus-4-8`), pins authenticated Haiku for lightweight background work when available, and sets `CLAUDE_CODE_DISABLE_THINKING=1` because third-party Anthropic-compatible providers do not support Claude Code's proprietary extended-thinking control envelope. It verifies a one-token Messages request and, if verification fails, removes only the ScaleMax-managed changes. Claude Code picker aliases such as `claude-opus-4-8[1m]` are normalized by the gateway.
151
+ ScaleMax updates `~/.claude/settings.json` with `ANTHROPIC_BASE_URL=https://api.scalemax.pro`, gateway model discovery, and a cached `apiKeyHelper` that reads a private `~/.claude/scalemax.key` file. The raw key is not stored in the settings JSON. Because Claude Code's gateway discovery only accepts IDs beginning with `claude` or `anthropic`, a catalog that includes Fable also receives a managed `ANTHROPIC_CUSTOM_MODEL_OPTION=fable-5` entry (with a friendly name and description), making `/model fable-5` selectable; any prior custom picker values are restored when ScaleMax configuration is removed. The Anthropic base URL intentionally has no `/v1` suffix because Claude Code adds `/v1/messages` itself. The CLI preserves unrelated settings, selects a live main model from the authenticated catalog (preferring `claude-opus-4-8`), pins authenticated Haiku for lightweight background work when available, and sets `CLAUDE_CODE_DISABLE_THINKING=1` because third-party Anthropic-compatible providers do not support Claude Code's proprietary extended-thinking control envelope. It verifies a one-token Messages request and, if verification fails, removes only the ScaleMax-managed changes. Claude Code picker aliases such as `claude-opus-4-8[1m]` are normalized by the gateway.
152
152
 
153
153
  ### OpenCode
154
154
 
@@ -14,7 +14,29 @@ const discoveryKey = 'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY';
14
14
  const fastModelKey = 'ANTHROPIC_DEFAULT_HAIKU_MODEL';
15
15
  const helperTtlKey = 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS';
16
16
  const disableThinkingKey = 'CLAUDE_CODE_DISABLE_THINKING';
17
- const managedEnvKeys = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_MODEL', discoveryKey, fastModelKey, helperTtlKey, disableThinkingKey];
17
+ // Claude Code's gateway discovery intentionally ignores model IDs that do not
18
+ // start with `claude` or `anthropic`. Fable's public ScaleMax ID is
19
+ // deliberately `fable-5`, so keep it selectable with the supported custom
20
+ // picker entry as well as the normal discovery feed.
21
+ const customModelOptionKey = 'ANTHROPIC_CUSTOM_MODEL_OPTION';
22
+ const customModelOptionNameKey = 'ANTHROPIC_CUSTOM_MODEL_OPTION_NAME';
23
+ const customModelOptionDescriptionKey = 'ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION';
24
+ const fableModelId = 'fable-5';
25
+ const fableModelName = 'Fable 5';
26
+ const fableModelDescription = 'Fable 5 via ScaleMax';
27
+ const managedEnvKeys = [
28
+ 'ANTHROPIC_AUTH_TOKEN',
29
+ 'ANTHROPIC_API_KEY',
30
+ 'ANTHROPIC_BASE_URL',
31
+ 'ANTHROPIC_MODEL',
32
+ customModelOptionKey,
33
+ customModelOptionNameKey,
34
+ customModelOptionDescriptionKey,
35
+ discoveryKey,
36
+ fastModelKey,
37
+ helperTtlKey,
38
+ disableThinkingKey
39
+ ];
18
40
  function secretPath(home) { return path.join(home, '.claude', 'scalemax.key'); }
19
41
  function helperCommand(home, platform) {
20
42
  const file = secretPath(home);
@@ -25,10 +47,17 @@ function helperCommand(home, platform) {
25
47
  function asRecord(value) {
26
48
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
27
49
  }
50
+ function hasFableModel(ctx) {
51
+ return (ctx.models || []).some(model => {
52
+ const id = String(model.id || '').trim().toLowerCase();
53
+ return id === fableModelId || id === 'claude-fable-5';
54
+ });
55
+ }
28
56
  export async function configure(ctx) {
29
57
  const file = clientConfigPath(id, ctx.home, ctx.platform);
30
58
  const model = preferredModel(ctx);
31
59
  const quick = fastModel(ctx);
60
+ const fable = hasFableModel(ctx);
32
61
  const baseUrl = anthropicOrigin(ctx.baseUrl);
33
62
  const apiKeyHelper = helperCommand(ctx.home, ctx.platform);
34
63
  const secret = await writeClientSecret(secretPath(ctx.home), ctx);
@@ -51,6 +80,25 @@ export async function configure(ctx) {
51
80
  [helperTtlKey]: '900000',
52
81
  [disableThinkingKey]: '1'
53
82
  };
83
+ if (fable) {
84
+ // This is a public model ID, not an upstream/provider alias. Claude
85
+ // Code's custom option bypasses its `claude*` discovery filter while
86
+ // still sending exactly `fable-5` to the configured gateway.
87
+ nextEnv[customModelOptionKey] = fableModelId;
88
+ nextEnv[customModelOptionNameKey] = fableModelName;
89
+ nextEnv[customModelOptionDescriptionKey] = fableModelDescription;
90
+ }
91
+ else if (oldMetadata.managedBy === 'ScaleMax CLI') {
92
+ // A key can be reconfigured from a Fable-enabled catalog to one that
93
+ // does not include Fable. Remove only the values this CLI previously
94
+ // managed, restoring any user values captured on the first configure.
95
+ for (const key of [customModelOptionKey, customModelOptionNameKey, customModelOptionDescriptionKey]) {
96
+ if (Object.prototype.hasOwnProperty.call(previousEnv, key))
97
+ nextEnv[key] = previousEnv[key];
98
+ else if (nextEnv[key] === fableModelId || nextEnv[key] === fableModelName || nextEnv[key] === fableModelDescription)
99
+ delete nextEnv[key];
100
+ }
101
+ }
54
102
  delete nextEnv.ANTHROPIC_AUTH_TOKEN;
55
103
  delete nextEnv.ANTHROPIC_API_KEY;
56
104
  if (quick)
@@ -70,6 +118,9 @@ export async function configure(ctx) {
70
118
  fastModel: quick || null,
71
119
  helperTtl: '900000',
72
120
  disableThinking: '1',
121
+ customModelOption: fable ? fableModelId : null,
122
+ customModelOptionName: fable ? fableModelName : null,
123
+ customModelOptionDescription: fable ? fableModelDescription : null,
73
124
  apiKeyHelper,
74
125
  authTokenSha256: crypto.createHash('sha256').update(ctx.apiKey).digest('hex'),
75
126
  previousEnv,
@@ -89,7 +140,12 @@ export async function verify(ctx) {
89
140
  const settings = await fs.readJson(file);
90
141
  const env = asRecord(settings?.env);
91
142
  const model = String(env.ANTHROPIC_MODEL || '');
92
- if (!modelIds(ctx).includes(model) || env.ANTHROPIC_BASE_URL !== anthropicOrigin(ctx.baseUrl) || settings.apiKeyHelper !== helperCommand(ctx.home, ctx.platform) || env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || env[discoveryKey] !== '1' || env[disableThinkingKey] !== '1') {
143
+ const fable = hasFableModel(ctx);
144
+ const customModelMatches = !fable
145
+ || (env[customModelOptionKey] === fableModelId
146
+ && env[customModelOptionNameKey] === fableModelName
147
+ && env[customModelOptionDescriptionKey] === fableModelDescription);
148
+ if (!modelIds(ctx).includes(model) || !customModelMatches || env.ANTHROPIC_BASE_URL !== anthropicOrigin(ctx.baseUrl) || settings.apiKeyHelper !== helperCommand(ctx.home, ctx.platform) || env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || env[discoveryKey] !== '1' || env[disableThinkingKey] !== '1') {
93
149
  throw new Error('Claude Code settings do not match the authenticated ScaleMax model catalog.');
94
150
  }
95
151
  await verifyClientSecret(secretPath(ctx.home), ctx);
@@ -143,6 +199,12 @@ export async function remove(ctx) {
143
199
  restore(helperTtlKey);
144
200
  if (env[disableThinkingKey] === managed.disableThinking)
145
201
  restore(disableThinkingKey);
202
+ if (env[customModelOptionKey] === managed.customModelOption)
203
+ restore(customModelOptionKey);
204
+ if (env[customModelOptionNameKey] === managed.customModelOptionName)
205
+ restore(customModelOptionNameKey);
206
+ if (env[customModelOptionDescriptionKey] === managed.customModelOptionDescription)
207
+ restore(customModelOptionDescriptionKey);
146
208
  if (!Object.prototype.hasOwnProperty.call(env, 'ANTHROPIC_AUTH_TOKEN'))
147
209
  restore('ANTHROPIC_AUTH_TOKEN');
148
210
  if (!Object.prototype.hasOwnProperty.call(env, 'ANTHROPIC_API_KEY'))
@@ -14,7 +14,6 @@ const preferredOpenAi = [
14
14
  'gpt-5.4',
15
15
  'gpt-5.4-mini',
16
16
  'gpt-5.3-codex',
17
- 'fable-5',
18
17
  'codex-auto-review'
19
18
  ];
20
19
  export function modelIds(ctx) {
@@ -11,7 +11,7 @@ export function jsonConfig(client, apiKey, baseUrl, extra = {}) {
11
11
  }, null, 2) + '\n';
12
12
  }
13
13
  export function continueConfig(apiKey, baseUrl) {
14
- const models = ['fable-5', 'gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review'];
14
+ const models = ['gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review'];
15
15
  return JSON.stringify({
16
16
  models: models.map(model => ({
17
17
  title: `ScaleMax ${model}`,
@@ -5,7 +5,7 @@ const id = 'trae-solo';
5
5
  const name = 'TRAE SOLO';
6
6
  export async function configure(ctx) {
7
7
  const file = clientConfigPath(id, ctx.home, ctx.platform);
8
- const content = jsonConfig(name, '$SCALEMAX_API_KEY', ctx.baseUrl, { "officialSetup": ["Settings > Models > Add Model", "API format: OpenAI Chat Completions", `Request URL: ${ctx.baseUrl}/chat/completions or base URL ${ctx.baseUrl}`, "Use only a model ID returned by the authenticated catalog"], "recommendedModel": "fable-5" });
8
+ const content = jsonConfig(name, '$SCALEMAX_API_KEY', ctx.baseUrl, { "officialSetup": ["Settings > Models > Add Model", "API format: OpenAI Chat Completions", `Request URL: ${ctx.baseUrl}/chat/completions or base URL ${ctx.baseUrl}`, "Use only a model ID returned by the authenticated catalog"], "recommendedModel": "gpt-5.4-mini" });
9
9
  const result = await writeManagedConfig(ctx, id, name, file, content);
10
10
  return { ...result, message: 'setup guide written; enter these values in TRAE SOLO custom model settings' };
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scalemax",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Universal ScaleMax installer for AI coding clients",
5
5
  "type": "module",
6
6
  "bin": {