@petercjl/product-asset-set 0.1.1 → 0.1.2
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/INSTALL.md +3 -1
- package/README.md +4 -4
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/UPDATE.md +5 -1
- package/bin/product-asset-set.mjs +6 -0
- package/capabilities.json +514 -0
- package/lib/cli.mjs +53 -0
- package/lib/core.mjs +280 -0
- package/package.json +16 -4
- package/scripts/sync-version.mjs +39 -0
- package/scripts/verify-package.mjs +7 -0
- package/skill-integrity.json +18 -0
- package/skills/product-asset-set/SKILL.md +5 -0
- package/skills/product-asset-set/capabilities.json +5 -1
- package/skills/product-asset-set/package.json +5 -0
package/INSTALL.md
CHANGED
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
> 请安装 `@petercjl/product-asset-set` 提供的 Skill。
|
|
4
4
|
|
|
5
|
-
当前 Agent
|
|
5
|
+
当前 Agent 先识别自己的 Skill 目录,从 npm 获取指定版本并通过标准管理 CLI 的 skill source 读取当前来源和合同。以明确的 Agent 名称和完整目标目录执行 skill install,实体安装包含完整 Skill 和目标自有运行时。已有未受管副本先核对来源、保留备份。安装后核对 skill status 的版本、哈希与 current 状态,并按当前宿主的机制注册和刷新加载。版本一致只是身份检查,实际资源由 SHA-256 校验。npm 脚本被禁用时,同样完成上述交接。
|
|
6
|
+
|
|
7
|
+
目标参数 --target 表示精确 Skill 目录,由当前 Agent 解析。任何满足所需能力的 Agent 均可安装。管理 CLI 与本地资产工具需要 Node.js 22+。媒体服务权限由任务入口单独核实。
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# product-asset-set
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
请把下面的提示词交给当前使用的 Agent。
|
|
3
|
+
制作可复用的独立产品参考图组及资产说明,每张图按实际用途选择画幅。
|
|
6
4
|
|
|
7
5
|
> 请安装 `@petercjl/product-asset-set` 提供的 Skill。
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
> 请检查并更新 `@petercjl/product-asset-set` 提供的 Skill。
|
|
8
|
+
|
|
9
|
+
面向当前使用的 Agent,由其解析自己的安装目录并完成注册。包提供通用管理 CLI、完整 Skill 和独立运行时安装。安装与更新不授予媒体服务权限。[安装说明](INSTALL.md) · [更新与恢复](UPDATE.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Management lifecycle helpers are adapted from @petercjl/portable-skill-creator 0.2.1.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 petercjl
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/UPDATE.md
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> 请检查并更新 `@petercjl/product-asset-set` 提供的 Skill。
|
|
4
4
|
|
|
5
|
-
当前 Agent
|
|
5
|
+
当前 Agent 先定位自己的明确目标,读取 skill source 中的管理合同并检查 skill status。普通任务 prepare 自动检测稳定版更新,缓存最长一天,离线回退五分钟;AGENT_PACKAGE_UPDATE_CHECK=0 可停用检测。update check 和 update status 提供只读检查,update policy --mode off|notify --yes 配置所选安装的通知模式。默认通知,有明确更新授权后才替换。
|
|
6
|
+
|
|
7
|
+
update install --agent NAME --target EXACT_DIRECTORY --yes 获取候选,保留替换前的副本,核对身份后同步该目标的 Skill 与自有运行时。相同版本也修复缺失文件,已完整同步时返回 verified no-op。保留本地编辑与宿主界面元数据。更新回执提供新入口,当前 Agent 随后加载该入口和 Skill,按宿主机制确认发现状态。
|
|
8
|
+
|
|
9
|
+
失败或结果未知时,先读取 update operation,恢复已获取的候选用 update resume,需回退时用 update rollback;两者均要求所选目标和明确的 --yes。恢复保持原事务及备份,避免重复 npm 获取。父集合管理安装显示 owner,由所属集合进行更新。0.1.0、0.1.1 只有实体复制安装,没有历史管理 CLI;迁移时核对旧副本并先保留可恢复备份。
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ok": true,
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"package": "@petercjl/product-asset-set",
|
|
5
|
+
"version": "0.1.2",
|
|
6
|
+
"skills": [
|
|
7
|
+
"product-asset-set"
|
|
8
|
+
],
|
|
9
|
+
"agentEligibility": "capability-based",
|
|
10
|
+
"commandContracts": {
|
|
11
|
+
"version": {
|
|
12
|
+
"options": {
|
|
13
|
+
"--json": {
|
|
14
|
+
"type": "boolean"
|
|
15
|
+
},
|
|
16
|
+
"--help": {
|
|
17
|
+
"type": "boolean"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": "none"
|
|
21
|
+
},
|
|
22
|
+
"help": {
|
|
23
|
+
"options": {
|
|
24
|
+
"--json": {
|
|
25
|
+
"type": "boolean"
|
|
26
|
+
},
|
|
27
|
+
"--help": {
|
|
28
|
+
"type": "boolean"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": "none"
|
|
32
|
+
},
|
|
33
|
+
"capabilities": {
|
|
34
|
+
"options": {
|
|
35
|
+
"--json": {
|
|
36
|
+
"type": "boolean"
|
|
37
|
+
},
|
|
38
|
+
"--help": {
|
|
39
|
+
"type": "boolean"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"sideEffects": "none"
|
|
43
|
+
},
|
|
44
|
+
"doctor": {
|
|
45
|
+
"options": {
|
|
46
|
+
"--json": {
|
|
47
|
+
"type": "boolean"
|
|
48
|
+
},
|
|
49
|
+
"--help": {
|
|
50
|
+
"type": "boolean"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": "none"
|
|
54
|
+
},
|
|
55
|
+
"install-notice": {
|
|
56
|
+
"options": {
|
|
57
|
+
"--json": {
|
|
58
|
+
"type": "boolean"
|
|
59
|
+
},
|
|
60
|
+
"--help": {
|
|
61
|
+
"type": "boolean"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"sideEffects": "none"
|
|
65
|
+
},
|
|
66
|
+
"prepare": {
|
|
67
|
+
"options": {
|
|
68
|
+
"--json": {
|
|
69
|
+
"type": "boolean"
|
|
70
|
+
},
|
|
71
|
+
"--help": {
|
|
72
|
+
"type": "boolean"
|
|
73
|
+
},
|
|
74
|
+
"--state-dir": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"sideEffects": "local-write"
|
|
79
|
+
},
|
|
80
|
+
"skill source": {
|
|
81
|
+
"options": {
|
|
82
|
+
"--json": {
|
|
83
|
+
"type": "boolean"
|
|
84
|
+
},
|
|
85
|
+
"--help": {
|
|
86
|
+
"type": "boolean"
|
|
87
|
+
},
|
|
88
|
+
"--name": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"sideEffects": "none"
|
|
93
|
+
},
|
|
94
|
+
"skill status": {
|
|
95
|
+
"options": {
|
|
96
|
+
"--json": {
|
|
97
|
+
"type": "boolean"
|
|
98
|
+
},
|
|
99
|
+
"--help": {
|
|
100
|
+
"type": "boolean"
|
|
101
|
+
},
|
|
102
|
+
"--agent": {
|
|
103
|
+
"type": "string"
|
|
104
|
+
},
|
|
105
|
+
"--target": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
},
|
|
108
|
+
"--name": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"sideEffects": "none"
|
|
113
|
+
},
|
|
114
|
+
"skill install": {
|
|
115
|
+
"options": {
|
|
116
|
+
"--json": {
|
|
117
|
+
"type": "boolean"
|
|
118
|
+
},
|
|
119
|
+
"--help": {
|
|
120
|
+
"type": "boolean"
|
|
121
|
+
},
|
|
122
|
+
"--agent": {
|
|
123
|
+
"type": "string"
|
|
124
|
+
},
|
|
125
|
+
"--target": {
|
|
126
|
+
"type": "string"
|
|
127
|
+
},
|
|
128
|
+
"--name": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"--owner": {
|
|
132
|
+
"type": "string"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"sideEffects": "local-write"
|
|
136
|
+
},
|
|
137
|
+
"skill update": {
|
|
138
|
+
"options": {
|
|
139
|
+
"--json": {
|
|
140
|
+
"type": "boolean"
|
|
141
|
+
},
|
|
142
|
+
"--help": {
|
|
143
|
+
"type": "boolean"
|
|
144
|
+
},
|
|
145
|
+
"--agent": {
|
|
146
|
+
"type": "string"
|
|
147
|
+
},
|
|
148
|
+
"--target": {
|
|
149
|
+
"type": "string"
|
|
150
|
+
},
|
|
151
|
+
"--name": {
|
|
152
|
+
"type": "string"
|
|
153
|
+
},
|
|
154
|
+
"--owner": {
|
|
155
|
+
"type": "string"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"sideEffects": "local-write"
|
|
159
|
+
},
|
|
160
|
+
"update check": {
|
|
161
|
+
"options": {
|
|
162
|
+
"--json": {
|
|
163
|
+
"type": "boolean"
|
|
164
|
+
},
|
|
165
|
+
"--help": {
|
|
166
|
+
"type": "boolean"
|
|
167
|
+
},
|
|
168
|
+
"--state-dir": {
|
|
169
|
+
"type": "string"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"sideEffects": "none"
|
|
173
|
+
},
|
|
174
|
+
"update status": {
|
|
175
|
+
"options": {
|
|
176
|
+
"--json": {
|
|
177
|
+
"type": "boolean"
|
|
178
|
+
},
|
|
179
|
+
"--help": {
|
|
180
|
+
"type": "boolean"
|
|
181
|
+
},
|
|
182
|
+
"--state-dir": {
|
|
183
|
+
"type": "string"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"sideEffects": "none"
|
|
187
|
+
},
|
|
188
|
+
"update policy": {
|
|
189
|
+
"options": {
|
|
190
|
+
"--json": {
|
|
191
|
+
"type": "boolean"
|
|
192
|
+
},
|
|
193
|
+
"--help": {
|
|
194
|
+
"type": "boolean"
|
|
195
|
+
},
|
|
196
|
+
"--state-dir": {
|
|
197
|
+
"type": "string"
|
|
198
|
+
},
|
|
199
|
+
"--mode": {
|
|
200
|
+
"type": "string"
|
|
201
|
+
},
|
|
202
|
+
"--yes": {
|
|
203
|
+
"type": "boolean"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"sideEffects": "local-write",
|
|
207
|
+
"writeCondition": "Only an explicit --mode with --yes changes policy."
|
|
208
|
+
},
|
|
209
|
+
"update install": {
|
|
210
|
+
"options": {
|
|
211
|
+
"--json": {
|
|
212
|
+
"type": "boolean"
|
|
213
|
+
},
|
|
214
|
+
"--help": {
|
|
215
|
+
"type": "boolean"
|
|
216
|
+
},
|
|
217
|
+
"--agent": {
|
|
218
|
+
"type": "string"
|
|
219
|
+
},
|
|
220
|
+
"--target": {
|
|
221
|
+
"type": "string"
|
|
222
|
+
},
|
|
223
|
+
"--yes": {
|
|
224
|
+
"type": "boolean"
|
|
225
|
+
},
|
|
226
|
+
"--state-dir": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
},
|
|
229
|
+
"--package-file": {
|
|
230
|
+
"type": "string"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"sideEffects": "package-and-skill-update"
|
|
234
|
+
},
|
|
235
|
+
"update operation": {
|
|
236
|
+
"options": {
|
|
237
|
+
"--json": {
|
|
238
|
+
"type": "boolean"
|
|
239
|
+
},
|
|
240
|
+
"--help": {
|
|
241
|
+
"type": "boolean"
|
|
242
|
+
},
|
|
243
|
+
"--agent": {
|
|
244
|
+
"type": "string"
|
|
245
|
+
},
|
|
246
|
+
"--target": {
|
|
247
|
+
"type": "string"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"sideEffects": "none"
|
|
251
|
+
},
|
|
252
|
+
"update resume": {
|
|
253
|
+
"options": {
|
|
254
|
+
"--json": {
|
|
255
|
+
"type": "boolean"
|
|
256
|
+
},
|
|
257
|
+
"--help": {
|
|
258
|
+
"type": "boolean"
|
|
259
|
+
},
|
|
260
|
+
"--agent": {
|
|
261
|
+
"type": "string"
|
|
262
|
+
},
|
|
263
|
+
"--target": {
|
|
264
|
+
"type": "string"
|
|
265
|
+
},
|
|
266
|
+
"--yes": {
|
|
267
|
+
"type": "boolean"
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"sideEffects": "local-write"
|
|
271
|
+
},
|
|
272
|
+
"update rollback": {
|
|
273
|
+
"options": {
|
|
274
|
+
"--json": {
|
|
275
|
+
"type": "boolean"
|
|
276
|
+
},
|
|
277
|
+
"--help": {
|
|
278
|
+
"type": "boolean"
|
|
279
|
+
},
|
|
280
|
+
"--agent": {
|
|
281
|
+
"type": "string"
|
|
282
|
+
},
|
|
283
|
+
"--target": {
|
|
284
|
+
"type": "string"
|
|
285
|
+
},
|
|
286
|
+
"--yes": {
|
|
287
|
+
"type": "boolean"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"sideEffects": "local-write"
|
|
291
|
+
},
|
|
292
|
+
"asset check": {
|
|
293
|
+
"options": {
|
|
294
|
+
"--json": {
|
|
295
|
+
"type": "boolean"
|
|
296
|
+
},
|
|
297
|
+
"--help": {
|
|
298
|
+
"type": "boolean"
|
|
299
|
+
},
|
|
300
|
+
"--guide": {
|
|
301
|
+
"type": "string"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"sideEffects": "none"
|
|
305
|
+
},
|
|
306
|
+
"asset write": {
|
|
307
|
+
"options": {
|
|
308
|
+
"--json": {
|
|
309
|
+
"type": "boolean"
|
|
310
|
+
},
|
|
311
|
+
"--help": {
|
|
312
|
+
"type": "boolean"
|
|
313
|
+
},
|
|
314
|
+
"--directory": {
|
|
315
|
+
"type": "string"
|
|
316
|
+
},
|
|
317
|
+
"--plan": {
|
|
318
|
+
"type": "string"
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"sideEffects": "local-write"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"maintenanceContract": {
|
|
325
|
+
"schemaVersion": 1,
|
|
326
|
+
"source": {
|
|
327
|
+
"name": {
|
|
328
|
+
"field": "name",
|
|
329
|
+
"type": "string"
|
|
330
|
+
},
|
|
331
|
+
"package": {
|
|
332
|
+
"field": "package",
|
|
333
|
+
"type": "string"
|
|
334
|
+
},
|
|
335
|
+
"version": {
|
|
336
|
+
"field": "version",
|
|
337
|
+
"type": "string"
|
|
338
|
+
},
|
|
339
|
+
"path": {
|
|
340
|
+
"field": "path",
|
|
341
|
+
"type": "string"
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
"status": {
|
|
345
|
+
"name": {
|
|
346
|
+
"field": "name",
|
|
347
|
+
"type": "string"
|
|
348
|
+
},
|
|
349
|
+
"package": {
|
|
350
|
+
"field": "package",
|
|
351
|
+
"type": "string"
|
|
352
|
+
},
|
|
353
|
+
"version": {
|
|
354
|
+
"field": "version",
|
|
355
|
+
"type": "string"
|
|
356
|
+
},
|
|
357
|
+
"target": {
|
|
358
|
+
"field": "target",
|
|
359
|
+
"type": "string"
|
|
360
|
+
},
|
|
361
|
+
"installed": {
|
|
362
|
+
"field": "installed",
|
|
363
|
+
"type": "boolean"
|
|
364
|
+
},
|
|
365
|
+
"managed": {
|
|
366
|
+
"field": "managed",
|
|
367
|
+
"type": "boolean"
|
|
368
|
+
},
|
|
369
|
+
"current": {
|
|
370
|
+
"field": "current",
|
|
371
|
+
"type": "boolean"
|
|
372
|
+
},
|
|
373
|
+
"edited": {
|
|
374
|
+
"field": "edited",
|
|
375
|
+
"type": "string[]"
|
|
376
|
+
},
|
|
377
|
+
"verification": {
|
|
378
|
+
"field": "verification",
|
|
379
|
+
"type": "object"
|
|
380
|
+
},
|
|
381
|
+
"recovery": {
|
|
382
|
+
"field": "recovery",
|
|
383
|
+
"type": "object"
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
"targetOptions": {
|
|
387
|
+
"--target": {
|
|
388
|
+
"kind": "skill-directory"
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
"agentEligibility": "valid-name-with-generic-fallback",
|
|
392
|
+
"hostDiscoveryAuthority": "host"
|
|
393
|
+
},
|
|
394
|
+
"discoveryContract": {
|
|
395
|
+
"schemaVersion": 1,
|
|
396
|
+
"entryPoints": [
|
|
397
|
+
{
|
|
398
|
+
"argv": [
|
|
399
|
+
"capabilities",
|
|
400
|
+
"--json"
|
|
401
|
+
],
|
|
402
|
+
"result": "manifest"
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
"commandsField": "commandContracts",
|
|
406
|
+
"version": {
|
|
407
|
+
"command": "version",
|
|
408
|
+
"argv": [
|
|
409
|
+
"version",
|
|
410
|
+
"--json"
|
|
411
|
+
],
|
|
412
|
+
"fields": {
|
|
413
|
+
"package": {
|
|
414
|
+
"field": "package",
|
|
415
|
+
"type": "string"
|
|
416
|
+
},
|
|
417
|
+
"version": {
|
|
418
|
+
"field": "version",
|
|
419
|
+
"type": "string"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
"skillCollection": {
|
|
424
|
+
"namesField": "skills",
|
|
425
|
+
"default": "product-asset-set",
|
|
426
|
+
"selector": {
|
|
427
|
+
"option": "--name",
|
|
428
|
+
"type": "string"
|
|
429
|
+
},
|
|
430
|
+
"enumeration": {
|
|
431
|
+
"argv": [
|
|
432
|
+
"skill",
|
|
433
|
+
"source",
|
|
434
|
+
"--json"
|
|
435
|
+
],
|
|
436
|
+
"itemsField": "sources",
|
|
437
|
+
"completeField": "complete"
|
|
438
|
+
},
|
|
439
|
+
"namedSource": [
|
|
440
|
+
"skill",
|
|
441
|
+
"source",
|
|
442
|
+
"--name",
|
|
443
|
+
"{skill}",
|
|
444
|
+
"--json"
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"installationLifecycle": {
|
|
449
|
+
"skillRequired": true,
|
|
450
|
+
"updateRequiresSkillSync": true,
|
|
451
|
+
"required_components": [
|
|
452
|
+
"cli",
|
|
453
|
+
"skill"
|
|
454
|
+
],
|
|
455
|
+
"installGuide": "INSTALL.md",
|
|
456
|
+
"updateGuide": "UPDATE.md",
|
|
457
|
+
"source": [
|
|
458
|
+
"skill",
|
|
459
|
+
"source",
|
|
460
|
+
"--json"
|
|
461
|
+
],
|
|
462
|
+
"install": "skill install --agent {agent} --target {target} --json",
|
|
463
|
+
"update": "update install --agent {agent} --target {target} --yes --json",
|
|
464
|
+
"verification": "skill status --agent {agent} --target {target} --json",
|
|
465
|
+
"hostDiscoveryAuthority": "host",
|
|
466
|
+
"physicalIsolation": true,
|
|
467
|
+
"runtimeEntry": "{target}/.runtime/bin/product-asset-set.mjs",
|
|
468
|
+
"agentNeutral": true,
|
|
469
|
+
"targetRequired": true,
|
|
470
|
+
"hostRegistrationRequired": true,
|
|
471
|
+
"installationMode": "copy",
|
|
472
|
+
"sharedLinksAllowed": false,
|
|
473
|
+
"isolatedRuntimeRequired": true
|
|
474
|
+
},
|
|
475
|
+
"automaticUpdates": {
|
|
476
|
+
"trigger": "ordinary task entry prepare",
|
|
477
|
+
"channel": "latest",
|
|
478
|
+
"ttlHours": 24,
|
|
479
|
+
"timeoutMs": 1500,
|
|
480
|
+
"optOut": "AGENT_PACKAGE_UPDATE_CHECK=0",
|
|
481
|
+
"automaticInstall": false,
|
|
482
|
+
"policy": "advisory-only; explicit install authorization required",
|
|
483
|
+
"enabledByDefault": true,
|
|
484
|
+
"intervalHours": 24,
|
|
485
|
+
"configuration": "update status and update policy --mode off|notify --yes",
|
|
486
|
+
"disable": "AGENT_PACKAGE_UPDATE_CHECK=0",
|
|
487
|
+
"recovery": "offline backoff keeps installed state; malformed cache is preserved",
|
|
488
|
+
"defaultMode": "notify"
|
|
489
|
+
},
|
|
490
|
+
"historicalUpdater": {
|
|
491
|
+
"status": "first-management-release",
|
|
492
|
+
"previousPublishedVersions": [
|
|
493
|
+
"0.1.0",
|
|
494
|
+
"0.1.1"
|
|
495
|
+
],
|
|
496
|
+
"previousUpdater": "absent; previous packages were binless and host-copied",
|
|
497
|
+
"migration": "Existing unowned physical copies require a verified backup before managed installation."
|
|
498
|
+
},
|
|
499
|
+
"requiredCapabilities": [
|
|
500
|
+
"filesystem.read-write",
|
|
501
|
+
"command.execute",
|
|
502
|
+
"media.image.inspect"
|
|
503
|
+
],
|
|
504
|
+
"runtime": {
|
|
505
|
+
"node": ">=22",
|
|
506
|
+
"dependencies": []
|
|
507
|
+
},
|
|
508
|
+
"adapters": {
|
|
509
|
+
"generic": {
|
|
510
|
+
"status": "implemented",
|
|
511
|
+
"destination": "caller-resolved explicit exact directory"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
package/lib/cli.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {spawnSync} from 'node:child_process';
|
|
3
|
+
import * as core from './core.mjs';
|
|
4
|
+
|
|
5
|
+
export async function main(args) {
|
|
6
|
+
const words=[],options={};
|
|
7
|
+
for(let i=0;i<args.length;i++){
|
|
8
|
+
const arg=args[i];
|
|
9
|
+
if(arg==='--version'){words.push('version');continue;}
|
|
10
|
+
if(!arg.startsWith('--')){words.push(arg);continue;}
|
|
11
|
+
const key=arg.slice(2);
|
|
12
|
+
if(Object.hasOwn(options,key))core.fail('INVALID_ARGUMENT','Duplicate option: '+arg);
|
|
13
|
+
if(['json','yes','help'].includes(key))options[key]=true;
|
|
14
|
+
else {
|
|
15
|
+
if(!args[i+1]||args[i+1].startsWith('--'))core.fail('INVALID_ARGUMENT','Missing value for '+arg);
|
|
16
|
+
options[key]=args[++i];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const command=words.join(' ')||'help';
|
|
20
|
+
const manifest=core.readJSON(path.join(core.root,'capabilities.json'));
|
|
21
|
+
const contract=manifest.commandContracts[command];
|
|
22
|
+
if(!contract)core.fail('INVALID_ARGUMENT','Unknown command: '+command);
|
|
23
|
+
for(const key of Object.keys(options))if(!Object.hasOwn(contract.options,'--'+key))core.fail('INVALID_ARGUMENT','Unsupported option --'+key);
|
|
24
|
+
if(options.name&&options.name!==core.name)core.fail('SKILL_NOT_FOUND',options.name);
|
|
25
|
+
let result;
|
|
26
|
+
if(command==='help'||options.help)result={...manifest,commands:manifest.commandContracts,options:contract.options};
|
|
27
|
+
else if(command==='version')result={ok:true,package:core.pkg.name,version:core.pkg.version};
|
|
28
|
+
else if(command==='capabilities')result=manifest;
|
|
29
|
+
else if(command==='install-notice')result={ok:true,partial:true,message:'Read INSTALL.md and UPDATE.md. Resolve skill source, install the complete Skill and runtime into the current Agent’s explicit directory, and verify host discovery.'};
|
|
30
|
+
else if(command==='doctor')result=core.doctor();
|
|
31
|
+
else if(command==='prepare')result={...core.doctor(),update:await core.checkUpdate(options,true)};
|
|
32
|
+
else if(command==='skill source'){
|
|
33
|
+
const item={ok:true,name:core.name,package:core.pkg.name,version:core.pkg.version,source:core.source,path:core.source};
|
|
34
|
+
result={...manifest,...item,complete:true,sources:[item],provenance:{ownership:core.exists(path.join(core.root,'.git'))?'git-source':'package',registryContentVerified:false}};
|
|
35
|
+
}else if(command==='skill status')result=core.status(options);
|
|
36
|
+
else if(command==='skill install'||command==='skill update')result=core.install(options);
|
|
37
|
+
else if(command==='update check')result=await core.checkUpdate(options);
|
|
38
|
+
else if(command==='update status'||command==='update policy')result=core.updatePolicy(options);
|
|
39
|
+
else if(command==='update operation')result=core.updateOperation(options);
|
|
40
|
+
else if(command==='update resume')result=core.resume(options);
|
|
41
|
+
else if(command==='update rollback')result=core.rollback(options);
|
|
42
|
+
else if(command==='update install')result=await core.update(options);
|
|
43
|
+
else if(command==='asset check'||command==='asset write'){
|
|
44
|
+
const required=command==='asset check'?['guide']:['directory','plan'];
|
|
45
|
+
for(const field of required)if(!options[field])core.fail('INVALID_ARGUMENT','Missing --'+field);
|
|
46
|
+
const operation=command==='asset check'?['check',options.guide]:['write',options.directory,options.plan];
|
|
47
|
+
const child=spawnSync(process.execPath,[path.join(core.source,'scripts/asset-guide.mjs'),...operation],{encoding:'utf8',timeout:30000,windowsHide:true});
|
|
48
|
+
try{result=JSON.parse(child.stdout||child.stderr);}catch{core.fail('ASSET_OPERATION_FAILED','Asset tool returned no structured result');}
|
|
49
|
+
if(child.status!==0)process.exitCode=1;
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(command==='version'&&!options.json?core.pkg.version+'\n':JSON.stringify(result,null,2)+'\n');
|
|
52
|
+
if(result?.ok===false)process.exitCode=1;
|
|
53
|
+
}
|
package/lib/core.mjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {spawnSync} from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
export const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json')));
|
|
10
|
+
export const name = pkg.name.split('/')[1];
|
|
11
|
+
export const source = path.join(root, 'skills', name);
|
|
12
|
+
export const receiptName = '.product-skill-install.json';
|
|
13
|
+
export const readJSON = p => JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
14
|
+
export const exists = p => {try {fs.lstatSync(p); return true;} catch(e) {if(e.code==='ENOENT') return false; throw e;}};
|
|
15
|
+
export function fail(code, message, details={}) {throw Object.assign(new Error(message), {code, details});}
|
|
16
|
+
export function create(p, bytes) {if(exists(p)) fail('TARGET_EXISTS', 'Refusing existing file', {path:p}); fs.mkdirSync(path.dirname(p),{recursive:true}); fs.writeFileSync(p,bytes,{flag:'wx'});}
|
|
17
|
+
export function files(dir, prefix='') {
|
|
18
|
+
return fs.readdirSync(dir,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name)).flatMap(d=>{
|
|
19
|
+
const rel=path.posix.join(prefix,d.name), p=path.join(dir,d.name);
|
|
20
|
+
if(d.isSymbolicLink()) fail('UNSAFE_LINK','Symbolic links are not distributable resources',{path:p});
|
|
21
|
+
if(!d.isDirectory()&&!d.isFile()) fail('UNSAFE_RESOURCE','Expected a regular file',{path:p});
|
|
22
|
+
return d.isDirectory()?files(p,rel):[rel];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export const hash = p => crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
|
|
26
|
+
export function hashes(dir) {return Object.fromEntries(files(dir).map(f=>[f,hash(path.join(dir,f))]));}
|
|
27
|
+
export function copy(from,to) {if(exists(to)) fail('TARGET_EXISTS','Copy destination exists',{path:to}); fs.cpSync(from,to,{recursive:true,dereference:false,errorOnExist:true,force:false});}
|
|
28
|
+
export function targetOf(o) {
|
|
29
|
+
if(!o.agent || !/^[a-z][a-z0-9-]{0,63}$/.test(o.agent)) fail('INVALID_AGENT','Supply a valid Agent identifier and its resolved --target');
|
|
30
|
+
if(!o.target || !path.isAbsolute(o.target)) fail('TARGET_REQUIRED','--target must be the absolute exact Skill directory resolved by the current Agent');
|
|
31
|
+
const t=path.resolve(o.target);
|
|
32
|
+
const ownInstalledRuntime = root===path.join(t,'.runtime');
|
|
33
|
+
if(t===path.parse(t).root || t===os.homedir() || t===root || t===source || (!ownInstalledRuntime&&root.startsWith(t+path.sep)) || t.startsWith(root+path.sep)) fail('UNSAFE_TARGET','Target overlaps a protected or source directory');
|
|
34
|
+
let p=path.dirname(t);
|
|
35
|
+
while(p!==path.dirname(p)) {if(exists(p)&&fs.lstatSync(p).isSymbolicLink()) fail('UNSAFE_TARGET','Parent path contains a symlink',{path:p}); p=path.dirname(p);}
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
export function operation(target) {
|
|
39
|
+
const p=target+'.operation.json';
|
|
40
|
+
if(!exists(p)) return null;
|
|
41
|
+
if(fs.lstatSync(p).isSymbolicLink()) fail('UNMANAGED_OPERATION','Operation record is a symlink');
|
|
42
|
+
const data=readJSON(p);
|
|
43
|
+
if(data.package!==pkg.name||data.target!==target) fail('UNMANAGED_OPERATION','Operation record belongs to another owner',{path:p});
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
function saveOperation(target,fields) {
|
|
47
|
+
const previous=operation(target),p=target+'.operation.json';
|
|
48
|
+
if(previous) fs.renameSync(p,p+'.backup-'+crypto.randomUUID());
|
|
49
|
+
create(p,JSON.stringify({...previous,...fields,package:pkg.name,target,updatedAt:new Date().toISOString()},null,2)+'\n');
|
|
50
|
+
}
|
|
51
|
+
export function status(o) {
|
|
52
|
+
const target=targetOf(o), installed=exists(target), rp=path.join(target,receiptName);
|
|
53
|
+
const op=operation(target),pending=Boolean(op&&op.phase!=='complete');
|
|
54
|
+
let receipt=null;
|
|
55
|
+
if(installed && !fs.lstatSync(target).isSymbolicLink() && exists(rp)) {
|
|
56
|
+
try {receipt=readJSON(rp);}catch {fail('INVALID_RECEIPT','Receipt is malformed; preserve it before recovery',{path:rp});}
|
|
57
|
+
}
|
|
58
|
+
const managed=Boolean(receipt?.package===pkg.name && receipt?.name===name && receipt?.target===target && receipt?.agent===o.agent);
|
|
59
|
+
if(managed && (!receipt.hashes || typeof receipt.hashes!=='object' || Object.entries(receipt.hashes).some(([f,h])=>path.isAbsolute(f)||f.split(/[\\/]/).includes('..')||typeof h!=='string'||!/^[a-f0-9]{64}$/.test(h)))) fail('INVALID_RECEIPT','Receipt hashes missing or unsafe');
|
|
60
|
+
const edited=managed?Object.entries(receipt.hashes).filter(([f,h])=>exists(path.join(target,f)) && (fs.lstatSync(path.join(target,f)).isSymbolicLink() || hash(path.join(target,f))!==h)).map(([f])=>f):[];
|
|
61
|
+
const missing=managed?Object.keys(receipt.hashes).filter(f=>!exists(path.join(target,f))):[];
|
|
62
|
+
const canonical=managed && Object.entries(hashes(source)).filter(([f])=>!f.startsWith('agents/')).every(([f,h])=>exists(path.join(target,f))&&hash(path.join(target,f))===h) && runtimeFiles().every(f=>exists(path.join(target,'.runtime',f))&&hash(path.join(target,'.runtime',f))===hash(path.join(root,f))) && exists(path.join(target,'.runtime/skill-integrity.json')) && hash(path.join(target,'.runtime/skill-integrity.json'))===hash(path.join(root,'skill-integrity.json'));
|
|
63
|
+
const verified=managed && edited.length===0 && missing.length===0;
|
|
64
|
+
return {ok:true,name,package:pkg.name,version:pkg.version,installedVersion:receipt?.version??null,target,agent:o.agent,installed,managed,current:Boolean(verified&&canonical&&receipt.version===pkg.version&&!pending),edited,missing,owner:receipt?.owner??'self',verification:{verified,algorithm:'sha256'},recovery:{required:pending||(managed&&!verified),backup:op?.backup??receipt?.backup??null,operation:op},entry:path.join(target,'.runtime','bin',name+'.mjs'),hostDiscovery:{authority:'host',status:'pending'}};
|
|
65
|
+
}
|
|
66
|
+
export function install(o) {
|
|
67
|
+
const before=status(o), t=before.target;
|
|
68
|
+
if(before.installed&&!before.managed) fail('UNMANAGED_TARGET','Existing Skill is user-owned; move it to a verified backup before installing',{target:t});
|
|
69
|
+
if(before.edited.length) fail('LOCAL_EDITS','Managed files have local edits; preserve and reconcile them first',{edited:before.edited});
|
|
70
|
+
if(before.current) return {...before,changed:false};
|
|
71
|
+
if(!doctor().ok) fail('SOURCE_INTEGRITY_FAILED','Verify the acquired package before installation');
|
|
72
|
+
fs.mkdirSync(path.dirname(t),{recursive:true});
|
|
73
|
+
const lock=t+'.install-lock';
|
|
74
|
+
try {fs.mkdirSync(lock);} catch(e) {if(e.code==='EEXIST') fail('LOCKED','Another maintenance operation may be active',{lock});throw e;}
|
|
75
|
+
let stage,backup=before.recovery.required?before.recovery.backup:null,activated=false;
|
|
76
|
+
try {
|
|
77
|
+
stage=fs.mkdtempSync(t+'.stage-');
|
|
78
|
+
for(const f of files(source)) {const p=path.join(stage,f);fs.mkdirSync(path.dirname(p),{recursive:true});fs.copyFileSync(path.join(source,f),p,fs.constants.COPYFILE_EXCL);}
|
|
79
|
+
const runtime=path.join(stage,'.runtime');fs.mkdirSync(runtime);
|
|
80
|
+
for(const f of [...runtimeFiles(),'skill-integrity.json']) {const p=path.join(runtime,f);fs.mkdirSync(path.dirname(p),{recursive:true});fs.copyFileSync(path.join(root,f),p,fs.constants.COPYFILE_EXCL);}
|
|
81
|
+
if(before.installed) {
|
|
82
|
+
const again=status(o);
|
|
83
|
+
if(again.edited.length||!again.managed) fail('LOCAL_EDITS','Concurrent modification detected');
|
|
84
|
+
backup=fs.mkdtempSync(t+'.backup-');
|
|
85
|
+
copy(t,path.join(backup,'skill'));
|
|
86
|
+
for(const f of files(t)) {
|
|
87
|
+
if(f===receiptName || f.startsWith('agents/') || f.startsWith('.runtime/') || Object.hasOwn(readJSON(path.join(t,receiptName)).hashes,f)) continue;
|
|
88
|
+
if(exists(path.join(stage,f))) fail('UNMANAGED_COLLISION','Unmanaged file collides with new managed resource',{file:f,backup});
|
|
89
|
+
fs.mkdirSync(path.dirname(path.join(stage,f)),{recursive:true});fs.copyFileSync(path.join(t,f),path.join(stage,f));
|
|
90
|
+
}
|
|
91
|
+
const metadata=path.join(t,'agents');
|
|
92
|
+
if(exists(metadata)) fs.cpSync(metadata,path.join(stage,'agents'),{recursive:true,force:true});
|
|
93
|
+
}
|
|
94
|
+
const owned=Object.keys(hashes(source)).filter(f=>!f.startsWith('agents/')).concat([...runtimeFiles(),'skill-integrity.json'].map(f=>'.runtime/'+f));
|
|
95
|
+
const digest=Object.fromEntries(owned.map(f=>[f,hash(path.join(stage,f))]));
|
|
96
|
+
create(path.join(stage,receiptName),JSON.stringify({schemaVersion:1,name,package:pkg.name,version:pkg.version,target:t,agent:o.agent,owner:o.owner??before.owner,hashes:digest,backup,installedAt:new Date().toISOString()},null,2)+'\n');
|
|
97
|
+
saveOperation(t,{phase:'synchronizing',agent:o.agent,version:pkg.version,backup});
|
|
98
|
+
if(before.installed) {
|
|
99
|
+
try {fs.renameSync(t,path.join(backup,'previous'));activated=true;}
|
|
100
|
+
catch(e) {
|
|
101
|
+
if(!['EPERM','EACCES','EBUSY'].includes(e.code)) throw e;
|
|
102
|
+
activated=true;
|
|
103
|
+
// Recoverable copy fallback for a live host that locks directory rename.
|
|
104
|
+
const old=readJSON(path.join(t,receiptName));
|
|
105
|
+
for(const f of Object.keys(old.hashes)) if(exists(path.join(t,f))) fs.rmSync(path.join(t,f),{force:true});
|
|
106
|
+
fs.rmSync(path.join(t,receiptName));
|
|
107
|
+
fs.cpSync(stage,t,{recursive:true,force:true});
|
|
108
|
+
fs.rmSync(stage,{recursive:true});stage=null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if(stage) {fs.renameSync(stage,t);stage=null;activated=true;}
|
|
112
|
+
const verified=status(o);
|
|
113
|
+
if(!verified.verification.verified) fail('VERIFICATION_FAILED','Skill synchronization incomplete',{backup,target:t});
|
|
114
|
+
saveOperation(t,{phase:'complete',agent:o.agent,version:pkg.version,backup});
|
|
115
|
+
return {...status(o),changed:true,backup,physicalCopy:true};
|
|
116
|
+
} catch(e) {
|
|
117
|
+
if(activated&&backup&&exists(path.join(backup,'skill'))) {
|
|
118
|
+
if(exists(t)) fs.rmSync(t,{recursive:true,force:true});
|
|
119
|
+
copy(path.join(backup,'skill'),t);
|
|
120
|
+
}
|
|
121
|
+
if(operation(t)) saveOperation(t,{phase:'failed-sync',backup,error:e.code??'SYSTEM_ERROR'});
|
|
122
|
+
e.details={...e.details,backup,target:t,recovery:backup?'Original copy is in backup/skill; inspect state before retrying':'Inspect target and lock before retrying'};
|
|
123
|
+
throw e;
|
|
124
|
+
} finally {if(stage&&exists(stage)) fs.rmSync(stage,{recursive:true,force:true});fs.rmSync(lock,{recursive:true,force:true});}
|
|
125
|
+
}
|
|
126
|
+
export function runtimeFiles() {
|
|
127
|
+
return ['package.json','capabilities.json','README.md','INSTALL.md','UPDATE.md','THIRD_PARTY_NOTICES.md',...['bin','lib','skills'].flatMap(d=>files(path.join(root,d)).map(f=>d+'/'+f))];
|
|
128
|
+
}
|
|
129
|
+
export function doctor() {
|
|
130
|
+
const expected=readJSON(path.join(root,'skill-integrity.json'));
|
|
131
|
+
const observed=Object.fromEntries(runtimeFiles().map(file=>[file,hash(path.join(root,file))]));
|
|
132
|
+
const valid=Object.keys(expected).length===Object.keys(observed).length&&Object.entries(expected).every(([file,value])=>observed[file]===value);
|
|
133
|
+
return {ok:valid,package:pkg.name,version:pkg.version,node:process.version,local_ready:valid,
|
|
134
|
+
integrity:{verified:valid,algorithm:'sha256'},service_access:'not-probed',hostDiscovery:{authority:'host',status:'pending'}};
|
|
135
|
+
}
|
|
136
|
+
function cacheDir(o={}) {
|
|
137
|
+
const value=o['state-dir']??process.env.PRODUCT_SKILL_STATE_DIR??path.join(os.homedir(),'.local','state',name);
|
|
138
|
+
if(!path.isAbsolute(value)) fail('INVALID_INPUT','State directory must be absolute');
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
function policy(o) {
|
|
142
|
+
const file=path.join(cacheDir(o),'update-policy.json');
|
|
143
|
+
const data=exists(file)?readJSON(file):{mode:'notify'};
|
|
144
|
+
if(!['off','notify'].includes(data.mode)) fail('INVALID_POLICY','Preserve invalid update policy before repair');
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
export function updatePolicy(o) {
|
|
148
|
+
const current=policy(o);
|
|
149
|
+
if(o.mode){
|
|
150
|
+
if(!o.yes) fail('CONFIRMATION_REQUIRED','Policy changes require --yes');
|
|
151
|
+
if(!['off','notify'].includes(o.mode)) fail('INVALID_ARGUMENT','Policy supports off or notify');
|
|
152
|
+
const file=path.join(cacheDir(o),'update-policy.json');
|
|
153
|
+
if(exists(file)) fs.renameSync(file,file+'.backup-'+crypto.randomUUID());
|
|
154
|
+
create(file,JSON.stringify({mode:o.mode})+'\n');
|
|
155
|
+
}
|
|
156
|
+
return {ok:true,mode:o.mode??current.mode,automaticInstall:false,channel:'latest',timeoutMs:1500,ttlHours:24,disable:'AGENT_PACKAGE_UPDATE_CHECK=0',owner:runtimeOwner()};
|
|
157
|
+
}
|
|
158
|
+
function runtimeOwner() {
|
|
159
|
+
const receipt=path.join(root,'..',receiptName);
|
|
160
|
+
return exists(receipt)?readJSON(receipt).owner??'self':'self';
|
|
161
|
+
}
|
|
162
|
+
export function compareVersions(a,b) {
|
|
163
|
+
const aa=a.split('.').map(Number),bb=b.split('.').map(Number);
|
|
164
|
+
for(let i=0;i<3;i++) if(aa[i]!==bb[i]) return aa[i]-bb[i];
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
async function queryRegistry() {
|
|
168
|
+
const url=process.env.PRODUCT_SKILL_REGISTRY_URL??'https://registry.npmjs.org';
|
|
169
|
+
try {
|
|
170
|
+
const response=await fetch(url+'/'+encodeURIComponent(pkg.name)+'/latest',{signal:AbortSignal.timeout(1500)});
|
|
171
|
+
if(!response.ok) throw Error('registry-status-'+response.status);
|
|
172
|
+
const data=await response.json();
|
|
173
|
+
if(data.name!==pkg.name||!/^\d+\.\d+\.\d+$/.test(data.version)) throw Error('invalid-registry-identity');
|
|
174
|
+
return {ok:true,package:pkg.name,current:pkg.version,latest:data.version,available:compareVersions(data.version,pkg.version)>0,checkedAt:Date.now(),channel:'latest',automaticInstall:false};
|
|
175
|
+
} catch {
|
|
176
|
+
return {ok:false,code:'REGISTRY_UNAVAILABLE',checkedAt:Date.now(),retained:true};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export async function checkUpdate(o={},automatic=false) {
|
|
180
|
+
if(runtimeOwner()!=='self') return {ok:true,deferred:true,owner:runtimeOwner(),reason:'parent-managed'};
|
|
181
|
+
if(process.env.AGENT_PACKAGE_UPDATE_CHECK==='0') return {ok:true,disabled:true};
|
|
182
|
+
const settings=policy(o);
|
|
183
|
+
if(automatic&&settings.mode==='off') return {ok:true,disabled:true};
|
|
184
|
+
if(!automatic) return queryRegistry();
|
|
185
|
+
const dir=cacheDir(o),cache=path.join(dir,'update-cache.json');
|
|
186
|
+
let previous=null;
|
|
187
|
+
if(exists(cache)){
|
|
188
|
+
try{previous=readJSON(cache);}catch{return {ok:false,code:'INVALID_CACHE',cache,preserved:true};}
|
|
189
|
+
if(typeof previous.checkedAt!=='number'||previous.package!==pkg.name) return {ok:false,code:'INVALID_CACHE',cache,preserved:true};
|
|
190
|
+
const ttl=previous.ok?86400000:300000;
|
|
191
|
+
if(Date.now()-previous.checkedAt<ttl) return {...previous,cached:true};
|
|
192
|
+
}
|
|
193
|
+
fs.mkdirSync(dir,{recursive:true});
|
|
194
|
+
const lock=path.join(dir,'update-check-lock');
|
|
195
|
+
try{fs.mkdirSync(lock);}catch(e){if(e.code==='EEXIST')return {ok:true,deferred:true,reason:'concurrent-check'};throw e;}
|
|
196
|
+
try{
|
|
197
|
+
const result={...await queryRegistry(),package:pkg.name};
|
|
198
|
+
const pending=path.join(dir,'update-cache-'+crypto.randomUUID()+'.json');
|
|
199
|
+
create(pending,JSON.stringify(result)+'\n');
|
|
200
|
+
if(exists(cache)) fs.renameSync(cache,cache+'.previous-'+crypto.randomUUID());
|
|
201
|
+
fs.renameSync(pending,cache);
|
|
202
|
+
return result;
|
|
203
|
+
}finally{fs.rmSync(lock,{recursive:true,force:true});}
|
|
204
|
+
}
|
|
205
|
+
function npmEntry() {
|
|
206
|
+
if(process.env.npm_execpath?.endsWith('npm-cli.js')&&exists(process.env.npm_execpath)) return process.env.npm_execpath;
|
|
207
|
+
for(const directory of (process.env.PATH??'').split(path.delimiter)){
|
|
208
|
+
for(const command of process.platform==='win32'?['npm.cmd','npm']:['npm']){
|
|
209
|
+
const file=path.join(directory,command);
|
|
210
|
+
if(!exists(file))continue;
|
|
211
|
+
const real=fs.realpathSync(file);
|
|
212
|
+
const candidates=[real,path.join(path.dirname(real),'node_modules/npm/bin/npm-cli.js'),path.join(path.dirname(real),'../lib/node_modules/npm/bin/npm-cli.js')];
|
|
213
|
+
const entry=candidates.find(candidate=>candidate.endsWith('npm-cli.js')&&exists(candidate));
|
|
214
|
+
if(entry)return entry;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
fail('NPM_UNAVAILABLE','A stable npm CLI is required for package acquisition');
|
|
218
|
+
}
|
|
219
|
+
export function updateOperation(o) {
|
|
220
|
+
const target=targetOf(o);
|
|
221
|
+
return {ok:true,package:pkg.name,version:pkg.version,target,operation:operation(target),hostDiscovery:{authority:'host',status:'pending'}};
|
|
222
|
+
}
|
|
223
|
+
export function resume(o) {
|
|
224
|
+
const before=status(o),op=operation(before.target);
|
|
225
|
+
if(!o.yes)fail('CONFIRMATION_REQUIRED','Recovery requires --yes');
|
|
226
|
+
if(before.edited.length)fail('LOCAL_EDITS','Preserve edits before recovery',{edited:before.edited});
|
|
227
|
+
if(!op?.candidateEntry||!exists(op.candidateEntry)) fail('RECOVERY_REQUIRED','No acquired candidate entry; inspect operation or roll back',{operation:op});
|
|
228
|
+
const actual=readJSON(path.join(path.dirname(op.candidateEntry),'../package.json'));
|
|
229
|
+
if(actual.name!==pkg.name||actual.version!==op.to) fail('VERSION_MISMATCH','Recorded candidate identity mismatch');
|
|
230
|
+
const sync=spawnSync(process.execPath,[op.candidateEntry,'skill','update','--agent',o.agent,'--target',before.target,'--json'],{encoding:'utf8',timeout:120000,windowsHide:true,env:{...process.env,AGENT_PACKAGE_UPDATE_CHECK:'0'}});
|
|
231
|
+
let data;
|
|
232
|
+
try{data=JSON.parse(sync.stdout||sync.stderr);}catch{}
|
|
233
|
+
if(sync.status!==0||data?.current!==true) fail('SKILL_SYNC_FAILED','Verified candidate retained; resume without another npm installation',{stage:'skill-synchronization',exitCode:sync.status,child:data,operation:op});
|
|
234
|
+
return {ok:true,package:pkg.name,version:actual.version,packageChanged:true,transaction:op.transaction,entry:data.entry,synchronization:data,hostDiscovery:{authority:'host',status:'pending'}};
|
|
235
|
+
}
|
|
236
|
+
export function rollback(o) {
|
|
237
|
+
if(!o.yes)fail('CONFIRMATION_REQUIRED','Rollback requires --yes');
|
|
238
|
+
const before=status(o),op=operation(before.target),snapshot=op?.transaction&&path.join(op.transaction,'before');
|
|
239
|
+
if(before.edited.length)fail('LOCAL_EDITS','Preserve edits before rollback',{edited:before.edited});
|
|
240
|
+
if(!snapshot||!exists(snapshot)) fail('RECOVERY_REQUIRED','No pre-acquisition snapshot exists',{operation:op});
|
|
241
|
+
files(snapshot);
|
|
242
|
+
const pending=fs.mkdtempSync(before.target+'.rollback-');
|
|
243
|
+
copy(snapshot,path.join(pending,'restored'));
|
|
244
|
+
const held=path.join(pending,'previous');
|
|
245
|
+
if(exists(before.target))fs.renameSync(before.target,held);
|
|
246
|
+
try{fs.renameSync(path.join(pending,'restored'),before.target);}
|
|
247
|
+
catch(error){if(exists(held))fs.renameSync(held,before.target);throw error;}
|
|
248
|
+
saveOperation(before.target,{phase:'complete',rolledBack:true});
|
|
249
|
+
return {...status(o),rolledBack:true,backup:pending};
|
|
250
|
+
}
|
|
251
|
+
export async function update(o) {
|
|
252
|
+
if(!o.yes)fail('CONFIRMATION_REQUIRED','Package update requires --yes');
|
|
253
|
+
const before=status(o);
|
|
254
|
+
if(before.owner!=='self')fail('PARENT_MANAGED','Update this installation through its collection owner',{owner:before.owner});
|
|
255
|
+
if(before.installed&&!before.managed)fail('UNMANAGED_TARGET','Resolve existing user-owned target before acquisition');
|
|
256
|
+
if(before.edited.length)fail('LOCAL_EDITS','Reconcile local edits before package acquisition',{edited:before.edited});
|
|
257
|
+
const previous=operation(before.target);
|
|
258
|
+
if(previous&&previous.phase!=='complete')fail('RECOVERY_REQUIRED','Inspect the original operation; resume or roll back without acquiring again',{operation:previous});
|
|
259
|
+
if(exists(path.join(root,'.git')))fail('SOURCE_CHECKOUT','Update this source checkout through Git; install a packed version for package maintenance');
|
|
260
|
+
const check=o['package-file']?null:await checkUpdate(o);
|
|
261
|
+
if(check&&!check.ok)fail(check.code,'Registry evidence unavailable; current installation retained');
|
|
262
|
+
if(check&&!check.available)return {...install(o),packageChanged:false,update:check};
|
|
263
|
+
if(o['package-file']&&(!path.isAbsolute(o['package-file'])||!exists(o['package-file'])))fail('INVALID_INPUT','--package-file must identify an existing absolute tarball');
|
|
264
|
+
fs.mkdirSync(path.dirname(before.target),{recursive:true});
|
|
265
|
+
const lock=before.target+'.update-lock';
|
|
266
|
+
try{fs.mkdirSync(lock);}catch(e){if(e.code==='EEXIST')fail('LOCKED','Package acquisition is already active');throw e;}
|
|
267
|
+
try{
|
|
268
|
+
const transaction=fs.mkdtempSync(before.target+'.update-');
|
|
269
|
+
if(before.installed)copy(before.target,path.join(transaction,'before'));
|
|
270
|
+
const candidate=path.join(transaction,'candidate/node_modules',...pkg.name.split('/'));
|
|
271
|
+
const candidateEntry=path.join(candidate,'bin',name+'.mjs');
|
|
272
|
+
saveOperation(before.target,{phase:'acquisition',agent:o.agent,from:pkg.version,to:check?.latest??null,transaction,preReplacementBackup:before.installed?path.join(transaction,'before'):null,candidateEntry});
|
|
273
|
+
const result=spawnSync(process.execPath,[npmEntry(),'install','--prefix',path.join(transaction,'candidate'),'--ignore-scripts','--no-audit','--no-fund',o['package-file']??pkg.name+'@'+check.latest],{encoding:'utf8',timeout:120000,windowsHide:true,env:{...process.env,npm_config_dry_run:'false',npm_config_json:'false'}});
|
|
274
|
+
if(result.status!==0)fail('ACQUISITION_FAILED','npm acquisition failed; original target retained',{stage:'package-acquisition',exitCode:result.status,signal:result.signal,transaction});
|
|
275
|
+
const actual=readJSON(path.join(candidate,'package.json'));
|
|
276
|
+
if(actual.name!==pkg.name||!/^\d+\.\d+\.\d+$/.test(actual.version)||compareVersions(actual.version,pkg.version)<0||check&&actual.version!==check.latest)fail('VERSION_MISMATCH','Candidate identity mismatch; original target retained',{transaction});
|
|
277
|
+
saveOperation(before.target,{phase:'candidate-ready',to:actual.version});
|
|
278
|
+
return resume(o);
|
|
279
|
+
}finally{fs.rmSync(lock,{recursive:true,force:true});}
|
|
280
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/product-asset-set",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Build reusable product reference image sets.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
"skills",
|
|
15
15
|
"INSTALL.md",
|
|
16
16
|
"UPDATE.md",
|
|
17
|
-
"scripts/verify-package.mjs"
|
|
17
|
+
"scripts/verify-package.mjs",
|
|
18
|
+
"bin",
|
|
19
|
+
"lib",
|
|
20
|
+
"capabilities.json",
|
|
21
|
+
"skill-integrity.json",
|
|
22
|
+
"THIRD_PARTY_NOTICES.md",
|
|
23
|
+
"scripts/sync-version.mjs"
|
|
18
24
|
],
|
|
19
25
|
"publishConfig": {
|
|
20
26
|
"access": "public"
|
|
@@ -27,13 +33,19 @@
|
|
|
27
33
|
"path": "skills/product-asset-set"
|
|
28
34
|
}
|
|
29
35
|
],
|
|
30
|
-
"installation": "
|
|
36
|
+
"installation": "managed-physical-copy",
|
|
31
37
|
"installGuide": "INSTALL.md",
|
|
32
38
|
"updateGuide": "UPDATE.md"
|
|
33
39
|
},
|
|
34
40
|
"scripts": {
|
|
35
41
|
"check": "node scripts/verify-package.mjs",
|
|
36
42
|
"prepack": "node scripts/verify-package.mjs",
|
|
37
|
-
"test": "node --test test/*.test.mjs"
|
|
43
|
+
"test": "node --test test/*.test.mjs",
|
|
44
|
+
"version": "node scripts/sync-version.mjs --write",
|
|
45
|
+
"postinstall": "node bin/product-asset-set.mjs install-notice"
|
|
46
|
+
},
|
|
47
|
+
"type": "module",
|
|
48
|
+
"bin": {
|
|
49
|
+
"product-asset-set": "bin/product-asset-set.mjs"
|
|
38
50
|
}
|
|
39
51
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
6
|
+
const pkg=JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
|
|
7
|
+
const name=pkg.name.split('/')[1];
|
|
8
|
+
function inventory(dir,prefix=''){
|
|
9
|
+
return fs.readdirSync(dir,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name)).flatMap(item=>{
|
|
10
|
+
const rel=path.posix.join(prefix,item.name),file=path.join(dir,item.name);
|
|
11
|
+
if(item.isDirectory())return inventory(file,rel);
|
|
12
|
+
if(!item.isFile())throw Error('Only physical resources are distributable: '+file);
|
|
13
|
+
return [rel];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const resources=['package.json','capabilities.json','README.md','INSTALL.md','UPDATE.md','THIRD_PARTY_NOTICES.md',...['bin','lib','skills'].flatMap(dir=>inventory(path.join(root,dir)).map(file=>dir+'/'+file))];
|
|
17
|
+
if(process.argv.includes('--write')){
|
|
18
|
+
const capFile=path.join(root,'capabilities.json');
|
|
19
|
+
const cap=JSON.parse(fs.readFileSync(capFile,'utf8'));
|
|
20
|
+
cap.version=pkg.version;
|
|
21
|
+
fs.writeFileSync(capFile,JSON.stringify(cap,null,2)+'\n');
|
|
22
|
+
const skill=path.join(root,'skills',name);
|
|
23
|
+
fs.writeFileSync(path.join(skill,'package.json'),JSON.stringify({name,version:pkg.version,package:pkg.name},null,2)+'\n');
|
|
24
|
+
const file=path.join(skill,'SKILL.md');
|
|
25
|
+
const text=fs.readFileSync(file,'utf8');
|
|
26
|
+
const frontmatter='metadata:\n package: "'+pkg.name+'"\n version: "'+pkg.version+'"\n';
|
|
27
|
+
fs.writeFileSync(file,text.replace(/\n(?:metadata:\n package: .*\n version: .*\n)?---\n/, '\n'+frontmatter+'---\n'));
|
|
28
|
+
const actual=[...new Set([...resources,'skills/'+name+'/package.json'])].sort();
|
|
29
|
+
const hashes=Object.fromEntries(actual.map(file=>[file,createHash('sha256').update(fs.readFileSync(path.join(root,file))).digest('hex')]));
|
|
30
|
+
fs.writeFileSync(path.join(root,'skill-integrity.json'),JSON.stringify(hashes,null,2)+'\n');
|
|
31
|
+
}else{
|
|
32
|
+
const cap=JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
|
|
33
|
+
const nested=JSON.parse(fs.readFileSync(path.join(root,'skills',name,'package.json'),'utf8'));
|
|
34
|
+
if(cap.package!==pkg.name||cap.version!==pkg.version||nested.version!==pkg.version||nested.package!==pkg.name)throw Error('Release identity mismatch');
|
|
35
|
+
const manifest=JSON.parse(fs.readFileSync(path.join(root,'skill-integrity.json'),'utf8'));
|
|
36
|
+
const actual=[...new Set(resources)].sort();
|
|
37
|
+
if(JSON.stringify(Object.keys(manifest).sort())!==JSON.stringify(actual))throw Error('Integrity scope drift');
|
|
38
|
+
for(const file of actual)if(manifest[file]!==createHash('sha256').update(fs.readFileSync(path.join(root,file))).digest('hex'))throw Error('Release integrity drift: '+file);
|
|
39
|
+
}
|
|
@@ -22,3 +22,10 @@ function verifyTree(directory) {
|
|
|
22
22
|
verifyTree(skill.path);
|
|
23
23
|
const result = spawnSync(process.execPath, ['--check', join(skill.path, 'scripts', 'asset-guide.mjs')], {stdio:'inherit'});
|
|
24
24
|
assert.equal(result.status, 0);
|
|
25
|
+
|
|
26
|
+
const identityCheck=spawnSync(process.execPath,['scripts/sync-version.mjs'],{stdio:'inherit'});
|
|
27
|
+
assert.equal(identityCheck.status,0);
|
|
28
|
+
assert.ok(pkg.bin[skill.name]);
|
|
29
|
+
const metadata=spawnSync(process.execPath,[pkg.bin[skill.name],'version','--json'],{encoding:'utf8'});
|
|
30
|
+
assert.equal(metadata.status,0);
|
|
31
|
+
assert.equal(JSON.parse(metadata.stdout).version,pkg.version);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"INSTALL.md": "cb2478c191d82f35719bfe81bab0c901d2397a56a25b613b30fc006b6527bd5c",
|
|
3
|
+
"README.md": "416fbd27489b1a2af74e98a0caf62cb60fe32247e8efc9b3dc4ec91e3b08c20e",
|
|
4
|
+
"THIRD_PARTY_NOTICES.md": "a5034bf4b0b527c89ecc5b0c6fc9eef5869b94541b93c4e55bde1da47bf407b8",
|
|
5
|
+
"UPDATE.md": "6612edc7807be29ba8e9ea3a1814ed8afa29a7bb26ea6565568e0d242bd59058",
|
|
6
|
+
"bin/product-asset-set.mjs": "25cdc4f8f7dc59f31205cdaec7273a8362ce9eec728803131e724568b8b5fc0f",
|
|
7
|
+
"capabilities.json": "b837c7c51f9097e8734b4d513c0a7c8241bd0e20ee09906731fca6ff23d678f1",
|
|
8
|
+
"lib/cli.mjs": "f25d170ba55b703b3bdf476e0621c74a30eec75a246d34b9b490367c05aae07b",
|
|
9
|
+
"lib/core.mjs": "339dfcd79abebca4a776e8a6e8b907d6e6ce4a027dc3a4808596d5e9cd797260",
|
|
10
|
+
"package.json": "4846c15d5d8700c4abe0cc404c1b85e7444de7e2d3e23613912b71f2fdf34f5a",
|
|
11
|
+
"skills/product-asset-set/SKILL.md": "8de7a640e9381e4215723c555411379cea3e5224d145ee20048de0842be1f981",
|
|
12
|
+
"skills/product-asset-set/adapters/generic.json": "38258c58db4b17105b1018c7aab9e00e59a372267f525005ddc2027f735e123a",
|
|
13
|
+
"skills/product-asset-set/agents/openai.yaml": "67bd446b7e2b81468af6cf5d968c7130f8f045161604223913ac0b32ddf94f77",
|
|
14
|
+
"skills/product-asset-set/capabilities.json": "81f067f1d6e070bd9cde5cbf2ae2283bf36eaf6f46ec9204d9982e4e3ca4d100",
|
|
15
|
+
"skills/product-asset-set/package.json": "682d712ad9da337679e37344f70bdb23e2952f0773ed18c2c2629e4a132b15c4",
|
|
16
|
+
"skills/product-asset-set/references/asset-format.md": "0719d9457ed59418e578925c16cc27bee38d10d4b1161cb5dd94293bd591c9d4",
|
|
17
|
+
"skills/product-asset-set/scripts/asset-guide.mjs": "412e4827b09def5e8532be566173029e36f36e00783739551c1eeb7a9404f555"
|
|
18
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: product-asset-set
|
|
3
3
|
description: 从产品原图、使用场景或详情页制作可复用的独立高清产品参考图组,交付资产说明.txt,支持逐张验收、修订和定稿。
|
|
4
|
+
metadata:
|
|
5
|
+
package: "@petercjl/product-asset-set"
|
|
6
|
+
version: "0.1.2"
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
# 产品资产生成
|
|
7
10
|
|
|
8
11
|
输入是产品资料、可选的指定款色与形态基准。交付一个版本目录,包含独立高清 PNG 和一份 UTF-8 `资产说明.txt`。Agent 根据每张图的产品自然形态、拍摄角度和证据用途独立选择画幅,同一图组可同时包含横图、竖图和方图。用户明确指定的尺寸优先;与关键证据完整性冲突时说明取舍并确认。结束时提供版本目录、各图链接及用途;若用户想继续替换,提示另行提供目标场景和这套资产。
|
|
9
12
|
|
|
13
|
+
普通任务入口先执行 `node <本 Skill 目录>/.runtime/bin/product-asset-set.mjs prepare --json`,进行有界更新检测及本地诊断;源码开发使用包内 bin 入口。父集合管理的安装由所属集合统一更新。安装、更新和恢复遵循包根目录 INSTALL.md / UPDATE.md,执行入口为当前目标自有运行时。
|
|
14
|
+
|
|
10
15
|
1. **核对来源与形态。** 逐个打开实际文件,记录路径、编号、哈希和观察。每个查看结果与同次调用的文件标识绑定;文件名与描述冲突时,单独重开该文件核实。详情页查看全部商品相关区域,必要时按原像素裁切。比较轮廓、比例和可见结构,区分透视、遮挡与正常受压。如果有真实冲突或无法排除冲突,展示对应原图,等待用户指定基准对象;已有明确选择则复用。记录基准与相关素材各自的证据职责。
|
|
11
16
|
2. **规划独立图片。** 至少包含一张完整全貌(`overall`)和一张有可辨参照物或人物的相对尺度(`scale`)。按新增证据规划 `structure`、`texture`、`marking` 或 `use`。同一用途优先留最清楚的一张。为每张分配稳定编号,列出画幅、目标尺寸、选择理由、证据来源、保留内容、编辑范围和验收点。先确定产品与必要参照物的自然朝向,再选择能完整容纳主体且主体占比足够的画幅。横向长条产品全貌优先评估 16:9 或 3:2,竖向产品或人物使用图优先评估 3:4、2:3 或 9:16,紧凑全貌和局部细节可用 1:1 或 4:3;按实际内容选择。产品的摆放、姿态和比例服从原始参考,避免为了适配画幅而旋转成长对角线、悬浮或改变产品形态。源图缺少必要证据时请求补图,可交付清楚标明缺口的阶段性预览。
|
|
12
17
|
3. **先全貌,再逐张制作。** 优先用原照片和原人物、动作、场景。需要生成时,发现当前宿主的参考图编辑能力,核对所选画幅、服务支持的尺寸和费用,按授权每次生成一张。先对照原始基准验收全貌,再做依赖该全貌的图片。使用场景图由原图提供人物和使用关系,产品身份由基准参考提供。提示词聚焦构图和必要编辑,产品结构与材质服从参考。记录每张实际输出尺寸和比例。实际比例与计划略有偏差时,先看完整性与主体可读性;需要精确比例时仅采用保留关键证据的等比整理。逐张查看真实结果,验收身份、用途、清晰度和主体占比。每次调用记录原始输入、请求、可用的真实任务标识和结果;未知提交先查原任务。
|