@fengye404/termpilot 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/cli.js +93 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -649,7 +649,10 @@ function createDaemonFromEnv() {
649
649
  }
650
650
 
651
651
  // agent/src/relay-admin.ts
652
- function getRelayBaseUrl() {
652
+ function isLocalRelayHost(hostname) {
653
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || /^10\./.test(hostname) || /^192\.168\./.test(hostname) || /^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname);
654
+ }
655
+ function getRelayBaseCandidates() {
653
656
  const relayUrl = process.env.TERMPILOT_RELAY_URL ?? "ws://127.0.0.1:8787/ws";
654
657
  let url;
655
658
  try {
@@ -657,10 +660,31 @@ function getRelayBaseUrl() {
657
660
  } catch {
658
661
  throw new Error("TERMPILOT_RELAY_URL \u65E0\u6548\uFF0C\u8BF7\u63D0\u4F9B\u5B8C\u6574\u7684 ws:// \u6216 wss:// \u5730\u5740\u3002");
659
662
  }
663
+ const wsUrl = new URL(url.toString());
664
+ wsUrl.search = "";
665
+ wsUrl.hash = "";
666
+ if (!wsUrl.pathname || wsUrl.pathname === "/") {
667
+ wsUrl.pathname = "/ws";
668
+ }
660
669
  url.protocol = url.protocol === "wss:" ? "https:" : "http:";
661
670
  url.pathname = "/";
662
671
  url.search = "";
663
- return url.toString();
672
+ url.hash = "";
673
+ const candidates = [{
674
+ baseUrl: url.toString(),
675
+ relayUrl: wsUrl.toString()
676
+ }];
677
+ if (!isLocalRelayHost(url.hostname) && url.port === "8787") {
678
+ const fallbackBase = new URL(url.toString());
679
+ fallbackBase.port = "";
680
+ const fallbackRelay = new URL(wsUrl.toString());
681
+ fallbackRelay.port = "";
682
+ candidates.push({
683
+ baseUrl: fallbackBase.toString(),
684
+ relayUrl: fallbackRelay.toString()
685
+ });
686
+ }
687
+ return candidates;
664
688
  }
665
689
  function getAgentToken() {
666
690
  return process.env.TERMPILOT_AGENT_TOKEN ?? DEFAULT_AGENT_TOKEN;
@@ -675,18 +699,30 @@ async function readJsonOrThrow(response, message) {
675
699
  return response.json();
676
700
  }
677
701
  async function fetchJson(input, init, message) {
678
- let response;
679
- try {
680
- response = await fetch(input, init);
681
- } catch (error) {
682
- const detail = error instanceof Error ? error.message : "\u672A\u77E5\u7F51\u7EDC\u9519\u8BEF";
683
- throw new Error(`${message}: \u65E0\u6CD5\u8FDE\u63A5 relay (${input.origin})\uFF0C${detail}`);
702
+ const candidates = getRelayBaseCandidates();
703
+ let lastError = null;
704
+ let lastOrigin = input.origin;
705
+ for (const candidate of candidates) {
706
+ const target = new URL(input.pathname + input.search, candidate.baseUrl);
707
+ lastOrigin = target.origin;
708
+ let response;
709
+ try {
710
+ response = await fetch(target, init);
711
+ } catch (error) {
712
+ lastError = error;
713
+ continue;
714
+ }
715
+ if (process.env.TERMPILOT_RELAY_URL !== candidate.relayUrl) {
716
+ process.env.TERMPILOT_RELAY_URL = candidate.relayUrl;
717
+ }
718
+ return readJsonOrThrow(response, message);
684
719
  }
685
- return readJsonOrThrow(response, message);
720
+ const detail = lastError instanceof Error ? lastError.message : "\u672A\u77E5\u7F51\u7EDC\u9519\u8BEF";
721
+ throw new Error(`${message}: \u65E0\u6CD5\u8FDE\u63A5 relay (${lastOrigin})\uFF0C${detail}`);
686
722
  }
687
723
  async function createPairingCode(deviceId) {
688
724
  return fetchJson(
689
- new URL("/api/pairing-codes", getRelayBaseUrl()),
725
+ new URL("/api/pairing-codes", "https://placeholder.invalid"),
690
726
  {
691
727
  method: "POST",
692
728
  headers: {
@@ -700,7 +736,7 @@ async function createPairingCode(deviceId) {
700
736
  }
701
737
  async function listDeviceGrants(deviceId) {
702
738
  return fetchJson(
703
- new URL(`/api/devices/${deviceId}/grants`, getRelayBaseUrl()),
739
+ new URL(`/api/devices/${deviceId}/grants`, "https://placeholder.invalid"),
704
740
  {
705
741
  headers: {
706
742
  authorization: `Bearer ${getAgentToken()}`
@@ -711,7 +747,7 @@ async function listDeviceGrants(deviceId) {
711
747
  }
712
748
  async function revokeDeviceGrant(deviceId, accessToken) {
713
749
  await fetchJson(
714
- new URL(`/api/devices/${deviceId}/grants/${accessToken}`, getRelayBaseUrl()),
750
+ new URL(`/api/devices/${deviceId}/grants/${accessToken}`, "https://placeholder.invalid"),
715
751
  {
716
752
  method: "DELETE",
717
753
  headers: {
@@ -724,7 +760,7 @@ async function revokeDeviceGrant(deviceId, accessToken) {
724
760
  async function listAuditEvents(deviceId, limit) {
725
761
  const constrainedLimit = Math.max(1, Math.min(limit, 100));
726
762
  return fetchJson(
727
- new URL(`/api/devices/${deviceId}/audit-events?limit=${constrainedLimit}`, getRelayBaseUrl()),
763
+ new URL(`/api/devices/${deviceId}/audit-events?limit=${constrainedLimit}`, "https://placeholder.invalid"),
728
764
  {
729
765
  headers: {
730
766
  authorization: `Bearer ${getAgentToken()}`
@@ -844,7 +880,7 @@ function getDeviceId(argv) {
844
880
  const saved = loadAgentConfig();
845
881
  return resolveDeviceId(saved?.deviceId);
846
882
  }
847
- function isLocalRelayHost(hostname) {
883
+ function isLocalRelayHost2(hostname) {
848
884
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || /^10\./.test(hostname) || /^192\.168\./.test(hostname) || /^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname);
849
885
  }
850
886
  function normalizeRelayUrl(rawHost, rawPort) {
@@ -863,7 +899,7 @@ function normalizeRelayUrl(rawHost, rawPort) {
863
899
  throw new Error("\u7AEF\u53E3\u65E0\u6548\uFF0C\u8BF7\u8F93\u5165 1 \u5230 65535 \u4E4B\u95F4\u7684\u6570\u5B57\u3002");
864
900
  }
865
901
  parsed.port = String(normalizedPort2);
866
- } else if (!parsed.port && parsed.protocol === "ws:" && isLocalRelayHost(parsed.hostname)) {
902
+ } else if (!parsed.port && parsed.protocol === "ws:" && isLocalRelayHost2(parsed.hostname)) {
867
903
  parsed.port = "8787";
868
904
  }
869
905
  if (!parsed.pathname || parsed.pathname === "/") {
@@ -873,7 +909,7 @@ function normalizeRelayUrl(rawHost, rawPort) {
873
909
  parsed.hash = "";
874
910
  return parsed.toString();
875
911
  }
876
- const protocol = isLocalRelayHost(hostInput) ? "ws:" : "wss:";
912
+ const protocol = isLocalRelayHost2(hostInput) ? "ws:" : "wss:";
877
913
  if (!portInput) {
878
914
  if (protocol === "ws:") {
879
915
  return `${protocol}//${hostInput}:8787/ws`;
@@ -956,6 +992,31 @@ function applyAgentConfig(config) {
956
992
  process.env.TERMPILOT_RELAY_URL = config.relayUrl;
957
993
  process.env.TERMPILOT_DEVICE_ID = config.deviceId;
958
994
  }
995
+ function persistMigratedRelayUrl(deviceId) {
996
+ const migratedRelayUrl = process.env.TERMPILOT_RELAY_URL?.trim();
997
+ if (!migratedRelayUrl) {
998
+ return false;
999
+ }
1000
+ let changed = false;
1001
+ const saved = loadAgentConfig();
1002
+ if (saved && saved.deviceId === deviceId && saved.relayUrl !== migratedRelayUrl) {
1003
+ saveAgentConfig({
1004
+ relayUrl: migratedRelayUrl,
1005
+ deviceId
1006
+ });
1007
+ console.log(`\u5DF2\u81EA\u52A8\u66F4\u65B0 relay \u5730\u5740\u4E3A: ${migratedRelayUrl}`);
1008
+ changed = true;
1009
+ }
1010
+ const runtime = loadAgentRuntime();
1011
+ if (runtime && runtime.deviceId === deviceId && runtime.relayUrl !== migratedRelayUrl) {
1012
+ saveAgentRuntime({
1013
+ ...runtime,
1014
+ relayUrl: migratedRelayUrl
1015
+ });
1016
+ changed = true;
1017
+ }
1018
+ return changed;
1019
+ }
959
1020
  function printRuntimeStatus(runtime = readRuntimeStatus().runtime) {
960
1021
  if (!runtime) {
961
1022
  console.log("\u540E\u53F0 agent \u5F53\u524D\u672A\u8FD0\u884C\u3002");
@@ -998,9 +1059,12 @@ async function waitForPairingCode(deviceId) {
998
1059
  let lastError = null;
999
1060
  for (let attempt = 0; attempt < 12; attempt += 1) {
1000
1061
  try {
1001
- return await createPairingCode(deviceId);
1062
+ const payload = await createPairingCode(deviceId);
1063
+ persistMigratedRelayUrl(deviceId);
1064
+ return payload;
1002
1065
  } catch (error) {
1003
1066
  lastError = error;
1067
+ persistMigratedRelayUrl(deviceId);
1004
1068
  await delay2(500);
1005
1069
  }
1006
1070
  }
@@ -1036,6 +1100,14 @@ async function runStart(argv) {
1036
1100
  if (pairing) {
1037
1101
  console.log(`\u914D\u5BF9\u7801: ${pairing.pairingCode}`);
1038
1102
  console.log(`\u6709\u6548\u671F\u81F3: ${pairing.expiresAt}`);
1103
+ } else {
1104
+ const migrated = loadAgentConfig();
1105
+ if (migrated && migrated.deviceId === deviceId && migrated.relayUrl !== existing.runtime.relayUrl) {
1106
+ console.log("\u68C0\u6D4B\u5230\u540E\u53F0 agent \u4ECD\u5728\u4F7F\u7528\u65E7 relay \u5730\u5740\uFF0C\u6B63\u5728\u81EA\u52A8\u91CD\u542F\u540E\u91CD\u8BD5\u3002");
1107
+ await runStop();
1108
+ await runStart(argv);
1109
+ return;
1110
+ }
1039
1111
  }
1040
1112
  } else {
1041
1113
  console.log("\u5982\u9700\u91CD\u65B0\u7ED9\u624B\u673A\u914D\u5BF9\uFF0C\u8BF7\u6267\u884C\uFF1Atermpilot agent --pair");
@@ -1171,6 +1243,7 @@ async function runPair(argv) {
1171
1243
  applyAgentConfig(config.config);
1172
1244
  const deviceId = getDeviceId(argv);
1173
1245
  const payload = await createPairingCode(deviceId);
1246
+ persistMigratedRelayUrl(deviceId);
1174
1247
  console.log(`\u8BBE\u5907: ${payload.deviceId}`);
1175
1248
  console.log(`\u914D\u5BF9\u7801: ${payload.pairingCode}`);
1176
1249
  console.log(`\u6709\u6548\u671F\u81F3: ${payload.expiresAt}`);
@@ -1181,6 +1254,7 @@ async function runGrants(argv) {
1181
1254
  applyAgentConfig(config.config);
1182
1255
  const deviceId = getDeviceId(argv);
1183
1256
  const payload = await listDeviceGrants(deviceId);
1257
+ persistMigratedRelayUrl(deviceId);
1184
1258
  if (payload.grants.length === 0) {
1185
1259
  console.log(`\u8BBE\u5907 ${payload.deviceId} \u5F53\u524D\u6CA1\u6709\u4EFB\u4F55\u5DF2\u7ED1\u5B9A\u8BBF\u95EE\u4EE4\u724C\u3002`);
1186
1260
  return;
@@ -1203,6 +1277,7 @@ async function runRevoke(argv) {
1203
1277
  applyAgentConfig(config.config);
1204
1278
  const deviceId = getDeviceId(argv);
1205
1279
  await revokeDeviceGrant(deviceId, accessToken);
1280
+ persistMigratedRelayUrl(deviceId);
1206
1281
  console.log(`\u5DF2\u64A4\u9500\u8BBE\u5907 ${deviceId} \u7684\u8BBF\u95EE\u4EE4\u724C ${accessToken}`);
1207
1282
  }
1208
1283
  async function runAudit(argv) {
@@ -1216,6 +1291,7 @@ async function runAudit(argv) {
1216
1291
  }
1217
1292
  const limit = Math.floor(parsedLimit);
1218
1293
  const payload = await listAuditEvents(deviceId, limit);
1294
+ persistMigratedRelayUrl(deviceId);
1219
1295
  if (payload.events.length === 0) {
1220
1296
  console.log(`\u8BBE\u5907 ${payload.deviceId} \u5F53\u524D\u6CA1\u6709\u5BA1\u8BA1\u65E5\u5FD7\u3002`);
1221
1297
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fengye404/termpilot",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.31.0",
6
6
  "description": "一个基于 tmux 的终端会话跨端查看与控制原型。",