@petercjl/taobao-ai-ops 0.1.0 → 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.
Files changed (76) hide show
  1. package/README.md +29 -22
  2. package/package.json +2 -7
  3. package/skills/taobao-ai-operations/SKILL.md +5 -1
  4. package/src/cli.mjs +16 -6
  5. package/src/components.mjs +73 -0
  6. package/src/core.mjs +5 -1
  7. package/src/runtime.mjs +11 -15
  8. package/src/skill-manager.mjs +4 -4
  9. package/src/update.mjs +5 -5
  10. package/src/workflow.mjs +3 -3
  11. package/suite-manifest.json +163 -10
  12. package/skills/taobao-search-product-form/SKILL.md +0 -74
  13. package/skills/taobao-search-product-form/adapters/codex.json +0 -139
  14. package/skills/taobao-search-product-form/adapters/sealseek.json +0 -24
  15. package/skills/taobao-search-product-form/agents/openai.yaml +0 -3
  16. package/skills/taobao-search-product-form/capabilities.json +0 -148
  17. package/skills/taobao-search-product-form/references/SCHEMA.md +0 -3
  18. package/skills/taobao-search-product-form/references/index.md +0 -10
  19. package/skills/taobao-search-product-form/references/log.md +0 -35
  20. package/skills/taobao-search-product-form/references/queries/evaluate-white-label-forms.md +0 -3
  21. package/skills/taobao-search-product-form/references/queries/identify-advertisers.md +0 -5
  22. package/skills/taobao-search-product-form/references/queries/identify-geography.md +0 -5
  23. package/skills/taobao-search-product-form/references/queries/identify-price-bands.md +0 -3
  24. package/skills/taobao-search-product-form/references/queries/identify-product-forms.md +0 -3
  25. package/skills/taobao-search-product-form/references/queries/identify-title-roots.md +0 -5
  26. package/skills/taobao-search-product-form/references/report-output.md +0 -23
  27. package/skills/taobao-search-product-form/references/source-manifest.md +0 -37
  28. package/skills/taobao-search-product-form/references/title-lexicon.json +0 -55
  29. package/skills/taobao-search-product-form/references/topics/advertiser-analysis-method.md +0 -43
  30. package/skills/taobao-search-product-form/references/topics/geography-analysis-method.md +0 -23
  31. package/skills/taobao-search-product-form/references/topics/price-band-analysis-method.md +0 -27
  32. package/skills/taobao-search-product-form/references/topics/title-root-analysis-method.md +0 -37
  33. package/skills/taobao-search-product-form/references/topics/visual-form-method.md +0 -31
  34. package/skills/taobao-search-product-form/references/topics/white-label-evidence.md +0 -38
  35. package/skills/taobao-search-product-form/references/workflow-ledger.md +0 -28
  36. package/skills/taobao-search-product-form/report_runtime/VERSION +0 -1
  37. package/skills/taobao-search-product-form/report_runtime/renderer.py +0 -485
  38. package/skills/taobao-search-product-form/report_runtime/templates/workbench.html +0 -67
  39. package/skills/taobao-search-product-form/report_runtime/validator.py +0 -117
  40. package/skills/taobao-search-product-form/scripts/analyze.py +0 -119
  41. package/skills/taobao-search-product-form/scripts/analyze_advertisers.py +0 -352
  42. package/skills/taobao-search-product-form/scripts/analyze_geography.py +0 -316
  43. package/skills/taobao-search-product-form/scripts/analyze_price_bands.py +0 -257
  44. package/skills/taobao-search-product-form/scripts/analyze_title_roots.py +0 -327
  45. package/skills/taobao-search-product-form/scripts/assemble_opportunity_evidence.py +0 -322
  46. package/skills/taobao-search-product-form/scripts/build_report_viewmodel.py +0 -822
  47. package/skills/taobao-search-product-form/scripts/build_visual_review_viewmodel.py +0 -177
  48. package/skills/taobao-search-product-form/scripts/clean_search_export.mjs +0 -61
  49. package/skills/taobao-search-product-form/scripts/cluster_visual_forms.py +0 -193
  50. package/skills/taobao-search-product-form/scripts/compile_style_prototypes.py +0 -115
  51. package/skills/taobao-search-product-form/scripts/compile_visual_review.py +0 -60
  52. package/skills/taobao-search-product-form/scripts/compile_visual_selection.py +0 -148
  53. package/skills/taobao-search-product-form/scripts/prepare.py +0 -180
  54. package/skills/taobao-search-product-form/scripts/render_report.py +0 -31
  55. package/skills/taobao-search-product-form/scripts/validate_opportunity_cards.py +0 -84
  56. package/skills/taobao-search-product-form/scripts/validate_report_viewmodel.py +0 -152
  57. package/skills/taobao-search-product-form/scripts/visual_form_pipeline.py +0 -247
  58. package/skills/taobao-search-product-form/scripts/visual_observation_batches.py +0 -117
  59. package/skills/taobao-search-product-form/scripts/workflow_ledger.py +0 -177
  60. package/skills/yuce-category-opportunity-report/SKILL.md +0 -151
  61. package/skills/yuce-category-opportunity-report/adapters/codex.json +0 -113
  62. package/skills/yuce-category-opportunity-report/adapters/sealseek.json +0 -39
  63. package/skills/yuce-category-opportunity-report/agents/openai.yaml +0 -3
  64. package/skills/yuce-category-opportunity-report/assets/chart-views.js +0 -80
  65. package/skills/yuce-category-opportunity-report/assets/package.json +0 -1
  66. package/skills/yuce-category-opportunity-report/assets/report-template.html +0 -62
  67. package/skills/yuce-category-opportunity-report/capabilities.json +0 -48
  68. package/skills/yuce-category-opportunity-report/references/SCHEMA.md +0 -5
  69. package/skills/yuce-category-opportunity-report/references/data-contract.md +0 -40
  70. package/skills/yuce-category-opportunity-report/references/index.md +0 -6
  71. package/skills/yuce-category-opportunity-report/references/log.md +0 -29
  72. package/skills/yuce-category-opportunity-report/references/queries/assess-category-opportunity.md +0 -7
  73. package/skills/yuce-category-opportunity-report/references/source-manifest.md +0 -14
  74. package/skills/yuce-category-opportunity-report/references/topics/category-opportunity-method.md +0 -39
  75. package/skills/yuce-category-opportunity-report/scripts/build_report.py +0 -779
  76. 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
- 面向淘宝电商工作者的独立 SOP Skill 套件。当前版本包含运营路由、预策类目机会研究和淘宝搜索商品形态研究。类目研究支持本地 Excel 与授权 NAS/预策取数;商品研究接受搜索结果表。每个节点独立执行,报告保留其原有方法和完整证据。
3
+ 面向员工的统一管理入口。研究 Skill 和工具插件各自拥有独立 npm 包、源码和发布版本;套件只维护组件清单、安装更新、Skill发现与业务路由。
4
4
 
5
- ```sh
6
- taobao-ai-ops nodes list --json
7
- taobao-ai-ops doctor --node category-research --mode excel --json
8
- taobao-ai-ops doctor --node product-research --json
9
- taobao-ai-ops skill source --name yuce-category-opportunity-report --json
10
- taobao-ai-ops skill install --agent codex --profile research --json
11
- ```
12
-
13
- 正式版本使用默认 `latest` 通道;跨 Agent 的完整验收范围见下文。安装后按需运行 `skill install`,服务访问仍需单独配置。
5
+ ## 安装与更新
14
6
 
15
7
  ```sh
16
8
  npm install --global @petercjl/taobao-ai-ops
17
- taobao-ai-ops version
18
- taobao-ai-ops skill install --agent codex --profile research --json
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
19
13
  ```
20
14
 
21
- 开发时用 `node bin/taobao-ai-ops.mjs` 执行。Node >=20,Python 依赖见 requirements.txt,可运行 `python -m pip install -r requirements.txt`;可通过 TAOBAO_AI_OPS_PYTHON 指定稳定解释器。CLI 也发现 Agent 管理的 Python 和已有商品研究环境。已有独立 Skill 先检查 `skill status`;明确授权迁移后使用 `--adopt`,CLI 会保留恢复点。
15
+ SealSeek 用户改用 --agent sealseek。目标有歧义时通过 SEALSEEK_SKILLS_DIR 指定。已有非套件管理的 Skill 会阻止覆盖;明确授权迁移后用 --adopt,保留完整备份。本地修改会阻止更新。
16
+
17
+ 首次安装会获取清单中全部独立组件的正式 latest。统一更新检查套件及每个组件:套件版本不变也会更新组件。已安装组件的 Skill 同步可用 skill update;该命令只同步本地安装内容,不查询注册表。日常业务调用不更新环境,任务中保持已安装版本。
18
+
19
+ 组件在用户状态目录中独立安装,不随套件自身重新安装而被删除。更新先准备新安装目录,核对包身份、版本与 CLI 返回的 Skill 来源,再同步受管理 Skill。npm、来源验证或 Skill 同步失败时保留旧组件记录;Skill 同步有完整备份和恢复机制。离线检查失败时报告并保留当前环境。
22
20
 
23
- 新安装默认在 macOS 使用链接、Windows 使用受管理副本。已有 Skill 迁移使用 `--adopt`,完整备份并校验;受管理本地修改会阻止覆盖。SealSeek 根目录可用 SEALSEEK_SKILLS_DIR 指定,发现歧义时会明确报错。
21
+ ## 组件
22
+
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
- tbcli 0.10.4 与 sycmcli 0.3.1 是固定版本依赖,通过套件解析的实际入口调用,执行时关闭其独立自动更新。yccli 是按需发现的外部工具,可由 TAOBAO_AI_OPS_YCCLI 指定。Excel 分析不要求 NAS、VPN 或平台登录。服务账号和业务数据由使用者提供,包内不含凭证。
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
- 套件手动检查更新,运行期间使用固定版本;开发源码通过 Git 更新。已发布全局安装可用 `update install --agent AGENT --yes` 更新同一 npm prefix 的包与受管理 Skill,失败恢复旧包。安装、更新不发布商品或修改广告。
43
+ Node >=20。Python 研究依赖通过稳定解释器准备;TAOBAO_AI_OPS_PYTHON 可指定已配置解释器。脚本和报告模板来自实际组件目录。原生图片理解由当前 Agent 提供;缺失时报告而非缩减流程。
37
44
 
38
- 目前合并范围是研究链第一批。供应链、内容制作、经营复盘按后续阶段接入。来源记录在 suite-manifest.json;旧独立包仍保留作历史与过渡,后续维护以本套件的迁入节点为准。
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.0",
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",
@@ -37,10 +37,5 @@
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
- Bundled node 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 suite selects its fixed tool entry. Accounts, warehouse access and write permissions remain external. The category child's database-consent and additive-import boundaries apply in its NAS route.
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 codex|sealseek|agents [--profile research] [--adopt] [--mode link|copy]\nscript 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 next|latest] [--agent AGENT --yes]`);
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:PACKAGE.version})));return print(opts.name?sources[0]:{package:PACKAGE.name,version:PACKAGE.version,skills:sources});}
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')return print(await checkUpdate(opts.tag));
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 source=n=>path.join(ROOT,'skills',n.skill);
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
- let packageRoot;
25
- try{packageRoot=path.dirname(require.resolve(`${tool.package}/package.json`));}
26
- catch{let dir=path.dirname(require.resolve(tool.package));while(dir!==path.dirname(dir)){const p=path.join(dir,'package.json');if(fs.existsSync(p)&&JSON.parse(fs.readFileSync(p,'utf8')).name===tool.package){packageRoot=dir;break;}dir=path.dirname(dir);}}
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.scripts.includes(name))throw fail('SCRIPT_NOT_ALLOWED',`Script ${name} is not exposed by ${n.id}`);
34
- const runtime=name.endsWith('.py')?resolvePython(n.python_imports):{command:process.execPath,prefix:[]};
35
- if(!runtime)throw fail('RUNTIME_UNAVAILABLE',`Python imports required: ${n.python_imports.join(', ')}`);
36
- const r=spawnSync(runtime.command,[...runtime.prefix,path.join(source(n),'scripts',name),...args],{stdio:'inherit',env:pythonEnvironment()});
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
- checks.push({id:'exceljs',ok:Boolean(await import('@excel.js/exceljs').catch(()=>null))});
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');
@@ -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:PACKAGE.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:PACKAGE.version,sourceDigest:digest(files),files}),{flag:'wx'});
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:PACKAGE.version,digest:after.sourceDigest,mode,backup:record.backup};
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='next'){
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||'next');if(!check.ok)throw fail(check.code,check.message);if(!check.updateAvailable)return {...check,action:'unchanged'};
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=['skill','update','--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:60000});if(r.status!==0)throw fail('SKILL_SYNC_FAILED',r.stderr||r.error?.message||'Skill synchronization failed');
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'));}
@@ -1,17 +1,170 @@
1
1
  {
2
- "schema_version": 1,
2
+ "schema_version": 2,
3
3
  "package": "@petercjl/taobao-ai-ops",
4
- "profiles": {"research": ["operations", "category-research", "product-research"]},
4
+ "profiles": {
5
+ "research": [
6
+ "operations",
7
+ "category-research",
8
+ "product-research",
9
+ "tbcli-tools",
10
+ "sycmcli-tools"
11
+ ]
12
+ },
5
13
  "nodes": [
6
- {"id": "operations", "skill": "taobao-ai-operations", "stage": "routing", "method_version": "1.0", "requires_skills": ["yuce-category-opportunity-report", "taobao-search-product-form"], "python_imports": [], "scripts": [], "native_requirements": []},
7
- {"id": "category-research", "skill": "yuce-category-opportunity-report", "stage": "discover-market", "method_version": "yuce-category-opportunity@2.0", "requires_skills": [], "python_imports": ["openpyxl"], "scripts": ["build_report.py"], "native_requirements": [], "optional_tools": ["tbcli", "yccli"], "origin": {"kind": "accepted-local-skill", "skill_md_sha256": "e93ea7817d44eada35f9934007f5d4d04736553fb28afb01339fdcdacf8275ef"}},
8
- {"id": "product-research", "skill": "taobao-search-product-form", "stage": "research-product", "method_version": "taobao-search-product-form@0.1.1", "requires_skills": [], "python_imports": ["pandas", "numpy", "openpyxl", "PIL", "jieba"], "native_requirements": ["image.observe"], "origin": {"package": "@petercjl/taobao-search-product-form", "version": "0.1.1", "repository": "https://github.com/petercjl/taobao-search-product-form", "commit": "49a574d572895ea4f0047d54766633de754b07d4", "npm_integrity": "sha512-RSoCkbdp3Ggh+VwJl9ejR+IpGHaWeIkExWq4/WcqBS6xzcxYbfuPzX6Fx15PXgh/K2QffjLjgll4mQMiJK3yig=="}, "scripts": ["clean_search_export.mjs", "prepare.py", "analyze.py", "analyze_advertisers.py", "analyze_geography.py", "analyze_price_bands.py", "analyze_title_roots.py", "assemble_opportunity_evidence.py", "build_report_viewmodel.py", "build_visual_review_viewmodel.py", "cluster_visual_forms.py", "compile_style_prototypes.py", "compile_visual_review.py", "compile_visual_selection.py", "render_report.py", "validate_opportunity_cards.py", "validate_report_viewmodel.py", "visual_form_pipeline.py", "visual_observation_batches.py", "workflow_ledger.py"]}
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": {"package": "@petercjl/tbcli", "version": "0.10.4", "entry": "scripts/tbcli.mjs", "role": "existing monthly exports, warehouse and additive category import"},
12
- "sycmcli": {"package": "@petercjl/sycmcli", "version": "0.3.1", "entry": "scripts/sycmcli.mjs", "role": "store-bound market, category, keyword and competitor read operations"},
13
- "yccli": {"external": true, "minimum_version": "0.5.0", "executable_env": "TAOBAO_AI_OPS_YCCLI", "role": "authorized Yuce monthly collection; optional for supplied Excel"}
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
- "targets": {"codex:macos": "implemented", "sealseek:macos": "implemented", "sealseek:windows": "implemented"},
16
- "update_policy": {"owner": "suite", "during_run": "fixed-version-set", "automatic_install": false, "offline": "keep-current", "source_checkout": "git-managed"}
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
  }