@petercjl/codex-own 0.6.4 → 0.6.5
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 +69 -39
- package/bin/codex-own.mjs +4 -41
- package/capabilities.json +44 -4
- package/lib/archive-inventory.mjs +40 -0
- package/lib/cli.mjs +47 -0
- package/lib/extraction.mjs +42 -0
- package/lib/file-operations.mjs +22 -0
- package/lib/maintenance.mjs +54 -0
- package/lib/manager.mjs +21 -18
- package/lib/network.mjs +3 -1
- package/lib/tool-runtime.mjs +6 -3
- package/package.json +449 -608
- package/release.json +446 -607
- package/skills/codex-own/SKILL.md +8 -0
package/lib/manager.mjs
CHANGED
|
@@ -11,6 +11,9 @@ import {ensureRuntime,validateRuntime,restoreRuntimeSelection} from './tool-runt
|
|
|
11
11
|
import {validateBinding,packageManifest,sameDesktop,identity as bindingHash} from './binding.mjs';
|
|
12
12
|
import {deepseekConfiguration} from './model-configuration.mjs';
|
|
13
13
|
import windowsAgentShell from './windows-agent-shell.cjs';
|
|
14
|
+
import {extractChecked} from './extraction.mjs';
|
|
15
|
+
import {copyNewDirectory,removeTemporary,removeNewTreeSync} from './file-operations.mjs';
|
|
16
|
+
import {lockStatus,temporaryStages} from './maintenance.mjs';
|
|
14
17
|
|
|
15
18
|
export const packageRoot = path.resolve(import.meta.dirname, '..');
|
|
16
19
|
export const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'),'utf8'));
|
|
@@ -52,14 +55,7 @@ export function tarExecutable(platform=process.platform,env=process.env,exists=f
|
|
|
52
55
|
return executable;
|
|
53
56
|
}
|
|
54
57
|
export function unpack(archive,dest) {
|
|
55
|
-
|
|
56
|
-
// page. CreateProcessW's cwd supports Unicode; sibling relative args avoid it.
|
|
57
|
-
const cwd=path.dirname(path.resolve(archive)),source=path.basename(archive),target=path.relative(cwd,path.resolve(dest));
|
|
58
|
-
const names=execFileSync(tarExecutable(),['-tf',source],{cwd,encoding:'utf8',maxBuffer:30*1024*1024}).split('\n').filter(Boolean);checkEntries(names);
|
|
59
|
-
fs.mkdirSync(dest);execFileSync(tarExecutable(),['-xf',source,'-C',target],{cwd,stdio:'pipe'});
|
|
60
|
-
// Bundled framework links may be relative but must stay within the extracted tree.
|
|
61
|
-
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);}}
|
|
62
|
-
walk(dest);
|
|
58
|
+
return extractChecked(archive,dest,{tar:tarExecutable(),report:progress});
|
|
63
59
|
}
|
|
64
60
|
export function macOwnProcessIds(lines) {
|
|
65
61
|
return lines.filter(l=>l.includes('codex-own.app/Contents/')&&!l.includes(' ps -')&&!l.includes('/Contents/Resources/cua_node/')&&!/\/Contents\/MacOS\/WinAppHelper(?:\s|$)/.test(l)).map(l=>l.trim().split(/\s+/)[0]);
|
|
@@ -91,17 +87,22 @@ export function switchApp({p,source,version,manifest,runtimeSelection,adopt=fals
|
|
|
91
87
|
const id=crypto.randomUUID(),backup=path.join(p.manager,'backups',id);fs.mkdirSync(backup,{recursive:true,mode:0o700});
|
|
92
88
|
if(exists)backupData(p,path.join(backup,'data'));
|
|
93
89
|
const old=path.join(path.dirname(p.target),'.codex-own-backup-'+id), next=path.join(path.dirname(p.target),'.codex-own-next-'+id);
|
|
94
|
-
|
|
95
|
-
const journal=path.join(p.manager,'transaction-'+id+'.json');
|
|
90
|
+
const ownership=path.join(p.manager,'temporary-'+id+'.json');
|
|
91
|
+
const journal=path.join(p.manager,'transaction-'+id+'.json');let preparedNext=false;
|
|
92
|
+
try{
|
|
93
|
+
fs.mkdirSync(path.dirname(p.target),{recursive:true});copyNewDirectory(source,next,{onCreate:()=>{preparedNext=true;exclusive(ownership,{package:pkg.name,kind:'next',path:next,target:p.target});}});
|
|
94
|
+
exclusive(journal,{target:p.target,old,next,version,previous,backup});
|
|
95
|
+
}catch(e){if(preparedNext&&fs.existsSync(next))try{removeNewTreeSync(next);}catch{e.partialTarget=next;}throw e;}
|
|
96
96
|
let moved=false,activated=false;
|
|
97
97
|
try {if(exists){fs.renameSync(p.target,old);moved=true;}fs.renameSync(next,p.target);activated=true;
|
|
98
98
|
const active=path.join(p.root,'runtimes','active.json');
|
|
99
99
|
writeState(p,{schema:1,identity:'local.codex-own',version,...(manifest?{desktop:{version,identity:manifest.platforms[p.platform].sha256},releaseManifest:manifest}:{}),target:p.target,installedAt:new Date().toISOString(),previous:exists?{app:old,data:path.join(backup,'data'),state:previous,runtimeSelection:runtimeSelection===undefined?(fs.existsSync(active)?read(active):null):runtimeSelection}:null});
|
|
100
|
-
}catch(e){if(activated&&fs.existsSync(p.target))fs.renameSync(p.target,path.join(path.dirname(p.target),'.codex-own-failed-'+id));if(moved)fs.renameSync(old,p.target);throw e;}
|
|
100
|
+
}catch(e){if(activated&&fs.existsSync(p.target))fs.renameSync(p.target,path.join(path.dirname(p.target),'.codex-own-failed-'+id));if(moved)fs.renameSync(old,p.target);if(fs.existsSync(next))try{removeNewTreeSync(next);}catch{e.partialTarget=next;}throw e;}
|
|
101
101
|
exclusive(path.join(p.manager,'transaction-'+id+'.complete.json'),{ok:true});
|
|
102
|
+
if(fs.existsSync(ownership))try{fs.unlinkSync(ownership);}catch{}
|
|
102
103
|
return {ok:true,version,target:p.target,backup:exists?old:null,dataBackup:exists?path.join(backup,'data'):null};
|
|
103
104
|
}
|
|
104
|
-
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
|
|
105
|
+
export async function locked(p,fn,{recovery=false}={}){rejectLinks(p.root);fs.mkdirSync(p.manager,{recursive:true,mode:0o700});const lock=path.join(p.manager,'operation.lock'),guard=path.join(p.manager,'recovery.lock');if(!recovery&&fs.existsSync(guard))fail('BUSY','Recovery is active');let fd;try{fd=fs.openSync(lock,'wx',0o600);}catch{fail('BUSY','Another operation or interrupted lock exists; inspect doctor and repair plan');}try{if(!recovery&&fs.existsSync(guard))fail('BUSY','Recovery is active');fs.writeSync(fd,JSON.stringify({pid:process.pid,time:new Date().toISOString()}));return await fn();}finally{fs.closeSync(fd);fs.unlinkSync(lock);}}
|
|
105
106
|
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;}
|
|
106
107
|
export async function manifestFor(v){if(v===pkg.version){const local=read(path.join(packageRoot,'release.json'));if((local.binding?.managerVersion===v||local.version===v)&&Object.keys(local.platforms||{}).length){validateBinding(local,{managerVersion:v});return local;}}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}`);return packageManifest(await r.json(),v);}
|
|
107
108
|
export function installedBinding(p=paths()){const f=path.join(p.manager,'binding.json');return fs.existsSync(f)?read(f).manifest:null;}
|
|
@@ -119,7 +120,7 @@ export async function configure(keyFile,{p=paths(),isRunning=running}={}){if(!ke
|
|
|
119
120
|
if(!fs.existsSync(models))fs.copyFileSync(path.join(packageRoot,'defaults','models.json'),models,fs.constants.COPYFILE_EXCL);
|
|
120
121
|
if(previous!==updated){const next=config+'.new-'+crypto.randomUUID();fs.writeFileSync(next,updated,{flag:'wx',mode:0o600});fs.renameSync(next,config);if(fs.readFileSync(config,'utf8')!==updated)fail('VERIFY_FAILED','Model configuration did not match the requested provider');}
|
|
121
122
|
return {ok:true,configured:true,backup,configBackup,configPreserved:previous===updated,settingsPreserved:true,modelProvider:'dashscope_deepseek'};});}
|
|
122
|
-
export async function install({version,adopt=false,archive,manifest,expectedCurrent,p=paths(),runtimeInstaller=ensureRuntime,identify=appIdentity}={}) {
|
|
123
|
+
export async function install({version,adopt=false,archive,manifest,expectedCurrent,p=paths(),runtimeInstaller=ensureRuntime,identify=appIdentity,isRunning=running}={}) {
|
|
123
124
|
if(!['darwin-arm64','win32-x64'].includes(p.platform))fail('UNSUPPORTED_PLATFORM',p.platform);
|
|
124
125
|
progress('准备安装:检查版本、平台与独立目录');
|
|
125
126
|
return locked(p,async()=>{const m=manifest||await manifestFor(version||pkg.version);const a=validateManifest(m,p.platform);
|
|
@@ -132,8 +133,9 @@ export async function install({version,adopt=false,archive,manifest,expectedCurr
|
|
|
132
133
|
if(m.binding){recordBinding(p,m);if(!prior.desktop||bindingHash(prior.releaseManifest)!==bindingHash(m))writeState(p,{...prior,desktop:{version:m.version,identity:a.sha256},releaseManifest:m});}
|
|
133
134
|
return {ok:true,unchanged:true,version:m.version,managerVersion:m.binding?.managerVersion||pkg.version,runtimeVersion:m.runtimes?.version||null,runtimeChanged:!!tools&&!tools.unchanged,transfer:{downloadBytes:0,downloadBlocks:0},desktopActivated:false};
|
|
134
135
|
}
|
|
135
|
-
if(
|
|
136
|
+
if(isRunning(p).length)fail('APP_RUNNING','Quit codex-own before install/update');
|
|
136
137
|
const stage=fs.mkdtempSync(path.join(p.manager,'stage-'));let f=archive,transfer;
|
|
138
|
+
try{
|
|
137
139
|
if(f){if(fs.statSync(f).size!==a.size||await digestFile(f)!==a.sha256)fail('CHECKSUM_MISMATCH','Local archive checksum mismatch');}
|
|
138
140
|
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');const cache=path.join(p.manager,'npm-assets');rejectLinks(cache);
|
|
139
141
|
if(state(p)?.identity==='local.codex-own'&&fs.existsSync(p.target)&&appIdentity(p.target)==='local.codex-own'){
|
|
@@ -143,12 +145,13 @@ export async function install({version,adopt=false,archive,manifest,expectedCurr
|
|
|
143
145
|
progress('校验通过,正在解压桌面程序');
|
|
144
146
|
const content=path.join(stage,'unpacked');unpack(f,content);const source=path.join(content,a.entry);
|
|
145
147
|
if(process.platform==='darwin')execFileSync('codesign',['--verify','--deep','--strict',source],{stdio:'pipe'});
|
|
146
|
-
if(m.runtimes)await
|
|
148
|
+
if(m.runtimes)await runtimeInstaller(m.runtimes,{p,report:progress});
|
|
147
149
|
progress('正在安装到独立目录');
|
|
148
|
-
const result=switchApp({p,source,version:m.version,manifest:m,runtimeSelection:previousRuntime,adopt});if(m.binding)recordBinding(p,m);
|
|
150
|
+
const result=switchApp({p,source,version:m.version,manifest:m,runtimeSelection:previousRuntime,adopt,isRunning});if(m.binding)recordBinding(p,m);
|
|
149
151
|
if(process.platform==='darwin')execFileSync('/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister',['-f',p.target],{stdio:'pipe'});
|
|
150
152
|
else shortcut(p);
|
|
151
|
-
|
|
153
|
+
progress('安装完成');return {...result,downloadSource:archive?'local-archive':'registry.npmjs.org',...(transfer?{transfer}:{})};
|
|
154
|
+
}finally{await removeTemporary(stage,{report:progress});}
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
157
|
export function shortcut(p) {
|
|
@@ -165,4 +168,4 @@ export async function rollback({restoreData=false}={}){const p=paths();return lo
|
|
|
165
168
|
if(prev.state?.releaseManifest){const m=prev.state.releaseManifest;if(!prev.runtimeSelection&&m.runtimes)await ensureRuntime(m.runtimes,{p,report:progress});if(m.binding)recordBinding(p,m);}
|
|
166
169
|
else{const f=path.join(p.manager,'binding.json');if(fs.existsSync(f))fs.renameSync(f,f+'.before-legacy-rollback-'+crypto.randomUUID());}
|
|
167
170
|
writeState(p,{...(prev.state||{schema:1,identity:'local.codex-own',version:'adopted-legacy',target:p.target}),previous:null});return {ok:true,preserved,dataBackup:backup};});}
|
|
168
|
-
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')),agentShell:{...windowsAgentShell.discover(),desktopAdapterBundled:process.platform==='win32'&&fs.existsSync(path.join(p.target,'resources','own-updater','lib','windows-agent-shell.cjs')),policyContract:1},windowsGuiTested:false};}
|
|
171
|
+
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&&!fs.existsSync(path.join(p.manager,'operation.lock'))&&!fs.existsSync(path.join(p.manager,'recovery.lock')),bootstrap:{nodeVersion:process.version,executable:process.execPath,architecture:process.arch,supported:Number(process.versions.node.split('.')[0])>=22},lockDetails:lockStatus(p),orphanStages:temporaryStages(p),recoveryLock:fs.existsSync(path.join(p.manager,'recovery.lock')),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')),agentShell:{...windowsAgentShell.discover(),desktopAdapterBundled:process.platform==='win32'&&fs.existsSync(path.join(p.target,'resources','own-updater','lib','windows-agent-shell.cjs')),policyContract:1},windowsGuiTested:false};}
|
package/lib/network.mjs
CHANGED
|
@@ -3,7 +3,9 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import {EnvHttpProxyAgent, fetch} from 'undici';
|
|
4
4
|
|
|
5
5
|
const dispatcher = new EnvHttpProxyAgent();
|
|
6
|
-
|
|
6
|
+
let progressMode='stderr';
|
|
7
|
+
export function setProgressMode(mode){if(!['stderr','stdout','quiet'].includes(mode))throw Object.assign(new Error('Progress must be stderr, stdout or quiet'),{code:'INVALID_ARGUMENT'});progressMode=mode;}
|
|
8
|
+
export const progress = message => {if(progressMode!=='quiet')(progressMode==='stdout'?process.stdout:process.stderr).write(`[codex-own] ${message}\n`);};
|
|
7
9
|
export function networkFetch(url, options = {}) {
|
|
8
10
|
return fetch(url, {dispatcher, redirect: 'error', ...options});
|
|
9
11
|
}
|
package/lib/tool-runtime.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import {execFileSync,spawnSync} from 'node:child_process';
|
|
|
5
5
|
import {createRequire} from 'node:module';
|
|
6
6
|
import {assembleNpmAsset,validateParts} from './npm-assets.mjs';
|
|
7
7
|
import {paths,state,locked,rejectLinks,exclusive,unpack,fail,tarExecutable,digestFile} from './manager.mjs';
|
|
8
|
+
import {copyNewDirectory,removeTemporary} from './file-operations.mjs';
|
|
8
9
|
const require=createRequire(import.meta.url);
|
|
9
10
|
export const toolEnv=require('./tool-env.cjs');
|
|
10
11
|
export function validateRuntime(m,platform){
|
|
@@ -17,6 +18,8 @@ function writeActive(base,r){
|
|
|
17
18
|
const f=path.join(base,'active.json');rejectLinks(f);
|
|
18
19
|
if(fs.existsSync(f))fs.copyFileSync(f,f+'.backup-'+crypto.randomUUID(),fs.constants.COPYFILE_EXCL);
|
|
19
20
|
const tmp=f+'.new-'+crypto.randomUUID();exclusive(tmp,r);fs.renameSync(tmp,f);
|
|
21
|
+
// Bound verified selection receipts, preserving every immutable tool version.
|
|
22
|
+
try{const history=fs.readdirSync(base).filter(n=>/^active\.json\.backup-[a-f0-9-]{36}$/.test(n)).map(n=>path.join(base,n)).filter(p=>{const s=fs.lstatSync(p);if(!s.isFile()||s.isSymbolicLink())return false;try{const v=JSON.parse(fs.readFileSync(p));return v.schema===1&&/^node[0-9.]+-python[0-9.]+-r\d+$/.test(v.version);}catch{return false;}}).sort((a,b)=>fs.statSync(b).mtimeMs-fs.statSync(a).mtimeMs);for(const p of history.slice(20))fs.unlinkSync(p);}catch{}
|
|
20
23
|
}
|
|
21
24
|
export function runtimeDoctor(base=toolEnv.runtimeRoot(),{version}={}){
|
|
22
25
|
try{const r=toolEnv.active(base,process.platform,version);if(!r)return {ok:false,code:'RUNTIME_NOT_INSTALLED',root:base};
|
|
@@ -63,7 +66,7 @@ export async function ensureRuntime(m,{p=paths(),assemble=assembleNpmAsset,repor
|
|
|
63
66
|
// Windows scanners may hold executables after execution. Never rename an
|
|
64
67
|
// executed tool directory: copy to a fresh immutable final path, probe there,
|
|
65
68
|
// and only then atomically switch the small active receipt.
|
|
66
|
-
|
|
69
|
+
copyNewDirectory(candidate,target,{onCreate:()=>{created=true;}});
|
|
67
70
|
if(process.platform==='darwin'){const shell=path.join(target,'shell');fs.mkdirSync(shell);for(const [name,body]of Object.entries(toolEnv.shellFiles(base,m.version)))fs.writeFileSync(path.join(shell,name),body,{flag:'wx',mode:0o600});}
|
|
68
71
|
const tested=runtimeDoctor(base,{version:m.version});if(!tested.ok)fail('RUNTIME_PROBE_FAILED',tested.message||'Tool verification failed');
|
|
69
72
|
const expected=m.version.match(/^node([0-9.]+)-python([0-9.]+)-/);
|
|
@@ -72,8 +75,8 @@ export async function ensureRuntime(m,{p=paths(),assemble=assembleNpmAsset,repor
|
|
|
72
75
|
exclusive(path.join(target,'codex-own-runtime.json'),{schema:1,version:m.version,platform:p.platform,assets:{node:assets.node.sha256,python:assets.python.sha256},verified:tested});
|
|
73
76
|
writeActive(base,{schema:1,version:m.version,platform:p.platform,previous:previous?.version||null,installedAt:new Date().toISOString()});
|
|
74
77
|
committed=true;
|
|
75
|
-
return
|
|
76
|
-
}finally{for(const f of [stage,...(created&&!committed?[target]:[])])
|
|
78
|
+
return tested;
|
|
79
|
+
}finally{for(const f of [stage,...(created&&!committed?[target]:[])])await removeTemporary(f,{report});}
|
|
77
80
|
}
|
|
78
81
|
export async function installRuntime(m,options={}){const p=options.p||paths();return locked(p,async()=>{const result=await ensureRuntime(m,{...options,p});if(options.selection){const base=path.join(p.root,'runtimes'),active=JSON.parse(fs.readFileSync(path.join(base,'active.json')));writeActive(base,{...active,selection:options.selection,selectedForDesktop:state(p)?.version||null});}return result;});}
|
|
79
82
|
export function restoreRuntimeSelection(selected,{p=paths()}={}){
|