agent-relay 2.1.23-beta.0 → 2.1.23-beta.2

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.
@@ -24,7 +24,7 @@ import { RelayPtyOrchestrator, getTmuxPath } from '@agent-relay/wrapper';
24
24
  import { AgentSpawner, readWorkersMetadata, getWorkerLogsDir, selectShadowCli, ensureMcpPermissions } from '@agent-relay/bridge';
25
25
  import { generateAgentName, checkForUpdatesInBackground, checkForUpdates } from '@agent-relay/utils';
26
26
  import { getShadowForAgent, getProjectPaths, loadRuntimeConfig } from '@agent-relay/config';
27
- import { CLI_AUTH_CONFIG } from '@agent-relay/config/cli-auth-config';
27
+ import { CLI_AUTH_CONFIG, stripAnsiCodes } from '@agent-relay/config/cli-auth-config';
28
28
  import { createStorageAdapter } from '@agent-relay/storage/adapter';
29
29
  import { initTelemetry, track, enableTelemetry, disableTelemetry, getStatus, isDisabledByEnv, } from '@agent-relay/telemetry';
30
30
  import { installMcpConfig } from '@agent-relay/mcp';
@@ -3818,6 +3818,9 @@ program
3818
3818
  sshClient.end();
3819
3819
  process.exit(1);
3820
3820
  }
3821
+ // Get success/error patterns from CLI_AUTH_CONFIG for auto-detection
3822
+ const successPatterns = providerConfig.successPatterns || [];
3823
+ const errorPatterns = providerConfig.errorPatterns || [];
3821
3824
  const execInteractive = async (cmd, timeoutMs) => {
3822
3825
  return await new Promise((resolve, reject) => {
3823
3826
  const cols = process.stdout.columns || 80;
@@ -3828,6 +3831,8 @@ program
3828
3831
  return reject(err);
3829
3832
  let exitCode = null;
3830
3833
  let exitSignal = null;
3834
+ let authDetected = false;
3835
+ let outputBuffer = ''; // Rolling buffer for pattern matching
3831
3836
  const stdin = process.stdin;
3832
3837
  const stdout = process.stdout;
3833
3838
  const stderr = process.stderr;
@@ -3843,8 +3848,50 @@ program
3843
3848
  stream.write(data);
3844
3849
  };
3845
3850
  stdin.on('data', onStdinData);
3851
+ const cleanup = () => {
3852
+ stdin.off('data', onStdinData);
3853
+ stdout.off('resize', onResize);
3854
+ try {
3855
+ stdin.setRawMode?.(wasRaw);
3856
+ }
3857
+ catch {
3858
+ // ignore
3859
+ }
3860
+ stdin.pause();
3861
+ };
3862
+ // Auto-close the session when auth success is detected
3863
+ const closeOnAuthSuccess = () => {
3864
+ authDetected = true;
3865
+ // Brief delay so the user sees the success message
3866
+ setTimeout(() => {
3867
+ cleanup();
3868
+ clearTimeout(timer);
3869
+ try {
3870
+ stream.close();
3871
+ }
3872
+ catch {
3873
+ // ignore
3874
+ }
3875
+ }, 1500);
3876
+ };
3846
3877
  stream.on('data', (data) => {
3847
3878
  stdout.write(data);
3879
+ // Accumulate output for pattern matching (keep last 2KB to avoid memory growth)
3880
+ const text = data.toString();
3881
+ outputBuffer += text;
3882
+ if (outputBuffer.length > 2048) {
3883
+ outputBuffer = outputBuffer.slice(-2048);
3884
+ }
3885
+ // Check for auth success patterns
3886
+ if (!authDetected && successPatterns.length > 0) {
3887
+ const clean = stripAnsiCodes(outputBuffer);
3888
+ for (const pattern of successPatterns) {
3889
+ if (pattern.test(clean)) {
3890
+ closeOnAuthSuccess();
3891
+ break;
3892
+ }
3893
+ }
3894
+ }
3848
3895
  });
3849
3896
  stream.stderr.on('data', (data) => {
3850
3897
  stderr.write(data);
@@ -3858,16 +3905,6 @@ program
3858
3905
  }
3859
3906
  };
3860
3907
  stdout.on('resize', onResize);
3861
- const cleanup = () => {
3862
- stdin.off('data', onStdinData);
3863
- stdout.off('resize', onResize);
3864
- try {
3865
- stdin.setRawMode?.(wasRaw);
3866
- }
3867
- catch {
3868
- // ignore
3869
- }
3870
- };
3871
3908
  const timer = setTimeout(() => {
3872
3909
  cleanup();
3873
3910
  try {
@@ -3887,7 +3924,7 @@ program
3887
3924
  stream.on('close', () => {
3888
3925
  clearTimeout(timer);
3889
3926
  cleanup();
3890
- resolve({ exitCode, exitSignal });
3927
+ resolve({ exitCode, exitSignal, authDetected });
3891
3928
  });
3892
3929
  stream.on('error', (e) => {
3893
3930
  clearTimeout(timer);
@@ -3901,7 +3938,7 @@ program
3901
3938
  let execError = null;
3902
3939
  try {
3903
3940
  console.log(yellow('Starting interactive authentication...'));
3904
- console.log(dim('Follow the prompts below. Press Ctrl+C in the remote CLI to abort.'));
3941
+ console.log(dim('Follow the prompts below. The session will close automatically when auth completes.'));
3905
3942
  console.log('');
3906
3943
  execResult = await execInteractive(remoteCommand, TIMEOUT_MS);
3907
3944
  }
@@ -3918,7 +3955,8 @@ program
3918
3955
  // Step 2: Notify cloud completion (cloud will verify and persist credentials).
3919
3956
  console.log('');
3920
3957
  console.log('Finalizing authentication with cloud...');
3921
- const success = execError === null && execResult?.exitCode === 0;
3958
+ // Auth is successful if: success patterns were detected, OR the process exited cleanly (code 0)
3959
+ const success = execError === null && (execResult?.authDetected === true || execResult?.exitCode === 0);
3922
3960
  const providerForComplete = (typeof start.provider === 'string' && start.provider.trim().length > 0)
3923
3961
  ? start.provider.trim()
3924
3962
  : provider;
@@ -3970,17 +4008,42 @@ program
3970
4008
  console.log('');
3971
4009
  });
3972
4010
  // ============================================================================
3973
- // codex-auth - SSH tunnel helper for Codex/OpenAI authentication
4011
+ // cli-auth - SSH tunnel helper for provider authentication (Claude, Codex, Cursor, etc.)
3974
4012
  // ============================================================================
3975
- program
3976
- .command('codex-auth')
3977
- .description('Connect Codex via SSH tunnel to workspace (run this when connecting Codex in Agent Relay)')
3978
- .option('--workspace <id>', 'Workspace ID to connect to')
3979
- .option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com')
3980
- .option('--token <token>', 'CLI authentication token (from dashboard)')
3981
- .option('--session-cookie <cookie>', 'Session cookie for authentication (deprecated, use --token)')
3982
- .option('--timeout <seconds>', 'Timeout in seconds (default: 300)', '300')
3983
- .action(async (options) => {
4013
+ // Provider display names for CLI output
4014
+ const CLI_AUTH_DISPLAY_NAMES = {
4015
+ anthropic: 'Claude',
4016
+ openai: 'Codex',
4017
+ google: 'Gemini',
4018
+ cursor: 'Cursor',
4019
+ copilot: 'GitHub Copilot',
4020
+ opencode: 'OpenCode',
4021
+ droid: 'Droid',
4022
+ };
4023
+ // CLI command names per provider (used in help text)
4024
+ const CLI_AUTH_COMMAND_NAMES = {
4025
+ anthropic: 'claude',
4026
+ openai: 'codex',
4027
+ google: 'gemini',
4028
+ cursor: 'cursor',
4029
+ copilot: 'copilot',
4030
+ opencode: 'opencode',
4031
+ droid: 'droid',
4032
+ };
4033
+ // Provider alias mapping (CLI name → config key)
4034
+ const CLI_AUTH_PROVIDER_MAP = {
4035
+ claude: 'anthropic',
4036
+ codex: 'openai',
4037
+ gemini: 'google',
4038
+ };
4039
+ /**
4040
+ * Shared action handler for cli-auth and its provider-specific aliases.
4041
+ */
4042
+ async function runCliAuth(providerArg, options) {
4043
+ // Resolve provider alias
4044
+ const provider = CLI_AUTH_PROVIDER_MAP[providerArg.toLowerCase()] || providerArg.toLowerCase();
4045
+ const displayName = CLI_AUTH_DISPLAY_NAMES[provider] || provider;
4046
+ const cliName = CLI_AUTH_COMMAND_NAMES[provider] || provider;
3984
4047
  const TIMEOUT_MS = parseInt(options.timeout, 10) * 1000;
3985
4048
  const CLOUD_URL = options.cloudUrl.replace(/\/$/, '');
3986
4049
  const TUNNEL_PORT = 1455;
@@ -3992,25 +4055,26 @@ program
3992
4055
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
3993
4056
  console.log('');
3994
4057
  console.log(cyan('═══════════════════════════════════════════════════'));
3995
- console.log(cyan(' Codex Authentication Helper'));
4058
+ console.log(cyan(` ${displayName} Authentication Helper`));
3996
4059
  console.log(cyan('═══════════════════════════════════════════════════'));
3997
4060
  console.log('');
3998
4061
  if (!options.workspace) {
3999
4062
  console.log(red('Missing --workspace parameter.'));
4000
4063
  console.log('');
4001
- console.log('To connect Codex, follow these steps:');
4064
+ console.log(`To connect ${displayName}, follow these steps:`);
4002
4065
  console.log('');
4003
4066
  console.log(' 1. Go to the Agent Relay dashboard');
4004
- console.log(' 2. Click "Connect with Codex" (Settings → AI Providers)');
4067
+ console.log(` 2. Click "Connect with ${displayName}" (Settings → AI Providers)`);
4005
4068
  console.log(' 3. Copy the command shown (it includes the workspace ID and token)');
4006
4069
  console.log(' 4. Run the command in your terminal');
4007
4070
  console.log('');
4008
4071
  console.log('The command will look like:');
4009
- console.log(cyan(' npx agent-relay codex-auth --workspace=<ID> --token=<TOKEN>'));
4072
+ console.log(cyan(` npx agent-relay cli-auth ${cliName} --workspace=<ID> --token=<TOKEN>`));
4010
4073
  console.log('');
4011
4074
  process.exit(1);
4012
4075
  }
4013
4076
  const workspaceId = options.workspace;
4077
+ console.log(`Provider: ${displayName}`);
4014
4078
  console.log(`Workspace: ${workspaceId.slice(0, 8)}...`);
4015
4079
  // Get tunnel info from cloud API
4016
4080
  console.log('Getting workspace connection info...');
@@ -4022,18 +4086,19 @@ program
4022
4086
  if (!options.token && !options.sessionCookie) {
4023
4087
  console.log(red('Missing --token parameter.'));
4024
4088
  console.log('');
4025
- console.log('The token is provided by the dashboard when you click "Connect with Codex".');
4089
+ console.log(`The token is provided by the dashboard when you click "Connect with ${displayName}".`);
4026
4090
  console.log('Copy the complete command from the dashboard and paste it here.');
4027
4091
  console.log('');
4028
4092
  process.exit(1);
4029
4093
  }
4030
4094
  let tunnelInfo;
4031
4095
  try {
4032
- // Build URL with token query parameter
4096
+ // Build URL with token and provider query parameters
4033
4097
  const tunnelInfoUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/tunnel-info/${workspaceId}`);
4034
4098
  if (options.token) {
4035
4099
  tunnelInfoUrl.searchParams.set('token', options.token);
4036
4100
  }
4101
+ tunnelInfoUrl.searchParams.set('provider', provider);
4037
4102
  const response = await fetch(tunnelInfoUrl.toString(), {
4038
4103
  method: 'GET',
4039
4104
  headers,
@@ -4162,8 +4227,8 @@ program
4162
4227
  console.log('');
4163
4228
  console.log(cyan(tunnelInfo.authUrl));
4164
4229
  console.log('');
4165
- console.log(dim('The browser will redirect to localhost:1455, which tunnels to the workspace.'));
4166
- console.log(dim('The Codex CLI in the workspace will receive the callback and complete auth.'));
4230
+ console.log(dim(`The browser will redirect to localhost:${TUNNEL_PORT}, which tunnels to the workspace.`));
4231
+ console.log(dim(`The ${displayName} CLI in the workspace will receive the callback and complete auth.`));
4167
4232
  console.log('');
4168
4233
  }
4169
4234
  else {
@@ -4178,11 +4243,12 @@ program
4178
4243
  while (!authenticated && (Date.now() - startTime) < TIMEOUT_MS) {
4179
4244
  await new Promise(resolve => setTimeout(resolve, 3000));
4180
4245
  try {
4181
- // Build URL with token for authentication
4246
+ // Build URL with token and provider for authentication
4182
4247
  const authStatusUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/auth-status/${workspaceId}`);
4183
4248
  if (options.token) {
4184
4249
  authStatusUrl.searchParams.set('token', options.token);
4185
4250
  }
4251
+ authStatusUrl.searchParams.set('provider', provider);
4186
4252
  const statusResponse = await fetch(authStatusUrl.toString(), { method: 'GET', headers, credentials: 'include' });
4187
4253
  if (statusResponse.ok) {
4188
4254
  const statusData = await statusResponse.json();
@@ -4210,7 +4276,7 @@ program
4210
4276
  console.log(green(' Authentication Complete!'));
4211
4277
  console.log(green('═══════════════════════════════════════════════════'));
4212
4278
  console.log('');
4213
- console.log('Your Codex account is now connected to the workspace.');
4279
+ console.log(`Your ${displayName} account is now connected to the workspace.`);
4214
4280
  console.log('You can close this terminal and return to the dashboard.');
4215
4281
  console.log('');
4216
4282
  }
@@ -4222,6 +4288,63 @@ program
4222
4288
  console.log('the callback. Check if the SSH tunnel was working correctly.');
4223
4289
  process.exit(1);
4224
4290
  }
4291
+ }
4292
+ // Shared options for all cli-auth commands
4293
+ const cliAuthOptions = {
4294
+ workspace: '--workspace <id>',
4295
+ cloudUrl: '--cloud-url <url>',
4296
+ token: '--token <token>',
4297
+ sessionCookie: '--session-cookie <cookie>',
4298
+ timeout: '--timeout <seconds>',
4299
+ };
4300
+ const defaultCloudUrl = process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com';
4301
+ // cli-auth <provider> - Generic command
4302
+ program
4303
+ .command('cli-auth <provider>')
4304
+ .description('Connect a provider via SSH tunnel to workspace (Claude, Codex, Cursor, etc.)')
4305
+ .option(cliAuthOptions.workspace, 'Workspace ID to connect to')
4306
+ .option(cliAuthOptions.cloudUrl, 'Cloud API URL', defaultCloudUrl)
4307
+ .option(cliAuthOptions.token, 'CLI authentication token (from dashboard)')
4308
+ .option(cliAuthOptions.sessionCookie, 'Session cookie for authentication (deprecated, use --token)')
4309
+ .option(cliAuthOptions.timeout, 'Timeout in seconds (default: 300)', '300')
4310
+ .action(async (providerArg, options) => {
4311
+ await runCliAuth(providerArg, options);
4312
+ });
4313
+ // codex-auth - Backward-compatible alias
4314
+ program
4315
+ .command('codex-auth')
4316
+ .description('Connect Codex via SSH tunnel to workspace (alias for cli-auth codex)')
4317
+ .option(cliAuthOptions.workspace, 'Workspace ID to connect to')
4318
+ .option(cliAuthOptions.cloudUrl, 'Cloud API URL', defaultCloudUrl)
4319
+ .option(cliAuthOptions.token, 'CLI authentication token (from dashboard)')
4320
+ .option(cliAuthOptions.sessionCookie, 'Session cookie for authentication (deprecated, use --token)')
4321
+ .option(cliAuthOptions.timeout, 'Timeout in seconds (default: 300)', '300')
4322
+ .action(async (options) => {
4323
+ await runCliAuth('codex', options);
4324
+ });
4325
+ // claude-auth - Alias for Claude/Anthropic
4326
+ program
4327
+ .command('claude-auth')
4328
+ .description('Connect Claude via SSH tunnel to workspace (alias for cli-auth claude)')
4329
+ .option(cliAuthOptions.workspace, 'Workspace ID to connect to')
4330
+ .option(cliAuthOptions.cloudUrl, 'Cloud API URL', defaultCloudUrl)
4331
+ .option(cliAuthOptions.token, 'CLI authentication token (from dashboard)')
4332
+ .option(cliAuthOptions.sessionCookie, 'Session cookie for authentication (deprecated, use --token)')
4333
+ .option(cliAuthOptions.timeout, 'Timeout in seconds (default: 300)', '300')
4334
+ .action(async (options) => {
4335
+ await runCliAuth('claude', options);
4336
+ });
4337
+ // cursor-auth - Alias for Cursor
4338
+ program
4339
+ .command('cursor-auth')
4340
+ .description('Connect Cursor via SSH tunnel to workspace (alias for cli-auth cursor)')
4341
+ .option(cliAuthOptions.workspace, 'Workspace ID to connect to')
4342
+ .option(cliAuthOptions.cloudUrl, 'Cloud API URL', defaultCloudUrl)
4343
+ .option(cliAuthOptions.token, 'CLI authentication token (from dashboard)')
4344
+ .option(cliAuthOptions.sessionCookie, 'Session cookie for authentication (deprecated, use --token)')
4345
+ .option(cliAuthOptions.timeout, 'Timeout in seconds (default: 300)', '300')
4346
+ .action(async (options) => {
4347
+ await runCliAuth('cursor', options);
4225
4348
  });
4226
4349
  // init - First-time setup wizard for Agent Relay
4227
4350
  async function runInit(options) {