@vanillagreen/pi-claude-bridge 1.6.2 → 1.8.0
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 +33 -2
- package/bundle/index.js +25449 -19866
- package/package.json +5 -5
- package/src/agents-md.ts +16 -6
- package/src/auth-presence.ts +158 -0
- package/src/config.ts +75 -3
- package/src/index.ts +443 -62
- package/src/prompt-context.ts +3 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-claude-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Pi provider bridge that runs Claude Code through the Claude Agent SDK, with opt-in forwarding for Pi prompt context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
146
|
-
"@anthropic-ai/sdk": "0.
|
|
145
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.215",
|
|
146
|
+
"@anthropic-ai/sdk": "^0.112.4",
|
|
147
147
|
"cc-session-io": "^0.3.1",
|
|
148
148
|
"change-case": "^5.4.4"
|
|
149
149
|
},
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"@earendil-works/pi-coding-agent": "*"
|
|
153
153
|
},
|
|
154
154
|
"devDependencies": {
|
|
155
|
-
"@earendil-works/pi-ai": "^0.
|
|
156
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
155
|
+
"@earendil-works/pi-ai": "^0.80.10",
|
|
156
|
+
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
157
157
|
"@types/node": "^24.3.0",
|
|
158
158
|
"esbuild": "^0.28.0",
|
|
159
159
|
"tsx": "^4.21.0",
|
package/src/agents-md.ts
CHANGED
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Pi uses AGENTS.md for long-lived instructions; Claude Code reads the same
|
|
4
4
|
// content under "# CLAUDE.md". We walk up from cwd looking for AGENTS.md,
|
|
5
|
-
// fall back to ~/.pi/agent/AGENTS.md
|
|
5
|
+
// fall back to <piUserDir>/AGENTS.md (~/.pi/agent/AGENTS.md unless
|
|
6
|
+
// PI_CODING_AGENT_DIR points elsewhere), and rewrite pi-specific references
|
|
6
7
|
// (~/.pi, .pi/, .pi, pi) to their Claude Code equivalents so any paths or
|
|
7
8
|
// references in the file still resolve inside the CC subprocess.
|
|
9
|
+
//
|
|
10
|
+
// In isolated mode (CLAUDE_BRIDGE_ISOLATED=1) the cwd walk is disabled: only
|
|
11
|
+
// the piUserDir file is consulted, so a host app that owns the agent dir owns
|
|
12
|
+
// the full instruction surface.
|
|
8
13
|
|
|
9
14
|
import { existsSync, readFileSync } from "fs";
|
|
10
|
-
import { homedir } from "os";
|
|
11
15
|
import { dirname, join, resolve } from "path";
|
|
16
|
+
import { isolatedFromEnv, piUserDir } from "./config.js";
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
function globalAgentsPath(): string {
|
|
19
|
+
return join(piUserDir(), "AGENTS.md");
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
export function resolveAgentsMdPath(): string | undefined {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
if (!isolatedFromEnv()) {
|
|
24
|
+
const fromCwd = findAgentsMdInParents(process.cwd());
|
|
25
|
+
if (fromCwd) return fromCwd;
|
|
26
|
+
}
|
|
27
|
+
const globalPath = globalAgentsPath();
|
|
28
|
+
if (existsSync(globalPath)) return globalPath;
|
|
19
29
|
return undefined;
|
|
20
30
|
}
|
|
21
31
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// --- Claude credential presence (availability honesty) ---
|
|
2
|
+
//
|
|
3
|
+
// The bridge may only advertise claude-bridge models when the machine actually
|
|
4
|
+
// has Claude credentials the Claude Agent SDK can authenticate with. Otherwise
|
|
5
|
+
// pi's ModelRegistry.hasConfiguredAuth() would treat the dummy `apiKey:
|
|
6
|
+
// "not-used"` as "configured" and the provider would look connected while every
|
|
7
|
+
// request fails at spawn time.
|
|
8
|
+
//
|
|
9
|
+
// This module answers two pure questions used to gate registration:
|
|
10
|
+
// 1. hasClaudeCredentials() — are real credentials present RIGHT NOW?
|
|
11
|
+
// 2. decideRegistration() — given credential presence + the primary-instance
|
|
12
|
+
// / stream-guard tokens, should we register / unregister / do nothing?
|
|
13
|
+
//
|
|
14
|
+
// SECURITY: this module only ever checks for the EXISTENCE of credentials — a
|
|
15
|
+
// file's presence, an env var being non-empty, a settings key being a non-empty
|
|
16
|
+
// string. It NEVER opens or logs `.credentials.json`, and where it must parse
|
|
17
|
+
// `settings.json` (for apiKeyHelper) it reads only whether the key is a
|
|
18
|
+
// non-empty string and never logs its value. Credential CONTENTS are never read
|
|
19
|
+
// or logged.
|
|
20
|
+
|
|
21
|
+
import { existsSync, readFileSync } from "fs";
|
|
22
|
+
import { homedir, platform as osPlatform } from "os";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the Claude config directory the same way the bundled cc-session-io
|
|
27
|
+
* (getClaudeDir) and Claude Code itself resolve it: an explicit
|
|
28
|
+
* CLAUDE_CONFIG_DIR wins, otherwise ~/.claude.
|
|
29
|
+
*
|
|
30
|
+
* Deliberate divergence from cc-session-io's plain `env ?? default`: we treat a
|
|
31
|
+
* SET-BUT-EMPTY/whitespace CLAUDE_CONFIG_DIR as unset and fall back to ~/.claude
|
|
32
|
+
* (an empty string would otherwise resolve credential probes to the process cwd
|
|
33
|
+
* root). The returned value is trimmed so downstream joins never carry stray
|
|
34
|
+
* whitespace.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveClaudeConfigDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
37
|
+
const configured = env.CLAUDE_CONFIG_DIR;
|
|
38
|
+
if (typeof configured === "string" && configured.trim().length > 0) return configured.trim();
|
|
39
|
+
return join(homedir(), ".claude");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function nonEmptyEnv(value: string | undefined): boolean {
|
|
43
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Matches how Claude Code interprets its boolean provider-routing env flags:
|
|
47
|
+
// only "1" / "true" (case-insensitive) enable them.
|
|
48
|
+
function envTruthy(value: string | undefined): boolean {
|
|
49
|
+
const v = value?.trim().toLowerCase();
|
|
50
|
+
return v === "1" || v === "true";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* True when `${configDir}/settings.json` exists, parses as JSON, and carries a
|
|
55
|
+
* non-empty `apiKeyHelper` string (an enterprise/custom auth command that
|
|
56
|
+
* produces a key). Parse/read errors are tolerated as "not present". The helper
|
|
57
|
+
* VALUE is never logged — only its presence/non-emptiness is used.
|
|
58
|
+
*/
|
|
59
|
+
function hasApiKeyHelper(configDir: string): boolean {
|
|
60
|
+
try {
|
|
61
|
+
const settingsPath = join(configDir, "settings.json");
|
|
62
|
+
if (!existsSync(settingsPath)) return false;
|
|
63
|
+
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { apiKeyHelper?: unknown };
|
|
64
|
+
return typeof parsed?.apiKeyHelper === "string" && parsed.apiKeyHelper.trim().length > 0;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* True when real Claude credentials are present in any location the Claude Agent
|
|
72
|
+
* SDK would authenticate from, WITHOUT reading credential contents. Checked in
|
|
73
|
+
* cheap-first order:
|
|
74
|
+
* - env: non-empty CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY /
|
|
75
|
+
* ANTHROPIC_AUTH_TOKEN, or any truthy cloud-provider routing flag the Claude
|
|
76
|
+
* Code SDK recognizes — CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY /
|
|
77
|
+
* _ANTHROPIC_AWS / _MANTLE (such routing needs no local key file);
|
|
78
|
+
* - `.credentials.json` in the resolved config dir (existence only — the file
|
|
79
|
+
* is never opened; written mode 0600 by `claude login`);
|
|
80
|
+
* - `settings.json` apiKeyHelper (presence only — see hasApiKeyHelper).
|
|
81
|
+
*
|
|
82
|
+
* PLATFORM ASYMMETRY: on macOS the `claude` CLI stores OAuth tokens in the login
|
|
83
|
+
* Keychain, NOT in `.credentials.json`, so file-absence is NOT evidence of
|
|
84
|
+
* logged-out and we cannot cheaply/safely probe the Keychain here. On darwin,
|
|
85
|
+
* when no other signal is present, we default to credentialed=true — preserving
|
|
86
|
+
* the pre-fix "always available" behavior for Mac subscription users. Honesty
|
|
87
|
+
* enforcement therefore applies on Linux/Windows, where `.credentials.json`
|
|
88
|
+
* existence is an observable, truthful proxy (empirically, `claude auth logout`
|
|
89
|
+
* unlinks it).
|
|
90
|
+
*/
|
|
91
|
+
export function hasClaudeCredentials(
|
|
92
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
93
|
+
platform: NodeJS.Platform = osPlatform(),
|
|
94
|
+
): boolean {
|
|
95
|
+
if (nonEmptyEnv(env.CLAUDE_CODE_OAUTH_TOKEN)) return true;
|
|
96
|
+
if (nonEmptyEnv(env.ANTHROPIC_API_KEY)) return true;
|
|
97
|
+
if (nonEmptyEnv(env.ANTHROPIC_AUTH_TOKEN)) return true;
|
|
98
|
+
if (envTruthy(env.CLAUDE_CODE_USE_BEDROCK)) return true;
|
|
99
|
+
if (envTruthy(env.CLAUDE_CODE_USE_VERTEX)) return true;
|
|
100
|
+
if (envTruthy(env.CLAUDE_CODE_USE_FOUNDRY)) return true;
|
|
101
|
+
if (envTruthy(env.CLAUDE_CODE_USE_ANTHROPIC_AWS)) return true;
|
|
102
|
+
if (envTruthy(env.CLAUDE_CODE_USE_MANTLE)) return true;
|
|
103
|
+
|
|
104
|
+
const configDir = resolveClaudeConfigDir(env);
|
|
105
|
+
if (existsSync(join(configDir, ".credentials.json"))) return true;
|
|
106
|
+
if (hasApiKeyHelper(configDir)) return true;
|
|
107
|
+
|
|
108
|
+
if (platform === "darwin") return true;
|
|
109
|
+
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Snapshot of the inputs to a registration decision.
|
|
115
|
+
*
|
|
116
|
+
* The bridge keeps two process-global tokens (Symbol.for): a PRIMARY-instance
|
|
117
|
+
* token, claimed unconditionally by the first-loaded module instance, and the
|
|
118
|
+
* stream-guard token holding the registered instance's streamSimple. ONLY the
|
|
119
|
+
* primary instance may ever register/unregister or claim the stream guard — this
|
|
120
|
+
* prevents a subagent module reload (a fresh, non-primary instance) from
|
|
121
|
+
* stealing ownership and registering ITS streamSimple, which would split-brain
|
|
122
|
+
* the shared session/ctx and break tool-result delivery.
|
|
123
|
+
*/
|
|
124
|
+
export interface RegistrationState {
|
|
125
|
+
/** Does the machine have Claude credentials right now? */
|
|
126
|
+
credentialed: boolean;
|
|
127
|
+
/** Is THIS module instance the primary (first-loaded) instance? */
|
|
128
|
+
isPrimary: boolean;
|
|
129
|
+
/** Has this instance already registered (owns the stream guard)? */
|
|
130
|
+
registered: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type RegistrationDecision = "register" | "unregister" | "noop";
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Pure decision for extension load, every session_start re-check, and the
|
|
137
|
+
* pre-spawn fail-fast path.
|
|
138
|
+
*
|
|
139
|
+
* Rules:
|
|
140
|
+
* - Not the primary instance → NOOP (never touch registration).
|
|
141
|
+
* - Primary + credentialed + not registered → REGISTER (claim guard + register).
|
|
142
|
+
* - Primary + credentialed + already registered → NOOP.
|
|
143
|
+
* - Primary + uncredentialed → UNREGISTER (defensive).
|
|
144
|
+
*
|
|
145
|
+
* The uncredentialed primary always returns UNREGISTER rather than NOOP:
|
|
146
|
+
* pi.unregisterProvider is idempotent ("Has no effect if the provider was never
|
|
147
|
+
* registered"), and a defensive call is the ONLY way to retract a registration
|
|
148
|
+
* that survived a /reload — the ModelRegistry's registeredProviders is a
|
|
149
|
+
* process-lifetime Map and module reload does NOT clear it. (At extension-load
|
|
150
|
+
* time this defensive unregister only filters the pending-registration queue and
|
|
151
|
+
* cannot mutate the persistent registry; the authoritative retraction happens on
|
|
152
|
+
* the post-load session_start re-check — see applyProviderRegistration.)
|
|
153
|
+
*/
|
|
154
|
+
export function decideRegistration(state: RegistrationState): RegistrationDecision {
|
|
155
|
+
if (!state.isPrimary) return "noop";
|
|
156
|
+
if (state.credentialed) return state.registered ? "noop" : "register";
|
|
157
|
+
return "unregister";
|
|
158
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -13,6 +13,14 @@ export type BridgeEffortLevel = "low" | "medium" | "high" | "xhigh" | "max";
|
|
|
13
13
|
|
|
14
14
|
const VALID_EFFORT_LEVELS = new Set<BridgeEffortLevel>(["low", "medium", "high", "xhigh", "max"]);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Per-session control over claude.ai connector WRITE tools when connectors are
|
|
18
|
+
* enabled. `deny` (default) hides Gmail/Calendar/Drive mutating tools so
|
|
19
|
+
* connector chat sessions are read-only; `allow` exposes them (used only by the
|
|
20
|
+
* one-shot approved-write executor). Reads are always available.
|
|
21
|
+
*/
|
|
22
|
+
export type ConnectorWriteMode = "deny" | "allow";
|
|
23
|
+
|
|
16
24
|
export interface Config {
|
|
17
25
|
enabled?: boolean;
|
|
18
26
|
/** Low-level Claude Agent SDK plumbing. Most users won't need these. */
|
|
@@ -28,6 +36,27 @@ export interface Config {
|
|
|
28
36
|
settingSources?: SettingSource[];
|
|
29
37
|
strictMcpConfig?: boolean;
|
|
30
38
|
pathToClaudeCodeExecutable?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Expose the authenticated Claude account's claude.ai cloud MCP
|
|
41
|
+
* connectors (Gmail / Google Calendar / Google Drive, etc.) to the model.
|
|
42
|
+
* Off by default so Pi owns tool execution and tokens stay lean. Also
|
|
43
|
+
* settable via the CLAUDE_BRIDGE_ENABLE_CONNECTORS env var (env OR config
|
|
44
|
+
* enables it). See docs/plans/claude-bridge-google-connectors.md.
|
|
45
|
+
*/
|
|
46
|
+
enableConnectors?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* When connectors are enabled, whether their WRITE tools
|
|
49
|
+
* (create/update/delete/label/etc.) are exposed. Defaults to `deny`
|
|
50
|
+
* (read-only), enforced two ways: known write tools are removed from the
|
|
51
|
+
* model's context (disallowedTools by exact id), and a PreToolUse hook
|
|
52
|
+
* blocks any connector write tool by name prefix at call time (covers
|
|
53
|
+
* future write tools). `allow` disables both — intended ONLY for a
|
|
54
|
+
* one-shot approved-write executor process. Also settable via
|
|
55
|
+
* CLAUDE_BRIDGE_CONNECTOR_WRITE=deny|allow (env wins over config). Any
|
|
56
|
+
* value but exact `allow` is treated as `deny`. Ignored when connectors
|
|
57
|
+
* are disabled.
|
|
58
|
+
*/
|
|
59
|
+
connectorWriteMode?: ConnectorWriteMode;
|
|
31
60
|
};
|
|
32
61
|
/** Extra Pi context forwarded to Claude Code on top of AGENTS.md + skills. */
|
|
33
62
|
promptContext?: {
|
|
@@ -46,10 +75,29 @@ function expandHome(input: string): string {
|
|
|
46
75
|
return input;
|
|
47
76
|
}
|
|
48
77
|
|
|
49
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The Pi agent config dir: `PI_CODING_AGENT_DIR` when set, else `~/.pi/agent`.
|
|
80
|
+
* Every bridge default that used to hardcode `~/.pi/agent` routes through this
|
|
81
|
+
* so a host app that owns the agent dir owns those paths too.
|
|
82
|
+
*/
|
|
83
|
+
export function piUserDir(): string {
|
|
50
84
|
return resolve(expandHome(process.env.PI_CODING_AGENT_DIR?.trim() || "~/.pi/agent"));
|
|
51
85
|
}
|
|
52
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Isolated mode (`CLAUDE_BRIDGE_ISOLATED=1`): a host app embedding the bridge
|
|
89
|
+
* declares that nothing outside its explicitly configured dirs may be read.
|
|
90
|
+
* Disables every cwd/home discovery fallback — the cwd AGENTS.md walk, project
|
|
91
|
+
* `.pi/` settings + claude-bridge.json, project APPEND_SYSTEM.md, and the
|
|
92
|
+
* `$PATH` claude executable search. Reads stay confined to `piUserDir()` (i.e.
|
|
93
|
+
* `PI_CODING_AGENT_DIR`) and the explicitly configured executable path.
|
|
94
|
+
* Default (unset) behavior for normal pi CLI users is unchanged.
|
|
95
|
+
*/
|
|
96
|
+
export function isolatedFromEnv(): boolean {
|
|
97
|
+
const v = (process.env.CLAUDE_BRIDGE_ISOLATED ?? "").trim().toLowerCase();
|
|
98
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
99
|
+
}
|
|
100
|
+
|
|
53
101
|
function asRecord(value: unknown): SettingsRecord | undefined {
|
|
54
102
|
return value && typeof value === "object" && !Array.isArray(value) ? value as SettingsRecord : undefined;
|
|
55
103
|
}
|
|
@@ -93,6 +141,10 @@ function projectTrustRegistry(): ProjectTrustRegistry {
|
|
|
93
141
|
|
|
94
142
|
export function recordProjectTrust(ctx: { cwd?: string; isProjectTrusted?: () => boolean }): void {
|
|
95
143
|
if (!ctx.cwd) return;
|
|
144
|
+
// Isolated mode never reads project config, so recording trust would only
|
|
145
|
+
// run the cwd-ancestor `.pi/settings.json` walk (a filesystem probe outside
|
|
146
|
+
// the host-owned dirs) for a result nothing consumes. Skip it entirely.
|
|
147
|
+
if (isolatedFromEnv()) return;
|
|
96
148
|
let trusted = true;
|
|
97
149
|
try {
|
|
98
150
|
trusted = ctx.isProjectTrusted?.() === true;
|
|
@@ -111,6 +163,7 @@ function projectSettingsTrusted(settingsPath: string): boolean {
|
|
|
111
163
|
|
|
112
164
|
function settingsPaths(cwd: string): string[] {
|
|
113
165
|
const user = join(piUserDir(), "settings.json");
|
|
166
|
+
if (isolatedFromEnv()) return [user];
|
|
114
167
|
const project = projectSettingsPath(cwd);
|
|
115
168
|
return projectSettingsTrusted(project) ? [user, project] : [user];
|
|
116
169
|
}
|
|
@@ -155,6 +208,13 @@ function hasOwn(raw: SettingsRecord, key: string): boolean {
|
|
|
155
208
|
return Object.prototype.hasOwnProperty.call(raw, key);
|
|
156
209
|
}
|
|
157
210
|
|
|
211
|
+
export function normalizeConnectorWriteMode(value: unknown): ConnectorWriteMode | undefined {
|
|
212
|
+
if (typeof value !== "string") return undefined;
|
|
213
|
+
const normalized = value.trim().toLowerCase();
|
|
214
|
+
if (normalized === "deny" || normalized === "allow") return normalized;
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
158
218
|
export function normalizeEffortLevel(value: unknown): BridgeEffortLevel | undefined {
|
|
159
219
|
if (typeof value !== "string") return undefined;
|
|
160
220
|
const normalized = value.trim().toLowerCase();
|
|
@@ -195,6 +255,13 @@ function normalizeProviderConfig(provider: Config["provider"] | undefined): Conf
|
|
|
195
255
|
const modelEffortOverrides = normalizeModelEffortOverrides(raw.modelEffortOverrides);
|
|
196
256
|
if (modelEffortOverrides) out.modelEffortOverrides = modelEffortOverrides;
|
|
197
257
|
else delete out.modelEffortOverrides;
|
|
258
|
+
// Fail closed: legacy config files are merged raw, so an unvalidated
|
|
259
|
+
// connectorWriteMode (e.g. "Deny", "read-only", true) must not slip through as
|
|
260
|
+
// a truthy non-"allow" value. Drop anything that isn't exactly deny/allow so
|
|
261
|
+
// the resolver falls back to the default deny.
|
|
262
|
+
const connectorWriteMode = normalizeConnectorWriteMode(raw.connectorWriteMode);
|
|
263
|
+
if (connectorWriteMode) out.connectorWriteMode = connectorWriteMode;
|
|
264
|
+
else delete out.connectorWriteMode;
|
|
198
265
|
return out;
|
|
199
266
|
}
|
|
200
267
|
|
|
@@ -216,6 +283,10 @@ function managerToConfig(raw: SettingsRecord): Partial<Config> {
|
|
|
216
283
|
}
|
|
217
284
|
const strictMcpConfig = boolFrom(raw, "strictMcpConfig");
|
|
218
285
|
if (strictMcpConfig !== undefined) provider.strictMcpConfig = strictMcpConfig;
|
|
286
|
+
const enableConnectors = boolFrom(raw, "enableConnectors");
|
|
287
|
+
if (enableConnectors !== undefined) provider.enableConnectors = enableConnectors;
|
|
288
|
+
const connectorWriteMode = normalizeConnectorWriteMode(raw.connectorWriteMode);
|
|
289
|
+
if (connectorWriteMode) provider.connectorWriteMode = connectorWriteMode;
|
|
219
290
|
const claudePath = stringFrom(raw, "pathToClaudeCodeExecutable");
|
|
220
291
|
if (claudePath) provider.pathToClaudeCodeExecutable = claudePath;
|
|
221
292
|
|
|
@@ -237,8 +308,9 @@ function managerToConfig(raw: SettingsRecord): Partial<Config> {
|
|
|
237
308
|
|
|
238
309
|
export function loadConfig(cwd: string): Config {
|
|
239
310
|
const global = tryParseJson(join(piUserDir(), "claude-bridge.json"));
|
|
240
|
-
const
|
|
241
|
-
const
|
|
311
|
+
const isolated = isolatedFromEnv();
|
|
312
|
+
const projectSettings = isolated ? undefined : projectSettingsPath(cwd);
|
|
313
|
+
const trustedProject = projectSettings !== undefined && projectSettingsTrusted(projectSettings);
|
|
242
314
|
const project = trustedProject ? tryParseJson(join(dirname(projectSettings), "claude-bridge.json")) : {};
|
|
243
315
|
const manager = managerToConfig(readManagerConfig(cwd));
|
|
244
316
|
const provider = normalizeProviderConfig({ ...global.provider, ...project.provider, ...manager.provider });
|