@petercjl/cloud-hug-style-transfer 0.3.0 → 0.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  将旧详情页、产品摄影、主 KV 或 SKU 转换为抱抱云朵社品牌视觉,保留当次商品事实、文案与用途。包内包含完整 19 页定稿、品牌标识、人物设定与摄影排版方法,约 41 MB 素材。
4
4
 
5
- 支持 Codex、SealSeek、WorkBuddy。需要 Node.js 22+、宿主命令执行和看图能力。生图统一通过必需的 `@petercjl/sealseek-media-cli`(0.6.1+)及其配套 `sealseek-media` Skill 执行;每次任务先检查,缺失或不兼容时暂停整个任务,安装并复查通过后继续。有效 SealSeek 登录、网络及额度由使用者准备,包不附带账号或额度。宿主生图效果仍需分别实测。
5
+ 支持 Codex、SealSeek、WorkBuddy。需要 Node.js 22+、宿主命令执行和看图能力。生图统一通过必需的 `@petercjl/sealseek-media-cli`(0.6.1+)及其配套 `sealseek-media` Skill 执行;每次业务任务先检查,缺失或不兼容时暂停整个任务,安装并复查通过后继续。有效 SealSeek 登录、网络及额度由使用者准备,包不附带账号或额度。宿主生图效果仍需分别实测。
6
6
 
7
7
  先安装生图依赖,将提示词发送给 Agent:
8
8
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  > 请诊断并修复 cloud-hug-style-transfer 的这次报错:粘贴报错内容。
30
30
 
31
- 安装、更新与检查由 Agent 执行包内 CLI。包的 `skill source --json` 提供规范来源;Agent 随后调用 `skill install --agent <当前宿主>`,支持 `--root` 指定实际技能目录。普通任务不联网检查或自动更新。显式更新使用 stable latest,备份旧包并同步已登记的受管 Skill,失败时恢复旧包;离线保留当前安装。已有独立安装或本地修改将停止覆盖并报告。源码或上层工具管理的包由其拥有者更新。
31
+ 安装、更新与检查由 Agent 执行包内 CLI。包的 `skill source --json` 提供规范来源;Agent 随后调用 `skill install --agent <当前宿主>`,支持 `--root` 指定实际技能目录。普通任务不联网检查或自动更新。显式更新使用 stable latest,备份旧包并同步已登记的受管 Skill;npm 已是最新版时仍修复旧 Skill 与兼容元数据,返回安装位置、来源、校验、备份和耗时。包升级失败时恢复旧包;离线保留当前安装。已有独立安装或本地修改将停止覆盖并报告。源码或上层工具管理的包由其拥有者更新。
32
32
 
33
33
  兼容证据分别记录安装机制与生图效果。CI 覆盖 macOS、Windows、Linux 的包管理机制;宿主实际生成需使用各自账户独立验证。品牌与输入素材须具备相应使用权;公开 npm 分发不转让商标与素材的独立再分发权。
34
34
 
package/capabilities.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "nested_skills": [
25
25
  "sealseek-media"
26
26
  ],
27
- "generation": "Required @petercjl/sealseek-media-cli >=0.6.1 and its canonical sealseek-media Skill; missing or incompatible dependency pauses all tasks before analysis or planning.",
27
+ "generation": "Required for business planning and generation; maintenance is independent of media service dependencies.",
28
28
  "host_capability_skills": {
29
29
  "codex": [],
30
30
  "sealseek": [],
@@ -70,5 +70,249 @@
70
70
  "archive": "none during generation; explicit pair batch after each success",
71
71
  "planner": "canvas row --input JSON_FILE --json",
72
72
  "recovery": "same canvas/task/row IDs; inspect before replay"
73
+ },
74
+ "commandContracts": {
75
+ "version": {
76
+ "options": {
77
+ "--json": {
78
+ "type": "boolean",
79
+ "description": "Machine-readable JSON (also the default)."
80
+ },
81
+ "--help": {
82
+ "type": "boolean",
83
+ "description": "Read-only help for this command."
84
+ }
85
+ },
86
+ "sideEffects": "none"
87
+ },
88
+ "capabilities": {
89
+ "options": {
90
+ "--json": {
91
+ "type": "boolean",
92
+ "description": "Machine-readable JSON (also the default)."
93
+ },
94
+ "--help": {
95
+ "type": "boolean",
96
+ "description": "Read-only help for this command."
97
+ }
98
+ },
99
+ "sideEffects": "none"
100
+ },
101
+ "doctor": {
102
+ "options": {
103
+ "--json": {
104
+ "type": "boolean",
105
+ "description": "Machine-readable JSON (also the default)."
106
+ },
107
+ "--help": {
108
+ "type": "boolean",
109
+ "description": "Read-only help for this command."
110
+ },
111
+ "--agent": {
112
+ "type": "string",
113
+ "enum": [
114
+ "codex",
115
+ "sealseek",
116
+ "workbuddy"
117
+ ],
118
+ "description": "Target host; defaults to codex."
119
+ },
120
+ "--root": {
121
+ "type": "string",
122
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
123
+ }
124
+ },
125
+ "sideEffects": "none"
126
+ },
127
+ "skill source": {
128
+ "options": {
129
+ "--json": {
130
+ "type": "boolean",
131
+ "description": "Machine-readable JSON (also the default)."
132
+ },
133
+ "--help": {
134
+ "type": "boolean",
135
+ "description": "Read-only help for this command."
136
+ }
137
+ },
138
+ "sideEffects": "none"
139
+ },
140
+ "skill status": {
141
+ "options": {
142
+ "--json": {
143
+ "type": "boolean",
144
+ "description": "Machine-readable JSON (also the default)."
145
+ },
146
+ "--help": {
147
+ "type": "boolean",
148
+ "description": "Read-only help for this command."
149
+ },
150
+ "--agent": {
151
+ "type": "string",
152
+ "enum": [
153
+ "codex",
154
+ "sealseek",
155
+ "workbuddy"
156
+ ],
157
+ "description": "Target host; defaults to codex."
158
+ },
159
+ "--root": {
160
+ "type": "string",
161
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
162
+ }
163
+ },
164
+ "sideEffects": "none"
165
+ },
166
+ "skill install": {
167
+ "options": {
168
+ "--json": {
169
+ "type": "boolean",
170
+ "description": "Machine-readable JSON (also the default)."
171
+ },
172
+ "--help": {
173
+ "type": "boolean",
174
+ "description": "Read-only help for this command."
175
+ },
176
+ "--agent": {
177
+ "type": "string",
178
+ "enum": [
179
+ "codex",
180
+ "sealseek",
181
+ "workbuddy"
182
+ ],
183
+ "description": "Target host; defaults to codex."
184
+ },
185
+ "--root": {
186
+ "type": "string",
187
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
188
+ }
189
+ },
190
+ "sideEffects": "local-write"
191
+ },
192
+ "skill update": {
193
+ "options": {
194
+ "--json": {
195
+ "type": "boolean",
196
+ "description": "Machine-readable JSON (also the default)."
197
+ },
198
+ "--help": {
199
+ "type": "boolean",
200
+ "description": "Read-only help for this command."
201
+ },
202
+ "--agent": {
203
+ "type": "string",
204
+ "enum": [
205
+ "codex",
206
+ "sealseek",
207
+ "workbuddy"
208
+ ],
209
+ "description": "Target host; defaults to codex."
210
+ },
211
+ "--root": {
212
+ "type": "string",
213
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
214
+ }
215
+ },
216
+ "sideEffects": "local-write"
217
+ },
218
+ "update check": {
219
+ "options": {
220
+ "--json": {
221
+ "type": "boolean",
222
+ "description": "Machine-readable JSON (also the default)."
223
+ },
224
+ "--help": {
225
+ "type": "boolean",
226
+ "description": "Read-only help for this command."
227
+ },
228
+ "--agent": {
229
+ "type": "string",
230
+ "enum": [
231
+ "codex",
232
+ "sealseek",
233
+ "workbuddy"
234
+ ],
235
+ "description": "Target host; defaults to codex."
236
+ },
237
+ "--root": {
238
+ "type": "string",
239
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
240
+ }
241
+ },
242
+ "sideEffects": "none"
243
+ },
244
+ "update install": {
245
+ "options": {
246
+ "--json": {
247
+ "type": "boolean",
248
+ "description": "Machine-readable JSON (also the default)."
249
+ },
250
+ "--help": {
251
+ "type": "boolean",
252
+ "description": "Read-only help for this command."
253
+ },
254
+ "--agent": {
255
+ "type": "string",
256
+ "enum": [
257
+ "codex",
258
+ "sealseek",
259
+ "workbuddy"
260
+ ],
261
+ "description": "Target host; defaults to codex."
262
+ },
263
+ "--root": {
264
+ "type": "string",
265
+ "description": "Skill parent directory; the CLI appends cloud-hug-style-transfer. Requires --agent on doctor and update."
266
+ },
267
+ "--yes": {
268
+ "type": "boolean",
269
+ "required": true,
270
+ "description": "Authorize package and managed Skill reconciliation."
271
+ }
272
+ },
273
+ "sideEffects": "package-and-skill-update"
274
+ },
275
+ "canvas row": {
276
+ "options": {
277
+ "--json": {
278
+ "type": "boolean",
279
+ "description": "Machine-readable JSON (also the default)."
280
+ },
281
+ "--help": {
282
+ "type": "boolean",
283
+ "description": "Read-only help for this command."
284
+ },
285
+ "--input": {
286
+ "type": "string",
287
+ "required": true,
288
+ "description": "Comparison input JSON file."
289
+ }
290
+ },
291
+ "sideEffects": "none"
292
+ }
293
+ },
294
+ "maintenance": {
295
+ "targetPrecedence": [
296
+ "explicit --root",
297
+ "host skills directory environment",
298
+ "Agent home/default"
299
+ ],
300
+ "codexSkillsEnvironment": "CODEX_SKILLS_DIR",
301
+ "defaultUpdateScope": "registered and current-host managed targets; explicit --agent selects one target",
302
+ "receipt": [
303
+ "package",
304
+ "version",
305
+ "source",
306
+ "entry",
307
+ "owner",
308
+ "packageChanged",
309
+ "skills",
310
+ "verified",
311
+ "backup",
312
+ "warnings",
313
+ "timingsMs"
314
+ ],
315
+ "sameVersion": "reconcile managed Skills and metadata; verified no-op when current",
316
+ "metadata": "preserve customization; remove known unsupported api product with backup; unknown invalid schema requires reconciliation"
73
317
  }
74
318
  }
@@ -0,0 +1,28 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { root, fail } from './runtime.mjs';
4
+ export const manifest=JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
5
+ export function parse(args) {
6
+ const tokens=[...args];
7
+ if(tokens[0]==='--version')tokens[0]='version';
8
+ if(tokens[0]==='help') {tokens.shift();tokens.push('--help');}
9
+ const words=[];while(tokens.length&&!tokens[0].startsWith('-'))words.push(tokens.shift());
10
+ const command=words.join(' '), contract=manifest.commandContracts[command];
11
+ if(!command&&(!tokens.length||tokens.every(x=>['--help','-h','--json'].includes(x))))return {command:'help',options:{}};
12
+ if(!contract)throw fail('INVALID_COMMAND','Unknown command; use --help.');
13
+ const options={};
14
+ while(tokens.length) {
15
+ let flag=tokens.shift();if(flag==='-h')flag='--help';
16
+ const spec=contract.options[flag];
17
+ if(!spec||flag in options)throw fail('INVALID_ARGUMENT',`Unknown or duplicate option ${flag}; use ${command} --help.`);
18
+ const value=spec.type==='boolean'?true:tokens.shift();
19
+ if(value===undefined || (spec.type==='string'&&value.startsWith('-')))throw fail('INVALID_ARGUMENT',`${flag} needs a value.`);
20
+ if(spec.enum&&!spec.enum.includes(value))throw fail('INVALID_AGENT',`Choose ${spec.enum.join(', ')}.`);
21
+ options[flag]=value;
22
+ }
23
+ if(!options['--help']) {
24
+ for(const [flag,spec]of Object.entries(contract.options))if(spec.required&&!options[flag])throw fail(flag==='--yes'?'PERMISSION_REQUIRED':'INVALID_ARGUMENT',`${command} requires ${flag}.`);
25
+ if(['doctor','update check','update install'].includes(command)&&options['--root']&&!options['--agent'])throw fail('INVALID_ARGUMENT','--root requires an explicit --agent for this command.');
26
+ }
27
+ return {command,options,contract};
28
+ }
package/npm/cli.mjs CHANGED
@@ -2,44 +2,38 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { root, pkg, fail } from './runtime.mjs';
5
- import { source, targetPath, status, install, inventory } from './skills.mjs';
6
- import { checkUpdate, installUpdate } from './update.mjs';
5
+ import { source, resolveTarget, status, install, inventory, skillMetadata } from './skills.mjs';
6
+ import { checkUpdate, installUpdate, globalPrefix } from './update.mjs';
7
7
  import { mediaDependency } from './media-dependency.mjs';
8
8
  import { comparisonRow } from './canvas-layout.mjs';
9
- const args=process.argv.slice(2);
10
- const option=name=>{const i=args.indexOf(name);if(i<0)return undefined;if(!args[i+1]||args[i+1].startsWith('--'))throw fail('INVALID_ARGUMENT',`${name} needs a value.`);return args[i+1];};
9
+ import { parse, manifest } from './arguments.mjs';
10
+ const started=performance.now();
11
11
  const out=value=>console.log(JSON.stringify(value,null,2));
12
12
  try {
13
- if(args[0]==='version'||args[0]==='--version') out({ok:true,name:pkg.name,version:pkg.version});
14
- else if(args[0]==='capabilities') out({...JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8')),version:pkg.version});
15
- else if(args[0]==='canvas'&&args[1]==='row') {
16
- const input=option('--input');if(!input)throw fail('INVALID_ARGUMENT','canvas row requires --input JSON_FILE.');
17
- out(comparisonRow(JSON.parse(fs.readFileSync(input,'utf8'))));
18
- }
19
- else if(args[0]==='skill') {
20
- const action=args[1];
21
- if(action==='source')out({ok:true,package:pkg.name,version:pkg.version,source});
22
- else {
23
- const target=targetPath(option('--agent')||'codex',option('--root'));
24
- if(action==='status')out({...status(target),record:undefined});
25
- else if(['install','update'].includes(action))out(install(target));
26
- else throw fail('INVALID_COMMAND','Use skill source/status/install/update.');
27
- }
28
- } else if(args[0]==='doctor') {
29
- const files=inventory(source), expected=JSON.parse(fs.readFileSync(path.join(root,'npm','integrity.json'),'utf8'));
13
+ const {command,options:o,contract}=parse(process.argv.slice(2));
14
+ const identity={package:pkg.name,version:pkg.version,source,entry:{node:process.execPath,cli:path.join(root,'npm/cli.mjs')},owner:globalPrefix()?'global-npm':'source-or-parent'};
15
+ if(command==='help')out({ok:true,...identity,commands:manifest.commandContracts});
16
+ else if(o['--help'])out({ok:true,command,...contract});
17
+ else if(command==='version')out({ok:true,name:pkg.name,...identity});
18
+ else if(command==='capabilities')out({...manifest,...identity});
19
+ else if(command==='canvas row')out(comparisonRow(JSON.parse(fs.readFileSync(o['--input'],'utf8'))));
20
+ else if(command==='skill source')out({ok:true,...identity});
21
+ else if(command.startsWith('skill ')) {
22
+ const resolution=resolveTarget(o['--agent']||'codex',o['--root']);
23
+ const result=command==='skill status'?{ok:true,...status(resolution.target),record:undefined}:install(resolution.target);
24
+ out({...identity,...result,resolution});
25
+ } else if(command==='doctor') {
26
+ const files=inventory(source),expected=JSON.parse(fs.readFileSync(path.join(root,'npm/integrity.json'),'utf8'));
30
27
  const problems=Object.entries(expected).filter(([name,hash])=>files[name]!==hash).map(([name])=>name);
31
- const capabilities=JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
32
- const media=mediaDependency();
33
- const result={ok:Number(process.versions.node.split('.')[0])>=22&&problems.length===0&&media.ok,package:pkg.name,version:pkg.version,node:process.versions.node,source,files:Object.keys(files).length,integrity:{ok:problems.length===0,problems},installationReady:problems.length===0,dependencies:{media},hostCapabilityDependencies:capabilities.requirements.host_capability_skills,generation:{provider:'@petercjl/sealseek-media-cli',state:media.ok?'requires-live-preflight':'paused',runtimeTested:false,requiredFeatures:['image-inspection','multiple-reference-edit','local-reference-input','viewable-output']}};
28
+ const media=mediaDependency();const metadata=skillMetadata(source);
29
+ const result={ok:Number(process.versions.node.split('.')[0])>=22&&!problems.length&&metadata.valid&&media.ok,...identity,node:process.versions.node,files:Object.keys(files).length,integrity:{ok:!problems.length,problems},metadata:{valid:metadata.valid},installationReady:!problems.length&&metadata.valid,dependencies:{media},hostCapabilityDependencies:manifest.requirements.host_capability_skills,generation:{provider:'@petercjl/sealseek-media-cli',state:media.ok?'requires-live-preflight':'paused',runtimeTested:false,requiredFeatures:['image-inspection','multiple-reference-edit','local-reference-input','viewable-output']}};
34
30
  if(!media.ok){result.code=media.code;result.action='pause';result.installPrompt=media.installPrompt;}
35
- if(option('--agent'))result.installation={...status(targetPath(option('--agent'),option('--root'))),record:undefined};
36
- out(result);if(!result.ok)process.exitCode=1;
37
- } else if(args[0]==='update') {
38
- if(!['check','install'].includes(args[1]))throw fail('INVALID_COMMAND','Use update check/install.');
39
- if(args[1]==='install'&&!args.includes('--yes'))throw fail('PERMISSION_REQUIRED','Package update requires --yes.');
40
- const check=await checkUpdate();
41
- if(args[1]==='check'){out(check);if(!check.ok)process.exitCode=1;}
42
- else {if(!check.ok)throw fail(check.code,'Registry unavailable; installed version retained.');out(check.updateAvailable?installUpdate(check):{ok:true,changed:false,version:pkg.version});}
43
- } else if(!args.length||['--help','help','-h'].includes(args[0])) console.log('cloud-hug-style-transfer\nversion | capabilities --json | doctor --json [--agent AGENT]\ncanvas row --input JSON_FILE --json (read-only comparison layout)\nskill source/status/install/update [--agent codex|sealseek|workbuddy] [--root SKILLS_DIRECTORY]\nupdate check | update install --yes\nNode.js 22+. Updates are explicit; business calls never auto-update.');
44
- else throw fail('INVALID_COMMAND','Unknown command; use --help.');
45
- } catch(e){out({ok:false,code:e.code||'COMMAND_FAILED',message:e.message});process.exitCode=1;}
31
+ if(o['--agent']){result.resolution=resolveTarget(o['--agent'],o['--root']);result.installation={...status(result.resolution.target),record:undefined};if(!result.installation.current){result.ok=false;result.code||='SKILL_RECONCILIATION_REQUIRED';}}
32
+ out({...result,timingsMs:{total:performance.now()-started}});if(!result.ok)process.exitCode=1;
33
+ } else if(command.startsWith('update ')) {
34
+ const resolution=o['--agent']?resolveTarget(o['--agent'],o['--root']):undefined;
35
+ const registryStarted=performance.now();const check=await checkUpdate();const registryMs=performance.now()-registryStarted;
36
+ if(command==='update check'){out({...check,...identity,...(resolution?{resolution,installation:{...status(resolution.target),record:undefined}}:{}),timingsMs:{total:performance.now()-started}});if(!check.ok)process.exitCode=1;}
37
+ else {if(!check.ok)throw fail(check.code,'Registry unavailable; installed version retained.');const result=installUpdate(check,{resolution});out({...result,timingsMs:{...result.timingsMs,registry:registryMs,total:performance.now()-started}});}
38
+ }
39
+ } catch(e){out({ok:false,code:e.code||'COMMAND_FAILED',message:e.message,...(e.causeCode?{causeCode:e.causeCode}:{}),...(e.backup?{backup:e.backup}:{}),...(e.details?{details:e.details}:{}),timingsMs:{total:performance.now()-started}});process.exitCode=1;}
@@ -1,9 +1,9 @@
1
1
  {
2
- "SKILL.md": "7d7e8c42e4ace458fc92ba9aa544fd9688047136ae991ef10bd1633264a7cdda",
2
+ "SKILL.md": "98df8d4271e525c687a1f0b3e877345bd42f3ca537515c0f84a9c0d58b17612b",
3
3
  "adapters/codex.json": "bea8d59e6e0dafae7439693b33356825023516a6f52be02a64c976d7ce47ce76",
4
4
  "adapters/sealseek.json": "04ef053762fd4cfc2309573ec87425afad14b2df85a1ea8ef0aa06d98fee1aee",
5
5
  "adapters/workbuddy.json": "cd0a1d8d5b9fcebc5a709b8c7523911f41973adb95f71af2c0f644473a43a6b4",
6
- "agents/openai.yaml": "8afeabba706e83f2f20d616579e8d79b6509fe93427acdc2b650ef6387049983",
6
+ "agents/openai.yaml": "d8ebec24c4e3034f4b45aa052b9468ab35790ec7fd93febcfde512c746d2d1d1",
7
7
  "assets/approved/all-colors-user-finish.webp": "539c77de8ec0dd46a29793befecf813e97ce6ae66f7cbe69486846383b2ae936",
8
8
  "assets/approved/brand-kv-user-finish-20261012.png": "cb09198481e779444696864b265c689f467207bf7de9610f7adc2b850f0be7b4",
9
9
  "assets/approved/detail-final-20261016/01.jpg": "ecb481a422b3eadb0050b8c1080a1781e3fbd50ce4479f92e9f4a8beffd91187",
@@ -56,15 +56,17 @@
56
56
  "assets/rejected/overhead-rest-b.webp": "16e5e6fabdc665868ac244d556f3a8a93db826a34425fdee042e4f7a8ebf64c3",
57
57
  "assets/rejected/prone-pile-a.webp": "d07f38bbb69fc23b30dca4ed27faa3d7ae58e2e81840a1c3d46101911c28a7a2",
58
58
  "capabilities.json": "5d40595bf64402acf700cbaea63065d104547b12e59c275bca59cc9b4658de85",
59
- "references/assets.md": "5675ee6b395caca641e8ce56e200cd4391ad67e7afceb44fc6732f253a126926",
60
- "references/brand-layout.md": "5668190c1e618a903aa1b5e859405fa43a004da2082dc7040e83ef8cfc0aae86",
59
+ "references/assets.md": "98b1a60d70cbc9ceec3e0d11dadbc0dc96344ac6f15cd5289009dfbfd8d71d6e",
60
+ "references/brand-layout.md": "74f2674b7c9987e15a9d2ca7bb18498378eecc2d869d37e9ca1e71bd228ee883",
61
61
  "references/canvas-comparison.md": "df85594c92de3e87daa85ee7cd9cc92df4be9049a5d42c32904542faf329a6c7",
62
62
  "references/cases.md": "9d0d2d3d210e367411d5a291fc78ef6c9c733afd79c9da76451c580fcbcd4d3e",
63
- "references/detail-final-20261016.md": "14acdc2f991b9db1c7d9ada32a35782603ac0aa310dcbe75f9693796a06cad83",
63
+ "references/detail-final-20261016.md": "d9311413420db743466bbecfe98c31b9753eeead905472d69839afec9578c557",
64
64
  "references/kv-layout-case-20261012.md": "845b287cfc57912aabedc48e558ca661cf86bf136304ea841030205a806ef06f",
65
+ "references/maintenance.md": "ccaa22cbe537885c7936d76b9091ef681491fd75d3791f6dbcf803d2337cee8e",
65
66
  "references/model-generation.json": "21daa2c3bf7d2000c7ddf52ab0b980cb7ee95d6c9d345602dc59ecbc128ec147",
66
67
  "references/model.md": "c4da688e22e2ada3f251fe0dc87effcaf5cb6ed6176f9d9c20a9661414bffab5",
67
- "references/page-patterns.md": "3c126c75e9ccd5fc0b8860efcf2a0e27e9d3ba31e5cdeb407a0a683d8861a978",
68
- "references/photography-method.md": "86ed457fc78112c1a2ad9bf9fdb9bcdf92aed71066bac2b3d4255159e3ee5c1c",
69
- "references/products.md": "642795ae16803669b169531240723bf72a388cb6351679a2787e67e181dca9cd"
68
+ "references/page-patterns.md": "bdd6756e303c95e432d0239be27ec5af1faf75abf71594917f2e11467255a1b4",
69
+ "references/photography-method.md": "c0e54598863f3b4a2d87f27d6f18afb75db22ae71ce2aead41f817f35258807e",
70
+ "references/products.md": "642795ae16803669b169531240723bf72a388cb6351679a2787e67e181dca9cd",
71
+ "references/source-page-three-variants.md": "02a265cf11c3ff3bb7796eb289529ce97f6f64f0350a15a7e3275cec24037720"
70
72
  }
@@ -0,0 +1,18 @@
1
+ import { parseDocument } from 'yaml';
2
+ import { fail } from './runtime.mjs';
3
+ const allowed=new Set(['chatgpt','codex','atlas','CHATGPT','CODEX','ATLAS']);
4
+ export function metadataPlan(text) {
5
+ const doc=parseDocument(text,{uniqueKeys:true});
6
+ if(doc.errors.length)throw fail('SKILL_METADATA_INVALID','Invalid agents/openai.yaml; preserve and repair the YAML before updating.');
7
+ const value=doc.toJS();
8
+ if(!value||typeof value!=='object'||Array.isArray(value))throw fail('SKILL_METADATA_INVALID','Skill metadata must be a mapping.');
9
+ if(value.interface!==undefined&&(!value.interface||typeof value.interface!=='object'||Array.isArray(value.interface)||Object.values(value.interface).some(v=>typeof v!=='string')))throw fail('SKILL_METADATA_INVALID','Skill interface values must be strings.');
10
+ if(value.policy!==undefined&&(!value.policy||typeof value.policy!=='object'||Array.isArray(value.policy)))throw fail('SKILL_METADATA_INVALID','Skill policy must be a mapping.');
11
+ const policy=value.policy;
12
+ if(policy?.allow_implicit_invocation!==undefined&&typeof policy.allow_implicit_invocation!=='boolean')throw fail('SKILL_METADATA_INVALID','allow_implicit_invocation must be boolean.');
13
+ const products=policy?.products;
14
+ if(products!==undefined&&(!Array.isArray(products)||products.some(p=>!allowed.has(p)&&p!=='api')))throw fail('SKILL_METADATA_INVALID','Unsupported policy.products; preserve and resolve the custom policy before updating.');
15
+ const changed=products?.includes('api')||false;
16
+ if(changed){const valid=products.filter(p=>p!=='api');if(!valid.length)throw fail('SKILL_METADATA_INVALID','Removing api would leave an empty custom product policy.');doc.setIn(['policy','products'],valid);}
17
+ return {valid:!changed,changed,text:changed?doc.toString():text,warnings:changed?['Removed unsupported legacy api product; custom interface and other policy fields preserved.']:[]};
18
+ }
package/npm/pack.mjs ADDED
@@ -0,0 +1,8 @@
1
+ import path from 'node:path';
2
+ import { pkg, fail } from './runtime.mjs';
3
+ export function packReceipt(text) {
4
+ let value;try{value=JSON.parse(text);}catch{throw fail('UPDATE_BACKUP_FAILED','npm pack did not return valid JSON.');}
5
+ const receipt=Array.isArray(value)&&value.length===1?value[0]:value?.[pkg.name];
6
+ if(!receipt||typeof receipt.filename!=='string'||!receipt.filename.endsWith('.tgz')||receipt.filename.includes('\\')||path.basename(receipt.filename)!==receipt.filename||(receipt.name&&receipt.name!==pkg.name))throw fail('UPDATE_BACKUP_FAILED','Invalid npm pack receipt.');
7
+ return receipt;
8
+ }
package/npm/skills.mjs CHANGED
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import crypto from 'node:crypto';
5
+ import { metadataPlan } from './metadata.mjs';
5
6
  import { stateRoot } from './update.mjs';
6
7
  import { root, pkg, fail } from './runtime.mjs';
7
8
 
@@ -23,14 +24,19 @@ export function inventory(dir) {
23
24
  walk(dir);
24
25
  return result;
25
26
  }
26
- export function targetPath(agent, targetRoot) {
27
+ export function resolveTarget(agent='codex', targetRoot) {
27
28
  if (!['codex', 'sealseek', 'workbuddy'].includes(agent)) throw fail('INVALID_AGENT', 'Choose --agent codex, sealseek or workbuddy.');
28
- let base = targetRoot;
29
- if (!base && agent === 'codex') base = path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
30
- if (!base && agent === 'sealseek') base = process.env.SEALSEEK_SKILLS_DIR || path.join(os.homedir(), '.sealseek', 'workspaces', 'default', 'skills');
31
- if (!base && agent === 'workbuddy') base = process.env.WORKBUDDY_SKILLS_DIR || path.join(os.homedir(), '.workbuddy', 'skills');
32
- if (!base) throw fail('AGENT_PATH_REQUIRED', 'Provide the actual SealSeek skills directory through --root or SEALSEEK_SKILLS_DIR.');
33
- return path.join(path.resolve(base), 'cloud-hug-style-transfer');
29
+ const envName={codex:'CODEX_SKILLS_DIR',sealseek:'SEALSEEK_SKILLS_DIR',workbuddy:'WORKBUDDY_SKILLS_DIR'}[agent];
30
+ const homeBase=agent==='codex' ? path.join(process.env.CODEX_HOME || path.join(os.homedir(),'.codex'),'skills') : agent==='sealseek' ? path.join(os.homedir(),'.sealseek','workspaces','default','skills') : path.join(os.homedir(),'.workbuddy','skills');
31
+ const candidates=[targetRoot&&{source:'--root',root:targetRoot},process.env[envName]&&{source:envName,root:process.env[envName]},{source:agent==='codex'&&process.env.CODEX_HOME?'CODEX_HOME':'agent-default',root:homeBase}].filter(Boolean).map(v=>({...v,root:path.resolve(v.root)}));
32
+ const selected=candidates[0];
33
+ return {agent,target:path.join(selected.root,'cloud-hug-style-transfer'),root:selected.root,resolvedFrom:selected.source,candidates,warnings:[...new Set(candidates.filter(c=>c.root!==selected.root).map(c=>`${selected.source} takes precedence over ${c.source}.`))]};
34
+ }
35
+ export const targetPath=(agent,targetRoot)=>resolveTarget(agent,targetRoot).target;
36
+ export function skillMetadata(target) {
37
+ const file=path.join(target,metadata);
38
+ if(!exists(file))return {valid:false,changed:false,missing:true,warnings:['Skill UI metadata is missing.']};
39
+ return metadataPlan(fs.readFileSync(file,'utf8'));
34
40
  }
35
41
  export function status(target) {
36
42
  const st = exists(target);
@@ -43,18 +49,21 @@ export function status(target) {
43
49
  const files = inventory(target);
44
50
  const edited = Object.entries(record.files).some(([file, hash]) => file !== metadata && files[file] !== hash);
45
51
  const expected = inventory(source);
46
- const current = !edited && Object.entries(expected).every(([file, hash]) => file === metadata || files[file] === hash);
47
- return { installed: true, managed: true, current, edited, target, version: record.version, record };
52
+ const ui=skillMetadata(target);
53
+ const current = record.version === pkg.version && ui.valid && !edited && Object.entries(expected).every(([file, hash]) => file === metadata || files[file] === hash);
54
+ return { installed: true, managed: true, current, edited, target, version: record.version, metadata:{valid:ui.valid,migrationRequired:ui.changed,warnings:ui.warnings}, record };
48
55
  }
49
56
  export function install(target) {
50
- const state = status(target);
57
+ const started=performance.now();
58
+ let state = status(target);
51
59
  if (state.installed && !state.managed) throw fail('SKILL_UNMANAGED', 'An existing user-owned Skill occupies this target; preserve it and choose a new root or explicitly migrate it.');
52
60
  if (state.edited) throw fail('SKILL_EDITED', 'Managed Skill files contain local edits; preserve and reconcile those edits before updating.');
53
61
  const registry = path.join(stateRoot(), 'skill-targets');
54
62
  fs.mkdirSync(registry, { recursive: true });
55
63
  const registration = path.join(registry, crypto.createHash('sha256').update(target).digest('hex') + '.json');
56
64
  if (!exists(registration)) fs.writeFileSync(registration, JSON.stringify({ target }), { flag: 'wx', mode: 0o600 });
57
- if (state.current && state.version === pkg.version) return { ...state, record: undefined, ok: true, changed: false };
65
+ const receipt={package:pkg.name,source,owner:'package-managed',verified:true};
66
+ if (state.current && state.version === pkg.version) return { ...state, record: undefined, ...receipt, ok: true, changed: false, timingsMs:{total:performance.now()-started} };
58
67
  const expected = inventory(source);
59
68
  for (const file of Object.keys(expected)) {
60
69
  if (state.installed && exists(path.join(target, file)) && !(file in state.record.files) && file !== metadata) throw fail('SKILL_UNMANAGED_FILE', 'A new bundled file would overwrite a user-owned file.');
@@ -65,6 +74,11 @@ export function install(target) {
65
74
  try { lockfd = fs.openSync(lock, 'wx', 0o600); } catch (e) { if (e.code === 'EEXIST') throw fail('SKILL_BUSY', 'Another Skill installation holds the lock.'); throw e; }
66
75
  let backup;
67
76
  try {
77
+ state=status(target);
78
+ if(state.installed&&!state.managed)throw fail('SKILL_UNMANAGED','Target ownership changed while waiting for the lock.');
79
+ if(state.edited)throw fail('SKILL_EDITED','Target changed while waiting for the lock.');
80
+ for(const file of Object.keys(expected))if(state.installed&&exists(path.join(target,file))&&!(file in state.record.files)&&file!==metadata)throw fail('SKILL_UNMANAGED_FILE','Target file ownership changed before the lock.');
81
+ const ui=state.installed?skillMetadata(target):null;
68
82
  if (state.installed) {
69
83
  backup = path.join(stateRoot(), 'skill-backups', crypto.randomUUID());
70
84
  fs.mkdirSync(path.dirname(backup), { recursive: true });
@@ -72,7 +86,7 @@ export function install(target) {
72
86
  } else fs.mkdirSync(target);
73
87
  // In-place updates support hosts holding a directory handle open. Backup is recoverable.
74
88
  for (const file of Object.keys(expected)) {
75
- if (file === metadata && exists(path.join(target, file))) continue;
89
+ if (file === metadata && exists(path.join(target, file))) { if(ui.changed)fs.writeFileSync(path.join(target,file),ui.text); continue; }
76
90
  fs.mkdirSync(path.dirname(path.join(target, file)), { recursive: true });
77
91
  fs.copyFileSync(path.join(source, file), path.join(target, file));
78
92
  }
@@ -81,9 +95,10 @@ export function install(target) {
81
95
  }
82
96
  const actual = inventory(target);
83
97
  if (Object.entries(expected).some(([file, hash]) => file !== metadata && actual[file] !== hash)) throw fail('SKILL_VERIFY_FAILED', 'Installed Skill hashes differ from the package.');
98
+ if(!skillMetadata(target).valid)throw fail('SKILL_VERIFY_FAILED','Skill UI metadata validation failed.');
84
99
  fs.writeFileSync(path.join(target, marker), JSON.stringify({ package: pkg.name, version: pkg.version, files: expected }, null, 2));
85
- return { ok: true, installed: true, managed: true, current: true, changed: true, target, version: pkg.version, backup: backup || null };
100
+ return { ...receipt, metadata:{valid:true,migrated:ui?.changed||false}, warnings:ui?.warnings||[], timingsMs:{total:performance.now()-started}, ok: true, installed: true, managed: true, current: true, changed: true, target, version: pkg.version, backup: backup || null };
86
101
  } catch (e) {
87
- throw fail('SKILL_INSTALL_FAILED', `Skill installation failed; ${backup ? 'restore from ' + backup : 'inspect the partial target at ' + target}.`);
102
+ throw Object.assign(fail('SKILL_INSTALL_FAILED', `Skill installation failed; ${backup ? 'restore from ' + backup : 'inspect the partial target at ' + target}.`),{causeCode:e.code,backup:backup||null,target});
88
103
  } finally { fs.closeSync(lockfd); fs.unlinkSync(lock); }
89
104
  }