@walkhi/code-relax 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/bin/self-relay-server.mjs +5 -2
- package/dist/shared/app-server-events.cjs +45 -19
- package/dist/shared/p2p-data-channel.cjs +50 -0
- package/dist/src/app-server-tasks.mjs +28 -25
- package/dist/src/lan-socket-server.mjs +8 -2
- package/dist/src/platform/windows/IsolatedProcess.cs +7 -3
- package/dist/src/platform/windows/background-process.mjs +7 -2
- package/dist/src/platform/windows/launch-worker.ps1 +13 -6
- package/dist/src/platform/windows/start-hidden-console.ps1 +11 -6
- package/dist/src/self-relay/admin-state.mjs +61 -0
- package/dist/src/self-relay/client.mjs +2 -0
- package/dist/src/self-relay/connector.mjs +23 -8
- package/dist/src/self-relay/demo.mjs +2 -2
- package/dist/src/self-relay/lifecycle.mjs +1 -1
- package/dist/src/self-relay/p2p-probe.mjs +123 -83
- package/dist/src/self-relay/server.mjs +137 -11
- package/dist/src/server.mjs +337 -255
- package/dist/src/shared-app-server.mjs +180 -24
- package/dist/src/shared-catalog.mjs +4 -14
- package/dist/src/thread-catalog.mjs +12 -7
- package/dist/web/activity-view.js +283 -0
- package/dist/web/capabilities.js +2 -2
- package/dist/web/chat-transport.js +15 -9
- package/dist/web/chat.css +139 -93
- package/dist/web/chat.js +1451 -2770
- package/dist/web/community-view.js +20 -0
- package/dist/web/community.css +77 -0
- package/dist/web/community.html +37 -0
- package/dist/web/composer-controller.js +101 -0
- package/dist/web/conversation-controller.js +99 -0
- package/dist/web/disclosure-state-controller.js +95 -0
- package/dist/web/draft-controller.js +103 -0
- package/dist/web/harmony-platform.js +3 -2
- package/dist/web/history-cache.js +112 -18
- package/dist/web/history-controller.js +167 -0
- package/dist/web/index.html +141 -38
- package/dist/web/link-action-controller.js +212 -0
- package/dist/web/message-send-controller.js +177 -0
- package/dist/web/message-view.js +98 -0
- package/dist/web/p2p-data-channel.js +50 -0
- package/dist/web/p2p-probe.js +67 -27
- package/dist/web/page-resume.js +28 -0
- package/dist/web/pending-message-store.js +108 -0
- package/dist/web/queue-controller.js +82 -0
- package/dist/web/resources.json +1 -1
- package/dist/web/self-relay-session.js +28 -18
- package/dist/web/station-connection-controller.js +75 -0
- package/dist/web/task-list-view.js +296 -0
- package/dist/web/thread-attention-controller.js +124 -0
- package/dist/web/thread-context-controller.js +30 -0
- package/dist/web/thread-list-controller.js +61 -0
- package/dist/web/thread-list-sync.js +86 -0
- package/dist/web/thread-title-controller.js +57 -0
- package/dist/web/timeline-formatters.js +249 -0
- package/dist/web/timeline-reducer.js +81 -0
- package/dist/web/timeline-renderer.js +161 -0
- package/dist/web/timeline-scroll-controller.js +50 -0
- package/dist/web/usage-controller.js +258 -0
- package/dist/web/vendor/lucide.LICENSE.txt +17 -0
- package/package.json +1 -1
- package/tools/postinstall.mjs +66 -2
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const create = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = create;
|
|
4
|
+
else root.createUsageController = create;
|
|
5
|
+
})(globalThis, function () {
|
|
6
|
+
function createUsageController(options) {
|
|
7
|
+
const { document, el, transport, api } = options;
|
|
8
|
+
let stream = null, latest = null, clock;
|
|
9
|
+
|
|
10
|
+
function windowLabel(window, now = Date.now()) {
|
|
11
|
+
if (!window) return '';
|
|
12
|
+
const duration = Number(window.windowDurationMins);
|
|
13
|
+
const label = duration === 300 ? '5 小时' : duration === 10080 ? '周' : duration > 0 ? `${duration} 分钟` : '额度';
|
|
14
|
+
const used = typeof window.usedPercent === 'number' && Number.isFinite(window.usedPercent)
|
|
15
|
+
? Math.max(0, Math.min(100, window.usedPercent)) : null;
|
|
16
|
+
const remaining = used === null ? null : 100 - used;
|
|
17
|
+
const resetAt = Number(window.resetsAt) * 1000;
|
|
18
|
+
const validReset = Number.isFinite(resetAt) && resetAt > 0;
|
|
19
|
+
const reset = validReset ? new Date(resetAt).toLocaleString('zh-CN', {
|
|
20
|
+
month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }) : '';
|
|
21
|
+
let countdown = '', ratio = null, timeElapsed = null;
|
|
22
|
+
if (validReset) {
|
|
23
|
+
const left = resetAt - now;
|
|
24
|
+
if (duration > 0 && Number.isFinite(duration) && left <= duration * 60000) {
|
|
25
|
+
timeElapsed = Math.max(0, Math.min(100, 100 - left / (duration * 60000) * 100));
|
|
26
|
+
}
|
|
27
|
+
if (left <= 0) countdown = '已到重置时间,等待额度更新';
|
|
28
|
+
else {
|
|
29
|
+
const minutes = Math.ceil(left / 60000);
|
|
30
|
+
const days = Math.floor(minutes / 1440), hours = Math.floor(minutes % 1440 / 60), mins = minutes % 60;
|
|
31
|
+
countdown = `剩余 ${[days && `${days}天`, hours && `${hours}小时`, mins && `${mins}分钟`].filter(Boolean).join('')}`;
|
|
32
|
+
if (timeElapsed > 0 && used !== null) ratio = used / timeElapsed;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const segments = duration === 300 ? 5 : duration === 10080 ? 7 : 0;
|
|
36
|
+
const scaleLabels = [];
|
|
37
|
+
if (duration === 300 && validReset) {
|
|
38
|
+
for (let index = 0; index <= segments; index++) {
|
|
39
|
+
const time = new Date(resetAt - index * 60 * 60000).toLocaleTimeString('zh-CN', {
|
|
40
|
+
hour: '2-digit', minute: '2-digit', hour12: false });
|
|
41
|
+
scaleLabels.push({ position: index / segments * 100, text: time });
|
|
42
|
+
}
|
|
43
|
+
} else if (duration === 10080) {
|
|
44
|
+
for (let day = 0; day <= segments; day++) {
|
|
45
|
+
scaleLabels.push({ position: day / segments * 100, text: String(day) });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { label, used, remaining, timeElapsed, timeRemaining: timeElapsed === null ? null : 100 - timeElapsed,
|
|
49
|
+
reset, countdown, ratio, segments, scaleLabels };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function appendRemainingLabel(container, label, value, colorize = false) {
|
|
53
|
+
container.append(document.createTextNode(`${label} `));
|
|
54
|
+
const percentage = document.createElement('span');
|
|
55
|
+
percentage.className = 'usage-percentage';
|
|
56
|
+
percentage.textContent = value === null ? '—%' : `${Math.round(value)}%`;
|
|
57
|
+
if (colorize && value !== null) percentage.style.color = `hsl(${Math.round(value * 1.2)}, 68%, 52%)`;
|
|
58
|
+
container.append(percentage);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function mainLimit(data) {
|
|
62
|
+
return data?.rateLimitsByLimitId?.codex
|
|
63
|
+
|| ((!data?.rateLimits?.limitId || data.rateLimits.limitId === 'codex') ? data?.rateLimits : null);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function visibleWindows(data) {
|
|
67
|
+
const limit = mainLimit(data);
|
|
68
|
+
const pro = ['pro', 'prolite'].includes(String(limit?.planType || data?.rateLimits?.planType || '').toLowerCase());
|
|
69
|
+
return [limit?.primary, limit?.secondary]
|
|
70
|
+
.filter(window => window && (Number(window.windowDurationMins) === 10080
|
|
71
|
+
|| (!pro && Number(window.windowDurationMins) === 300)))
|
|
72
|
+
.sort((left, right) => Number(right.windowDurationMins) - Number(left.windowDurationMins));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function summary(data, now = Date.now()) {
|
|
76
|
+
const windows = visibleWindows(data)
|
|
77
|
+
.filter(window => typeof window?.usedPercent === 'number' && Number.isFinite(window.usedPercent));
|
|
78
|
+
if (!windows.length) return { label: '剩余', remaining: null, pace: '', paceClass: '', title: '暂无 Codex 主额度数据,点击查看详情' };
|
|
79
|
+
const window = windows.reduce((lowest, item) => item.usedPercent > lowest.usedPercent ? item : lowest);
|
|
80
|
+
const remaining = Math.round(Math.max(0, Math.min(100, 100 - window.usedPercent)));
|
|
81
|
+
const name = window.windowDurationMins === 300 ? '5 小时' : window.windowDurationMins === 10080 ? '周' : '限制窗口';
|
|
82
|
+
const ratio = windowLabel(window, now).ratio;
|
|
83
|
+
const pace = Number.isFinite(ratio) ? `${ratio.toFixed(2)}×` : '';
|
|
84
|
+
const paceClass = pace ? ratio <= 1 ? 'usage-ratio-good' : 'usage-ratio-low' : '';
|
|
85
|
+
return { label: `${name}剩余`, remaining, pace, paceClass,
|
|
86
|
+
title: `${name}额度剩余 ${remaining}%;${pace ? `已用额度 ÷ 已过时间 ${pace};` : ''}显示主额度中剩余较少的窗口,点击查看详情` };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderSummary(data = latest) {
|
|
90
|
+
const value = summary(data);
|
|
91
|
+
const remaining = document.createElement('span');
|
|
92
|
+
appendRemainingLabel(remaining, value.label, value.remaining, true);
|
|
93
|
+
const content = [remaining];
|
|
94
|
+
if (value.pace) {
|
|
95
|
+
const ratio = document.createElement('span');
|
|
96
|
+
ratio.className = `usage-summary-ratio ${value.paceClass}`;
|
|
97
|
+
ratio.textContent = value.pace;
|
|
98
|
+
content.push(ratio);
|
|
99
|
+
}
|
|
100
|
+
el('usageSummary').replaceChildren(...content);
|
|
101
|
+
el('usageButton').title = value.title;
|
|
102
|
+
el('usageButton').setAttribute('aria-label', value.title);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderDetails(data) {
|
|
106
|
+
const rows = [];
|
|
107
|
+
const windows = visibleWindows(data).map(window => windowLabel(window));
|
|
108
|
+
if (windows.length) {
|
|
109
|
+
const group = document.createElement('div');
|
|
110
|
+
group.className = 'usage-group';
|
|
111
|
+
for (const window of windows) {
|
|
112
|
+
const section = document.createElement('div');
|
|
113
|
+
section.className = 'usage-window';
|
|
114
|
+
const heading = document.createElement('strong');
|
|
115
|
+
heading.textContent = window.label;
|
|
116
|
+
const header = document.createElement('div');
|
|
117
|
+
header.className = 'usage-heading';
|
|
118
|
+
header.append(heading);
|
|
119
|
+
const ratio = document.createElement('span');
|
|
120
|
+
ratio.className = `usage-ratio ${window.ratio === null ? '' : window.ratio <= 1 ? 'usage-ratio-good' : 'usage-ratio-low'}`;
|
|
121
|
+
ratio.textContent = window.ratio === null ? '—×' : `${window.ratio.toFixed(2)}×`;
|
|
122
|
+
ratio.title = '已用额度 ÷ 已过时间;1× 为均匀使用基准';
|
|
123
|
+
ratio.setAttribute('aria-label', `${ratio.title},当前 ${ratio.textContent}`);
|
|
124
|
+
header.append(ratio);
|
|
125
|
+
section.append(header);
|
|
126
|
+
|
|
127
|
+
const timeline = document.createElement('div');
|
|
128
|
+
timeline.className = `usage-timeline ${window.ratio !== null && window.ratio > 1 ? 'usage-timeline-ahead' : ''}`;
|
|
129
|
+
const value = document.createElement('div');
|
|
130
|
+
value.className = 'usage-timeline-summary';
|
|
131
|
+
const used = document.createElement('span');
|
|
132
|
+
used.className = 'usage-timeline-used';
|
|
133
|
+
appendRemainingLabel(used, '额度剩余', window.remaining, true);
|
|
134
|
+
const elapsed = document.createElement('span');
|
|
135
|
+
elapsed.className = 'usage-timeline-elapsed';
|
|
136
|
+
appendRemainingLabel(elapsed, '时间剩余', window.timeRemaining);
|
|
137
|
+
value.append(used);
|
|
138
|
+
|
|
139
|
+
const track = document.createElement('div');
|
|
140
|
+
track.className = 'usage-timeline-track';
|
|
141
|
+
track.setAttribute('role', 'img');
|
|
142
|
+
track.setAttribute('aria-label', `${window.label}额度:${used.textContent},${elapsed.textContent}`);
|
|
143
|
+
if (window.used !== null) {
|
|
144
|
+
const fill = document.createElement('span');
|
|
145
|
+
fill.className = 'usage-timeline-fill';
|
|
146
|
+
fill.style.width = `${window.remaining}%`;
|
|
147
|
+
track.append(fill);
|
|
148
|
+
}
|
|
149
|
+
if (window.segments > 0) {
|
|
150
|
+
for (let index = 1; index < window.segments; index++) {
|
|
151
|
+
const tick = document.createElement('span');
|
|
152
|
+
tick.className = 'usage-timeline-tick';
|
|
153
|
+
tick.style.left = `${index / window.segments * 100}%`;
|
|
154
|
+
track.append(tick);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (window.timeElapsed !== null) {
|
|
158
|
+
const marker = document.createElement('span');
|
|
159
|
+
marker.className = 'usage-timeline-now';
|
|
160
|
+
marker.style.left = `${window.timeRemaining}%`;
|
|
161
|
+
marker.title = `当前时间 · 时间剩余 ${Math.round(window.timeRemaining)}%`;
|
|
162
|
+
track.append(marker);
|
|
163
|
+
}
|
|
164
|
+
timeline.append(value, track);
|
|
165
|
+
|
|
166
|
+
if (window.scaleLabels.length) {
|
|
167
|
+
const scale = document.createElement('div');
|
|
168
|
+
scale.className = 'usage-timeline-scale';
|
|
169
|
+
for (const label of window.scaleLabels) {
|
|
170
|
+
const tickLabel = document.createElement('span');
|
|
171
|
+
tickLabel.className = `usage-timeline-scale-label${label.position === 0 ? ' is-first' : label.position === 100 ? ' is-last' : ''}`;
|
|
172
|
+
tickLabel.style.left = `${label.position}%`;
|
|
173
|
+
tickLabel.textContent = label.text;
|
|
174
|
+
scale.append(tickLabel);
|
|
175
|
+
}
|
|
176
|
+
timeline.append(scale);
|
|
177
|
+
}
|
|
178
|
+
const timeSummary = document.createElement('div');
|
|
179
|
+
timeSummary.className = 'usage-timeline-time-summary';
|
|
180
|
+
timeSummary.append(elapsed);
|
|
181
|
+
timeline.append(timeSummary);
|
|
182
|
+
section.append(timeline);
|
|
183
|
+
const detail = document.createElement('div');
|
|
184
|
+
detail.className = 'usage-window-detail';
|
|
185
|
+
detail.textContent = window.reset ? `${window.reset} 重置 · ${window.countdown}` : '重置时间暂不可用';
|
|
186
|
+
section.append(detail);
|
|
187
|
+
group.append(section);
|
|
188
|
+
}
|
|
189
|
+
rows.push(group);
|
|
190
|
+
}
|
|
191
|
+
const plan = (mainLimit(data)?.planType || data?.rateLimits?.planType || '未知').toLowerCase();
|
|
192
|
+
el('usagePlan').textContent = plan === 'pro' || plan === 'prolite' ? 'Pro' : plan.toUpperCase();
|
|
193
|
+
if (plan === 'pro' || plan === 'prolite') {
|
|
194
|
+
const tier = document.createElement('span');
|
|
195
|
+
tier.className = 'usage-plan-tier';
|
|
196
|
+
tier.textContent = plan === 'pro' ? '×20' : '×5';
|
|
197
|
+
el('usagePlan').append(tier);
|
|
198
|
+
}
|
|
199
|
+
el('usageRows').replaceChildren(...rows);
|
|
200
|
+
if (!rows.length) el('usageRows').textContent = '暂无额度数据';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function load() {
|
|
204
|
+
el('usageRows').textContent = '正在查询…';
|
|
205
|
+
el('usageRows').removeAttribute('title');
|
|
206
|
+
try {
|
|
207
|
+
const data = (stream && latest) || await api('/api/usage-limits');
|
|
208
|
+
latest = data;
|
|
209
|
+
renderSummary();
|
|
210
|
+
renderDetails(data);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
el('usageRows').textContent = '额度查询失败';
|
|
213
|
+
el('usageRows').title = error.message;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function start() {
|
|
218
|
+
if (stream) return;
|
|
219
|
+
const source = transport.usageEvents();
|
|
220
|
+
if (!source) { void load(); return; }
|
|
221
|
+
stream = source;
|
|
222
|
+
source.addEventListener('usage', event => {
|
|
223
|
+
latest = JSON.parse(event.data);
|
|
224
|
+
renderSummary();
|
|
225
|
+
if (el('usageDialog').open) {
|
|
226
|
+
if (latest) void load();
|
|
227
|
+
else { el('usageRows').textContent = '等待连接恢复…'; el('usagePlan').textContent = ''; }
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
source.addEventListener('usage-error', event => {
|
|
231
|
+
if (!latest) { el('usageRows').textContent = '额度读取失败'; el('usageRows').title = JSON.parse(event.data).error; }
|
|
232
|
+
});
|
|
233
|
+
source.onerror = () => {
|
|
234
|
+
latest = null;
|
|
235
|
+
renderSummary();
|
|
236
|
+
if (el('usageDialog').open) el('usageRows').textContent = '等待连接恢复…';
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function open() {
|
|
241
|
+
el('usageDialog').showModal();
|
|
242
|
+
void load();
|
|
243
|
+
clearInterval(clock);
|
|
244
|
+
clock = setInterval(() => { if (latest) renderDetails(latest); }, 30000);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function close() {
|
|
248
|
+
clearInterval(clock);
|
|
249
|
+
stream?.close();
|
|
250
|
+
stream = null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
el('usageDialog').addEventListener('close', () => { clearInterval(clock); });
|
|
254
|
+
return Object.freeze({ start, load, open, close, refreshSummary: () => renderSummary() });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return createUsageController;
|
|
258
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
8
|
+
|
|
9
|
+
The Download icon is derived from the Feather project and is also available under the MIT License:
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2013-present Cole Bemis
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
CHANGED
package/tools/postinstall.mjs
CHANGED
|
@@ -1,9 +1,73 @@
|
|
|
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
|
|
|
7
|
+
const serviceRoot = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
|
8
|
+
'Programs', 'CodexRemote', 'service');
|
|
9
|
+
|
|
10
|
+
function installationSnapshot() {
|
|
11
|
+
const recordPath = path.join(serviceRoot, 'installation.json');
|
|
12
|
+
if (!fs.existsSync(serviceRoot)) return { exists: false, valid: false };
|
|
13
|
+
try {
|
|
14
|
+
const record = JSON.parse(fs.readFileSync(recordPath, 'utf8').replace(/^\uFEFF/u, ''));
|
|
15
|
+
return { exists: true, valid: Boolean(record?.installedAt && record?.version),
|
|
16
|
+
version: record?.version, installedAt: record?.installedAt };
|
|
17
|
+
} catch {
|
|
18
|
+
return { exists: true, valid: false };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sameInstallation(left, right) {
|
|
23
|
+
return left.valid && right.valid && left.version === right.version && left.installedAt === right.installedAt;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function setupFailureGuidance(previous, current) {
|
|
27
|
+
if (previous.valid && sameInstallation(previous, current)) {
|
|
28
|
+
return `检测到旧安装 ${previous.version};新版本部署未完成,旧安装记录仍保留。\n`
|
|
29
|
+
+ '请先执行 relax doctor;修复报错原因后重新运行 npm 安装命令,无需卸载旧版本。';
|
|
30
|
+
}
|
|
31
|
+
if (previous.valid && current.valid) {
|
|
32
|
+
return `检测到旧安装 ${previous.version};服务文件已更新为 ${current.version},但 setup 后续步骤失败。\n`
|
|
33
|
+
+ '请执行 relax doctor 确认当前状态,修复后执行 relax setup;无需先卸载。';
|
|
34
|
+
}
|
|
35
|
+
if (previous.exists && current.valid) {
|
|
36
|
+
return `检测到既有服务目录,但无法确认有效的旧安装记录;服务文件现已写入 ${current.version},setup 后续步骤仍失败。\n`
|
|
37
|
+
+ '请执行 relax doctor 确认当前状态,修复后执行 relax setup;不要直接删除配置目录。';
|
|
38
|
+
}
|
|
39
|
+
if (previous.exists) {
|
|
40
|
+
return '检测到既有服务目录,但无法确认有效的旧安装记录;本次部署未完成。\n'
|
|
41
|
+
+ '请执行 relax doctor,根据检查结果修复后重新运行 npm 安装命令,不要直接删除配置目录。';
|
|
42
|
+
}
|
|
43
|
+
if (current.valid) {
|
|
44
|
+
return `这是首次安装;服务文件 ${current.version} 已写入,但 setup 后续步骤失败。\n`
|
|
45
|
+
+ '如果 relax 命令仍可用,请执行 relax doctor,再执行 relax setup;否则修复原因后重新运行 npm 安装命令。';
|
|
46
|
+
}
|
|
47
|
+
return '这是首次安装,尚未形成可确认的固定服务。\n修复上方错误后重新运行 npm 安装命令;无需执行卸载。';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function skillFailureGuidance(previous, current) {
|
|
51
|
+
const service = current.valid
|
|
52
|
+
? `${previous.valid ? `服务已从 ${previous.version} 更新到` : '固定服务已安装为'} ${current.version}`
|
|
53
|
+
: 'setup 已返回成功,但当前固定服务记录无法确认';
|
|
54
|
+
return `${service};Codex Skill 安装失败。\n请执行 relax skill-install,再执行 relax doctor;无需重新卸载服务。`;
|
|
55
|
+
}
|
|
56
|
+
|
|
4
57
|
if (process.env.npm_config_global === 'true') {
|
|
5
58
|
const entry = fileURLToPath(new URL('../dist/bin/codex-remote.mjs', import.meta.url));
|
|
6
|
-
|
|
7
|
-
|
|
59
|
+
const previous = installationSnapshot();
|
|
60
|
+
let stage = 'setup';
|
|
61
|
+
try {
|
|
62
|
+
execFileSync(process.execPath, [entry, 'setup', '--json'], { stdio: 'inherit', windowsHide: true });
|
|
63
|
+
stage = 'skill-install';
|
|
64
|
+
execFileSync(process.execPath, [entry, 'skill-install', '--json'], { stdio: 'inherit', windowsHide: true });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const current = installationSnapshot();
|
|
67
|
+
console.error(`\nCode Relax 安装未完全完成:\n${stage === 'setup'
|
|
68
|
+
? setupFailureGuidance(previous, current)
|
|
69
|
+
: skillFailureGuidance(previous, current)}`);
|
|
70
|
+
if (error?.code && error.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') console.error(`启动安装步骤失败:${error.message}`);
|
|
71
|
+
process.exitCode = Number.isInteger(error?.status) && error.status > 0 ? error.status : 1;
|
|
8
72
|
}
|
|
9
73
|
}
|