@dev-anywhere/proxy 0.1.0 → 0.1.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.
package/dist/serve.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  KnownContentBlockSchema,
6
6
  SeqCounter,
7
7
  StreamJsonEventSchema
8
- } from "./chunk-JPJMOVQ5.js";
8
+ } from "./chunk-QXOARRC2.js";
9
9
  import {
10
10
  createFSM,
11
11
  defineFSM,
@@ -14,7 +14,7 @@ import {
14
14
  serviceLogger,
15
15
  shouldReleaseApprovalWait,
16
16
  stateAfterApprovalRelease
17
- } from "./chunk-WXWH6L7J.js";
17
+ } from "./chunk-TG7JPHE5.js";
18
18
  import {
19
19
  spawnScript
20
20
  } from "./chunk-ZUWAB67J.js";
@@ -27,9 +27,12 @@ import {
27
27
  CONFIG_PATH,
28
28
  ControlErrorCode,
29
29
  DATA_DIR,
30
+ DEFAULT_PROXY_PROFILE,
30
31
  HOOK_REGISTRY_PATH,
31
32
  MessageEnvelopeSchema,
32
33
  PID_PATH,
34
+ PROFILE_NAME,
35
+ PROXY_ID_PATH,
33
36
  SESSIONS_PATH,
34
37
  SOCK_PATH,
35
38
  STOPPED_PATH,
@@ -37,11 +40,13 @@ import {
37
40
  buildMessage,
38
41
  createIpcReader,
39
42
  createWorkerReader,
43
+ defaultHookPortForProfile,
44
+ ensureProfileWorkspace,
40
45
  serializeIpc,
41
46
  serializeWorkerMsg,
42
47
  sessionPaths,
43
48
  tildify
44
- } from "./chunk-QWPI6YON.js";
49
+ } from "./chunk-DFLQ3TFT.js";
45
50
 
46
51
  // src/serve.ts
47
52
  import { createServer as createServer2 } from "net";
@@ -620,31 +625,20 @@ function parsePort(value, source) {
620
625
  }
621
626
  return port;
622
627
  }
623
- function resolveFileConfig(fromFile, requestedEnv) {
624
- if (!fromFile.envs) {
625
- return {
626
- envName: void 0,
627
- envNameSource: fromFile.relayUrl || fromFile.relayToken || fromFile.hookPort ? "single" : "none",
628
- config: fromFile
629
- };
630
- }
631
- const envName = requestedEnv ?? fromFile.defaultEnv ?? "local";
632
- const config = fromFile.envs[envName];
633
- if (!config) {
634
- const available = Object.keys(fromFile.envs).sort();
635
- throw new Error(
636
- `Unknown config env "${envName}". Available envs: ${available.length > 0 ? available.join(", ") : "(none)"}`
637
- );
628
+ function isRecord(value) {
629
+ return typeof value === "object" && value !== null && !Array.isArray(value);
630
+ }
631
+ function validateConfigShape(value) {
632
+ if (!isRecord(value) || !isRecord(value.profiles) || !isRecord(value.relays)) {
633
+ throw new Error(`Invalid config shape in ${CONFIG_PATH}: expected "profiles" and "relays".`);
638
634
  }
639
- return {
640
- envName,
641
- envNameSource: requestedEnv ? "cli" : fromFile.defaultEnv ? "file" : "default",
642
- config
643
- };
635
+ return value;
644
636
  }
645
637
  function readConfigFile() {
646
- if (!existsSync3(CONFIG_PATH)) return {};
647
- return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
638
+ if (!existsSync3(CONFIG_PATH)) {
639
+ throw new Error(`Dev Anywhere config not found at ${CONFIG_PATH}. Run "dev-anywhere init".`);
640
+ }
641
+ return validateConfigShape(JSON.parse(readFileSync3(CONFIG_PATH, "utf-8")));
648
642
  }
649
643
  function agentCliField(provider) {
650
644
  return provider === "claude" ? "claudeBin" : "codexBin";
@@ -669,68 +663,71 @@ function uniqueAbsolutePaths(paths) {
669
663
  }
670
664
  return result;
671
665
  }
672
- function updateAgentCliPathInEnvConfig(config, provider, path) {
673
- const field = agentCliField(provider);
674
- const historyField = agentCliHistoryField(provider);
675
- const history = uniqueAbsolutePaths([path, ...config[historyField] ?? []]).slice(0, 8);
666
+ function resolveRelayConfig(fromFile, requestedRelayName) {
667
+ const profile = fromFile.profiles[PROFILE_NAME];
668
+ if (!profile) {
669
+ const available = Object.keys(fromFile.profiles).sort();
670
+ throw new Error(
671
+ `Unknown profile "${PROFILE_NAME}". Available profiles: ${available.length > 0 ? available.join(", ") : "(none)"}`
672
+ );
673
+ }
674
+ const relayName = requestedRelayName?.trim() || profile.relay?.trim();
675
+ if (!relayName) {
676
+ throw new Error(`Profile "${PROFILE_NAME}" must specify a relay.`);
677
+ }
678
+ const relay = fromFile.relays[relayName];
679
+ if (!relay) {
680
+ const available = Object.keys(fromFile.relays).sort();
681
+ throw new Error(
682
+ `Unknown relay "${relayName}". Available relays: ${available.length > 0 ? available.join(", ") : "(none)"}`
683
+ );
684
+ }
676
685
  return {
677
- ...config,
678
- [field]: path,
679
- [historyField]: history
686
+ relayName,
687
+ relayNameSource: requestedRelayName?.trim() ? "cli" : "profile",
688
+ relay
680
689
  };
681
690
  }
682
691
  function loadConfig(options) {
683
- let fromFile = {};
684
- if (existsSync3(CONFIG_PATH)) {
685
- try {
686
- fromFile = readConfigFile();
687
- } catch (err) {
688
- serviceLogger.warn(
689
- { path: CONFIG_PATH, err: err instanceof Error ? err.message : String(err) },
690
- "Failed to parse config file, falling back to env-only"
691
- );
692
- }
693
- } else {
694
- serviceLogger.debug({ path: CONFIG_PATH }, "Config file not found, using env-only");
695
- }
696
- const resolved = resolveFileConfig(fromFile, options?.envName);
697
- const hookPortFromFile = resolved.config.hookPort ?? fromFile.hookPort;
698
- const claudeBinFromFile = resolved.config.claudeBin ?? fromFile.claudeBin;
699
- const codexBinFromFile = resolved.config.codexBin ?? fromFile.codexBin;
700
- const claudeBinHistory = [
701
- ...resolved.config.claudeBinHistory ?? [],
702
- ...fromFile.claudeBinHistory ?? []
703
- ];
704
- const codexBinHistory = [
705
- ...resolved.config.codexBinHistory ?? [],
706
- ...fromFile.codexBinHistory ?? []
707
- ];
708
- const claudeBin = process.env.CLAUDE_BIN ?? claudeBinFromFile;
709
- const codexBin = process.env.CODEX_BIN ?? codexBinFromFile;
692
+ const fromFile = readConfigFile();
693
+ const agentCli = fromFile.agentCli ?? {};
694
+ const resolved = resolveRelayConfig(fromFile, options?.relayName);
695
+ const claudeBin = process.env.CLAUDE_BIN ?? agentCli.claudeBin;
696
+ const codexBin = process.env.CODEX_BIN ?? agentCli.codexBin;
710
697
  const config = {
711
- envName: resolved.envName,
712
- relayUrl: process.env.RELAY_URL ?? resolved.config.relayUrl,
713
- relayToken: process.env.RELAY_PROXY_TOKEN ?? resolved.config.relayToken,
714
- hookPort: parsePort(process.env.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT") ?? hookPortFromFile ?? 17654,
698
+ profileName: PROFILE_NAME,
699
+ relayName: resolved.relayName,
700
+ relayUrl: process.env.RELAY_URL ?? resolved.relay.url,
701
+ relayToken: process.env.RELAY_PROXY_TOKEN ?? resolved.relay.proxyToken,
702
+ hookPort: parsePort(process.env.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT") ?? defaultHookPortForProfile(PROFILE_NAME),
715
703
  claudeBin,
716
704
  codexBin,
717
705
  agentCliSuggestions: {
718
- claude: uniqueAbsolutePaths([process.env.CLAUDE_BIN, claudeBinFromFile, ...claudeBinHistory]),
719
- codex: uniqueAbsolutePaths([process.env.CODEX_BIN, codexBinFromFile, ...codexBinHistory])
706
+ claude: uniqueAbsolutePaths([
707
+ process.env.CLAUDE_BIN,
708
+ agentCli.claudeBin,
709
+ ...agentCli.claudeBinHistory ?? []
710
+ ]),
711
+ codex: uniqueAbsolutePaths([
712
+ process.env.CODEX_BIN,
713
+ agentCli.codexBin,
714
+ ...agentCli.codexBinHistory ?? []
715
+ ])
720
716
  },
721
717
  sources: {
722
- envName: resolved.envNameSource,
723
- relayUrl: process.env.RELAY_URL ? "env" : resolved.config.relayUrl ? "file" : "none",
724
- relayToken: process.env.RELAY_PROXY_TOKEN ? "env" : resolved.config.relayToken ? "file" : "none",
725
- hookPort: process.env.DEV_ANYWHERE_HOOK_PORT ? "env" : hookPortFromFile ? "file" : "default",
726
- claudeBin: process.env.CLAUDE_BIN ? "env" : claudeBinFromFile ? "file" : "none",
727
- codexBin: process.env.CODEX_BIN ? "env" : codexBinFromFile ? "file" : "none"
718
+ relayName: resolved.relayNameSource,
719
+ relayUrl: process.env.RELAY_URL ? "env" : resolved.relay.url ? "file" : "none",
720
+ relayToken: process.env.RELAY_PROXY_TOKEN ? "env" : resolved.relay.proxyToken ? "file" : "none",
721
+ hookPort: process.env.DEV_ANYWHERE_HOOK_PORT ? "env" : "default",
722
+ claudeBin: process.env.CLAUDE_BIN ? "env" : agentCli.claudeBin ? "file" : "none",
723
+ codexBin: process.env.CODEX_BIN ? "env" : agentCli.codexBin ? "file" : "none"
728
724
  }
729
725
  };
730
726
  serviceLogger.info(
731
727
  {
732
- envName: config.envName ?? "(single)",
733
- envNameSource: config.sources.envName,
728
+ profile: config.profileName,
729
+ relayName: config.relayName,
730
+ relayNameSource: config.sources.relayName,
734
731
  relayUrl: config.relayUrl ?? "(unset)",
735
732
  relayUrlSource: config.sources.relayUrl,
736
733
  relayTokenSource: config.sources.relayToken,
@@ -750,20 +747,20 @@ function buildProviderEnv(config, baseEnv = process.env) {
750
747
  ...config.codexBin ? { CODEX_BIN: config.codexBin } : {}
751
748
  };
752
749
  }
753
- function saveAgentCliPath(provider, path, options) {
750
+ function updateAgentCliConfig(config, provider, path) {
751
+ const field = agentCliField(provider);
752
+ const historyField = agentCliHistoryField(provider);
753
+ const history = uniqueAbsolutePaths([path, ...config[historyField] ?? []]).slice(0, 8);
754
+ return {
755
+ ...config,
756
+ [field]: path,
757
+ [historyField]: history
758
+ };
759
+ }
760
+ function saveAgentCliPath(provider, path) {
754
761
  const normalized = validateAgentCliPath(path);
755
762
  const fromFile = readConfigFile();
756
- const resolved = resolveFileConfig(fromFile, options?.envName);
757
- if (fromFile.envs) {
758
- const envName = resolved.envName ?? options?.envName ?? fromFile.defaultEnv ?? "local";
759
- fromFile.envs[envName] = updateAgentCliPathInEnvConfig(
760
- fromFile.envs[envName] ?? {},
761
- provider,
762
- normalized
763
- );
764
- } else {
765
- Object.assign(fromFile, updateAgentCliPathInEnvConfig(fromFile, provider, normalized));
766
- }
763
+ fromFile.agentCli = updateAgentCliConfig(fromFile.agentCli ?? {}, provider, normalized);
767
764
  mkdirSync3(dirname3(CONFIG_PATH), { recursive: true });
768
765
  writeFileSync3(CONFIG_PATH, `${JSON.stringify(fromFile, null, 2)}
769
766
  `, "utf-8");
@@ -2063,7 +2060,7 @@ function terminateSessionByOwnership(deps, sessionId) {
2063
2060
  }
2064
2061
 
2065
2062
  // src/serve/clipboard-image-upload.ts
2066
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2063
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "fs";
2067
2064
  import { isAbsolute as isAbsolute3, join as join5, relative, resolve } from "path";
2068
2065
  import { nanoid as nanoid3 } from "nanoid";
2069
2066
  var MAX_CLIPBOARD_IMAGE_BYTES = 10 * 1024 * 1024;
@@ -2096,15 +2093,50 @@ function decodeBase64Image(dataBase64) {
2096
2093
  }
2097
2094
  return buffer;
2098
2095
  }
2099
- function resolveSessionClipboardDir(dataDir, sessionId) {
2100
- const root = resolve(dataDir);
2101
- const uploadDir = resolve(root, sessionId, "clipboard");
2096
+ function resolveChildDir(rootPath, ...segments) {
2097
+ const root = resolve(rootPath);
2098
+ const uploadDir = resolve(root, ...segments);
2102
2099
  const relativePath = relative(root, uploadDir);
2103
2100
  if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath)) {
2104
2101
  throw new Error("\u4F1A\u8BDD\u8DEF\u5F84\u65E0\u6548");
2105
2102
  }
2106
2103
  return uploadDir;
2107
2104
  }
2105
+ function resolveSessionClipboardDir(dataDir, sessionId) {
2106
+ return resolveChildDir(dataDir, sessionId, "clipboard");
2107
+ }
2108
+ function normalizeGitignoreLine(line) {
2109
+ return line.trim().replace(/^\/+/, "").replace(/\/+$/, "");
2110
+ }
2111
+ function ensureProjectClipboardIgnored(cwd) {
2112
+ const gitignorePath = join5(cwd, ".gitignore");
2113
+ if (!existsSync5(gitignorePath)) return;
2114
+ try {
2115
+ const current = readFileSync5(gitignorePath, "utf-8");
2116
+ const alreadyIgnored = current.split(/\r?\n/).some((line) => normalizeGitignoreLine(line) === ".dev-anywhere");
2117
+ if (alreadyIgnored) return;
2118
+ const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
2119
+ writeFileSync4(gitignorePath, `${current}${separator}.dev-anywhere/
2120
+ `);
2121
+ } catch {
2122
+ }
2123
+ }
2124
+ function trySaveProjectClipboardImage(options) {
2125
+ if (!options.cwd) return null;
2126
+ try {
2127
+ const cwd = resolve(options.cwd);
2128
+ if (!statSync(cwd).isDirectory()) return null;
2129
+ const clipboardRoot = resolve(cwd, ".dev-anywhere", "clipboard");
2130
+ const uploadDir = resolveChildDir(clipboardRoot, options.sessionId);
2131
+ const path = join5(uploadDir, options.fileName);
2132
+ mkdirSync4(uploadDir, { recursive: true });
2133
+ writeFileSync4(path, options.buffer, { mode: 384 });
2134
+ ensureProjectClipboardIgnored(cwd);
2135
+ return { success: true, path: relative(cwd, path) };
2136
+ } catch {
2137
+ return null;
2138
+ }
2139
+ }
2108
2140
  function saveClipboardImageUpload(request, options = {}) {
2109
2141
  const extension = IMAGE_EXTENSIONS.get(request.mimeType);
2110
2142
  if (!extension) {
@@ -2116,12 +2148,19 @@ function saveClipboardImageUpload(request, options = {}) {
2116
2148
  };
2117
2149
  }
2118
2150
  try {
2119
- const dataDir = options.dataDir ?? DATA_DIR;
2120
- const uploadDir = resolveSessionClipboardDir(dataDir, request.sessionId);
2121
2151
  const buffer = decodeBase64Image(request.dataBase64);
2122
2152
  const now = options.now ?? Date.now;
2123
2153
  const suffix = options.randomSuffix?.() ?? nanoid3(6);
2124
2154
  const fileName = `pasted-${formatTimestamp(now())}-${suffix}.${extension}`;
2155
+ const projectResult = trySaveProjectClipboardImage({
2156
+ cwd: options.cwd,
2157
+ sessionId: request.sessionId,
2158
+ fileName,
2159
+ buffer
2160
+ });
2161
+ if (projectResult) return projectResult;
2162
+ const dataDir = options.dataDir ?? DATA_DIR;
2163
+ const uploadDir = resolveSessionClipboardDir(dataDir, request.sessionId);
2125
2164
  const path = join5(uploadDir, fileName);
2126
2165
  mkdirSync4(uploadDir, { recursive: true });
2127
2166
  writeFileSync4(path, buffer, { mode: 384 });
@@ -2229,12 +2268,17 @@ var RelayInputHandlers = class {
2229
2268
  serviceLogger.warn({ sessionId }, "Clipboard image upload rejected: session not found");
2230
2269
  return;
2231
2270
  }
2232
- const result = saveClipboardImageUpload({
2233
- sessionId,
2234
- mimeType: typeof msg.mimeType === "string" ? msg.mimeType : "",
2235
- dataBase64: typeof msg.dataBase64 === "string" ? msg.dataBase64 : "",
2236
- fileName: typeof msg.fileName === "string" ? msg.fileName : void 0
2237
- });
2271
+ const result = saveClipboardImageUpload(
2272
+ {
2273
+ sessionId,
2274
+ mimeType: typeof msg.mimeType === "string" ? msg.mimeType : "",
2275
+ dataBase64: typeof msg.dataBase64 === "string" ? msg.dataBase64 : "",
2276
+ fileName: typeof msg.fileName === "string" ? msg.fileName : void 0
2277
+ },
2278
+ {
2279
+ cwd: session.cwd
2280
+ }
2281
+ );
2238
2282
  this.deps.relayConnection.sendRaw(
2239
2283
  JSON.stringify({
2240
2284
  type: "clipboard_image_upload_response",
@@ -2444,14 +2488,14 @@ var RelayPermissionHandlers = class {
2444
2488
 
2445
2489
  // src/serve/relay-resource-handlers.ts
2446
2490
  import { homedir as homedir4 } from "os";
2447
- import { accessSync, constants, statSync } from "fs";
2491
+ import { accessSync, constants, statSync as statSync2 } from "fs";
2448
2492
  function errorMessage(err) {
2449
2493
  return err instanceof Error ? err.message : String(err);
2450
2494
  }
2451
2495
  function validateExecutablePath(path) {
2452
2496
  const normalized = path.trim();
2453
2497
  if (!normalized.startsWith("/")) throw new Error("CLI \u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
2454
- const stat2 = statSync(normalized);
2498
+ const stat2 = statSync2(normalized);
2455
2499
  if (!stat2.isFile()) throw new Error("CLI \u8DEF\u5F84\u4E0D\u662F\u53EF\u6267\u884C\u6587\u4EF6");
2456
2500
  accessSync(normalized, constants.X_OK);
2457
2501
  return normalized;
@@ -2491,7 +2535,7 @@ var RelayResourceHandlers = class {
2491
2535
  }
2492
2536
  try {
2493
2537
  const path = validateExecutablePath(rawPath ?? "");
2494
- saveAgentCliPath(provider, path, { envName: this.deps.envName });
2538
+ saveAgentCliPath(provider, path);
2495
2539
  this.deps.setAgentCliPath(provider, path);
2496
2540
  const agentCli = detectAgentCliStatus(this.deps.getProviderEnv(), {
2497
2541
  suggestions: this.deps.getAgentCliSuggestions()
@@ -2560,7 +2604,7 @@ var RelayResourceHandlers = class {
2560
2604
  };
2561
2605
 
2562
2606
  // src/serve/relay-session-create-handler.ts
2563
- import { rmSync, statSync as statSync2 } from "fs";
2607
+ import { rmSync, statSync as statSync3 } from "fs";
2564
2608
  import { isAbsolute as isAbsolute4 } from "path";
2565
2609
  import { nanoid as nanoid4 } from "nanoid";
2566
2610
 
@@ -2855,7 +2899,7 @@ function validateSessionCwd(cwd) {
2855
2899
  return { message: "\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84", code: ControlErrorCode.INVALID_PATH };
2856
2900
  }
2857
2901
  try {
2858
- const stat2 = statSync2(trimmed);
2902
+ const stat2 = statSync3(trimmed);
2859
2903
  return stat2.isDirectory() ? null : { message: "\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55", code: ControlErrorCode.PATH_NOT_DIRECTORY };
2860
2904
  } catch (err) {
2861
2905
  return {
@@ -3110,7 +3154,6 @@ var RelayRouter = class {
3110
3154
  relaySend: deps.relaySend,
3111
3155
  controlHandlers: deps.controlHandlers,
3112
3156
  sessionManager: deps.sessionManager,
3113
- envName: deps.envName,
3114
3157
  getProviderEnv: deps.getProviderEnv,
3115
3158
  getAgentCliSuggestions: deps.getAgentCliSuggestions,
3116
3159
  setAgentCliPath: deps.setAgentCliPath
@@ -3652,7 +3695,7 @@ function touchSessionActivity(sessionManager, relay, sessionId, now = Date.now()
3652
3695
 
3653
3696
  // src/serve/service-files.ts
3654
3697
  import { execSync } from "child_process";
3655
- import { existsSync as existsSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync2 } from "fs";
3698
+ import { existsSync as existsSync6, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
3656
3699
  import { hostname } from "os";
3657
3700
  import { connect as connect2 } from "net";
3658
3701
  function tryConnectSocket(sockPath) {
@@ -3671,7 +3714,7 @@ function isProcessAlive(pid) {
3671
3714
  }
3672
3715
  }
3673
3716
  async function cleanupStaleResources() {
3674
- if (existsSync5(SOCK_PATH)) {
3717
+ if (existsSync6(SOCK_PATH)) {
3675
3718
  const existing = await tryConnectSocket(SOCK_PATH);
3676
3719
  if (existing) {
3677
3720
  existing.destroy();
@@ -3683,8 +3726,8 @@ async function cleanupStaleResources() {
3683
3726
  unlinkSync2(SOCK_PATH);
3684
3727
  serviceLogger.info("Removed stale socket file");
3685
3728
  }
3686
- if (existsSync5(PID_PATH)) {
3687
- const pidStr = readFileSync5(PID_PATH, "utf-8").trim();
3729
+ if (existsSync6(PID_PATH)) {
3730
+ const pidStr = readFileSync6(PID_PATH, "utf-8").trim();
3688
3731
  const pid = parseInt(pidStr, 10);
3689
3732
  if (!isNaN(pid) && isProcessAlive(pid)) {
3690
3733
  const msg = `Another service is already running with PID ${pid}`;
@@ -3696,8 +3739,13 @@ async function cleanupStaleResources() {
3696
3739
  serviceLogger.info("Removed stale PID file");
3697
3740
  }
3698
3741
  }
3742
+ function formatProxyNameForProfile(baseName, profileName = PROFILE_NAME) {
3743
+ return profileName === DEFAULT_PROXY_PROFILE ? baseName : `${baseName} (${profileName})`;
3744
+ }
3699
3745
  function getProxyName() {
3700
- return process.env.DEV_ANYWHERE_PROXY_NAME || getComputerName() || hostname();
3746
+ const explicitName = process.env.DEV_ANYWHERE_PROXY_NAME?.trim();
3747
+ if (explicitName) return explicitName;
3748
+ return formatProxyNameForProfile(getComputerName() || hostname());
3701
3749
  }
3702
3750
  function getComputerName() {
3703
3751
  try {
@@ -4054,7 +4102,7 @@ function handleTerminalConnection(socket, deps) {
4054
4102
 
4055
4103
  // src/serve/hook-registry.ts
4056
4104
  import { createHash, randomBytes } from "crypto";
4057
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
4105
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
4058
4106
  import { dirname as dirname4 } from "path";
4059
4107
  import { z } from "zod";
4060
4108
  var PersistedHookSessionBindingSchema = z.object({
@@ -4115,10 +4163,10 @@ var HookRegistry = class {
4115
4163
  }
4116
4164
  }
4117
4165
  load() {
4118
- if (!this.persistPath || !existsSync6(this.persistPath)) return;
4166
+ if (!this.persistPath || !existsSync7(this.persistPath)) return;
4119
4167
  try {
4120
4168
  const parsed = PersistedHookRegistrySchema.parse(
4121
- JSON.parse(readFileSync6(this.persistPath, "utf8"))
4169
+ JSON.parse(readFileSync7(this.persistPath, "utf8"))
4122
4170
  );
4123
4171
  this.bindingsBySession.clear();
4124
4172
  for (const binding of parsed.bindings) {
@@ -4434,24 +4482,25 @@ function parseServiceOptions(argv) {
4434
4482
  const options = {};
4435
4483
  for (let i = 0; i < argv.length; i++) {
4436
4484
  const arg = argv[i];
4437
- if (arg === "--env") {
4438
- const envName = argv[i + 1];
4439
- if (!envName || envName.startsWith("-")) {
4440
- throw new Error("Missing value for --env");
4485
+ if (arg === "--relay") {
4486
+ const relayName = argv[i + 1];
4487
+ if (!relayName || relayName.startsWith("-")) {
4488
+ throw new Error("Missing value for --relay");
4441
4489
  }
4442
- options.envName = envName;
4490
+ options.relayName = relayName;
4443
4491
  i++;
4444
4492
  continue;
4445
4493
  }
4446
- if (arg.startsWith("--env=")) {
4447
- const envName = arg.slice("--env=".length);
4448
- if (!envName) throw new Error("Missing value for --env");
4449
- options.envName = envName;
4494
+ if (arg.startsWith("--relay=")) {
4495
+ const relayName = arg.slice("--relay=".length);
4496
+ if (!relayName) throw new Error("Missing value for --relay");
4497
+ options.relayName = relayName;
4450
4498
  }
4451
4499
  }
4452
4500
  return options;
4453
4501
  }
4454
4502
  async function startService(options) {
4503
+ ensureProfileWorkspace();
4455
4504
  await cleanupStaleResources();
4456
4505
  try {
4457
4506
  unlinkSync3(STOPPED_PATH);
@@ -4479,7 +4528,7 @@ async function startService(options) {
4479
4528
  sessionManager.startReaper();
4480
4529
  const terminalSockets = /* @__PURE__ */ new Map();
4481
4530
  const proxyName = getProxyName();
4482
- let proxyConfig = loadConfig({ envName: options?.envName });
4531
+ let proxyConfig = loadConfig({ relayName: options?.relayName });
4483
4532
  const getProviderEnv = () => buildProviderEnv(proxyConfig, process.env);
4484
4533
  const getAgentCliSuggestions = () => proxyConfig.agentCliSuggestions;
4485
4534
  const setAgentCliPath = (provider, path) => {
@@ -4501,8 +4550,9 @@ async function startService(options) {
4501
4550
  const relayUrl = options?.relayUrl ?? proxyConfig.relayUrl;
4502
4551
  const relayToken = proxyConfig.relayToken;
4503
4552
  const statusConfig = {
4504
- envName: proxyConfig.envName,
4505
- envNameSource: proxyConfig.sources.envName,
4553
+ profile: PROFILE_NAME,
4554
+ relayName: proxyConfig.relayName,
4555
+ relayNameSource: proxyConfig.sources.relayName,
4506
4556
  relayUrl,
4507
4557
  relayUrlSource: proxyConfig.sources.relayUrl,
4508
4558
  relayTokenSource: proxyConfig.sources.relayToken,
@@ -4510,12 +4560,16 @@ async function startService(options) {
4510
4560
  hookPortSource: proxyConfig.sources.hookPort
4511
4561
  };
4512
4562
  if (!relayUrl) {
4513
- const msg = 'Relay URL is required. Set it via RELAY_URL or ~/.dev-anywhere/config.json {"defaultEnv":"local","envs":{"local":{"relayUrl":"ws://..."}}}.';
4563
+ const msg = `Relay URL is required. Set relays.${proxyConfig.relayName}.url in ~/.dev-anywhere/config.json or pass --relay <name>.`;
4514
4564
  serviceLogger.error(msg);
4515
4565
  console.error(msg);
4516
4566
  process.exit(1);
4517
4567
  }
4518
- const relayConnection = new RelayConnection(relayUrl, { name: proxyName, token: relayToken });
4568
+ const relayConnection = new RelayConnection(relayUrl, {
4569
+ name: proxyName,
4570
+ token: relayToken,
4571
+ proxyIdPath: PROXY_ID_PATH
4572
+ });
4519
4573
  const relaySend = (data) => relayConnection.sendRaw(data);
4520
4574
  const controlHandlers = createControlMessageHandlers(relaySend, sessionManager);
4521
4575
  const observerChangeState = (sessionId, next) => changeSessionState(sessionManager, relayConnection, sessionId, next);
@@ -4577,7 +4631,8 @@ async function startService(options) {
4577
4631
  relayConnection.connect();
4578
4632
  serviceLogger.info(
4579
4633
  {
4580
- envName: proxyConfig.envName ?? "(single)",
4634
+ relayName: proxyConfig.relayName,
4635
+ profile: PROFILE_NAME,
4581
4636
  relayUrl,
4582
4637
  proxyName,
4583
4638
  tokenSet: !!relayToken,
@@ -4601,7 +4656,6 @@ async function startService(options) {
4601
4656
  permissionBroker,
4602
4657
  hookEventRouter: hookRuntime.hookEventRouter,
4603
4658
  agentStatusRegistry,
4604
- envName: proxyConfig.envName,
4605
4659
  getProviderEnv,
4606
4660
  getAgentCliSuggestions,
4607
4661
  setAgentCliPath