vibe-coding-master 0.7.14 → 0.7.15

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
@@ -235,14 +235,16 @@ a host-running [Claude Code Router](https://github.com/musistudio/claude-code-ro
235
235
  CCR must already be installed, authenticated, configured, and running on the
236
236
  host. VCM does not manage the CCR process.
237
237
 
238
- VCM uses this fixed Dev Container endpoint:
238
+ VCM automatically checks the local-host and DevContainer endpoints:
239
239
 
240
240
  ```text
241
+ http://127.0.0.1:3456
241
242
  http://host.docker.internal:3456
242
243
  ```
243
244
 
244
- Configure CCR to listen on port `3456` with an API key and make it reachable
245
- from the container. In the VCM `Settings` section:
245
+ Configure CCR to listen on port `3456` with an API key. When VCM runs in a
246
+ DevContainer, make the second endpoint reachable from the container. In the VCM
247
+ `Settings` section:
246
248
 
247
249
  1. enter and save the CCR API key;
248
250
  2. enable `CCR GPT models`;
@@ -250,13 +252,15 @@ from the container. In the VCM `Settings` section:
250
252
  4. select `GPT-5.6 Sol (CCR)` in any Session model control.
251
253
 
252
254
  The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
253
- permissions, is used only to check CCR and discover models, and is never
254
- returned by the settings API. GPT sessions use the CCR-managed Claude Code
255
- `apiKeyHelper`; VCM removes inherited Anthropic token variables only from those
256
- child processes. Native Claude selections retain their existing launch and
257
- authentication behavior. If CCR is disabled, unreachable, rejects the key, or
258
- does not expose `Codex API/gpt-5.6-sol`, VCM blocks the new Start, Resume, or
259
- Restart and does not fall back to another model.
255
+ permissions and is never returned by the settings API. It is used for CCR
256
+ checks, model discovery, and the GPT-only `apiKeyHelper`. GPT sessions receive a
257
+ child-only `--settings` override; VCM removes inherited Anthropic token
258
+ variables only from those child processes. VCM removes CCR-owned takeover
259
+ entries from global Claude settings, so native Claude selections keep their
260
+ normal launch and account authentication. If CCR is disabled, unreachable,
261
+ rejects the key, or does not expose
262
+ `Codex API/gpt-5.6-sol`, VCM blocks the new Start, Resume, or Restart and does
263
+ not fall back to another model.
260
264
 
261
265
  ## Launch Template
262
266
 
@@ -1,62 +1,84 @@
1
- import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID } from "../../shared/types/session.js";
1
+ import { CCR_GATEWAY_BASE_URLS, CCR_GPT_MODEL_ID } from "../../shared/types/session.js";
2
2
  const DEFAULT_TIMEOUT_MS = 3_000;
3
3
  const CCR_ENCODED_MODEL_PREFIX = "anthropic/claude-ccr-h";
4
4
  export function createCcrGatewayAdapter(deps = {}) {
5
- const baseUrl = (deps.baseUrl ?? CCR_GATEWAY_BASE_URL).replace(/\/+$/, "");
5
+ const baseUrls = (deps.baseUrl ? [deps.baseUrl] : CCR_GATEWAY_BASE_URLS)
6
+ .map((baseUrl) => baseUrl.replace(/\/+$/, ""));
6
7
  const fetchImpl = deps.fetch ?? globalThis.fetch;
7
8
  const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8
9
  return {
9
10
  async probe(apiKey) {
10
- try {
11
- const root = await requestJson(fetchImpl, `${baseUrl}/`, timeoutMs);
12
- if (!root.response.ok) {
13
- return invalidResponse(`CCR gateway root returned HTTP ${root.response.status}.`);
11
+ const failures = [];
12
+ for (const baseUrl of baseUrls) {
13
+ const result = await probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs);
14
+ if (result.baseUrl) {
15
+ return result;
14
16
  }
15
- if (!isCcrGateway(root.payload)) {
16
- return {
17
- connectionState: "not-ccr",
18
- modelAvailable: false,
19
- error: `The fixed CCR endpoint ${baseUrl} did not identify a Claude Code Router gateway.`
20
- };
21
- }
22
- const models = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, {
23
- authorization: `Bearer ${apiKey}`,
24
- "user-agent": "Claude Code"
25
- });
26
- if (models.response.status === 401 || models.response.status === 403) {
27
- return {
28
- connectionState: "unauthorized",
29
- modelAvailable: false,
30
- error: "CCR rejected the configured API key."
31
- };
32
- }
33
- if (!models.response.ok) {
34
- return invalidResponse(`CCR model discovery returned HTTP ${models.response.status}.`);
35
- }
36
- const modelDescriptors = readModelDescriptors(models.payload);
37
- if (!modelDescriptors) {
38
- return invalidResponse("CCR returned an invalid /v1/models response.");
39
- }
40
- const modelAvailable = modelDescriptors.some(isRequiredModel);
41
- return {
42
- connectionState: "available",
43
- modelAvailable,
44
- ...(modelAvailable
45
- ? {}
46
- : { error: `CCR does not expose the required model ${CCR_GPT_MODEL_ID}.` })
47
- };
48
- }
49
- catch (error) {
50
- const timedOut = isAbortError(error);
51
- return {
52
- connectionState: "unreachable",
53
- modelAvailable: false,
54
- error: timedOut
55
- ? `CCR connection timed out after ${timeoutMs} ms at ${baseUrl}.`
56
- : `CCR could not be reached from the VCM container backend at ${baseUrl}. Reason: ${errorMessage(error)}`
57
- };
17
+ failures.push(result);
58
18
  }
19
+ return combineProbeFailures(baseUrls, failures);
20
+ }
21
+ };
22
+ }
23
+ async function probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs) {
24
+ try {
25
+ const root = await requestJson(fetchImpl, `${baseUrl}/`, timeoutMs);
26
+ if (!root.response.ok) {
27
+ return invalidResponse(`Gateway root at ${baseUrl} returned HTTP ${root.response.status}.`);
28
+ }
29
+ if (!isCcrGateway(root.payload)) {
30
+ return {
31
+ connectionState: "not-ccr",
32
+ modelAvailable: false,
33
+ error: `${baseUrl} did not identify a Claude Code Router gateway.`
34
+ };
59
35
  }
36
+ const models = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, {
37
+ authorization: `Bearer ${apiKey}`,
38
+ "user-agent": "Claude Code"
39
+ });
40
+ if (models.response.status === 401 || models.response.status === 403) {
41
+ return {
42
+ connectionState: "unauthorized",
43
+ modelAvailable: false,
44
+ baseUrl,
45
+ error: "CCR rejected the configured API key."
46
+ };
47
+ }
48
+ if (!models.response.ok) {
49
+ return invalidResponse(`CCR model discovery at ${baseUrl} returned HTTP ${models.response.status}.`, baseUrl);
50
+ }
51
+ const modelDescriptors = readModelDescriptors(models.payload);
52
+ if (!modelDescriptors) {
53
+ return invalidResponse("CCR returned an invalid /v1/models response.", baseUrl);
54
+ }
55
+ const modelAvailable = modelDescriptors.some(isRequiredModel);
56
+ return {
57
+ connectionState: "available",
58
+ modelAvailable,
59
+ baseUrl,
60
+ ...(modelAvailable
61
+ ? {}
62
+ : { error: `CCR does not expose the required model ${CCR_GPT_MODEL_ID}.` })
63
+ };
64
+ }
65
+ catch (error) {
66
+ const timedOut = isAbortError(error);
67
+ return {
68
+ connectionState: "unreachable",
69
+ modelAvailable: false,
70
+ error: timedOut
71
+ ? `CCR connection timed out after ${timeoutMs} ms at ${baseUrl}.`
72
+ : `CCR could not be reached from the VCM backend at ${baseUrl}. Reason: ${errorMessage(error)}`
73
+ };
74
+ }
75
+ }
76
+ function combineProbeFailures(baseUrls, failures) {
77
+ const lastFailure = failures.at(-1);
78
+ return {
79
+ connectionState: lastFailure?.connectionState ?? "unreachable",
80
+ modelAvailable: false,
81
+ error: `CCR was not found at ${baseUrls.join(" or ")}. ${failures.map((failure) => failure.error).filter(Boolean).join(" ")}`.trim()
60
82
  };
61
83
  }
62
84
  async function requestJson(fetchImpl, url, timeoutMs, headers = {}) {
@@ -131,10 +153,11 @@ function decodeCcrModelId(modelId) {
131
153
  function normalizeModelName(value) {
132
154
  return (value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
133
155
  }
134
- function invalidResponse(error) {
156
+ function invalidResponse(error, baseUrl) {
135
157
  return {
136
158
  connectionState: "invalid-response",
137
159
  modelAvailable: false,
160
+ ...(baseUrl ? { baseUrl } : {}),
138
161
  error
139
162
  };
140
163
  }
@@ -18,8 +18,9 @@ export function createClaudeAdapter(runner) {
18
18
  }
19
19
  return result.stdout.trim();
20
20
  },
21
- buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default") {
21
+ buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride) {
22
22
  const args = ["--agent", role];
23
+ const sessionSettings = { ...settingsOverride };
23
24
  if (claudeSessionId) {
24
25
  args.push(resume ? "--resume" : "--session-id", claudeSessionId);
25
26
  }
@@ -27,11 +28,14 @@ export function createClaudeAdapter(runner) {
27
28
  args.push("--model", model);
28
29
  }
29
30
  if (effort === "ultracode") {
30
- args.push("--settings", JSON.stringify({ ultracode: true }));
31
+ sessionSettings.ultracode = true;
31
32
  }
32
33
  else if (effort !== "default") {
33
34
  args.push("--effort", effort);
34
35
  }
36
+ if (Object.keys(sessionSettings).length > 0) {
37
+ args.push("--settings", JSON.stringify(sessionSettings));
38
+ }
35
39
  if (permissionMode !== "default") {
36
40
  args.push("--permission-mode", permissionMode);
37
41
  }
@@ -0,0 +1,79 @@
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
3
+ const CCR_ENV_KEYS = [
4
+ "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
5
+ "ANTHROPIC_BASE_URL",
6
+ "ANTHROPIC_API_BASE_URL",
7
+ "CLAUDE_AGENT_API_BASE_URL",
8
+ "ANTHROPIC_MODEL",
9
+ "CCR_CLAUDE_CODE_MODEL",
10
+ "CODEXL_CLAUDE_CODE_MODEL",
11
+ "ANTHROPIC_SMALL_FAST_MODEL"
12
+ ];
13
+ export function createClaudeSettingsAdapter(deps) {
14
+ const settingsPath = deps.settingsPath ?? resolveClaudeSettingsPath(deps.env ?? process.env);
15
+ return {
16
+ async restoreNativeSettings() {
17
+ if (!(await deps.fs.pathExists(settingsPath))) {
18
+ return false;
19
+ }
20
+ const current = await deps.fs.readJson(settingsPath);
21
+ const restored = restoreNativeClaudeSettings(current);
22
+ if (!restored.changed) {
23
+ return false;
24
+ }
25
+ await deps.fs.writeJsonAtomic(settingsPath, restored.settings);
26
+ return true;
27
+ }
28
+ };
29
+ }
30
+ export function restoreNativeClaudeSettings(value) {
31
+ const settings = isObject(value) ? structuredClone(value) : {};
32
+ const helperIsCcr = typeof settings.apiKeyHelper === "string"
33
+ && settings.apiKeyHelper.includes(".claude-code-router");
34
+ const env = isObject(settings.env) ? settings.env : undefined;
35
+ const envUsesCcr = env
36
+ ? ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_BASE_URL", "CLAUDE_AGENT_API_BASE_URL"]
37
+ .some((key) => isCcrGatewayUrl(env[key]))
38
+ : false;
39
+ if (!helperIsCcr && !envUsesCcr) {
40
+ return { changed: false, settings };
41
+ }
42
+ let changed = false;
43
+ if (helperIsCcr) {
44
+ delete settings.apiKeyHelper;
45
+ changed = true;
46
+ }
47
+ if (env) {
48
+ for (const key of CCR_ENV_KEYS) {
49
+ if (key in env) {
50
+ delete env[key];
51
+ changed = true;
52
+ }
53
+ }
54
+ if (Object.keys(env).length === 0) {
55
+ delete settings.env;
56
+ }
57
+ }
58
+ return { changed, settings };
59
+ }
60
+ function resolveClaudeSettingsPath(env) {
61
+ const configDir = env.CLAUDE_CONFIG_DIR?.trim();
62
+ return path.join(configDir ? path.resolve(configDir) : path.join(homedir(), ".claude"), "settings.json");
63
+ }
64
+ function isCcrGatewayUrl(value) {
65
+ if (typeof value !== "string") {
66
+ return false;
67
+ }
68
+ try {
69
+ const url = new URL(value);
70
+ return url.port === "3456"
71
+ && ["127.0.0.1", "localhost", "host.docker.internal"].includes(url.hostname);
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ function isObject(value) {
78
+ return typeof value === "object" && value !== null && !Array.isArray(value);
79
+ }
@@ -5,6 +5,7 @@ import fastifyStatic from "@fastify/static";
5
5
  import { createArtifactService } from "./services/artifact-service.js";
6
6
  import { createClaudeAdapter } from "./adapters/claude-adapter.js";
7
7
  import { createCcrGatewayAdapter } from "./adapters/ccr-gateway-adapter.js";
8
+ import { createClaudeSettingsAdapter } from "./adapters/claude-settings-adapter.js";
8
9
  import { createCommandRunner } from "./adapters/command-runner.js";
9
10
  import { createCommandDispatcher } from "./services/command-dispatcher.js";
10
11
  import { createClaudeHookService } from "./services/claude-hook-service.js";
@@ -208,9 +209,11 @@ export function createDefaultServerDeps(options = {}) {
208
209
  const git = createGitAdapter(runner);
209
210
  const claude = createClaudeAdapter(runner);
210
211
  const appSettings = createAppSettingsService({ fs });
212
+ const claudeSettings = createClaudeSettingsAdapter({ fs });
211
213
  const ccrIntegration = createCcrIntegrationService({
212
214
  settings: appSettings,
213
- gateway: createCcrGatewayAdapter()
215
+ gateway: createCcrGatewayAdapter(),
216
+ restoreNativeClaudeSettings: () => claudeSettings.restoreNativeSettings()
214
217
  });
215
218
  const runtime = createNodePtyTerminalRuntime({ fs });
216
219
  const registry = createSessionRegistry();
@@ -1,6 +1,8 @@
1
1
  import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
2
+ import { fileURLToPath } from "node:url";
2
3
  import { VcmError } from "../errors.js";
3
4
  const DEFAULT_CACHE_TTL_MS = 10_000;
5
+ const DEFAULT_API_KEY_HELPER_PATH = fileURLToPath(new URL("../../../scripts/ccr-api-key-helper.mjs", import.meta.url));
4
6
  export function createCcrIntegrationService(deps) {
5
7
  const now = deps.now ?? (() => new Date());
6
8
  const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
@@ -65,6 +67,9 @@ export function createCcrIntegrationService(deps) {
65
67
  return {
66
68
  async initialize() {
67
69
  const settings = await deps.settings.getCcrIntegrationSettings();
70
+ if (settings.apiKey) {
71
+ await deps.restoreNativeClaudeSettings?.();
72
+ }
68
73
  if (settings.enabled) {
69
74
  await probe(true);
70
75
  }
@@ -97,6 +102,9 @@ export function createCcrIntegrationService(deps) {
97
102
  enabled: nextEnabled,
98
103
  apiKey: nextApiKey
99
104
  });
105
+ if (nextApiKey) {
106
+ await deps.restoreNativeClaudeSettings?.();
107
+ }
100
108
  if (clearApiKey || !nextEnabled) {
101
109
  cachedProbe = undefined;
102
110
  return buildStatus();
@@ -142,11 +150,13 @@ export function createCcrIntegrationService(deps) {
142
150
  hint: "Check that host CCR is running on port 3456 and exposes GPT-5.6 Sol."
143
151
  });
144
152
  }
145
- const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy, "host.docker.internal");
153
+ const gatewayBaseUrl = checked.result.baseUrl ?? CCR_GATEWAY_BASE_URL;
154
+ const gatewayHost = new URL(gatewayBaseUrl).hostname;
155
+ const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy, gatewayHost);
146
156
  return {
147
- ANTHROPIC_BASE_URL: CCR_GATEWAY_BASE_URL,
148
- ANTHROPIC_API_BASE_URL: CCR_GATEWAY_BASE_URL,
149
- CLAUDE_AGENT_API_BASE_URL: CCR_GATEWAY_BASE_URL,
157
+ ANTHROPIC_BASE_URL: gatewayBaseUrl,
158
+ ANTHROPIC_API_BASE_URL: gatewayBaseUrl,
159
+ CLAUDE_AGENT_API_BASE_URL: gatewayBaseUrl,
150
160
  ANTHROPIC_AUTH_TOKEN: undefined,
151
161
  ANTHROPIC_API_KEY: undefined,
152
162
  ANTHROPIC_MODEL: CCR_GPT_MODEL_ID,
@@ -157,6 +167,18 @@ export function createCcrIntegrationService(deps) {
157
167
  NO_PROXY: noProxy,
158
168
  no_proxy: noProxy
159
169
  };
170
+ },
171
+ async getLaunchSettingsOverride(model) {
172
+ const settings = await deps.settings.getCcrIntegrationSettings();
173
+ if (settings.apiKey) {
174
+ await deps.restoreNativeClaudeSettings?.();
175
+ }
176
+ if (!isCcrSessionModel(model)) {
177
+ return undefined;
178
+ }
179
+ return {
180
+ apiKeyHelper: buildApiKeyHelperCommand(deps.apiKeyHelperPath ?? DEFAULT_API_KEY_HELPER_PATH)
181
+ };
160
182
  }
161
183
  };
162
184
  }
@@ -193,3 +215,12 @@ export function mergeNoProxy(current, host) {
193
215
  }
194
216
  return values.join(",");
195
217
  }
218
+ export function buildApiKeyHelperCommand(helperPath) {
219
+ if (process.platform === "win32") {
220
+ return `${JSON.stringify(process.execPath)} ${JSON.stringify(helperPath)}`;
221
+ }
222
+ return `${quotePosixShell(process.execPath)} ${quotePosixShell(helperPath)}`;
223
+ }
224
+ function quotePosixShell(value) {
225
+ return `'${value.replaceAll("'", `'\\''`)}'`;
226
+ }
@@ -55,7 +55,10 @@ export function createSessionService(deps) {
55
55
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
56
56
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
57
57
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
58
- const modelEnvironment = await getModelLaunchEnvironment(model);
58
+ const [modelEnvironment, modelSettingsOverride] = await Promise.all([
59
+ getModelLaunchEnvironment(model),
60
+ getModelLaunchSettingsOverride(model)
61
+ ]);
59
62
  const resumeClaudeSessionId = launchMode === "resume"
60
63
  ? persisted?.claudeSessionId
61
64
  : undefined;
@@ -74,7 +77,7 @@ export function createSessionService(deps) {
74
77
  ? claudeTranscriptPath(taskRepoRoot, resumeClaudeSessionId)
75
78
  : undefined;
76
79
  const startCommand = {
77
- ...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
80
+ ...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
78
81
  cwd: taskRepoRoot
79
82
  };
80
83
  const runtimeSession = await deps.runtime.createSession({
@@ -160,7 +163,10 @@ export function createSessionService(deps) {
160
163
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
161
164
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
162
165
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
163
- const modelEnvironment = await getModelLaunchEnvironment(model);
166
+ const [modelEnvironment, modelSettingsOverride] = await Promise.all([
167
+ getModelLaunchEnvironment(model),
168
+ getModelLaunchSettingsOverride(model)
169
+ ]);
164
170
  const resumeClaudeSessionId = launchMode === "resume"
165
171
  ? persisted?.claudeSessionId
166
172
  : undefined;
@@ -191,7 +197,7 @@ export function createSessionService(deps) {
191
197
  ? claudeTranscriptPath(repoRoot, resumeClaudeSessionId)
192
198
  : undefined;
193
199
  const startCommand = {
194
- ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
200
+ ...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
195
201
  cwd: launchCwd
196
202
  };
197
203
  const runtimeSession = await deps.runtime.createSession({
@@ -270,7 +276,10 @@ export function createSessionService(deps) {
270
276
  const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
271
277
  const model = normalizeClaudeModel(input.model ?? persisted?.model);
272
278
  const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
273
- const modelEnvironment = await getModelLaunchEnvironment(model);
279
+ const [modelEnvironment, modelSettingsOverride] = await Promise.all([
280
+ getModelLaunchEnvironment(model),
281
+ getModelLaunchSettingsOverride(model)
282
+ ]);
274
283
  const resumeClaudeSessionId = launchMode === "resume"
275
284
  ? persisted?.claudeSessionId
276
285
  : undefined;
@@ -297,7 +306,7 @@ export function createSessionService(deps) {
297
306
  ? claudeTranscriptPath(repoRoot, resumeClaudeSessionId)
298
307
  : undefined;
299
308
  const startCommand = {
300
- ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
309
+ ...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
301
310
  cwd: launchCwd
302
311
  };
303
312
  const runtimeSession = await deps.runtime.createSession({
@@ -480,14 +489,17 @@ export function createSessionService(deps) {
480
489
  const permissionMode = normalizeClaudePermissionMode(session.permissionMode);
481
490
  const model = normalizeClaudeModel(session.model);
482
491
  const effort = normalizeClaudeEffort(session.effort);
483
- const modelEnvironment = await getModelLaunchEnvironment(model);
492
+ const [modelEnvironment, modelSettingsOverride] = await Promise.all([
493
+ getModelLaunchEnvironment(model),
494
+ getModelLaunchSettingsOverride(model)
495
+ ]);
484
496
  // Spawn (`claude --resume`) always anchors at the base repoRoot so resume works
485
497
  // even if the persisted task cwd was deleted. `--resume` then restores the
486
498
  // session's own last cwd (tracked on `session.cwd`), so the `/cd` migrate below
487
499
  // fires only when that restored cwd differs from the target worktree.
488
500
  const launchCwd = repoRoot;
489
501
  const startCommand = {
490
- ...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort),
502
+ ...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort, modelSettingsOverride),
491
503
  cwd: launchCwd
492
504
  };
493
505
  const runtimeSession = await deps.runtime.createSession({
@@ -561,6 +573,9 @@ export function createSessionService(deps) {
561
573
  }
562
574
  return deps.ccrIntegration.getLaunchEnvironment(model);
563
575
  }
576
+ async function getModelLaunchSettingsOverride(model) {
577
+ return deps.ccrIntegration?.getLaunchSettingsOverride(model);
578
+ }
564
579
  function isRuntimeSessionAlive(session) {
565
580
  if (!session || isExitedStatus(session.status) || session.pid === undefined) {
566
581
  return false;
@@ -45,7 +45,13 @@ export const CLAUDE_MODEL_OPTIONS = [
45
45
  available: true
46
46
  }
47
47
  ];
48
- export const CCR_GATEWAY_BASE_URL = "http://host.docker.internal:3456";
48
+ export const CCR_LOCAL_GATEWAY_BASE_URL = "http://127.0.0.1:3456";
49
+ export const CCR_CONTAINER_GATEWAY_BASE_URL = "http://host.docker.internal:3456";
50
+ export const CCR_GATEWAY_BASE_URL = CCR_CONTAINER_GATEWAY_BASE_URL;
51
+ export const CCR_GATEWAY_BASE_URLS = [
52
+ CCR_LOCAL_GATEWAY_BASE_URL,
53
+ CCR_CONTAINER_GATEWAY_BASE_URL
54
+ ];
49
55
  export const CCR_GPT_MODEL_ID = "Codex API/gpt-5.6-sol";
50
56
  export const CCR_GPT_SESSION_MODEL = `ccr:${CCR_GPT_MODEL_ID}`;
51
57
  export function isCcrSessionModel(model) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.7.14",
3
+ "version": "0.7.15",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import path from "node:path";
6
+
7
+ const dataDir = process.env.VCM_DATA_DIR?.trim()
8
+ ? path.resolve(process.env.VCM_DATA_DIR)
9
+ : path.join(homedir(), ".vcm");
10
+
11
+ try {
12
+ const settings = JSON.parse(await readFile(path.join(dataDir, "settings.json"), "utf8"));
13
+ const apiKey = settings?.ccr?.apiKey;
14
+ if (typeof apiKey !== "string" || !apiKey.trim()) {
15
+ process.exitCode = 1;
16
+ } else {
17
+ process.stdout.write(`${apiKey.trim()}\n`);
18
+ }
19
+ } catch {
20
+ process.exitCode = 1;
21
+ }