@walkhi/code-relax 0.1.0-beta.30 → 0.1.0-beta.32

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 (187) hide show
  1. package/README.md +95 -89
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +374 -367
  4. package/dist/bin/manage-relay-admin.mjs +20 -20
  5. package/dist/bin/migrate-relay-db.mjs +109 -109
  6. package/dist/bin/publish-android-release.mjs +63 -58
  7. package/dist/bin/publish-harmony-release.mjs +46 -0
  8. package/dist/bin/release-upload-server.mjs +174 -0
  9. package/dist/bin/self-relay-admin.mjs +8 -8
  10. package/dist/bin/self-relay-demo.mjs +33 -33
  11. package/dist/bin/self-relay-server.mjs +66 -66
  12. package/dist/build-platform.json +1 -1
  13. package/dist/get-lan-networks.ps1 +40 -40
  14. package/dist/get-shared-service-url.ps1 +6 -6
  15. package/dist/shared/app-server-events.cjs +422 -412
  16. package/dist/shared/code-browser.cjs +159 -159
  17. package/dist/shared/file-changes.cjs +31 -31
  18. package/dist/shared/file-citations.cjs +117 -117
  19. package/dist/shared/file-mentions.cjs +92 -92
  20. package/dist/shared/message-delivery.cjs +54 -54
  21. package/dist/shared/p2p-data-channel.cjs +72 -72
  22. package/dist/shared/p2p-diagnostics.cjs +28 -28
  23. package/dist/shared/p2p-file-channel.cjs +78 -74
  24. package/dist/shared/plan-pricing.cjs +32 -32
  25. package/dist/shared/question-attention.cjs +69 -0
  26. package/dist/shared/server-requests.cjs +219 -219
  27. package/dist/shared/timeline-sync.cjs +173 -173
  28. package/dist/skills/code-relax/SKILL.md +22 -22
  29. package/dist/src/archived-threads.mjs +22 -0
  30. package/dist/src/background-process.mjs +18 -18
  31. package/dist/src/bounded-process-log.mjs +63 -63
  32. package/dist/src/celsius-client.mjs +107 -107
  33. package/dist/src/code-browser.mjs +100 -100
  34. package/dist/src/desktop-launcher.mjs +46 -46
  35. package/dist/src/dots-approval-timeline.mjs +99 -99
  36. package/dist/src/dots-avatar.mjs +42 -42
  37. package/dist/src/dots-chat-projection.mjs +50 -50
  38. package/dist/src/dots-chat-server.mjs +461 -461
  39. package/dist/src/dots-message-images.mjs +319 -319
  40. package/dist/src/dots-profile.mjs +77 -77
  41. package/dist/src/dots-room-activity.mjs +94 -94
  42. package/dist/src/dots-room-client.mjs +135 -135
  43. package/dist/src/dots-room-projection.mjs +94 -94
  44. package/dist/src/dots-schedules.mjs +66 -66
  45. package/dist/src/dots-summary-tasks.mjs +32 -32
  46. package/dist/src/dots-task-catalog.mjs +430 -430
  47. package/dist/src/dots-task-files.mjs +52 -52
  48. package/dist/src/execution-health.mjs +26 -26
  49. package/dist/src/file-transfer-range.mjs +22 -22
  50. package/dist/src/file-upload-session.mjs +130 -130
  51. package/dist/src/hosted-history-app-server.mjs +103 -103
  52. package/dist/src/hosted-history-cache.mjs +113 -113
  53. package/dist/src/lan-socket-server.mjs +265 -263
  54. package/dist/src/managed-app-server.mjs +244 -244
  55. package/dist/src/message-images.mjs +9 -9
  56. package/dist/src/message-upload-files.mjs +74 -74
  57. package/dist/src/onboarding.mjs +12 -12
  58. package/dist/src/plan-pricing.mjs +47 -47
  59. package/dist/src/platform/README.md +12 -12
  60. package/dist/src/platform/codex-protocol.mjs +46 -46
  61. package/dist/src/platform/codex-runtime.mjs +3 -3
  62. package/dist/src/platform/credential-store.mjs +3 -3
  63. package/dist/src/platform/desktop-host.mjs +3 -3
  64. package/dist/src/platform/desktop-project-registration.mjs +3 -3
  65. package/dist/src/platform/desktop-project-store.mjs +208 -149
  66. package/dist/src/platform/file-opener.mjs +9 -0
  67. package/dist/src/platform/linux/README.md +3 -3
  68. package/dist/src/platform/linux/codex-runtime.mjs +56 -0
  69. package/dist/src/platform/linux/credential-store.mjs +28 -0
  70. package/dist/src/platform/linux/desktop-host.mjs +83 -0
  71. package/dist/src/platform/linux/desktop-installation.mjs +36 -0
  72. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -0
  73. package/dist/src/platform/linux/network-info.mjs +11 -0
  74. package/dist/src/platform/linux/process.mjs +42 -0
  75. package/dist/src/platform/linux/project-locations.mjs +17 -0
  76. package/dist/src/platform/linux/resident-startup.mjs +52 -0
  77. package/dist/src/platform/linux/retained-runtime.mjs +76 -0
  78. package/dist/src/platform/linux/service-host.mjs +18 -0
  79. package/dist/src/platform/linux/system.mjs +49 -0
  80. package/dist/src/platform/linux/user-environment.mjs +7 -0
  81. package/dist/src/platform/macos/README.md +3 -3
  82. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  83. package/dist/src/platform/macos/credential-store.mjs +10 -10
  84. package/dist/src/platform/macos/desktop-host.mjs +82 -82
  85. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -10
  86. package/dist/src/platform/macos/network-info.mjs +8 -28
  87. package/dist/src/platform/macos/project-locations.mjs +8 -8
  88. package/dist/src/platform/macos/resident-startup.mjs +84 -84
  89. package/dist/src/platform/macos/retained-runtime.mjs +127 -127
  90. package/dist/src/platform/macos/service-host.mjs +18 -18
  91. package/dist/src/platform/macos/system.mjs +29 -29
  92. package/dist/src/platform/macos/user-environment.mjs +7 -7
  93. package/dist/src/platform/native-package.mjs +58 -46
  94. package/dist/src/platform/network-info.mjs +3 -3
  95. package/dist/src/platform/path-identity.mjs +5 -5
  96. package/dist/src/platform/posix/network-candidates.mjs +23 -0
  97. package/dist/src/platform/posix/runtime-paths.mjs +25 -25
  98. package/dist/src/platform/project-locations.mjs +3 -3
  99. package/dist/src/platform/runtime-paths.mjs +8 -8
  100. package/dist/src/platform/select.mjs +5 -5
  101. package/dist/src/platform/service-host.mjs +3 -3
  102. package/dist/src/platform/user-environment.mjs +3 -3
  103. package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
  104. package/dist/src/platform/windows/activate-desktop.ps1 +48 -48
  105. package/dist/src/platform/windows/background-process.mjs +30 -30
  106. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  107. package/dist/src/platform/windows/credential-store.mjs +27 -27
  108. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  109. package/dist/src/platform/windows/desktop-helper.mjs +179 -179
  110. package/dist/src/platform/windows/desktop-host.mjs +58 -58
  111. package/dist/src/platform/windows/desktop-info.ps1 +14 -14
  112. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  113. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
  114. package/dist/src/platform/windows/desktop-shortcuts.ps1 +21 -21
  115. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  116. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  117. package/dist/src/platform/windows/network-info.mjs +34 -34
  118. package/dist/src/platform/windows/project-locations.mjs +16 -16
  119. package/dist/src/platform/windows/resident-start.js +20 -20
  120. package/dist/src/platform/windows/resident-start.ps1 +7 -7
  121. package/dist/src/platform/windows/resident-startup.ps1 +83 -83
  122. package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
  123. package/dist/src/platform/windows/runtime-paths.mjs +20 -20
  124. package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
  125. package/dist/src/platform/windows/service-host.mjs +31 -31
  126. package/dist/src/platform/windows/start-hidden-console.ps1 +49 -49
  127. package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
  128. package/dist/src/platform/windows/user-environment.mjs +50 -50
  129. package/dist/src/platform/windows/user-environment.ps1 +41 -41
  130. package/dist/src/project-archive.mjs +30 -0
  131. package/dist/src/project-create.mjs +121 -119
  132. package/dist/src/project-management.mjs +59 -69
  133. package/dist/src/project-remove.mjs +55 -0
  134. package/dist/src/project-update.mjs +56 -0
  135. package/dist/src/runtime-discovery.mjs +47 -47
  136. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  137. package/dist/src/self-relay/account-connections.mjs +52 -52
  138. package/dist/src/self-relay/admin-cloud.mjs +101 -101
  139. package/dist/src/self-relay/admin-page.mjs +80 -80
  140. package/dist/src/self-relay/admin-server.mjs +74 -74
  141. package/dist/src/self-relay/admin-state.mjs +127 -127
  142. package/dist/src/self-relay/admin-style.mjs +14 -14
  143. package/dist/src/self-relay/android-releases.mjs +61 -61
  144. package/dist/src/self-relay/client.mjs +61 -61
  145. package/dist/src/self-relay/connector.mjs +439 -438
  146. package/dist/src/self-relay/debug-allowlist.mjs +33 -33
  147. package/dist/src/self-relay/demo.mjs +37 -37
  148. package/dist/src/self-relay/device-info.mjs +27 -0
  149. package/dist/src/self-relay/device-store.mjs +92 -84
  150. package/dist/src/self-relay/diagnostics.mjs +54 -54
  151. package/dist/src/self-relay/email-accounts.mjs +183 -183
  152. package/dist/src/self-relay/file-wire.mjs +13 -13
  153. package/dist/src/self-relay/lan-authorizations.mjs +71 -42
  154. package/dist/src/self-relay/lifecycle.mjs +264 -250
  155. package/dist/src/self-relay/long-pairing.mjs +40 -40
  156. package/dist/src/self-relay/mysql-state.mjs +118 -105
  157. package/dist/src/self-relay/p2p-probe.mjs +159 -159
  158. package/dist/src/self-relay/privacy-page.mjs +26 -26
  159. package/dist/src/self-relay/public-releases.mjs +52 -0
  160. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  161. package/dist/src/self-relay/server.mjs +1006 -932
  162. package/dist/src/self-relay/usage-daily.mjs +55 -55
  163. package/dist/src/self-relay/usage-meter.mjs +141 -141
  164. package/dist/src/self-relay/windows-route-helper.mjs +75 -75
  165. package/dist/src/self-relay/wire.mjs +36 -36
  166. package/dist/src/server.mjs +1861 -1778
  167. package/dist/src/service-control.mjs +82 -82
  168. package/dist/src/service-doctor.mjs +110 -110
  169. package/dist/src/service-health.mjs +33 -33
  170. package/dist/src/service-install.mjs +91 -91
  171. package/dist/src/service-lifecycle.mjs +138 -138
  172. package/dist/src/service-restart.mjs +81 -81
  173. package/dist/src/shared-app-server.mjs +615 -611
  174. package/dist/src/shared-catalog.mjs +132 -132
  175. package/dist/src/shared-recovery.mjs +146 -146
  176. package/dist/src/shared-thread-stream.mjs +164 -164
  177. package/dist/src/skill-install.mjs +51 -51
  178. package/dist/src/thread-title-generation.mjs +150 -150
  179. package/dist/src/turn-model-history.mjs +47 -47
  180. package/dist/src/usage-reset.mjs +17 -17
  181. package/dist/src/workstation-downloads.mjs +120 -120
  182. package/dist/tools/rtc-route-helper.ps1 +99 -99
  183. package/dist/tools/start-rtc-route-helper.ps1 +15 -15
  184. package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
  185. package/dist/web/vendor/markdown-it.umd.min.js +13 -13
  186. package/package.json +7 -6
  187. package/tools/postinstall.mjs +127 -127
@@ -1,82 +1,82 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { startBackground } from '../../background-process.mjs';
4
- import { systemRequest } from './system.mjs';
5
- import { assertPlainPath } from '../../skill-install.mjs';
6
- import { setTimeout as delay } from 'node:timers/promises';
7
- import { runtimePaths } from '../runtime-paths.mjs';
8
- import { readRecord } from '../../service-lifecycle.mjs';
9
- export { residentStartup } from './resident-startup.mjs';
10
-
11
- const managedUrl = 'ws://127.0.0.1:45839';
12
- export function createDesktopHost({ request = systemRequest, wait = delay, now = Date.now, timeout = 10000,
13
- stateRoot = () => runtimePaths().stateRoot, fingerprint = file => {
14
- const stat = fs.statSync(path.join(file, 'Contents/Info.plist'));
15
- return `${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
16
- } } = {}) {
17
- return {
18
- desktopIntegration: true,
19
- async inspect() {
20
- const info = await request({ operation: 'desktop-info' });
21
- return { ...info, desktopIdentity: info.desktop ? `${info.desktop}:${info.version}:${info.build}:${fingerprint(info.desktop)}` : null };
22
- },
23
- process: pid => request({ operation: 'process', pid }),
24
- start: startBackground,
25
- async stop(pid, expected) {
26
- if (!expected || expected.pid !== pid) throw new Error('停止内核前缺少进程身份,未结束任何进程。');
27
- return request({ operation: 'stop', expected });
28
- },
29
- async sharedServiceUrl() {
30
- return (await this.inspect()).connectedToManagedServer ? managedUrl : '';
31
- },
32
- async startDesktop(bundlePath, environment) {
33
- if (environment.CODEX_APP_SERVER_WS_URL !== managedUrl) throw new Error('Desktop 内核地址与工作站不一致。');
34
- const runtime = readRecord(path.join(stateRoot(), 'codex-runtime.json'));
35
- const codexHome = environment.CODEX_HOME || runtime?.codexHome;
36
- if (!codexHome || !path.isAbsolute(codexHome)) throw new Error('缺少已选内核的 CODEX_HOME,请先运行 relax setup。');
37
- const info = await this.inspect();
38
- if (info.running) throw new Error('Desktop 仍在运行,未创建第二个实例。');
39
- if (info.desktop !== bundlePath) throw new Error('Codex Desktop 安装位置已变化,请重新检查。');
40
- return request({ operation: 'desktop-start', bundlePath,
41
- environment: { CODEX_HOME: codexHome, CODEX_APP_SERVER_WS_URL: managedUrl } });
42
- },
43
- async closeDesktop(bundlePath) {
44
- const info = await this.inspect();
45
- if (!info.running) return;
46
- if (info.desktop !== bundlePath || !info.process) throw new Error('Desktop 进程身份已变化,未请求退出。');
47
- await request({ operation: 'desktop-close', bundlePath, expected: info.process });
48
- const deadline = now() + timeout;
49
- while ((await this.inspect()).running) {
50
- if (now() >= deadline) throw new Error('Desktop 尚未正常退出,请在 Mac 上处理未保存内容后重试;未强制结束进程。');
51
- await wait(200);
52
- }
53
- },
54
- async desktopReady() {
55
- const info = await this.inspect();
56
- return info.running && info.finishedLaunching && info.connectedToManagedServer;
57
- },
58
- async prepareKernelRestart() {
59
- const info = await this.inspect();
60
- if (!info.running || !info.connectedToManagedServer) return null;
61
- await this.closeDesktop(info.desktop);
62
- return info.desktop;
63
- },
64
- async resumeAfterKernelRestart(bundlePath, environment) {
65
- if (!bundlePath) return;
66
- await this.startDesktop(bundlePath, environment);
67
- const deadline = now() + 30000;
68
- while (!await this.desktopReady()) {
69
- if (now() >= deadline) throw new Error('内核已就绪,但 Mac Desktop 尚未重新接入。');
70
- await wait(500);
71
- }
72
- },
73
- ready: async url => { try { return (await fetch(url, { signal: AbortSignal.timeout(1000) })).ok; } catch { return false; } },
74
- };
75
- }
76
- export const desktopHost = createDesktopHost();
77
- export async function resolveStateRoot(stateRoot) {
78
- assertPlainPath(stateRoot);
79
- fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
80
- return fs.realpathSync(stateRoot);
81
- }
82
- export async function removeDesktopShortcut() { return { removed: false, reason: 'not-used-on-macos' }; }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { startBackground } from '../../background-process.mjs';
4
+ import { systemRequest } from './system.mjs';
5
+ import { assertPlainPath } from '../../skill-install.mjs';
6
+ import { setTimeout as delay } from 'node:timers/promises';
7
+ import { runtimePaths } from '../runtime-paths.mjs';
8
+ import { readRecord } from '../../service-lifecycle.mjs';
9
+ export { residentStartup } from './resident-startup.mjs';
10
+
11
+ const managedUrl = 'ws://127.0.0.1:45839';
12
+ export function createDesktopHost({ request = systemRequest, wait = delay, now = Date.now, timeout = 10000,
13
+ stateRoot = () => runtimePaths().stateRoot, fingerprint = file => {
14
+ const stat = fs.statSync(path.join(file, 'Contents/Info.plist'));
15
+ return `${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
16
+ } } = {}) {
17
+ return {
18
+ desktopIntegration: true,
19
+ async inspect() {
20
+ const info = await request({ operation: 'desktop-info' });
21
+ return { ...info, desktopIdentity: info.desktop ? `${info.desktop}:${info.version}:${info.build}:${fingerprint(info.desktop)}` : null };
22
+ },
23
+ process: pid => request({ operation: 'process', pid }),
24
+ start: startBackground,
25
+ async stop(pid, expected) {
26
+ if (!expected || expected.pid !== pid) throw new Error('停止内核前缺少进程身份,未结束任何进程。');
27
+ return request({ operation: 'stop', expected });
28
+ },
29
+ async sharedServiceUrl() {
30
+ return (await this.inspect()).connectedToManagedServer ? managedUrl : '';
31
+ },
32
+ async startDesktop(bundlePath, environment) {
33
+ if (environment.CODEX_APP_SERVER_WS_URL !== managedUrl) throw new Error('Desktop 内核地址与工作站不一致。');
34
+ const runtime = readRecord(path.join(stateRoot(), 'codex-runtime.json'));
35
+ const codexHome = environment.CODEX_HOME || runtime?.codexHome;
36
+ if (!codexHome || !path.isAbsolute(codexHome)) throw new Error('缺少已选内核的 CODEX_HOME,请先运行 relax setup。');
37
+ const info = await this.inspect();
38
+ if (info.running) throw new Error('Desktop 仍在运行,未创建第二个实例。');
39
+ if (info.desktop !== bundlePath) throw new Error('Codex Desktop 安装位置已变化,请重新检查。');
40
+ return request({ operation: 'desktop-start', bundlePath,
41
+ environment: { CODEX_HOME: codexHome, CODEX_APP_SERVER_WS_URL: managedUrl } });
42
+ },
43
+ async closeDesktop(bundlePath) {
44
+ const info = await this.inspect();
45
+ if (!info.running) return;
46
+ if (info.desktop !== bundlePath || !info.process) throw new Error('Desktop 进程身份已变化,未请求退出。');
47
+ await request({ operation: 'desktop-close', bundlePath, expected: info.process });
48
+ const deadline = now() + timeout;
49
+ while ((await this.inspect()).running) {
50
+ if (now() >= deadline) throw new Error('Desktop 尚未正常退出,请在 Mac 上处理未保存内容后重试;未强制结束进程。');
51
+ await wait(200);
52
+ }
53
+ },
54
+ async desktopReady() {
55
+ const info = await this.inspect();
56
+ return info.running && info.finishedLaunching && info.connectedToManagedServer;
57
+ },
58
+ async prepareKernelRestart() {
59
+ const info = await this.inspect();
60
+ if (!info.running || !info.connectedToManagedServer) return null;
61
+ await this.closeDesktop(info.desktop);
62
+ return info.desktop;
63
+ },
64
+ async resumeAfterKernelRestart(bundlePath, environment) {
65
+ if (!bundlePath) return;
66
+ await this.startDesktop(bundlePath, environment);
67
+ const deadline = now() + 30000;
68
+ while (!await this.desktopReady()) {
69
+ if (now() >= deadline) throw new Error('内核已就绪,但 Mac Desktop 尚未重新接入。');
70
+ await wait(500);
71
+ }
72
+ },
73
+ ready: async url => { try { return (await fetch(url, { signal: AbortSignal.timeout(1000) })).ok; } catch { return false; } },
74
+ };
75
+ }
76
+ export const desktopHost = createDesktopHost();
77
+ export async function resolveStateRoot(stateRoot) {
78
+ assertPlainPath(stateRoot);
79
+ fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
80
+ return fs.realpathSync(stateRoot);
81
+ }
82
+ export async function removeDesktopShortcut() { return { removed: false, reason: 'not-used-on-macos' }; }
@@ -1,10 +1,11 @@
1
- import { desktopHost } from './desktop-host.mjs';
2
- import { withDesktopProjectRegistration as register } from '../desktop-project-store.mjs';
3
-
4
- // Verified against the Mac 26.1007.71326 local-projects/project-order store.
5
- export async function withDesktopProjectRegistration(options, action) {
6
- const host = options.host || desktopHost;
7
- if (!(await host.inspect()).desktop)
8
- return action({ register: async () => ({ status: 'not-applicable', reason: 'desktop-not-installed' }) });
9
- return register({ ...options, host }, action);
10
- }
1
+ import { desktopHost } from './desktop-host.mjs';
2
+ import { withDesktopProjectRegistration as register } from '../desktop-project-store.mjs';
3
+
4
+ // Verified against the Mac 26.1007.71326 local-projects/project-order store.
5
+ export async function withDesktopProjectRegistration(options, action) {
6
+ const host = options.host || desktopHost;
7
+ if (!(await host.inspect()).desktop)
8
+ return action({ register: async () => ({ status: 'not-applicable', reason: 'desktop-not-installed' }),
9
+ remove: async (_project, _operation, _save, removeNative) => { await removeNative(); return { status: 'not-applicable', reason: 'desktop-not-installed' }; } });
10
+ return register({ ...options, host }, action);
11
+ }
@@ -1,28 +1,8 @@
1
- import os from 'node:os';
2
- import net from 'node:net';
3
- import { systemRequest } from './system.mjs';
4
-
5
- const privateIpv4 = address => {
6
- if (net.isIP(address) !== 4) return false;
7
- const [a, b] = address.split('.').map(Number);
8
- return a === 10 || (a === 192 && b === 168) || (a === 172 && b >= 16 && b <= 31) || (a === 100 && b >= 64 && b <= 127);
9
- };
10
- export function networkCandidates(hardware, addresses) {
11
- const results = [];
12
- const seen = new Set();
13
- for (const item of hardware) {
14
- if (!['wifi', 'ethernet'].includes(item.networkType) || typeof item.interfaceName !== 'string') continue;
15
- const label = value => String(value).replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 80);
16
- const interfaceName = label(item.interfaceName), networkName = label(item.networkName || interfaceName);
17
- if (!interfaceName || !networkName) continue;
18
- for (const info of addresses[item.interfaceName] || []) {
19
- if (info.internal || !privateIpv4(info.address) || seen.has(info.address)) continue;
20
- seen.add(info.address);
21
- results.push({ address: info.address, interfaceName, networkName, networkType: item.networkType });
22
- }
23
- }
24
- return results.slice(0, 8);
25
- }
26
- export async function lanNetworks() {
27
- return networkCandidates(await systemRequest({ operation: 'network-interfaces' }), os.networkInterfaces());
28
- }
1
+ import os from 'node:os';
2
+ import { systemRequest } from './system.mjs';
3
+ import { networkCandidates } from '../posix/network-candidates.mjs';
4
+ export { networkCandidates } from '../posix/network-candidates.mjs';
5
+
6
+ export async function lanNetworks() {
7
+ return networkCandidates(await systemRequest({ operation: 'network-interfaces' }), os.networkInterfaces());
8
+ }
@@ -1,8 +1,8 @@
1
- import path from 'node:path';
2
- import { systemRequest } from './system.mjs';
3
-
4
- export async function projectLocations() {
5
- const { home, documents, downloads } = await systemRequest({ operation: 'locations' });
6
- if (![home, documents, downloads].every(value => typeof value === 'string' && path.isAbsolute(value))) throw new Error('Mac 用户目录无效。');
7
- return { home, downloads, defaultParent: path.join(documents, 'ChatGPT'), roots: ['/'] };
8
- }
1
+ import path from 'node:path';
2
+ import { systemRequest } from './system.mjs';
3
+
4
+ export async function projectLocations() {
5
+ const { home, documents, downloads } = await systemRequest({ operation: 'locations' });
6
+ if (![home, documents, downloads].every(value => typeof value === 'string' && path.isAbsolute(value))) throw new Error('Mac 用户目录无效。');
7
+ return { home, downloads, defaultParent: path.join(documents, 'ChatGPT'), roots: ['/'] };
8
+ }
@@ -1,84 +1,84 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { execFile } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { readRecord } from '../../service-lifecycle.mjs';
7
- import { assertPlainPath } from '../../skill-install.mjs';
8
- import { runtimePaths } from '../runtime-paths.mjs';
9
-
10
- const exec = promisify(execFile);
11
- export const launchAgentLabel = 'cn.walkhi.code-relax';
12
- const xml = value => String(value).replace(/[&<>"']/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' }[character]));
13
- export function launchAgentPlist({ serviceRoot, nodeExecutable, stateRoot, home, environment }) {
14
- const entry = path.join(serviceRoot, 'dist/bin/codex-remote.mjs');
15
- // npm adds temporary .bin directories during postinstall. Login must use the
16
- // user's stable tool paths after the source archive has been removed.
17
- const stablePath = String(environment.PATH || '').split(':')
18
- .filter(value => value.startsWith('/') && !value.endsWith('/node_modules/.bin')).join(':');
19
- const variables = { HOME: home, PATH: stablePath || '/usr/bin:/bin:/usr/sbin:/sbin' };
20
- for (const name of ['CODEX_HOME', 'CODEX_INSTALL_DIR', 'CODE_RELAX_CODEX_PATH']) if (environment[name]) variables[name] = environment[name];
21
- const strings = [nodeExecutable, entry, ...Object.values(variables), stateRoot];
22
- if (strings.some(value => /[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(value))) throw new Error('登录启动参数含无效控制字符。');
23
- return `<?xml version="1.0" encoding="UTF-8"?>
24
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
25
- <plist version="1.0"><dict>
26
- <key>Label</key><string>${launchAgentLabel}</string>
27
- <key>ProgramArguments</key><array>${[nodeExecutable, entry, 'start', '--json'].map(value => `<string>${xml(value)}</string>`).join('')}</array>
28
- <key>RunAtLoad</key><true/>
29
- <key>AbandonProcessGroup</key><true/>
30
- <key>EnvironmentVariables</key><dict>${Object.entries(variables).map(([key, value]) => `<key>${key}</key><string>${xml(value)}</string>`).join('')}</dict>
31
- <key>StandardOutPath</key><string>${xml(path.join(stateRoot, 'login.stdout.log'))}</string>
32
- <key>StandardErrorPath</key><string>${xml(path.join(stateRoot, 'login.stderr.log'))}</string>
33
- </dict></plist>
34
- `;
35
- }
36
-
37
- export async function residentStartup({ serviceRoot, nodeExecutable }, action = 'status') {
38
- if (!['status', 'enable', 'disable'].includes(action)) throw new Error('无效的登录启动操作。');
39
- const uid = process.getuid();
40
- if (uid === 0) throw new Error('请以普通 Mac 用户安装 Code Relax,不使用 sudo。');
41
- const home = os.homedir();
42
- const file = path.join(home, 'Library/LaunchAgents', `${launchAgentLabel}.plist`);
43
- const domain = `gui/${uid}`, target = `${domain}/${launchAgentLabel}`;
44
- assertPlainPath(file);
45
- let current;
46
- if (fs.existsSync(file)) {
47
- const { stdout } = await exec('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file], { timeout: 5000 });
48
- current = JSON.parse(stdout);
49
- if (current.Label !== launchAgentLabel || current.ProgramArguments?.[1] !== path.join(serviceRoot, 'dist/bin/codex-remote.mjs'))
50
- throw new Error('同名 LaunchAgent 不属于此安装,未覆盖或移除。');
51
- }
52
- const { stdout: disabled } = await exec('/bin/launchctl', ['print-disabled', domain], { timeout: 5000 });
53
- const isDisabled = /"cn\.walkhi\.code-relax"\s*=>\s*true/.test(disabled);
54
- const expectedArguments = [nodeExecutable, path.join(serviceRoot, 'dist/bin/codex-remote.mjs'), 'start', '--json'];
55
- const enabled = JSON.stringify(current?.ProgramArguments) === JSON.stringify(expectedArguments)
56
- && current.RunAtLoad === true && current.AbandonProcessGroup === true
57
- && current.EnvironmentVariables?.HOME === home && !isDisabled;
58
- if (action === 'status') return { enabled, path: file, mechanism: 'LaunchAgent' };
59
- if (action === 'disable') {
60
- if (!current) return { enabled: false, removed: false, path: file };
61
- let loaded = false;
62
- try { await exec('/bin/launchctl', ['print', target], { timeout: 5000 }); loaded = true; }
63
- catch (error) { if (error.code !== 113) throw error; }
64
- if (loaded) await exec('/bin/launchctl', ['bootout', target], { timeout: 5000 });
65
- await exec('/bin/launchctl', ['disable', target], { timeout: 5000 });
66
- fs.unlinkSync(file);
67
- return { enabled: false, removed: true, path: file };
68
- }
69
- const { stateRoot } = runtimePaths();
70
- const runtime = readRecord(path.join(stateRoot, 'codex-runtime.json'));
71
- const environment = { ...process.env, ...(runtime?.codexHome ? { CODEX_HOME: runtime.codexHome } : {}) };
72
- fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
73
- fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
74
- const temporary = `${file}.${process.pid}.tmp`;
75
- try {
76
- fs.writeFileSync(temporary, launchAgentPlist({ serviceRoot, nodeExecutable, stateRoot, home, environment }), { flag: 'wx', mode: 0o600 });
77
- await exec('/usr/bin/plutil', ['-lint', temporary], { timeout: 5000 });
78
- fs.renameSync(temporary, file);
79
- } finally { if (fs.existsSync(temporary)) fs.unlinkSync(temporary); }
80
- await exec('/bin/launchctl', ['enable', target], { timeout: 5000 });
81
- // setup/start already launches the service. Bootstrap would run a second start
82
- // concurrently; launchd will load this user agent at the next GUI login.
83
- return { enabled: true, path: file, mechanism: 'LaunchAgent', appliesAtNextLogin: true };
84
- }
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { readRecord } from '../../service-lifecycle.mjs';
7
+ import { assertPlainPath } from '../../skill-install.mjs';
8
+ import { runtimePaths } from '../runtime-paths.mjs';
9
+
10
+ const exec = promisify(execFile);
11
+ export const launchAgentLabel = 'cn.walkhi.code-relax';
12
+ const xml = value => String(value).replace(/[&<>"']/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' }[character]));
13
+ export function launchAgentPlist({ serviceRoot, nodeExecutable, stateRoot, home, environment }) {
14
+ const entry = path.join(serviceRoot, 'dist/bin/codex-remote.mjs');
15
+ // npm adds temporary .bin directories during postinstall. Login must use the
16
+ // user's stable tool paths after the source archive has been removed.
17
+ const stablePath = String(environment.PATH || '').split(':')
18
+ .filter(value => value.startsWith('/') && !value.endsWith('/node_modules/.bin')).join(':');
19
+ const variables = { HOME: home, PATH: stablePath || '/usr/bin:/bin:/usr/sbin:/sbin' };
20
+ for (const name of ['CODEX_HOME', 'CODEX_INSTALL_DIR', 'CODE_RELAX_CODEX_PATH']) if (environment[name]) variables[name] = environment[name];
21
+ const strings = [nodeExecutable, entry, ...Object.values(variables), stateRoot];
22
+ if (strings.some(value => /[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(value))) throw new Error('登录启动参数含无效控制字符。');
23
+ return `<?xml version="1.0" encoding="UTF-8"?>
24
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
25
+ <plist version="1.0"><dict>
26
+ <key>Label</key><string>${launchAgentLabel}</string>
27
+ <key>ProgramArguments</key><array>${[nodeExecutable, entry, 'start', '--json'].map(value => `<string>${xml(value)}</string>`).join('')}</array>
28
+ <key>RunAtLoad</key><true/>
29
+ <key>AbandonProcessGroup</key><true/>
30
+ <key>EnvironmentVariables</key><dict>${Object.entries(variables).map(([key, value]) => `<key>${key}</key><string>${xml(value)}</string>`).join('')}</dict>
31
+ <key>StandardOutPath</key><string>${xml(path.join(stateRoot, 'login.stdout.log'))}</string>
32
+ <key>StandardErrorPath</key><string>${xml(path.join(stateRoot, 'login.stderr.log'))}</string>
33
+ </dict></plist>
34
+ `;
35
+ }
36
+
37
+ export async function residentStartup({ serviceRoot, nodeExecutable }, action = 'status') {
38
+ if (!['status', 'enable', 'disable'].includes(action)) throw new Error('无效的登录启动操作。');
39
+ const uid = process.getuid();
40
+ if (uid === 0) throw new Error('请以普通 Mac 用户安装 Code Relax,不使用 sudo。');
41
+ const home = os.homedir();
42
+ const file = path.join(home, 'Library/LaunchAgents', `${launchAgentLabel}.plist`);
43
+ const domain = `gui/${uid}`, target = `${domain}/${launchAgentLabel}`;
44
+ assertPlainPath(file);
45
+ let current;
46
+ if (fs.existsSync(file)) {
47
+ const { stdout } = await exec('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file], { timeout: 5000 });
48
+ current = JSON.parse(stdout);
49
+ if (current.Label !== launchAgentLabel || current.ProgramArguments?.[1] !== path.join(serviceRoot, 'dist/bin/codex-remote.mjs'))
50
+ throw new Error('同名 LaunchAgent 不属于此安装,未覆盖或移除。');
51
+ }
52
+ const { stdout: disabled } = await exec('/bin/launchctl', ['print-disabled', domain], { timeout: 5000 });
53
+ const isDisabled = /"cn\.walkhi\.code-relax"\s*=>\s*true/.test(disabled);
54
+ const expectedArguments = [nodeExecutable, path.join(serviceRoot, 'dist/bin/codex-remote.mjs'), 'start', '--json'];
55
+ const enabled = JSON.stringify(current?.ProgramArguments) === JSON.stringify(expectedArguments)
56
+ && current.RunAtLoad === true && current.AbandonProcessGroup === true
57
+ && current.EnvironmentVariables?.HOME === home && !isDisabled;
58
+ if (action === 'status') return { enabled, path: file, mechanism: 'LaunchAgent' };
59
+ if (action === 'disable') {
60
+ if (!current) return { enabled: false, removed: false, path: file };
61
+ let loaded = false;
62
+ try { await exec('/bin/launchctl', ['print', target], { timeout: 5000 }); loaded = true; }
63
+ catch (error) { if (error.code !== 113) throw error; }
64
+ if (loaded) await exec('/bin/launchctl', ['bootout', target], { timeout: 5000 });
65
+ await exec('/bin/launchctl', ['disable', target], { timeout: 5000 });
66
+ fs.unlinkSync(file);
67
+ return { enabled: false, removed: true, path: file };
68
+ }
69
+ const { stateRoot } = runtimePaths();
70
+ const runtime = readRecord(path.join(stateRoot, 'codex-runtime.json'));
71
+ const environment = { ...process.env, ...(runtime?.codexHome ? { CODEX_HOME: runtime.codexHome } : {}) };
72
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
73
+ fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
74
+ const temporary = `${file}.${process.pid}.tmp`;
75
+ try {
76
+ fs.writeFileSync(temporary, launchAgentPlist({ serviceRoot, nodeExecutable, stateRoot, home, environment }), { flag: 'wx', mode: 0o600 });
77
+ await exec('/usr/bin/plutil', ['-lint', temporary], { timeout: 5000 });
78
+ fs.renameSync(temporary, file);
79
+ } finally { if (fs.existsSync(temporary)) fs.unlinkSync(temporary); }
80
+ await exec('/bin/launchctl', ['enable', target], { timeout: 5000 });
81
+ // setup/start already launches the service. Bootstrap would run a second start
82
+ // concurrently; launchd will load this user agent at the next GUI login.
83
+ return { enabled: true, path: file, mechanism: 'LaunchAgent', appliesAtNextLogin: true };
84
+ }