@petercjl/taobao-ai-ops 0.1.0-next.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -22
- package/package.json +3 -8
- package/skills/taobao-ai-operations/SKILL.md +5 -1
- package/src/cli.mjs +16 -6
- package/src/components.mjs +73 -0
- package/src/core.mjs +5 -1
- package/src/runtime.mjs +11 -15
- package/src/skill-manager.mjs +4 -4
- package/src/update.mjs +5 -5
- package/src/workflow.mjs +3 -3
- package/suite-manifest.json +163 -10
- package/skills/taobao-search-product-form/SKILL.md +0 -74
- package/skills/taobao-search-product-form/adapters/codex.json +0 -139
- package/skills/taobao-search-product-form/adapters/sealseek.json +0 -24
- package/skills/taobao-search-product-form/agents/openai.yaml +0 -3
- package/skills/taobao-search-product-form/capabilities.json +0 -148
- package/skills/taobao-search-product-form/references/SCHEMA.md +0 -3
- package/skills/taobao-search-product-form/references/index.md +0 -10
- package/skills/taobao-search-product-form/references/log.md +0 -35
- package/skills/taobao-search-product-form/references/queries/evaluate-white-label-forms.md +0 -3
- package/skills/taobao-search-product-form/references/queries/identify-advertisers.md +0 -5
- package/skills/taobao-search-product-form/references/queries/identify-geography.md +0 -5
- package/skills/taobao-search-product-form/references/queries/identify-price-bands.md +0 -3
- package/skills/taobao-search-product-form/references/queries/identify-product-forms.md +0 -3
- package/skills/taobao-search-product-form/references/queries/identify-title-roots.md +0 -5
- package/skills/taobao-search-product-form/references/report-output.md +0 -23
- package/skills/taobao-search-product-form/references/source-manifest.md +0 -37
- package/skills/taobao-search-product-form/references/title-lexicon.json +0 -55
- package/skills/taobao-search-product-form/references/topics/advertiser-analysis-method.md +0 -43
- package/skills/taobao-search-product-form/references/topics/geography-analysis-method.md +0 -23
- package/skills/taobao-search-product-form/references/topics/price-band-analysis-method.md +0 -27
- package/skills/taobao-search-product-form/references/topics/title-root-analysis-method.md +0 -37
- package/skills/taobao-search-product-form/references/topics/visual-form-method.md +0 -31
- package/skills/taobao-search-product-form/references/topics/white-label-evidence.md +0 -38
- package/skills/taobao-search-product-form/references/workflow-ledger.md +0 -28
- package/skills/taobao-search-product-form/report_runtime/VERSION +0 -1
- package/skills/taobao-search-product-form/report_runtime/renderer.py +0 -485
- package/skills/taobao-search-product-form/report_runtime/templates/workbench.html +0 -67
- package/skills/taobao-search-product-form/report_runtime/validator.py +0 -117
- package/skills/taobao-search-product-form/scripts/analyze.py +0 -119
- package/skills/taobao-search-product-form/scripts/analyze_advertisers.py +0 -352
- package/skills/taobao-search-product-form/scripts/analyze_geography.py +0 -316
- package/skills/taobao-search-product-form/scripts/analyze_price_bands.py +0 -257
- package/skills/taobao-search-product-form/scripts/analyze_title_roots.py +0 -327
- package/skills/taobao-search-product-form/scripts/assemble_opportunity_evidence.py +0 -322
- package/skills/taobao-search-product-form/scripts/build_report_viewmodel.py +0 -822
- package/skills/taobao-search-product-form/scripts/build_visual_review_viewmodel.py +0 -177
- package/skills/taobao-search-product-form/scripts/clean_search_export.mjs +0 -61
- package/skills/taobao-search-product-form/scripts/cluster_visual_forms.py +0 -193
- package/skills/taobao-search-product-form/scripts/compile_style_prototypes.py +0 -115
- package/skills/taobao-search-product-form/scripts/compile_visual_review.py +0 -60
- package/skills/taobao-search-product-form/scripts/compile_visual_selection.py +0 -148
- package/skills/taobao-search-product-form/scripts/prepare.py +0 -180
- package/skills/taobao-search-product-form/scripts/render_report.py +0 -31
- package/skills/taobao-search-product-form/scripts/validate_opportunity_cards.py +0 -84
- package/skills/taobao-search-product-form/scripts/validate_report_viewmodel.py +0 -152
- package/skills/taobao-search-product-form/scripts/visual_form_pipeline.py +0 -247
- package/skills/taobao-search-product-form/scripts/visual_observation_batches.py +0 -117
- package/skills/taobao-search-product-form/scripts/workflow_ledger.py +0 -177
- package/skills/yuce-category-opportunity-report/SKILL.md +0 -151
- package/skills/yuce-category-opportunity-report/adapters/codex.json +0 -113
- package/skills/yuce-category-opportunity-report/adapters/sealseek.json +0 -39
- package/skills/yuce-category-opportunity-report/agents/openai.yaml +0 -3
- package/skills/yuce-category-opportunity-report/assets/chart-views.js +0 -80
- package/skills/yuce-category-opportunity-report/assets/package.json +0 -1
- package/skills/yuce-category-opportunity-report/assets/report-template.html +0 -62
- package/skills/yuce-category-opportunity-report/capabilities.json +0 -48
- package/skills/yuce-category-opportunity-report/references/SCHEMA.md +0 -5
- package/skills/yuce-category-opportunity-report/references/data-contract.md +0 -40
- package/skills/yuce-category-opportunity-report/references/index.md +0 -6
- package/skills/yuce-category-opportunity-report/references/log.md +0 -29
- package/skills/yuce-category-opportunity-report/references/queries/assess-category-opportunity.md +0 -7
- package/skills/yuce-category-opportunity-report/references/source-manifest.md +0 -14
- package/skills/yuce-category-opportunity-report/references/topics/category-opportunity-method.md +0 -39
- package/skills/yuce-category-opportunity-report/scripts/build_report.py +0 -779
- package/skills/yuce-category-opportunity-report/scripts/market_routes.py +0 -139
package/README.md
CHANGED
|
@@ -1,38 +1,45 @@
|
|
|
1
|
-
# 淘宝 AI
|
|
1
|
+
# 淘宝 AI 运营套件
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向员工的统一管理入口。研究 Skill 和工具插件各自拥有独立 npm 包、源码和发布版本;套件只维护组件清单、安装更新、Skill发现与业务路由。
|
|
4
|
+
|
|
5
|
+
## 安装与更新
|
|
4
6
|
|
|
5
7
|
```sh
|
|
6
|
-
taobao-ai-ops
|
|
7
|
-
taobao-ai-ops
|
|
8
|
-
taobao-ai-ops doctor --
|
|
9
|
-
taobao-ai-ops
|
|
10
|
-
taobao-ai-ops
|
|
8
|
+
npm install --global @petercjl/taobao-ai-ops
|
|
9
|
+
taobao-ai-ops components install --agent codex --yes --json
|
|
10
|
+
taobao-ai-ops doctor --json
|
|
11
|
+
taobao-ai-ops update check --json
|
|
12
|
+
taobao-ai-ops update install --agent codex --yes --json
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
SealSeek 用户改用 --agent sealseek。目标有歧义时通过 SEALSEEK_SKILLS_DIR 指定。已有非套件管理的 Skill 会阻止覆盖;明确授权迁移后用 --adopt,保留完整备份。本地修改会阻止更新。
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
taobao-ai-ops skill install --agent codex --profile research --json
|
|
19
|
-
```
|
|
17
|
+
首次安装会获取清单中全部独立组件的正式 latest。统一更新检查套件及每个组件:套件版本不变也会更新组件。已安装组件的 Skill 同步可用 skill update;该命令只同步本地安装内容,不查询注册表。日常业务调用不更新环境,任务中保持已安装版本。
|
|
18
|
+
|
|
19
|
+
组件在用户状态目录中独立安装,不随套件自身重新安装而被删除。更新先准备新安装目录,核对包身份、版本与 CLI 返回的 Skill 来源,再同步受管理 Skill。npm、来源验证或 Skill 同步失败时保留旧组件记录;Skill 同步有完整备份和恢复机制。离线检查失败时报告并保留当前环境。
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## 组件
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
- @petercjl/yuce-category-opportunity-report:24个月类目数据与相对容量路线、增长分析、双视图 HTML。
|
|
24
|
+
- @petercjl/taobao-search-product-form:搜索结果数据和商品形态研究、机会判断卡及 HTML。
|
|
25
|
+
- @petercjl/tbcli:淘宝数据与 NAS 数据库工具,含其独立 Skill。
|
|
26
|
+
- @petercjl/sycmcli:店铺经营数据工具,含其独立 Skill。
|
|
27
|
+
- taobao-ai-operations:套件自带的管理与业务路由 Skill。
|
|
28
|
+
|
|
29
|
+
yccli 为取数时按需准备的外部工具,可用 TAOBAO_AI_OPS_YCCLI 指定;Excel 分析不需要 NAS、VPN 或平台登录。账号与授权留在包外,安装不会获得服务权限。
|
|
30
|
+
|
|
31
|
+
## 调用与开发
|
|
24
32
|
|
|
25
33
|
```sh
|
|
34
|
+
taobao-ai-ops components status --json
|
|
35
|
+
taobao-ai-ops components check --json
|
|
36
|
+
taobao-ai-ops skill source --name yuce-category-opportunity-report --json
|
|
26
37
|
taobao-ai-ops script category-research build_report.py --help
|
|
27
|
-
taobao-ai-ops script product-research clean_search_export.mjs SOURCE.xlsx CLEANED.xlsx
|
|
28
38
|
taobao-ai-ops tool run tbcli capabilities --json
|
|
29
|
-
taobao-ai-ops workflow plan --run-dir NEW_RUN --node product-research --input SOURCE.xlsx --json
|
|
30
39
|
```
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
节点的业务判断和图像审阅由 Agent 完成。doctor 验证确定性运行资源,并将原生图像能力列为需当前 Agent 核实;它不宣称完整业务回归通过。目标为 Codex/macOS、SealSeek/macOS 与 Windows,统一套件的真实跨 Agent 验收尚待完成。
|
|
41
|
+
维护者分别发布各能力包的正式 latest。新增/移除组件或调整管理机制时才发布套件。源码开发可通过 components register --name ID --path CHECKOUT --yes 注册独立组件;开发记录由 Git 管理,不会被注册表更新覆盖。已安装 npm 组件的显式接入可加 --mode npm。源码套件自身通过 Git 更新。
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
Node >=20。Python 研究依赖通过稳定解释器准备;TAOBAO_AI_OPS_PYTHON 可指定已配置解释器。脚本和报告模板来自实际组件目录。原生图片理解由当前 Agent 提供;缺失时报告而非缩减流程。
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
本地 macOS 确定性安装、更新与恢复测试单独记录;Windows/SealSeek 的真实宿主安装和端到端行为尚待验证。CI 发布前须先有可安装的类目独立包;所有发布通过 GitHub Actions OIDC。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/taobao-ai-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Independent Taobao AI operations SOP skills with a managed suite lifecycle",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"bin",
|
|
19
19
|
"src",
|
|
20
|
-
"skills",
|
|
20
|
+
"skills/taobao-ai-operations",
|
|
21
21
|
"contracts",
|
|
22
22
|
"suite-manifest.json",
|
|
23
23
|
"requirements.txt",
|
|
@@ -30,17 +30,12 @@
|
|
|
30
30
|
],
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public",
|
|
33
|
-
"tag": "
|
|
33
|
+
"tag": "latest"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"test": "node --test test/*.test.mjs",
|
|
37
37
|
"test:business": "node scripts/test-business.mjs",
|
|
38
38
|
"check": "node scripts/validate-suite.mjs",
|
|
39
39
|
"prepack": "npm run check"
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"@excel.js/exceljs": "0.9.0",
|
|
43
|
-
"@petercjl/tbcli": "0.10.4",
|
|
44
|
-
"@petercjl/sycmcli": "0.3.1"
|
|
45
40
|
}
|
|
46
41
|
}
|
|
@@ -18,7 +18,11 @@ Input: an operator's goal, supplied workbooks or authorized data sources, option
|
|
|
18
18
|
|
|
19
19
|
## Execution and branches
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Component scripts run via `taobao-ai-ops script NODE SCRIPT ...`; supplied Excel does not require platform accounts. Optional platform calls use `taobao-ai-ops tool run TOOL ...` so the shell selects the installed independent component. Accounts, warehouse access and write permissions remain external. The category child's database-consent and additive-import boundaries apply in its NAS route.
|
|
22
|
+
|
|
23
|
+
When a child was installed by the shell, forward its CLI examples through `taobao-ai-ops component run COMPONENT ...`, retaining all remaining arguments. This resolves the actual independent package even when its CLI is not on PATH or another global version exists. Use the shell's unified update entry for shell-managed components; a child's standalone global updater belongs only to independent global installations.
|
|
24
|
+
|
|
25
|
+
The shell registers independent npm packages; each owns its method, resources and release. Ordinary tasks use installed versions. On an explicit install/update request, use `taobao-ai-ops skill install --agent AGENT` or `taobao-ai-ops update install --agent AGENT --yes`; the latter checks both shell and every registered component's stable latest version even when the shell is unchanged. Inspect returned versions and managed Skill state. Registry failure preserves current components; local edits stop synchronization. Missing components return COMPONENT_NOT_INSTALLED and require authorized installation before returning to step 2. Development registrations update through their Git owners and are reported separately. Skill source resolves to each component's own canonical package, not a shell copy.
|
|
22
26
|
|
|
23
27
|
Current node selection and source resolution are deterministic contracts, with method judgment owned by each child Skill. The package manifest is the node registry and shared contracts are maintained in the package's contracts directory. Do not load unrelated node methods at startup.
|
|
24
28
|
|
package/src/cli.mjs
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {spawnSync} from 'node:child_process';
|
|
3
|
-
import {MANIFEST,PACKAGE,nodeFor,source,fileHashes,digest,fail} from './core.mjs';
|
|
3
|
+
import {MANIFEST,PACKAGE,nodeFor,source,nodeVersion,fileHashes,digest,fail,componentRecords,componentRecord} from './core.mjs';
|
|
4
|
+
import {checkComponents,installComponents,registerComponent} from './components.mjs';
|
|
4
5
|
import {targetRoot,status,install} from './skill-manager.mjs';
|
|
5
6
|
import {runScript,doctor,toolEntry} from './runtime.mjs';
|
|
6
7
|
import {plan,workflowStatus,createHandoff} from './workflow.mjs';
|
|
7
8
|
import {checkUpdate,installUpdate} from './update.mjs';
|
|
8
9
|
const print=v=>console.log(JSON.stringify(v,null,2));
|
|
9
|
-
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(['--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;}
|
|
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;}
|
|
10
11
|
export async function main(argv){
|
|
11
12
|
const [command,action,...rest]=argv;
|
|
12
|
-
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
|
|
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]`);
|
|
13
14
|
if(['version','--version'].includes(command))return console.log(PACKAGE.version);
|
|
14
15
|
if(command==='script')return runScript(action,rest[0],rest.slice(1));
|
|
16
|
+
if(command==='component'&&action==='run'){
|
|
17
|
+
const record=componentRecord(rest[0]);const r=spawnSync(process.execPath,[record.entry,...rest.slice(1)],{stdio:'inherit',env:{...process.env,TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1'}});if(r.error)throw fail('EXECUTION_FAILED',r.error.message);process.exitCode=r.status??1;return;
|
|
18
|
+
}
|
|
15
19
|
if(command==='tool'&&action==='run'){
|
|
16
20
|
const entry=toolEntry(rest[0]);const r=spawnSync(entry.command,[...entry.args,...rest.slice(1)],{stdio:'inherit',env:{...process.env,TBCLI_UPDATE_CHECK:'0',SYCMCLI_DISABLE_AUTO_UPDATE:'1'}});if(r.error)throw fail('CAPABILITY_UNAVAILABLE',r.error.message);process.exitCode=r.status??1;return;
|
|
17
21
|
}
|
|
@@ -20,15 +24,21 @@ export async function main(argv){
|
|
|
20
24
|
if(command==='capabilities')return print({package:PACKAGE.name,version:PACKAGE.version,...MANIFEST});
|
|
21
25
|
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;}
|
|
22
26
|
const opts=options(rest);
|
|
27
|
+
if(command==='components'){
|
|
28
|
+
if(action==='status')return print({ok:true,components:componentRecords()});
|
|
29
|
+
if(action==='check')return print(await checkComponents());
|
|
30
|
+
if(action==='install')return print(await installComponents(opts));
|
|
31
|
+
if(action==='register')return print(await registerComponent(opts));
|
|
32
|
+
}
|
|
23
33
|
if(command==='skill'){
|
|
24
|
-
if(action==='source'){const selected=opts.name?[nodeFor(opts.name)]:MANIFEST.nodes;const sources=await Promise.all(selected.map(async n=>({skill:n.skill,node:n.id,source:source(n),sourceDigest:digest(await fileHashes(source(n))),version:
|
|
34
|
+
if(action==='source'){const selected=opts.name?[nodeFor(opts.name)]:MANIFEST.nodes;const sources=await Promise.all(selected.map(async n=>({skill:n.skill,node:n.id,source:source(n),sourceDigest:digest(await fileHashes(source(n))),version:nodeVersion(n)})));return print(opts.name?sources[0]:{package:PACKAGE.name,version:PACKAGE.version,skills:sources});}
|
|
25
35
|
if(action==='status'){const root=targetRoot(opts);return print({package:PACKAGE.name,root,skills:await Promise.all(MANIFEST.nodes.map(n=>status(root,n)))});}
|
|
26
|
-
if(['install','update'].includes(action))return print(await install(opts));
|
|
36
|
+
if(['install','update'].includes(action)){const records=componentRecords();return print(Object.keys(MANIFEST.components).every(id=>records[id])?await install(opts):await installComponents({...opts,yes:true}));}
|
|
27
37
|
}
|
|
28
38
|
if(command==='workflow'&&action==='plan')return print(await plan(opts));
|
|
29
39
|
if(command==='workflow'&&action==='status')return print(await workflowStatus(opts));
|
|
30
40
|
if(command==='handoff'&&action==='create')return print(await createHandoff(opts));
|
|
31
|
-
if(command==='update'&&action==='check')
|
|
41
|
+
if(command==='update'&&action==='check'){const suite=await checkUpdate(opts.tag),components=await checkComponents();print({ok:suite.ok&&components.ok,suite,components});if(!suite.ok||!components.ok)process.exitCode=1;return;}
|
|
32
42
|
if(command==='update'&&action==='install')return print(await installUpdate(opts));
|
|
33
43
|
throw fail('USAGE','Unknown command; run taobao-ai-ops --help');
|
|
34
44
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {randomUUID} from 'node:crypto';
|
|
5
|
+
import {MANIFEST,PACKAGE,stateRoot,componentRecords,fail,stat} from './core.mjs';
|
|
6
|
+
import {install} from './skill-manager.mjs';
|
|
7
|
+
import {npm} from './update.mjs';
|
|
8
|
+
|
|
9
|
+
const config=id=>{const c=MANIFEST.components[id];if(!c)throw fail('UNKNOWN_COMPONENT',id);return c;};
|
|
10
|
+
export async function inspectComponent(id,root){
|
|
11
|
+
root=await fs.realpath(root);
|
|
12
|
+
const c=config(id),p=JSON.parse(await fs.readFile(path.join(root,'package.json'),'utf8'));
|
|
13
|
+
if(p.name!==c.package)throw fail('COMPONENT_IDENTITY_MISMATCH',`${id}: ${p.name}`);
|
|
14
|
+
const entry=path.resolve(root,p.bin?.[c.bin]||'');
|
|
15
|
+
if(!p.bin?.[c.bin]||!entry.startsWith(path.resolve(root)+path.sep))throw fail('INVALID_COMPONENT_ENTRY',id);
|
|
16
|
+
const sources={};
|
|
17
|
+
for(const skill of c.skills){
|
|
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'}});
|
|
19
|
+
if(r.status!==0)throw fail('COMPONENT_SOURCE_FAILED',r.stderr||id);
|
|
20
|
+
const result=JSON.parse(r.stdout);const s=result.source||(result.skills||[]).find(s=>s.skill===skill)?.source;
|
|
21
|
+
if(!s||!path.resolve(s).startsWith(path.resolve(root)+path.sep))throw fail('INVALID_COMPONENT_SOURCE',skill);
|
|
22
|
+
const text=await fs.readFile(path.join(s,'SKILL.md'),'utf8');
|
|
23
|
+
if(!new RegExp(`^name: ${skill}\\s*$`,'m').test(text))throw fail('SKILL_IDENTITY_MISMATCH',skill);
|
|
24
|
+
sources[skill]=s;
|
|
25
|
+
}
|
|
26
|
+
return {package:p.name,version:p.version,root:path.resolve(root),entry,sources};
|
|
27
|
+
}
|
|
28
|
+
async function save(records){
|
|
29
|
+
await fs.mkdir(stateRoot(),{recursive:true});const file=path.join(stateRoot(),'components.json'),tmp=file+'.'+randomUUID()+'.pending';
|
|
30
|
+
await fs.writeFile(tmp,JSON.stringify({schema_version:1,components:records},null,2),{flag:'wx',mode:0o600});await fs.rename(tmp,file);
|
|
31
|
+
}
|
|
32
|
+
async function locked(fn){
|
|
33
|
+
await fs.mkdir(stateRoot(),{recursive:true});const lock=path.join(stateRoot(),'components.lock');
|
|
34
|
+
try{await fs.mkdir(lock);}catch(e){if(e.code==='EEXIST')throw fail('UPDATE_BUSY','Another component operation is running');throw e;}
|
|
35
|
+
try{return await fn();}finally{await fs.rmdir(lock);}
|
|
36
|
+
}
|
|
37
|
+
export async function registerComponent(opts){
|
|
38
|
+
if(!opts.yes)throw fail('AUTHORIZATION_REQUIRED','Development registration requires --yes');
|
|
39
|
+
return locked(async()=>{const old=componentRecords(),record=await inspectComponent(opts.name,path.resolve(opts.path));
|
|
40
|
+
const backup=path.join(stateRoot(),'component-backups',randomUUID());await fs.mkdir(backup,{recursive:true});await fs.writeFile(path.join(backup,'components.json'),JSON.stringify(old),{flag:'wx'});
|
|
41
|
+
const development=opts.mode!=='npm';await save({...old,[opts.name]:{...record,development}});return {ok:true,component:opts.name,...record,development,backup};});
|
|
42
|
+
}
|
|
43
|
+
export async function checkComponents(registry=fetch){
|
|
44
|
+
const installed=componentRecords(),components=[];
|
|
45
|
+
for(const [id,c] of Object.entries(MANIFEST.components)){
|
|
46
|
+
if(installed[id]?.development){components.push({id,package:c.package,current:installed[id].version,updateAvailable:false,development:true,updateOwner:'git'});continue;}
|
|
47
|
+
try{const r=await registry(`https://registry.npmjs.org/${encodeURIComponent(c.package)}`,{signal:AbortSignal.timeout(15000)});
|
|
48
|
+
if(!r.ok)throw Error(`Registry HTTP ${r.status}`);const data=await r.json(),latest=data['dist-tags']?.latest;
|
|
49
|
+
if(!latest||latest.includes('-'))throw Error('No stable latest version');
|
|
50
|
+
components.push({id,package:c.package,current:installed[id]?.version||null,available:latest,updateAvailable:installed[id]?.version!==latest,development:Boolean(installed[id]?.development)});
|
|
51
|
+
}catch(e){components.push({id,package:c.package,current:installed[id]?.version||null,ok:false,code:'REGISTRY_UNAVAILABLE',message:e.message,offline:'keep-current'});}
|
|
52
|
+
}
|
|
53
|
+
return {ok:components.every(c=>c.ok!==false),components};
|
|
54
|
+
}
|
|
55
|
+
export async function installComponents(opts,services={}){
|
|
56
|
+
if(!opts.yes)throw fail('AUTHORIZATION_REQUIRED','Component installation/update requires --yes');
|
|
57
|
+
if(!opts.agent&&!opts.targetDir)throw fail('USAGE','Specify Agent or target directory');
|
|
58
|
+
return locked(async()=>{
|
|
59
|
+
const old=componentRecords(),check=await (services.check||checkComponents)();if(!check.ok)throw fail('REGISTRY_UNAVAILABLE','No changes made; one or more component checks failed',check);
|
|
60
|
+
const updates=check.components.filter(c=>c.updateAvailable&&!c.development);
|
|
61
|
+
if(updates.length===0)return {ok:true,action:'unchanged',check,skills:await install(opts)};
|
|
62
|
+
const generation=path.join(stateRoot(),'component-generations',randomUUID());await fs.mkdir(generation,{recursive:true});
|
|
63
|
+
await fs.writeFile(path.join(generation,'previous-components.json'),JSON.stringify(old),{flag:'wx'});
|
|
64
|
+
const next={...old};
|
|
65
|
+
try{
|
|
66
|
+
await (services.npm||npm)(['install','--prefix',generation,'--no-audit','--no-fund','--ignore-scripts',...updates.map(c=>`${c.package}@${c.available}`)]);
|
|
67
|
+
for(const c of updates){const r=await inspectComponent(c.id,path.join(generation,'node_modules',...c.package.split('/')));if(r.version!==c.available)throw fail('COMPONENT_VERSION_MISMATCH',c.id);next[c.id]=r;}
|
|
68
|
+
await save(next);
|
|
69
|
+
// The Skill manager backs up changed targets and rolls all Skill changes back on failure.
|
|
70
|
+
const skills=await install(opts);return {ok:true,action:'updated',suiteVersion:PACKAGE.version,components:updates,skills,backup:path.join(generation,'previous-components.json')};
|
|
71
|
+
}catch(e){await save(old);throw e;}
|
|
72
|
+
});
|
|
73
|
+
}
|
package/src/core.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import {fileURLToPath} from 'node:url';
|
|
5
5
|
import {createHash} from 'node:crypto';
|
|
6
|
+
import {readFileSync,existsSync} from 'node:fs';
|
|
6
7
|
export const ROOT=fileURLToPath(new URL('../',import.meta.url));
|
|
7
8
|
export const PACKAGE=JSON.parse(await fs.readFile(path.join(ROOT,'package.json'),'utf8'));
|
|
8
9
|
export const MANIFEST=JSON.parse(await fs.readFile(path.join(ROOT,'suite-manifest.json'),'utf8'));
|
|
@@ -11,7 +12,10 @@ export const json=p=>fs.readFile(p,'utf8').then(JSON.parse);
|
|
|
11
12
|
export const stat=p=>fs.lstat(p).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
|
|
12
13
|
export const hash=data=>createHash('sha256').update(data).digest('hex');
|
|
13
14
|
export function nodeFor(id){const n=MANIFEST.nodes.find(n=>n.id===id||n.skill===id);if(!n)throw fail('UNKNOWN_NODE',`Unknown node: ${id}`);return n;}
|
|
14
|
-
export const
|
|
15
|
+
export function componentRecords(){const file=path.join(stateRoot(),'components.json');return existsSync(file)?JSON.parse(readFileSync(file,'utf8')).components||{}:{};}
|
|
16
|
+
export function componentRecord(id){const r=componentRecords()[id];if(!r)throw fail('COMPONENT_NOT_INSTALLED',`Install registered component ${id} with components install`);return r;}
|
|
17
|
+
export const source=n=>n.component?componentRecord(n.component).sources[n.skill]||(()=>{throw fail('SKILL_SOURCE_MISSING',n.skill);})():path.join(ROOT,'skills',n.skill);
|
|
18
|
+
export const nodeVersion=n=>n.component?componentRecord(n.component).version:PACKAGE.version;
|
|
15
19
|
export function stateRoot(){return process.env.TAOBAO_AI_OPS_STATE_DIR||path.join(os.homedir(),'.local','state','taobao-ai-ops');}
|
|
16
20
|
export async function fileHashes(root,rel=''){
|
|
17
21
|
const out={};
|
package/src/runtime.mjs
CHANGED
|
@@ -3,8 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import {spawnSync} from 'node:child_process';
|
|
5
5
|
import {createRequire} from 'node:module';
|
|
6
|
-
import {ROOT,MANIFEST,PACKAGE,nodeFor,source,fail} from './core.mjs';
|
|
7
|
-
const require=createRequire(import.meta.url);
|
|
6
|
+
import {ROOT,MANIFEST,PACKAGE,nodeFor,source,componentRecord,fail} from './core.mjs';
|
|
8
7
|
export function pythonEnvironment(env=process.env){
|
|
9
8
|
return {...env,PYTHONUTF8:'1',PYTHONIOENCODING:'utf-8',PYTHONDONTWRITEBYTECODE:'1'};
|
|
10
9
|
}
|
|
@@ -21,25 +20,21 @@ export function resolvePython(imports,candidates=pythonCandidates()){
|
|
|
21
20
|
export function toolEntry(id){
|
|
22
21
|
const tool=MANIFEST.tools[id];if(!tool)throw fail('UNKNOWN_TOOL',`Unknown tool ${id}`);
|
|
23
22
|
if(tool.external)return {command:process.env[tool.executable_env]||id,args:[],external:true};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if(!packageRoot)throw fail('CAPABILITY_UNAVAILABLE',`Cannot resolve ${tool.package}`);
|
|
28
|
-
const entry=path.join(packageRoot,tool.entry);
|
|
29
|
-
if(!fs.existsSync(entry))throw fail('CAPABILITY_UNAVAILABLE',`Missing ${id} entry`,{entry});
|
|
30
|
-
return {command:process.execPath,args:[entry],version:JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'),'utf8')).version};
|
|
23
|
+
const managed=componentRecord(tool.component);
|
|
24
|
+
if(!fs.existsSync(managed.entry))throw fail('CAPABILITY_UNAVAILABLE',`Missing ${id} entry`);
|
|
25
|
+
return {command:process.execPath,args:[managed.entry],version:managed.version};
|
|
31
26
|
}
|
|
32
27
|
export function runScript(id,name,args){
|
|
33
|
-
const n=nodeFor(id);if(!n.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const r=spawnSync(
|
|
28
|
+
const n=nodeFor(id);if(!n.component||!/^[a-z][a-z0-9_]*\.(py|mjs)$/.test(name))throw fail('SCRIPT_NOT_ALLOWED',`Invalid component script ${name}`);
|
|
29
|
+
// The independent CLI owns its current allowlist and runtime; new component scripts need no shell release.
|
|
30
|
+
const record=componentRecord(n.component);
|
|
31
|
+
const r=spawnSync(process.execPath,[record.entry,'script',name,...args],{stdio:'inherit',env:pythonEnvironment()});
|
|
37
32
|
if(r.error)throw fail('EXECUTION_FAILED',r.error.message);process.exitCode=r.status??1;
|
|
38
33
|
}
|
|
39
34
|
export async function doctor(id,mode='excel'){
|
|
40
35
|
const n=nodeFor(id||'operations'),checks=[];
|
|
41
36
|
checks.push({id:'node',ok:Number(process.versions.node.split('.')[0])>=20,value:process.version});
|
|
42
|
-
checks.push({id:'skill',ok:fs.existsSync(path.join(source(n),'SKILL.md'))});
|
|
37
|
+
try{checks.push({id:'skill',ok:fs.existsSync(path.join(source(n),'SKILL.md'))});}catch(e){return {ok:false,node:n.id,checks:[...checks,{id:'component',ok:false,code:e.code,message:e.message}],native_requirements:[]};}
|
|
43
38
|
if(n.id==='operations'){
|
|
44
39
|
const children=await Promise.all(n.requires_skills.map(skill=>doctor(skill,mode)));
|
|
45
40
|
return {ok:checks.every(c=>c.ok)&&children.every(c=>c.ok),package:PACKAGE.name,version:PACKAGE.version,node:n.id,checks,children,native_requirements:children.flatMap(c=>c.native_requirements)};
|
|
@@ -47,7 +42,8 @@ export async function doctor(id,mode='excel'){
|
|
|
47
42
|
if(n.python_imports.length){const python=resolvePython(n.python_imports);checks.push({id:'python',ok:Boolean(python),value:python,required_imports:n.python_imports});}
|
|
48
43
|
if(n.id==='category-research')checks.push({id:'report-runtime',ok:['assets/report-template.html','assets/chart-views.js'].every(p=>fs.existsSync(path.join(source(n),p)))});
|
|
49
44
|
if(n.id==='product-research'){
|
|
50
|
-
|
|
45
|
+
const componentRequire=createRequire(path.join(componentRecord(n.component).root,'package.json'));
|
|
46
|
+
try{checks.push({id:'exceljs',ok:Boolean(componentRequire.resolve('@excel.js/exceljs'))});}catch(e){checks.push({id:'exceljs',ok:false,message:e.message});}
|
|
51
47
|
checks.push({id:'report-runtime',ok:['report_runtime/renderer.py','report_runtime/validator.py','report_runtime/templates/workbench.html'].every(p=>fs.existsSync(path.join(source(n),p)))});
|
|
52
48
|
}
|
|
53
49
|
if(!['excel','nas'].includes(mode))throw fail('USAGE','Doctor mode must be excel or nas');
|
package/src/skill-manager.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import {randomUUID} from 'node:crypto';
|
|
5
|
-
import {PACKAGE,source,closure,json,stat,fileHashes,digest,fail,stateRoot} from './core.mjs';
|
|
5
|
+
import {PACKAGE,source,nodeVersion,closure,json,stat,fileHashes,digest,fail,stateRoot} from './core.mjs';
|
|
6
6
|
|
|
7
7
|
const OWNER='.taobao-ai-ops-managed.json';
|
|
8
8
|
export function targetRoot(opts){
|
|
@@ -22,7 +22,7 @@ export function targetRoot(opts){
|
|
|
22
22
|
import {existsSync as osExists} from 'node:fs';
|
|
23
23
|
async function owners(root){const p=path.join(root,OWNER);if(!await stat(p))return {package:PACKAGE.name,skills:{}};const value=await json(p);if(value.package!==PACKAGE.name)throw fail('OWNERSHIP_CONFLICT','Suite ownership file belongs to another package');return value;}
|
|
24
24
|
export async function status(root,n){
|
|
25
|
-
const dest=path.join(root,n.skill),files=await fileHashes(source(n));const base={skill:n.skill,node:n.id,source:source(n),destination:dest,sourceDigest:digest(files),version:
|
|
25
|
+
const dest=path.join(root,n.skill),files=await fileHashes(source(n));const base={skill:n.skill,node:n.id,source:source(n),destination:dest,sourceDigest:digest(files),version:nodeVersion(n)};
|
|
26
26
|
const s=await stat(dest);if(!s)return {...base,state:'absent',managed:false};
|
|
27
27
|
const own=(await owners(root)).skills[n.skill];
|
|
28
28
|
if(s.isSymbolicLink()){
|
|
@@ -40,7 +40,7 @@ export async function status(root,n){
|
|
|
40
40
|
async function makeCopy(dest,n){
|
|
41
41
|
const files=await fileHashes(source(n));await fs.mkdir(dest,{recursive:true});
|
|
42
42
|
for(const relative of Object.keys(files)){const d=path.join(dest,relative);await fs.mkdir(path.dirname(d),{recursive:true});await fs.copyFile(path.join(source(n),relative),d);}
|
|
43
|
-
await fs.writeFile(path.join(dest,'.suite-install.json'),JSON.stringify({package:PACKAGE.name,skill:n.skill,version:
|
|
43
|
+
await fs.writeFile(path.join(dest,'.suite-install.json'),JSON.stringify({package:PACKAGE.name,skill:n.skill,version:nodeVersion(n),sourceDigest:digest(files),files}),{flag:'wx'});
|
|
44
44
|
await fs.writeFile(path.join(dest,'.install-meta.json'),JSON.stringify({title:n.skill,description:`Taobao AI operations: ${n.id}`}),{flag:'wx'});
|
|
45
45
|
}
|
|
46
46
|
async function restore(backup,dest,inPlace){
|
|
@@ -72,7 +72,7 @@ export async function install(opts,options={}){
|
|
|
72
72
|
if(mode==='link')await fs.symlink(source(n),dest,process.platform==='win32'?'junction':'dir');else await makeCopy(dest,n);
|
|
73
73
|
if(mode==='copy'&&record.backup){const ui=path.join(record.backup,'.install-meta.json');if(await stat(ui))await fs.copyFile(ui,path.join(dest,'.install-meta.json'));}
|
|
74
74
|
const after=await status(root,n);if(after.state!=='current')throw fail('INSTALL_VERIFY_FAILED',`Skill verification failed: ${n.skill}`);
|
|
75
|
-
old.skills[n.skill]={source:source(n),version:
|
|
75
|
+
old.skills[n.skill]={source:source(n),version:nodeVersion(n),digest:after.sourceDigest,mode,backup:record.backup};
|
|
76
76
|
results.push({...after,action:item.state==='absent'?'installed':item.managed?'updated':'adopted',backup:record.backup,strategy:inPlace?'in-place':'rename'});
|
|
77
77
|
if(options.failAfter===changed.length)throw fail('TEST_INJECTED_FAILURE','Injected migration failure');
|
|
78
78
|
}
|
package/src/update.mjs
CHANGED
|
@@ -3,12 +3,12 @@ import path from 'node:path';
|
|
|
3
3
|
import {spawnSync} from 'node:child_process';
|
|
4
4
|
import {existsSync,realpathSync} from 'node:fs';
|
|
5
5
|
import {ROOT,PACKAGE,fail,stateRoot} from './core.mjs';
|
|
6
|
-
export async function checkUpdate(tag='
|
|
6
|
+
export async function checkUpdate(tag='latest'){
|
|
7
7
|
if(!['latest','next'].includes(tag))throw fail('USAGE','Tag must be latest or next');
|
|
8
8
|
try{const r=await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE.name)}`,{signal:AbortSignal.timeout(15000)});if(r.status===404)return {ok:true,published:false,current:PACKAGE.version,updateAvailable:false,tag};if(!r.ok)throw Error(`Registry HTTP ${r.status}`);const p=await r.json();const version=p['dist-tags']?.[tag];return {ok:true,published:true,current:PACKAGE.version,available:version,updateAvailable:Boolean(version&&version!==PACKAGE.version),tag};}
|
|
9
9
|
catch(e){return {ok:false,code:'REGISTRY_UNAVAILABLE',current:PACKAGE.version,updateAvailable:false,message:e.message,offline:'keep-current'};}
|
|
10
10
|
}
|
|
11
|
-
function npm(args){
|
|
11
|
+
export function npm(args){
|
|
12
12
|
let cli=process.env.npm_execpath;
|
|
13
13
|
if(!cli||!cli.endsWith('npm-cli.js')){
|
|
14
14
|
const found=spawnSync(process.platform==='win32'?'where':'which',[process.platform==='win32'?'npm.cmd':'npm'],{encoding:'utf8',timeout:10000});
|
|
@@ -23,7 +23,7 @@ export async function installUpdate(opts){
|
|
|
23
23
|
if(!opts.agent&&!opts.targetDir)throw fail('USAGE','Specify --agent or --target-dir before updating');
|
|
24
24
|
const nodeModules=path.dirname(path.dirname(ROOT.slice(0,-1)));
|
|
25
25
|
if(path.basename(nodeModules)!=='node_modules'||(process.platform!=='win32'&&path.basename(path.dirname(nodeModules))!=='lib'))throw fail('SOURCE_CHECKOUT','This source or local test install updates through its owner, not global npm');
|
|
26
|
-
const check=await checkUpdate(opts.tag||'
|
|
26
|
+
const check=await checkUpdate(opts.tag||'latest');if(!check.ok)throw fail(check.code,check.message);if(!check.updateAvailable)return {...check,components:await (await import('./components.mjs')).installComponents(opts)};
|
|
27
27
|
// Persist the installed version as a local archive for recovery, including unpublished prerequisites.
|
|
28
28
|
const backup=await fs.mkdtemp(path.join(await fs.mkdir(path.join(stateRoot(),'package-backups'),{recursive:true}).then(()=>path.join(stateRoot(),'package-backups')),'update-'));
|
|
29
29
|
const packed=JSON.parse(npm(['pack',ROOT,'--ignore-scripts','--pack-destination',backup,'--json']))[0];
|
|
@@ -31,8 +31,8 @@ export async function installUpdate(opts){
|
|
|
31
31
|
try{
|
|
32
32
|
npm(['install','--global','--prefix',prefix,'--ignore-scripts',`${PACKAGE.name}@${check.available}`]);
|
|
33
33
|
const fresh=path.join(nodeModules,...PACKAGE.name.split('/'),'bin','taobao-ai-ops.mjs');
|
|
34
|
-
const args=['
|
|
35
|
-
const r=spawnSync(process.execPath,[fresh,...args],{encoding:'utf8',timeout:
|
|
34
|
+
const args=['components','install','--yes','--json'];if(opts.targetDir)args.push('--target-dir',opts.targetDir);else if(opts.agent)args.push('--agent',opts.agent);else throw fail('USAGE','Specify the Agent or custom target for managed Skill synchronization');
|
|
35
|
+
const r=spawnSync(process.execPath,[fresh,...args],{encoding:'utf8',timeout:300000});if(r.status!==0)throw fail('SKILL_SYNC_FAILED',r.stderr||r.error?.message||'Skill synchronization failed');
|
|
36
36
|
const v=spawnSync(process.execPath,[fresh,'version'],{encoding:'utf8',timeout:10000});if(v.stdout.trim()!==check.available)throw fail('UPDATE_VERIFY_FAILED','Installed version differs from registry selection');
|
|
37
37
|
return {ok:true,from:PACKAGE.version,to:check.available,backup:path.join(backup,packed.filename),skills:JSON.parse(r.stdout)};
|
|
38
38
|
}catch(e){npm(['install','--global','--prefix',prefix,'--ignore-scripts',path.join(backup,packed.filename)]);const restored=path.join(nodeModules,...PACKAGE.name.split('/'),'bin','taobao-ai-ops.mjs');const args=['skill','update','--json',...(opts.targetDir?['--target-dir',opts.targetDir]:['--agent',opts.agent])];const synced=spawnSync(process.execPath,[restored,...args],{encoding:'utf8',timeout:60000});if(synced.status!==0)throw fail('ROLLBACK_SYNC_REQUIRED','Old package restored; managed Skill recovery needs attention',{original_error:e.message,backup:path.join(backup,packed.filename)});throw e;}
|
package/src/workflow.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {randomUUID} from 'node:crypto';
|
|
4
|
-
import {ROOT,PACKAGE,MANIFEST,closure,nodeFor,hash,json,stat,fail} from './core.mjs';
|
|
4
|
+
import {ROOT,PACKAGE,MANIFEST,closure,nodeFor,nodeVersion,hash,json,stat,fail} from './core.mjs';
|
|
5
5
|
export async function plan(opts){
|
|
6
6
|
if(!opts.runDir)throw fail('USAGE','--run-dir is required');
|
|
7
7
|
const root=path.resolve(opts.runDir),dest=path.join(root,'taobao-ai-ops-run.json');
|
|
8
8
|
if(await stat(dest))throw fail('OUTPUT_EXISTS','Run already exists; use workflow status');
|
|
9
|
-
const nodes=closure(opts).filter(n=>n.id!=='operations');
|
|
9
|
+
const nodes=closure(opts).filter(n=>n.id!=='operations'&&n.stage!=='tools');
|
|
10
10
|
const inputs=[];for(const p of [].concat(opts.input||[])){inputs.push({path:path.resolve(p),sha256:hash(await fs.readFile(p))});}
|
|
11
|
-
const value={schema_version:1,run_id:randomUUID(),package:PACKAGE.name,package_version:PACKAGE.version,manifest_sha256:hash(await fs.readFile(path.join(ROOT,'suite-manifest.json'))),created_at:new Date().toISOString(),inputs,nodes:nodes.map(n=>({node_id:n.id,method_version:n.method_version,status:'planned',artifacts:[],verified:false})),context:opts.context||null,requirement:'Each node loads its complete Skill and executes its own QA; planning does not execute business work.'};
|
|
11
|
+
const value={schema_version:1,run_id:randomUUID(),package:PACKAGE.name,package_version:PACKAGE.version,manifest_sha256:hash(await fs.readFile(path.join(ROOT,'suite-manifest.json'))),created_at:new Date().toISOString(),inputs,nodes:nodes.map(n=>({node_id:n.id,component:n.component,component_version:nodeVersion(n),method_version:n.method_version,status:'planned',artifacts:[],verified:false})),context:opts.context||null,requirement:'Each node loads its complete Skill and executes its own QA; planning does not execute business work.'};
|
|
12
12
|
await fs.mkdir(root,{recursive:true});await fs.writeFile(dest,JSON.stringify(value,null,2),{flag:'wx'});return {ok:true,ledger:dest,...value};
|
|
13
13
|
}
|
|
14
14
|
export async function workflowStatus(opts){if(!opts.runDir)throw fail('USAGE','--run-dir is required');return json(path.join(path.resolve(opts.runDir),'taobao-ai-ops-run.json'));}
|
package/suite-manifest.json
CHANGED
|
@@ -1,17 +1,170 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema_version":
|
|
2
|
+
"schema_version": 2,
|
|
3
3
|
"package": "@petercjl/taobao-ai-ops",
|
|
4
|
-
"profiles": {
|
|
4
|
+
"profiles": {
|
|
5
|
+
"research": [
|
|
6
|
+
"operations",
|
|
7
|
+
"category-research",
|
|
8
|
+
"product-research",
|
|
9
|
+
"tbcli-tools",
|
|
10
|
+
"sycmcli-tools"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
5
13
|
"nodes": [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
{
|
|
15
|
+
"id": "operations",
|
|
16
|
+
"skill": "taobao-ai-operations",
|
|
17
|
+
"stage": "routing",
|
|
18
|
+
"method_version": "1.0",
|
|
19
|
+
"requires_skills": [
|
|
20
|
+
"yuce-category-opportunity-report",
|
|
21
|
+
"taobao-search-product-form"
|
|
22
|
+
],
|
|
23
|
+
"python_imports": [],
|
|
24
|
+
"scripts": [],
|
|
25
|
+
"native_requirements": []
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "category-research",
|
|
29
|
+
"skill": "yuce-category-opportunity-report",
|
|
30
|
+
"stage": "discover-market",
|
|
31
|
+
"method_version": "yuce-category-opportunity@2.0",
|
|
32
|
+
"requires_skills": [],
|
|
33
|
+
"python_imports": [
|
|
34
|
+
"openpyxl"
|
|
35
|
+
],
|
|
36
|
+
"scripts": [
|
|
37
|
+
"build_report.py"
|
|
38
|
+
],
|
|
39
|
+
"native_requirements": [],
|
|
40
|
+
"optional_tools": [
|
|
41
|
+
"tbcli",
|
|
42
|
+
"yccli"
|
|
43
|
+
],
|
|
44
|
+
"component": "category-research"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "product-research",
|
|
48
|
+
"skill": "taobao-search-product-form",
|
|
49
|
+
"stage": "research-product",
|
|
50
|
+
"method_version": "taobao-search-product-form@0.1.1",
|
|
51
|
+
"requires_skills": [],
|
|
52
|
+
"python_imports": [
|
|
53
|
+
"pandas",
|
|
54
|
+
"numpy",
|
|
55
|
+
"openpyxl",
|
|
56
|
+
"PIL",
|
|
57
|
+
"jieba"
|
|
58
|
+
],
|
|
59
|
+
"native_requirements": [
|
|
60
|
+
"image.observe"
|
|
61
|
+
],
|
|
62
|
+
"scripts": [
|
|
63
|
+
"clean_search_export.mjs",
|
|
64
|
+
"prepare.py",
|
|
65
|
+
"analyze.py",
|
|
66
|
+
"analyze_advertisers.py",
|
|
67
|
+
"analyze_geography.py",
|
|
68
|
+
"analyze_price_bands.py",
|
|
69
|
+
"analyze_title_roots.py",
|
|
70
|
+
"assemble_opportunity_evidence.py",
|
|
71
|
+
"build_report_viewmodel.py",
|
|
72
|
+
"build_visual_review_viewmodel.py",
|
|
73
|
+
"cluster_visual_forms.py",
|
|
74
|
+
"compile_style_prototypes.py",
|
|
75
|
+
"compile_visual_review.py",
|
|
76
|
+
"compile_visual_selection.py",
|
|
77
|
+
"render_report.py",
|
|
78
|
+
"validate_opportunity_cards.py",
|
|
79
|
+
"validate_report_viewmodel.py",
|
|
80
|
+
"visual_form_pipeline.py",
|
|
81
|
+
"visual_observation_batches.py",
|
|
82
|
+
"workflow_ledger.py"
|
|
83
|
+
],
|
|
84
|
+
"component": "product-research"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "tbcli-tools",
|
|
88
|
+
"skill": "tbcli",
|
|
89
|
+
"component": "tbcli",
|
|
90
|
+
"stage": "tools",
|
|
91
|
+
"requires_skills": [],
|
|
92
|
+
"python_imports": [],
|
|
93
|
+
"scripts": [],
|
|
94
|
+
"native_requirements": []
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "sycmcli-tools",
|
|
98
|
+
"skill": "sycmcli",
|
|
99
|
+
"component": "sycmcli",
|
|
100
|
+
"stage": "tools",
|
|
101
|
+
"requires_skills": [],
|
|
102
|
+
"python_imports": [],
|
|
103
|
+
"scripts": [],
|
|
104
|
+
"native_requirements": []
|
|
105
|
+
}
|
|
9
106
|
],
|
|
10
107
|
"tools": {
|
|
11
|
-
"tbcli": {
|
|
12
|
-
|
|
13
|
-
|
|
108
|
+
"tbcli": {
|
|
109
|
+
"package": "@petercjl/tbcli",
|
|
110
|
+
"entry": "scripts/tbcli.mjs",
|
|
111
|
+
"role": "existing monthly exports, warehouse and additive category import",
|
|
112
|
+
"component": "tbcli"
|
|
113
|
+
},
|
|
114
|
+
"sycmcli": {
|
|
115
|
+
"package": "@petercjl/sycmcli",
|
|
116
|
+
"entry": "scripts/sycmcli.mjs",
|
|
117
|
+
"role": "store-bound market, category, keyword and competitor read operations",
|
|
118
|
+
"component": "sycmcli"
|
|
119
|
+
},
|
|
120
|
+
"yccli": {
|
|
121
|
+
"external": true,
|
|
122
|
+
"minimum_version": "0.5.0",
|
|
123
|
+
"executable_env": "TAOBAO_AI_OPS_YCCLI",
|
|
124
|
+
"role": "authorized Yuce monthly collection; optional for supplied Excel"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"targets": {
|
|
128
|
+
"codex:macos": "implemented",
|
|
129
|
+
"sealseek:macos": "implemented",
|
|
130
|
+
"sealseek:windows": "implemented"
|
|
131
|
+
},
|
|
132
|
+
"update_policy": {
|
|
133
|
+
"owner": "shell",
|
|
134
|
+
"channel": "latest",
|
|
135
|
+
"during_run": "installed-component-set",
|
|
136
|
+
"automatic_install": false,
|
|
137
|
+
"offline": "keep-current",
|
|
138
|
+
"source_checkout": "git-managed"
|
|
14
139
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
140
|
+
"components": {
|
|
141
|
+
"category-research": {
|
|
142
|
+
"package": "@petercjl/yuce-category-opportunity-report",
|
|
143
|
+
"bin": "yuce-category-research",
|
|
144
|
+
"skills": [
|
|
145
|
+
"yuce-category-opportunity-report"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"product-research": {
|
|
149
|
+
"package": "@petercjl/taobao-search-product-form",
|
|
150
|
+
"bin": "taobao-search-form",
|
|
151
|
+
"skills": [
|
|
152
|
+
"taobao-search-product-form"
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"tbcli": {
|
|
156
|
+
"package": "@petercjl/tbcli",
|
|
157
|
+
"bin": "tbcli",
|
|
158
|
+
"skills": [
|
|
159
|
+
"tbcli"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"sycmcli": {
|
|
163
|
+
"package": "@petercjl/sycmcli",
|
|
164
|
+
"bin": "sycmcli",
|
|
165
|
+
"skills": [
|
|
166
|
+
"sycmcli"
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
17
170
|
}
|