@walkhi/code-relax 0.1.0-beta.1 → 0.1.0-beta.11

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 (80) hide show
  1. package/README.md +53 -64
  2. package/dist/bin/codex-remote.mjs +90 -71
  3. package/dist/bin/self-relay-admin.mjs +8 -0
  4. package/dist/bin/self-relay-demo.mjs +1 -1
  5. package/dist/bin/self-relay-server.mjs +5 -2
  6. package/dist/shared/app-server-events.cjs +64 -24
  7. package/dist/shared/file-mentions.cjs +35 -0
  8. package/dist/shared/p2p-data-channel.cjs +50 -0
  9. package/dist/skills/code-relax/SKILL.md +1 -1
  10. package/dist/src/desktop-launcher.mjs +16 -21
  11. package/dist/src/file-upload-session.mjs +124 -0
  12. package/dist/src/lan-socket-server.mjs +59 -10
  13. package/dist/src/managed-app-server.mjs +36 -14
  14. package/dist/src/message-images.mjs +0 -18
  15. package/dist/src/onboarding.mjs +6 -8
  16. package/dist/src/platform/README.md +1 -1
  17. package/dist/src/platform/windows/IsolatedProcess.cs +7 -3
  18. package/dist/src/platform/windows/background-process.mjs +7 -2
  19. package/dist/src/platform/windows/codex-runtime.mjs +117 -0
  20. package/dist/src/platform/windows/desktop-host.mjs +17 -3
  21. package/dist/src/platform/windows/desktop-info.ps1 +2 -4
  22. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
  23. package/dist/src/platform/windows/launch-worker.mjs +42 -0
  24. package/dist/src/platform/windows/resident-startup.ps1 +58 -18
  25. package/dist/src/platform/windows/service-host.mjs +4 -16
  26. package/dist/src/platform/windows/start-hidden-console.ps1 +18 -8
  27. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  28. package/dist/src/runtime-discovery.mjs +43 -0
  29. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  30. package/dist/src/self-relay/admin-page.mjs +71 -0
  31. package/dist/src/self-relay/admin-server.mjs +61 -0
  32. package/dist/src/self-relay/admin-state.mjs +122 -0
  33. package/dist/src/self-relay/client.mjs +2 -0
  34. package/dist/src/self-relay/connector.mjs +107 -20
  35. package/dist/src/self-relay/demo.mjs +4 -5
  36. package/dist/src/self-relay/lifecycle.mjs +58 -70
  37. package/dist/src/self-relay/p2p-probe.mjs +123 -83
  38. package/dist/src/self-relay/server.mjs +224 -29
  39. package/dist/src/server.mjs +616 -1299
  40. package/dist/src/service-doctor.mjs +14 -26
  41. package/dist/src/service-install.mjs +1 -4
  42. package/dist/src/service-lifecycle.mjs +12 -12
  43. package/dist/src/shared-app-server.mjs +206 -46
  44. package/dist/src/shared-catalog.mjs +5 -25
  45. package/dist/src/shared-recovery.mjs +11 -9
  46. package/dist/src/shared-thread-stream.mjs +1 -1
  47. package/dist/src/skill-install.mjs +0 -13
  48. package/package.json +16 -18
  49. package/tools/postinstall.mjs +140 -3
  50. package/dist/src/app-server-client.mjs +0 -468
  51. package/dist/src/app-server-tasks.mjs +0 -355
  52. package/dist/src/debug-ui.mjs +0 -131
  53. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  54. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  55. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  56. package/dist/src/platform/windows/launch-worker.ps1 +0 -13
  57. package/dist/src/thread-catalog.mjs +0 -42
  58. package/dist/tools/find-desktop-pipe.mjs +0 -10
  59. package/dist/web/approval-ui.js +0 -188
  60. package/dist/web/capabilities.js +0 -57
  61. package/dist/web/chat-transport.js +0 -196
  62. package/dist/web/chat.css +0 -736
  63. package/dist/web/chat.js +0 -5149
  64. package/dist/web/client-platform.js +0 -11
  65. package/dist/web/harmony-platform.js +0 -51
  66. package/dist/web/highlight-powershell.min.js +0 -39
  67. package/dist/web/highlight.min.js +0 -1262
  68. package/dist/web/history-cache.js +0 -146
  69. package/dist/web/index.html +0 -222
  70. package/dist/web/lan-session.js +0 -122
  71. package/dist/web/p2p-diagnostics.js +0 -28
  72. package/dist/web/p2p-probe.js +0 -83
  73. package/dist/web/resources.json +0 -1
  74. package/dist/web/self-relay-session.js +0 -119
  75. package/dist/web/server-requests.js +0 -67
  76. package/dist/web/timeline-sync.js +0 -67
  77. package/dist/web/vendor/README.md +0 -14
  78. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  79. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  80. package/dist/web/vendor/highlight.min.js +0 -1262
@@ -8,6 +8,7 @@ import { startBackground } from './background-process.mjs';
8
8
  import { desktopHost } from './platform/windows/desktop-host.mjs';
9
9
  import * as serviceHost from './platform/windows/service-host.mjs';
10
10
  import { launchDesktop, portAvailable } from './desktop-launcher.mjs';
11
+ import { configureManagedEnvironment } from './platform/windows/user-environment.mjs';
11
12
 
12
13
  const script = fileURLToPath(import.meta.url);
13
14
  const recordPath = root => path.join(root, 'shared-recovery.json');
@@ -17,9 +18,9 @@ const active = value => value && ['scheduled', 'running'].includes(value.status)
17
18
  const recoverySteps = () => [
18
19
  ['checking', '检查启动条件'], ['stopping-desktop', '关闭当前 Codex'],
19
20
  ['starting-app-server', '确认 Codex 内核'], ['starting-desktop', '重新打开 Codex'],
20
- ['checking-execution', '确认手机控制就绪'],
21
+ ['checking-execution', '确认移动端控制就绪'],
21
22
  ].map(([id, label]) => ({ id, label, status: 'pending' }));
22
- export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex 内核。手机完整控制和 Codex 内核会保持运行;旧 Desktop 实例中的任务可能中断,未发送内容请先保存。切换不会清空项目、对话历史、设置或设备授权。';
23
+ export const recoveryWarning = 'Codex Desktop 需要重新打开,才能接入同一个 Codex 内核。移动端连接和 Codex 内核会保持运行;旧 Desktop 实例中的任务可能中断,未发送内容请先保存。切换不会清空项目、对话历史、设置或设备授权。';
23
24
 
24
25
  export async function recoveryStatus(stateRoot, host = desktopHost) {
25
26
  const record = readRecord(recordPath(stateRoot));
@@ -40,11 +41,11 @@ export async function recoveryStatus(stateRoot, host = desktopHost) {
40
41
  export async function planRecovery(stateRoot, host, available = portAvailable) {
41
42
  const info = await host.inspect();
42
43
  const action = await host.sharedServiceUrl() ? 'restart' : 'switch';
43
- if (!info.desktop || !info.cli) throw new Error('未找到 Codex Desktop 或执行程序,请先在电脑完成安装和登录。');
44
- const record = readRecord(path.join(stateRoot, 'managed-app-server.json')) || readRecord(path.join(stateRoot, 'shared-desktop.json'));
44
+ if (!info.desktop) throw new Error('未找到 Codex Desktop;CLI 用户无需执行 Desktop 接入。');
45
+ const record = readRecord(path.join(stateRoot, 'managed-app-server.json'));
45
46
  const actual = record ? await host.process(record.pid) : null;
46
47
  if (actual && (!sameProcess(record, actual) || !actual.command?.includes('app-server') || !actual.command.includes('ws://127.0.0.1:45839'))) {
47
- throw new Error('完整控制进程身份与记录不符,未结束任何进程。');
48
+ throw new Error('Codex 内核进程身份与记录不符,未结束任何进程。');
48
49
  }
49
50
  if (!actual && !await available()) throw new Error('45839 被未识别进程占用,未关闭 Desktop 或结束任何进程。');
50
51
  return { desktop: info.desktop, action };
@@ -63,7 +64,7 @@ export async function requestRecovery(stateRoot, { confirmed, operationId = rand
63
64
  catch (error) { if (error.code === 'EEXIST') throw new Error('已有恢复操作,请先查看恢复状态。'); throw error; }
64
65
  fs.writeFileSync(lock, JSON.stringify({ operationId })); fs.closeSync(lock);
65
66
  const record = { operationId, status: 'scheduled', stage: 'prepared', startedAt: new Date().toISOString(),
66
- message: '完整控制请求已接受,电脑管理连接保持运行。', steps: recoverySteps(), ...planned };
67
+ message: 'Codex 连接请求已接受,电脑管理连接保持运行。', steps: recoverySteps(), ...planned };
67
68
  try {
68
69
  writeRecord(recordPath(stateRoot), record);
69
70
  const log = path.join(stateRoot, 'shared-recovery.log');
@@ -83,7 +84,7 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
83
84
  const host = dependencies.host || desktopHost;
84
85
  const service = dependencies.service || lifecycle({ stateRoot, host: serviceHost });
85
86
  const available = dependencies.available || portAvailable;
86
- const start = dependencies.launch || (options => launchDesktop('shared', options));
87
+ const start = dependencies.launch || launchDesktop;
87
88
  let record = readRecord(recordPath(stateRoot));
88
89
  if (record?.operationId !== operationId || readRecord(lockPath(stateRoot))?.operationId !== operationId) throw new Error('恢复实例已失效。');
89
90
  record.steps ||= recoverySteps();
@@ -99,6 +100,7 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
99
100
  try {
100
101
  stage('checking');
101
102
  const planned = await planRecovery(stateRoot, host, available);
103
+ await configureManagedEnvironment(stateRoot);
102
104
  record = { ...record, ...planned };
103
105
  if (fs.existsSync(path.join(stateRoot, 'desktop-launch.lock'))) throw new Error('Desktop 正在启动,未与启动操作并行恢复。');
104
106
  stage('stopping-desktop'); await host.closeDesktop(record.desktop);
@@ -109,9 +111,9 @@ export async function runRecovery(stateRoot, operationId, dependencies = {}) {
109
111
  await waitFor(async () => {
110
112
  const health = await service.status();
111
113
  return health.running && health.mode === 'lan-shared' && health.execution?.ready === true;
112
- }, '完整控制未就绪;Bridge 和中继管理连接仍保留。');
114
+ }, 'Codex 内核未就绪;Bridge 和中继管理连接仍保留。');
113
115
  record = { ...record, status: 'completed', stage: 'ready', steps: record.steps.map(step => ({ ...step, status: 'completed' })),
114
- message: 'Codex Desktop 已接入 Codex 内核;手机完整控制保持就绪,原任务未自动重发。' };
116
+ message: 'Codex Desktop 已接入 Codex 内核;移动端控制保持就绪,原任务未自动重发。' };
115
117
  } catch (error) { record = { ...record, status: 'failed', error: error.message,
116
118
  steps: record.steps.map(step => ({ ...step, status: step.status === 'running' ? 'failed' : step.status })),
117
119
  message: 'Codex Desktop 未完成接入,已停止后续步骤。' }; }
@@ -43,7 +43,7 @@ export async function streamSharedThread(response, threadId, client, normalize,
43
43
  if (!threadListNotificationMethods.has(method)) return;
44
44
  emit('thread-list-changed', { method, threadId: params.threadId || params.thread?.id || '' });
45
45
  };
46
- const onDisconnected = () => { emit('bridge-error', { error: '完整控制已断开,请重新连接。' }); response.end(); finish(); };
46
+ const onDisconnected = () => { emit('bridge-error', { error: 'Codex 内核连接已断开,请重新连接。' }); response.end(); finish(); };
47
47
  response.once('close', () => { closed = true; finish(); });
48
48
  client.on('turn', onTurn); client.on('settings', onSettings); client.on('disconnected', onDisconnected);
49
49
  client.on('queue-changed', onQueue);
@@ -6,11 +6,8 @@ import { readRecord, writeRecord } from './service-lifecycle.mjs';
6
6
 
7
7
  const owner = '@walkhi/code-relax';
8
8
  const markerName = '.code-relax-install.json';
9
- const legacyOwner = '@closedhi/codex-remote';
10
- const legacyMarkerName = '.codex-remote-install.json';
11
9
  const digest = value => createHash('sha256').update(value).digest('hex');
12
10
  export const skillDirectory = (home = os.homedir()) => path.join(home, '.agents/skills/code-relax');
13
- const legacySkillDirectory = (home = os.homedir()) => path.join(home, '.agents/skills/codex-remote');
14
11
 
15
12
  // Refuse junctions/symlinks along managed paths before writing or removing files.
16
13
  export function assertPlainPath(target) {
@@ -33,15 +30,6 @@ export function inspectSkill(target = skillDirectory()) {
33
30
  return { installed: true, managed, target };
34
31
  }
35
32
 
36
- export function removeLegacyManagedSkill(target = legacySkillDirectory()) {
37
- const marker = readRecord(path.join(target, legacyMarkerName));
38
- const file = path.join(target, 'SKILL.md');
39
- if (marker?.owner !== legacyOwner || !fs.existsSync(file) || marker.sha256 !== digest(fs.readFileSync(file))
40
- || !fs.readdirSync(target).every(name => ['SKILL.md', legacyMarkerName].includes(name))) return false;
41
- fs.unlinkSync(file); fs.unlinkSync(path.join(target, legacyMarkerName)); fs.rmdirSync(target);
42
- return true;
43
- }
44
-
45
33
  export function installSkill(source, target = skillDirectory()) {
46
34
  const state = inspectSkill(target);
47
35
  if (state.installed && !state.managed) throw new Error(`同名 Skill 非本包管理或已被修改,请保留并手动处理:${target}`);
@@ -49,7 +37,6 @@ export function installSkill(source, target = skillDirectory()) {
49
37
  fs.mkdirSync(target, { recursive: true });
50
38
  fs.writeFileSync(path.join(target, 'SKILL.md'), content);
51
39
  writeRecord(path.join(target, markerName), { owner, sha256: digest(content) });
52
- if (path.resolve(target) === path.resolve(skillDirectory())) removeLegacyManagedSkill();
53
40
  return { installed: true, target, message: 'Skill 已安装/更新;若 Codex 尚未显示,请重新打开 Codex。' };
54
41
  }
55
42
 
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
- "name": "@walkhi/code-relax",
3
- "version": "0.1.0-beta.1",
4
- "license": "UNLICENSED",
2
+ "name": "@walkhi/code-relax",
3
+ "version": "0.1.0-beta.11",
4
+ "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "postinstall": "node ./tools/postinstall.mjs",
8
8
  "relay:demo": "node ./bin/self-relay-demo.mjs",
9
9
  "relay:check": "node ./bin/self-relay-demo.mjs --check",
10
- "probe:app-server": "node ./tools/probe-app-server.mjs",
11
- "probe:app-server-tasks": "node ./tools/probe-app-server-tasks.mjs",
12
10
  "build": "node ./tools/build-package.mjs",
13
11
  "prepack": "npm run build"
14
12
  },
@@ -18,10 +16,10 @@
18
16
  "sharp": "0.35.4",
19
17
  "ws": "8.21.3"
20
18
  },
21
- "bin": {
22
- "relax": "dist/bin/codex-remote.mjs",
23
- "code-relax": "dist/bin/codex-remote.mjs"
24
- },
19
+ "bin": {
20
+ "relax": "dist/bin/codex-remote.mjs",
21
+ "code-relax": "dist/bin/codex-remote.mjs"
22
+ },
25
23
  "description": "Windows Code Relax LAN Bridge and CLI",
26
24
  "engines": {
27
25
  "node": ">=22.12.0"
@@ -36,15 +34,15 @@
36
34
  "dist/",
37
35
  "tools/postinstall.mjs"
38
36
  ],
39
- "bundleDependencies": [
37
+ "bundleDependencies": [
40
38
  "node-datachannel",
41
39
  "qrcode",
42
40
  "sharp",
43
- "ws"
44
- ],
45
- "publishConfig": {
46
- "access": "public",
47
- "registry": "https://registry.npmjs.org",
48
- "tag": "beta"
49
- }
50
- }
41
+ "ws"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org",
46
+ "tag": "beta"
47
+ }
48
+ }
@@ -1,9 +1,146 @@
1
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';
2
5
  import { fileURLToPath } from 'node:url';
3
6
 
4
- if (process.env.npm_config_global === 'true') {
7
+ const serviceRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
8
+ 'Programs', 'CodexRemote', 'service');
9
+ const stateRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'CodexRemote');
10
+ const installLog = path.join(stateRoot, 'install.log');
11
+
12
+ function sanitizeLog(value) {
13
+ let result = String(value).replace(/npm_[A-Za-z0-9]+/gu, '[redacted npm token]')
14
+ .replace(/([?&]token=)[^&\s"']+/giu, '$1[redacted]');
15
+ const paths = [['LOCALAPPDATA', process.env.LOCALAPPDATA], ['APPDATA', process.env.APPDATA],
16
+ ['USERPROFILE', process.env.USERPROFILE], ['TEMP', process.env.TEMP], ['TMP', process.env.TMP]]
17
+ .filter(([, item]) => item).sort((left, right) => right[1].length - left[1].length);
18
+ for (const [name, item] of paths) {
19
+ const pattern = item.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
20
+ result = result.replace(new RegExp(pattern, 'giu'), `%${name}%`);
21
+ }
22
+ return result;
23
+ }
24
+
25
+ function appendLog(message) {
26
+ fs.mkdirSync(stateRoot, { recursive: true });
27
+ fs.appendFileSync(installLog, `${new Date().toISOString()} ${sanitizeLog(message)}\n`, 'utf8');
28
+ }
29
+
30
+ function systemSummary() {
31
+ const npm = String(process.env.npm_config_user_agent || 'unknown').replace(/[\r\n]/gu, ' ');
32
+ return `platform=${process.platform} osRelease=${os.release()} osVersion=${os.version()} `
33
+ + `osArch=${os.arch()} processArch=${process.arch} node=${process.version} npmUserAgent=${npm}`;
34
+ }
35
+
36
+ function runStage(entry, stage, args, formatOutput = value => value) {
37
+ appendLog(`[${stage}] start`);
38
+ try {
39
+ const output = execFileSync(process.execPath, [entry, ...args], { encoding: 'utf8', windowsHide: true,
40
+ stdio: ['inherit', 'pipe', 'pipe'] });
41
+ if (output) { process.stdout.write(formatOutput(output)); appendLog(`[${stage}] stdout\n${output.trimEnd()}`); }
42
+ appendLog(`[${stage}] success`);
43
+ } catch (error) {
44
+ if (error?.stdout) { process.stdout.write(error.stdout); appendLog(`[${stage}] stdout\n${String(error.stdout).trimEnd()}`); }
45
+ if (error?.stderr) { process.stderr.write(error.stderr); appendLog(`[${stage}] stderr\n${String(error.stderr).trimEnd()}`); }
46
+ appendLog(`[${stage}] failed: ${error?.stack || error?.message || String(error)}`);
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ export function formatSetupOutput(output, previous = {}) {
52
+ try {
53
+ const result = JSON.parse(output);
54
+ const migration = result.managedAppServerMigration || {};
55
+ const pid = migration.currentPid || result.managedAppServer?.pid || '未知';
56
+ const appServer = migration.restarted
57
+ ? `已重启(旧启动链迁移,PID ${migration.previousPid || '未知'} → ${pid})`
58
+ : previous.valid
59
+ ? `未重启(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'} 已是最新)`
60
+ : `已启动(PID ${pid},启动代次 ${migration.launcherRevision ?? '未知'})`;
61
+ return `Code Relax ${result.version || '未知版本'} 安装成功。\n`
62
+ + `受管 app-server:${appServer}\n`
63
+ + `Bridge:${result.restarted ? '已恢复并健康' : '已启动并健康'}\n`
64
+ + `${result.desktopRestarted ? `Codex Desktop:已干净重启并重新接入(${result.desktopRestartReason === 'attach-managed-server' ? '接入受管内核' : 'app-server 迁移'})\n` : ''}`
65
+ + `${result.desktopRestartRecommended ? 'Codex Desktop:建议完全退出后重新打开。\n' : ''}`;
66
+ } catch { return output; }
67
+ }
68
+
69
+ export function formatSkillOutput(output) {
70
+ try {
71
+ const result = JSON.parse(output);
72
+ return `Code Relax Skill:${result.message || (result.installed ? '已安装' : '已检查')}\n`;
73
+ } catch { return output; }
74
+ }
75
+
76
+ function installationSnapshot() {
77
+ const recordPath = path.join(serviceRoot, 'installation.json');
78
+ if (!fs.existsSync(serviceRoot)) return { exists: false, valid: false };
79
+ try {
80
+ const record = JSON.parse(fs.readFileSync(recordPath, 'utf8').replace(/^\uFEFF/u, ''));
81
+ return { exists: true, valid: Boolean(record?.installedAt && record?.version),
82
+ version: record?.version, installedAt: record?.installedAt };
83
+ } catch {
84
+ return { exists: true, valid: false };
85
+ }
86
+ }
87
+
88
+ function sameInstallation(left, right) {
89
+ return left.valid && right.valid && left.version === right.version && left.installedAt === right.installedAt;
90
+ }
91
+
92
+ function setupFailureGuidance(previous, current) {
93
+ if (previous.valid && sameInstallation(previous, current)) {
94
+ return `检测到旧安装 ${previous.version};新版本部署未完成,旧安装记录仍保留。\n`
95
+ + '请先执行 relax doctor;修复报错原因后重新运行 npm 安装命令,无需卸载旧版本。';
96
+ }
97
+ if (previous.valid && current.valid) {
98
+ return `检测到旧安装 ${previous.version};服务文件已更新为 ${current.version},但 setup 后续步骤失败。\n`
99
+ + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;无需先卸载。';
100
+ }
101
+ if (previous.exists && current.valid) {
102
+ return `检测到既有服务目录,但无法确认有效的旧安装记录;服务文件现已写入 ${current.version},setup 后续步骤仍失败。\n`
103
+ + '请执行 relax doctor 确认当前状态,修复后执行 relax setup;不要直接删除配置目录。';
104
+ }
105
+ if (previous.exists) {
106
+ return '检测到既有服务目录,但无法确认有效的旧安装记录;本次部署未完成。\n'
107
+ + '请执行 relax doctor,根据检查结果修复后重新运行 npm 安装命令,不要直接删除配置目录。';
108
+ }
109
+ if (current.valid) {
110
+ return `这是首次安装;服务文件 ${current.version} 已写入,但 setup 后续步骤失败。\n`
111
+ + '如果 relax 命令仍可用,请执行 relax doctor,再执行 relax setup;否则修复原因后重新运行 npm 安装命令。';
112
+ }
113
+ return '这是首次安装,尚未形成可确认的固定服务。\n修复上方错误后重新运行 npm 安装命令;无需执行卸载。';
114
+ }
115
+
116
+ function skillFailureGuidance(previous, current) {
117
+ const service = current.valid
118
+ ? `${previous.valid ? `服务已从 ${previous.version} 更新到` : '固定服务已安装为'} ${current.version}`
119
+ : 'setup 已返回成功,但当前固定服务记录无法确认';
120
+ return `${service};Codex Skill 安装失败。\n请执行 relax skill-install,再执行 relax doctor;无需重新卸载服务。`;
121
+ }
122
+
123
+ const invokedDirectly = process.argv[1]
124
+ && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
125
+ if (invokedDirectly && process.env.npm_config_global === 'true') {
5
126
  const entry = fileURLToPath(new URL('../dist/bin/codex-remote.mjs', import.meta.url));
6
- for (const command of ['setup', 'skill-install']) {
7
- execFileSync(process.execPath, [entry, command, '--json'], { stdio: 'inherit', windowsHide: true });
127
+ const previous = installationSnapshot();
128
+ let stage = 'setup';
129
+ appendLog(`[postinstall] start package=${process.env.npm_package_version || 'unknown'} ${systemSummary()}`);
130
+ try {
131
+ runStage(entry, stage, ['setup', '--json'], output => formatSetupOutput(output, previous));
132
+ stage = 'skill-install';
133
+ runStage(entry, stage, ['skill-install', '--json'], formatSkillOutput);
134
+ console.log(`安装日志:${installLog}`);
135
+ appendLog('[postinstall] success');
136
+ } catch (error) {
137
+ const current = installationSnapshot();
138
+ console.error(`\nCode Relax 安装未完全完成:\n${stage === 'setup'
139
+ ? setupFailureGuidance(previous, current)
140
+ : skillFailureGuidance(previous, current)}`);
141
+ if (error?.code && error.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') console.error(`启动安装步骤失败:${error.message}`);
142
+ console.error(`安装日志:${installLog}`);
143
+ appendLog(`[postinstall] failed stage=${stage}`);
144
+ process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
8
145
  }
9
146
  }