@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gakim-digital/dexter-bridge",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Local Companion bridge for the Dexter Framer plugin — runs Codex or Claude Code on your machine.",
5
5
  "type": "module",
6
6
  "bin": {
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 Mac, complete sign-in, then try again.';
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
- ? [path.join(process.env.APPDATA || '', 'npm'), path.join(process.env.LOCALAPPDATA || '', 'Programs')]
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(process.env.PATH || '');
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
- ...process.env,
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 || processEnvWithCliPath(platform);
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 || processEnvWithCliPath(platform);
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 || processEnvWithCliPath(platform);
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 available = checks.agents.filter((check) => check.ok);
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
- available
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
- deviceToken = readConfig(configDir).deviceToken;
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(config);
259
- const metadata = await availabilityMetadata(config);
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
  };