@kanyun-ai-infra/band-cli 0.1.0 → 0.2.0

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 (96) hide show
  1. package/bundled/examples/aitutor-tool/band/views/release/package.json +1 -1
  2. package/bundled/examples/aitutor-tool/band/views/tool-preview/package.json +1 -1
  3. package/bundled/examples/analysis/band/views/intake/package.json +1 -1
  4. package/bundled/examples/compliance/band/views/compliance/package.json +1 -1
  5. package/bundled/examples/content-review/band/views/checklist/package.json +1 -1
  6. package/bundled/examples/rush-site/band/views/rush-db/package.json +1 -1
  7. package/bundled/examples/rush-site/band/views/rush-deploy/package.json +1 -1
  8. package/bundled/examples/video/band/views/timeline/package.json +1 -1
  9. package/bundled/kernel/fixture.js +1 -1
  10. package/bundled/kernel/index.js +1 -1
  11. package/bundled/official/activity/package.json +4 -4
  12. package/bundled/official/approval/package.json +4 -4
  13. package/bundled/official/artifacts/package.json +4 -4
  14. package/bundled/official/chat/package.json +4 -4
  15. package/bundled/official/chunks/chunk-DIL5YSVK.js +1 -0
  16. package/bundled/official/comment/dist/index.js +2 -2
  17. package/bundled/official/comment/package.json +4 -4
  18. package/bundled/official/header/package.json +4 -4
  19. package/bundled/official/home/package.json +4 -4
  20. package/bundled/official/layouts/package.json +4 -4
  21. package/bundled/official/library/dist/index.js +1 -1
  22. package/bundled/official/library/package.json +4 -4
  23. package/bundled/official/members/dist/index.js +1 -1
  24. package/bundled/official/members/package.json +4 -4
  25. package/bundled/official/memory/dist/index.js +2 -2
  26. package/bundled/official/memory/package.json +4 -4
  27. package/bundled/official/native/package.json +2 -2
  28. package/bundled/official/params-form/package.json +4 -4
  29. package/bundled/official/plugins/package.json +4 -4
  30. package/bundled/official/project/dist/index.js +1 -1
  31. package/bundled/official/project/package.json +4 -4
  32. package/bundled/official/queue/package.json +4 -4
  33. package/bundled/official/settings/dist/index.js +1 -1
  34. package/bundled/official/settings/package.json +4 -4
  35. package/bundled/official/settings/plugin.yaml +7 -0
  36. package/bundled/official/shell/dist/index.js +1 -1
  37. package/bundled/official/shell/package.json +4 -4
  38. package/bundled/official/stages/package.json +4 -4
  39. package/bundled/official/threads/package.json +4 -4
  40. package/bundled/official/turn-bar/package.json +4 -4
  41. package/bundled/official/turn-trace/package.json +4 -4
  42. package/bundled/official/version-switch/dist/index.js +1 -1
  43. package/bundled/official/version-switch/package.json +4 -4
  44. package/bundled/official/viewer-entity/package.json +2 -2
  45. package/bundled/official/viewer-external/package.json +2 -2
  46. package/bundled/official/viewer-image/package.json +2 -2
  47. package/bundled/official/viewer-media/index.js +2 -2
  48. package/bundled/official/viewer-media/package.json +2 -2
  49. package/bundled/official/viewer-storyboard/index.js +2 -2
  50. package/bundled/official/viewer-storyboard/package.json +2 -2
  51. package/bundled/official/viewer-text/dist/index.js +2 -2
  52. package/bundled/official/viewer-text/package.json +4 -4
  53. package/bundled/official/viewer-tool/package.json +2 -2
  54. package/bundled/official/viewer-web/package.json +2 -2
  55. package/bundled/official/viewer-web-app/package.json +2 -2
  56. package/bundled/official/workshops/dist/index.js +2 -2
  57. package/bundled/official/workshops/package.json +4 -4
  58. package/bundled/official/workshops/plugin.yaml +2 -2
  59. package/bundled/skills/band-plugin/SKILL.md +20 -273
  60. package/bundled/skills/band-plugin/references/code.md +58 -0
  61. package/bundled/skills/band-plugin/references/dev.md +37 -0
  62. package/bundled/skills/band-plugin/references/execution.md +26 -0
  63. package/bundled/skills/band-plugin/references/manifest.md +65 -0
  64. package/bundled/skills/band-plugin/references/placement.md +34 -0
  65. package/bundled/skills/band-plugin/references/reuse.md +38 -0
  66. package/bundled/skills/band-plugin/references/rules.md +13 -0
  67. package/bundled/skills/band-plugin/references/shape.md +17 -0
  68. package/bundled/skills/band-plugin/references/testing-publish.md +35 -0
  69. package/bundled/skills/band-plugin/references/viewer-comments.md +25 -0
  70. package/bundled/skills/band-plugin/templates/dev/fixture.ts +15 -0
  71. package/bundled/skills/band-plugin/templates/dev/resources.ts +6 -0
  72. package/bundled/skills/band-plugin/templates/panel/index.ts +24 -0
  73. package/bundled/skills/band-plugin/templates/panel/package.json +12 -0
  74. package/bundled/skills/band-plugin/templates/panel/plugin.yaml +5 -0
  75. package/bundled/skills/band-plugin/templates/toolbar-dock/plugin.yaml +11 -0
  76. package/dev-host/Inspector.tsx +28 -10
  77. package/dev-host/app.tsx +11 -6
  78. package/dev-host/host.css +11 -0
  79. package/dev-host/main.tsx +5 -0
  80. package/dist/bin.js +3 -3
  81. package/dist/build-plugin.d.ts +1 -1
  82. package/dist/build-plugin.js +1 -1
  83. package/dist/{chunk-TN7Z3OF6.js → chunk-LGYGWMSY.js} +19 -17
  84. package/dist/{chunk-VVJNG5GV.js → chunk-Q2XJXNVH.js} +22 -15
  85. package/dist/{chunk-RAD6MMSL.js → chunk-XPLYTHNX.js} +1 -1
  86. package/dist/{dev-server-RAZQ32W7.js → dev-server-Q7NTJ3QD.js} +116 -11
  87. package/dist/fixture.d.ts +11 -0
  88. package/dist/index.d.ts +2 -2
  89. package/dist/index.js +3 -3
  90. package/dist/vite.d.ts +1 -1
  91. package/dist/vite.js +2 -2
  92. package/dist/{workshop-repo-CO6YMDee.d.ts → workshop-repo-DqoVdcGu.d.ts} +10 -10
  93. package/package.json +10 -7
  94. package/src/dev-managers.ts +16 -0
  95. package/src/inspector-plugins.ts +18 -0
  96. package/bundled/official/chunks/chunk-JI6ZNGOE.js +0 -1
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@kanyun-ai-infra/band-workshops",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "工坊目录、工坊详情、新建项目、新建与复制工坊、工坊设置与对话改工坊九个页面",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./dist/index.js"
8
8
  },
9
9
  "peerDependencies": {
10
- "@kanyun-ai-infra/band-plugin-sdk": "^1.213.0",
11
- "react": "^18.3.1",
12
- "react-dom": "^18.3.1",
10
+ "@kanyun-ai-infra/band-plugin-sdk": "^2.0.0",
11
+ "react": "^19.0.0",
12
+ "react-dom": "^19.0.0",
13
13
  "@kanyun-ai-infra/band-shell": "*"
14
14
  }
15
15
  }
@@ -1,4 +1,4 @@
1
- # 官方插件:工坊目录、工坊详情、新建项目、新建与复制工坊、工坊设置(v8 §7)。往外壳的 pages 贡献九页:/workshops 目录,
1
+ # 官方插件:工坊目录、工坊详情、新建项目、新建工坊与基于已有工坊新建、工坊设置(v8 §7)。往外壳的 pages 贡献九页:/workshops 目录,
2
2
  # /w/<工坊>/about 详情(工坊轻应用的「关于工坊」),/w/<工坊>/new 用这个工坊新建项目,/workshops/new 新建工坊,
3
3
  # /workshops/<工坊>/copy 复制这个工坊(不放在 /w/ 下:那一层的第二段是项目短名),/w/<工坊>/about/model 工坊设置 ·
4
4
  # 模型,/w/<工坊>/about/secrets 工坊设置 · 密钥,/w/<工坊>/new/resume 从「我的模型与凭据」配好回来后接着新建。旧地址写在 aliases,打开时改写成新地址。
@@ -39,7 +39,7 @@ contributes:
39
39
  - slot: "@kanyun-ai-infra/band-shell/pages"
40
40
  id: copy
41
41
  route: /workshops/:workshop/copy
42
- title: 复制工坊
42
+ title: 基于已有工坊新建
43
43
  frame: shell
44
44
  # 工坊设置 · 模型(建工坊的第 2 步也是这一页):/w/<工坊>/about/model,比 about 更具体;部署没开代建时页面写明没有这一步
45
45
  - slot: "@kanyun-ai-infra/band-shell/pages"
@@ -7,283 +7,30 @@ description: >
7
7
 
8
8
  # 写 Band 工坊插件
9
9
 
10
- 工坊插件是一段跑在 Band 项目页里的前端代码:自己的面板、查看器、阶段界面,或者查看器工具栏上的一个按钮加一个停靠位。插件就是一个包,往别的插件声明的**插槽**里贡献界面;只经内核发的 `ctx` 读写,看不到 Band 的服务端,也拿不到任何凭据。行为以 Band 用户文档「插件协议 1.0」为准。
10
+ 工坊插件是跑在 Band 项目页里的一段前端代码:面板、查看器、阶段界面,或查看器工具栏上的按钮加停靠位。它是一个包,往别的插件声明的**插槽**贡献界面;只经内核发的 `ctx` 读写,看不到服务端,拿不到凭据。行为以 Band 用户文档「插件协议 1.0」为准。
11
11
 
12
- ## 1. 先找现成的
12
+ 本文件只放流程。细节按需读 `references/`,骨架在 `templates/`;每个 reference 开头写了什么时候读它。
13
13
 
14
- 顺序:**先用官方插件,再找别的工坊共享的插件,都不合适才写新的**。官方插件在 `workshop.yaml` 的 `views` 里按 id 选用,不用写代码:查看器写在 `artifactTypes.<类型>.viewer`,面板写在 `panels` 的区里,要换掉某个官方面板写 `replace`。
14
+ ## 流程
15
15
 
16
- | 种类 | id(`views` 里写的) | 管什么 |
17
- |---|---|---|
18
- | 查看器 | `web` | 网页类产物:预览、元素标注、实时预览、直接编辑(`band-viewer-web`) |
19
- | 查看器 | `text` | Markdown 与纯文本,按段落、选中文字提意见(`band-viewer-text`) |
20
- | 查看器 | `image` | 图片与候选网格,框选提意见,版本并排对比(`band-viewer-image`) |
21
- | 查看器 | `media` | 音频与视频,按时间点提意见(`band-viewer-media`) |
22
- | 查看器 | `shots` | 分镜的镜头表:逐镜看静帧、时长、画面说明与旁白,和上一版按镜头 id 对比(`band-viewer-storyboard`) |
23
- | 查看器 | `web-app` | Web 应用走查:内嵌打开加了走查 SDK 的真实页面,按设备尺寸看、点元素提意见(`band-viewer-web-app`) |
24
- | 查看器 | `tool-pack` | 教具包:直接体验(投屏、平板、手机),调试指令(换示例、按指令集生成参数表单、看状态与事件记录),意见挂画面元素或某条指令(`band-viewer-tool`) |
25
- | 查看器 | `entity` | 实体集合(角色表这类结构化条目),按条目提意见(`band-viewer-entity`) |
26
- | 查看器 | `external` | 外部文档的快照,按文档块提意见(`band-viewer-external`) |
27
- | 查看器 | `material-text` | 素材的某个版本当文档看(Markdown、纯文本、网页源码),只读、不带阶段来源(`band-viewer-text`) |
28
- | 查看器 | `material-image` | 素材的某个版本当图片看,只读、不带阶段来源(`band-viewer-image`) |
29
- | 查看器 | `material-media` | 素材的某个版本当音频/视频看,只读、不带阶段来源(`band-viewer-media`) |
30
- | 面板 | `stages` | 阶段列表与进度,`{ stages: { compact: true } }` 收成流程条(`band-stages`) |
31
- | 面板 | `artifacts` | 产物列表,点一个切换主区(`band-artifacts`) |
32
- | 面板 | `threads` | 意见栏(`band-threads`) |
33
- | 面板 | `chat` | 和 Agent 的对话流(`band-chat`) |
34
- | 面板 | `activity` | 项目动态(`band-activity`) |
35
- | 面板 | `turn-trace` | 回合轨迹:一回合的步骤、流水、在做什么与用时(`band-turn-trace`) |
36
- | 面板 | `members` | 成员与角色(`band-members`) |
37
- | 面板 | `params-form` | 阶段参数表单(`band-params-form`) |
16
+ 1. **先找现成的**:官方插件在 `workshop.yaml` 的 `views` 里按 id 选,不用写代码。→ [reuse.md](references/reuse.md)
17
+ 2. **定形态**:面板、查看器、阶段界面,`band new plugin <名> --template panel|viewer|stage-view`。→ [shape.md](references/shape.md)
18
+ 3. **写清单**:`package.json` + `plugin.yaml`,改完跑 `band check`。→ [manifest.md](references/manifest.md)
19
+ 4. **写代码**:`index.ts` 默认导出 `definePlugin`,只用 `ctx`。→ [code.md](references/code.md);查看器要接评论再读 [viewer-comments.md](references/viewer-comments.md)
20
+ 5. **本地调试**:`band dev` + `band/dev/fixture.ts`。→ [dev.md](references/dev.md)
21
+ 6. **提交前**:`band check`、`band dev` 里走一遍、纯函数写单测。→ [testing-publish.md](references/testing-publish.md);自查对照 [rules.md](references/rules.md)
38
22
 
39
- 完整目录(说明、关键词、在用的工坊数)在 Band 的 `GET /api/plugins`,每个插件的详情(README、版本、对外面、资源权限、谁在用)在 `GET /api/plugins/<作用域>/<名>`,人看插件页(`/plugins`)。别的工坊共享的插件在 `views.plugins` 里钉精确版本(`'@rush-site/rush-db@1.2.0'`)或写 `^` 兼容区间(`'@rush-site/rush-db@^1.2.0'`,解析成区间内最大的可用版本)引用,发布工坊时由服务端从插件登记处解析;`band check` 配了登记处才核对,没配逐条标「未校验」;`band dev` 不装载,只在页面上列出来。写了新插件、别的工坊也用得上时,去掉 `package.json` 的 `"private": true` 就是共享。
23
+ ## 按问题找
40
24
 
41
- Agent 写在工坊级(`workshop.yaml` 顶层的 `agent`),作所有 agent 阶段的缺省;某个阶段确实要另一个 Agent 时,在那个阶段上写 `agent` 覆盖。按这个顺序选:
25
+ | 问题 | 读 |
26
+ |---|---|
27
+ | 文件该放哪、改哪份 yaml | [placement.md](references/placement.md) |
28
+ | 接到任务怎么排步骤、怎么汇报 | [execution.md](references/execution.md) |
29
+ | 要一份可抄的骨架 | `templates/panel/`(package.json、plugin.yaml、index.ts)、`templates/toolbar-dock/plugin.yaml`、`templates/dev/`(夹具与资源处理函数) |
42
30
 
43
- 1. 先用 Rondo(缺省,不用写)。
44
- 2. 不够时扩展 Rondo:在 `band/agent/` 写工坊说明(`instructions.md`,按阶段写 `stages/<阶段>.md`);项目记忆由 Agent 自己积累;要特殊系统环境时选 Band 维护的环境档 `runtime`,没有合适的档先向 Band 申请。
45
- 3. 还不够,比如要 MCP、自己的模型服务或密钥,或者申请不到合适的环境档,才写 `agenthub/agents/<名字>/` 定制 Agent,在工坊级 `agent` 写它的名字。
31
+ ## 不能破的几条
46
32
 
47
- 按阶段的差异优先写进 `stages/<阶段>.md`;只有某个阶段确实要另一套插件、环境或提示词时才给它单独写 `agent`。
48
-
49
- ## 2. 先定形态
50
-
51
- | 想做 | 模板 | 往这个插槽贡献 |
52
- |---|---|---|
53
- | 页面四周的一块面板(分镜墙、时间轴) | `panel` | `@kanyun-ai-infra/band-project/panels`,写 `title` |
54
- | 某种产物的显示方式 | `viewer` | `@kanyun-ai-infra/band-project/viewers`,写 `title` |
55
- | 某个阶段的专属界面(发布进度) | `stage-view` | `@kanyun-ai-infra/band-project/stage-views`,写 `stage` |
56
- | 查看器工具栏上的按钮 + 停靠位(数据库、环境变量) | 用 `panel` 起步改 | `@kanyun-ai-infra/band-viewer-web/toolbar`(写 `label`、`dock`)+ `@kanyun-ai-infra/band-project/docks`(写 `title`) |
57
- | 项目之外的整页(看板、报表、素材总览) | 用 `panel` 起步改 | `@kanyun-ai-infra/band-shell/pages`,写 `route: /w/:workshop/x/<页面>` 与 `title`;`peerDependencies` 加 `@kanyun-ai-infra/band-shell`(见用户文档「给工坊加页面」) |
58
-
59
- ```bash
60
- band new plugin <名> --template panel|viewer|stage-view [--stage <阶段>] [--title <标题>]
61
- ```
62
-
63
- 它生成 `band/views/<名>/package.json`、`plugin.yaml` 与 `index.ts`,并把 `./views/<名>` 加进 `workshop.yaml` 的 `views.plugins`。插件名用小写 kebab-case,不能和平台插件重名(comment、approval、threads、stages……)。
64
-
65
- ## 3. 包与清单
66
-
67
- `package.json`:名字、版本、依赖。
68
-
69
- ```json
70
- {
71
- "name": "@rush-site/rush-db",
72
- "version": "0.1.0",
73
- "private": true,
74
- "description": "查看与编辑建站项目的数据表,写线上前确认",
75
- "keywords": ["数据库", "表格"],
76
- "type": "module",
77
- "peerDependencies": {
78
- "@kanyun-ai-infra/band-plugin-sdk": "^1.0.0",
79
- "@kanyun-ai-infra/band-project": "*",
80
- "@kanyun-ai-infra/band-viewer-web": "*"
81
- },
82
- "peerDependenciesMeta": { "@kanyun-ai-infra/band-viewer-web": { "optional": true } }
83
- }
84
- ```
85
-
86
- - 包名带作用域,工坊插件用工坊名(`band new plugin` 取工坊仓库的目录名,去掉 `band-` 前缀);`@kanyun-ai-infra/band-` 前缀只留给官方插件。
87
- - `description`(一句话说明)与 `keywords` 显示在插件目录里,人和 Rondo 靠它们挑插件,写上。`"private": true` 表示只在本工坊用;工坊插件不发 npm 包。
88
- - `peerDependencies` 必须有 `@kanyun-ai-infra/band-plugin-sdk`(协议版本,`^1.0.0`)。往谁的插槽贡献、用谁的服务,就把谁写进来;官方插件还没有对外版本号,写 `"*"`。
89
-
90
- `plugin.yaml`:Band 的声明,不写 `name`(顶层写了 `name` 的是已废弃的 0.x 清单,`band check` 直接报错)。
91
-
92
- ```yaml
93
- entry: ./index.ts # 仓库内插件可以直接写 TS 源文件
94
- activation: [project] # 有项目钩子 scopes.project 时写;只有挂载面的插件不写
95
- contributes:
96
- - slot: "@kanyun-ai-infra/band-viewer-web/toolbar"
97
- id: open-db
98
- optional: true # 工坊没用网页查看器时静默不生效
99
- label: 数据库
100
- dock: db # 点了打开本插件的停靠位 db
101
- - slot: "@kanyun-ai-infra/band-project/docks"
102
- id: db
103
- title: 数据库
104
- anchors: [row] # 自己的锚点类型,要先声明再注册
105
- resources:
106
- database:
107
- api: 'rush:/projects/{project}/database'
108
- read: ['GET /**']
109
- write: ['POST /tables/*/rows', 'DELETE /tables/*/rows/*']
110
- prod: ['* /**?env=prod']
111
- ```
112
-
113
- - 所有字段 strict,写错字段名 `band check` 报错;贡献里只能写声明者列出的字段,必填的要写。
114
- - 贡献要收工坊的配置,就在这条贡献里写 `config`(平铺 schema,同阶段参数那一套),工坊在 `views` 里照着写(面板 `{ db: { columns: 3 } }`,查看器 `artifactTypes.<类型>.config`),代码从 `props.config` 读:
115
-
116
- ```yaml
117
- contributes:
118
- - slot: "@kanyun-ai-infra/band-project/panels"
119
- id: db
120
- title: 数据库
121
- config:
122
- columns: { type: number, default: 3 }
123
- ```
124
-
125
- 没声明的配置项、类型不对,`band check` 和发布都报错;声明了 `default` 的,没写时 `props.config` 里也有。
126
- - 要挂界面的贡献,`id` 就是代码里 `surfaces` 的键,多一个、少一个都是装载错误。工具栏按钮这种 `mount: false` 的贡献只是数据,不写挂载函数。
127
- - `resources` 的路径模式:`*` 匹配一段,`**` 匹配任意段;先看 `prod`,再看 `write`,最后 `read`;没声明的路径一律拒绝。
128
-
129
- ## 4. 代码
130
-
131
- ```ts
132
- import { type ProjectCtx, definePlugin, defineScopeState } from "@kanyun-ai-infra/band-plugin-sdk";
133
-
134
- // 每个项目一份:注册锚点类型,返回宿主给的完整类型名(查线程用它,不要手写)
135
- const rowKind = defineScopeState((ctx: ProjectCtx) =>
136
- ctx.anchors.register("row", {
137
- // 在给定版本里重新定位;找不到返回 null(锚点成孤儿,不要挪到别处)
138
- async resolve(payload, version, readFile) {
139
- const rows = await readRows(readFile); // readFile 读的是 version 这个版本
140
- return rows?.some((r) => r.id === payload.rowId) ? payload : null;
141
- },
142
- }),
143
- );
144
-
145
- export default definePlugin({
146
- // 可选:项目打开时调用一次(清单写 activation: [project]);项目关掉时它注册的东西一起收回
147
- scopes: {
148
- project(ctx) {
149
- rowKind.of(ctx);
150
- return undefined;
151
- },
152
- },
153
- surfaces: {
154
- db(ctx: ProjectCtx, mount: unknown) {
155
- const root = mount as HTMLElement;
156
- // 画界面;任何框架都行,宿主只给一个 DOM 元素(一列 flex 容器:要排成一行的控件自己包一层)
157
- const off = ctx.selection.subscribe(() => { /* 高亮 */ });
158
- return () => off(); // 卸载时清理订阅、定时器
159
- },
160
- },
161
- });
162
- ```
163
-
164
- - 挂载函数的参数直接写类型 `ctx: ProjectCtx, mount: unknown`,不用 `defineSurface`:它靠各插件包往 `SlotMap` 里补的类型推 ctx 和参数,工坊仓库里没装这些包,推不出来(ctx 会退成应用级的类型)。用户文档「插件协议」几页里的 `defineSurface` 写法是 Band 仓库里官方插件的写法。第三个参数 `props` 是 `{ owner, config, entry }`:`owner` 是插槽给的参数(查看器是 `{ projectId, artifactId, artifactType, versionId }`),`config` 是工坊在 `views` 里写的配置(按贡献声明的 `config` 校验过、补好了 `default`)。阶段界面拿到的是 `ViewCtx`(项目 ctx 多一个 `view`)。
165
- - 代码里只 import `@kanyun-ai-infra/band-plugin-sdk`、`@kanyun-ai-infra/band-plugin-react`、`@kanyun-ai-infra/band-contracts`、`react`、`react-dom`(含它们的子路径,如 `band-plugin-react/scroll`):这几个由 Band 提供同一份。别的官方插件(`band-project`、`band-native`……)不要 import,只经插槽和服务打交道。Band 内部的包(`band-engine`、`band-highlight`……)也不能 import:不是共享模块,发布构建会失败;官方查看器用的跨版本判定函数(`band-engine/relocate`)因此工坊用不到,要自己按语义判。`@kanyun-ai-infra/band-kernel` 只在测试文件(`*.test.ts`)和 `band/dev/` 夹具里用;插件代码 import 它,`band check` 和发布都会失败,要的能力从 `ctx` 取。
166
- - 源码入口(Band 用固定预设构建)暂时不能带自己的 npm 依赖,插件也不能写构建命令(`scripts.build`),这两种发布会失败(隔离构建上线后开放)。要用第三方库,先在本地构建成 `.js` 产物提交:入口放进子目录(如 `./dist/index.js`),依赖打进产物,`package.json` 里的依赖可以留着(发布不装),但不要写 `scripts.build`;产物里只 import 上一条那几个包。
167
- - 按项目存的状态放进 `defineScopeState`,不放模块顶层(模块只加载一次,两个项目会共用顶层变量)。
168
-
169
- `ctx` 常用的几样(完整类型见 `@kanyun-ai-infra/band-plugin-sdk` 的 `ProjectCtx`):
170
-
171
- - `ctx.version`:`current()` 当前版本(可能是 null),`subscribe` 换版本,`list(artifactId)` 一份产物的全部版本(从旧到新),`readFile(path)`、`objectUrl(path)` 读当前版本的文件(可传 `{ versionId }` 读别的版本)。查看器的 `ctx.version` 钉在正在看的版本上,换版本时整个重挂。
172
- - `ctx.selection`:本项目里插件共享的选中,`get` / `set` / `subscribe`。五种:`{ kind: "artifact", artifactId }`、`{ kind: "entity", entityType, id, artifactId? }`、`{ kind: "timecode", seconds, end?, artifactId?, path? }`、`{ kind: "anchor", anchor }`、`{ kind: "stage", stage }`。`subscribe` 收到 `{ value, source }`,`source === ctx.plugin` 是自己的回声,跳过;不认识的 `kind` 当作与己无关。插件之间联动只靠它,不要互相 import 状态。
173
- - `ctx.anchors.make(kind, payload, snapshot?)`:在当前版本上造锚点。内置类型直接写;自己注册的写短名,内核补成 `plugin:<包名>/<kind>`。内置类型的 payload 都要带 `label`,内容在文件里的还要带 `path`,多写字段也算不合写法(1.x 只在控制台警告,2.0 起报错;`shot` 现在就抛错)。自己注册的类型也把 `label` 写进 payload:线程里显示的位置文字取它,`AnchorType.label` 现在没人调用。形状:`entity` 是 `{ path, label, entityType, id, hash?, point? }`,`text-range` 是 `{ path, label, quote: { exact, prefix?, suffix? }, pos?, lines? }`,`region` 是 `{ path, label, rect: { x, y, w, h }, page? }`(0–1 比例),`timecode` 是 `{ path, label, seconds, end?, rect? }`,`shot` 是 `{ path: "storyboard.json", label, shotId, takeId? }`。
174
- - `ctx.threads.list(filter)` 读线程;`ctx.threads.openAt(anchor, { draft, handToAgent, intent })` 请锁定的评论框在锚点上打开(`intent` 只有 `"pick"`:镜头候选上「用这条」,锚点是带 `takeId` 的 `shot`);`ctx.threads.open(id)` 打开已有线程。插件不能自己建线程。
175
- - 引用:`ctx.threads.quote(threadId, commentId)` 把一条消息、`ctx.threads.quoteAnchor(anchor)` 把一处位置作为引用放进发送框,都不发送。
176
- - `ctx.gate.open(stage, key?)` 请批准面板打开;`ctx.stages.list / get / start / rerun / rerunWith` 读写阶段运行。
177
- - `ctx.layers.open({ title, size, dismissable }, render)`:开一层浮层(详情、大图)。宿主画标题栏,管 Esc、遮罩、焦点、滚动锁,开层时退出标注模式;`render(el)` 往 `el` 里画,返回清理函数。浮层里的查看器不画意见编号,要在内容上提意见放在查看器挂载点里。
178
- - `ctx.storage.get / set / remove / list`:插件自己的小键值,按(项目,插件)一份,值是任意 JSON;成员能写,看得到项目的人能读。不是协作状态,不进版本和线程。超限抛 `StorageLimitError`。
179
- - `ctx.attachments.importExternal?.({ connector, ref, target? })`:用当前成员自己的连接导入附件快照,或指定阶段与类型导入外部版本;调用前说明「用你的权限读,项目成员都会看到这份快照」。首页 `ctx.app.attachments.importExternal` 只落本人暂存附件。方法可缺省,不能自行取凭据或绕过宿主调用外部平台。
180
- - `ctx.resources.fetch(key, path, init)`:只能调清单里声明过的接口。读直接放行;写记审计;`prod` 先由锁定插件弹确认,成员拒绝时抛 `ResourceDeniedError`。
181
- - `ctx.events.subscribe("thread.*", fn)`:按类型或前缀订阅事件。事件名以 `EVENT_TYPES`(`@kanyun-ai-infra/band-plugin-sdk` 导出)为准;关心意见就订 `EVENT_GROUPS.threads`(含评论、改动请求、回合),关心流程就订 `EVENT_GROUPS.stages`,别只订 `thread.*`。
182
- - 打开停靠位:`(ctx.services.get("@kanyun-ai-infra/band-project/dock") as { open(key: string): void }).open("<包名>/<停靠位 id>")`。工具栏按钮写了 `dock` 的,查看器替你开。
183
- - `ctx.member`(`can("comment")` 判能不能提意见)、`ctx.params`、`ctx.plugin`(本插件的包名)。
184
- - 只给官方插件、工坊插件拿到 `undefined` 的:`notify`、`approvalSettings`、`memberSettings`、`visibilitySettings`、`artifactShares`、`version.archiveUrl`、`turns.requestStop`、`project.shared`、`project.notices`、`project.upgrade`。不要写依赖它们的功能。
185
-
186
- ### 查看器接评论
187
-
188
- 查看器回答「意见在哪」,意见编号、评论框、孤儿列表、标注模式的开关都归锁定的评论插件。查看器里不画意见编号,也不自己画评论框。完整例子见用户文档「自定义查看器接评论」,示例工坊 `aitutor-tool` 的 `band/views/tool-preview` 是完整的工坊查看器。
189
-
190
- 1. 提意见:把点的地方 `ctx.anchors.make(...)` 成锚点,`ctx.threads.openAt(anchor)`。能用内置类型就用内置类型(条目、卡片用 `entity`,视频镜头用 `shot`):自己注册的 `plugin:*` 类型现在评论框不贴着内容弹,只贴在按钮下。
191
- 2. 挂载时登记锚点宿主,评论插件按它画意见编号:
192
-
193
- ```ts
194
- const off = ctx.anchors.host({
195
- kinds: ["entity"], // 内置类型名;自己注册的写 register 的返回值
196
- locate, // (anchor) => AnchorPlace,必须同步
197
- reveal, // async (anchor) => boolean,用 band-plugin-react/scroll 的 scrollIntoPanel 滚到它;做不到返回 false
198
- onLayout(fn) { // 内容重画、换文件、图片撑高时调 fn(页面滚动、挂载点尺寸评论插件自己跟)
199
- listeners.add(fn);
200
- return () => listeners.delete(fn);
201
- },
202
- });
203
- ```
204
-
205
- `locate` 返回 `{ at: "rects", rects: [{ x, y, width, height }], status? }`(相对挂载点,文字跨行多个矩形,宽高为 0 是一个点)、`{ at: "track", x, y, w? }`(音视频时间轴)、`{ at: "elsewhere" }`(这一版里有、不在视口)、`{ at: "pending" }`(布局没好,等 `onLayout`)或 `{ at: "none" }`(孤儿)。`status` 是跨版本结果:不写是原位,`"changed"` 内容改过,`"moved"` 挪了位置。要读文件才判得出的先异步算好,`locate` 里只取结果。
206
- 3. 点击本身有含义的内容(图片、网页、iframe):宿主写 `annotate: true`(评论插件因此接 C 进、Esc 出),`ctx.anchors.annotating.subscribe` 跟着开关,只在模式开着时把点击变成锚点。文字、清单类查看器可以不写,不开模式也能提意见。
207
- 4. Esc 一次只关一样,先后是评论框 > 标注模式 > 浮层。查看器自己也接 Esc 时,先看 `e.defaultPrevented`,已被处理过的不再处理,处理了就 `preventDefault`。
208
- 5. 可选:音视频给 `candidates()` 报锚点小标,视觉类给 `thumbnail(anchor)` 附截图。
209
-
210
- ## 5. 规矩
211
-
212
- - **不碰协作状态**:评论、批准、队列、回合、版本只有锁定插件能改。工坊插件要提意见就 `openAt`,要批准就 `gate.open`。
213
- - **异步要防乱序**:换版本、切环境时旧请求的结果可能晚到。每次加载记一个序号,结果回来时对不上就丢掉;读不到新数据时清空界面,不留旧数据(尤其是带「删除」按钮的表格)。
214
- - **读不到不抛错**:版本里可能没有你要的文件、JSON 可能坏了。界面显示「这个版本没有……」,锚点解析返回 null。
215
- - **路径参数要编码**:`ctx.resources.fetch` 的 path 里拼进去的 id 用 `encodeURIComponent`。
216
- - **样式只用 Band Design token**:页面提供 `--panel`、`--ink`、`--muted`、`--accent`、`--r-lg` 等 CSS 变量,插件里不写死颜色,类名带插件名前缀。琥珀色(`--warn*`)只给部署方案装的插件(锁定插件、应用级页面)用,工坊插件不用。
217
- - **工坊页面跟工坊最新发布走,项目页跟项目钉住的版本走**:同一工坊里两者可能是不同版本的插件代码。页面里不要假设项目页的面板、服务是同一版;页面只读本工坊的应用数据(`ctx.app.data` 已固定在本工坊,没有 `write`)。路由只写 `/w/:workshop/x/<页面>`,不写 `aliases`、`opens`。
218
- - **不放凭据**:接口身份由 Band 代理注入,插件代码和仓库里都不出现 token、连接串。
219
- - **渲染出错只坏自己那一块**:用 React 就用 `@kanyun-ai-infra/band-plugin-react` 的 `reactMount` 挂,它给每个挂载面包了错误边界,渲染期抛错显示插件名、原因和「重试」。自己 `createRoot` 或用别的框架的,要自己包错误边界;不包的话那一块只剩空白,没有说明也没有重试,宿主管不到。挂载函数同步抛错由宿主画错误卡。
220
-
221
- ## 6. 本地调试:`band dev`
222
-
223
- ```bash
224
- band dev [工坊仓库目录] [--port 5310]
225
- ```
226
-
227
- 本地起一个插件宿主,跑的是 Band 项目页本身:按 `workshop.yaml` 的 `views` 摆面板、按产物类型挑查看器,加载仓库里的工坊插件,`ctx` 由夹具提供;右边的「夹具」栏显示选中、线程、审计和事件。改插件代码或 yaml,保存后页面自动重载。官方插件(查看器、意见栏、流程条)随命令行一起装,和你的插件走同一条路(`package.json` + 清单 + 入口);别的工坊共享的插件(`@scope/name@x.y.z`)暂不装载,页面上标出来。`band` 命令行从 npm 装(`npm install -g @kanyun-ai-infra/band-cli`),工坊仓库本身不用装依赖。`entry` 相对 plugin.yaml 所在目录,不能用 `../` 跳出工坊仓库(`band check` 会报)。
228
-
229
- 夹具放在 `band/dev/`:
230
-
231
- ```ts
232
- // band/dev/fixture.ts —— 必须有
233
- import type { FixtureData } from "@kanyun-ai-infra/band-cli/fixture";
234
- export const fixture: FixtureData = {
235
- projectId: "prj_demo",
236
- runId: "run_demo_1",
237
- versions: [/* Version;打开时看第一个版本所在的产物 */],
238
- artifacts: [{ id: "art_storyboard", name: "分镜", artifactType: "storyboard" }], // 多种产物时要写;项目页按类型挑查看器
239
- files: { "<versionId>": { "storyboard.json": "{...}" } },
240
- stages: [{ stage: "deploy", status: "ready" }],
241
- };
242
- // 可选:export const member = { id, displayName, roles, permissions }
243
-
244
- // band/dev/resources.ts —— 插件用了 resources 时写;键是清单里的资源名
245
- import type { ProxyRequest } from "@kanyun-ai-infra/band-cli/fixture";
246
- export const resources = {
247
- database: (req: ProxyRequest) => new Response(JSON.stringify([]), { status: 200 }),
248
- };
249
- ```
250
-
251
- `files` 的值可以是字符串、`Uint8Array`,或 `{ url }`(视频、音频这类二进制放在 `band/dev/` 里,写 `{ url: new URL("./cut.webm", import.meta.url).href }`)。`ctx.version.objectUrl` 对字节返回 blob URL,对 `{ url }` 原样返回,`<video>`/`<img>` 能直接放。
252
-
253
- 资源处理函数收到的 `req` 带 `method`、`path`、`body`、`level`(read/write/prod)。默认写内存 mock;要连测试环境,就在处理函数里自己 `fetch` 测试环境的地址,凭据从本机环境变量读,不进仓库。
254
-
255
- 页面里的锁定插件就是线上那五个:提意见、批准、确认访问线上都在它们的真实界面里走;请求有没有发对,看「夹具」栏的线程(锚点类型与 payload)和审计(哪个插件发的)。版本切换在主区上方。插件名在页面上写全名,就是 `package.json` 的 `name`,自定义锚点类型是 `plugin:<包名>/<kind>`:查线程时用 `ctx.anchors.register` 的返回值,不要手写类型名。点流程条上的阶段,阶段界面在停靠区打开。工坊写了 `views.roles` 时,地址栏 `?role=<角色>`(或页头的角色下拉框)换成员角色:面板按 `views.roles` 摆,权限按工坊 `roles` 里这个角色的那些。
256
-
257
- ## 7. 提交前
258
-
259
- - `band check`:校验 `workshop.yaml`、每个插件的 `package.json` 与 `plugin.yaml`,再拼一遍装配(插槽在不在、字段对不对、声明者写没写进 `peerDependencies`)。
260
- - 在 `band dev` 里走一遍:换版本、联动选中、提意见、资源读写(看「夹具」栏的审计)。
261
- - 插件逻辑能抽成纯函数的(解析文件、时间点落在哪个镜头),给它写单测。
262
- - 要测挂载后的行为,用 `@kanyun-ai-infra/band-kernel/testing` 的测试台,不用自己拼内核。它要从 `node_modules` 装官方插件(外壳、项目页),这些不发 npm,所以**只在 Band 仓库里能用**;工坊仓库里挂载后的行为在 `band dev` 里走。Band 仓库里的写法:
263
-
264
- ```ts
265
- // @vitest-environment jsdom
266
- import { createTestbed, loadPluginPackage } from "@kanyun-ai-infra/band-kernel/testing";
267
- const bed = await createTestbed({
268
- plugins: [
269
- // 插槽的声明者要一起装,和真实宿主一样
270
- await loadPluginPackage("@kanyun-ai-infra/band-shell", import.meta.url),
271
- await loadPluginPackage("@kanyun-ai-infra/band-project", import.meta.url),
272
- { packageJson, manifest, module: plugin }, // 被测插件
273
- ],
274
- data: fixture,
275
- });
276
- const root = bed.mount("@kanyun-ai-infra/band-project/panels", {
277
- key: "@<工坊>/checklist/main", // 插件全名 / 贡献 id
278
- owner: { projectId: fixture.projectId },
279
- });
280
- await bed.settle();
281
- // 断言 root 的 DOM、bed.requests(开线程等发给锁定插件的请求)、bed.ctx("@<工坊>/checklist").selection.get()
282
- bed.dispose();
283
- ```
284
-
285
- ## 发布工坊
286
-
287
- `band publish <工坊仓库目录> --url <Band 服务地址> --workshop <id>` 先校验并要求工作区干净,用作者的 git 凭据推送远端同名分支(须与工坊发布分支一致),再用 `BAND_TOKEN` 的 Bearer 身份只调一次 POST releases,不调用工坊 GET。`--workshop` 必填,id 从工坊页地址 `/w/<id>` 获取。可 `--no-push` 跳过推送。不要将 token 写进工坊仓库或命令文本;真实服务发布依赖平台 token 功能,缺少鉴权时继续用页面。构建命令与带依赖预设在生产隔离端口未装配前仍关闭。
288
-
289
- `band preview [分支] --workshop <id> [--project <项目 id>]`:把已 push 的分支建成预览快照(只读工坊仓库、不发新版本),带 `--project` 时打印 `?preview=` 的项目链接,只有工坊管理员能打开;token 要 `workshops:preview` scope。预览页的写入会落到真实项目。
33
+ - 只 import `band-plugin-sdk`、`band-plugin-react`、`band-contracts`、`react`、`react-dom`,不 import 别的官方插件和 Band 内部包。
34
+ - 不改协作状态(评论、批准、队列、回合、版本),只用 `ctx.threads.openAt`、`ctx.gate.open`。
35
+ - 不放凭据;接口由 Band 代理注入身份。
36
+ - 按项目存的状态放 `defineScopeState`,不放模块顶层。
@@ -0,0 +1,58 @@
1
+ # 代码与 ctx
2
+
3
+ > 什么时候读:写 `index.ts`:挂载函数、`definePlugin`、`ctx` 各服务怎么用时。
4
+
5
+ ```ts
6
+ import { type ProjectCtx, definePlugin, defineScopeState } from "@kanyun-ai-infra/band-plugin-sdk";
7
+
8
+ // 每个项目一份:注册锚点类型,返回宿主给的完整类型名(查线程用它,不要手写)
9
+ const rowKind = defineScopeState((ctx: ProjectCtx) =>
10
+ ctx.anchors.register("row", {
11
+ // 在给定版本里重新定位;找不到返回 null(锚点成孤儿,不要挪到别处)
12
+ async resolve(payload, version, readFile) {
13
+ const rows = await readRows(readFile); // readFile 读的是 version 这个版本
14
+ return rows?.some((r) => r.id === payload.rowId) ? payload : null;
15
+ },
16
+ }),
17
+ );
18
+
19
+ export default definePlugin({
20
+ // 可选:项目打开时调用一次(清单写 activation: [project]);项目关掉时它注册的东西一起收回
21
+ scopes: {
22
+ project(ctx) {
23
+ rowKind.of(ctx);
24
+ return undefined;
25
+ },
26
+ },
27
+ surfaces: {
28
+ db(ctx: ProjectCtx, mount: unknown) {
29
+ const root = mount as HTMLElement;
30
+ // 画界面;任何框架都行,宿主只给一个 DOM 元素(一列 flex 容器:要排成一行的控件自己包一层)
31
+ const off = ctx.selection.subscribe(() => { /* 高亮 */ });
32
+ return () => off(); // 卸载时清理订阅、定时器
33
+ },
34
+ },
35
+ });
36
+ ```
37
+
38
+ - 挂载函数的参数直接写类型 `ctx: ProjectCtx, mount: unknown`,不用 `defineSurface`:它靠各插件包往 `SlotMap` 里补的类型推 ctx 和参数,工坊仓库里没装这些包,推不出来(ctx 会退成应用级的类型)。用户文档「插件协议」几页里的 `defineSurface` 写法是 Band 仓库里官方插件的写法。第三个参数 `props` 是 `{ owner, config, entry }`:`owner` 是插槽给的参数(查看器是 `{ projectId, artifactId, artifactType, versionId }`),`config` 是工坊在 `views` 里写的配置(按贡献声明的 `config` 校验过、补好了 `default`)。阶段界面拿到的是 `ViewCtx`(项目 ctx 多一个 `view`)。
39
+ - 代码里只 import `@kanyun-ai-infra/band-plugin-sdk`、`@kanyun-ai-infra/band-plugin-react`、`@kanyun-ai-infra/band-contracts`、`react`、`react-dom`(含它们的子路径,如 `band-plugin-react/scroll`):这几个由 Band 提供同一份。别的官方插件(`band-project`、`band-native`……)不要 import,只经插槽和服务打交道。Band 内部的包(`band-engine`、`band-highlight`……)也不能 import:不是共享模块,发布构建会失败;官方查看器用的跨版本判定函数(`band-engine/relocate`)因此工坊用不到,要自己按语义判。`@kanyun-ai-infra/band-kernel` 只在测试文件(`*.test.ts`)和 `band/dev/` 夹具里用;插件代码 import 它,`band check` 和发布都会失败,要的能力从 `ctx` 取。
40
+ - 源码入口(Band 用固定预设构建)暂时不能带自己的 npm 依赖,插件也不能写构建命令(`scripts.build`),这两种发布会失败(隔离构建上线后开放)。要用第三方库,先在本地构建成 `.js` 产物提交:入口放进子目录(如 `./dist/index.js`),依赖打进产物,`package.json` 里的依赖可以留着(发布不装),但不要写 `scripts.build`;产物里只 import 上一条那几个包。
41
+ - 按项目存的状态放进 `defineScopeState`,不放模块顶层(模块只加载一次,两个项目会共用顶层变量)。
42
+
43
+ `ctx` 常用的几样(完整类型见 `@kanyun-ai-infra/band-plugin-sdk` 的 `ProjectCtx`):
44
+
45
+ - `ctx.version`:`current()` 当前版本(可能是 null),`subscribe` 换版本,`list(artifactId)` 一份产物的全部版本(从旧到新),`readFile(path)`、`objectUrl(path)` 读当前版本的文件(可传 `{ versionId }` 读别的版本)。查看器的 `ctx.version` 钉在正在看的版本上,换版本时整个重挂。
46
+ - `ctx.selection`:本项目里插件共享的选中,`get` / `set` / `subscribe`。五种:`{ kind: "artifact", artifactId }`、`{ kind: "entity", entityType, id, artifactId? }`、`{ kind: "timecode", seconds, end?, artifactId?, path? }`、`{ kind: "anchor", anchor }`、`{ kind: "stage", stage }`。`subscribe` 收到 `{ value, source }`,`source === ctx.plugin` 是自己的回声,跳过;不认识的 `kind` 当作与己无关。插件之间联动只靠它,不要互相 import 状态。
47
+ - `ctx.anchors.make(kind, payload, snapshot?)`:在当前版本上造锚点。内置类型直接写;自己注册的写短名,内核补成 `plugin:<包名>/<kind>`。内置类型的 payload 都要带 `label`,内容在文件里的还要带 `path`,多写字段也算不合写法(当前只在控制台警告,以后的协议大版本会改成报错;`shot` 现在就抛错)。自己注册的类型也把 `label` 写进 payload:线程里显示的位置文字取它,`AnchorType.label` 现在没人调用。形状:`entity` 是 `{ path, label, entityType, id, hash?, point? }`,`text-range` 是 `{ path, label, quote: { exact, prefix?, suffix? }, pos?, lines? }`,`region` 是 `{ path, label, rect: { x, y, w, h }, page? }`(0–1 比例),`timecode` 是 `{ path, label, seconds, end?, rect? }`,`shot` 是 `{ path: "storyboard.json", label, shotId, takeId? }`。
48
+ - `ctx.threads.list(filter)` 读线程;`ctx.threads.openAt(anchor, { draft, handToAgent, intent })` 请锁定的评论框在锚点上打开(`intent` 只有 `"pick"`:镜头候选上「用这条」,锚点是带 `takeId` 的 `shot`);`ctx.threads.open(id)` 打开已有线程。插件不能自己建线程。
49
+ - 引用:`ctx.threads.quote(threadId, commentId)` 把一条消息、`ctx.threads.quoteAnchor(anchor)` 把一处位置作为引用放进发送框,都不发送。
50
+ - `ctx.gate.open(stage, key?)` 请批准面板打开;`ctx.stages.list / get / start / rerun / rerunWith` 读写阶段运行。
51
+ - `ctx.layers.open({ title, size, dismissable }, render)`:开一层浮层(详情、大图)。宿主画标题栏,管 Esc、遮罩、焦点、滚动锁,开层时退出标注模式;`render(el)` 往 `el` 里画,返回清理函数。浮层里的查看器不画意见编号,要在内容上提意见放在查看器挂载点里。
52
+ - `ctx.storage.get / set / remove / list`:插件自己的小键值,按(项目,插件)一份,值是任意 JSON;成员能写,看得到项目的人能读。不是协作状态,不进版本和线程。超限抛 `StorageLimitError`。
53
+ - `ctx.attachments.importExternal?.({ connector, ref, target? })`:用当前成员自己的连接导入附件快照,或指定阶段与类型导入外部版本;调用前说明「用你的权限读,项目成员都会看到这份快照」。首页 `ctx.app.attachments.importExternal` 只落本人暂存附件。方法可缺省,不能自行取凭据或绕过宿主调用外部平台。
54
+ - `ctx.resources.fetch(key, path, init)`:只能调清单里声明过的接口。读直接放行;写记审计;`prod` 先由锁定插件弹确认,成员拒绝时抛 `ResourceDeniedError`。
55
+ - `ctx.events.subscribe("thread.*", fn)`:按类型或前缀订阅事件。事件名以 `EVENT_TYPES`(`@kanyun-ai-infra/band-plugin-sdk` 导出)为准;关心意见就订 `EVENT_GROUPS.threads`(含评论、改动请求、回合),关心流程就订 `EVENT_GROUPS.stages`,别只订 `thread.*`。
56
+ - 打开停靠位:`(ctx.services.get("@kanyun-ai-infra/band-project/dock") as { open(key: string): void }).open("<包名>/<停靠位 id>")`。工具栏按钮写了 `dock` 的,查看器替你开。
57
+ - `ctx.member`(`can("comment")` 判能不能提意见)、`ctx.params`、`ctx.plugin`(本插件的包名)。
58
+ - 只给官方插件、工坊插件拿到 `undefined` 的:`notify`、`approvalSettings`、`memberSettings`、`visibilitySettings`、`artifactShares`、`version.archiveUrl`、`turns.requestStop`、`project.shared`、`project.notices`、`project.upgrade`、`project.remove`;`app.data.write.workshopArchive`、`people`(人员目录)只给部署方案插件。不要写依赖它们的功能。
@@ -0,0 +1,37 @@
1
+ # 本地调试:band dev
2
+
3
+ > 什么时候读:要在本地跑 `band dev`、写 `band/dev/` 夹具或 resources 处理函数时。
4
+
5
+ ```bash
6
+ band dev [工坊仓库目录] [--port 5310]
7
+ ```
8
+
9
+ 本地起一个插件宿主,跑的是 Band 项目页本身:按 `workshop.yaml` 的 `views` 摆面板、按产物类型挑查看器,加载仓库里的工坊插件,`ctx` 由夹具提供;右边的「夹具」栏显示选中、线程、审计和事件。改插件代码或 yaml,保存后页面自动重载。官方插件(查看器、意见栏、流程条)随命令行一起装,和你的插件走同一条路(`package.json` + 清单 + 入口);别的工坊共享的插件(`@scope/name@x.y.z`)暂不装载,页面上标出来。`band` 命令行从 npm 装(`npm install -g @kanyun-ai-infra/band-cli`),工坊仓库本身不用装依赖。`entry` 相对 plugin.yaml 所在目录,不能用 `../` 跳出工坊仓库(`band check` 会报)。
10
+
11
+ 夹具放在 `band/dev/`:
12
+
13
+ ```ts
14
+ // band/dev/fixture.ts —— 必须有
15
+ import type { FixtureData } from "@kanyun-ai-infra/band-cli/fixture";
16
+ export const fixture: FixtureData = {
17
+ projectId: "prj_demo",
18
+ runId: "run_demo_1",
19
+ versions: [/* Version;打开时看第一个版本所在的产物 */],
20
+ artifacts: [{ id: "art_storyboard", name: "分镜", artifactType: "storyboard" }], // 多种产物时要写;项目页按类型挑查看器
21
+ files: { "<versionId>": { "storyboard.json": "{...}" } },
22
+ stages: [{ stage: "deploy", status: "ready" }],
23
+ };
24
+ // 可选:export const member = { id, displayName, roles, permissions }
25
+
26
+ // band/dev/resources.ts —— 插件用了 resources 时写;键是清单里的资源名
27
+ import type { ProxyRequest } from "@kanyun-ai-infra/band-cli/fixture";
28
+ export const resources = {
29
+ database: (req: ProxyRequest) => new Response(JSON.stringify([]), { status: 200 }),
30
+ };
31
+ ```
32
+
33
+ `files` 的值可以是字符串、`Uint8Array`,或 `{ url }`(视频、音频这类二进制放在 `band/dev/` 里,写 `{ url: new URL("./cut.webm", import.meta.url).href }`)。`ctx.version.objectUrl` 对字节返回 blob URL,对 `{ url }` 原样返回,`<video>`/`<img>` 能直接放。
34
+
35
+ 资源处理函数收到的 `req` 带 `method`、`path`、`body`、`level`(read/write/prod)。默认写内存 mock;要连测试环境,就在处理函数里自己 `fetch` 测试环境的地址,凭据从本机环境变量读,不进仓库。
36
+
37
+ 页面里的锁定插件就是线上那五个:提意见、批准、确认访问线上都在它们的真实界面里走;请求有没有发对,看「夹具」栏的线程(锚点类型与 payload)和审计(哪个插件发的)。版本切换在主区上方。插件名在页面上写全名,就是 `package.json` 的 `name`,自定义锚点类型是 `plugin:<包名>/<kind>`:查线程时用 `ctx.anchors.register` 的返回值,不要手写类型名。点流程条上的阶段,阶段界面在停靠区打开。工坊写了 `views.roles` 时,地址栏 `?role=<角色>`(或页头的角色下拉框)换成员角色:面板按 `views.roles` 摆,权限按工坊 `roles` 里这个角色的那些。
@@ -0,0 +1,26 @@
1
+ # 执行层写法:Claude Code 在工坊仓库里怎么推进
2
+
3
+ > 什么时候读:接到「写一个插件 / 改一个插件」的任务、要排步骤和自检顺序时;也给人看 Claude Code 应当怎么做。
4
+
5
+ ## 推进顺序
6
+
7
+ 1. 读任务,判断是不是已有官方插件能解决([reuse.md](reuse.md)),能就只改 `workshop.yaml`,不写代码。
8
+ 2. 定形态,`band new plugin <名> --template ...` 起骨架([shape.md](shape.md));不手建目录。骨架不对口时看 `templates/` 里的现成片段。
9
+ 3. 先改 `plugin.yaml` 与 `package.json`([manifest.md](manifest.md)),每改一处跑一次 `band check`;清单错了代码再对也装载不了。
10
+ 4. 写 `index.ts`([code.md](code.md)),挂载函数里的 `ctx` 只用文档列出的服务。
11
+ 5. 补 `band/dev/fixture.ts`([dev.md](dev.md)),`band dev` 打开页面,把面板、选中联动、提意见、资源读写各走一遍。
12
+ 6. 提交前按 [testing-publish.md](testing-publish.md) 过一遍,再对照 [rules.md](rules.md)。
13
+
14
+ ## 写法约定
15
+
16
+ - 一次只改一个插件;改清单与改代码分两次保存,让 `band check` 的报错指到具体一步。
17
+ - 报错先读全文再改:`band check` 的消息带插件名与字段路径,照路径改,不猜。
18
+ - 不确定的 `ctx` 方法,去 `@kanyun-ai-infra/band-plugin-sdk` 的类型里查(`ProjectCtx`),不凭印象写;`undefined` 的服务(见 [code.md](code.md) 末条)一律不用。
19
+ - 读不到文件、JSON 坏了、版本里没有该文件,一律显示空态,不抛错。
20
+ - 做完汇报写清:改了哪些文件、`band check` 的输出、`band dev` 里走过的路径;没走的路径写明没走。
21
+
22
+ ## 不做
23
+
24
+ - 不 import 别的官方插件、`band-kernel`(测试与夹具除外)、`band-engine` 等内部包。
25
+ - 不改协作状态(评论、批准、队列、回合、版本),只用 `openAt`、`gate.open`。
26
+ - 不写 `scripts.build`、不带 npm 依赖的源码入口(隔离构建未开放)。
@@ -0,0 +1,65 @@
1
+ # 包与清单
2
+
3
+ > 什么时候读:写或改 `package.json`、`plugin.yaml`(贡献、config、resources、锚点)时。
4
+
5
+ `package.json`:名字、版本、依赖。
6
+
7
+ ```json package.json
8
+ {
9
+ "name": "@rush-site/rush-db",
10
+ "version": "0.1.0",
11
+ "private": true,
12
+ "description": "查看与编辑建站项目的数据表,写线上前确认",
13
+ "keywords": ["数据库", "表格"],
14
+ "type": "module",
15
+ "peerDependencies": {
16
+ "@kanyun-ai-infra/band-plugin-sdk": "^2.0.0",
17
+ "@kanyun-ai-infra/band-project": "*",
18
+ "@kanyun-ai-infra/band-viewer-web": "*"
19
+ },
20
+ "peerDependenciesMeta": { "@kanyun-ai-infra/band-viewer-web": { "optional": true } }
21
+ }
22
+ ```
23
+
24
+ - 包名带作用域,工坊插件用工坊名(`band new plugin` 取工坊仓库的目录名,去掉 `band-` 前缀);`@kanyun-ai-infra/band-` 前缀只留给官方插件。
25
+ - `description`(一句话说明)与 `keywords` 显示在插件目录里,人和 Rondo 靠它们挑插件,写上。`"private": true` 表示只在本工坊用;工坊插件不发 npm 包。
26
+ - `peerDependencies` 必须有 `@kanyun-ai-infra/band-plugin-sdk`(协议版本,`^2.0.0`)。往谁的插槽贡献、用谁的服务,就把谁写进来;官方插件还没有对外版本号,写 `"*"`。
27
+
28
+ `plugin.yaml`:Band 的声明,不写 `name`(顶层写了 `name` 的是已废弃的 0.x 清单,`band check` 直接报错)。
29
+
30
+ ```yaml plugin.yaml
31
+ entry: ./index.ts # 仓库内插件可以直接写 TS 源文件
32
+ activation: [project] # 有项目钩子 scopes.project 时写;只有挂载面的插件不写
33
+ contributes:
34
+ - slot: "@kanyun-ai-infra/band-viewer-web/toolbar"
35
+ id: open-db
36
+ optional: true # 工坊没用网页查看器时静默不生效
37
+ label: 数据库
38
+ dock: db # 点了打开本插件的停靠位 db
39
+ - slot: "@kanyun-ai-infra/band-project/docks"
40
+ id: db
41
+ title: 数据库
42
+ anchors: [row] # 自己的锚点类型,要先声明再注册
43
+ resources:
44
+ database:
45
+ api: 'rush:/projects/{project}/database'
46
+ read: ['GET /**']
47
+ write: ['POST /tables/*/rows', 'DELETE /tables/*/rows/*']
48
+ prod: ['* /**?env=prod']
49
+ ```
50
+
51
+ - 所有字段 strict,写错字段名 `band check` 报错;贡献里只能写声明者列出的字段,必填的要写。
52
+ - 贡献要收工坊的配置,就在这条贡献里写 `config`(平铺 schema,同阶段参数那一套),工坊在 `views` 里照着写(面板 `{ db: { columns: 3 } }`,查看器 `artifactTypes.<类型>.config`),代码从 `props.config` 读:
53
+
54
+ ```yaml fragment
55
+ contributes:
56
+ - slot: "@kanyun-ai-infra/band-project/panels"
57
+ id: db
58
+ title: 数据库
59
+ config:
60
+ columns: { type: number, default: 3 }
61
+ ```
62
+
63
+ 没声明的配置项、类型不对,`band check` 和发布都报错;声明了 `default` 的,没写时 `props.config` 里也有。
64
+ - 要挂界面的贡献,`id` 就是代码里 `surfaces` 的键,多一个、少一个都是装载错误。工具栏按钮这种 `mount: false` 的贡献只是数据,不写挂载函数。
65
+ - `resources` 的路径模式:`*` 匹配一段,`**` 匹配任意段;先看 `prod`,再看 `write`,最后 `read`;没声明的路径一律拒绝。
@@ -0,0 +1,34 @@
1
+ # 位置指南:文件放哪
2
+
3
+ > 什么时候读:不确定某个文件该写进工坊仓库的哪个目录、改哪份 yaml,或 `band check` 报「找不到」时。
4
+
5
+ ```text
6
+ <工坊仓库>/
7
+ band/
8
+ workshop.yaml 工坊定义:阶段、产物类型、views(面板、查看器、插件列表)、agent
9
+ views/<名>/ 工坊插件,一个插件一个目录
10
+ package.json 名字、版本、peerDependencies
11
+ plugin.yaml 清单:贡献、config、resources、锚点
12
+ index.ts 入口(或构建产物 ./dist/index.js)
13
+ *.test.ts 纯函数单测
14
+ dev/ 只给 band dev 用
15
+ fixture.ts 必须有:版本、文件、产物、阶段
16
+ resources.ts 插件用了 resources 才写
17
+ agent/ 扩展 Rondo:instructions.md、stages/<阶段>.md、runtime 档
18
+ agenthub/agents/<名>/ 定制 Agent(只有 MCP、自有模型服务或密钥时才写)
19
+ .claude/skills/band-plugin/ 本 skill(band new skill 装的快照,不手改)
20
+ ```
21
+
22
+ | 要改 | 去哪里 |
23
+ |---|---|
24
+ | 换某个产物类型的查看器、给面板排位置 | `band/workshop.yaml` 的 `views`(`artifactTypes.<类型>.viewer`、`panels`、`replace`) |
25
+ | 让插件出现在项目页 | `band new plugin` 已把 `./views/<名>` 写进 `views.plugins`;手写时用同一格式 |
26
+ | 引用别的工坊共享的插件 | `views.plugins` 写 `'@scope/name@x.y.z'` 或 `^` 区间 |
27
+ | 给插件传配置 | `views` 里按贡献声明的 `config` 写(面板 `{ <id>: {...} }`,查看器 `artifactTypes.<类型>.config`) |
28
+ | 调某个阶段的 Agent 行为 | `band/agent/stages/<阶段>.md`;确要另一个 Agent 才在阶段上写 `agent` |
29
+ | 插件要调的接口 | `plugin.yaml` 的 `resources`;本地 mock 写 `band/dev/resources.ts` |
30
+ | 插件自己的小状态 | `ctx.storage`,不进仓库、不进版本 |
31
+
32
+ - 插件代码只在 `band/views/<名>/` 内,`entry` 不能用 `../` 跳出仓库。
33
+ - 不手改 `.claude/skills/` 下的快照;升级命令行后重跑 `band new skill`。
34
+ - 不在 `band/` 之外放插件、夹具;不把凭据、连接串写进任何文件。