made-market 0.1.2 → 0.3.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.
- package/README.md +2 -2
- package/bin/made-market.mjs +71 -13
- package/lib/capabilities.mjs +208 -0
- package/package.json +1 -1
- package/registry/index.json +11 -11
- package/src/init.mjs +43 -9
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ npx made-market publish <包目录>
|
|
|
23
23
|
|
|
24
24
|
## 服务地址
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
26
|
+
- 默认走市场服务的分页 API(`--server` 或 `MADE_MARKET_SERVER`,缺省官方市场);list/search/info 按页拉取,install/info 按包直查,大市场友好
|
|
27
|
+
- 自建静态源:`--registry` 或 `MADE_MARKET_REGISTRY` 指向索引文件(离线时 install 回落到包内自带示例快照)
|
|
28
28
|
|
|
29
29
|
## 服务端
|
|
30
30
|
|
package/bin/made-market.mjs
CHANGED
|
@@ -33,8 +33,8 @@ const USAGE = `码得 · 插件市场 (made-market)
|
|
|
33
33
|
命令
|
|
34
34
|
init <目录> --kind <类型> 生成插件脚手架(可直接 publish 的最小包,SDK 入口)
|
|
35
35
|
validate <包目录> 本地校验一个包(与市场服务端同一套规则)
|
|
36
|
-
list
|
|
37
|
-
search <关键词>
|
|
36
|
+
list 列出市场上的包(分页 API,大市场友好)
|
|
37
|
+
search <关键词> 在包名、简介和标签里搜索(服务端搜索,分页)
|
|
38
38
|
info <包 ID> 看一个包的详情:文件、校验和、安装位置、创建人
|
|
39
39
|
install <包 ID…> 装到包目录,重启桥接器后生效(匿名,无需登录)
|
|
40
40
|
publish <包目录> 把一个包目录上传到市场服务(需先登录)
|
|
@@ -46,8 +46,8 @@ const USAGE = `码得 · 插件市场 (made-market)
|
|
|
46
46
|
|
|
47
47
|
选项
|
|
48
48
|
--home <目录> 包目录根,默认 $MADE_HOME 或 ~/made
|
|
49
|
-
--registry <路径|URL>
|
|
50
|
-
|
|
49
|
+
--registry <路径|URL> 索引来源(install 默认走市场 API 按包直查;
|
|
50
|
+
显式指定后 list/search/info 也改读这个索引文件,自建静态源用)
|
|
51
51
|
--server <URL> 市场服务地址(register/login/publish/install;install 默认官方市场,
|
|
52
52
|
可用 $MADE_MARKET_SERVER 覆盖,已登录时 publish 也可从凭证读取)
|
|
53
53
|
--token <t> 发布 token(publish;默认 $MADE_MARKET_TOKEN 或登录凭证)
|
|
@@ -99,7 +99,7 @@ const semverGreater = (a, b) => {
|
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
101
|
* API 模式安装:按包直查市场服务的 /api/packages/<id>,不下载全量索引。
|
|
102
|
-
*
|
|
102
|
+
* 大市场(十几万包,全量索引根本不存在)时用这个,归档地址改写成服务上的绝对地址。
|
|
103
103
|
*/
|
|
104
104
|
async function resolveViaApi(ids, server) {
|
|
105
105
|
const base = String(server).replace(/\/+$/, '');
|
|
@@ -115,6 +115,36 @@ async function resolveViaApi(ids, server) {
|
|
|
115
115
|
return { packages, base };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* 分页拉目录:/api/packages 每页最多 100,循环翻页直到拉完。
|
|
120
|
+
* 十几万包的市场没有全量索引,list/search 只能这么吃。maxPages 是
|
|
121
|
+
* 防呆上限(默认 500 页 = 5 万条,超出提示加关键词/过滤)。
|
|
122
|
+
*/
|
|
123
|
+
async function fetchAllPages(server, { kind = '', q = '', maxPages = 500 } = {}) {
|
|
124
|
+
const base = String(server).replace(/\/+$/, '');
|
|
125
|
+
const items = [];
|
|
126
|
+
let offset = 0;
|
|
127
|
+
let total = Infinity;
|
|
128
|
+
while (offset < total) {
|
|
129
|
+
const params = new URLSearchParams({ limit: '100', offset: String(offset) });
|
|
130
|
+
if (kind) params.set('kind', kind);
|
|
131
|
+
if (q) params.set('q', q);
|
|
132
|
+
const response = await fetch(`${base}/api/packages?${params}`, { signal: AbortSignal.timeout(20000) });
|
|
133
|
+
if (!response.ok) throw new Error(`目录拉取失败:HTTP ${response.status}`);
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
total = Number(data.total) || 0;
|
|
136
|
+
const batch = Array.isArray(data.items) ? data.items : [];
|
|
137
|
+
items.push(...batch);
|
|
138
|
+
if (!batch.length) break;
|
|
139
|
+
offset += batch.length;
|
|
140
|
+
if (offset >= 100 * maxPages) {
|
|
141
|
+
console.error(`包太多(共 ${total} 个),只列出前 ${offset} 个;用 search 或 --kind 缩小范围。`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { items, total, base };
|
|
146
|
+
}
|
|
147
|
+
|
|
118
148
|
const human = (bytes) => (bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1048576).toFixed(2)} MB`);
|
|
119
149
|
const homeOf = (options) => resolveHome(options.home);
|
|
120
150
|
|
|
@@ -198,12 +228,21 @@ async function main() {
|
|
|
198
228
|
return;
|
|
199
229
|
}
|
|
200
230
|
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
const loadIndex = () => loadRegistry(resolveRegistry(
|
|
231
|
+
// list/search/info 的读路径:显式 --registry 或 $MADE_MARKET_REGISTRY 指向
|
|
232
|
+
// 索引文件(自建静态源/离线复现)时读文件;否则走市场分页 API(大市场没有全量索引)。
|
|
233
|
+
const staticRegistry = options.registry || process.env.MADE_MARKET_REGISTRY || '';
|
|
234
|
+
const loadIndex = () => loadRegistry(resolveRegistry(staticRegistry));
|
|
235
|
+
|
|
236
|
+
// list/search 的服务端地址:--server > $MADE_MARKET_SERVER > 官方市场
|
|
237
|
+
const listServer = () => (options.server || process.env.MADE_MARKET_SERVER || DEFAULT_SERVER).replace(/\/+$/, '');
|
|
205
238
|
|
|
206
239
|
if (options.command === 'list') {
|
|
240
|
+
if (!staticRegistry) {
|
|
241
|
+
const { items } = await fetchAllPages(listServer(), { kind: options.kind });
|
|
242
|
+
if (options.json) console.log(JSON.stringify(items.map(describe), null, 2));
|
|
243
|
+
else printTable(items);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
207
246
|
const rows = listRows((await loadIndex()).index, options);
|
|
208
247
|
if (options.json) console.log(JSON.stringify(rows.map(describe), null, 2));
|
|
209
248
|
else printTable(rows);
|
|
@@ -213,6 +252,13 @@ async function main() {
|
|
|
213
252
|
if (options.command === 'search') {
|
|
214
253
|
const query = options.args.join(' ').trim().toLowerCase();
|
|
215
254
|
if (!query) throw new Error('search 需要一个关键词');
|
|
255
|
+
// 搜索在服务端做(id/名称/简介/标签全字段),客户端只翻页聚合。
|
|
256
|
+
if (!staticRegistry) {
|
|
257
|
+
const { items } = await fetchAllPages(listServer(), { q: query, kind: options.kind });
|
|
258
|
+
if (options.json) console.log(JSON.stringify(items.map(describe), null, 2));
|
|
259
|
+
else printTable(items);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
216
262
|
const rows = listRows((await loadIndex()).index, options).filter((entry) => [
|
|
217
263
|
entry.id, entry.name, entry.nameEn, entry.summary, entry.summaryEn, ...(entry.tags || []), ...(entry.highlights || []),
|
|
218
264
|
].filter(Boolean).some((field) => String(field).toLowerCase().includes(query)));
|
|
@@ -224,8 +270,19 @@ async function main() {
|
|
|
224
270
|
if (options.command === 'info') {
|
|
225
271
|
const id = options.args[0];
|
|
226
272
|
if (!id) throw new Error('info 需要一个包 ID');
|
|
227
|
-
|
|
228
|
-
if (!
|
|
273
|
+
let entry;
|
|
274
|
+
if (!staticRegistry) {
|
|
275
|
+
const base = listServer();
|
|
276
|
+
const response = await fetch(`${base}/api/packages/${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(15000) });
|
|
277
|
+
if (!response.ok) throw new Error(`服务上没有包 ${id}(HTTP ${response.status})`);
|
|
278
|
+
const versions = await response.json();
|
|
279
|
+
entry = versions.reduce((best, item) => (!best || semverGreater(item.version, best.version) ? item : best), null);
|
|
280
|
+
if (!entry) throw new Error(`服务上没有包 ${id}`);
|
|
281
|
+
if (!/^https?:\/\//.test(entry.download)) entry.download = `${base}/${entry.download}`;
|
|
282
|
+
} else {
|
|
283
|
+
entry = (await loadIndex()).index.packages.find((item) => item.id === id);
|
|
284
|
+
if (!entry) throw new Error(`索引里没有包 ${id}`);
|
|
285
|
+
}
|
|
229
286
|
if (options.json) {
|
|
230
287
|
console.log(JSON.stringify(describe(entry), null, 2));
|
|
231
288
|
return;
|
|
@@ -254,8 +311,9 @@ async function main() {
|
|
|
254
311
|
if (options.command === 'install') {
|
|
255
312
|
if (!options.args.length) throw new Error('install 需要至少一个包 ID');
|
|
256
313
|
// 默认走市场 API:按包直查最新版本,不拉全量索引(大市场友好),
|
|
257
|
-
// 新发布的包立即可装。显式 --registry
|
|
258
|
-
|
|
314
|
+
// 新发布的包立即可装。显式 --registry 或 $MADE_MARKET_REGISTRY 才按索引文件走
|
|
315
|
+
// (自建源/精确复现用)。
|
|
316
|
+
if (!options.registry && !process.env.MADE_MARKET_REGISTRY) {
|
|
259
317
|
const server = options.server || process.env.MADE_MARKET_SERVER || DEFAULT_SERVER;
|
|
260
318
|
try {
|
|
261
319
|
const { packages, base } = await resolveViaApi(options.args, server);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管控插件的开放能力清单(apiVersion 1 的能力面真源)。
|
|
3
|
+
*
|
|
4
|
+
* 单一真源,三处消费:init 脚手架把它生成进包里(capabilities.json +
|
|
5
|
+
* contracts.d.ts,作者写插件时编辑器直接有提示)、市场网站说明页按它排版、
|
|
6
|
+
* 桥接器侧的实现(made 仓库 enhance.mjs)是它的运行时对应物。改能力面时
|
|
7
|
+
* 先改这里,再同步 made 仓库 AGENTS.md 的清单表与 site/capabilities.html。
|
|
8
|
+
*
|
|
9
|
+
* 两条供给线:
|
|
10
|
+
* - board:事件与命令,清单由固件上报(announceBoardCatalog),扩清单要刷固件;
|
|
11
|
+
* - bridge:桥接器进程内露出的句柄(enhance.mjs),重启桥接器即生效。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const CAPABILITIES = {
|
|
15
|
+
apiVersion: 1,
|
|
16
|
+
/** 能力清单版本:加能力不升它;改既有能力的语义/形状才升,并同步桥接器。 */
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
board: {
|
|
19
|
+
events: [
|
|
20
|
+
{ name: 'boot.click', cadence: 'edge', desc: '按键短按(语音意图)' },
|
|
21
|
+
{ name: 'boot.double', cadence: 'edge', desc: '双击(确认)' },
|
|
22
|
+
{ name: 'boot.triple', cadence: 'edge', desc: '三击(取消)' },
|
|
23
|
+
{ name: 'boot.long', cadence: 'edge', desc: '长按(回主页)' },
|
|
24
|
+
],
|
|
25
|
+
commands: [
|
|
26
|
+
{ name: 'caption.show', desc: '屏幕字幕,fields.text 为正文' },
|
|
27
|
+
{ name: 'audio.play', desc: '播放音频,可附带 WAV:16-bit PCM、单声道、8k–48kHz、≤256KB' },
|
|
28
|
+
{ name: 'theme.apply', desc: '应用主题插件' },
|
|
29
|
+
{ name: 'volume.set', desc: '设置音量' },
|
|
30
|
+
{ name: 'agent.select', desc: '切换当前助手' },
|
|
31
|
+
{ name: 'session.create', desc: '新建会话' },
|
|
32
|
+
{ name: 'session.select', desc: '切换会话' },
|
|
33
|
+
{ name: 'session.delete', desc: '删除会话' },
|
|
34
|
+
{ name: 'voice.start', desc: '主动点亮录音' },
|
|
35
|
+
{ name: 'task.confirm', desc: '批准待确认任务' },
|
|
36
|
+
{ name: 'task.cancel', desc: '取消任务' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
/** onJob 收到的任务生命周期,type 字段的全部取值(按典型顺序排列)。 */
|
|
40
|
+
jobLifecycle: ['created', 'confirmed', 'started', 'progress', 'completed', 'handed_off', 'failed', 'cancelled'],
|
|
41
|
+
/** 回合管道:按插件注册顺序成链,返回字符串即 override,最后一个说话的算数。 */
|
|
42
|
+
turnPipes: [
|
|
43
|
+
{ name: 'onTurnInput', context: ['instruction', 'job', 'agentId', 'config', 'bridge'], desc: '改写本轮实际执行的指令;存储里的任务原文不动' },
|
|
44
|
+
{ name: 'onTurnOutput', context: ['result', 'job', 'agentId', 'status', 'config', 'bridge'], desc: '仅在任务完成时调用,改写回给用户的结果' },
|
|
45
|
+
],
|
|
46
|
+
/** bridge 门面:事件、钩子、通知的上下文里都拿得到。 */
|
|
47
|
+
bridge: [
|
|
48
|
+
{ name: 'agents', signature: 'agents()', desc: '全部助手(含可用性与探测原因)' },
|
|
49
|
+
{ name: 'agent', signature: 'agent(id)', desc: '按 id 查单个助手' },
|
|
50
|
+
{ name: 'jobs', signature: 'jobs()', desc: '全部任务' },
|
|
51
|
+
{ name: 'job', signature: 'job(id)', desc: '按 id 查单个任务' },
|
|
52
|
+
{ name: 'sessions', signature: 'sessions(agentId?)', desc: '会话清单,可按助手过滤' },
|
|
53
|
+
{ name: 'createTask', signature: 'createTask({instruction, agentId?, projectId?, title?})', desc: '建会话并落一个待确认任务,返回 {job, session};限流 10 次/分钟' },
|
|
54
|
+
{ name: 'submitTurn', signature: 'submitTurn({instruction, sessionId})', desc: '向既有会话追加一轮(同样落待确认)' },
|
|
55
|
+
{ name: 'confirmJob', signature: 'confirmJob(id)', desc: '批准待确认任务,批准后立即执行' },
|
|
56
|
+
{ name: 'cancelJob', signature: 'cancelJob(id)', desc: '取消任务' },
|
|
57
|
+
{ name: 'themes', signature: 'themes()', desc: '已安装主题清单' },
|
|
58
|
+
{ name: 'devices', signature: 'devices()', desc: '当前在线设备 id 列表' },
|
|
59
|
+
{ name: 'send', signature: 'send(deviceId, command, audio?)', desc: '向指定设备入队一条板侧命令(须在板侧清单内)' },
|
|
60
|
+
{ name: 'pending', signature: 'pending(deviceId)', desc: '该设备尚未取走的命令队列' },
|
|
61
|
+
],
|
|
62
|
+
limits: {
|
|
63
|
+
hookTimeoutMs: 1500,
|
|
64
|
+
createTaskPerMinute: 10,
|
|
65
|
+
audioFormat: '16-bit PCM WAV,单声道,8k–48kHz,≤256KB',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** 生成进包里的 capabilities.json(作者可读,也方便脚本化消费)。 */
|
|
70
|
+
export const capabilitiesJson = () => `${JSON.stringify(CAPABILITIES, null, 2)}\n`;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 生成进包里的 contracts.d.ts:只描述插件作者看得见、用得上的接口面。
|
|
74
|
+
* 类型仅给编辑器提示,运行时不加载(plugin.mjs 是纯 JS)。
|
|
75
|
+
*/
|
|
76
|
+
export const contractsDts = () => `/**
|
|
77
|
+
* 码得管控插件 · 接口契约(apiVersion ${CAPABILITIES.apiVersion})。
|
|
78
|
+
* 由 npx made-market init 生成,运行时对应桥接器的 bridge/src/plugins/enhance.mjs。
|
|
79
|
+
* 能力清单(事件/命令/句柄/限制)见同目录 capabilities.json。
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
export interface BoardEvent {
|
|
83
|
+
name: string;
|
|
84
|
+
fields: Record<string, string>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface BoardCommand {
|
|
88
|
+
name: string;
|
|
89
|
+
fields?: Record<string, string>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface PluginConfig {
|
|
93
|
+
/** 各插件的设置:pluginSettings[<你的插件 id>] */
|
|
94
|
+
pluginSettings: Record<string, Record<string, string>>;
|
|
95
|
+
/** 用户当前默认助手 id */
|
|
96
|
+
defaultProvider: string;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface JobView {
|
|
101
|
+
id: string;
|
|
102
|
+
sessionId: string;
|
|
103
|
+
instruction: string;
|
|
104
|
+
status: string;
|
|
105
|
+
/** 执行该任务的助手 id */
|
|
106
|
+
provider: string;
|
|
107
|
+
projectId?: string;
|
|
108
|
+
/** 任务来源:'voice' | 'plugin' | ... */
|
|
109
|
+
source?: string;
|
|
110
|
+
result?: string;
|
|
111
|
+
progress?: string;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
updatedAt: string;
|
|
114
|
+
[key: string]: unknown;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SessionView {
|
|
118
|
+
id: string;
|
|
119
|
+
provider: string;
|
|
120
|
+
title?: string;
|
|
121
|
+
[key: string]: unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface AgentView {
|
|
125
|
+
id: string;
|
|
126
|
+
label: string;
|
|
127
|
+
available: boolean;
|
|
128
|
+
reason: string;
|
|
129
|
+
mode: string;
|
|
130
|
+
capabilities?: unknown;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 桥接器句柄:事件、钩子、通知的上下文里都拿得到(context.bridge)。 */
|
|
134
|
+
export interface Bridge {
|
|
135
|
+
agents(): AgentView[];
|
|
136
|
+
agent(id: string): AgentView | undefined;
|
|
137
|
+
jobs(): JobView[];
|
|
138
|
+
job(id: string): JobView | undefined;
|
|
139
|
+
sessions(agentId?: string): SessionView[];
|
|
140
|
+
createTask(options: { instruction: string; agentId?: string; projectId?: string; title?: string }):
|
|
141
|
+
{ job: JobView; session: SessionView };
|
|
142
|
+
submitTurn(options: { instruction: string; sessionId: string }): JobView;
|
|
143
|
+
confirmJob(id: string): JobView;
|
|
144
|
+
cancelJob(id: string): JobView;
|
|
145
|
+
themes(): Array<Record<string, unknown>>;
|
|
146
|
+
devices(): string[];
|
|
147
|
+
send(deviceId: string, command: BoardCommand, audio?: Uint8Array): unknown;
|
|
148
|
+
pending(deviceId: string): unknown[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type JobEventType =
|
|
152
|
+
| 'created' | 'confirmed' | 'started' | 'progress'
|
|
153
|
+
| 'completed' | 'handed_off' | 'failed' | 'cancelled';
|
|
154
|
+
|
|
155
|
+
export interface PluginBase {
|
|
156
|
+
apiVersion: 1;
|
|
157
|
+
kind: 'board-plugin';
|
|
158
|
+
id: string;
|
|
159
|
+
label: string;
|
|
160
|
+
/** 健康探测:工作台据此显示 available / reason。 */
|
|
161
|
+
probe: (config: PluginConfig) => { available: boolean; reason?: string };
|
|
162
|
+
|
|
163
|
+
/** 板侧事件:events 里登记过的才会被上报。返回 {commands} 或调 context.send 入队。 */
|
|
164
|
+
onEvent(context: {
|
|
165
|
+
event: BoardEvent;
|
|
166
|
+
deviceId: string;
|
|
167
|
+
config: PluginConfig;
|
|
168
|
+
send: (command: BoardCommand, audio?: Uint8Array) => unknown;
|
|
169
|
+
bridge: Bridge;
|
|
170
|
+
}): Promise<{ commands?: BoardCommand[] } | void> | { commands?: BoardCommand[] } | void;
|
|
171
|
+
|
|
172
|
+
onConnect?(context: {
|
|
173
|
+
deviceId: string;
|
|
174
|
+
config: PluginConfig;
|
|
175
|
+
send: (command: BoardCommand, audio?: Uint8Array) => unknown;
|
|
176
|
+
bridge: Bridge;
|
|
177
|
+
}): Promise<void> | void;
|
|
178
|
+
|
|
179
|
+
onDisconnect?(context: { deviceId: string }): Promise<void> | void;
|
|
180
|
+
|
|
181
|
+
/** 任务生命周期通知(${CAPABILITIES.jobLifecycle.join(' / ')})。 */
|
|
182
|
+
onJob?(context: {
|
|
183
|
+
type: JobEventType;
|
|
184
|
+
job: JobView;
|
|
185
|
+
config: PluginConfig;
|
|
186
|
+
bridge: Bridge;
|
|
187
|
+
}): Promise<void> | void;
|
|
188
|
+
|
|
189
|
+
/** 回合管道:返回字符串即 override。超时 ${CAPABILITIES.limits.hookTimeoutMs}ms 或抛错则沿用上一环。 */
|
|
190
|
+
onTurnInput?(context: {
|
|
191
|
+
instruction: string;
|
|
192
|
+
job: JobView;
|
|
193
|
+
agentId: string;
|
|
194
|
+
config: PluginConfig;
|
|
195
|
+
bridge: Bridge;
|
|
196
|
+
}): Promise<string | void> | string | void;
|
|
197
|
+
|
|
198
|
+
/** 仅在任务完成时调用;返回字符串即改写回给用户的结果。 */
|
|
199
|
+
onTurnOutput?(context: {
|
|
200
|
+
result: string;
|
|
201
|
+
job: JobView;
|
|
202
|
+
agentId: string;
|
|
203
|
+
status: 'completed';
|
|
204
|
+
config: PluginConfig;
|
|
205
|
+
bridge: Bridge;
|
|
206
|
+
}): Promise<string | void> | string | void;
|
|
207
|
+
}
|
|
208
|
+
`;
|
package/package.json
CHANGED
package/registry/index.json
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
],
|
|
81
81
|
"payloadBytes": 209717,
|
|
82
82
|
"archiveBytes": 39822,
|
|
83
|
-
"sha256": "
|
|
83
|
+
"sha256": "f99ab016bda97781fba979cf5d31ffec18edf1ea91a2339c1d7e4cf63f36a0a7",
|
|
84
84
|
"download": "packages/sunset-1.0.0.tar.gz",
|
|
85
85
|
"swatch": [
|
|
86
86
|
"#e0834a",
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
],
|
|
154
154
|
"payloadBytes": 212917,
|
|
155
155
|
"archiveBytes": 44910,
|
|
156
|
-
"sha256": "
|
|
156
|
+
"sha256": "a48c12c45c83185041a25ea0f408f8ce15a1632d4473440a0a1fbc7b5f674cd1",
|
|
157
157
|
"download": "packages/ocean-1.0.0.tar.gz",
|
|
158
158
|
"swatch": [
|
|
159
159
|
"#2fa3b8",
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
],
|
|
227
227
|
"payloadBytes": 198517,
|
|
228
228
|
"archiveBytes": 31125,
|
|
229
|
-
"sha256": "
|
|
229
|
+
"sha256": "9dd6037414f9303ecc507c4348380baeaf8dd2a97c98c58748410c18b37c014e",
|
|
230
230
|
"download": "packages/graphite-1.0.0.tar.gz",
|
|
231
231
|
"swatch": [
|
|
232
232
|
"#8e9aa6",
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
],
|
|
300
300
|
"payloadBytes": 206517,
|
|
301
301
|
"archiveBytes": 34163,
|
|
302
|
-
"sha256": "
|
|
302
|
+
"sha256": "76095ce19587f8e0bc7ba5aeb7ca62f1e622bc8ef14847fdb3479dbf0ea87b29",
|
|
303
303
|
"download": "packages/bamboo-1.0.0.tar.gz",
|
|
304
304
|
"swatch": [
|
|
305
305
|
"#5aa85e",
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
],
|
|
368
368
|
"payloadBytes": 1576,
|
|
369
369
|
"archiveBytes": 1067,
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "35c4768f607bb936b2e263e972895920c66cce74a8829d944cd86c6d2e5b0030",
|
|
371
371
|
"download": "packages/theme-cycler-1.0.0.tar.gz",
|
|
372
372
|
"sourcePath": "packages/control/theme-cycler"
|
|
373
373
|
},
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
],
|
|
431
431
|
"payloadBytes": 3023,
|
|
432
432
|
"archiveBytes": 1808,
|
|
433
|
-
"sha256": "
|
|
433
|
+
"sha256": "0c7c6453b78a76c327282dfd62f6d023e90dc12a2c7458a67b1b43e5e20e15ba",
|
|
434
434
|
"download": "packages/webhook-notify-1.0.0.tar.gz",
|
|
435
435
|
"sourcePath": "packages/control/webhook-notify"
|
|
436
436
|
},
|
|
@@ -492,7 +492,7 @@
|
|
|
492
492
|
],
|
|
493
493
|
"payloadBytes": 2370,
|
|
494
494
|
"archiveBytes": 1450,
|
|
495
|
-
"sha256": "
|
|
495
|
+
"sha256": "cea53eedab0232272ed6f5ebd5c6b8a7250ff08240fb2d754b16b1e7c9088d7a",
|
|
496
496
|
"download": "packages/key-chime-1.0.0.tar.gz",
|
|
497
497
|
"sourcePath": "packages/control/key-chime"
|
|
498
498
|
},
|
|
@@ -554,7 +554,7 @@
|
|
|
554
554
|
],
|
|
555
555
|
"payloadBytes": 3940,
|
|
556
556
|
"archiveBytes": 1959,
|
|
557
|
-
"sha256": "
|
|
557
|
+
"sha256": "bd4c7ae744be6a99dacaf663a0624e61e568f8da5e83566eb104127d39ddf11d",
|
|
558
558
|
"download": "packages/alarm-clock-1.0.0.tar.gz",
|
|
559
559
|
"sourcePath": "packages/control/alarm-clock"
|
|
560
560
|
},
|
|
@@ -621,7 +621,7 @@
|
|
|
621
621
|
],
|
|
622
622
|
"payloadBytes": 3793,
|
|
623
623
|
"archiveBytes": 1612,
|
|
624
|
-
"sha256": "
|
|
624
|
+
"sha256": "aaf99b7ab9af49712f9ff5da37695c4d41cf89e09ae6ec5168057dede7075546",
|
|
625
625
|
"download": "packages/demo-echo-1.0.0.tar.gz",
|
|
626
626
|
"icon": {
|
|
627
627
|
"format": "indexed4",
|
|
@@ -702,7 +702,7 @@
|
|
|
702
702
|
],
|
|
703
703
|
"payloadBytes": 6098,
|
|
704
704
|
"archiveBytes": 2672,
|
|
705
|
-
"sha256": "
|
|
705
|
+
"sha256": "8da05c474aa4b0d3d8913e16109b0ed887a4bab2d1d136527bb85bd798860cff",
|
|
706
706
|
"download": "packages/ollama-agent-1.0.0.tar.gz",
|
|
707
707
|
"icon": {
|
|
708
708
|
"format": "indexed4",
|
|
@@ -786,7 +786,7 @@
|
|
|
786
786
|
],
|
|
787
787
|
"payloadBytes": 5814,
|
|
788
788
|
"archiveBytes": 2456,
|
|
789
|
-
"sha256": "
|
|
789
|
+
"sha256": "12de415fa0bb0bfaa3eb119c5e0930b3d3239836d184eab781dc1b36be93d06a",
|
|
790
790
|
"download": "packages/shell-agent-1.0.0.tar.gz",
|
|
791
791
|
"icon": {
|
|
792
792
|
"format": "indexed4",
|
package/src/init.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { PACKAGE_DIR } from '../lib/plugin.mjs';
|
|
11
11
|
import { renderIcon } from '../lib/icon.mjs';
|
|
12
|
+
import { capabilitiesJson, contractsDts } from '../lib/capabilities.mjs';
|
|
12
13
|
|
|
13
14
|
const THEME_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
14
15
|
|
|
@@ -101,12 +102,15 @@ function controlMarketJson(id, pluginId) {
|
|
|
101
102
|
|
|
102
103
|
function controlPluginMjs(pluginId) {
|
|
103
104
|
return `/**
|
|
104
|
-
* 管控插件模板:改掉 id/label
|
|
105
|
+
* 管控插件模板:改掉 id/label,挑需要的钩子实现,就能发布。
|
|
105
106
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
* 能力清单(板侧事件/命令、任务生命周期、bridge 句柄、限流)见同目录
|
|
108
|
+
* capabilities.json;接口类型见 contracts.d.ts(编辑器自动提示)。
|
|
109
|
+
*
|
|
110
|
+
* events/commands 只能填板侧清单里的名字:
|
|
111
|
+
* - 事件:boot.click / boot.double / boot.triple / boot.long
|
|
112
|
+
* - 命令:caption.show / audio.play / theme.apply / volume.set / agent.select /
|
|
113
|
+
* session.create / session.select / session.delete / voice.start / task.confirm / task.cancel
|
|
110
114
|
*/
|
|
111
115
|
export default {
|
|
112
116
|
apiVersion: 1,
|
|
@@ -119,15 +123,41 @@ export default {
|
|
|
119
123
|
// 健康探测:桥接器会显示 available / reason。
|
|
120
124
|
return { available: true, reason: '模板插件,尚未实现逻辑' };
|
|
121
125
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
onEvent({ event, deviceId, config, send }) {
|
|
126
|
+
|
|
127
|
+
// ── 触发面一:板侧事件 ────────────────────────────────────────────
|
|
128
|
+
onEvent({ event, deviceId, config, send, bridge }) {
|
|
126
129
|
if (event.name === 'boot.click') {
|
|
130
|
+
// 两条出命令的路:返回 {commands},或稍后调 send(command, audio?)。
|
|
127
131
|
return { commands: [{ name: 'caption.show', fields: { text: '你好,码得!' } }] };
|
|
128
132
|
}
|
|
129
133
|
return null;
|
|
130
134
|
},
|
|
135
|
+
|
|
136
|
+
// ── 触发面二:任务生命周期(可选)────────────────────────────────
|
|
137
|
+
// type: created / confirmed / started / progress / completed / handed_off /
|
|
138
|
+
// failed / cancelled。注意:插件自建的任务不广播 created(防自激)。
|
|
139
|
+
// onJob({ type, job, bridge }) {
|
|
140
|
+
// if (type === 'completed' && job.provider === '某助手id') {
|
|
141
|
+
// // 例:把操作日志上报远端、或在板上播报完成。
|
|
142
|
+
// }
|
|
143
|
+
// },
|
|
144
|
+
|
|
145
|
+
// ── 触发面三:回合管道(可选,override 权限最高)─────────────────
|
|
146
|
+
// 按插件注册顺序成链,返回字符串即改写,最后一个说话的算数;
|
|
147
|
+
// 超时 1500ms 或抛错则沿用上一环,绝不阻断任务本身。
|
|
148
|
+
// onTurnOutput({ result, job, agentId }) {
|
|
149
|
+
// if (agentId !== '某助手id') return; // 只加工目标助手
|
|
150
|
+
// return \`\${result}\\n—— 由我的插件加工\`;
|
|
151
|
+
// },
|
|
152
|
+
|
|
153
|
+
// ── 自主行为:bridge 句柄(可选)────────────────────────────────
|
|
154
|
+
// bridge.agents() / bridge.createTask({...})(落待确认任务,限流 10 次/分钟)/
|
|
155
|
+
// bridge.confirmJob(id) / bridge.send(deviceId, {name:'audio.play'}, wav) ……
|
|
156
|
+
// 全部句柄与签名见 capabilities.json 的 bridge 一节。
|
|
157
|
+
|
|
158
|
+
onConnect({ deviceId, config, send, bridge }) {
|
|
159
|
+
// 设备连上来时触发(可选实现)。
|
|
160
|
+
},
|
|
131
161
|
onDisconnect({ deviceId }) {
|
|
132
162
|
// 设备断开时触发(可选实现)。
|
|
133
163
|
},
|
|
@@ -216,6 +246,10 @@ const SCHEMAS = {
|
|
|
216
246
|
{ name: 'plugin.mjs', data: controlPluginMjs(pluginId) },
|
|
217
247
|
{ name: 'plugin.json', data: `${JSON.stringify(controlPluginJson(pluginId), null, 2)}\n` },
|
|
218
248
|
{ name: 'settings.json', data: '{}\n' },
|
|
249
|
+
// 能力 SDK:清单(机器可读)+ 类型契约(编辑器提示)。随包发布,
|
|
250
|
+
// 装到 ~/made/plugins/<id>/ 后依然在,作者与使用者都能查。
|
|
251
|
+
{ name: 'capabilities.json', data: capabilitiesJson() },
|
|
252
|
+
{ name: 'contracts.d.ts', data: contractsDts() },
|
|
219
253
|
];
|
|
220
254
|
},
|
|
221
255
|
},
|