已合并
源码安装使用 submodule Skill 软链 #4
源码安装使用 submodule Skill 软链 #4
已合并
chendunyang创建于 8 天前
20 个文件变更+429-340
MREADME.md+23-244
@@ -1,267 +1,46 @@
1# CANNBot1# CANNBot
2 2 
3[![npm](https://img.shields.io/npm/v/%40cannbot-plugin%2Fcannbot?style=flat-square&label=npm)](https://www.npmjs.com/package/@cannbot-plugin/cannbot)3[![npm](https://img.shields.io/npm/v/%40cannbot-plugin%2Fcannbot?style=flat-square&label=npm)](https://www.npmjs.com/package/@cannbot-plugin/cannbot)
4-![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white)
5-![Platform](https://img.shields.io/badge/Platform-Ascend%20NPU-orange?style=flat-square)
6-![Clients](https://img.shields.io/badge/Clients-OpenCode%20%7C%20Codex%20%7C%20Claude%20Code%20%7C%20TRAE%20%7C%20DSH-blue?style=flat-square)
7 4 
8-🌐 [官方网站](https://cann.cannbot.cn) · 📦 [官方插件](plugins/) · 🧩 [安装与发布](script/) · 📖 [工作流指南](script/docs/cannbot-workflows.md) · 🧠 [CANNBot Skills](https://gitcode.com/cann/cannbot-skills)5+## 🚀 概述
9 6 
10----7+CANNBot 面向 CANN 与昇腾 NPU 开发场景,提供可组合的 Agent 插件、专业角色和工程工作流,支持算子开发、算子测试、模型迁移与推理优化。
11 8 
12-## 项目概述9+## ⚡ 快速开始
13 10 
14-**CANNBot** 面向 CANN 与昇腾 NPU 开发场景,提供可组合的 Agent 插件、专业角色和工程工作流,帮助开发者通过自然语言完成算子开发、算子测试、模型迁移与推理优化等任务。11+### npm 安装
15- 
16-本仓库是 CANNBot 的**插件编排与交付仓库**,主要维护:
17- 
18-- 官方插件的 Agents、Workflows、Hooks、客户端清单与安装声明;
19-- 面向 OpenCode、Codex、Claude Code、TRAE 和 DSH 的统一安装器;
20-- npm 自包含发布包的组装、测试与发布流程。
21- 
22-可复用 Skill 源码不在本仓库重复维护,其唯一源码来源是 [cann/cannbot-skills](https://gitcode.com/cann/cannbot-skills)。本仓库通过 Git submodule 锁定经过验证的 Skill 版本,并通过插件声明选择、组装和发布所需能力。
23- 
24-## 仓库定位与分工
25- 
26-| 维度 | 本仓库 `cannbot` | `cannbot-skills` 仓库 |
27-|------|------------------|----------------------|
28-| 核心职责 | 插件编排、安装交付、npm 发布 | 可复用 Skill 的设计、实现、测试与治理 |
29-| 主要资产 | Plugins、Agents、Workflows、Hooks、安装器 | `SKILL.md`、领域知识、脚本、模板、参考资料 |
30-| 复用方式 | 用 `plugin-sources.json` 声明插件需要的 Skills | 按领域提供稳定、可独立安装的 Skills |
31-| 版本关系 | 通过 `vendor/cannbot-skills` 锁定确定的 commit | 独立演进并发布新的能力版本 |
32-| 面向对象 | 插件用户、集成开发者、npm 发布维护者 | Skill 使用者、领域专家、Skill 贡献者 |
33- 
34-职责划分遵循以下原则:
35- 
36-1. **Skill 单一源码**:领域能力只在 `cannbot-skills` 中维护,避免两仓内容漂移。
37-2. **插件就近编排**:Agents、Workflows、Hooks 和依赖声明与插件放在一起,便于端到端演进。
38-3. **统一安装实现**:npm 安装与源码安装只在插件 bundle 的来源上不同,后续共用同一安装流程。
39-4. **可复现发布**:npm 包使用 submodule 锁定的 commit 进行组装,用户安装时不再拉取 Skill 仓库。
40- 
41-## 快速开始
42- 
43-### 前置条件
44- 
45-- Node.js 20 或更高版本;
46-- OpenCode、Codex、Claude Code、TRAE 或 DSH 中的任意一种;
47-- 使用源码安装时需要 Git。
48- 
49-### npm 一键安装
50- 
51-在目标项目目录中执行:
52 12 
53```bash13```bash
54-npx @cannbot-plugin/cannbot@latest install ops-direct-invoke --tool opencode14+npx @cannbot-plugin/cannbot@latest install ${plugin} --tool opencode
55```15```
56 16 
57-完整命令格式:
58- 
59-```bash
60-npx @cannbot-plugin/cannbot@latest install <plugin-id> \
61- --tool <opencode|codex|claude|trae|dsh> \
62- --target /path/to/target-project
63-```
64- 
65-进入目标项目后可以省略 `--target`
66- 
67### 源码安装17### 源码安装
68 18 
69-推荐在克隆主仓时同时拉取 submodule:
70- 
71```bash19```bash
72-git clone --recurse-submodules --shallow-submodules \20+git clone --recurse-submodules --shallow-submodules https://gitcode.com/cann/cannbot.git
73- https://gitcode.com/cann/cannbot.git21+cd cannbot/plugins/${plugin}
74-cd cannbot/plugins/ops-direct-invoke
75bash init.sh22bash init.sh
76```23```
77 24 
78-`init.sh` 默认使用 OpenCode 并安装到当前目录安装到其他项目或客户端时传入参数:25+`${plugin}`:插件名称,见[官方插件](#-官方插件),例如 `ops-direct-invoke`
79 26 
80-```bash27+## 📦 官方插件
81-bash init.sh project claude /path/to/target-project
82-```
83 28 
84-如果普通 `git clone` 没有拉取 submodule,源码安装器会在组装插件前自动初始化 `vendor/cannbot-skills`。29+| 插件 | 能力 |
30+|------|------|
31+| [`ops-direct-invoke`](plugins/ops-direct-invoke/) | Ascend C Kernel 直调开发、审查、精度与性能验收 |
32+| [`ascendc-st-design`](plugins/ascendc-st-design/) | Ascend C 算子 L0/L1/L2 ST 用例设计 |
33+| [`model-infer-optimize`](plugins/model-infer-optimize/) | NPU 模型迁移、精度对齐与推理性能优化 |
85 34 
86-## 官方插件35+## 📖 文档
87 36 
88-| 插件 | 领域 | 能力说明 |37+- [插件安装与工作流](script/docs/cannbot-workflows.md)
89-|------|------|----------|38+- [仓库架构与维护](docs/repository-guide.md)
90-| [`ops-direct-invoke`](plugins/ops-direct-invoke/) | Ascend C 算子开发 | Kernel 直调开发工作流,覆盖需求分析、方案设计、实现、审查、精度与性能验收 |39+- [插件目录说明](plugins/README.md)
91-| [`ascendc-st-design`](plugins/ascendc-st-design/) | Ascend C 算子测试 | 基于 aclnn 接口文档完成参数定义、测试因子、约束分析及 L0/L1/L2 ST 用例设计 |40+- [npm 构建与发布](script/README.md)
92-| [`model-infer-optimize`](plugins/model-infer-optimize/) | NPU 模型推理 | 从模型迁移、baseline 建立到精度对齐、profiling 分析和端到端性能优化 |
93 41 
94-使用 `ascendc-st-design` 的生成脚本还需要 Python 3,以及 PyYAML、NumPy 和 pandas。可以在安装插件后用以下命令检查运行环境:42+## 💬 相关信息
95 43 
96-```bash44+- [CANNBot Skills](https://gitcode.com/cann/cannbot-skills)
97-python3 -c "import yaml, numpy, pandas"45+- [问题反馈](https://gitcode.com/cann/cannbot/issues)
98-```46+- [许可证](LICENSE)
99- 
100-社区插件统一规划在 [`plugins-community/`](plugins-community/) 中,与官方插件保持清晰的准入和发布范围。
101- 
102-## 项目架构
103- 
104-### 双仓协作架构
105- 
106-`plugin-sources.json` 是插件编排仓与 Skill 源码仓之间的关键接口(seam):Skill 仓可以独立演进,本仓只需要更新 submodule commit 和插件映射,无需复制或修改 Skill 源码。
107- 
108-```mermaid
109-flowchart LR
110- SKILL_REPO["cannbot-skills<br/>Skill 唯一源码仓"]
111- SUBMODULE["vendor/cannbot-skills<br/>锁定验证 commit"]
112- PLUGIN_DEF["plugins/&lt;plugin-id&gt;<br/>Agents · Workflows · Hooks"]
113- MAPPING["plugin-sources.json<br/>Skill 选择映射"]
114- ASSEMBLER["Bundle Assembler<br/>script/lib/plugin-bundle.js"]
115- DIST["script/dist/plugins<br/>自包含插件 bundle"]
116- NPM["@cannbot-plugin/cannbot<br/>npm 发布包"]
117- 
118- SKILL_REPO -->|Git submodule| SUBMODULE
119- PLUGIN_DEF --> MAPPING
120- SUBMODULE --> MAPPING
121- MAPPING --> ASSEMBLER
122- PLUGIN_DEF --> ASSEMBLER
123- ASSEMBLER --> DIST
124- DIST --> NPM
125-```
126- 
127-### 统一安装架构
128- 
129-npm 与源码安装共享一个深模块。两个 Adapter 分别解析“发布包 bundle”和“源码 bundle”,在获得标准插件目录后,Skills、Agents、Workflows、Hooks、依赖仓和客户端配置全部由同一实现完成。
130- 
131-```mermaid
132-flowchart TD
133- USER{安装入口}
134- NPX["npx 安装<br/>下载自包含 npm 包"]
135- SOURCE["源码安装<br/>plugins/&lt;id&gt;/init.sh"]
136- PACKAGED["dist/plugins/&lt;id&gt;<br/>发布包 Adapter"]
137- CHECK["检查并按需初始化<br/>vendor/cannbot-skills"]
138- CACHE["组装当前插件<br/>~/.cache/cannbot/source/&lt;version&gt;/&lt;id&gt;"]
139- INSTALLER["统一安装器<br/>script/bin/cannbot.js"]
140- OUTPUT["目标项目<br/>Skills · Agents · Workflows · Hooks · Dependencies"]
141- CLIENTS["OpenCode · Codex · Claude Code · TRAE · DSH"]
142- 
143- USER --> NPX
144- USER --> SOURCE
145- NPX --> PACKAGED
146- SOURCE --> CHECK
147- CHECK --> CACHE
148- PACKAGED --> INSTALLER
149- CACHE --> INSTALLER
150- INSTALLER --> OUTPUT
151- OUTPUT --> CLIENTS
152-```
153- 
154-npm 用户安装时不会拉取 `cannbot-skills` submodule;Skill 内容已经在发布前复制进 npm 包。源码用户只有在 submodule 尚未初始化时,才会在执行 `init.sh` 的 bundle 解析阶段拉取 Skill 仓库。
155- 
156-## 目录规划
157- 
158-```text
159-cannbot/
160-├── plugins/ # 官方插件编排与发布定义
161-│ └── <plugin-id>/
162-│ ├── .claude-plugin/ # Claude Plugin manifest
163-│ ├── .codex-plugin/ # Codex Plugin manifest
164-│ ├── agents/ # 专业角色定义
165-│ ├── workflows/ # 端到端工作流与模板
166-│ ├── hooks/ # 可选客户端 Hooks
167-│ ├── AGENTS.md # 插件级协作说明
168-│ ├── plugin-sources.json # Skill 仓映射
169-│ ├── plugin-install.json # 可选依赖仓声明
170-│ └── init.sh # 源码安装薄入口
171-├── plugins-community/ # 社区插件预留目录
172-├── script/ # npm 工程与统一安装模块
173-│ ├── bin/ # CLI 与源码安装 Adapter
174-│ ├── lib/ # 插件 bundle 组装模块
175-│ ├── scripts/ # 构建脚本
176-│ ├── test/ # 安装行为测试
177-│ └── docs/ # 安装和工作流文档
178-└── vendor/
179- └── cannbot-skills/ # Skill 仓 Git submodule
180-```
181- 
182-### 插件目录契约
183- 
184-| 文件或目录 | 是否必需 | 作用 |
185-|------------|----------|------|
186-| `.claude-plugin/plugin.json` | 是 | 插件 ID、版本、描述、Skills 与 Agents 清单 |
187-| `.codex-plugin/plugin.json` | 是 | Codex 插件发现与展示元数据 |
188-| `plugin-sources.json` | 是 | 将插件声明映射到 Skill 仓中的具体路径 |
189-| `init.sh` | 是 | 源码安装入口,仅负责转交插件、工具、目标和源码位置 |
190-| `AGENTS.md``agents/` | 可选 | 插件级指令与专业角色 |
191-| `workflows/``hooks/` | 可选 | 工作流资产和客户端生命周期扩展 |
192-| `plugin-install.json` | 可选 | 插件所需外部依赖仓及项目暴露路径 |
193- 
194-## Submodule 与发布流程
195- 
196-### Skill 版本映射
197- 
198-每个插件通过 `plugin-sources.json` 指定 Skill 仓和所需目录,例如:
199- 
200-```json
201-{
202- "skillsRepository": "vendor/cannbot-skills",
203- "skills": [
204- "ops/ascendc-st-design"
205- ]
206-}
207-```
208- 
209-构建器会校验插件 manifest 与映射文件中的 Skill 集合是否一致,并拒绝缺失、越界或重复的 Skill 路径。
210- 
211-### npm 发布
212- 
213-```bash
214-git submodule update --init --recursive --depth 1
215-npm --prefix script test
216-npm --prefix script run build:plugins
217-npm --prefix script run pack:check
218-npm --prefix script run pack:smoke
219-```
220- 
221-执行 `npm publish` 时,`prepublishOnly` 会先运行完整测试和实际 tgz 安装 smoke test,`prepack` 再重新组装 `script/dist/plugins/`。发布产物包含完整 Skill 内容,因此 npm 用户获得的是可离线解析的固定版本 bundle。
222- 
223-### 更新 Skill 版本
224- 
225-1. 执行 `npm --prefix script run skills:update` 显式拉取 `cannbot-skills/master` 最新提交;
226-2. 核对各插件的 `plugin-sources.json`
227-3. 运行完整安装测试和打包检查;
228-4. 提交新的 submodule gitlink;
229-5. 按语义化版本规则更新并发布 npm 包。
230- 
231-普通构建和发布始终使用 gitlink 锁定的 Skill 提交,不会自动跟随远程分支,以保证产物可复现。
232- 
233-## 开发与验证
234- 
235-```bash
236-# 完整测试:覆盖源码/发布包两种来源和五种客户端
237-npm --prefix script test
238- 
239-# 重新组装插件 bundle
240-npm --prefix script run build:plugins
241- 
242-# 检查 npm 发布内容
243-npm --prefix script run pack:check
244- 
245-# 通过实际 tgz 和 npm bin 验证安装
246-npm --prefix script run pack:smoke
247-```
248- 
249-测试以统一安装器的外部 interface 为主要验证面,覆盖 Skills/Agents 复制、工作流资产、Claude Hooks、重复安装、依赖仓声明以及源码 `init.sh` Adapter。
250- 
251-## 修改应该提交到哪里
252- 
253-| 修改类型 | 提交位置 |
254-|----------|----------|
255-| Skill 知识、脚本、模板或参考资料 | [`cannbot-skills`](https://gitcode.com/cann/cannbot-skills) |
256-| 官方插件的 Agents、Workflows、Hooks 或 Skill 组合 | 本仓库 [`plugins/`](plugins/) |
257-| 社区插件 | 本仓库 [`plugins-community/`](plugins-community/) |
258-| npm 安装、bundle 组装或客户端适配 | 本仓库 [`script/`](script/) |
259-| Skill 版本升级 | Skill 仓先合入,本仓再更新 submodule gitlink |
260- 
261-## 许可证与免责声明
262- 
263-- 本仓库默认适用根目录 [`LICENSE`](LICENSE) 中的 CANN Open Software License Agreement Version 2.0,官方插件使用相同许可证。
264-- npm 包为混合许可证分发:安装器实现适用 MIT License,组装的官方插件和 Skills 保留各自许可证,详见 [`script/LICENSE`](script/LICENSE) 及各插件产物中的许可文件。
265-- submodule 内容遵循 `cannbot-skills` 仓库自身的许可证。
266- 
267-CANNBot 生成或修改的代码仍需开发者完成编译、测试、精度验证、性能验证和安全审查后再投入使用。
@@ -0,0 +1,143 @@
1+# CANNBot 仓库架构与维护
2+ 
3+## 仓库定位
4+ 
5+本仓库维护:
6+ 
7+- 官方插件的 Agents、Workflows、Hooks、客户端清单与安装声明;
8+- 面向 OpenCode、Codex、Claude Code、TRAE 和 DSH 的统一安装器;
9+- npm 自包含发布包的组装、测试与发布流程。
10+ 
11+可复用 Skill 源码由 [cannbot-skills](https://gitcode.com/cann/cannbot-skills) 维护。本仓库通过 Git submodule 锁定 Skill 版本,并通过插件声明选择所需能力。
12+ 
13+| 维度 | `cannbot` | `cannbot-skills` |
14+|------|-----------|------------------|
15+| 核心职责 | 插件编排、安装交付、npm 发布 | Skill 设计、实现、测试与治理 |
16+| 主要资产 | Plugins、Agents、Workflows、Hooks、安装器 | `SKILL.md`、领域知识、脚本、模板、参考资料 |
17+| 复用方式 | `plugin-sources.json` 声明插件所需 Skills | 按领域提供可独立使用的 Skills |
18+| 版本关系 | `vendor/cannbot-skills` 锁定确定的 commit | 独立演进 |
19+ 
20+## 架构
21+ 
22+`plugin-sources.json` 是插件与 Skill 源码之间的映射接口。源码安装将 Skill 链接到 submodule;npm 发布前将 Skill 复制进自包含包。
23+ 
24+```mermaid
25+flowchart LR
26+ SKILL_REPO["cannbot-skills"]
27+ SUBMODULE["vendor/cannbot-skills"]
28+ PLUGIN["plugins/&lt;plugin&gt;<br/>Agents · Workflows · Hooks"]
29+ MAPPING["plugin-sources.json"]
30+ INSTALLER["统一安装器"]
31+ DIST["npm 自包含包"]
32+ 
33+ SKILL_REPO -->|Git submodule| SUBMODULE
34+ SUBMODULE --> MAPPING
35+ PLUGIN --> MAPPING
36+ MAPPING --> INSTALLER
37+ MAPPING --> DIST
38+ PLUGIN --> INSTALLER
39+ PLUGIN --> DIST
40+```
41+ 
42+源码执行 `plugins/<plugin>/init.sh` 时,安装器按需初始化 submodule,并在目标客户端目录创建相对 Skill 软链。npm 安装直接使用发布包内的 Skill 副本,不依赖 submodule。
43+ 
44+## 目录结构
45+ 
46+```text
47+cannbot/
48+├── plugins/ # 官方插件
49+│ └── <plugin>/
50+│ ├── .claude-plugin/ # Claude Plugin manifest
51+│ ├── .codex-plugin/ # Codex Plugin manifest
52+│ ├── agents/ # 专业角色定义
53+│ ├── workflows/ # 工作流与模板
54+│ ├── hooks/ # 客户端 Hooks
55+│ ├── AGENTS.md # 插件级协作说明
56+│ ├── plugin-sources.json # Skill 映射
57+│ ├── plugin-install.json # 外部依赖声明
58+│ └── init.sh # 源码安装入口
59+├── plugins-community/ # 社区插件
60+├── script/ # npm 工程与统一安装器
61+│ ├── bin/
62+│ ├── lib/
63+│ ├── scripts/
64+│ ├── test/
65+│ └── docs/
66+└── vendor/
67+ └── cannbot-skills/ # Skill 仓 Git submodule
68+```
69+ 
70+## 插件目录契约
71+ 
72+| 文件或目录 | 是否必需 | 作用 |
73+|------------|----------|------|
74+| `.claude-plugin/plugin.json` | 是 | 插件名称、版本、描述与 Agents 清单 |
75+| `.codex-plugin/plugin.json` | 是 | Codex 插件发现与展示元数据 |
76+| `plugin-sources.json` | 是 | Skill 仓路径、安装模式与 Skill 清单 |
77+| `init.sh` | 是 | 源码安装入口 |
78+| `AGENTS.md``agents/` | 可选 | 插件指令与专业角色 |
79+| `workflows/``hooks/` | 可选 | 工作流资产与客户端扩展 |
80+| `plugin-install.json` | 可选 | 外部依赖仓及项目暴露路径 |
81+ 
82+Skill 映射示例:
83+ 
84+```json
85+{
86+ "skillsRepository": "vendor/cannbot-skills",
87+ "skillInstallMode": "symlink",
88+ "skills": [
89+ "ops/ascendc-st-design"
90+ ]
91+}
92+```
93+ 
94+## npm 发布
95+ 
96+```bash
97+git submodule update --init --recursive --depth 1
98+npm --prefix script test
99+npm --prefix script run build:plugins
100+npm --prefix script run pack:check
101+npm --prefix script run pack:smoke
102+```
103+ 
104+`prepublishOnly` 运行完整测试和实际 tarball 安装测试,`prepack` 重新组装 `script/dist/plugins/`。发布产物包含完整 Skill 内容。
105+ 
106+## 更新 Skill 版本
107+ 
108+1. 执行 `npm --prefix script run skills:update`
109+2. 核对各插件的 `plugin-sources.json`
110+3. 运行完整安装测试和打包检查;
111+4. 提交新的 submodule gitlink;
112+5. 更新 npm 版本并发布。
113+ 
114+普通构建和发布使用 gitlink 锁定的 Skill commit,不自动跟随远程分支。
115+ 
116+## 开发与验证
117+ 
118+```bash
119+npm --prefix script test
120+npm --prefix script run build:plugins
121+npm --prefix script run pack:check
122+npm --prefix script run pack:smoke
123+```
124+ 
125+测试覆盖源码与 npm 两种来源、五种客户端、Skills、Agents、Workflows、Claude Hooks、重复安装、依赖仓和源码 `init.sh`
126+ 
127+## 修改位置
128+ 
129+| 修改类型 | 提交位置 |
130+|----------|----------|
131+| Skill 知识、脚本、模板或参考资料 | [`cannbot-skills`](https://gitcode.com/cann/cannbot-skills) |
132+| 官方插件内容或 Skill 组合 | [`plugins/`](../plugins/) |
133+| 社区插件 | [`plugins-community/`](../plugins-community/) |
134+| npm 安装、组装或客户端适配 | [`script/`](../script/) |
135+| Skill 版本升级 | Skill 仓先合入,本仓再更新 submodule gitlink |
136+ 
137+## 许可证与免责声明
138+ 
139+- 本仓库默认适用根目录 [LICENSE](../LICENSE) 中的 CANN Open Software License Agreement Version 2.0。
140+- npm 安装器实现适用 MIT License;官方插件和 Skills 保留各自许可证,详见 [`script/LICENSE`](../script/LICENSE)。
141+- submodule 内容遵循 `cannbot-skills` 仓库许可证。
142+ 
143+CANNBot 生成或修改的代码需由开发者完成编译、测试、精度验证、性能验证和安全审查后再投入使用。
@@ -1,6 +1,6 @@
1# CANNBot managed plugins1# CANNBot managed plugins
2 2 
3-This directory contains the self-contained plugins in the official CANNBot catalog:3+This directory contains the official CANNBot plugin definitions and non-Skill files:
4 4 
5- `ops-direct-invoke`5- `ops-direct-invoke`
6- `ascendc-st-design`6- `ascendc-st-design`
@@ -8,8 +8,6 @@ This directory contains the self-contained plugins in the official CANNBot catal
8 8 
9Each plugin declares the Skills it needs in `plugin-sources.json`. The actual Skill source is maintained in the `vendor/cannbot-skills` submodule and is not duplicated in this repository.9Each plugin declares the Skills it needs in `plugin-sources.json`. The actual Skill source is maintained in the `vendor/cannbot-skills` submodule and is not duplicated in this repository.
10 10 
11-`npm --prefix script run build:plugins` copies the declared Skills into `script/dist/plugins/<plugin-id>/skills/`. Every direct child in that generated directory is a discoverable Skill, keeping the published layout valid for Codex, Claude Code, OpenCode, TRAE, and DSH.11+Each plugin's `init.sh` creates project Skill symlinks to the submodule. `npm --prefix script run build:plugins` instead copies the declared Skills into `script/dist/plugins/<plugin>/skills/` for a self-contained npm package.
12 12 
13-The submodule is currently pinned to `cann/cannbot-skills` commit `38728be73688df97a84d9570a24dae616ea4542e`. To consume an update, move the submodule commit, verify each plugin mapping, run `npm --prefix script test`, and bump the npm package version so users receive a new immutable cache directory.13+Each `init.sh` is a thin source adapter for the shared Node installer. Optional dependency repositories are declared in `plugin-install.json`.
14- 
15-Each plugin includes a thin `init.sh` source adapter. It delegates to the same Node installer used by the published npm package; installation behavior is not implemented in the plugin scripts. Optional dependency repositories are declared in `plugin-install.json`.
@@ -5,7 +5,7 @@
5 "author": {5 "author": {
6 "name": "CANNBot"6 "name": "CANNBot"
7 },7 },
8- "homepage": "https://cann.cannbot.cn/plugin.html?id=ascendc-st-design",8+ "homepage": "https://gitcode.com/cann/cannbot",
9 "repository": "https://gitcode.com/cann/cannbot",9 "repository": "https://gitcode.com/cann/cannbot",
10 "license": "CANN-2.0",10 "license": "CANN-2.0",
11 "keywords": ["ascend-c", "operator-testing", "st-design"],11 "keywords": ["ascend-c", "operator-testing", "st-design"],
@@ -3,7 +3,7 @@
3 "version": "1.0.0",3 "version": "1.0.0",
4 "description": "Ascend C 算子系统测试(ST)设计工作流。",4 "description": "Ascend C 算子系统测试(ST)设计工作流。",
5 "author": { "name": "CANNBot" },5 "author": { "name": "CANNBot" },
6- "homepage": "https://cann.cannbot.cn/plugin.html?id=ascendc-st-design",6+ "homepage": "https://gitcode.com/cann/cannbot",
7 "repository": "https://gitcode.com/cann/cannbot",7 "repository": "https://gitcode.com/cann/cannbot",
8 "license": "CANN-2.0",8 "license": "CANN-2.0",
9 "keywords": ["cannbot", "ascend-c", "operator-testing"],9 "keywords": ["cannbot", "ascend-c", "operator-testing"],
@@ -1,5 +1,6 @@
1{1{
2 "skillsRepository": "vendor/cannbot-skills",2 "skillsRepository": "vendor/cannbot-skills",
3+ "skillInstallMode": "symlink",
3 "skills": [4 "skills": [
4 "ops/ascendc-st-design"5 "ops/ascendc-st-design"
5 ]6 ]
@@ -5,7 +5,7 @@
5 "author": {5 "author": {
6 "name": "CANNBot"6 "name": "CANNBot"
7 },7 },
8- "homepage": "https://cann.cannbot.cn/plugin.html?id=model-infer-optimize",8+ "homepage": "https://gitcode.com/cann/cannbot",
9 "repository": "https://gitcode.com/cann/cannbot",9 "repository": "https://gitcode.com/cann/cannbot",
10 "license": "CANN-2.0",10 "license": "CANN-2.0",
11 "dependencies": ["model-infer-skills"],11 "dependencies": ["model-infer-skills"],
@@ -3,7 +3,7 @@
3 "version": "1.0.6",3 "version": "1.0.6",
4 "description": "NPU 模型迁移与推理性能优化工作流。",4 "description": "NPU 模型迁移与推理性能优化工作流。",
5 "author": { "name": "CANNBot" },5 "author": { "name": "CANNBot" },
6- "homepage": "https://cann.cannbot.cn/plugin.html?id=model-infer-optimize",6+ "homepage": "https://gitcode.com/cann/cannbot",
7 "repository": "https://gitcode.com/cann/cannbot",7 "repository": "https://gitcode.com/cann/cannbot",
8 "license": "CANN-2.0",8 "license": "CANN-2.0",
9 "keywords": ["cannbot", "npu", "model-inference"],9 "keywords": ["cannbot", "npu", "model-inference"],
@@ -1,5 +1,6 @@
1{1{
2 "skillsRepository": "vendor/cannbot-skills",2 "skillsRepository": "vendor/cannbot-skills",
3+ "skillInstallMode": "symlink",
3 "skills": [4 "skills": [
4 "model/model-infer-migrator",5 "model/model-infer-migrator",
5 "model/model-infer-parallel-analysis",6 "model/model-infer-parallel-analysis",
@@ -15,18 +15,15 @@
15 15 
16- 已安装 CANN Toolkit(建议 ≥ 9.0.0),具体版本配套关系请查阅 [CANN Release Notes](https://www.hiascend.com/cann/document)16- 已安装 CANN Toolkit(建议 ≥ 9.0.0),具体版本配套关系请查阅 [CANN Release Notes](https://www.hiascend.com/cann/document)
17- 已配置 NPU 设备(支持 Ascend 910/950 PR 等芯片)17- 已配置 NPU 设备(支持 Ascend 910/950 PR 等芯片)
18-- 已安装 OpenCode、Codex、Claude Code、TRAE 或 DSH
19 18 
20### OpenCode(推荐)19### OpenCode(推荐)
21 20 
22```bash21```bash
23-git clone https://gitcode.com/cann/cannbot.git22+git clone --recurse-submodules --shallow-submodules https://gitcode.com/cann/cannbot.git
24cd cannbot/plugins/model-infer-optimize23cd cannbot/plugins/model-infer-optimize
25bash init.sh24bash init.sh
26```25```
27 26 
28-该命令默认安装到当前目录并使用 OpenCode,缺失的 Skill submodule 会自动初始化。安装到其他项目时可执行 `bash init.sh project opencode /path/to/target-project`
29- 
30验证:27验证:
31 28 
32```bash29```bash
@@ -5,7 +5,7 @@
5 "author": {5 "author": {
6 "name": "CANNBot"6 "name": "CANNBot"
7 },7 },
8- "homepage": "https://cann.cannbot.cn/plugin.html?id=ops-direct-invoke",8+ "homepage": "https://gitcode.com/cann/cannbot",
9 "repository": "https://gitcode.com/cann/cannbot",9 "repository": "https://gitcode.com/cann/cannbot",
10 "license": "CANN-2.0",10 "license": "CANN-2.0",
11 "dependencies": [11 "dependencies": [
@@ -3,7 +3,7 @@
3 "version": "1.2.5",3 "version": "1.2.5",
4 "description": "Ascend C Kernel 直调算子开发工作流,包含设计、实现、审查与性能验收。",4 "description": "Ascend C Kernel 直调算子开发工作流,包含设计、实现、审查与性能验收。",
5 "author": { "name": "CANNBot" },5 "author": { "name": "CANNBot" },
6- "homepage": "https://cann.cannbot.cn/plugin.html?id=ops-direct-invoke",6+ "homepage": "https://gitcode.com/cann/cannbot",
7 "repository": "https://gitcode.com/cann/cannbot",7 "repository": "https://gitcode.com/cann/cannbot",
8 "license": "CANN-2.0",8 "license": "CANN-2.0",
9 "keywords": ["cannbot", "ascend-c", "operator-development"],9 "keywords": ["cannbot", "ascend-c", "operator-development"],
@@ -1,5 +1,6 @@
1{1{
2 "skillsRepository": "vendor/cannbot-skills",2 "skillsRepository": "vendor/cannbot-skills",
3+ "skillInstallMode": "symlink",
3 "skills": [4 "skills": [
4 "ops/ascendc-api-best-practices",5 "ops/ascendc-api-best-practices",
5 "ops/ascendc-regbase-best-practice",6 "ops/ascendc-regbase-best-practice",
@@ -20,18 +20,15 @@ CANNBot 算子直调开发模式适用于**快速验证自定义算子**场景
20 20 
21- 已安装 CANN Toolkit(建议 ≥ 9.0.0),具体版本配套关系请查阅 [CANN Release Notes](https://www.hiascend.com/cann/document)21- 已安装 CANN Toolkit(建议 ≥ 9.0.0),具体版本配套关系请查阅 [CANN Release Notes](https://www.hiascend.com/cann/document)
22- 已配置 NPU 设备(支持 Ascend 910/950 PR 等芯片)22- 已配置 NPU 设备(支持 Ascend 910/950 PR 等芯片)
23-- 已安装 OpenCode、Codex、Claude Code、TRAE 或 DSH
24 23 
25### OpenCode(推荐)24### OpenCode(推荐)
26 25 
27```bash26```bash
28-git clone https://gitcode.com/cann/cannbot.git27+git clone --recurse-submodules --shallow-submodules https://gitcode.com/cann/cannbot.git
29cd cannbot/plugins/ops-direct-invoke28cd cannbot/plugins/ops-direct-invoke
30bash init.sh29bash init.sh
31```30```
32 31 
33-该命令默认安装到当前目录并使用 OpenCode,缺失的 Skill submodule 会自动初始化。安装到其他项目时可执行 `bash init.sh project opencode /path/to/target-project`
34- 
35### 其他工具32### 其他工具
36 33 
37<details>34<details>
@@ -70,18 +67,6 @@ bash init.sh project trae /path/to/target-project
70 67 
71</details>68</details>
72 69 
73-### 在其他目录执行
74- 
75-`init.sh` 支持通过完整路径调用,无需先 `cd` 到插件目录。第三个参数指定目标项目路径,省略则安装到当前目录:
76- 
77-```bash
78-# 安装到当前目录
79-bash /path/to/cannbot/plugins/ops-direct-invoke/init.sh project opencode
80- 
81-# 安装到指定项目
82-bash /path/to/cannbot/plugins/ops-direct-invoke/init.sh project opencode /path/to/your_project_path
83-```
84- 
85### 验证安装70### 验证安装
86 71 
87```bash72```bash
@@ -24,8 +24,8 @@ The npm package is a mixed-license distribution. The installer implementation
24is MIT licensed. Assembled official plugins and bundled Skills retain the CANN24is MIT licensed. Assembled official plugins and bundled Skills retain the CANN
25Open Software License Agreement Version 2.0 and any license notices included25Open Software License Agreement Version 2.0 and any license notices included
26with their content; see the package `LICENSE` overview and the license files in26with their content; see the package `LICENSE` overview and the license files in
27-each `dist/plugins/<plugin-id>/` directory.27+each `dist/plugins/<plugin>/` directory.
28 28 
29-All clients and both source modes use the same Node installer. The `init.sh` in each source plugin is only a thin adapter that forwards the plugin ID, source repository, tool, and target to this CLI. Plugin-specific dependency repositories are declared in `plugin-install.json`; no plugin maintains a separate installation implementation.29+All clients use the same Node installer. Each source `init.sh` forwards the selected plugin, source repository, tool, and target to the CLI, which links Skills to the submodule. npm installation copies the bundled Skills. Plugin-specific dependency repositories are declared in `plugin-install.json`.
30 30 
31See [the workflow installation and usage guide](docs/cannbot-workflows.md) for all supported commands and prompt examples.31See [the workflow installation and usage guide](docs/cannbot-workflows.md) for all supported commands and prompt examples.
@@ -5,7 +5,6 @@ import {
5 existsSync,5 existsSync,
6 lstatSync,6 lstatSync,
7 mkdirSync,7 mkdirSync,
8- mkdtempSync,
9 readdirSync,8 readdirSync,
10 readFileSync,9 readFileSync,
11 renameSync,10 renameSync,
@@ -14,15 +13,23 @@ import {
14 writeFileSync,13 writeFileSync,
15} from "node:fs";14} from "node:fs";
16import { homedir } from "node:os";15import { homedir } from "node:os";
17-import { basename, dirname, join, resolve } from "node:path";16+import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
18import { spawnSync } from "node:child_process";17import { spawnSync } from "node:child_process";
19import { fileURLToPath } from "node:url";18import { fileURLToPath } from "node:url";
20-import { assemblePlugins, pluginSourceDefinition } from "../lib/plugin-bundle.js";19+import { pluginSourceDefinition } from "../lib/plugin-bundle.js";
21 20 
22const installerPackage = readJson(new URL("../package.json", import.meta.url));21const installerPackage = readJson(new URL("../package.json", import.meta.url));
23const OPENCODE_PLUGIN_SPEC = `${installerPackage.name}@${installerPackage.version}`;22const OPENCODE_PLUGIN_SPEC = `${installerPackage.name}@${installerPackage.version}`;
24const BUNDLED_REPOSITORY = fileURLToPath(new URL("../", import.meta.url));23const BUNDLED_REPOSITORY = fileURLToPath(new URL("../", import.meta.url));
25const PACKAGE_CACHEBUSTER = `cannbot-${installerPackage.version.replaceAll(".", "-")}`;24const PACKAGE_CACHEBUSTER = `cannbot-${installerPackage.version.replaceAll(".", "-")}`;
25+const TOOL_DIRECTORIES = Object.freeze({
26+ opencode: { configDirs: [".opencode"], defaultConfigDir: ".opencode", skillsDir: [".agents", "skills"] },
27+ codex: { configDirs: [".codex"], defaultConfigDir: ".codex", skillsDir: [".agents", "skills"] },
28+ claude: { configDirs: [".claude"], defaultConfigDir: ".claude" },
29+ trae: { configDirs: [".traecli", ".marscode", ".trae", ".trae-cn"], defaultConfigDir: ".trae" },
30+ dsh: { configDirs: [".dsh"], defaultConfigDir: ".dsh" },
31+});
32+const TOOL_NAMES = Object.keys(TOOL_DIRECTORIES);
26 33 
27function fail(message) {34function fail(message) {
28 console.error(`cannbot: ${message}`);35 console.error(`cannbot: ${message}`);
@@ -43,7 +50,7 @@ function tryRun(command, args, cwd) {
43function parseArgs(argv) {50function parseArgs(argv) {
44 const [command, pluginId, ...rest] = argv;51 const [command, pluginId, ...rest] = argv;
45 if (command !== "install" || !pluginId) {52 if (command !== "install" || !pluginId) {
46- fail("usage: cannbot install <plugin-id> --tool <opencode|codex|claude|trae|dsh> [--target <dir>] [--source <repository>]");53+ fail(`usage: cannbot install <plugin> --tool <${TOOL_NAMES.join("|")}> [--target <dir>] [--source <repository>]`);
47 }54 }
48 const options = { command, pluginId, target: process.cwd() };55 const options = { command, pluginId, target: process.cwd() };
49 for (let index = 0; index < rest.length; index += 1) {56 for (let index = 0; index < rest.length; index += 1) {
@@ -56,8 +63,8 @@ function parseArgs(argv) {
56 else fail(`unknown option: ${key}`);63 else fail(`unknown option: ${key}`);
57 index += 1;64 index += 1;
58 }65 }
59- if (!new Set(["opencode", "codex", "claude", "trae", "dsh"]).has(options.tool)) {66+ if (!TOOL_DIRECTORIES[options.tool]) {
60- fail("--tool must be opencode, codex, claude, trae, or dsh");67+ fail(`--tool must be one of: ${TOOL_NAMES.join(", ")}`);
61 }68 }
62 return options;69 return options;
63}70}
@@ -76,10 +83,6 @@ function containsPlugin(repoPath, pluginId) {
76}83}
77 84 
78function resolveBundle(override, pluginId) {85function resolveBundle(override, pluginId) {
79- const cacheBase = process.env.XDG_CACHE_HOME
80- ? resolve(process.env.XDG_CACHE_HOME)
81- : join(homedir(), ".cache");
82- 
83 if (override) {86 if (override) {
84 const source = pluginSourceDefinition(override, pluginId);87 const source = pluginSourceDefinition(override, pluginId);
85 if (source) {88 if (source) {
@@ -88,19 +91,7 @@ function resolveBundle(override, pluginId) {
88 console.log(`Initializing ${source.skillsRepository} submodule...`);91 console.log(`Initializing ${source.skillsRepository} submodule...`);
89 run("git", ["submodule", "update", "--init", "--recursive", "--depth", "1", source.skillsRepository], override);92 run("git", ["submodule", "update", "--init", "--recursive", "--depth", "1", source.skillsRepository], override);
90 }93 }
91- const bundlePath = join(cacheBase, "cannbot", "source", installerPackage.version, pluginId);94+ return source.pluginRoot;
92- const bundleParent = dirname(bundlePath);
93- mkdirSync(bundleParent, { recursive: true });
94- const staging = mkdtempSync(join(bundleParent, `.staging-${pluginId}-`));
95- try {
96- assemblePlugins(override, staging, [pluginId]);
97- } catch (error) {
98- rmSync(staging, { recursive: true, force: true });
99- fail(error.message);
100- }
101- rmSync(bundlePath, { recursive: true, force: true });
102- renameSync(staging, bundlePath);
103- return resolvePluginDir(bundlePath, pluginId);
104 }95 }
105 if (containsPlugin(override, pluginId)) return resolvePluginDir(override, pluginId);96 if (containsPlugin(override, pluginId)) return resolvePluginDir(override, pluginId);
106 fail(`invalid CANNBot repository: ${override}`);97 fail(`invalid CANNBot repository: ${override}`);
@@ -129,12 +120,23 @@ function instructionMarkers(pluginName) {
129 };120 };
130}121}
131 122 
132-function copySkill(source, destination) {123+function installSkill(source, destination, mode) {
124+ if (mode === "symlink") {
125+ if (pathExists(destination)) {
126+ if (!lstatSync(destination).isSymbolicLink()) {
127+ fail(`Skill destination already exists and is not a symlink: ${destination}`);
128+ }
129+ rmSync(destination, { force: true });
130+ }
131+ const linkTarget = relative(dirname(destination), source) || ".";
132+ symlinkSync(linkTarget, destination, "dir");
133+ return;
134+ }
133 rmSync(destination, { recursive: true, force: true });135 rmSync(destination, { recursive: true, force: true });
134 cpSync(source, destination, { recursive: true, dereference: true });136 cpSync(source, destination, { recursive: true, dereference: true });
135}137}
136 138 
137-function installPluginAssets(pluginDir, plugin, target) {139+function installPluginAssets(pluginDir, plugin, target, sourceDefinition) {
138 const relativeRoot = `.cannbot/plugins/${plugin.name}`;140 const relativeRoot = `.cannbot/plugins/${plugin.name}`;
139 const destinationRoot = join(target, relativeRoot);141 const destinationRoot = join(target, relativeRoot);
140 rmSync(destinationRoot, { recursive: true, force: true });142 rmSync(destinationRoot, { recursive: true, force: true });
@@ -142,7 +144,12 @@ function installPluginAssets(pluginDir, plugin, target) {
142 const assets = { destinationRoot, relativeRoot };144 const assets = { destinationRoot, relativeRoot };
143 let installed = false;145 let installed = false;
144 for (const name of ["workflows", "hooks", "LICENSE", "SKILLS_LICENSE"]) {146 for (const name of ["workflows", "hooks", "LICENSE", "SKILLS_LICENSE"]) {
145- const source = join(pluginDir, name);147+ let source = join(pluginDir, name);
148+ if (!existsSync(source) && sourceDefinition && name === "LICENSE") {
149+ source = join(dirname(pluginDir), "LICENSE");
150+ } else if (!existsSync(source) && sourceDefinition && name === "SKILLS_LICENSE") {
151+ source = join(resolve(pluginDir, "..", "..", sourceDefinition.skillsRepository), "LICENSE");
152+ }
146 if (!existsSync(source)) continue;153 if (!existsSync(source)) continue;
147 cpSync(source, join(destinationRoot, name), { recursive: true, dereference: true });154 cpSync(source, join(destinationRoot, name), { recursive: true, dereference: true });
148 installed = true;155 installed = true;
@@ -176,16 +183,15 @@ function rewriteTreeReferences(root, assets) {
176}183}
177 184 
178function toolConfigDir(target, tool) {185function toolConfigDir(target, tool) {
179- if (tool === "trae") {186+ const layout = TOOL_DIRECTORIES[tool];
180- return [".traecli", ".marscode", ".trae", ".trae-cn"]187+ return layout.configDirs
181- .map((name) => join(target, name))188+ .map((name) => join(target, name))
182- .find((path) => existsSync(path)) ?? join(target, ".trae");189+ .find((path) => existsSync(path)) ?? join(target, layout.defaultConfigDir);
183- }
184- return join(target, tool === "opencode" ? ".opencode" : `.${tool}`);
185}190}
186 191 
187function toolSkillsDir(target, tool) {192function toolSkillsDir(target, tool) {
188- if (tool === "codex") return join(target, ".agents", "skills");193+ const skillsDir = TOOL_DIRECTORIES[tool].skillsDir;
194+ if (skillsDir) return join(target, ...skillsDir);
189 return join(toolConfigDir(target, tool), "skills");195 return join(toolConfigDir(target, tool), "skills");
190}196}
191 197 
@@ -223,6 +229,28 @@ function resolveSkills(plugin, pluginDir) {
223 return [...skills.entries()].map(([name, source]) => ({ name, source }));229 return [...skills.entries()].map(([name, source]) => ({ name, source }));
224}230}
225 231 
232+function resolveSourceSkills(repositoryRoot, sourceDefinition) {
233+ const skillsRepository = resolve(repositoryRoot, sourceDefinition.skillsRepository);
234+ const skills = new Map();
235+ for (const relativeSkill of sourceDefinition.skills) {
236+ if (typeof relativeSkill !== "string" || !relativeSkill) {
237+ fail(`invalid Skill path in ${sourceDefinition.definitionPath}`);
238+ }
239+ const source = resolve(skillsRepository, relativeSkill);
240+ const pathFromRoot = relative(skillsRepository, source);
241+ if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
242+ fail(`Skill path escapes its repository root: ${relativeSkill}`);
243+ }
244+ const skillPath = join(source, "SKILL.md");
245+ if (!existsSync(skillPath)) fail(`skill is missing SKILL.md: ${source}`);
246+ const name = readFileSync(skillPath, "utf8").match(/^name:\s*([^\r\n]+)$/m)?.[1]?.trim();
247+ if (!name) fail(`skill is missing a frontmatter name: ${skillPath}`);
248+ if (skills.has(name)) fail(`duplicate Skill name in source definition: ${name}`);
249+ skills.set(name, source);
250+ }
251+ return [...skills.entries()].map(([name, source]) => ({ name, source }));
252+}
253+ 
226function parseAgent(markdown) {254function parseAgent(markdown) {
227 const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);255 const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
228 const frontmatter = match?.[1] ?? "";256 const frontmatter = match?.[1] ?? "";
@@ -345,11 +373,11 @@ function exposeDependency(source, destination) {
345 return false;373 return false;
346 }374 }
347 }375 }
348- symlinkSync(source, destination, "dir");376+ symlinkSync(relative(dirname(destination), source) || ".", destination, "dir");
349 return true;377 return true;
350}378}
351 379 
352-function provisionDependencies(pluginDir, plugin, target) {380+function provisionDependencies(pluginDir, plugin, target, skills) {
353 const definitionPath = join(pluginDir, "plugin-install.json");381 const definitionPath = join(pluginDir, "plugin-install.json");
354 if (!existsSync(definitionPath)) return [];382 if (!existsSync(definitionPath)) return [];
355 const definition = readJson(definitionPath);383 const definition = readJson(definitionPath);
@@ -393,10 +421,15 @@ function provisionDependencies(pluginDir, plugin, target) {
393 if (!updated) console.warn(`cannbot: ${dependency.name} submodule update was incomplete`);421 if (!updated) console.warn(`cannbot: ${dependency.name} submodule update was incomplete`);
394 }422 }
395 if (dependency.cleanMarkdownWithSkill) {423 if (dependency.cleanMarkdownWithSkill) {
396- const cleaner = join(pluginDir, "skills", dependency.cleanMarkdownWithSkill, "scripts", "clean_markdown.py");424+ const cleanerSkill = skills.find((skill) => skill.name === dependency.cleanMarkdownWithSkill);
397- if (existsSync(cleaner)) {425+ const cleaner = cleanerSkill
426+ ? join(cleanerSkill.source, "scripts", "clean_markdown.py")
427+ : null;
428+ if (cleaner && existsSync(cleaner)) {
398 const cleaned = tryRun("python3", [cleaner, "--dir", destination, "--no-backup", "--quiet"], target);429 const cleaned = tryRun("python3", [cleaner, "--dir", destination, "--no-backup", "--quiet"], target);
399 if (!cleaned) console.warn(`cannbot: markdown cleanup failed for ${dependency.name}`);430 if (!cleaned) console.warn(`cannbot: markdown cleanup failed for ${dependency.name}`);
431+ } else {
432+ console.warn(`cannbot: cleanup Skill is unavailable: ${dependency.cleanMarkdownWithSkill}`);
400 }433 }
401 }434 }
402 if (dependency.expose) exposeDependency(destination, join(target, dependency.expose));435 if (dependency.expose) exposeDependency(destination, join(target, dependency.expose));
@@ -405,14 +438,14 @@ function provisionDependencies(pluginDir, plugin, target) {
405 return installed;438 return installed;
406}439}
407 440 
408-function installCodexPlugin(plugin, skills, target) {441+function installCodexPlugin(plugin, skills, target, skillInstallMode) {
409 const userHome = resolve(process.env.HOME || homedir());442 const userHome = resolve(process.env.HOME || homedir());
410 const pluginRoot = join(userHome, "plugins", plugin.name);443 const pluginRoot = join(userHome, "plugins", plugin.name);
411 const pluginSkills = join(pluginRoot, "skills");444 const pluginSkills = join(pluginRoot, "skills");
412 rmSync(pluginRoot, { recursive: true, force: true });445 rmSync(pluginRoot, { recursive: true, force: true });
413 mkdirSync(pluginSkills, { recursive: true });446 mkdirSync(pluginSkills, { recursive: true });
414 for (const skill of skills) {447 for (const skill of skills) {
415- copySkill(skill.source, join(pluginSkills, skill.name));448+ installSkill(skill.source, join(pluginSkills, skill.name), skillInstallMode);
416 }449 }
417 450 
418 const developerName = plugin.author?.name || "CANNBot";451 const developerName = plugin.author?.name || "CANNBot";
@@ -482,7 +515,11 @@ function installOpenCodePlugin(target) {
482 515 
483function install(options) {516function install(options) {
484 const sourceRoot = options.source ?? process.env.CANNBOT_SOURCE_ROOT ?? process.env.CANNBOT_SKILLS_PATH;517 const sourceRoot = options.source ?? process.env.CANNBOT_SOURCE_ROOT ?? process.env.CANNBOT_SKILLS_PATH;
485- const pluginDir = resolveBundle(sourceRoot, options.pluginId);518+ const sourceRepositoryRoot = sourceRoot ? resolve(sourceRoot) : null;
519+ const sourceDefinition = sourceRepositoryRoot
520+ ? pluginSourceDefinition(sourceRepositoryRoot, options.pluginId)
521+ : null;
522+ const pluginDir = resolveBundle(sourceRepositoryRoot, options.pluginId);
486 const manifestPath = join(pluginDir, ".claude-plugin", "plugin.json");523 const manifestPath = join(pluginDir, ".claude-plugin", "plugin.json");
487 if (!existsSync(manifestPath)) {524 if (!existsSync(manifestPath)) {
488 fail(`plugin not found: ${options.pluginId}`);525 fail(`plugin not found: ${options.pluginId}`);
@@ -490,23 +527,26 @@ function install(options) {
490 mkdirSync(options.target, { recursive: true });527 mkdirSync(options.target, { recursive: true });
491 528 
492 const plugin = readJson(manifestPath);529 const plugin = readJson(manifestPath);
493- const resolvedSkills = resolveSkills(plugin, pluginDir);530+ const skillInstallMode = sourceDefinition?.skillInstallMode ?? "copy";
531+ const resolvedSkills = sourceDefinition
532+ ? resolveSourceSkills(sourceRepositoryRoot, sourceDefinition)
533+ : resolveSkills(plugin, pluginDir);
494 const skillsDir = toolSkillsDir(options.target, options.tool);534 const skillsDir = toolSkillsDir(options.target, options.tool);
495 mkdirSync(skillsDir, { recursive: true });535 mkdirSync(skillsDir, { recursive: true });
496 const installedSkills = [];536 const installedSkills = [];
497 for (const skill of resolvedSkills) {537 for (const skill of resolvedSkills) {
498 const destination = join(skillsDir, skill.name);538 const destination = join(skillsDir, skill.name);
499- copySkill(skill.source, destination);539+ installSkill(skill.source, destination, skillInstallMode);
500 installedSkills.push(destination);540 installedSkills.push(destination);
501 }541 }
502 542 
503- const assets = installPluginAssets(pluginDir, plugin, options.target);543+ const assets = installPluginAssets(pluginDir, plugin, options.target, sourceDefinition);
504 const installedAgents = installAgents(pluginDir, plugin, options.tool, options.target, assets);544 const installedAgents = installAgents(pluginDir, plugin, options.tool, options.target, assets);
505 const instructions = installInstructions(pluginDir, plugin, options.target, options.tool, assets);545 const instructions = installInstructions(pluginDir, plugin, options.target, options.tool, assets);
506 const hookSettings = installClaudeHooks(pluginDir, options.target, options.tool, assets);546 const hookSettings = installClaudeHooks(pluginDir, options.target, options.tool, assets);
507- const dependencies = provisionDependencies(pluginDir, plugin, options.target);547+ const dependencies = provisionDependencies(pluginDir, plugin, options.target, resolvedSkills);
508 const nativePlugin = options.tool === "codex"548 const nativePlugin = options.tool === "codex"
509- ? installCodexPlugin(plugin, resolvedSkills, options.target)549+ ? installCodexPlugin(plugin, resolvedSkills, options.target, skillInstallMode)
510 : options.tool === "opencode"550 : options.tool === "opencode"
511 ? installOpenCodePlugin(options.target)551 ? installOpenCodePlugin(options.target)
512 : { initializer: null, discovery: `${toolConfigDir("", options.tool)}/skills` };552 : { initializer: null, discovery: `${toolConfigDir("", options.tool)}/skills` };
@@ -518,6 +558,7 @@ function install(options) {
518 sourcePackage: OPENCODE_PLUGIN_SPEC,558 sourcePackage: OPENCODE_PLUGIN_SPEC,
519 source: sourceRoot ? { kind: "repository" } : { kind: "package", package: OPENCODE_PLUGIN_SPEC },559 source: sourceRoot ? { kind: "repository" } : { kind: "package", package: OPENCODE_PLUGIN_SPEC },
520 tool: options.tool,560 tool: options.tool,
561+ skillInstallMode,
521 skills: installedSkills.map((path) => path.slice(options.target.length + 1)),562 skills: installedSkills.map((path) => path.slice(options.target.length + 1)),
522 agents: installedAgents.map((path) => path.slice(options.target.length + 1)),563 agents: installedAgents.map((path) => path.slice(options.target.length + 1)),
523 instructions: instructions?.slice(options.target.length + 1) ?? null,564 instructions: instructions?.slice(options.target.length + 1) ?? null,
@@ -11,7 +11,7 @@ usage() {
11Usage: bash init.sh [project] [opencode|codex|claude|trae|dsh] [install_path]11Usage: bash init.sh [project] [opencode|codex|claude|trae|dsh] [install_path]
12 12 
13Installs ${PLUGIN_ID} from this source checkout. The default tool is opencode13Installs ${PLUGIN_ID} from this source checkout. The default tool is opencode
14-and the default install path is the current working directory.14+and the default install path is the source repository root.
15EOF15EOF
16}16}
17 17 
@@ -40,7 +40,6 @@ if [[ "${LEVEL}" != "project" ]]; then
40 exit 140 exit 1
41fi41fi
42 42 
43-INSTALL_PATH="${INSTALL_PATH:-${PWD}}"
44if [[ -f "${PLUGIN_DIR}/../../script/bin/cannbot.js" ]]; then43if [[ -f "${PLUGIN_DIR}/../../script/bin/cannbot.js" ]]; then
45 REPOSITORY_ROOT="$(cd "${PLUGIN_DIR}/../.." && pwd)"44 REPOSITORY_ROOT="$(cd "${PLUGIN_DIR}/../.." && pwd)"
46 CLI="${REPOSITORY_ROOT}/script/bin/cannbot.js"45 CLI="${REPOSITORY_ROOT}/script/bin/cannbot.js"
@@ -51,6 +50,7 @@ else
51 echo "cannbot: cannot locate the unified installer from ${PLUGIN_DIR}" >&250 echo "cannbot: cannot locate the unified installer from ${PLUGIN_DIR}" >&2
52 exit 151 exit 1
53fi52fi
53+INSTALL_PATH="${INSTALL_PATH:-${REPOSITORY_ROOT}}"
54 54 
55exec node "${CLI}" install "${PLUGIN_ID}" \55exec node "${CLI}" install "${PLUGIN_ID}" \
56 --tool "${TOOL}" --target "${INSTALL_PATH}" --source "${REPOSITORY_ROOT}"56 --tool "${TOOL}" --target "${INSTALL_PATH}" --source "${REPOSITORY_ROOT}"
@@ -2,14 +2,14 @@
2 2 
3## 安装方式3## 安装方式
4 4 
5-普通用户在目标项目中执行各插件下方的 `npx` 命令。源码开发者进入对应插件目录执行一个脚本即可,缺失的 Skill submodule 会自动初始化5+普通用户在目标项目中执行各插件下方的 `npx` 命令。源码安装进入对应插件目录执行:
6 6 
7```bash7```bash
8-cd plugins/<plugin-id>8+cd plugins/<plugin>
9bash init.sh9bash init.sh
10```10```
11 11 
12-默认目标是当前目录、工具是 OpenCode。其他情况可执行 `bash init.sh project <opencode|codex|claude|trae|dsh> /path/to/target-project`。开发者不要手动执行 Node 命令;两种方式只在插件 bundle 的来源上不同,后续共用同一套 Node 安装流程12+安装器按初始化 Skill submodule,并将 Skill 链接到 cannbot 仓库根目录的客户端配置中
13 13 
14## 算子领域14## 算子领域
15 15 
Mscript/lib/plugin-bundle.js+3-0文件内容审核中,请稍后刷新重试
Mscript/test/install.test.js+153-13文件内容审核中,请稍后刷新重试