@walkhi/code-relax 0.1.0-beta.65 → 0.1.0-beta.68

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 (121) hide show
  1. package/dist/bin/codex-remote.mjs +4 -2
  2. package/dist/bin/manage-relay-admin.mjs +20 -20
  3. package/dist/bin/migrate-relay-db.mjs +109 -109
  4. package/dist/bin/publish-harmony-release.mjs +46 -46
  5. package/dist/bin/self-relay-admin.mjs +8 -8
  6. package/dist/bin/self-relay-demo.mjs +33 -33
  7. package/dist/bin/self-relay-server.mjs +66 -66
  8. package/dist/bin/set-relay-admin-password.mjs +11 -11
  9. package/dist/build-platform.json +1 -1
  10. package/dist/get-lan-networks.ps1 +40 -40
  11. package/dist/shared/app-server-events.cjs +511 -511
  12. package/dist/shared/p2p-data-channel.cjs +72 -72
  13. package/dist/shared/p2p-diagnostics.cjs +28 -28
  14. package/dist/shared/p2p-ice-policy.cjs +22 -22
  15. package/dist/shared/plan-pricing.cjs +32 -32
  16. package/dist/shared/question-attention.cjs +69 -69
  17. package/dist/shared/release-version.cjs +31 -31
  18. package/dist/src/archived-threads.mjs +22 -22
  19. package/dist/src/background-process.mjs +18 -18
  20. package/dist/src/code-browser.mjs +100 -100
  21. package/dist/src/desktop-launcher.mjs +62 -62
  22. package/dist/src/dots-task-files.mjs +52 -52
  23. package/dist/src/execution-health.mjs +26 -26
  24. package/dist/src/message-images.mjs +9 -9
  25. package/dist/src/plan-pricing.mjs +47 -47
  26. package/dist/src/platform/codex-runtime.mjs +3 -3
  27. package/dist/src/platform/credential-store.mjs +19 -19
  28. package/dist/src/platform/desktop-host.mjs +3 -3
  29. package/dist/src/platform/desktop-permission-mode.mjs +17 -17
  30. package/dist/src/platform/desktop-project-registration.mjs +3 -3
  31. package/dist/src/platform/file-opener.mjs +9 -9
  32. package/dist/src/platform/linux/README.md +3 -3
  33. package/dist/src/platform/linux/codex-runtime.mjs +56 -56
  34. package/dist/src/platform/linux/desktop-installation.mjs +36 -36
  35. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -11
  36. package/dist/src/platform/linux/network-info.mjs +11 -11
  37. package/dist/src/platform/linux/project-locations.mjs +17 -17
  38. package/dist/src/platform/linux/resident-startup.mjs +52 -52
  39. package/dist/src/platform/linux/retained-runtime.mjs +76 -76
  40. package/dist/src/platform/linux/service-host.mjs +18 -18
  41. package/dist/src/platform/macos/README.md +3 -3
  42. package/dist/src/platform/macos/codex-runtime.mjs +0 -4
  43. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -11
  44. package/dist/src/platform/macos/network-info.mjs +8 -8
  45. package/dist/src/platform/macos/project-locations.mjs +8 -8
  46. package/dist/src/platform/macos/resident-startup.mjs +84 -84
  47. package/dist/src/platform/macos/retained-runtime.mjs +127 -127
  48. package/dist/src/platform/macos/service-host.mjs +18 -18
  49. package/dist/src/platform/macos/system.mjs +29 -29
  50. package/dist/src/platform/network-info.mjs +3 -3
  51. package/dist/src/platform/path-identity.mjs +5 -5
  52. package/dist/src/platform/posix/network-candidates.mjs +23 -23
  53. package/dist/src/platform/posix/runtime-paths.mjs +25 -25
  54. package/dist/src/platform/power-inhibitor.mjs +22 -22
  55. package/dist/src/platform/project-locations.mjs +3 -3
  56. package/dist/src/platform/runtime-paths.mjs +8 -8
  57. package/dist/src/platform/select.mjs +5 -5
  58. package/dist/src/platform/service-host.mjs +3 -3
  59. package/dist/src/platform/user-environment.mjs +3 -3
  60. package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
  61. package/dist/src/platform/windows/background-process.mjs +30 -30
  62. package/dist/src/platform/windows/desktop-helper.mjs +179 -179
  63. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
  64. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -7
  65. package/dist/src/platform/windows/keep-awake.ps1 +20 -20
  66. package/dist/src/platform/windows/network-info.mjs +34 -34
  67. package/dist/src/platform/windows/resident-start.js +20 -20
  68. package/dist/src/platform/windows/resident-start.ps1 +7 -7
  69. package/dist/src/platform/windows/resident-startup.ps1 +83 -83
  70. package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
  71. package/dist/src/platform/windows/runtime-paths.mjs +20 -20
  72. package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
  73. package/dist/src/platform/windows/start-hidden-console.ps1 +58 -58
  74. package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
  75. package/dist/src/platform/windows/user-environment.ps1 +41 -41
  76. package/dist/src/project-create.mjs +121 -121
  77. package/dist/src/project-management.mjs +1 -1
  78. package/dist/src/project-remove.mjs +55 -55
  79. package/dist/src/project-update.mjs +59 -59
  80. package/dist/src/relay-config-import.mjs +45 -45
  81. package/dist/src/self-relay/account-connections.mjs +52 -52
  82. package/dist/src/self-relay/admin-auth.mjs +59 -59
  83. package/dist/src/self-relay/admin-cloud.mjs +101 -101
  84. package/dist/src/self-relay/admin-login-page.mjs +7 -7
  85. package/dist/src/self-relay/admin-server.mjs +105 -105
  86. package/dist/src/self-relay/admin-state.mjs +127 -127
  87. package/dist/src/self-relay/admin-workstations-page.mjs +95 -95
  88. package/dist/src/self-relay/admin-workstations.mjs +143 -143
  89. package/dist/src/self-relay/android-releases.mjs +61 -61
  90. package/dist/src/self-relay/client.mjs +61 -61
  91. package/dist/src/self-relay/connection-reporting.mjs +38 -38
  92. package/dist/src/self-relay/debug-allowlist.mjs +33 -33
  93. package/dist/src/self-relay/demo.mjs +6 -6
  94. package/dist/src/self-relay/device-info.mjs +27 -27
  95. package/dist/src/self-relay/device-store.mjs +92 -92
  96. package/dist/src/self-relay/lan-authorizations.mjs +71 -71
  97. package/dist/src/self-relay/lifecycle.mjs +278 -278
  98. package/dist/src/self-relay/long-pairing.mjs +40 -40
  99. package/dist/src/self-relay/mysql-state.mjs +118 -118
  100. package/dist/src/self-relay/phone-connections.mjs +55 -55
  101. package/dist/src/self-relay/server.mjs +1066 -1066
  102. package/dist/src/self-relay/usage-daily.mjs +55 -55
  103. package/dist/src/self-relay/windows-route-helper.mjs +75 -75
  104. package/dist/src/self-relay/wire.mjs +36 -36
  105. package/dist/src/server.mjs +1947 -1947
  106. package/dist/src/service-control.mjs +82 -82
  107. package/dist/src/service-health.mjs +33 -33
  108. package/dist/src/service-power.mjs +58 -58
  109. package/dist/src/service-restart.mjs +81 -81
  110. package/dist/src/service-update-worker.mjs +42 -42
  111. package/dist/src/service-update.mjs +80 -80
  112. package/dist/src/shared-recovery.mjs +152 -152
  113. package/dist/src/skill-install.mjs +51 -51
  114. package/dist/src/thread-title-generation.mjs +150 -150
  115. package/dist/src/turn-model-history.mjs +47 -47
  116. package/dist/tools/rtc-route-helper.ps1 +99 -99
  117. package/dist/tools/start-rtc-route-helper.ps1 +15 -15
  118. package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
  119. package/dist/web/vendor/markdown-it.umd.min.js +13 -13
  120. package/package.json +4 -4
  121. package/tools/postinstall.mjs +1 -1
@@ -1,19 +1,19 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { runtimePaths } from './runtime-paths.mjs';
4
- import { readRecord, writeRecord } from '../service-lifecycle.mjs';
5
- import { assertPlainPath } from '../skill-install.mjs';
6
-
7
- // Workstation identity is ordinary per-user configuration, outside the service
8
- // installation. No platform vault or interactive permission is needed on load.
9
- export function credentialStore(action, record, root = runtimePaths().stateRoot) {
10
- if (!['load', 'save'].includes(action)) throw new Error('无效的工作站配置操作。');
11
- const file = path.join(root, 'self-relay-identity.json');
12
- assertPlainPath(file);
13
- if (action === 'save') {
14
- if (!record || typeof record !== 'object' || Array.isArray(record)) throw new Error('无效的工作站身份配置。');
15
- fs.mkdirSync(root, { recursive: true, mode: 0o700 });
16
- writeRecord(file, record);
17
- }
18
- return { path: file, record: readRecord(file) };
19
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { runtimePaths } from './runtime-paths.mjs';
4
+ import { readRecord, writeRecord } from '../service-lifecycle.mjs';
5
+ import { assertPlainPath } from '../skill-install.mjs';
6
+
7
+ // Workstation identity is ordinary per-user configuration, outside the service
8
+ // installation. No platform vault or interactive permission is needed on load.
9
+ export function credentialStore(action, record, root = runtimePaths().stateRoot) {
10
+ if (!['load', 'save'].includes(action)) throw new Error('无效的工作站配置操作。');
11
+ const file = path.join(root, 'self-relay-identity.json');
12
+ assertPlainPath(file);
13
+ if (action === 'save') {
14
+ if (!record || typeof record !== 'object' || Array.isArray(record)) throw new Error('无效的工作站身份配置。');
15
+ fs.mkdirSync(root, { recursive: true, mode: 0o700 });
16
+ writeRecord(file, record);
17
+ }
18
+ return { path: file, record: readRecord(file) };
19
+ }
@@ -1,3 +1,3 @@
1
- import { platformModule } from './select.mjs';
2
- const adapter = await import(platformModule('desktop-host'));
3
- export const { desktopHost, resolveStateRoot, removeDesktopShortcut, residentStartup } = adapter;
1
+ import { platformModule } from './select.mjs';
2
+ const adapter = await import(platformModule('desktop-host'));
3
+ export const { desktopHost, resolveStateRoot, removeDesktopShortcut, residentStartup } = adapter;
@@ -1,17 +1,17 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { runtimePaths } from './runtime-paths.mjs';
4
-
5
- // Desktop 26.1013.4894.0: LCo / agent-mode-by-host-id, local execution host.
6
- // Read the preference only; Desktop remains the owner of its state file.
7
- export async function desktopPermissionMode(codexDataRoot = runtimePaths().codexDataRoot) {
8
- let state;
9
- try {
10
- state = JSON.parse(await fs.readFile(path.join(codexDataRoot, '.codex-global-state.json'), 'utf8'));
11
- } catch (error) {
12
- if (error.code === 'ENOENT') return null;
13
- throw error;
14
- }
15
- const mode = state?.['electron-persisted-atom-state']?.['agent-mode-by-host-id']?.local;
16
- return ({ auto: 'ask', 'guardian-approvals': 'auto', 'full-access': 'full' })[mode] ?? null;
17
- }
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { runtimePaths } from './runtime-paths.mjs';
4
+
5
+ // Desktop 26.1013.4894.0: LCo / agent-mode-by-host-id, local execution host.
6
+ // Read the preference only; Desktop remains the owner of its state file.
7
+ export async function desktopPermissionMode(codexDataRoot = runtimePaths().codexDataRoot) {
8
+ let state;
9
+ try {
10
+ state = JSON.parse(await fs.readFile(path.join(codexDataRoot, '.codex-global-state.json'), 'utf8'));
11
+ } catch (error) {
12
+ if (error.code === 'ENOENT') return null;
13
+ throw error;
14
+ }
15
+ const mode = state?.['electron-persisted-atom-state']?.['agent-mode-by-host-id']?.local;
16
+ return ({ auto: 'ask', 'guardian-approvals': 'auto', 'full-access': 'full' })[mode] ?? null;
17
+ }
@@ -1,3 +1,3 @@
1
- import { platformModule } from './select.mjs';
2
- const adapter = await import(platformModule('desktop-project-registration'));
3
- export const { withDesktopProjectRegistration } = adapter;
1
+ import { platformModule } from './select.mjs';
2
+ const adapter = await import(platformModule('desktop-project-registration'));
3
+ export const { withDesktopProjectRegistration } = adapter;
@@ -1,9 +1,9 @@
1
- import { spawn } from 'node:child_process';
2
-
3
- export function openFile(file) {
4
- const executable = { win32: 'explorer.exe', darwin: '/usr/bin/open', linux: 'xdg-open' }[process.platform];
5
- if (!executable) throw new Error(`不支持此平台的文件打开操作:${process.platform}`);
6
- const viewer = spawn(executable, [file], { detached: true, stdio: 'ignore', windowsHide: true });
7
- viewer.once('error', error => console.error(`无法打开二维码:${error.message}`));
8
- viewer.unref();
9
- }
1
+ import { spawn } from 'node:child_process';
2
+
3
+ export function openFile(file) {
4
+ const executable = { win32: 'explorer.exe', darwin: '/usr/bin/open', linux: 'xdg-open' }[process.platform];
5
+ if (!executable) throw new Error(`不支持此平台的文件打开操作:${process.platform}`);
6
+ const viewer = spawn(executable, [file], { detached: true, stdio: 'ignore', windowsHide: true });
7
+ viewer.once('error', error => console.error(`无法打开二维码:${error.message}`));
8
+ viewer.unref();
9
+ }
@@ -1,3 +1,3 @@
1
- # linux 被控主机(预留)
2
-
3
- 仅规划 Codex 的系统路径、进程/IPC 和运行环境适配,尚未实现。接入排在控制端扩展之后;不预设支持 Desktop 共享任务。
1
+ # linux 被控主机(预留)
2
+
3
+ 仅规划 Codex 的系统路径、进程/IPC 和运行环境适配,尚未实现。接入排在控制端扩展之后;不预设支持 Desktop 共享任务。
@@ -1,56 +1,56 @@
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 { verifyProtocol } from '../codex-protocol.mjs';
7
- import { readRecord, writeRecord } from '../../service-lifecycle.mjs';
8
- import { installedDesktop, verifyDesktopPackage } from './desktop-installation.mjs';
9
- import { retainRuntime, runtimeFingerprint } from './retained-runtime.mjs';
10
- export { runtimeFingerprint, pruneRetainedDesktopRuntimes } from './retained-runtime.mjs';
11
-
12
- const exec = promisify(execFile);
13
- export const codeModeHostPath = executable => path.join(path.dirname(executable), 'codex-code-mode-host');
14
- export function nativeExecutable(file, arch = process.arch) {
15
- let descriptor;
16
- try {
17
- descriptor = fs.openSync(file, 'r');
18
- const bytes = Buffer.alloc(20);
19
- if (fs.readSync(descriptor, bytes, 0, 20, 0) !== 20) return false;
20
- return bytes.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70])) && bytes[4] === 2 && bytes[5] === 1
21
- && bytes.readUInt16LE(18) === ({ x64: 62, arm64: 183 }[arch]);
22
- } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
23
- finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
24
- }
25
- export function runtimeFilesAvailable(executable) {
26
- return nativeExecutable(executable) && nativeExecutable(codeModeHostPath(executable))
27
- && nativeExecutable(path.join(path.dirname(executable), 'rg'));
28
- }
29
- export function discoverCodex(environment = process.env) {
30
- const desktop = installedDesktop(environment);
31
- if (!desktop) return [];
32
- const executable = path.join(desktop.desktop, 'resources/codex');
33
- if (environment.CODE_RELAX_CODEX_PATH && path.resolve(environment.CODE_RELAX_CODEX_PATH) !== executable)
34
- throw new Error('Linux 工作站需要官方 ChatGPT 桌面版内核,CODE_RELAX_CODEX_PATH 与桌面内核不一致。');
35
- return nativeExecutable(executable) ? [{ executable, source: 'desktop', desktop: desktop.desktop }] : [];
36
- }
37
- export async function resolveCodexRuntime(stateRoot, { refresh = false, environment = process.env } = {}) {
38
- fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
39
- const recordFile = path.join(stateRoot, 'codex-runtime.json'), saved = readRecord(recordFile);
40
- const codexHome = path.resolve(environment.CODEX_HOME?.trim() || saved?.codexHome || path.join(os.homedir(), '.codex'));
41
- if (!refresh && saved?.source === 'desktop' && saved.executable && codexHome === saved.codexHome
42
- && (!environment.CODE_RELAX_CODEX_PATH || environment.CODE_RELAX_CODEX_PATH === saved.sourceExecutable)
43
- && runtimeFilesAvailable(saved.executable) && saved.fingerprint === runtimeFingerprint(saved.executable)) return saved;
44
- const [candidate] = discoverCodex(environment);
45
- if (!candidate) throw new Error('请先安装并登录官方 Linux ChatGPT 桌面版;独立 CLI 不能代替点点所需的桌面接入。');
46
- verifyDesktopPackage(candidate.desktop);
47
- if (!runtimeFilesAvailable(candidate.executable)) throw new Error('Linux 桌面内核缺少当前架构的配套组件。');
48
- const { stdout } = await exec(candidate.executable, ['--version'], { timeout: 10000, env: { ...environment, CODEX_HOME: codexHome } });
49
- const version = /^codex-cli (\d+\.\d+\.\d+(?:-[\w.]+)?)/m.exec(stdout)?.[1];
50
- if (!version) throw new Error('无法识别 Linux 桌面内核版本。');
51
- const retained = await retainRuntime(candidate.executable, stateRoot, version);
52
- await verifyProtocol(retained.executable, stateRoot);
53
- const result = { ...candidate, ...retained, version, codexHome, fingerprint: runtimeFingerprint(retained.executable) };
54
- writeRecord(recordFile, result);
55
- return result;
56
- }
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 { verifyProtocol } from '../codex-protocol.mjs';
7
+ import { readRecord, writeRecord } from '../../service-lifecycle.mjs';
8
+ import { installedDesktop, verifyDesktopPackage } from './desktop-installation.mjs';
9
+ import { retainRuntime, runtimeFingerprint } from './retained-runtime.mjs';
10
+ export { runtimeFingerprint, pruneRetainedDesktopRuntimes } from './retained-runtime.mjs';
11
+
12
+ const exec = promisify(execFile);
13
+ export const codeModeHostPath = executable => path.join(path.dirname(executable), 'codex-code-mode-host');
14
+ export function nativeExecutable(file, arch = process.arch) {
15
+ let descriptor;
16
+ try {
17
+ descriptor = fs.openSync(file, 'r');
18
+ const bytes = Buffer.alloc(20);
19
+ if (fs.readSync(descriptor, bytes, 0, 20, 0) !== 20) return false;
20
+ return bytes.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70])) && bytes[4] === 2 && bytes[5] === 1
21
+ && bytes.readUInt16LE(18) === ({ x64: 62, arm64: 183 }[arch]);
22
+ } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
23
+ finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
24
+ }
25
+ export function runtimeFilesAvailable(executable) {
26
+ return nativeExecutable(executable) && nativeExecutable(codeModeHostPath(executable))
27
+ && nativeExecutable(path.join(path.dirname(executable), 'rg'));
28
+ }
29
+ export function discoverCodex(environment = process.env) {
30
+ const desktop = installedDesktop(environment);
31
+ if (!desktop) return [];
32
+ const executable = path.join(desktop.desktop, 'resources/codex');
33
+ if (environment.CODE_RELAX_CODEX_PATH && path.resolve(environment.CODE_RELAX_CODEX_PATH) !== executable)
34
+ throw new Error('Linux 工作站需要官方 ChatGPT 桌面版内核,CODE_RELAX_CODEX_PATH 与桌面内核不一致。');
35
+ return nativeExecutable(executable) ? [{ executable, source: 'desktop', desktop: desktop.desktop }] : [];
36
+ }
37
+ export async function resolveCodexRuntime(stateRoot, { refresh = false, environment = process.env } = {}) {
38
+ fs.mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
39
+ const recordFile = path.join(stateRoot, 'codex-runtime.json'), saved = readRecord(recordFile);
40
+ const codexHome = path.resolve(environment.CODEX_HOME?.trim() || saved?.codexHome || path.join(os.homedir(), '.codex'));
41
+ if (!refresh && saved?.source === 'desktop' && saved.executable && codexHome === saved.codexHome
42
+ && (!environment.CODE_RELAX_CODEX_PATH || environment.CODE_RELAX_CODEX_PATH === saved.sourceExecutable)
43
+ && runtimeFilesAvailable(saved.executable) && saved.fingerprint === runtimeFingerprint(saved.executable)) return saved;
44
+ const [candidate] = discoverCodex(environment);
45
+ if (!candidate) throw new Error('请先安装并登录官方 Linux ChatGPT 桌面版;独立 CLI 不能代替点点所需的桌面接入。');
46
+ verifyDesktopPackage(candidate.desktop);
47
+ if (!runtimeFilesAvailable(candidate.executable)) throw new Error('Linux 桌面内核缺少当前架构的配套组件。');
48
+ const { stdout } = await exec(candidate.executable, ['--version'], { timeout: 10000, env: { ...environment, CODEX_HOME: codexHome } });
49
+ const version = /^codex-cli (\d+\.\d+\.\d+(?:-[\w.]+)?)/m.exec(stdout)?.[1];
50
+ if (!version) throw new Error('无法识别 Linux 桌面内核版本。');
51
+ const retained = await retainRuntime(candidate.executable, stateRoot, version);
52
+ await verifyProtocol(retained.executable, stateRoot);
53
+ const result = { ...candidate, ...retained, version, codexHome, fingerprint: runtimeFingerprint(retained.executable) };
54
+ writeRecord(recordFile, result);
55
+ return result;
56
+ }
@@ -1,36 +1,36 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { execFileSync } from 'node:child_process';
4
-
5
- // Official Linux 26.1007.71326 deb: /usr/bin/chatgpt -> codex-launcher,
6
- // ChatGPT and resources/{codex,codex-code-mode-host,rg} share this root.
7
- export function installedDesktop(environment = process.env) {
8
- const roots = ['/usr/lib/chatgpt', '/opt/chatgpt', '/opt/ChatGPT'];
9
- for (const directory of String(environment.PATH || '').split(':').filter(value => value.startsWith('/'))) {
10
- try { roots.push(path.dirname(fs.realpathSync(path.join(directory, 'chatgpt')))); }
11
- catch (error) { if (error.code !== 'ENOENT') throw error; }
12
- }
13
- for (const root of new Set(roots)) {
14
- const metadataFile = path.join(root, 'resources/linux-package-metadata.json');
15
- if (!fs.existsSync(metadataFile)) continue;
16
- const metadata = JSON.parse(fs.readFileSync(metadataFile, 'utf8'));
17
- if (metadata.codexAppBrand !== 'chatgpt' || metadata.codexBuildFlavor !== 'prod' || !metadata.version) continue;
18
- const executable = path.join(root, 'ChatGPT');
19
- if (!fs.existsSync(executable)) continue;
20
- return { desktop: fs.realpathSync(root), executable: fs.realpathSync(executable), version: metadata.version, build: metadata.version };
21
- }
22
- return null;
23
- }
24
- export function verifyDesktopPackage(root) {
25
- const file = path.join(root, 'resources/codex');
26
- // Trust the machine's installed package database; do not treat a JSON brand
27
- // marker as an operating-system signature or accept an unrelated CLI shim.
28
- if (fs.existsSync('/usr/bin/dpkg-query')) {
29
- const owner = execFileSync('/usr/bin/dpkg-query', ['-S', file], { encoding: 'utf8' }).trim();
30
- if (!owner.split('\n').some(line => line === `chatgpt: ${file}`)) throw new Error('内核不属于已安装的官方 chatgpt 软件包。');
31
- } else if (fs.existsSync('/usr/bin/rpm')) {
32
- if (execFileSync('/usr/bin/rpm', ['-qf', '--qf', '%{NAME}', file], { encoding: 'utf8' }).trim() !== 'chatgpt') throw new Error('内核软件包身份不匹配。');
33
- } else if (fs.existsSync('/usr/bin/pacman')) {
34
- if (!/^chatgpt(?:-bin)?$/.test(execFileSync('/usr/bin/pacman', ['-Qoq', file], { encoding: 'utf8' }).trim())) throw new Error('内核软件包身份不匹配。');
35
- } else throw new Error('无法核对 Linux 桌面版软件包,请通过官方软件包安装 ChatGPT。');
36
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+
5
+ // Official Linux 26.1007.71326 deb: /usr/bin/chatgpt -> codex-launcher,
6
+ // ChatGPT and resources/{codex,codex-code-mode-host,rg} share this root.
7
+ export function installedDesktop(environment = process.env) {
8
+ const roots = ['/usr/lib/chatgpt', '/opt/chatgpt', '/opt/ChatGPT'];
9
+ for (const directory of String(environment.PATH || '').split(':').filter(value => value.startsWith('/'))) {
10
+ try { roots.push(path.dirname(fs.realpathSync(path.join(directory, 'chatgpt')))); }
11
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
12
+ }
13
+ for (const root of new Set(roots)) {
14
+ const metadataFile = path.join(root, 'resources/linux-package-metadata.json');
15
+ if (!fs.existsSync(metadataFile)) continue;
16
+ const metadata = JSON.parse(fs.readFileSync(metadataFile, 'utf8'));
17
+ if (metadata.codexAppBrand !== 'chatgpt' || metadata.codexBuildFlavor !== 'prod' || !metadata.version) continue;
18
+ const executable = path.join(root, 'ChatGPT');
19
+ if (!fs.existsSync(executable)) continue;
20
+ return { desktop: fs.realpathSync(root), executable: fs.realpathSync(executable), version: metadata.version, build: metadata.version };
21
+ }
22
+ return null;
23
+ }
24
+ export function verifyDesktopPackage(root) {
25
+ const file = path.join(root, 'resources/codex');
26
+ // Trust the machine's installed package database; do not treat a JSON brand
27
+ // marker as an operating-system signature or accept an unrelated CLI shim.
28
+ if (fs.existsSync('/usr/bin/dpkg-query')) {
29
+ const owner = execFileSync('/usr/bin/dpkg-query', ['-S', file], { encoding: 'utf8' }).trim();
30
+ if (!owner.split('\n').some(line => line === `chatgpt: ${file}`)) throw new Error('内核不属于已安装的官方 chatgpt 软件包。');
31
+ } else if (fs.existsSync('/usr/bin/rpm')) {
32
+ if (execFileSync('/usr/bin/rpm', ['-qf', '--qf', '%{NAME}', file], { encoding: 'utf8' }).trim() !== 'chatgpt') throw new Error('内核软件包身份不匹配。');
33
+ } else if (fs.existsSync('/usr/bin/pacman')) {
34
+ if (!/^chatgpt(?:-bin)?$/.test(execFileSync('/usr/bin/pacman', ['-Qoq', file], { encoding: 'utf8' }).trim())) throw new Error('内核软件包身份不匹配。');
35
+ } else throw new Error('无法核对 Linux 桌面版软件包,请通过官方软件包安装 ChatGPT。');
36
+ }
@@ -1,11 +1,11 @@
1
- import { desktopHost } from './desktop-host.mjs';
2
- import { withDesktopProjectRegistration as register } from '../desktop-project-store.mjs';
3
-
4
- // Verified against the Linux 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
+ import { desktopHost } from './desktop-host.mjs';
2
+ import { withDesktopProjectRegistration as register } from '../desktop-project-store.mjs';
3
+
4
+ // Verified against the Linux 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,11 +1,11 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import { networkCandidates } from '../posix/network-candidates.mjs';
4
-
5
- export async function lanNetworks() {
6
- const hardware = fs.readdirSync('/sys/class/net').filter(name => fs.existsSync(`/sys/class/net/${name}/device`)).map(interfaceName => ({
7
- interfaceName, networkName: interfaceName,
8
- networkType: fs.existsSync(`/sys/class/net/${interfaceName}/wireless`) ? 'wifi' : 'ethernet',
9
- }));
10
- return networkCandidates(hardware, os.networkInterfaces());
11
- }
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import { networkCandidates } from '../posix/network-candidates.mjs';
4
+
5
+ export async function lanNetworks() {
6
+ const hardware = fs.readdirSync('/sys/class/net').filter(name => fs.existsSync(`/sys/class/net/${name}/device`)).map(interfaceName => ({
7
+ interfaceName, networkName: interfaceName,
8
+ networkType: fs.existsSync(`/sys/class/net/${interfaceName}/wireless`) ? 'wifi' : 'ethernet',
9
+ }));
10
+ return networkCandidates(hardware, os.networkInterfaces());
11
+ }
@@ -1,17 +1,17 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
-
5
- export async function projectLocations() {
6
- const home = os.homedir();
7
- const config = path.isAbsolute(process.env.XDG_CONFIG_HOME || '') ? process.env.XDG_CONFIG_HOME : path.join(home, '.config');
8
- let text = '';
9
- try { text = fs.readFileSync(path.join(config, 'user-dirs.dirs'), 'utf8'); }
10
- catch (error) { if (error.code !== 'ENOENT') throw error; }
11
- const directory = (name, fallback) => {
12
- const match = new RegExp(`^XDG_${name}_DIR="([^"\\n]+)"$`, 'm').exec(text);
13
- const value = match?.[1].replace(/^\$HOME(?=\/|$)/, home);
14
- return value && path.isAbsolute(value) && !value.includes('$') ? value : path.join(home, fallback);
15
- };
16
- return { home, downloads: directory('DOWNLOAD', 'Downloads'), defaultParent: path.join(directory('DOCUMENTS', 'Documents'), 'ChatGPT'), roots: ['/'] };
17
- }
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ export async function projectLocations() {
6
+ const home = os.homedir();
7
+ const config = path.isAbsolute(process.env.XDG_CONFIG_HOME || '') ? process.env.XDG_CONFIG_HOME : path.join(home, '.config');
8
+ let text = '';
9
+ try { text = fs.readFileSync(path.join(config, 'user-dirs.dirs'), 'utf8'); }
10
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
11
+ const directory = (name, fallback) => {
12
+ const match = new RegExp(`^XDG_${name}_DIR="([^"\\n]+)"$`, 'm').exec(text);
13
+ const value = match?.[1].replace(/^\$HOME(?=\/|$)/, home);
14
+ return value && path.isAbsolute(value) && !value.includes('$') ? value : path.join(home, fallback);
15
+ };
16
+ return { home, downloads: directory('DOWNLOAD', 'Downloads'), defaultParent: path.join(directory('DOCUMENTS', 'Documents'), 'ChatGPT'), roots: ['/'] };
17
+ }
@@ -1,52 +1,52 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { assertPlainPath } from '../../skill-install.mjs';
5
- import { runtimePaths } from '../runtime-paths.mjs';
6
- import { readRecord } from '../../service-lifecycle.mjs';
7
-
8
- // XDG autostart inherits the actual graphical session (Wayland/X11 and the
9
- // unlocked Secret Service). No system daemon, sudo, or shell profile mutation.
10
- const quote = value => {
11
- if (/[\u0000-\u001f\u007f]/.test(value)) throw new Error('登录启动路径含无效控制字符。');
12
- return `"${value.replace(/%/g, '%%').replace(/[\\"`$]/g, char => `\\${char}`).replace(/\\/g, '\\\\')}"`;
13
- };
14
- export function autostartEntry({ serviceRoot, nodeExecutable, environment = process.env }) {
15
- const entry = path.join(serviceRoot, 'dist/bin/codex-remote.mjs');
16
- const variables = {};
17
- for (const name of ['CODEX_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'XDG_CONFIG_HOME'])
18
- if (environment[name]) variables[name] = environment[name];
19
- const stablePath = String(environment.PATH || '').split(':').filter(value => value.startsWith('/') && !value.endsWith('/node_modules/.bin')).join(':');
20
- if (stablePath) variables.PATH = stablePath;
21
- const command = [quote(nodeExecutable), quote(entry), 'start', '--json'].join(' ');
22
- const assignments = Object.entries(variables).map(([key, value]) => quote(`${key}=${value}`));
23
- return `[Desktop Entry]\nType=Application\nName=Code Relax\nComment=Code Relax workstation\nExec=/usr/bin/env ${[...assignments, command].join(' ')}\nTerminal=false\nX-CodeRelax-Owner=${encodeURIComponent(path.resolve(serviceRoot))}\n`;
24
- }
25
- export async function residentStartup(installation, action = 'status') {
26
- if (!['status', 'enable', 'disable'].includes(action)) throw new Error('无效的登录启动操作。');
27
- if (process.getuid() === 0) throw new Error('请以普通 Linux 桌面用户安装,不使用 sudo。');
28
- const config = path.isAbsolute(process.env.XDG_CONFIG_HOME || '') ? process.env.XDG_CONFIG_HOME : path.join(os.homedir(), '.config');
29
- const file = path.join(config, 'autostart/cn.walkhi.code-relax.desktop');
30
- assertPlainPath(file);
31
- const runtime = readRecord(path.join(runtimePaths().stateRoot, 'codex-runtime.json'));
32
- const environment = { ...process.env, ...(runtime?.codexHome ? { CODEX_HOME: runtime.codexHome } : {}) };
33
- const expected = autostartEntry({ ...installation, environment });
34
- let current = '';
35
- try { current = fs.readFileSync(file, 'utf8'); }
36
- catch (error) { if (error.code !== 'ENOENT') throw error; }
37
- const marker = `X-CodeRelax-Owner=${encodeURIComponent(path.resolve(installation.serviceRoot))}`;
38
- if (current && !current.split(/\r?\n/).includes(marker)) throw new Error('同名登录启动项不属于此安装,未修改。');
39
- const command = `${quote(installation.nodeExecutable)} ${quote(path.join(installation.serviceRoot, 'dist/bin/codex-remote.mjs'))} start --json`;
40
- const execLine = current.split(/\r?\n/).find(line => line.startsWith('Exec=')) || '';
41
- if (action === 'status') return { enabled: execLine.startsWith('Exec=/usr/bin/env ') && execLine.endsWith(command)
42
- && !/^(?:Hidden=true|X-GNOME-Autostart-enabled=false)$/m.test(current), path: file, mechanism: 'XDG Autostart' };
43
- if (action === 'disable') {
44
- if (current) fs.unlinkSync(file);
45
- return { enabled: false, removed: !!current, path: file };
46
- }
47
- fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
48
- const temporary = `${file}.${process.pid}.tmp`;
49
- try { fs.writeFileSync(temporary, expected, { flag: 'wx', mode: 0o600 }); fs.renameSync(temporary, file); }
50
- finally { if (fs.existsSync(temporary)) fs.unlinkSync(temporary); }
51
- return { enabled: true, path: file, mechanism: 'XDG Autostart', appliesAtNextLogin: true };
52
- }
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { assertPlainPath } from '../../skill-install.mjs';
5
+ import { runtimePaths } from '../runtime-paths.mjs';
6
+ import { readRecord } from '../../service-lifecycle.mjs';
7
+
8
+ // XDG autostart inherits the actual graphical session (Wayland/X11 and the
9
+ // unlocked Secret Service). No system daemon, sudo, or shell profile mutation.
10
+ const quote = value => {
11
+ if (/[\u0000-\u001f\u007f]/.test(value)) throw new Error('登录启动路径含无效控制字符。');
12
+ return `"${value.replace(/%/g, '%%').replace(/[\\"`$]/g, char => `\\${char}`).replace(/\\/g, '\\\\')}"`;
13
+ };
14
+ export function autostartEntry({ serviceRoot, nodeExecutable, environment = process.env }) {
15
+ const entry = path.join(serviceRoot, 'dist/bin/codex-remote.mjs');
16
+ const variables = {};
17
+ for (const name of ['CODEX_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'XDG_CONFIG_HOME'])
18
+ if (environment[name]) variables[name] = environment[name];
19
+ const stablePath = String(environment.PATH || '').split(':').filter(value => value.startsWith('/') && !value.endsWith('/node_modules/.bin')).join(':');
20
+ if (stablePath) variables.PATH = stablePath;
21
+ const command = [quote(nodeExecutable), quote(entry), 'start', '--json'].join(' ');
22
+ const assignments = Object.entries(variables).map(([key, value]) => quote(`${key}=${value}`));
23
+ return `[Desktop Entry]\nType=Application\nName=Code Relax\nComment=Code Relax workstation\nExec=/usr/bin/env ${[...assignments, command].join(' ')}\nTerminal=false\nX-CodeRelax-Owner=${encodeURIComponent(path.resolve(serviceRoot))}\n`;
24
+ }
25
+ export async function residentStartup(installation, action = 'status') {
26
+ if (!['status', 'enable', 'disable'].includes(action)) throw new Error('无效的登录启动操作。');
27
+ if (process.getuid() === 0) throw new Error('请以普通 Linux 桌面用户安装,不使用 sudo。');
28
+ const config = path.isAbsolute(process.env.XDG_CONFIG_HOME || '') ? process.env.XDG_CONFIG_HOME : path.join(os.homedir(), '.config');
29
+ const file = path.join(config, 'autostart/cn.walkhi.code-relax.desktop');
30
+ assertPlainPath(file);
31
+ const runtime = readRecord(path.join(runtimePaths().stateRoot, 'codex-runtime.json'));
32
+ const environment = { ...process.env, ...(runtime?.codexHome ? { CODEX_HOME: runtime.codexHome } : {}) };
33
+ const expected = autostartEntry({ ...installation, environment });
34
+ let current = '';
35
+ try { current = fs.readFileSync(file, 'utf8'); }
36
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
37
+ const marker = `X-CodeRelax-Owner=${encodeURIComponent(path.resolve(installation.serviceRoot))}`;
38
+ if (current && !current.split(/\r?\n/).includes(marker)) throw new Error('同名登录启动项不属于此安装,未修改。');
39
+ const command = `${quote(installation.nodeExecutable)} ${quote(path.join(installation.serviceRoot, 'dist/bin/codex-remote.mjs'))} start --json`;
40
+ const execLine = current.split(/\r?\n/).find(line => line.startsWith('Exec=')) || '';
41
+ if (action === 'status') return { enabled: execLine.startsWith('Exec=/usr/bin/env ') && execLine.endsWith(command)
42
+ && !/^(?:Hidden=true|X-GNOME-Autostart-enabled=false)$/m.test(current), path: file, mechanism: 'XDG Autostart' };
43
+ if (action === 'disable') {
44
+ if (current) fs.unlinkSync(file);
45
+ return { enabled: false, removed: !!current, path: file };
46
+ }
47
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
48
+ const temporary = `${file}.${process.pid}.tmp`;
49
+ try { fs.writeFileSync(temporary, expected, { flag: 'wx', mode: 0o600 }); fs.renameSync(temporary, file); }
50
+ finally { if (fs.existsSync(temporary)) fs.unlinkSync(temporary); }
51
+ return { enabled: true, path: file, mechanism: 'XDG Autostart', appliesAtNextLogin: true };
52
+ }