@gakim-digital/dexter-bridge 0.5.9 → 0.5.10
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 +16 -0
- package/package.json +1 -1
- package/src/agent.js +25 -9
- package/src/cli.js +48 -7
package/README.md
CHANGED
|
@@ -53,6 +53,22 @@ npx @gakim-digital/dexter-bridge doctor
|
|
|
53
53
|
npx @gakim-digital/dexter-bridge logout
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
On Windows, the bridge checks the native Claude Code install at
|
|
57
|
+
`%USERPROFILE%\.local\bin\claude.exe` in addition to `PATH`. Before pairing,
|
|
58
|
+
verify the same PowerShell window can run:
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
claude --version
|
|
62
|
+
claude auth status
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If Claude Code is installed in a custom directory, set its absolute path for
|
|
66
|
+
that PowerShell session before running the Dexter command:
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
$env:DEXTER_BRIDGE_CLAUDE_BIN = "C:\path\to\claude.exe"
|
|
70
|
+
```
|
|
71
|
+
|
|
56
72
|
`dry-run` verifies pairing, run polling, and event delivery without editing the
|
|
57
73
|
canvas. `claude-code` executes through its local CLI. Codex uses one persistent
|
|
58
74
|
`codex app-server` process for authentication, threads, usage, and cancellation:
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -102,7 +102,7 @@ export const AGENT_DEFINITIONS = {
|
|
|
102
102
|
|
|
103
103
|
export const AGENT_AUTHENTICATION_REQUIRED_CODE = 'DEXTER_AGENT_AUTHENTICATION_REQUIRED';
|
|
104
104
|
export const CLAUDE_AUTHENTICATION_REQUIRED_MESSAGE =
|
|
105
|
-
'Claude Code sign-in has expired. Run `claude auth login` on this
|
|
105
|
+
'Claude Code sign-in has expired. Run `claude auth login` on this computer, complete sign-in, then try again.';
|
|
106
106
|
|
|
107
107
|
function nowIso() {
|
|
108
108
|
return new Date().toISOString();
|
|
@@ -498,16 +498,28 @@ export function mergePathEntries(paths, platform = process.platform) {
|
|
|
498
498
|
.join(delimiter);
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
function processEnvWithCliPath(platform = process.platform) {
|
|
501
|
+
export function processEnvWithCliPath(baseEnv = process.env, platform = process.platform) {
|
|
502
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
503
|
+
const homeDirectory = platform === 'win32'
|
|
504
|
+
? String(
|
|
505
|
+
baseEnv.USERPROFILE
|
|
506
|
+
|| baseEnv.HOME
|
|
507
|
+
|| (process.platform === 'win32' ? os.homedir() : ''),
|
|
508
|
+
).trim()
|
|
509
|
+
: String(baseEnv.HOME || (platform === process.platform ? os.homedir() : '')).trim();
|
|
502
510
|
const fallbackPath = platform === 'win32'
|
|
503
|
-
? [
|
|
511
|
+
? [
|
|
512
|
+
homeDirectory ? pathApi.join(homeDirectory, '.local', 'bin') : '',
|
|
513
|
+
baseEnv.APPDATA ? pathApi.join(baseEnv.APPDATA, 'npm') : '',
|
|
514
|
+
baseEnv.LOCALAPPDATA ? pathApi.join(baseEnv.LOCALAPPDATA, 'Programs') : '',
|
|
515
|
+
]
|
|
504
516
|
: ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
|
505
|
-
const shellEnv = loginShellEnv();
|
|
506
|
-
const existingPath = String(
|
|
517
|
+
const shellEnv = platform === 'win32' ? {} : loginShellEnv();
|
|
518
|
+
const existingPath = String(baseEnv.PATH || '');
|
|
507
519
|
const mergedPath = mergePathEntries([shellEnv.PATH, existingPath, ...fallbackPath], platform);
|
|
508
520
|
return {
|
|
509
521
|
...shellEnv,
|
|
510
|
-
...
|
|
522
|
+
...baseEnv,
|
|
511
523
|
PATH: mergedPath,
|
|
512
524
|
};
|
|
513
525
|
}
|
|
@@ -600,6 +612,9 @@ export function selectAgentRuntime(inspections, definition, modelDefinition) {
|
|
|
600
612
|
return {
|
|
601
613
|
ok: false,
|
|
602
614
|
command: first?.command || definition.fallbackCommand,
|
|
615
|
+
code: first?.code === 'ENOENT'
|
|
616
|
+
? 'DEXTER_AGENT_NOT_FOUND'
|
|
617
|
+
: first?.code,
|
|
603
618
|
error: first?.error || `${definition.label} is not installed or could not be started.`,
|
|
604
619
|
candidates: inspections,
|
|
605
620
|
};
|
|
@@ -712,7 +727,7 @@ function runProcess(command, args, stdin, {
|
|
|
712
727
|
// output) so slow-but-streaming model turns are not killed mid-generation;
|
|
713
728
|
// hardDeadlineMs bounds total wall-clock time regardless of activity.
|
|
714
729
|
const hardDeadlineMs = Math.max(maxDurationMs || timeoutMs * 5, 10);
|
|
715
|
-
const childEnv = providedChildEnv ||
|
|
730
|
+
const childEnv = processEnvWithCliPath(providedChildEnv || process.env, platform);
|
|
716
731
|
const cwd = resolveAgentCwd(childEnv, process.cwd(), platform);
|
|
717
732
|
const invocation = processInvocation(command, args, childEnv, platform);
|
|
718
733
|
trace?.info('agent_process_spawn', {
|
|
@@ -974,7 +989,7 @@ async function callProviderAdapter(adapter, input, {
|
|
|
974
989
|
|
|
975
990
|
export async function resolveAgentRuntime(definition, modelDefinition, options = {}) {
|
|
976
991
|
const platform = options.platform || process.platform;
|
|
977
|
-
const childEnv = options.env ||
|
|
992
|
+
const childEnv = processEnvWithCliPath(options.env || process.env, platform);
|
|
978
993
|
const configuredCommand = commandFromEnv(definition.commandEnv, definition.fallbackCommand, childEnv);
|
|
979
994
|
const candidates = executableCandidates(
|
|
980
995
|
configuredCommand,
|
|
@@ -1000,6 +1015,7 @@ export async function resolveAgentRuntime(definition, modelDefinition, options =
|
|
|
1000
1015
|
return {
|
|
1001
1016
|
ok: false,
|
|
1002
1017
|
command,
|
|
1018
|
+
code: error?.code,
|
|
1003
1019
|
error: error?.message || String(error || ''),
|
|
1004
1020
|
};
|
|
1005
1021
|
}
|
|
@@ -1059,7 +1075,7 @@ export async function checkAgentAuthentication(agent, runtime, options = {}) {
|
|
|
1059
1075
|
}
|
|
1060
1076
|
|
|
1061
1077
|
const platform = options.platform || process.platform;
|
|
1062
|
-
const childEnv = options.env ||
|
|
1078
|
+
const childEnv = processEnvWithCliPath(options.env || process.env, platform);
|
|
1063
1079
|
const inspect = options.inspect || inspectClaudeAuthentication;
|
|
1064
1080
|
try {
|
|
1065
1081
|
const authentication = await inspect(runtime.command, { childEnv, platform });
|
package/src/cli.js
CHANGED
|
@@ -154,7 +154,8 @@ function agentEnvironment(config = {}, baseEnv = process.env) {
|
|
|
154
154
|
async function inspectAvailability(config = {}) {
|
|
155
155
|
try {
|
|
156
156
|
const checks = await checkAllAgents({ env: agentEnvironment(config) });
|
|
157
|
-
const
|
|
157
|
+
const allChecks = [...checks.agents, checks.dryRun];
|
|
158
|
+
const available = allChecks.filter((check) => check.ok);
|
|
158
159
|
return {
|
|
159
160
|
metadata: {
|
|
160
161
|
availableAgents: available.map((check) => check.agent).join(','),
|
|
@@ -165,19 +166,27 @@ async function inspectAvailability(config = {}) {
|
|
|
165
166
|
bridgeBuildFingerprint: BRIDGE_BUILD_FINGERPRINT,
|
|
166
167
|
},
|
|
167
168
|
agentCommands: Object.fromEntries(
|
|
168
|
-
|
|
169
|
+
checks.agents
|
|
170
|
+
.filter((check) => check.ok)
|
|
169
171
|
.filter((check) => typeof check.command === 'string' && check.command.trim())
|
|
170
172
|
.map((check) => [check.agent, check.command]),
|
|
171
173
|
),
|
|
174
|
+
agents: checks.agents,
|
|
175
|
+
dryRun: checks.dryRun,
|
|
172
176
|
};
|
|
173
177
|
} catch {
|
|
174
178
|
return {
|
|
175
179
|
metadata: {
|
|
180
|
+
availableAgents: '',
|
|
181
|
+
availableModels: '',
|
|
182
|
+
agentVersions: '',
|
|
176
183
|
bridgeCapabilities: BRIDGE_CAPABILITIES.join(','),
|
|
177
184
|
bridgeVersion: BRIDGE_VERSION,
|
|
178
185
|
bridgeBuildFingerprint: BRIDGE_BUILD_FINGERPRINT,
|
|
179
186
|
},
|
|
180
187
|
agentCommands: {},
|
|
188
|
+
agents: [],
|
|
189
|
+
dryRun: null,
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
192
|
}
|
|
@@ -186,6 +195,31 @@ async function availabilityMetadata(config = {}) {
|
|
|
186
195
|
return (await inspectAvailability(config)).metadata;
|
|
187
196
|
}
|
|
188
197
|
|
|
198
|
+
function selectedAgentCheck(availability, agent) {
|
|
199
|
+
if (agent === 'dry-run') return availability.dryRun;
|
|
200
|
+
return availability.agents.find((check) => check.agent === agent) || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function requireAvailableAgent(availability, agent, platform = process.platform) {
|
|
204
|
+
const check = selectedAgentCheck(availability, agent);
|
|
205
|
+
if (check?.ok) return check;
|
|
206
|
+
|
|
207
|
+
const label = agent === 'claude-code' ? 'Claude Code' : agent === 'codex' ? 'Codex' : 'The selected agent';
|
|
208
|
+
let message = check?.error || `${label} is not installed or could not be started.`;
|
|
209
|
+
if (agent === 'claude-code' && check?.status === 'authentication_required') {
|
|
210
|
+
message = check.error || 'Claude Code is not signed in. Run `claude auth login`, then try again.';
|
|
211
|
+
} else if (agent === 'claude-code' && (check?.code === 'DEXTER_AGENT_NOT_FOUND' || !check)) {
|
|
212
|
+
message = platform === 'win32'
|
|
213
|
+
? 'Claude Code was not found. In PowerShell, run `claude --version`. Dexter also checks `%USERPROFILE%\\.local\\bin\\claude.exe`. If Claude is installed elsewhere, set `DEXTER_BRIDGE_CLAUDE_BIN` to its full path, then run the Dexter connection command again.'
|
|
214
|
+
: 'Claude Code was not found. Run `claude --version` in this terminal. If Claude is installed elsewhere, set `DEXTER_BRIDGE_CLAUDE_BIN` to its full path, then run the Dexter connection command again.';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const error = new Error(message);
|
|
218
|
+
error.code = check?.code || 'DEXTER_AGENT_NOT_FOUND';
|
|
219
|
+
error.exitCode = 2;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
|
|
189
223
|
async function pairCommand({ apiBaseUrl, args, flags, configDir }) {
|
|
190
224
|
const codeOrToken = args[0];
|
|
191
225
|
if (!codeOrToken) throw new Error('Pairing code or token is required.');
|
|
@@ -194,6 +228,7 @@ async function pairCommand({ apiBaseUrl, args, flags, configDir }) {
|
|
|
194
228
|
const agent = resolveAgentName({ flagValue: flags.agent, config });
|
|
195
229
|
const model = resolveCompanionModelName({ flagValue: flags.model, config, agent });
|
|
196
230
|
const availability = await inspectAvailability(config);
|
|
231
|
+
requireAvailableAgent(availability, agent);
|
|
197
232
|
const result = await claimPairing(apiBaseUrl, {
|
|
198
233
|
pairingCode: isToken ? undefined : codeOrToken,
|
|
199
234
|
pairingToken: isToken ? codeOrToken : undefined,
|
|
@@ -239,6 +274,7 @@ async function statusCommand({ apiBaseUrl, config, configDir }) {
|
|
|
239
274
|
}
|
|
240
275
|
|
|
241
276
|
async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
277
|
+
let activeConfig = config;
|
|
242
278
|
let deviceToken = config.deviceToken;
|
|
243
279
|
if (!deviceToken) {
|
|
244
280
|
// First run: pair interactively so `npx @gakim-digital/dexter-bridge` alone is
|
|
@@ -249,14 +285,17 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
249
285
|
const code = await promptForPairingCode();
|
|
250
286
|
if (!code) requireDeviceToken(config);
|
|
251
287
|
await pairCommand({ apiBaseUrl, args: [code], flags, configDir });
|
|
252
|
-
|
|
288
|
+
activeConfig = readConfig(configDir);
|
|
289
|
+
deviceToken = activeConfig.deviceToken;
|
|
253
290
|
}
|
|
254
|
-
const agent = resolveAgentName({ flagValue: flags.agent, config });
|
|
255
|
-
const model = resolveCompanionModelName({ flagValue: flags.model, config, agent });
|
|
291
|
+
const agent = resolveAgentName({ flagValue: flags.agent, config: activeConfig });
|
|
292
|
+
const model = resolveCompanionModelName({ flagValue: flags.model, config: activeConfig, agent });
|
|
256
293
|
const waitMs = Number(flags['wait-ms'] || 25000);
|
|
257
294
|
const once = Boolean(flags.once);
|
|
258
|
-
const bridgeEnv = agentEnvironment(
|
|
259
|
-
const
|
|
295
|
+
const bridgeEnv = agentEnvironment(activeConfig);
|
|
296
|
+
const availability = await inspectAvailability(activeConfig);
|
|
297
|
+
requireAvailableAgent(availability, agent);
|
|
298
|
+
const metadata = availability.metadata;
|
|
260
299
|
const pollLogger = createRunLogger({ runId: 'bridge-poll' });
|
|
261
300
|
const providerAdapters = new Map();
|
|
262
301
|
const providerAdapterForAgent = (runAgent) => {
|
|
@@ -390,5 +429,7 @@ export const __private__ = {
|
|
|
390
429
|
isInvalidPairingError,
|
|
391
430
|
parseArgv,
|
|
392
431
|
pollBackoffMs,
|
|
432
|
+
requireAvailableAgent,
|
|
433
|
+
selectedAgentCheck,
|
|
393
434
|
usage,
|
|
394
435
|
};
|