@petercjl/taobao-ai-ops 0.4.1 → 0.4.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/INSTALL.md +13 -0
- package/UPDATE.md +13 -0
- package/package.json +5 -2
- package/sealwork.collection.json +10 -0
- package/skills/taobao-ai-operations/SKILL.md +7 -0
- package/src/cli.mjs +11 -4
- package/src/components.mjs +2 -14
- package/src/subscription.mjs +105 -0
package/INSTALL.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 安装淘宝 AI 运营工具集
|
|
2
|
+
|
|
3
|
+
把以下提示词发送给当前使用的 Agent:
|
|
4
|
+
|
|
5
|
+
> 安装 @petercjl/taobao-ai-ops,并安装配套 Skill。
|
|
6
|
+
|
|
7
|
+
当前 Agent 先解析自己的 Skill 目录与可用能力,再读取包的安装合同。安装包含完整 Skill 资源、CLI 与依赖;安装完成后由当前宿主验证 Skill 发现与实际入口。配置、登录和业务服务授权分别办理。
|
|
8
|
+
|
|
9
|
+
支持能力集合合同的宿主可订阅包根的 `sealwork.collection.json`。sealwork 的插件页添加完整 npm 包名,启动后默认自动更新壳和成员,并安装以后登记的新成员。它使用宿主独占的整套版本目录,保留已有单独安装的 Skill;停止订阅后这些独立安装仍可使用。
|
|
10
|
+
|
|
11
|
+
开发者合同:`collection check --previous <collection.json> --json` 只检查;`collection prepare --directory <new-directory> --previous <collection.json> --yes --json` 生成候选。首次安装省略 previous。宿主提供 Node 和 `npm_execpath` 指向的 npm CLI,不运行 npm 生命周期脚本;宿主负责任务隔离、候选验证、激活、原生 Skill 刷新和失败回退。候选回执包含完整成员、所有 Skill、目标自有 CLI 入口与运行环境。Skill 和成员方法仍由各自包拥有。
|
|
12
|
+
|
|
13
|
+
目录必须是新目录。安装失败删除本次候选;未知结果先检查候选回执和实际入口,避免重复安装。安装成功与宿主发现确认分别记录。
|
package/UPDATE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 更新淘宝 AI 运营工具集
|
|
2
|
+
|
|
3
|
+
把以下提示词发送给当前使用的 Agent:
|
|
4
|
+
|
|
5
|
+
> 更新 @petercjl/taobao-ai-ops 及其成员包。
|
|
6
|
+
|
|
7
|
+
已在 sealwork 订阅并启动时,默认每六小时检查壳和全部成员。即使壳版本没有变化,成员也会独立检查。管理员新增成员后发布成员包、登记清单并发布壳;既有成员更新只发布该成员即可。普通业务调用使用已经选定的版本。
|
|
8
|
+
|
|
9
|
+
自动安装稳定兼容版本:1.x 及以上保持主版本,0.x 保持次版本,拒绝降版。跨兼容边界保留当前版本,并在插件详情报告需要显式选择的版本。关闭自动更新时冻结当前集合;停止时取消发现与自动检查,保留代码以便再次启动;移除清理该订阅自己的代码。
|
|
10
|
+
|
|
11
|
+
宿主安装完整候选后,在任务和审批空闲时切换并刷新 Skill。下载失败、离线、候选验证失败、用户修改过受管 Skill 或宿主刷新失败时,保留上一套。更新只涉及选定宿主的订阅目录,配置和凭据留在包外。插件详情显示实际成员版本及上次检查结果。
|
|
12
|
+
|
|
13
|
+
开发者:每次检查都以当前壳的注册表为准,与 previous 回执比较成员身份、版本和所需 Skill;`collection prepare` 返回独立候选,宿主验证后才激活。回执中的运行环境必须跟随 CLI 调用,当前任务内保持同一版本。新集合入口不接管历史全局 npm 安装;历史安装继续通过其已声明的维护入口操作。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/taobao-ai-ops",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Independent Taobao AI operations SOP skills with a managed suite lifecycle",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"!skills/**/__pycache__/**",
|
|
27
27
|
"!skills/**/*.pyc",
|
|
28
28
|
"!skills/**/*.bak",
|
|
29
|
-
"!skills/**/.install-meta.json"
|
|
29
|
+
"!skills/**/.install-meta.json",
|
|
30
|
+
"sealwork.collection.json",
|
|
31
|
+
"INSTALL.md",
|
|
32
|
+
"UPDATE.md"
|
|
30
33
|
],
|
|
31
34
|
"publishConfig": {
|
|
32
35
|
"access": "public",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "capability-collection",
|
|
4
|
+
"id": "petercjl.taobao-ai-ops",
|
|
5
|
+
"name": "淘宝 AI 运营工具集",
|
|
6
|
+
"description": "独立发布的电商 Skill 与 CLI,自动检查壳和成员更新并安装新增成员。",
|
|
7
|
+
"entry": "bin/taobao-ai-ops.mjs",
|
|
8
|
+
"platforms": ["darwin", "win32", "linux"],
|
|
9
|
+
"autoUpdate": {"channel": "latest", "compatibility": "semver"}
|
|
10
|
+
}
|
|
@@ -80,3 +80,10 @@ Before every audio component invocation, the suite checks stable latest, install
|
|
|
80
80
|
输入为当次源图、商品依据和用户目标,可补充已确认文案、比例和版式要求。输出为本轮约定数量的独立成图及简短验收说明,或仅要求的转换规划。品牌方法、人物与产品约束、素材选择、摄影与排版顺序、提示词、任务恢复及效果验收由子 Skill 的当前主线承接。遇到来源/合同不兼容、缺失资产或宿主能力不可用时,保留子层结构化错误并停止受影响阶段;满足条件后回到子层执行节点,完成后返回套件主线第 6 步交付。
|
|
81
81
|
|
|
82
82
|
独立包与素材不复制到套件。普通业务使用已安装版本;显式套件更新获取该组件的正式 latest 并同步受管 Skill。本应用依赖 sealseek-media-cli(0.6.1+)和配套 sealseek-media Skill;缺失或不兼容时暂停整个应用任务,给出“请安装 npm 包 @petercjl/sealseek-media-cli。”,安装并复查通过后恢复。模型和服务授权遵循媒体包当前合同,其他应用的生图路由保持各自合同。
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## 宿主管理的能力集合
|
|
86
|
+
|
|
87
|
+
当前宿主通过能力集合订阅安装时,宿主提供的 collection-runtime 回执声明本任务的 runtimeEntry 与 environment。调用所选 CLI 时使用该入口和环境,依照当前 Skill 完成业务;壳通过自己的组件记录解析成员。业务期间保持该版本集合。安装、定期更新、启停、移除和热刷新由宿主订阅管理;本 Skill 负责业务路由及子 Skill 的完整当前主线。
|
|
88
|
+
|
|
89
|
+
维护时,壳独立检查全部注册成员,无需壳先发布新版本。新建成员包发布后登记并发布壳;既有成员的后续更新只发布成员即可。首次订阅默认允许安装以后登记的新成员,业务登录、付费与外部操作继续遵循各能力的授权。离线、合同或安装失败保留当前集合,并回到失败阶段恢复;不要通过业务调用替换正在使用的集合。
|
package/src/cli.mjs
CHANGED
|
@@ -6,12 +6,14 @@ import {targetRoot,status,install} from './skill-manager.mjs';
|
|
|
6
6
|
import {runScript,doctor,toolEntry,componentEnvironment} from './runtime.mjs';
|
|
7
7
|
import {plan,workflowStatus,createHandoff} from './workflow.mjs';
|
|
8
8
|
import {checkUpdate,installUpdate} from './update.mjs';
|
|
9
|
+
import {collectionCheck,collectionPrepare} from './subscription.mjs';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
9
11
|
const print=v=>console.log(JSON.stringify(v,null,2));
|
|
10
|
-
function options(args){const out={};for(let i=0;i<args.length;i++){const k=args[i];if(['--json','--adopt','--yes'].includes(k))out[k.slice(2)]=true;else if(['--path','--agent','--target-dir','--name','--node','--profile','--mode','--run-dir','--input','--out','--candidate','--query','--tag','--context','--run-id'].includes(k)){if(!args[i+1]||args[i+1].startsWith('--'))throw fail('USAGE',`${k} needs a value`);out[k.slice(2).replace(/-([a-z])/g,(_,c)=>c.toUpperCase())]=args[++i];}else throw fail('USAGE',`Unknown option ${k}`);}return out;}
|
|
12
|
+
function options(args){const out={};for(let i=0;i<args.length;i++){const k=args[i];if(['--json','--adopt','--yes'].includes(k))out[k.slice(2)]=true;else if(['--directory','--previous','--path','--agent','--target-dir','--name','--node','--profile','--mode','--run-dir','--input','--out','--candidate','--query','--tag','--context','--run-id'].includes(k)){if(!args[i+1]||args[i+1].startsWith('--'))throw fail('USAGE',`${k} needs a value`);out[k.slice(2).replace(/-([a-z])/g,(_,c)=>c.toUpperCase())]=args[++i];}else throw fail('USAGE',`Unknown option ${k}`);}return out;}
|
|
11
13
|
export async function main(argv){
|
|
12
14
|
const [command,action,...rest]=argv;
|
|
13
|
-
if(!command||['help','--help','-h'].includes(command))return console.log(`taobao-ai-ops ${PACKAGE.version}\nversion | nodes list | node show ID | capabilities\ndoctor --node ID [--mode excel|nas]\nskill source [--name NAME] | skill status|install|update --agent AGENT [--profile research] [--adopt]\ncomponents status|check|install [--agent AGENT --yes]\ncomponents register --name ID --path CHECKOUT --yes [--mode npm]\ncomponent run ID [arguments...] | script NODE SCRIPT [arguments...] | tool run TOOL [arguments...]\nworkflow plan|status --run-dir DIR [--node ID] [--input FILE]\nhandoff create --node ID --input FILE --out FILE [--candidate PATH --query QUERY]\nupdate check|install [--tag latest] [--agent AGENT --yes]`);
|
|
14
|
-
if(['version','--version'].includes(command))return console.log(PACKAGE.version);
|
|
15
|
+
if(!command||['help','--help','-h'].includes(command))return console.log(`taobao-ai-ops ${PACKAGE.version}\nversion | nodes list | node show ID | capabilities\ncollection check [--previous RECEIPT] --json | collection prepare --directory NEW --yes --json [--previous RECEIPT]\ndoctor --node ID [--mode excel|nas]\nskill source [--name NAME] | skill status|install|update --agent AGENT [--profile research] [--adopt]\ncomponents status|check|install [--agent AGENT --yes]\ncomponents register --name ID --path CHECKOUT --yes [--mode npm]\ncomponent run ID [arguments...] | script NODE SCRIPT [arguments...] | tool run TOOL [arguments...]\nworkflow plan|status --run-dir DIR [--node ID] [--input FILE]\nhandoff create --node ID --input FILE --out FILE [--candidate PATH --query QUERY]\nupdate check|install [--tag latest] [--agent AGENT --yes]`);
|
|
16
|
+
if(['version','--version'].includes(command))return action==='--json'?print({ok:true,package:PACKAGE.name,version:PACKAGE.version}):console.log(PACKAGE.version);
|
|
15
17
|
if(command==='script')return runScript(action,rest[0],rest.slice(1));
|
|
16
18
|
if(command==='component'&&action==='run'){
|
|
17
19
|
await prepareComponentRun(rest[0]);
|
|
@@ -23,9 +25,14 @@ export async function main(argv){
|
|
|
23
25
|
}
|
|
24
26
|
if(command==='nodes'&&action==='list')return print(MANIFEST.nodes);
|
|
25
27
|
if(command==='node'&&action==='show')return print(nodeFor(rest[0]));
|
|
26
|
-
if(command==='capabilities')return print({package:PACKAGE.name,version:PACKAGE.version,...MANIFEST});
|
|
28
|
+
if(command==='capabilities')return print({package:PACKAGE.name,version:PACKAGE.version,...MANIFEST,collectionContract:{schemaVersion:1,manifest:'sealwork.collection.json',check:['collection','check','--json'],prepare:['collection','prepare','--directory','{newDirectory}','--yes','--json'],previousOption:'--previous',installationMode:'host-owned-generation',hostDiscoveryAuthority:'host'}});
|
|
27
29
|
if(command==='doctor'){const opts=options(argv.slice(1));const r=await doctor(opts.node,opts.mode);print(r);if(!r.ok)process.exitCode=1;return;}
|
|
28
30
|
const opts=options(rest);
|
|
31
|
+
if(command==='collection'){
|
|
32
|
+
if(action==='check')return print(await collectionCheck(opts.previous?JSON.parse(await fs.readFile(opts.previous,'utf8')):undefined));
|
|
33
|
+
if(action==='prepare')return print(await collectionPrepare(opts));
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
if(command==='components'){
|
|
30
37
|
if(action==='status')return print({ok:true,components:componentRecords()});
|
|
31
38
|
if(action==='check')return print(await checkComponents());
|
package/src/components.mjs
CHANGED
|
@@ -13,20 +13,8 @@ export async function inspectComponent(id,root){
|
|
|
13
13
|
if(p.name!==c.package)throw fail('COMPONENT_IDENTITY_MISMATCH',`${id}: ${p.name}`);
|
|
14
14
|
const entry=path.resolve(root,p.bin?.[c.bin]||'');
|
|
15
15
|
if(!p.bin?.[c.bin]||!entry.startsWith(path.resolve(root)+path.sep))throw fail('INVALID_COMPONENT_ENTRY',id);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const r=spawnSync(process.execPath,[entry,'skill','source','--json'],{encoding:'utf8',timeout:30000,env:{...process.env,TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1',SEEDAUDIO_AUTO_UPDATE:'0',SEALSEEK_MEDIA_AUTO_UPDATE:'0',COMPETITOR_CANVAS_AUTO_UPDATE:'0'}});
|
|
19
|
-
if(r.status!==0)throw fail('COMPONENT_SOURCE_FAILED',r.stderr||id);
|
|
20
|
-
const envelope=JSON.parse(r.stdout);
|
|
21
|
-
if(envelope.ok===false)throw fail('COMPONENT_SOURCE_FAILED',id, envelope.error);
|
|
22
|
-
const result=envelope.data??envelope;
|
|
23
|
-
if(result.skill&&result.skill!==skill)throw fail('SKILL_IDENTITY_MISMATCH',skill);
|
|
24
|
-
const s=result.source||(result.skills||[]).find(s=>s.skill===skill)?.source;
|
|
25
|
-
if(!s||!path.resolve(s).startsWith(path.resolve(root)+path.sep))throw fail('INVALID_COMPONENT_SOURCE',skill);
|
|
26
|
-
const text=await fs.readFile(path.join(s,'SKILL.md'),'utf8');
|
|
27
|
-
if(!new RegExp(`^name: ${skill}\\s*$`,'m').test(text))throw fail('SKILL_IDENTITY_MISMATCH',skill);
|
|
28
|
-
sources[skill]=s;
|
|
29
|
-
}
|
|
16
|
+
const {readSources}=await import('./subscription.mjs');
|
|
17
|
+
const sources=Object.fromEntries((await readSources(entry,root,c)).map(s=>[s.name,s.source]));
|
|
30
18
|
return {package:p.name,version:p.version,root:path.resolve(root),entry,sources};
|
|
31
19
|
}
|
|
32
20
|
async function save(records){
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {spawn} from 'node:child_process';
|
|
4
|
+
import {ROOT,PACKAGE,MANIFEST,fail,stat,fileHashes} from './core.mjs';
|
|
5
|
+
|
|
6
|
+
const stable=v=>typeof v==='string'&&/^\d+\.\d+\.\d+$/.test(v);
|
|
7
|
+
export function compatible(current,next){
|
|
8
|
+
if(!stable(current)||!stable(next))return false;
|
|
9
|
+
const a=current.split('.').map(Number),b=next.split('.').map(Number);
|
|
10
|
+
return a[0]===b[0]&&(a[0]!==0||a[1]===b[1])&&next.localeCompare(current,undefined,{numeric:true})>=0;
|
|
11
|
+
}
|
|
12
|
+
export async function collectionCheck(previous,registry=fetch){
|
|
13
|
+
if(previous&&(previous.package!==PACKAGE.name||!Array.isArray(previous.members)||!stable(previous.version)))throw fail('INVALID_PREVIOUS_COLLECTION','Previous receipt must belong to this collection');
|
|
14
|
+
const members=[];
|
|
15
|
+
for(const [id,c] of Object.entries(MANIFEST.components)){
|
|
16
|
+
const response=await registry(`https://registry.npmjs.org/${encodeURIComponent(c.package)}/latest`,{signal:AbortSignal.timeout(15000)});
|
|
17
|
+
if(!response.ok)throw fail('REGISTRY_UNAVAILABLE',`${c.package}: HTTP ${response.status}; keep current collection`);
|
|
18
|
+
const metadata=await response.json();
|
|
19
|
+
if(metadata.name!==c.package||!stable(metadata.version))throw fail('INVALID_REGISTRY_IDENTITY',c.package);
|
|
20
|
+
const old=previous?.members?.find(m=>m.package===c.package);
|
|
21
|
+
const accepted=!old||compatible(old.version,metadata.version);
|
|
22
|
+
members.push({id,package:c.package,version:accepted?metadata.version:old.version,available:metadata.version,
|
|
23
|
+
bin:c.bin,skills:c.skills,blocked:!accepted});
|
|
24
|
+
}
|
|
25
|
+
const changed=previous?.version!==PACKAGE.version||JSON.stringify(members.map(m=>[m.id,m.package,m.version,m.skills]))!==JSON.stringify(previous?.members?.map(m=>[m.id,m.package,m.version,m.skills]));
|
|
26
|
+
return {ok:true,schemaVersion:1,package:PACKAGE.name,version:PACKAGE.version,changed,members};
|
|
27
|
+
}
|
|
28
|
+
export const maintenanceEnvironment=()=>({...process.env,AGENT_PACKAGE_UPDATE_CHECK:'0',TAOBAO_AI_OPS_AUTO_UPDATE:'0',TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1',SEEDAUDIO_AUTO_UPDATE:'0',SEALSEEK_MEDIA_AUTO_UPDATE:'0',COMPETITOR_CANVAS_AUTO_UPDATE:'0'});
|
|
29
|
+
async function run(command,args,timeout=300000){
|
|
30
|
+
return new Promise((resolve,reject)=>{
|
|
31
|
+
const child=spawn(command,args,{env:maintenanceEnvironment(),stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
32
|
+
let output='',error='';
|
|
33
|
+
child.stdout.on('data',b=>{output+=b;if(output.length>8_000_000)child.kill();});
|
|
34
|
+
child.stderr.on('data',b=>{error=(error+b).slice(-4000);});
|
|
35
|
+
const timer=setTimeout(()=>child.kill(),timeout);
|
|
36
|
+
child.on('error',e=>{clearTimeout(timer);reject(e);});
|
|
37
|
+
child.on('close',code=>{clearTimeout(timer);code===0?resolve(output):reject(fail('COLLECTION_CHILD_FAILED',`Command exited ${code}`,{entry:command,args:args.slice(0,4),diagnostic:error}));});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function within(root,value){const p=path.resolve(root,value),r=path.relative(root,p);if(!r||r==='..'||r.startsWith('..'+path.sep)||path.isAbsolute(r))throw fail('UNSAFE_COMPONENT_PATH',value);return p;}
|
|
41
|
+
export async function readSources(entry,root,member){
|
|
42
|
+
root=await fs.realpath(root);
|
|
43
|
+
let capabilities={};
|
|
44
|
+
try{capabilities=JSON.parse(await fs.readFile(path.join(root,'capabilities.json'),'utf8'));}catch{}
|
|
45
|
+
const collection=capabilities.discoveryContract?.skillCollection;
|
|
46
|
+
const argv=collection?.enumeration?.argv||['skill','source','--json'];
|
|
47
|
+
const raw=JSON.parse(await run(process.execPath,[entry,...argv],45000));
|
|
48
|
+
const data=raw.data??raw;if(raw.ok===false)throw fail('COMPONENT_SOURCE_FAILED',member.package);
|
|
49
|
+
const items=data[collection?.enumeration?.itemsField||'sources']??data.skills??(data.source?[data]:[]);
|
|
50
|
+
const discovered=items.filter(s=>s&&typeof s==='object');
|
|
51
|
+
const result=[];
|
|
52
|
+
for(const item of discovered){
|
|
53
|
+
const name=item.skill??item.name??(discovered.length===1&&member.skills.length===1?member.skills[0]:undefined);
|
|
54
|
+
if(typeof name!=='string'||!/^[a-z0-9][a-z0-9-]{1,99}$/.test(name))throw fail('INVALID_SKILL_IDENTITY',member.package);
|
|
55
|
+
let source;try{source=await fs.realpath(item.source??item.path);within(root,source);}catch{throw fail('INVALID_COMPONENT_SOURCE',name);}
|
|
56
|
+
const real=await fs.realpath(source);within(await fs.realpath(root),real);
|
|
57
|
+
const text=(await fs.readFile(path.join(source,'SKILL.md'),'utf8')).replace(/\r\n?/g,'\n');
|
|
58
|
+
if(!new RegExp(`^name:\\s*${name}\\s*$`,'m').test(text))throw fail('SKILL_IDENTITY_MISMATCH',`${member.package}: ${name}`);
|
|
59
|
+
result.push({name,source});
|
|
60
|
+
}
|
|
61
|
+
for(const name of member.skills)if(!result.some(s=>s.name===name))throw fail('INCOMPLETE_COMPONENT_SOURCES',`${member.package}: ${name}`);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
// Creates a whole host-owned candidate. The host alone activates it or deletes it.
|
|
65
|
+
// Ordinary business calls never replace this graph underneath an active task.
|
|
66
|
+
export async function collectionPrepare(opts,services={}){
|
|
67
|
+
if(!opts.yes||!opts.directory)throw fail('USAGE','collection prepare requires --directory and --yes');
|
|
68
|
+
const directory=path.resolve(opts.directory);
|
|
69
|
+
if(await stat(directory))throw fail('TARGET_EXISTS','Candidate directory must be new');
|
|
70
|
+
const previous=opts.previous?JSON.parse(await fs.readFile(opts.previous,'utf8')):undefined;
|
|
71
|
+
const plan=await collectionCheck(previous,services.registry);
|
|
72
|
+
await fs.mkdir(directory,{recursive:true,mode:0o700});
|
|
73
|
+
try{
|
|
74
|
+
const shell=path.join(directory,'shell'),runtime=path.join(directory,'runtime'),skills=path.join(directory,'skills');
|
|
75
|
+
await fs.cp(ROOT,shell,{recursive:true,filter:p=>!['node_modules','.git','dev','test','.qa-skills'].includes(path.basename(p))});
|
|
76
|
+
await fs.mkdir(runtime);await fs.mkdir(skills);
|
|
77
|
+
const npmCli=process.env.npm_execpath;
|
|
78
|
+
if(!npmCli?.endsWith('npm-cli.js'))throw fail('NPM_UNAVAILABLE','Host must supply its managed npm-cli.js in npm_execpath');
|
|
79
|
+
await (services.run??run)(process.execPath,[npmCli,'install','--prefix',runtime,'--ignore-scripts','--no-audit','--no-fund','--package-lock=true',...plan.members.map(m=>`${m.package}@${m.version}`)],600000);
|
|
80
|
+
const installed=[],records={};
|
|
81
|
+
async function copySkill(name,source,entry,member){
|
|
82
|
+
if(installed.some(s=>s.name===name))throw fail('DUPLICATE_SKILL',name);
|
|
83
|
+
const destination=path.join(skills,name);await fileHashes(source);await fs.cp(source,destination,{recursive:true,dereference:true});
|
|
84
|
+
installed.push({name,package:member.package,version:member.version,source,directory:destination,runtimeEntry:entry});
|
|
85
|
+
}
|
|
86
|
+
for(const member of plan.members){
|
|
87
|
+
const root=path.join(runtime,'node_modules',...member.package.split('/'));
|
|
88
|
+
const identity=JSON.parse(await fs.readFile(path.join(root,'package.json'),'utf8'));
|
|
89
|
+
if(identity.name!==member.package||identity.version!==member.version)throw fail('COMPONENT_IDENTITY_MISMATCH',member.package);
|
|
90
|
+
const entry=within(root,typeof identity.bin==='string'?identity.bin:identity.bin?.[member.bin]??'');
|
|
91
|
+
await fs.access(entry);
|
|
92
|
+
const sources=await readSources(entry,root,member);
|
|
93
|
+
records[member.id]={package:member.package,version:member.version,root,entry,sources:Object.fromEntries(sources.map(s=>[s.name,s.source]))};
|
|
94
|
+
for(const s of sources)await copySkill(s.name,s.source,entry,member);
|
|
95
|
+
}
|
|
96
|
+
await copySkill('taobao-ai-operations',path.join(shell,'skills','taobao-ai-operations'),path.join(shell,'bin','taobao-ai-ops.mjs'),{package:PACKAGE.name,version:PACKAGE.version});
|
|
97
|
+
const state=path.join(directory,'state');await fs.mkdir(state);
|
|
98
|
+
await fs.writeFile(path.join(state,'components.json'),JSON.stringify({schema_version:1,components:records}),{flag:'wx',mode:0o600});
|
|
99
|
+
const receipt={...plan,skillsDirectory:skills,skills:installed,environment:{TAOBAO_AI_OPS_STATE_DIR:state,TAOBAO_AI_OPS_AUTO_UPDATE:'0',AGENT_PACKAGE_UPDATE_CHECK:'0'}};
|
|
100
|
+
for(const skill of installed)await fs.writeFile(path.join(skill.directory,'.collection-runtime.json'),JSON.stringify({runtimeEntry:skill.runtimeEntry,environment:receipt.environment,package:skill.package,version:skill.version}),{flag:'wx',mode:0o600});
|
|
101
|
+
await fs.writeFile(path.join(directory,'collection.json'),JSON.stringify(receipt,null,2),{flag:'wx',mode:0o600});
|
|
102
|
+
await fs.writeFile(path.join(directory,'integrity.json'),JSON.stringify(await fileHashes(skills)),{flag:'wx',mode:0o600});
|
|
103
|
+
return receipt;
|
|
104
|
+
}catch(error){await fs.rm(directory,{recursive:true,force:true});throw error;}
|
|
105
|
+
}
|