@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.59

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 (101) hide show
  1. package/README.md +105 -105
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +403 -403
  4. package/dist/bin/publish-android-release.mjs +63 -63
  5. package/dist/bin/release-upload-server.mjs +190 -190
  6. package/dist/bin/set-relay-admin-password.mjs +11 -0
  7. package/dist/build-platform.json +1 -1
  8. package/dist/get-shared-service-url.ps1 +14 -14
  9. package/dist/shared/code-browser.cjs +159 -159
  10. package/dist/shared/file-changes.cjs +31 -31
  11. package/dist/shared/file-citations.cjs +117 -117
  12. package/dist/shared/file-mentions.cjs +92 -92
  13. package/dist/shared/message-delivery.cjs +115 -115
  14. package/dist/shared/p2p-file-channel.cjs +87 -87
  15. package/dist/shared/release-version.cjs +31 -0
  16. package/dist/shared/server-requests.cjs +311 -311
  17. package/dist/shared/timeline-sync.cjs +173 -173
  18. package/dist/skills/code-relax/SKILL.md +22 -22
  19. package/dist/src/bounded-process-log.mjs +63 -63
  20. package/dist/src/celsius-client.mjs +107 -107
  21. package/dist/src/dots-approval-timeline.mjs +99 -99
  22. package/dist/src/dots-avatar.mjs +42 -42
  23. package/dist/src/dots-chat-projection.mjs +50 -50
  24. package/dist/src/dots-chat-server.mjs +483 -483
  25. package/dist/src/dots-message-images.mjs +319 -319
  26. package/dist/src/dots-profile.mjs +77 -77
  27. package/dist/src/dots-room-activity.mjs +172 -172
  28. package/dist/src/dots-room-client.mjs +137 -135
  29. package/dist/src/dots-room-projection.mjs +94 -94
  30. package/dist/src/dots-schedules.mjs +66 -66
  31. package/dist/src/dots-summary-tasks.mjs +32 -32
  32. package/dist/src/dots-task-catalog.mjs +430 -430
  33. package/dist/src/file-transfer-range.mjs +22 -22
  34. package/dist/src/file-upload-session.mjs +146 -130
  35. package/dist/src/hosted-history-app-server.mjs +103 -103
  36. package/dist/src/hosted-history-cache.mjs +113 -113
  37. package/dist/src/lan-socket-server.mjs +338 -266
  38. package/dist/src/managed-app-server.mjs +373 -373
  39. package/dist/src/managed-endpoint.mjs +25 -25
  40. package/dist/src/message-upload-files.mjs +109 -109
  41. package/dist/src/onboarding.mjs +12 -12
  42. package/dist/src/platform/README.md +1 -1
  43. package/dist/src/platform/codex-protocol.mjs +46 -46
  44. package/dist/src/platform/desktop-project-store.mjs +219 -208
  45. package/dist/src/platform/linux/desktop-host.mjs +87 -87
  46. package/dist/src/platform/linux/process.mjs +53 -53
  47. package/dist/src/platform/linux/system.mjs +50 -50
  48. package/dist/src/platform/linux/user-environment.mjs +9 -9
  49. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  50. package/dist/src/platform/macos/desktop-host.mjs +86 -86
  51. package/dist/src/platform/macos/user-environment.mjs +9 -9
  52. package/dist/src/platform/native-package.mjs +58 -58
  53. package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
  54. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  55. package/dist/src/platform/windows/credential-store.mjs +27 -27
  56. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  57. package/dist/src/platform/windows/desktop-host.mjs +65 -65
  58. package/dist/src/platform/windows/desktop-info.ps1 +38 -38
  59. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  60. package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
  61. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  62. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  63. package/dist/src/platform/windows/project-locations.mjs +16 -16
  64. package/dist/src/platform/windows/service-host.mjs +35 -35
  65. package/dist/src/platform/windows/user-environment.mjs +52 -52
  66. package/dist/src/project-management.mjs +58 -58
  67. package/dist/src/runtime-discovery.mjs +47 -47
  68. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  69. package/dist/src/self-relay/admin-auth.mjs +59 -0
  70. package/dist/src/self-relay/admin-login-page.mjs +7 -0
  71. package/dist/src/self-relay/admin-page.mjs +87 -80
  72. package/dist/src/self-relay/admin-server.mjs +49 -18
  73. package/dist/src/self-relay/admin-style.mjs +58 -14
  74. package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
  75. package/dist/src/self-relay/admin-workstations.mjs +143 -0
  76. package/dist/src/self-relay/connection-reporting.mjs +38 -0
  77. package/dist/src/self-relay/connector.mjs +444 -439
  78. package/dist/src/self-relay/demo.mjs +31 -31
  79. package/dist/src/self-relay/diagnostics.mjs +54 -54
  80. package/dist/src/self-relay/email-accounts.mjs +183 -183
  81. package/dist/src/self-relay/file-wire.mjs +13 -13
  82. package/dist/src/self-relay/lifecycle.mjs +11 -0
  83. package/dist/src/self-relay/p2p-probe.mjs +168 -168
  84. package/dist/src/self-relay/phone-connections.mjs +55 -0
  85. package/dist/src/self-relay/privacy-page.mjs +26 -26
  86. package/dist/src/self-relay/public-releases.mjs +67 -67
  87. package/dist/src/self-relay/rtc-file-bridge.mjs +46 -41
  88. package/dist/src/self-relay/server.mjs +63 -3
  89. package/dist/src/self-relay/usage-meter.mjs +141 -141
  90. package/dist/src/server.mjs +23 -9
  91. package/dist/src/service-doctor.mjs +148 -148
  92. package/dist/src/service-install.mjs +97 -97
  93. package/dist/src/service-lifecycle.mjs +139 -139
  94. package/dist/src/service-update.mjs +6 -3
  95. package/dist/src/shared-app-server.mjs +683 -666
  96. package/dist/src/shared-catalog.mjs +132 -132
  97. package/dist/src/shared-thread-stream.mjs +165 -165
  98. package/dist/src/usage-reset.mjs +17 -17
  99. package/dist/src/workstation-downloads.mjs +120 -120
  100. package/package.json +4 -4
  101. package/tools/postinstall.mjs +186 -186
@@ -1,186 +1,186 @@
1
- import { execFileSync } from 'node:child_process';
2
- import fs from 'node:fs';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- // The same path adapter is available in published dist and in a source checkout.
8
- const pathsModule = new URL('../dist/src/platform/runtime-paths.mjs', import.meta.url);
9
- const { runtimePaths } = await import(fs.existsSync(pathsModule) ? pathsModule : new URL('../src/platform/runtime-paths.mjs', import.meta.url));
10
- const { serviceRoot, stateRoot } = runtimePaths();
11
- const installLog = path.join(stateRoot, 'install.log');
12
-
13
- function sanitizeLog(value) {
14
- let result = String(value).replace(/npm_[A-Za-z0-9]+/gu, '[redacted npm token]')
15
- .replace(/([?&]token=)[^&\s"']+/giu, '$1[redacted]');
16
- const paths = [['LOCALAPPDATA', process.env.LOCALAPPDATA], ['APPDATA', process.env.APPDATA],
17
- ['USERPROFILE', process.env.USERPROFILE], ['TEMP', process.env.TEMP], ['TMP', process.env.TMP]]
18
- .filter(([, item]) => item).sort((left, right) => right[1].length - left[1].length);
19
- for (const [name, item] of paths) {
20
- const pattern = item.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
21
- result = result.replace(new RegExp(pattern, 'giu'), `%${name}%`);
22
- }
23
- return result;
24
- }
25
-
26
- function appendLog(message) {
27
- fs.mkdirSync(stateRoot, { recursive: true });
28
- fs.appendFileSync(installLog, `${new Date().toISOString()} ${sanitizeLog(message)}\n`, 'utf8');
29
- }
30
-
31
- function systemSummary() {
32
- const npm = String(process.env.npm_config_user_agent || 'unknown').replace(/[\r\n]/gu, ' ');
33
- return `platform=${process.platform} osRelease=${os.release()} osVersion=${os.version()} `
34
- + `osArch=${os.arch()} processArch=${process.arch} node=${process.version} npmUserAgent=${npm}`;
35
- }
36
-
37
- function runStage(entry, stage, args, formatOutput = value => value) {
38
- appendLog(`[${stage}] start`);
39
- try {
40
- const output = execFileSync(process.execPath, [entry, ...args], { encoding: 'utf8', windowsHide: true,
41
- stdio: ['inherit', 'pipe', 'pipe'] });
42
- if (output) { process.stdout.write(formatOutput(output)); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
43
- appendLog(`[${stage}] success`);
44
- } catch (error) {
45
- if (error?.stdout) { process.stdout.write(error.stdout); appendLog(`[${stage}] stdout\n${String(error.stdout).trimEnd()}`); }
46
- if (error?.stderr) { process.stderr.write(error.stderr); appendLog(`[${stage}] stderr\n${String(error.stderr).trimEnd()}`); }
47
- appendLog(`[${stage}] failed: ${error?.stack || error?.message || String(error)}`);
48
- throw error;
49
- }
50
- }
51
-
52
- export function formatSetupOutput(output) {
53
- try {
54
- const result = JSON.parse(output);
55
- const migration = result.managedAppServerMigration || {};
56
- return `Code Relax ${result.version || '未知版本'} setup 已完成;接下来执行安装后健康检查。\n`
57
- + '工作站服务和 Codex 内核已就绪,已启用登录自启。\n'
58
- + (result.preserveCodex ? '过渡版保留当前 Codex 和内核运行状态;待手机发起下一次完整更新。\n' : migration.restarted ? `Codex 内核已更新:${migration.previousVersion || '未知'} → ${result.managedAppServer?.version || '未知'}(PID ${migration.previousPid} → ${migration.currentPid})。\n` : 'Codex 内核已是当前选定版本,无需切换。\n')
59
- + (result.preserveCodex ? '' : result.desktopRestarted ? '电脑版 Codex 已重启,并确认连接到工作站内核。\n' : '安装前未运行电脑版 Codex,保持关闭。\n')
60
- + (result.desktopRestartRecommended ? 'Codex Desktop:请完全退出后重新打开。\n' : '');
61
- } catch { return output; }
62
- }
63
-
64
- // npm owns command links. Inspect them without changing the user's shell configuration.
65
- export function commandDiagnostics(entry, { prefix, env = process.env, platform = process.platform } = {}) {
66
- const windows = platform === 'win32';
67
- const bin = windows ? prefix : path.join(prefix, 'bin');
68
- const command = path.join(bin, windows ? 'relax.cmd' : 'relax');
69
- const lines = [`命令入口:${command}`];
70
- let linkReady = false;
71
- try {
72
- if (windows) {
73
- const shim = fs.readFileSync(command, 'utf8');
74
- linkReady = shim.replaceAll('\\', '/').includes('node_modules/@walkhi/code-relax/dist/bin/codex-remote.mjs') && fs.existsSync(entry);
75
- } else {
76
- linkReady = fs.realpathSync(command) === fs.realpathSync(entry);
77
- fs.accessSync(command, fs.constants.X_OK);
78
- }
79
- lines.push(linkReady ? '命令链接:正常' : '命令链接:目标与本次安装不一致');
80
- } catch (error) {
81
- linkReady = false;
82
- lines.push(`命令链接:不可用(${error.code || error.message})`);
83
- }
84
- const normalize = value => {
85
- let resolved;
86
- try { resolved = fs.realpathSync(value); } catch { resolved = path.resolve(value); }
87
- return windows ? resolved.toLowerCase() : resolved;
88
- };
89
- const pathValue = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] || '';
90
- const onPath = pathValue.split(windows ? ';' : ':').filter(Boolean)
91
- .some(value => normalize(value.replace(/^"|"$/g, '')) === normalize(bin));
92
- lines.push(`安装进程 PATH:${onPath ? '包含' : '未包含'}全局命令目录`);
93
- lines.push('注:npm 会调整安装进程 PATH;当前终端能否直接找到命令仍以终端为准。');
94
- if (!linkReady) lines.push('CLI 入口异常;服务状态请以上方健康检查为准,请保留本次诊断输出。');
95
- else if (!onPath) lines.push('全局命令目录未在 PATH 中,可先使用下方完整路径命令。');
96
- const quote = value => "'" + value.replaceAll("'", windows ? "''" : "'\"'\"'") + "'";
97
- const direct = `${windows ? '& ' : ''}${quote(process.execPath)} ${quote(entry)}`;
98
- lines.push(`无需依赖 relax 命令的检查入口${windows ? '(PowerShell)' : ''}:`, `${direct} doctor`);
99
- lines.push('生成配对二维码:', `${direct} pair`);
100
- return lines.join('\n') + '\n';
101
- }
102
-
103
- function installationSnapshot() {
104
- const recordPath = path.join(serviceRoot, 'installation.json');
105
- if (!fs.existsSync(serviceRoot)) return { exists: false, valid: false };
106
- try {
107
- const record = JSON.parse(fs.readFileSync(recordPath, 'utf8').replace(/^\uFEFF/u, ''));
108
- return { exists: true, valid: Boolean(record?.installedAt && record?.version),
109
- version: record?.version, installedAt: record?.installedAt };
110
- } catch {
111
- return { exists: true, valid: false };
112
- }
113
- }
114
-
115
- function sameInstallation(left, right) {
116
- return left.valid && right.valid && left.version === right.version && left.installedAt === right.installedAt;
117
- }
118
-
119
- function setupFailureGuidance(previous, current) {
120
- if (previous.valid && sameInstallation(previous, current)) {
121
- return `检测到旧安装 ${previous.version};新版本部署未完成,旧安装记录仍保留。\n`
122
- + '请先执行 relax doctor;修复报错原因后重新运行 npm 安装命令,无需卸载旧版本。';
123
- }
124
- if (previous.valid && current.valid) {
125
- return `检测到旧安装 ${previous.version};服务文件已更新为 ${current.version},但 setup 后续步骤失败。\n`
126
- + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;无需先卸载。';
127
- }
128
- if (previous.exists && current.valid) {
129
- return `检测到既有服务目录,但无法确认有效的旧安装记录;服务文件现已写入 ${current.version},setup 后续步骤仍失败。\n`
130
- + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;不要直接删除配置目录。';
131
- }
132
- if (previous.exists) {
133
- return '检测到既有服务目录,但无法确认有效的旧安装记录;本次部署未完成。\n'
134
- + '请执行 relax doctor,根据检查结果修复后重新运行 npm 安装命令,不要直接删除配置目录。';
135
- }
136
- if (current.valid) {
137
- return `这是首次安装;服务文件 ${current.version} 已写入,但 setup 后续步骤失败。\n`
138
- + '如果 relax 命令仍可用,请执行 relax doctor,再执行 relax setup;否则修复原因后重新运行 npm 安装命令。';
139
- }
140
- return '这是首次安装,尚未形成可确认的固定服务。\n修复上方错误后重新运行 npm 安装命令;无需执行卸载。';
141
- }
142
-
143
- const invokedDirectly = process.argv[1]
144
- && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
145
- if (invokedDirectly && process.env.npm_config_global === 'true') {
146
- const entry = fileURLToPath(new URL('../dist/bin/codex-remote.mjs', import.meta.url));
147
- const previous = installationSnapshot();
148
- appendLog(`[postinstall] start package=${process.env.npm_package_version || 'unknown'} ${systemSummary()}`);
149
- let setupStarted = false;
150
- try {
151
- const { prepareNativePackage } = await import('../dist/src/platform/native-package.mjs');
152
- prepareNativePackage(fileURLToPath(new URL('../', import.meta.url)));
153
- setupStarted = true;
154
- runStage(entry, 'setup', ['setup', '--json'], output => formatSetupOutput(output));
155
- try {
156
- runStage(entry, 'doctor', ['doctor']);
157
- console.log('Code Relax 自动安装成功:setup 与安装后健康检查均已通过,无需重复执行 setup。');
158
- console.log('npm 的脚本安全提示与安装结果分别判断;本次安装脚本已实际执行,未修改 npm 安全配置。');
159
- }
160
- catch (error) {
161
- console.error('服务安装已完成,但安装后健康检查未通过;请查看上方诊断与安装日志。');
162
- process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
163
- }
164
- console.log(`安装日志:${installLog}`);
165
- appendLog(process.exitCode ? '[postinstall] failed stage=doctor' : '[postinstall] success');
166
- } catch (error) {
167
- const current = installationSnapshot();
168
- if (!setupStarted) console.error(`平台组件检查失败:${error.message}`);
169
- console.error(`\nCode Relax 安装未完全完成:\n${setupFailureGuidance(previous, current)}`);
170
- if (error?.code && error.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') console.error(`启动安装步骤失败:${error.message}`);
171
- console.error(`安装日志:${installLog}`);
172
- appendLog('[postinstall] failed stage=setup');
173
- process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
174
- } finally {
175
- try {
176
- const prefix = execFileSync(process.execPath, [process.env.npm_execpath, 'prefix', '--global'],
177
- { encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
178
- const output = commandDiagnostics(entry, { prefix });
179
- process.stdout.write(output);
180
- appendLog(`[command-diagnostics]\n${output}`);
181
- } catch (error) {
182
- console.error(`命令入口诊断失败:${error.message}`);
183
- appendLog(`[command-diagnostics] failed: ${error.message}`);
184
- }
185
- }
186
- }
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ // The same path adapter is available in published dist and in a source checkout.
8
+ const pathsModule = new URL('../dist/src/platform/runtime-paths.mjs', import.meta.url);
9
+ const { runtimePaths } = await import(fs.existsSync(pathsModule) ? pathsModule : new URL('../src/platform/runtime-paths.mjs', import.meta.url));
10
+ const { serviceRoot, stateRoot } = runtimePaths();
11
+ const installLog = path.join(stateRoot, 'install.log');
12
+
13
+ function sanitizeLog(value) {
14
+ let result = String(value).replace(/npm_[A-Za-z0-9]+/gu, '[redacted npm token]')
15
+ .replace(/([?&]token=)[^&\s"']+/giu, '$1[redacted]');
16
+ const paths = [['LOCALAPPDATA', process.env.LOCALAPPDATA], ['APPDATA', process.env.APPDATA],
17
+ ['USERPROFILE', process.env.USERPROFILE], ['TEMP', process.env.TEMP], ['TMP', process.env.TMP]]
18
+ .filter(([, item]) => item).sort((left, right) => right[1].length - left[1].length);
19
+ for (const [name, item] of paths) {
20
+ const pattern = item.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
21
+ result = result.replace(new RegExp(pattern, 'giu'), `%${name}%`);
22
+ }
23
+ return result;
24
+ }
25
+
26
+ function appendLog(message) {
27
+ fs.mkdirSync(stateRoot, { recursive: true });
28
+ fs.appendFileSync(installLog, `${new Date().toISOString()} ${sanitizeLog(message)}\n`, 'utf8');
29
+ }
30
+
31
+ function systemSummary() {
32
+ const npm = String(process.env.npm_config_user_agent || 'unknown').replace(/[\r\n]/gu, ' ');
33
+ return `platform=${process.platform} osRelease=${os.release()} osVersion=${os.version()} `
34
+ + `osArch=${os.arch()} processArch=${process.arch} node=${process.version} npmUserAgent=${npm}`;
35
+ }
36
+
37
+ function runStage(entry, stage, args, formatOutput = value => value) {
38
+ appendLog(`[${stage}] start`);
39
+ try {
40
+ const output = execFileSync(process.execPath, [entry, ...args], { encoding: 'utf8', windowsHide: true,
41
+ stdio: ['inherit', 'pipe', 'pipe'] });
42
+ if (output) { process.stdout.write(formatOutput(output)); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
43
+ appendLog(`[${stage}] success`);
44
+ } catch (error) {
45
+ if (error?.stdout) { process.stdout.write(error.stdout); appendLog(`[${stage}] stdout\n${String(error.stdout).trimEnd()}`); }
46
+ if (error?.stderr) { process.stderr.write(error.stderr); appendLog(`[${stage}] stderr\n${String(error.stderr).trimEnd()}`); }
47
+ appendLog(`[${stage}] failed: ${error?.stack || error?.message || String(error)}`);
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ export function formatSetupOutput(output) {
53
+ try {
54
+ const result = JSON.parse(output);
55
+ const migration = result.managedAppServerMigration || {};
56
+ return `Code Relax ${result.version || '未知版本'} setup 已完成;接下来执行安装后健康检查。\n`
57
+ + '工作站服务和 Codex 内核已就绪,已启用登录自启。\n'
58
+ + (result.preserveCodex ? '过渡版保留当前 Codex 和内核运行状态;待手机发起下一次完整更新。\n' : migration.restarted ? `Codex 内核已更新:${migration.previousVersion || '未知'} → ${result.managedAppServer?.version || '未知'}(PID ${migration.previousPid} → ${migration.currentPid})。\n` : 'Codex 内核已是当前选定版本,无需切换。\n')
59
+ + (result.preserveCodex ? '' : result.desktopRestarted ? '电脑版 Codex 已重启,并确认连接到工作站内核。\n' : '安装前未运行电脑版 Codex,保持关闭。\n')
60
+ + (result.desktopRestartRecommended ? 'Codex Desktop:请完全退出后重新打开。\n' : '');
61
+ } catch { return output; }
62
+ }
63
+
64
+ // npm owns command links. Inspect them without changing the user's shell configuration.
65
+ export function commandDiagnostics(entry, { prefix, env = process.env, platform = process.platform } = {}) {
66
+ const windows = platform === 'win32';
67
+ const bin = windows ? prefix : path.join(prefix, 'bin');
68
+ const command = path.join(bin, windows ? 'relax.cmd' : 'relax');
69
+ const lines = [`命令入口:${command}`];
70
+ let linkReady = false;
71
+ try {
72
+ if (windows) {
73
+ const shim = fs.readFileSync(command, 'utf8');
74
+ linkReady = shim.replaceAll('\\', '/').includes('node_modules/@walkhi/code-relax/dist/bin/codex-remote.mjs') && fs.existsSync(entry);
75
+ } else {
76
+ linkReady = fs.realpathSync(command) === fs.realpathSync(entry);
77
+ fs.accessSync(command, fs.constants.X_OK);
78
+ }
79
+ lines.push(linkReady ? '命令链接:正常' : '命令链接:目标与本次安装不一致');
80
+ } catch (error) {
81
+ linkReady = false;
82
+ lines.push(`命令链接:不可用(${error.code || error.message})`);
83
+ }
84
+ const normalize = value => {
85
+ let resolved;
86
+ try { resolved = fs.realpathSync(value); } catch { resolved = path.resolve(value); }
87
+ return windows ? resolved.toLowerCase() : resolved;
88
+ };
89
+ const pathValue = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] || '';
90
+ const onPath = pathValue.split(windows ? ';' : ':').filter(Boolean)
91
+ .some(value => normalize(value.replace(/^"|"$/g, '')) === normalize(bin));
92
+ lines.push(`安装进程 PATH:${onPath ? '包含' : '未包含'}全局命令目录`);
93
+ lines.push('注:npm 会调整安装进程 PATH;当前终端能否直接找到命令仍以终端为准。');
94
+ if (!linkReady) lines.push('CLI 入口异常;服务状态请以上方健康检查为准,请保留本次诊断输出。');
95
+ else if (!onPath) lines.push('全局命令目录未在 PATH 中,可先使用下方完整路径命令。');
96
+ const quote = value => "'" + value.replaceAll("'", windows ? "''" : "'\"'\"'") + "'";
97
+ const direct = `${windows ? '& ' : ''}${quote(process.execPath)} ${quote(entry)}`;
98
+ lines.push(`无需依赖 relax 命令的检查入口${windows ? '(PowerShell)' : ''}:`, `${direct} doctor`);
99
+ lines.push('生成配对二维码:', `${direct} pair`);
100
+ return lines.join('\n') + '\n';
101
+ }
102
+
103
+ function installationSnapshot() {
104
+ const recordPath = path.join(serviceRoot, 'installation.json');
105
+ if (!fs.existsSync(serviceRoot)) return { exists: false, valid: false };
106
+ try {
107
+ const record = JSON.parse(fs.readFileSync(recordPath, 'utf8').replace(/^\uFEFF/u, ''));
108
+ return { exists: true, valid: Boolean(record?.installedAt && record?.version),
109
+ version: record?.version, installedAt: record?.installedAt };
110
+ } catch {
111
+ return { exists: true, valid: false };
112
+ }
113
+ }
114
+
115
+ function sameInstallation(left, right) {
116
+ return left.valid && right.valid && left.version === right.version && left.installedAt === right.installedAt;
117
+ }
118
+
119
+ function setupFailureGuidance(previous, current) {
120
+ if (previous.valid && sameInstallation(previous, current)) {
121
+ return `检测到旧安装 ${previous.version};新版本部署未完成,旧安装记录仍保留。\n`
122
+ + '请先执行 relax doctor;修复报错原因后重新运行 npm 安装命令,无需卸载旧版本。';
123
+ }
124
+ if (previous.valid && current.valid) {
125
+ return `检测到旧安装 ${previous.version};服务文件已更新为 ${current.version},但 setup 后续步骤失败。\n`
126
+ + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;无需先卸载。';
127
+ }
128
+ if (previous.exists && current.valid) {
129
+ return `检测到既有服务目录,但无法确认有效的旧安装记录;服务文件现已写入 ${current.version},setup 后续步骤仍失败。\n`
130
+ + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;不要直接删除配置目录。';
131
+ }
132
+ if (previous.exists) {
133
+ return '检测到既有服务目录,但无法确认有效的旧安装记录;本次部署未完成。\n'
134
+ + '请执行 relax doctor,根据检查结果修复后重新运行 npm 安装命令,不要直接删除配置目录。';
135
+ }
136
+ if (current.valid) {
137
+ return `这是首次安装;服务文件 ${current.version} 已写入,但 setup 后续步骤失败。\n`
138
+ + '如果 relax 命令仍可用,请执行 relax doctor,再执行 relax setup;否则修复原因后重新运行 npm 安装命令。';
139
+ }
140
+ return '这是首次安装,尚未形成可确认的固定服务。\n修复上方错误后重新运行 npm 安装命令;无需执行卸载。';
141
+ }
142
+
143
+ const invokedDirectly = process.argv[1]
144
+ && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
145
+ if (invokedDirectly && process.env.npm_config_global === 'true') {
146
+ const entry = fileURLToPath(new URL('../dist/bin/codex-remote.mjs', import.meta.url));
147
+ const previous = installationSnapshot();
148
+ appendLog(`[postinstall] start package=${process.env.npm_package_version || 'unknown'} ${systemSummary()}`);
149
+ let setupStarted = false;
150
+ try {
151
+ const { prepareNativePackage } = await import('../dist/src/platform/native-package.mjs');
152
+ prepareNativePackage(fileURLToPath(new URL('../', import.meta.url)));
153
+ setupStarted = true;
154
+ runStage(entry, 'setup', ['setup', '--json'], output => formatSetupOutput(output));
155
+ try {
156
+ runStage(entry, 'doctor', ['doctor']);
157
+ console.log('Code Relax 自动安装成功:setup 与安装后健康检查均已通过,无需重复执行 setup。');
158
+ console.log('npm 的脚本安全提示与安装结果分别判断;本次安装脚本已实际执行,未修改 npm 安全配置。');
159
+ }
160
+ catch (error) {
161
+ console.error('服务安装已完成,但安装后健康检查未通过;请查看上方诊断与安装日志。');
162
+ process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
163
+ }
164
+ console.log(`安装日志:${installLog}`);
165
+ appendLog(process.exitCode ? '[postinstall] failed stage=doctor' : '[postinstall] success');
166
+ } catch (error) {
167
+ const current = installationSnapshot();
168
+ if (!setupStarted) console.error(`平台组件检查失败:${error.message}`);
169
+ console.error(`\nCode Relax 安装未完全完成:\n${setupFailureGuidance(previous, current)}`);
170
+ if (error?.code && error.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') console.error(`启动安装步骤失败:${error.message}`);
171
+ console.error(`安装日志:${installLog}`);
172
+ appendLog('[postinstall] failed stage=setup');
173
+ process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
174
+ } finally {
175
+ try {
176
+ const prefix = execFileSync(process.execPath, [process.env.npm_execpath, 'prefix', '--global'],
177
+ { encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
178
+ const output = commandDiagnostics(entry, { prefix });
179
+ process.stdout.write(output);
180
+ appendLog(`[command-diagnostics]\n${output}`);
181
+ } catch (error) {
182
+ console.error(`命令入口诊断失败:${error.message}`);
183
+ appendLog(`[command-diagnostics] failed: ${error.message}`);
184
+ }
185
+ }
186
+ }