@petercjl/codex-own 0.2.0 → 0.2.1
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 +1 -1
- package/capabilities.json +2 -1
- package/lib/network.mjs +4 -1
- package/lib/npm-assets.mjs +19 -6
- package/package.json +34 -10
- package/release.json +33 -9
- package/skills/codex-own/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ $codexOwn = Join-Path (npm.cmd prefix --global) "codex-own.cmd"
|
|
|
76
76
|
& $codexOwn install --yes
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
管理工具和桌面资源全部从 registry.npmjs.org 下载。Windows 桌面下载量约 878 MiB
|
|
79
|
+
管理工具和桌面资源全部从 registry.npmjs.org 下载。Windows 桌面下载量约 878 MiB,最多并行下载 4 个分片,安装时显示分片编号、下载百分比和已接收大小,然后显示校验、解压、安装。看到“安装完成”和 ok: true 才表示完成。
|
|
80
80
|
|
|
81
81
|
若已有手动安装的 codex-own,先退出应用,将第二步的安装命令替换为以下接管命令;原安装会被备份。
|
|
82
82
|
|
package/capabilities.json
CHANGED
package/lib/network.mjs
CHANGED
|
@@ -13,17 +13,19 @@ export async function fileHash(file) {
|
|
|
13
13
|
return h.digest('hex');
|
|
14
14
|
}
|
|
15
15
|
export async function verifiedDownload(url, dest, expected, {
|
|
16
|
-
fetcher = networkFetch, report = progress, idleMs = 30000, attempts = 3, retryMs = 1000,
|
|
16
|
+
fetcher = networkFetch, report = progress, idleMs = 30000, attempts = 3, retryMs = 1000, signal,
|
|
17
17
|
} = {}) {
|
|
18
18
|
const error = (code, message) => Object.assign(new Error(message), {code});
|
|
19
19
|
if (fs.existsSync(dest)) throw error('TARGET_EXISTS', 'Download destination already exists');
|
|
20
20
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
21
|
+
if(signal?.aborted)throw error('CANCELLED','Download group cancelled');
|
|
21
22
|
const controller = new AbortController();
|
|
22
23
|
let file, bytes = 0, timer, cancelled = false;
|
|
23
24
|
const start = Date.now();
|
|
24
25
|
const cancel = () => { cancelled = true; controller.abort(); };
|
|
25
26
|
const reset = () => { clearTimeout(timer); timer = setTimeout(() => controller.abort(), idleMs); };
|
|
26
27
|
const tick = () => report(`下载 ${Math.floor(bytes / expected.size * 100)}% · ${(bytes / 1048576).toFixed(1)} / ${(expected.size / 1048576).toFixed(1)} MiB · ${((Date.now()-start)/1000).toFixed(0)}s`);
|
|
28
|
+
signal?.addEventListener('abort',cancel,{once:true});
|
|
27
29
|
process.once('SIGINT', cancel);
|
|
28
30
|
process.once('SIGTERM', cancel);
|
|
29
31
|
const interval = setInterval(tick, 3000);
|
|
@@ -55,6 +57,7 @@ export async function verifiedDownload(url, dest, expected, {
|
|
|
55
57
|
if (cancelled) throw error('CANCELLED', 'Download cancelled. Verified earlier parts are retained; rerun the install command to continue.');
|
|
56
58
|
} finally {
|
|
57
59
|
clearTimeout(timer); clearInterval(interval);
|
|
60
|
+
signal?.removeEventListener('abort',cancel);
|
|
58
61
|
process.removeListener('SIGINT', cancel); process.removeListener('SIGTERM', cancel);
|
|
59
62
|
if (file) await file.close();
|
|
60
63
|
}
|
package/lib/npm-assets.mjs
CHANGED
|
@@ -24,31 +24,44 @@ function safeCache(file) {
|
|
|
24
24
|
if (fs.existsSync(file) && (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()))
|
|
25
25
|
throw Object.assign(new Error('Unexpected npm asset cache entry'), {code:'UNSAFE_PATH'});
|
|
26
26
|
}
|
|
27
|
-
export async function assembleNpmAsset(asset, dest, cache, tar, {download = verifiedDownload, report = progress} = {}) {
|
|
27
|
+
export async function assembleNpmAsset(asset, dest, cache, tar, {download = verifiedDownload, report = progress, concurrency = 4} = {}) {
|
|
28
28
|
validateParts(asset);
|
|
29
29
|
fs.mkdirSync(cache, {recursive:true, mode:0o700});
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 4) throw new Error('Invalid download concurrency');
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
async function fetchPart(part, i) {
|
|
32
33
|
report(`npm 资源分片 ${i+1}/${asset.parts.length}`);
|
|
33
34
|
const file = path.join(cache, `${part.sha256}.bin`);
|
|
34
35
|
safeCache(file);
|
|
35
36
|
if (fs.existsSync(file) && fs.statSync(file).size === part.size && await fileHash(file) === part.sha256) {
|
|
36
|
-
report(
|
|
37
|
+
report(`[${i+1}/${asset.parts.length}] 复用已校验分片`); return file;
|
|
37
38
|
}
|
|
38
39
|
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
39
40
|
const scratch = fs.mkdtempSync(path.join(cache, 'fetch-'));
|
|
40
41
|
try {
|
|
41
42
|
const archive = path.join(scratch, 'package.tgz');
|
|
42
|
-
await download(part.url, archive, {size:part.archiveSize, sha256:part.archiveSha256});
|
|
43
|
+
await download(part.url, archive, {size:part.archiveSize, sha256:part.archiveSha256}, {signal:controller.signal, report:message=>report(`[${i+1}/${asset.parts.length}] ${message}`)});
|
|
43
44
|
// Extract only the fixed payload entry, never arbitrary paths from a package tarball.
|
|
44
45
|
execFileSync(tar, ['-xf', archive, '-C', scratch, 'package/payload.bin'], {stdio:'pipe'});
|
|
45
46
|
const payload = path.join(scratch, 'package', 'payload.bin');
|
|
46
47
|
safeCache(payload);
|
|
47
48
|
if (fs.statSync(payload).size !== part.size || await fileHash(payload) !== part.sha256)
|
|
48
49
|
throw Object.assign(new Error('npm payload checksum mismatch'), {code:'CHECKSUM_MISMATCH'});
|
|
49
|
-
fs.renameSync(payload, file);
|
|
50
|
+
fs.renameSync(payload, file); return file;
|
|
50
51
|
} finally { fs.rmSync(scratch, {recursive:true,force:true}); }
|
|
51
52
|
}
|
|
53
|
+
const unique = [...new Map(asset.parts.map((part,i)=>[part.sha256,{part,i}])).values()];
|
|
54
|
+
const completed = new Map(); let cursor = 0, failure;
|
|
55
|
+
async function worker() {
|
|
56
|
+
while(!controller.signal.aborted && cursor < unique.length) {
|
|
57
|
+
const {part,i}=unique[cursor++];
|
|
58
|
+
try { completed.set(part.sha256,await fetchPart(part,i)); }
|
|
59
|
+
catch(e) { if(!failure)failure=e;controller.abort();throw e; }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
await Promise.allSettled(Array.from({length:Math.min(concurrency,unique.length)},worker));
|
|
63
|
+
if(failure)throw failure;
|
|
64
|
+
const files=asset.parts.map(part=>completed.get(part.sha256));
|
|
52
65
|
report('合并并校验完整桌面安装包');
|
|
53
66
|
const output = fs.createWriteStream(dest, {flags:'wx',mode:0o600});
|
|
54
67
|
async function* chunks() { for (const f of files) yield* fs.createReadStream(f); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/codex-own",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Independent codex-own desktop installation and update manager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"ownDesktopManifest": {
|
|
45
45
|
"schema": 2,
|
|
46
46
|
"repository": "petercjl/codex-own",
|
|
47
|
-
"version": "0.2.
|
|
47
|
+
"version": "0.2.1",
|
|
48
48
|
"platforms": {
|
|
49
49
|
"darwin-arm64": {
|
|
50
50
|
"entry": "codex-own.app",
|
|
@@ -155,22 +155,46 @@
|
|
|
155
155
|
"provenance": {
|
|
156
156
|
"darwin-arm64": {
|
|
157
157
|
"darwin-arm64": {
|
|
158
|
-
"
|
|
158
|
+
"darwin-arm64": {
|
|
159
|
+
"sourceRelease": "own-v0.1.0"
|
|
160
|
+
},
|
|
161
|
+
"win32-x64": {
|
|
162
|
+
"kind": "prepared-independent-build",
|
|
163
|
+
"desktopVersion": "26.1007.21434",
|
|
164
|
+
"guiAcceptance": "user-pending"
|
|
165
|
+
}
|
|
159
166
|
},
|
|
160
167
|
"win32-x64": {
|
|
161
|
-
"
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
"darwin-arm64": {
|
|
169
|
+
"sourceRelease": "own-v0.1.0"
|
|
170
|
+
},
|
|
171
|
+
"win32-x64": {
|
|
172
|
+
"kind": "prepared-independent-build",
|
|
173
|
+
"desktopVersion": "26.1007.21434",
|
|
174
|
+
"guiAcceptance": "user-pending"
|
|
175
|
+
}
|
|
164
176
|
}
|
|
165
177
|
},
|
|
166
178
|
"win32-x64": {
|
|
167
179
|
"darwin-arm64": {
|
|
168
|
-
"
|
|
180
|
+
"darwin-arm64": {
|
|
181
|
+
"sourceRelease": "own-v0.1.0"
|
|
182
|
+
},
|
|
183
|
+
"win32-x64": {
|
|
184
|
+
"kind": "prepared-independent-build",
|
|
185
|
+
"desktopVersion": "26.1007.21434",
|
|
186
|
+
"guiAcceptance": "user-pending"
|
|
187
|
+
}
|
|
169
188
|
},
|
|
170
189
|
"win32-x64": {
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
"darwin-arm64": {
|
|
191
|
+
"sourceRelease": "own-v0.1.0"
|
|
192
|
+
},
|
|
193
|
+
"win32-x64": {
|
|
194
|
+
"kind": "prepared-independent-build",
|
|
195
|
+
"desktopVersion": "26.1007.21434",
|
|
196
|
+
"guiAcceptance": "user-pending"
|
|
197
|
+
}
|
|
174
198
|
}
|
|
175
199
|
}
|
|
176
200
|
}
|
package/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 2,
|
|
3
3
|
"repository": "petercjl/codex-own",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"platforms": {
|
|
6
6
|
"darwin-arm64": {
|
|
7
7
|
"entry": "codex-own.app",
|
|
@@ -112,22 +112,46 @@
|
|
|
112
112
|
"provenance": {
|
|
113
113
|
"darwin-arm64": {
|
|
114
114
|
"darwin-arm64": {
|
|
115
|
-
"
|
|
115
|
+
"darwin-arm64": {
|
|
116
|
+
"sourceRelease": "own-v0.1.0"
|
|
117
|
+
},
|
|
118
|
+
"win32-x64": {
|
|
119
|
+
"kind": "prepared-independent-build",
|
|
120
|
+
"desktopVersion": "26.1007.21434",
|
|
121
|
+
"guiAcceptance": "user-pending"
|
|
122
|
+
}
|
|
116
123
|
},
|
|
117
124
|
"win32-x64": {
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
"darwin-arm64": {
|
|
126
|
+
"sourceRelease": "own-v0.1.0"
|
|
127
|
+
},
|
|
128
|
+
"win32-x64": {
|
|
129
|
+
"kind": "prepared-independent-build",
|
|
130
|
+
"desktopVersion": "26.1007.21434",
|
|
131
|
+
"guiAcceptance": "user-pending"
|
|
132
|
+
}
|
|
121
133
|
}
|
|
122
134
|
},
|
|
123
135
|
"win32-x64": {
|
|
124
136
|
"darwin-arm64": {
|
|
125
|
-
"
|
|
137
|
+
"darwin-arm64": {
|
|
138
|
+
"sourceRelease": "own-v0.1.0"
|
|
139
|
+
},
|
|
140
|
+
"win32-x64": {
|
|
141
|
+
"kind": "prepared-independent-build",
|
|
142
|
+
"desktopVersion": "26.1007.21434",
|
|
143
|
+
"guiAcceptance": "user-pending"
|
|
144
|
+
}
|
|
126
145
|
},
|
|
127
146
|
"win32-x64": {
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
"darwin-arm64": {
|
|
148
|
+
"sourceRelease": "own-v0.1.0"
|
|
149
|
+
},
|
|
150
|
+
"win32-x64": {
|
|
151
|
+
"kind": "prepared-independent-build",
|
|
152
|
+
"desktopVersion": "26.1007.21434",
|
|
153
|
+
"guiAcceptance": "user-pending"
|
|
154
|
+
}
|
|
131
155
|
}
|
|
132
156
|
}
|
|
133
157
|
}
|
|
@@ -29,3 +29,5 @@ For installation-only acceptance, verify doctor, version, target and shortcut wi
|
|
|
29
29
|
- Manage this bundled Skill through `skill status/install/update --agent codex|sealseek`. For another Agent use its configured `--target-root`, not a guessed path. Unknown existing Skill targets are refused, managed updates retain backups and customized UI metadata.
|
|
30
30
|
|
|
31
31
|
The portable contract uses a terminal that can invoke the same CLI on either Agent. Agent-specific scheduling and publication are repository operations, outside ordinary installation. Runtime must not rewrite its own source. A recurring failure belongs in an authorized implementation/test change.
|
|
32
|
+
|
|
33
|
+
Downloads use at most four parallel npm payload requests, label progress by part, preserve archive order, and cancel active peer downloads on failure.
|