@petercjl/codex-own 0.1.0 → 0.2.0

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 CHANGED
@@ -1,13 +1,123 @@
1
1
  # codex-own
2
2
 
3
- Independent desktop installer and updater. Node.js 22+ is required for the manager. The desktop app is downloaded as a platform artifact, not compiled on the user's machine. Existing configuration, API credentials and conversations remain in the independent application data directory.
3
+ 独立桌面应用的 npm 安装与更新工具。支持 Mac ARM64 和 Windows x64。管理工具和桌面资源均从 npm 下载。
4
4
 
5
- Send your Agent: “Install @petercjl/codex-own and set up its desktop application.”
5
+ ## 在 Mac 上通过 Terminal 安装和更新
6
6
 
7
- Update: “Use @petercjl/codex-own to update my desktop application, preserving configuration and conversations.”
7
+ 适用于 Apple 芯片 Mac(ARM64)。从 Node.js 官网安装 LTS 版本(要求 Node.js 22 或以上,包含 npm),重新打开 Terminal。建议预留 10 GB 空间,用于下载、解压和备份。
8
8
 
9
- Check: “Use codex-own to report the installed desktop version, available version and installation health.”
9
+ [Node.js 官方下载](https://nodejs.org/en/download)
10
10
 
11
- Existing installs require explicit adoption and a backup. Quit the desktop application before updating. Rollback restores a matching data snapshot and preserves newer data in a separate backup. Windows GUI operation remains unverified until a real Windows launch check is recorded.
11
+ 安装验收只需完成前三步。无需 Agent、API Key 或模型登录。
12
12
 
13
- Developer command reference is available through `codex-own help`. The npm manager version and desktop version are separate; installing a new manager alone does not replace the desktop application. Model credentials are never included in this package.
13
+ 第一步:检查环境并安装 npm 管理工具。以下工具安装在当前用户的 ~/.local。逐段执行,每一步成功后再继续。
14
+
15
+ ```bash
16
+ node --version
17
+ npm --version
18
+ npm install --global --prefix "$HOME/.local" @petercjl/codex-own@latest
19
+ ```
20
+
21
+ 第二步:安装桌面程序。管理工具按当前平台从 npm 下载资源分片,依次显示下载进度、校验、解压和安装阶段。
22
+
23
+ ```bash
24
+ "$HOME/.local/bin/codex-own" install --yes
25
+ ```
26
+
27
+ 若已有手动安装的 codex-own,请先退出应用,将第二步命令替换为以下命令,备份后接管原安装。
28
+
29
+ ```bash
30
+ "$HOME/.local/bin/codex-own" install --adopt --yes
31
+ ```
32
+
33
+ 第三步:检查安装结果。安装结果应显示 ok: true、downloadSource: registry.npmjs.org;doctor 中 installed 有版本且 lock 为 false,version 显示管理工具和桌面版本。应用位于 /Applications/codex-own.app。
34
+
35
+ ```bash
36
+ "$HOME/.local/bin/codex-own" doctor
37
+ "$HOME/.local/bin/codex-own" version
38
+ ```
39
+
40
+ 可选:需要使用模型时,再配置 API Key 并启动。将只包含一行 DashScope API Key 的文件放到下载目录,命名为 codex-own-api-key.txt;文件在其他位置时修改路径。配置前退出应用,已有可用配置可跳过配置命令。
41
+
42
+ ```bash
43
+ "$HOME/.local/bin/codex-own" configure --api-key-file "$HOME/Downloads/codex-own-api-key.txt" --yes
44
+ "$HOME/.local/bin/codex-own" launch
45
+ ```
46
+
47
+ 以后更新:先退出 codex-own,再更新 npm 管理工具和桌面程序。配置与对话保存在 ~/Library/Application Support/codex-own-independent。
48
+
49
+ ```bash
50
+ npm install --global --prefix "$HOME/.local" @petercjl/codex-own@latest
51
+ "$HOME/.local/bin/codex-own" update check
52
+ "$HOME/.local/bin/codex-own" update install --yes
53
+ ```
54
+
55
+ ## 在 Windows 上通过 PowerShell 安装和测试
56
+
57
+ 适用于运行 x64 Node.js 的 Windows。先从 Node.js 官网安装 Windows x64 的 LTS 版本(要求 Node.js 22 或以上,包含 npm),再重新打开普通用户 PowerShell。建议预留 10 GB 空间,用于下载、解压和备份。
58
+
59
+ [Node.js 官方下载](https://nodejs.org/en/download)
60
+
61
+ 安装验收只需完成前三步。无需 Agent、API Key 或模型登录。
62
+
63
+ 第一步:检查环境并安装 npm 管理工具。使用 .cmd 入口,无需修改 PowerShell 脚本执行策略。逐段执行,每一步成功后再继续。
64
+
65
+ ```powershell
66
+ node --version
67
+ node -p "process.arch"
68
+ npm.cmd --version
69
+ npm.cmd install --global @petercjl/codex-own@latest
70
+ ```
71
+
72
+ process.arch 应输出 x64。第二步:解析命令路径并安装桌面程序。$codexOwn 只在当前窗口有效;打开新窗口后重新执行赋值行。
73
+
74
+ ```powershell
75
+ $codexOwn = Join-Path (npm.cmd prefix --global) "codex-own.cmd"
76
+ & $codexOwn install --yes
77
+ ```
78
+
79
+ 管理工具和桌面资源全部从 registry.npmjs.org 下载。Windows 桌面下载量约 878 MiB,安装时显示分片编号、下载百分比和已接收大小,然后显示校验、解压、安装。看到“安装完成”和 ok: true 才表示完成。
80
+
81
+ 若已有手动安装的 codex-own,先退出应用,将第二步的安装命令替换为以下接管命令;原安装会被备份。
82
+
83
+ ```powershell
84
+ & $codexOwn install --adopt --yes
85
+ ```
86
+
87
+ 第三步:检查安装结果。doctor 中 installed 应有版本、lock 为 false,pendingTransactions 为空;开始菜单出现 codex-own。仅检查安装时,configured: false 是正常状态。
88
+
89
+ ```powershell
90
+ & $codexOwn doctor
91
+ & $codexOwn version
92
+ ```
93
+
94
+ 程序目录:%LOCALAPPDATA%\Programs\codex-own\current。配置与对话目录:%LOCALAPPDATA%\codex-own-independent。二者独立于原版 Codex。
95
+
96
+ 安装测试通过后即可停止;账号配置和模型对话属于后续使用测试。
97
+
98
+ 可选:需要使用模型时,将只包含一行 DashScope API Key 的文件放到下载目录,命名为 codex-own-api-key.txt。配置前先退出应用,文件位置不同则修改路径;已有可用配置可跳过配置命令。
99
+
100
+ ```powershell
101
+ & $codexOwn configure --api-key-file "$HOME\Downloads\codex-own-api-key.txt" --yes
102
+ & $codexOwn launch
103
+ ```
104
+
105
+ 以后更新:先退出 codex-own,再执行以下命令。更新保留独立配置和对话。
106
+
107
+ ```powershell
108
+ npm.cmd install --global @petercjl/codex-own@latest
109
+ $codexOwn = Join-Path (npm.cmd prefix --global) "codex-own.cmd"
110
+ & $codexOwn update check
111
+ & $codexOwn update install --yes
112
+ ```
113
+
114
+ 下载失败:连续 30 秒无数据会重试当前分片,最多 3 次。出现 DOWNLOAD_FAILED 时检查 npm 网络连接,再执行同一安装命令;已校验分片会复用。下载期间按 Ctrl+C 可以取消。遇到 BUSY 时先确认是否有另一安装进程,保留错误信息,不直接删除锁文件。
115
+
116
+ npm 安装出现缓存 ENOENT 或缓存损坏提示时,先检查缓存,再重新安装 npm 工具:
117
+
118
+ ```powershell
119
+ npm.cmd cache verify
120
+ npm.cmd install --global @petercjl/codex-own@latest
121
+ ```
122
+
123
+ 管理工具与桌面版本分别报告。API Key 和用户数据保存在 npm 包之外。更多命令见 `codex-own help`。
package/capabilities.json CHANGED
@@ -20,5 +20,15 @@
20
20
  },
21
21
  "dataPolicy": "independent external directories; preserve credentials and conversations",
22
22
  "updatePolicy": "explicit update; pinned verified build; refuse running app; backup before switch; restore data snapshot for rollback",
23
- "authentication": "npm install does not grant model access; existing independent credentials remain local"
23
+ "authentication": "npm install does not grant model access; existing independent credentials remain local",
24
+ "distribution": {
25
+ "runtimeHosts": [
26
+ "registry.npmjs.org"
27
+ ],
28
+ "desktopAssets": "content-addressed npm payload prereleases; install latest manager only",
29
+ "cache": "verified platform parts retained for retries",
30
+ "progress": "stderr; final result JSON on stdout",
31
+ "idleTimeoutSeconds": 30,
32
+ "downloadAttempts": 3
33
+ }
24
34
  }
package/lib/manager.mjs CHANGED
@@ -4,8 +4,8 @@ import path from 'node:path';
4
4
  import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import {execFileSync, spawn} from 'node:child_process';
7
- import {Readable} from 'node:stream';
8
- import {pipeline} from 'node:stream/promises';
7
+ import {networkFetch, verifiedDownload, progress} from './network.mjs';
8
+ import {validateParts, assembleNpmAsset} from './npm-assets.mjs';
9
9
 
10
10
  export const packageRoot = path.resolve(import.meta.dirname, '..');
11
11
  export const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'),'utf8'));
@@ -22,27 +22,31 @@ export function rejectLinks(p) {
22
22
  let q=path.resolve(p); while(true){ if(fs.existsSync(q)&&fs.lstatSync(q).isSymbolicLink())fail('UNSAFE_PATH',`Symbolic link/reparse path: ${q}`); const parent=path.dirname(q);if(parent===q)break;q=parent; }
23
23
  }
24
24
  export function validateManifest(m, platform) {
25
- if(m.schema!==1||m.repository!=='petercjl/codex-own'||!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(m.version))fail('INVALID_MANIFEST','Invalid release identity');
25
+ if(![1,2].includes(m.schema)||m.repository!=='petercjl/codex-own'||!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(m.version))fail('INVALID_MANIFEST','Invalid release identity');
26
26
  const a=m.platforms?.[platform];if(!a)fail('PLATFORM_UNAVAILABLE',`No verified build for ${platform} in ${m.version}`);
27
27
  if(!/^[a-f0-9]{64}$/.test(a.sha256)||!Number.isSafeInteger(a.size)||a.size<=0)fail('INVALID_MANIFEST','Invalid archive checksum/size');
28
+ if(m.schema===2){validateParts(a);if(a.entry!==(platform==='darwin-arm64'?'codex-own.app':'app'))fail('INVALID_MANIFEST','Unexpected archive entry');return a;}
28
29
  const expected=`https://github.com/petercjl/codex-own/releases/download/own-v${m.version}/`;
29
30
  if(!a.url?.startsWith(expected)||/[?#]/.test(a.url))fail('INVALID_MANIFEST','Unexpected artifact origin');
30
31
  if(a.entry!==(platform==='darwin-arm64'?'codex-own.app':'app'))fail('INVALID_MANIFEST','Unexpected archive entry');
31
32
  return a;
32
33
  }
33
34
  export async function digestFile(p) { const h=crypto.createHash('sha256');for await(const b of fs.createReadStream(p))h.update(b);return h.digest('hex'); }
34
- export async function download(url,dest,expected) {
35
- if(fs.existsSync(dest))fail('TARGET_EXISTS','Download target exists');
36
- const r=await fetch(url,{signal:AbortSignal.timeout(1200000)}); if(!r.ok)fail('DOWNLOAD_FAILED',`HTTP ${r.status}`);
37
- await pipeline(Readable.fromWeb(r.body),fs.createWriteStream(dest,{flags:'wx',mode:0o600}));
38
- if(fs.statSync(dest).size!==expected.size||await digestFile(dest)!==expected.sha256)fail('CHECKSUM_MISMATCH','Archive verification failed; installed app is unchanged');
39
- }
35
+ export const download = verifiedDownload;
40
36
  export function checkEntries(names) {
41
37
  for(const n of names){const p=n.replaceAll('\\','/');if(p.startsWith('/')||/^[a-z]:/i.test(p)||p.split('/').includes('..'))fail('UNSAFE_ARCHIVE','Unsafe archive entry');}
42
38
  }
39
+ export function tarExecutable(platform=process.platform,env=process.env,exists=fs.existsSync) {
40
+ if(platform!=='win32')return 'tar';
41
+ const root=env.SystemRoot||env.SYSTEMROOT||env.windir||env.WINDIR;
42
+ if(!root)fail('DEPENDENCY_MISSING','Windows system directory is unavailable');
43
+ const executable=path.win32.join(root,'System32','tar.exe');
44
+ if(!exists(executable))fail('DEPENDENCY_MISSING','Windows system tar.exe is required');
45
+ return executable;
46
+ }
43
47
  export function unpack(archive,dest) {
44
- const names=execFileSync('tar',['-tf',archive],{encoding:'utf8',maxBuffer:30*1024*1024}).split('\n').filter(Boolean);checkEntries(names);
45
- fs.mkdirSync(dest);execFileSync('tar',['-xf',archive,'-C',dest],{stdio:'pipe'});
48
+ const names=execFileSync(tarExecutable(),['-tf',archive],{encoding:'utf8',maxBuffer:30*1024*1024}).split('\n').filter(Boolean);checkEntries(names);
49
+ fs.mkdirSync(dest);execFileSync(tarExecutable(),['-xf',archive,'-C',dest],{stdio:'pipe'});
46
50
  // Bundled framework links may be relative but must stay within the extracted tree.
47
51
  function walk(p){for(const x of fs.readdirSync(p,{withFileTypes:true})){const f=path.join(p,x.name);if(x.isSymbolicLink()){const resolved=path.resolve(path.dirname(f),fs.readlinkSync(f));if(!resolved.startsWith(dest+path.sep))fail('UNSAFE_ARCHIVE','Escaping symlink');}else if(x.isDirectory())walk(f);}}
48
52
  walk(dest);
@@ -84,8 +88,8 @@ export function switchApp({p,source,version,adopt=false,identity=appIdentity,isR
84
88
  return {ok:true,version,target:p.target,backup:exists?old:null,dataBackup:exists?path.join(backup,'data'):null};
85
89
  }
86
90
  export async function locked(p,fn){rejectLinks(p.root);fs.mkdirSync(p.manager,{recursive:true,mode:0o700});const lock=path.join(p.manager,'operation.lock');let fd;try{fd=fs.openSync(lock,'wx',0o600);}catch{fail('BUSY','Another operation or interrupted lock exists; inspect doctor before recovery');}try{fs.writeSync(fd,JSON.stringify({pid:process.pid,time:new Date().toISOString()}));return await fn();}finally{fs.closeSync(fd);fs.unlinkSync(lock);}}
87
- export async function latest() {const r=await fetch('https://registry.npmjs.org/@petercjl%2fcodex-own/latest',{signal:AbortSignal.timeout(30000)});if(!r.ok)fail('REGISTRY_UNAVAILABLE',`Registry HTTP ${r.status}`);const p=await r.json();if(p.name!==pkg.name)fail('INVALID_MANIFEST','Registry package mismatch');return p.version;}
88
- export async function manifestFor(v){if(v===pkg.version)return read(path.join(packageRoot,'release.json'));if(!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(v))fail('INVALID_VERSION','Invalid version');const r=await fetch(`https://github.com/petercjl/codex-own/releases/download/own-v${v}/manifest.json`,{signal:AbortSignal.timeout(30000)});if(!r.ok)fail('MANIFEST_UNAVAILABLE',`Manifest HTTP ${r.status}`);const m=await r.json();if(m.version!==v)fail('INVALID_MANIFEST','Version mismatch');return m;}
91
+ export async function latest() {const r=await networkFetch('https://registry.npmjs.org/@petercjl%2fcodex-own/latest',{signal:AbortSignal.timeout(30000)});if(!r.ok)fail('REGISTRY_UNAVAILABLE',`Registry HTTP ${r.status}`);const p=await r.json();if(p.name!==pkg.name)fail('INVALID_MANIFEST','Registry package mismatch');return p.version;}
92
+ export async function manifestFor(v){if(v===pkg.version)return read(path.join(packageRoot,'release.json'));if(!/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(v))fail('INVALID_VERSION','Invalid version');const r=await networkFetch(`https://registry.npmjs.org/@petercjl%2fcodex-own/${v}`,{signal:AbortSignal.timeout(30000)});if(!r.ok)fail('MANIFEST_UNAVAILABLE',`npm HTTP ${r.status}`);const p=await r.json(),m=p.ownDesktopManifest;if(p.name!==pkg.name||p.version!==v||m?.version!==v||m?.schema!==2)fail('MANIFEST_UNAVAILABLE','This version has no npm desktop manifest; update the npm manager to latest.');return m;}
89
93
  export async function configure(keyFile){if(!keyFile)fail('KEY_FILE_REQUIRED','Provide --api-key-file');const key=fs.readFileSync(keyFile,'utf8').trim();if(!/^sk-[A-Za-z0-9_-]{10,}$/.test(key))fail('INVALID_KEY_FILE','Expected a single API key');const p=paths();return locked(p,async()=>{if(running(p).length)fail('APP_RUNNING','Quit codex-own first');rejectLinks(p.core);fs.mkdirSync(p.core,{recursive:true,mode:0o700});fs.chmodSync(p.core,0o700);
90
94
  if(process.platform==='win32'){const sid=execFileSync('powershell.exe',['-NoProfile','-Command','[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value'],{encoding:'utf8'}).trim();execFileSync('icacls.exe',[p.core,'/inheritance:r','/grant:r',`*${sid}:(OI)(CI)F`],{stdio:'pipe'});}
91
95
  const auth=path.join(p.core,'auth.json');rejectLinks(auth);let backup=null;if(fs.existsSync(auth)){backup=auth+'.backup-'+crypto.randomUUID();fs.copyFileSync(auth,backup,fs.constants.COPYFILE_EXCL);fs.chmodSync(backup,0o600);}const tmp=auth+'.new-'+crypto.randomUUID();exclusive(tmp,{auth_mode:'apikey',OPENAI_API_KEY:key});fs.renameSync(tmp,auth);
@@ -93,18 +97,21 @@ export async function configure(keyFile){if(!keyFile)fail('KEY_FILE_REQUIRED','P
93
97
  const config=path.join(p.core,'config.toml');if(!fs.existsSync(config))fs.writeFileSync(config,`model = "deepseek-v4.1-flash"\nmodel_provider = "dashscope_deepseek"\nforced_login_method = "api"\ncli_auth_credentials_store = "file"\nmodel_catalog_json = ${JSON.stringify(models.replaceAll('\\','/'))}\nmodel_reasoning_effort = "high"\nweb_search = "disabled"\n[model_providers.dashscope_deepseek]\nname = "DeepSeek-DashScope"\nbase_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"\nwire_api = "responses"\nrequires_openai_auth = true\n`,{flag:'wx',mode:0o600});return {ok:true,configured:true,backup,configPreserved:true};});}
94
98
  export async function install({version,adopt=false,archive,manifest}={}) {
95
99
  const p=paths();if(!['darwin-arm64','win32-x64'].includes(p.platform))fail('UNSUPPORTED_PLATFORM',p.platform);
100
+ progress('准备安装:检查版本、平台与独立目录');
96
101
  return locked(p,async()=>{const m=manifest||await manifestFor(version||pkg.version);const a=validateManifest(m,p.platform);
97
102
  if(state(p)?.version===m.version&&!archive)return {ok:true,unchanged:true,version:m.version};
98
103
  if(running(p).length)fail('APP_RUNNING','Quit codex-own before install/update');
99
104
  const stage=fs.mkdtempSync(path.join(p.manager,'stage-'));let f=archive;
100
105
  if(f){if(fs.statSync(f).size!==a.size||await digestFile(f)!==a.sha256)fail('CHECKSUM_MISMATCH','Local archive checksum mismatch');}
101
- else{f=path.join(stage,'app.tar.gz');await download(a.url,f,a);}
106
+ else{if(m.schema!==2)fail('NPM_ASSETS_REQUIRED','Update @petercjl/codex-own to the latest npm-only release.');f=path.join(stage,'app.tar.gz');const cache=path.join(p.manager,'npm-assets');rejectLinks(cache);await assembleNpmAsset(a,f,cache,tarExecutable());}
107
+ progress('校验通过,正在解压桌面程序');
102
108
  const content=path.join(stage,'unpacked');unpack(f,content);const source=path.join(content,a.entry);
103
109
  if(process.platform==='darwin')execFileSync('codesign',['--verify','--deep','--strict',source],{stdio:'pipe'});
110
+ progress('正在安装到独立目录');
104
111
  const result=switchApp({p,source,version:m.version,adopt});
105
112
  if(process.platform==='darwin')execFileSync('/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister',['-f',p.target],{stdio:'pipe'});
106
113
  else shortcut(p);
107
- fs.rmSync(stage,{recursive:true,force:true});return result;
114
+ fs.rmSync(stage,{recursive:true,force:true});progress('安装完成');return {...result,downloadSource:archive?'local-archive':'registry.npmjs.org'};
108
115
  });
109
116
  }
110
117
  export function shortcut(p) {
@@ -118,4 +125,4 @@ export async function rollback({restoreData=false}={}){const p=paths();return lo
118
125
  const preserved=path.join(path.dirname(p.target),'.codex-own-before-rollback-'+crypto.randomUUID());fs.renameSync(p.target,preserved);fs.renameSync(prev.app,p.target);
119
126
  for(const n of ['core','desktop']){const snap=path.join(prev.data,n);if(fs.existsSync(p[n]))fs.renameSync(p[n],path.join(p.root,n+'.before-rollback-'+crypto.randomUUID()));if(fs.existsSync(snap))fs.cpSync(snap,p[n],{recursive:true,force:false,errorOnExist:true});}
120
127
  writeState(p,{...(prev.state||{schema:1,identity:'local.codex-own',version:'adopted-legacy',target:p.target}),previous:null});return {ok:true,preserved,dataBackup:backup};});}
121
- export function doctor(){const p=paths(),s=state(p);let tar=true;try{execFileSync('tar',['--version'],{stdio:'pipe'});}catch{tar=false;}const pending=fs.existsSync(p.manager)?fs.readdirSync(p.manager).filter(n=>/^transaction-.*\.json$/.test(n)&&!n.endsWith('.complete.json')&&!fs.existsSync(path.join(p.manager,n.replace('.json','.complete.json')))):[];return {ok:tar&&!pending.length,managerVersion:pkg.version,platform:p.platform,supported:['darwin-arm64','win32-x64'].includes(p.platform),installed:s?.version||null,paths:p,tar,pendingTransactions:pending,lock:fs.existsSync(path.join(p.manager,'operation.lock')),configured:fs.existsSync(path.join(p.core,'auth.json')),windowsGuiTested:false};}
128
+ export function doctor(){const p=paths(),s=state(p);let tar=true;try{execFileSync(tarExecutable(),['--version'],{stdio:'pipe'});}catch{tar=false;}const pending=fs.existsSync(p.manager)?fs.readdirSync(p.manager).filter(n=>/^transaction-.*\.json$/.test(n)&&!n.endsWith('.complete.json')&&!fs.existsSync(path.join(p.manager,n.replace('.json','.complete.json')))):[];return {ok:tar&&!pending.length,managerVersion:pkg.version,platform:p.platform,supported:['darwin-arm64','win32-x64'].includes(p.platform),installed:s?.version||null,paths:p,tar,pendingTransactions:pending,lock:fs.existsSync(path.join(p.manager,'operation.lock')),configured:fs.existsSync(path.join(p.core,'auth.json')),windowsGuiTested:false};}
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs';
2
+ import crypto from 'node:crypto';
3
+ import {EnvHttpProxyAgent, fetch} from 'undici';
4
+
5
+ const dispatcher = new EnvHttpProxyAgent();
6
+ export const progress = message => process.stderr.write(`[codex-own] ${message}\n`);
7
+ export function networkFetch(url, options = {}) {
8
+ return fetch(url, {dispatcher, redirect: 'error', ...options});
9
+ }
10
+ export async function fileHash(file) {
11
+ const h = crypto.createHash('sha256');
12
+ for await (const b of fs.createReadStream(file)) h.update(b);
13
+ return h.digest('hex');
14
+ }
15
+ export async function verifiedDownload(url, dest, expected, {
16
+ fetcher = networkFetch, report = progress, idleMs = 30000, attempts = 3, retryMs = 1000,
17
+ } = {}) {
18
+ const error = (code, message) => Object.assign(new Error(message), {code});
19
+ if (fs.existsSync(dest)) throw error('TARGET_EXISTS', 'Download destination already exists');
20
+ for (let attempt = 1; attempt <= attempts; attempt++) {
21
+ const controller = new AbortController();
22
+ let file, bytes = 0, timer, cancelled = false;
23
+ const start = Date.now();
24
+ const cancel = () => { cancelled = true; controller.abort(); };
25
+ const reset = () => { clearTimeout(timer); timer = setTimeout(() => controller.abort(), idleMs); };
26
+ 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`);
27
+ process.once('SIGINT', cancel);
28
+ process.once('SIGTERM', cancel);
29
+ const interval = setInterval(tick, 3000);
30
+ try {
31
+ report(`连接 npm 下载服务(尝试 ${attempt}/${attempts})`);
32
+ reset();
33
+ const response = await fetcher(url, {signal: controller.signal});
34
+ if (!response.ok) { await response.body?.cancel(); throw error('DOWNLOAD_HTTP', `npm HTTP ${response.status}`); }
35
+ file = await fs.promises.open(dest, 'wx', 0o600);
36
+ const h = crypto.createHash('sha256');
37
+ for await (const data of response.body) {
38
+ reset(); bytes += data.length;
39
+ if (bytes > expected.size) throw error('CHECKSUM_MISMATCH', 'Download exceeded expected size');
40
+ h.update(data);
41
+ let offset = 0;
42
+ while (offset < data.length) offset += (await file.write(data, offset, data.length-offset)).bytesWritten;
43
+ }
44
+ if (bytes !== expected.size || h.digest('hex') !== expected.sha256) throw error('CHECKSUM_MISMATCH', 'Download verification failed');
45
+ tick();
46
+ return;
47
+ } catch (e) {
48
+ controller.abort();
49
+ if (file) { await file.close(); file = null; fs.unlinkSync(dest); }
50
+ if (cancelled) throw error('CANCELLED', 'Download cancelled. Verified earlier parts are retained; rerun the install command to continue.');
51
+ if (e.code === 'CHECKSUM_MISMATCH' || e.code === 'EEXIST') throw e;
52
+ if (attempt === attempts) throw error('DOWNLOAD_FAILED', `npm download failed after ${attempts} attempts (${e.code === 'DOWNLOAD_HTTP' ? e.message : e.code || e.cause?.code || 'timeout/network'}). Check network access to registry.npmjs.org and retry; verified parts are retained.`);
53
+ report(`下载暂不可用(${e.code === 'DOWNLOAD_HTTP' ? e.message : e.code || e.cause?.code || 'timeout/network'}),稍后重试当前分片;已校验分片保留。`);
54
+ await new Promise(r=>setTimeout(r,retryMs*attempt));
55
+ if (cancelled) throw error('CANCELLED', 'Download cancelled. Verified earlier parts are retained; rerun the install command to continue.');
56
+ } finally {
57
+ clearTimeout(timer); clearInterval(interval);
58
+ process.removeListener('SIGINT', cancel); process.removeListener('SIGTERM', cancel);
59
+ if (file) await file.close();
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {execFileSync} from 'node:child_process';
4
+ import {pipeline} from 'node:stream/promises';
5
+ import {fileHash, verifiedDownload, progress} from './network.mjs';
6
+
7
+ export const payloadVersion = sha => `0.0.0-payload.h${sha}`;
8
+ export const payloadURL = sha => `https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-${payloadVersion(sha)}.tgz`;
9
+ export function validateParts(asset) {
10
+ const fail = () => { throw Object.assign(new Error('Invalid npm asset manifest'), {code:'INVALID_MANIFEST'}); };
11
+ if (!Array.isArray(asset.parts) || asset.parts.length < 1 || asset.parts.length > 64) fail();
12
+ let size = 0;
13
+ for (const p of asset.parts) {
14
+ if (!/^[a-f0-9]{64}$/.test(p.sha256) || !/^[a-f0-9]{64}$/.test(p.archiveSha256)
15
+ || !Number.isSafeInteger(p.size) || p.size < 1 || p.size > 128*1048576
16
+ || !Number.isSafeInteger(p.archiveSize) || p.archiveSize < 1 || p.archiveSize > 130*1048576
17
+ || p.url !== payloadURL(p.sha256)) fail();
18
+ size += p.size;
19
+ }
20
+ if (size !== asset.size) fail();
21
+ return asset;
22
+ }
23
+ function safeCache(file) {
24
+ if (fs.existsSync(file) && (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()))
25
+ throw Object.assign(new Error('Unexpected npm asset cache entry'), {code:'UNSAFE_PATH'});
26
+ }
27
+ export async function assembleNpmAsset(asset, dest, cache, tar, {download = verifiedDownload, report = progress} = {}) {
28
+ validateParts(asset);
29
+ fs.mkdirSync(cache, {recursive:true, mode:0o700});
30
+ const files = [];
31
+ for (const [i, part] of asset.parts.entries()) {
32
+ report(`npm 资源分片 ${i+1}/${asset.parts.length}`);
33
+ const file = path.join(cache, `${part.sha256}.bin`);
34
+ safeCache(file);
35
+ if (fs.existsSync(file) && fs.statSync(file).size === part.size && await fileHash(file) === part.sha256) {
36
+ report('复用已校验分片'); files.push(file); continue;
37
+ }
38
+ if (fs.existsSync(file)) fs.unlinkSync(file);
39
+ const scratch = fs.mkdtempSync(path.join(cache, 'fetch-'));
40
+ try {
41
+ const archive = path.join(scratch, 'package.tgz');
42
+ await download(part.url, archive, {size:part.archiveSize, sha256:part.archiveSha256});
43
+ // Extract only the fixed payload entry, never arbitrary paths from a package tarball.
44
+ execFileSync(tar, ['-xf', archive, '-C', scratch, 'package/payload.bin'], {stdio:'pipe'});
45
+ const payload = path.join(scratch, 'package', 'payload.bin');
46
+ safeCache(payload);
47
+ if (fs.statSync(payload).size !== part.size || await fileHash(payload) !== part.sha256)
48
+ throw Object.assign(new Error('npm payload checksum mismatch'), {code:'CHECKSUM_MISMATCH'});
49
+ fs.renameSync(payload, file); files.push(file);
50
+ } finally { fs.rmSync(scratch, {recursive:true,force:true}); }
51
+ }
52
+ report('合并并校验完整桌面安装包');
53
+ const output = fs.createWriteStream(dest, {flags:'wx',mode:0o600});
54
+ async function* chunks() { for (const f of files) yield* fs.createReadStream(f); }
55
+ await pipeline(chunks(), output);
56
+ if (fs.statSync(dest).size !== asset.size || await fileHash(dest) !== asset.sha256)
57
+ throw Object.assign(new Error('Assembled desktop checksum mismatch'), {code:'CHECKSUM_MISMATCH'});
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petercjl/codex-own",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Independent codex-own desktop installation and update manager",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,5 +37,142 @@
37
37
  "pe-library": "1.0.1",
38
38
  "plist": "3.1.0",
39
39
  "resedit": "3.0.2"
40
+ },
41
+ "dependencies": {
42
+ "undici": "7.30.0"
43
+ },
44
+ "ownDesktopManifest": {
45
+ "schema": 2,
46
+ "repository": "petercjl/codex-own",
47
+ "version": "0.2.0",
48
+ "platforms": {
49
+ "darwin-arm64": {
50
+ "entry": "codex-own.app",
51
+ "size": 781692456,
52
+ "sha256": "6512d6d2e8ef46f64339ca1233fe833a067329cf006b17b3a0aa1bd27fd8e457",
53
+ "parts": [
54
+ {
55
+ "sha256": "c7fe9284f617e60fdebbedc2b551d59a021f3c03249e11224d15720ce90d968e",
56
+ "size": 134217728,
57
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hc7fe9284f617e60fdebbedc2b551d59a021f3c03249e11224d15720ce90d968e.tgz",
58
+ "archiveSize": 134068421,
59
+ "archiveSha256": "7692f1071c0ed3a3c1937e00824815dfd50710a1bf16ff45b7ef54e3fc63a7da"
60
+ },
61
+ {
62
+ "sha256": "7ff2d11fbccf0d6f53b46eb0f62f12a399440640b4443a7770dab0e2aed07c86",
63
+ "size": 134217728,
64
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h7ff2d11fbccf0d6f53b46eb0f62f12a399440640b4443a7770dab0e2aed07c86.tgz",
65
+ "archiveSize": 134125467,
66
+ "archiveSha256": "dab67fa0d1e9ea1ce11414cc1f90175513e7cd433c530fb94bf3e02075da97aa"
67
+ },
68
+ {
69
+ "sha256": "e3bc7bf7a8857fd325ba5a6c328a8ec15701a87c623d08f2c66565736a8b2125",
70
+ "size": 134217728,
71
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.he3bc7bf7a8857fd325ba5a6c328a8ec15701a87c623d08f2c66565736a8b2125.tgz",
72
+ "archiveSize": 134200688,
73
+ "archiveSha256": "8dd17509eee96f46e48b9dca3ef798df2b78ca1ad8d63f4cf914a6e6cfdff587"
74
+ },
75
+ {
76
+ "sha256": "206c25d0e17fee0419b7d498b223e43a12a11a367fddef9ba7ceea5f8d031c4a",
77
+ "size": 134217728,
78
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h206c25d0e17fee0419b7d498b223e43a12a11a367fddef9ba7ceea5f8d031c4a.tgz",
79
+ "archiveSize": 133964876,
80
+ "archiveSha256": "013d7c22e993e2375f2ebac83954bb66c6b9e1e58ea62fea80339b4262922b62"
81
+ },
82
+ {
83
+ "sha256": "1e91bfbc20b3140e1f5770aaa732dcda1242a254bcd30b5d7c5cd1f350bc55fc",
84
+ "size": 134217728,
85
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1e91bfbc20b3140e1f5770aaa732dcda1242a254bcd30b5d7c5cd1f350bc55fc.tgz",
86
+ "archiveSize": 133524640,
87
+ "archiveSha256": "ab6368325f52c53f437dfdf774a13d24bf82c7c1102b3b4e588004cd850f3411"
88
+ },
89
+ {
90
+ "sha256": "1ab437b67cd09e7d3551ca659f41e7bfd984e7c763023d3b0575fa33f8be892a",
91
+ "size": 110603816,
92
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1ab437b67cd09e7d3551ca659f41e7bfd984e7c763023d3b0575fa33f8be892a.tgz",
93
+ "archiveSize": 110416084,
94
+ "archiveSha256": "0e0f0c34bf12238e6b737c90369aa014a70d70250dba37ccf9ebffd4c5af96a0"
95
+ }
96
+ ]
97
+ },
98
+ "win32-x64": {
99
+ "entry": "app",
100
+ "size": 920368892,
101
+ "sha256": "f110a1e7e0f7c1e184ccc8733674d00280ecae02762530b07ca3b7d2547a4259",
102
+ "parts": [
103
+ {
104
+ "sha256": "f10bf3a916546c9b8d334b9bed9da2688cf48629aaa677b6b1265721ef695f09",
105
+ "size": 134217728,
106
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hf10bf3a916546c9b8d334b9bed9da2688cf48629aaa677b6b1265721ef695f09.tgz",
107
+ "archiveSize": 134146498,
108
+ "archiveSha256": "cca9d9844548e9ada4d332be0620a248d20460c62b549faa3cb27532b1360ac9"
109
+ },
110
+ {
111
+ "sha256": "1c4eb544a87dae8072fbf6cfced05aba0c128be51ee59c1ba8a29ee1e06c2bb3",
112
+ "size": 134217728,
113
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1c4eb544a87dae8072fbf6cfced05aba0c128be51ee59c1ba8a29ee1e06c2bb3.tgz",
114
+ "archiveSize": 134017345,
115
+ "archiveSha256": "be3663292e80e953f48f2d3b59d93ea6030f78b411eca2333ff66c9b623f3e81"
116
+ },
117
+ {
118
+ "sha256": "96515fb363ccdfd35cabccb0c6c80d7377c07a17d546cb3dcd0e9995abdc52e3",
119
+ "size": 134217728,
120
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h96515fb363ccdfd35cabccb0c6c80d7377c07a17d546cb3dcd0e9995abdc52e3.tgz",
121
+ "archiveSize": 134217117,
122
+ "archiveSha256": "b992e7d853bc0ee32acc4416b670238362473e91bb0cd0bb49b57d7a1431da30"
123
+ },
124
+ {
125
+ "sha256": "c72e99cd0a9b99fc023bfc53da528b16fcff89d9d11aa8ffb7cffe163ce15ac2",
126
+ "size": 134217728,
127
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hc72e99cd0a9b99fc023bfc53da528b16fcff89d9d11aa8ffb7cffe163ce15ac2.tgz",
128
+ "archiveSize": 134037874,
129
+ "archiveSha256": "b7f72914ae410ca59d0f23038765683d096564f9a982880eeaaa2612121e9d98"
130
+ },
131
+ {
132
+ "sha256": "4cdd4bb887ae5046cac17eb6b6f197f0fc74d8d6f6cc7548d74d45a099c71f00",
133
+ "size": 134217728,
134
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h4cdd4bb887ae5046cac17eb6b6f197f0fc74d8d6f6cc7548d74d45a099c71f00.tgz",
135
+ "archiveSize": 133901929,
136
+ "archiveSha256": "51510b54827188f5957eb2814795e1d1fe9c5abb8cbe753715a87e3c666a5dc4"
137
+ },
138
+ {
139
+ "sha256": "27eb58e92a273dac2abfad90fb5493634993f3c5f60249ac0074fadbfe93a7a3",
140
+ "size": 134217728,
141
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h27eb58e92a273dac2abfad90fb5493634993f3c5f60249ac0074fadbfe93a7a3.tgz",
142
+ "archiveSize": 133616572,
143
+ "archiveSha256": "651d527dac76787fd19459804e1dac9957df897be44098f49085bb26035fa0de"
144
+ },
145
+ {
146
+ "sha256": "b38a0db220f90da06049e05516387bffdfa0f3b3fd2e9106bc2a04135d0b4779",
147
+ "size": 115062524,
148
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hb38a0db220f90da06049e05516387bffdfa0f3b3fd2e9106bc2a04135d0b4779.tgz",
149
+ "archiveSize": 114529446,
150
+ "archiveSha256": "8cba55fc4c9b3592ef80601ba8f65881720c26de0b5d8ae4d589ebb6bb4771fe"
151
+ }
152
+ ]
153
+ }
154
+ },
155
+ "provenance": {
156
+ "darwin-arm64": {
157
+ "darwin-arm64": {
158
+ "sourceRelease": "own-v0.1.0"
159
+ },
160
+ "win32-x64": {
161
+ "kind": "prepared-independent-build",
162
+ "desktopVersion": "26.1007.21434",
163
+ "guiAcceptance": "user-pending"
164
+ }
165
+ },
166
+ "win32-x64": {
167
+ "darwin-arm64": {
168
+ "sourceRelease": "own-v0.1.0"
169
+ },
170
+ "win32-x64": {
171
+ "kind": "prepared-independent-build",
172
+ "desktopVersion": "26.1007.21434",
173
+ "guiAcceptance": "user-pending"
174
+ }
175
+ }
176
+ }
40
177
  }
41
178
  }
package/release.json CHANGED
@@ -1,19 +1,134 @@
1
1
  {
2
- "schema": 1,
2
+ "schema": 2,
3
3
  "repository": "petercjl/codex-own",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "platforms": {
6
6
  "darwin-arm64": {
7
7
  "entry": "codex-own.app",
8
- "url": "https://github.com/petercjl/codex-own/releases/download/own-v0.1.0/codex-own-darwin-arm64.tar.gz",
8
+ "size": 781692456,
9
9
  "sha256": "6512d6d2e8ef46f64339ca1233fe833a067329cf006b17b3a0aa1bd27fd8e457",
10
- "size": 781692456
10
+ "parts": [
11
+ {
12
+ "sha256": "c7fe9284f617e60fdebbedc2b551d59a021f3c03249e11224d15720ce90d968e",
13
+ "size": 134217728,
14
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hc7fe9284f617e60fdebbedc2b551d59a021f3c03249e11224d15720ce90d968e.tgz",
15
+ "archiveSize": 134068421,
16
+ "archiveSha256": "7692f1071c0ed3a3c1937e00824815dfd50710a1bf16ff45b7ef54e3fc63a7da"
17
+ },
18
+ {
19
+ "sha256": "7ff2d11fbccf0d6f53b46eb0f62f12a399440640b4443a7770dab0e2aed07c86",
20
+ "size": 134217728,
21
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h7ff2d11fbccf0d6f53b46eb0f62f12a399440640b4443a7770dab0e2aed07c86.tgz",
22
+ "archiveSize": 134125467,
23
+ "archiveSha256": "dab67fa0d1e9ea1ce11414cc1f90175513e7cd433c530fb94bf3e02075da97aa"
24
+ },
25
+ {
26
+ "sha256": "e3bc7bf7a8857fd325ba5a6c328a8ec15701a87c623d08f2c66565736a8b2125",
27
+ "size": 134217728,
28
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.he3bc7bf7a8857fd325ba5a6c328a8ec15701a87c623d08f2c66565736a8b2125.tgz",
29
+ "archiveSize": 134200688,
30
+ "archiveSha256": "8dd17509eee96f46e48b9dca3ef798df2b78ca1ad8d63f4cf914a6e6cfdff587"
31
+ },
32
+ {
33
+ "sha256": "206c25d0e17fee0419b7d498b223e43a12a11a367fddef9ba7ceea5f8d031c4a",
34
+ "size": 134217728,
35
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h206c25d0e17fee0419b7d498b223e43a12a11a367fddef9ba7ceea5f8d031c4a.tgz",
36
+ "archiveSize": 133964876,
37
+ "archiveSha256": "013d7c22e993e2375f2ebac83954bb66c6b9e1e58ea62fea80339b4262922b62"
38
+ },
39
+ {
40
+ "sha256": "1e91bfbc20b3140e1f5770aaa732dcda1242a254bcd30b5d7c5cd1f350bc55fc",
41
+ "size": 134217728,
42
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1e91bfbc20b3140e1f5770aaa732dcda1242a254bcd30b5d7c5cd1f350bc55fc.tgz",
43
+ "archiveSize": 133524640,
44
+ "archiveSha256": "ab6368325f52c53f437dfdf774a13d24bf82c7c1102b3b4e588004cd850f3411"
45
+ },
46
+ {
47
+ "sha256": "1ab437b67cd09e7d3551ca659f41e7bfd984e7c763023d3b0575fa33f8be892a",
48
+ "size": 110603816,
49
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1ab437b67cd09e7d3551ca659f41e7bfd984e7c763023d3b0575fa33f8be892a.tgz",
50
+ "archiveSize": 110416084,
51
+ "archiveSha256": "0e0f0c34bf12238e6b737c90369aa014a70d70250dba37ccf9ebffd4c5af96a0"
52
+ }
53
+ ]
54
+ },
55
+ "win32-x64": {
56
+ "entry": "app",
57
+ "size": 920368892,
58
+ "sha256": "f110a1e7e0f7c1e184ccc8733674d00280ecae02762530b07ca3b7d2547a4259",
59
+ "parts": [
60
+ {
61
+ "sha256": "f10bf3a916546c9b8d334b9bed9da2688cf48629aaa677b6b1265721ef695f09",
62
+ "size": 134217728,
63
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hf10bf3a916546c9b8d334b9bed9da2688cf48629aaa677b6b1265721ef695f09.tgz",
64
+ "archiveSize": 134146498,
65
+ "archiveSha256": "cca9d9844548e9ada4d332be0620a248d20460c62b549faa3cb27532b1360ac9"
66
+ },
67
+ {
68
+ "sha256": "1c4eb544a87dae8072fbf6cfced05aba0c128be51ee59c1ba8a29ee1e06c2bb3",
69
+ "size": 134217728,
70
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h1c4eb544a87dae8072fbf6cfced05aba0c128be51ee59c1ba8a29ee1e06c2bb3.tgz",
71
+ "archiveSize": 134017345,
72
+ "archiveSha256": "be3663292e80e953f48f2d3b59d93ea6030f78b411eca2333ff66c9b623f3e81"
73
+ },
74
+ {
75
+ "sha256": "96515fb363ccdfd35cabccb0c6c80d7377c07a17d546cb3dcd0e9995abdc52e3",
76
+ "size": 134217728,
77
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h96515fb363ccdfd35cabccb0c6c80d7377c07a17d546cb3dcd0e9995abdc52e3.tgz",
78
+ "archiveSize": 134217117,
79
+ "archiveSha256": "b992e7d853bc0ee32acc4416b670238362473e91bb0cd0bb49b57d7a1431da30"
80
+ },
81
+ {
82
+ "sha256": "c72e99cd0a9b99fc023bfc53da528b16fcff89d9d11aa8ffb7cffe163ce15ac2",
83
+ "size": 134217728,
84
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hc72e99cd0a9b99fc023bfc53da528b16fcff89d9d11aa8ffb7cffe163ce15ac2.tgz",
85
+ "archiveSize": 134037874,
86
+ "archiveSha256": "b7f72914ae410ca59d0f23038765683d096564f9a982880eeaaa2612121e9d98"
87
+ },
88
+ {
89
+ "sha256": "4cdd4bb887ae5046cac17eb6b6f197f0fc74d8d6f6cc7548d74d45a099c71f00",
90
+ "size": 134217728,
91
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h4cdd4bb887ae5046cac17eb6b6f197f0fc74d8d6f6cc7548d74d45a099c71f00.tgz",
92
+ "archiveSize": 133901929,
93
+ "archiveSha256": "51510b54827188f5957eb2814795e1d1fe9c5abb8cbe753715a87e3c666a5dc4"
94
+ },
95
+ {
96
+ "sha256": "27eb58e92a273dac2abfad90fb5493634993f3c5f60249ac0074fadbfe93a7a3",
97
+ "size": 134217728,
98
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.h27eb58e92a273dac2abfad90fb5493634993f3c5f60249ac0074fadbfe93a7a3.tgz",
99
+ "archiveSize": 133616572,
100
+ "archiveSha256": "651d527dac76787fd19459804e1dac9957df897be44098f49085bb26035fa0de"
101
+ },
102
+ {
103
+ "sha256": "b38a0db220f90da06049e05516387bffdfa0f3b3fd2e9106bc2a04135d0b4779",
104
+ "size": 115062524,
105
+ "url": "https://registry.npmjs.org/@petercjl/codex-own/-/codex-own-0.0.0-payload.hb38a0db220f90da06049e05516387bffdfa0f3b3fd2e9106bc2a04135d0b4779.tgz",
106
+ "archiveSize": 114529446,
107
+ "archiveSha256": "8cba55fc4c9b3592ef80601ba8f65881720c26de0b5d8ae4d589ebb6bb4771fe"
108
+ }
109
+ ]
11
110
  }
12
111
  },
13
112
  "provenance": {
14
113
  "darwin-arm64": {
15
- "upstreamCommit": "a899ae46dd608826bdc9647fb488615c47168428",
16
- "kind": "adopted-verified-local-build"
114
+ "darwin-arm64": {
115
+ "sourceRelease": "own-v0.1.0"
116
+ },
117
+ "win32-x64": {
118
+ "kind": "prepared-independent-build",
119
+ "desktopVersion": "26.1007.21434",
120
+ "guiAcceptance": "user-pending"
121
+ }
122
+ },
123
+ "win32-x64": {
124
+ "darwin-arm64": {
125
+ "sourceRelease": "own-v0.1.0"
126
+ },
127
+ "win32-x64": {
128
+ "kind": "prepared-independent-build",
129
+ "desktopVersion": "26.1007.21434",
130
+ "guiAcceptance": "user-pending"
131
+ }
17
132
  }
18
133
  }
19
- }
134
+ }
@@ -7,7 +7,7 @@ description: Install, update, launch and diagnose the independent codex-own desk
7
7
 
8
8
  Input: the requested install, update, diagnosis or recovery action, existing installation state, optional supplied API key file, and user authorization. Output: manager and desktop versions, verified installation paths, backup evidence and unresolved errors. Package installation alone does not configure model access.
9
9
 
10
- Use the installed `codex-own` CLI. Resolve its current bundled instructions with `codex-own skill source --json`; probe `version`, `capabilities` and `doctor`. Requires Node.js 22+, tar, a supported desktop OS and network for downloads. macOS ARM64 and Windows x64 are implementation targets; a diagnostic result does not prove Windows GUI compatibility.
10
+ Use the installed `codex-own` CLI. Resolve its current bundled instructions with `codex-own skill source --json`; probe `version`, `capabilities` and `doctor`. Requires Node.js 22+, tar, a supported desktop OS and access to registry.npmjs.org. Install the latest manager from npm; it downloads only the current platform’s desktop payloads from npm. Internal payload prereleases are data, not CLI entry points. macOS ARM64 and Windows x64 are implementation targets; a diagnostic result does not prove Windows GUI compatibility.
11
11
 
12
12
  ## Main line
13
13
 
@@ -16,10 +16,13 @@ Use the installed `codex-own` CLI. Resolve its current bundled instructions with
16
16
  3. For an authorized update, quit codex-own gracefully and run `codex-own update install --yes`. Do not terminate the original Codex. The manager and desktop versions are separate. Updating the npm manager alone does not update the desktop application.
17
17
  4. Read `doctor` and `version`, retain the returned backup locations, then use `codex-own launch` when requested. Verify startup separately from installer success.
18
18
 
19
+ For installation-only acceptance, verify doctor, version, target and shortcut without configuring credentials or launching the GUI unless requested. Progress goes to stderr; final results remain JSON on stdout.
20
+
19
21
  ## Recovery and boundaries
20
22
 
21
23
  - APP_RUNNING: request or perform authorized graceful exit, then return to step 3.
22
24
  - CHECKSUM_MISMATCH, UNMANAGED_TARGET, UNSAFE_PATH, or incompatible patches: stop; preserve the current application.
25
+ - DOWNLOAD_FAILED: inspect npm registry connectivity and retry the same command; verified payload parts are reused. Ctrl+C during download cancels and releases the operation lock. Keep TLS verification enabled.
23
26
  - BUSY or pending transactions: inspect the recorded transaction and process before recovery; do not blindly delete locks or replay an installation.
24
27
  - Rollback requires `codex-own rollback --restore-data --yes`. Explain that this restores the matching old data snapshot; newer data is preserved separately. Obtain authorization covering this restoration, then verify state.
25
28
  - Credentials and conversation data stay outside Git and npm. Never print API keys. Never modify original Codex directories or global model environment variables.