@josephyulei/summon-foundation 0.1.0 → 0.1.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 +8 -4
- package/bin/summon.mjs +28 -28
- package/lib/acquire.mjs +10 -4
- package/lib/cli-options.mjs +18 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,17 @@ Foundation 的薄获取入口,不包含完整运行包,没有 npm 安装生
|
|
|
6
6
|
可用性以 npm registry 的实际版本和公共 GitHub Release 为准;本目录或说明存在不是发布回执。只使用本项目已核验的包名,缺包时不换同名来源。
|
|
7
7
|
|
|
8
8
|
```text
|
|
9
|
-
npx --yes --package @josephyulei/summon-foundation@0.1.
|
|
9
|
+
npx --yes --package @josephyulei/summon-foundation@0.1.2 summon foundation
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
0.1.2 入口配合 Foundation 0.2.7 或后续支持目录选择的正式发行使用。默认命令实际查询、固定版本、披露缓存、下载并校验,然后保持运行等待安装页目录选择。选择目录后还要亲自确认精确安装计划;不会注册 Skill 或接入项目。实际获取仍须通过 registry 与 Release 校验,本文不替代发布证明。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Codex 需要打开返回的内置浏览器网址,并分段等待同次操作结果。npm 不能自行操控或唤醒 Codex,对话主动告知不是程序退出码保证;新对话接续仍待真人验收。系统浏览器只是明确告知后的备用,不自动打开。
|
|
15
|
+
|
|
16
|
+
显式 `--inspect` 才只查询,不下载运行文件。已有选择时可用 `--prepare --version <固定版本> --destination <绝对目录>`;`--acquire --version <固定版本>` 只准备更新输入,不执行更新。更新使用已安装稳定入口。旧运行版不支持页面选择时拒绝自动采用默认目录。没有 npm 时先从 GitHub 安装说明检查环境,不自动安装全局依赖。
|
|
17
|
+
|
|
18
|
+
需要 macOS arm64、Node.js 22.9+ 与 npm。无 TTY 不会卡在终端提问;目录选择在页面进行,关闭页面不算确认或取消。
|
|
15
19
|
固定版本准备阶段匿名校验 GitHub 仓库身份、不可变 Release、签名证明、时间戳、源码提交和资产字节,随后使用随包运行时进入现有 Foundation 确认页面。
|
|
16
20
|
不关闭 Gatekeeper、不清除隔离属性,不冒充 Apple 签名或公证。遇到权限问题安全停止。
|
|
17
21
|
|
|
18
|
-
尚无 Foundation / Skill 的缓存说明:[公开清理指南](https://github.com/y15801172513-lang/foundation/blob/main/docs/cache-cleanup.md)
|
|
22
|
+
尚无 Foundation / Skill 的缓存说明:[公开清理指南](https://github.com/y15801172513-lang/foundation/blob/main/docs/cache-cleanup.md)。当前 README 不是安装完成回执。
|
package/bin/summon.mjs
CHANGED
|
@@ -1,34 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {spawn,spawnSync} from 'node:child_process';
|
|
5
5
|
import {inspectRelease,acquireRelease,plainPath} from '../lib/acquire.mjs';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import {parseSummonArgs} from '../lib/cli-options.mjs';
|
|
8
|
+
|
|
9
|
+
const help=`summon foundation [--version x.y.z] [--destination /absolute/folder]
|
|
10
|
+
summon foundation --inspect [--version x.y.z]
|
|
8
11
|
summon foundation --prepare --version x.y.z --destination /absolute/folder
|
|
9
12
|
summon foundation --acquire --version x.y.z
|
|
10
|
-
|
|
13
|
+
默认命令下载并验证正式发行,进入安装准备;未给目录时在安装页选择。不会静默安装或注册 Skill。
|
|
14
|
+
--inspect 才只读查询发行,不下载运行归档。
|
|
11
15
|
--prepare 下载并核验固定发行,打开 Foundation 本人确认流程;不会代替确认。
|
|
12
16
|
--acquire 仅下载并核验更新材料,返回候选与回执;不启动安装或执行更新。
|
|
13
17
|
仅 macOS arm64。npm 入口需要 Node/npm;已安装 Foundation 使用随包运行时。
|
|
14
18
|
普通终端不能控制 Codex 对话或内置浏览器。无需 npm 的引导见公共仓库安装说明。`;
|
|
15
19
|
function fail(message){throw Error(message);}
|
|
16
|
-
function parse(args){
|
|
17
|
-
if(args.length===0||args.length===1&&['--help','-h'].includes(args[0]))return {help:true};
|
|
18
|
-
if(args.shift()!=='foundation')fail('仅支持 summon foundation');
|
|
19
|
-
const result={prepare:false};const seen=new Set();
|
|
20
|
-
while(args.length){const flag=args.shift();if(seen.has(flag))fail('重复参数');seen.add(flag);
|
|
21
|
-
if(flag==='--prepare')result.prepare=true;
|
|
22
|
-
else if(flag==='--acquire')result.acquire=true;
|
|
23
|
-
else if(flag==='--inspect')result.inspect=true;
|
|
24
|
-
else if(flag==='--version'||flag==='--destination'){if(!args.length||args[0].startsWith('--'))fail('参数缺值');result[flag.slice(2)]=args.shift();}
|
|
25
|
-
else fail('未知参数;请查看 --help');
|
|
26
|
-
}
|
|
27
|
-
if((result.prepare||result.acquire)&&result.inspect||result.prepare&&result.acquire||result.destination&&!result.prepare)fail('参数组合不支持');
|
|
28
|
-
if(result.prepare&&(!result.version||!result.destination))fail('准备前须由对话明确固定版本和意向目录');
|
|
29
|
-
if(result.acquire&&!result.version)fail('获取更新材料前须固定版本');
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
20
|
function sourceGuard(){
|
|
33
21
|
for(const start of [fs.realpathSync(process.cwd()),fs.realpathSync(import.meta.dirname)]){
|
|
34
22
|
for(let p=start;p!==path.dirname(p);p=path.dirname(p)){
|
|
@@ -47,23 +35,35 @@ function stageDirectory(){
|
|
|
47
35
|
return fs.mkdtempSync(path.join(cache,'acquisition.'));
|
|
48
36
|
}
|
|
49
37
|
try{
|
|
50
|
-
const args=
|
|
38
|
+
const args=parseSummonArgs(process.argv.slice(2));
|
|
51
39
|
if(args.help){console.log(help);process.exit(0);}
|
|
40
|
+
const [major,minor]=process.versions.node.split('.').map(Number);
|
|
41
|
+
if(major<22||(major===22&&minor<9))fail('获取入口需要 Node.js 22.9 或以上;尚未安装,不自动安装依赖');
|
|
52
42
|
if(process.platform!=='darwin'||process.arch!=='arm64')fail('当前仅支持 macOS arm64;尚未安装');
|
|
43
|
+
if(args.prepare||args.acquire)sourceGuard();
|
|
44
|
+
if(args.prepare)console.log('正在准备 Foundation 安装:将查询并固定本次正式版本,说明缓存位置后下载核验。未选目录会在安装页选择;之后仍需本人确认精确计划。请保持当前 Codex 任务等待并打开返回的内置浏览器网址;不会自动改用系统浏览器。');
|
|
53
45
|
const context=inspectRelease(args.version);
|
|
54
|
-
const guide=`https://github.com/${context.repository.full_name}/blob/${context.
|
|
46
|
+
const guide=`https://github.com/${context.repository.full_name}/blob/${context.documentationCommit}/docs/install-with-codex.md`;
|
|
55
47
|
if(!args.prepare&&!args.acquire){
|
|
56
|
-
console.log(JSON.stringify({schemaVersion:'1.0.0',status:'RELEASE_DISCOVERED_NOT_ACQUIRED',product:'Foundation',version:context.version,sourceCommit:context.sourceCommit,repository:context.repository.full_name,guide,installationPerformed:false,skillRegistered:false,
|
|
57
|
-
conversationNextStep:'
|
|
48
|
+
console.log(JSON.stringify({schemaVersion:'1.0.0',status:'RELEASE_DISCOVERED_NOT_ACQUIRED',product:'Foundation',version:context.version,sourceCommit:context.sourceCommit,documentationCommit:context.documentationCommit,repository:context.repository.full_name,guide,installationPerformed:false,skillRegistered:false,
|
|
49
|
+
conversationNextStep:'本次 --inspect 仅查询;未下载、未安装,不自动接续写入。需要安装时由用户发起默认安装入口,并核对当前任务权限。',
|
|
58
50
|
terminalBoundary:'普通终端无法创建或唤醒 Codex 对话;请在有相应权限的 Codex 任务发起安装。',
|
|
59
|
-
acquisition:'
|
|
51
|
+
acquisition:'尚未下载或验证运行归档。默认入口或 --prepare 执行匿名 GitHub 证明与完整性核验。'},null,2));
|
|
60
52
|
}else{
|
|
61
|
-
|
|
53
|
+
if(args.destination)plainPath(args.destination);const stage=stageDirectory();console.log(`本次独占获取目录:${stage}`);
|
|
62
54
|
const receipt=await acquireRelease(context,stage);console.log(JSON.stringify({status:'GITHUB_ACQUISITION_VERIFIED',...receipt,destinationIntent:args.destination}));
|
|
63
55
|
if(args.acquire){console.log(JSON.stringify({status:'UPDATE_INPUT_READY_NOT_APPLIED',candidate:path.dirname(receipt.launcher),receipt:path.join(stage,'acquisition.json'),next:'使用当前健康安装的稳定入口生成独立更新计划;本人确认后才更新。获取回执不是删除授权。'}));process.exit(0);}
|
|
64
56
|
const env={...process.env};for(const key of ['NODE_OPTIONS','NODE_PATH','NODE_V8_COVERAGE','NODE_REDIRECT_WARNINGS','NODE_COMPILE_CACHE','NODE_COMPILE_CACHE_PORTABLE','NODE_PRESERVE_SYMLINKS'])delete env[key];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
if(!args.destination){
|
|
58
|
+
const supported=spawnSync(receipt.launcher,['onboarding','--help'],{encoding:'utf8',env,timeout:15000});
|
|
59
|
+
if(supported.status!==0||!supported.stdout.includes('--choose-destination'))fail('本次发行尚不支持页面选择目录;未启动安装。请由当前对话取得目录后使用明确的 --destination,或等待新版发行,不静默采用默认位置');
|
|
60
|
+
}
|
|
61
|
+
const child=spawn(receipt.launcher,['install',...(args.destination?['--destination',args.destination]:['--choose-destination']),'--browser','codex'],{stdio:'inherit',env});
|
|
62
|
+
const interrupt=signal=>{child.kill(signal);};
|
|
63
|
+
const int=()=>interrupt('SIGINT'),term=()=>interrupt('SIGTERM');
|
|
64
|
+
process.once('SIGINT',int);process.once('SIGTERM',term);
|
|
65
|
+
const ended=await new Promise((resolve,reject)=>{child.once('error',reject);child.once('close',(code,signal)=>resolve({code,signal}));}).finally(()=>{process.off('SIGINT',int);process.off('SIGTERM',term);});
|
|
66
|
+
if(ended.signal||ended.code===null)fail(`确认服务未正常返回;结果待核实,保留 ${stage},不要自动重试安装`);
|
|
67
|
+
process.exitCode=ended.code;
|
|
68
68
|
}
|
|
69
69
|
}catch(e){console.error(`错误:${e.message}`);process.exitCode=1;}
|
package/lib/acquire.mjs
CHANGED
|
@@ -14,12 +14,14 @@ const json = bytes => JSON.parse(bytes.toString('utf8'));
|
|
|
14
14
|
const canonical = value => Array.isArray(value) ? `[${value.map(canonical).join(',')}]` : value && typeof value==='object' ? `{${Object.keys(value).sort().map(k=>`${JSON.stringify(k)}:${canonical(value[k])}`).join(',')}}` : JSON.stringify(value);
|
|
15
15
|
// Public transport never reads gh credentials or curl configuration. Proxy
|
|
16
16
|
// environment remains optional; neither Mono nor an author path is packaged.
|
|
17
|
-
function download(url, limit=10_000_000) {
|
|
17
|
+
function download(url, limit=10_000_000, largeAsset=false) {
|
|
18
18
|
const u = new URL(url);
|
|
19
19
|
if (u.protocol !== 'https:' || u.username || u.password) fail('只接受无凭证 HTTPS 地址');
|
|
20
20
|
const env = {PATH:'/usr/bin:/bin'};
|
|
21
21
|
for (const key of ['HTTPS_PROXY','HTTP_PROXY','ALL_PROXY','NO_PROXY','https_proxy','http_proxy','all_proxy','no_proxy','TMPDIR']) if (process.env[key]) env[key]=process.env[key];
|
|
22
|
-
|
|
22
|
+
// Large immutable assets may take minutes on a constrained proxy. Keep a
|
|
23
|
+
// bounded transfer, fail stalled connections, and never execute partial data.
|
|
24
|
+
const r=spawnSync('/usr/bin/curl',['-q',...(largeAsset?['--http1.1','--speed-limit','1024','--speed-time','60']:[]),'--fail','--silent','--show-error','--location','--max-redirs','4','--proto','=https','--proto-redir','=https','--connect-timeout','15','--max-time',largeAsset?'1200':'120',url],{env,maxBuffer:limit});
|
|
23
25
|
if(r.status!==0 || r.error) fail(`匿名获取失败(${u.hostname});保留本次材料,不自动重试安装`);
|
|
24
26
|
return r.stdout;
|
|
25
27
|
}
|
|
@@ -44,7 +46,11 @@ export function inspectRelease(version) {
|
|
|
44
46
|
if(!/^v\d+\.\d+\.\d+$/.test(release.tag_name)||!release.immutable||release.draft||release.prerelease)fail('没有可用的正式不可变发行');
|
|
45
47
|
const commit=api(`commits/${release.tag_name}`);
|
|
46
48
|
if(!/^[a-f0-9]{40}$/.test(commit.sha))fail('无法解析发行提交');
|
|
47
|
-
|
|
49
|
+
// Instructions follow the maintained public docs, frozen for this invocation;
|
|
50
|
+
// runtime identity still comes exclusively from the immutable release tag.
|
|
51
|
+
const documentation=api('commits/main');
|
|
52
|
+
if(!/^[a-f0-9]{40}$/.test(documentation.sha))fail('无法固定当前安装说明提交');
|
|
53
|
+
return {repository,release,sourceCommit:commit.sha,documentationCommit:documentation.sha,version:release.tag_name.slice(1)};
|
|
48
54
|
}
|
|
49
55
|
async function trustRoot(stage) {
|
|
50
56
|
const metadata=path.join(stage,'tuf-metadata'),targets=path.join(stage,'tuf-targets');
|
|
@@ -59,7 +65,7 @@ async function trustRoot(stage) {
|
|
|
59
65
|
}
|
|
60
66
|
async function verifiedAsset(context,asset,stage,trust) {
|
|
61
67
|
if(!asset||!Number.isSafeInteger(asset.size)||asset.size<=0||asset.size>250_000_000||!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(asset.name)||!/^sha256:[a-f0-9]{64}$/.test(asset.digest)||asset.browser_download_url!==`https://github.com/${policy.repository}/releases/download/${context.release.tag_name}/${asset.name}`)fail('资产元数据无效');
|
|
62
|
-
const bytes=download(asset.browser_download_url,asset.size+1);
|
|
68
|
+
const bytes=download(asset.browser_download_url,asset.size+1,asset.size>10_000_000);
|
|
63
69
|
if(bytes.length!==asset.size||`sha256:${hash(bytes)}`!==asset.digest)fail('下载长度或摘要不匹配');
|
|
64
70
|
const proofs=api(`attestations/${asset.digest}?predicate_type=release&per_page=100`).attestations;
|
|
65
71
|
if(!Array.isArray(proofs)||proofs.length>=100)fail('证明列表不完整或分页待核实');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function fail(message){throw Error(message);}
|
|
2
|
+
export function parseSummonArgs(args){
|
|
3
|
+
args=[...args];
|
|
4
|
+
if(args.length===0||args.length===1&&['--help','-h'].includes(args[0]))return {help:true};
|
|
5
|
+
if(args.shift()!=='foundation')fail('仅支持 summon foundation');
|
|
6
|
+
const result={prepare:false};const seen=new Set();
|
|
7
|
+
while(args.length){const flag=args.shift();if(seen.has(flag))fail('重复参数');seen.add(flag);
|
|
8
|
+
if(flag==='--prepare')result.prepare=true;
|
|
9
|
+
else if(flag==='--acquire')result.acquire=true;
|
|
10
|
+
else if(flag==='--inspect')result.inspect=true;
|
|
11
|
+
else if(flag==='--version'||flag==='--destination'){if(!args.length||args[0].startsWith('--'))fail('参数缺值');result[flag.slice(2)]=args.shift();}
|
|
12
|
+
else fail('未知参数;请查看 --help');
|
|
13
|
+
}
|
|
14
|
+
if((result.prepare||result.acquire||result.destination)&&result.inspect||result.prepare&&result.acquire||result.destination&&result.acquire)fail('参数组合不支持');
|
|
15
|
+
if(!result.inspect&&!result.acquire)result.prepare=true;
|
|
16
|
+
if(result.acquire&&!result.version)fail('获取更新材料前须固定版本');
|
|
17
|
+
return result;
|
|
18
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyulei/summon-foundation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@josephyulei/summon-foundation",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@sigstore/bundle": "4.0.0",
|
package/package.json
CHANGED