@xiashe/skill 0.1.5 → 0.1.8
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 +11 -2
- package/bin/xiashe-skill.mjs +608 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,12 +9,17 @@ This package is intentionally separate from the full `@xiashe/cli` product CLI a
|
|
|
9
9
|
`xiashe-skill` helps creators prepare a Skill folder for third-party Skill hubs:
|
|
10
10
|
|
|
11
11
|
- inspect a local Skill project
|
|
12
|
-
- run one Agent-friendly
|
|
12
|
+
- run one high-level Agent-friendly publish handoff command
|
|
13
|
+
- run lower-level registry setup commands that prepare all supported hub handoffs
|
|
14
|
+
- diagnose whether a local Skill has the expected registry disclosure and runtime callback wiring
|
|
15
|
+
- send labeled local verification events so the Dashboard can confirm integration health
|
|
13
16
|
- write an explicit `xiashe.skill.json` registry manifest
|
|
14
17
|
- generate one unified `UPLOAD_HANDOFF.md` that the creator's Agent can use after the user pastes a third-party official upload prompt
|
|
15
18
|
- generate optional runtime analytics snippets
|
|
16
19
|
|
|
17
|
-
It does not
|
|
20
|
+
It does not install background services, run postinstall hooks, read secrets, or override third-party hub upload rules. The creator's Agent is responsible for packaging and uploading according to the target Skill hub's official rules.
|
|
21
|
+
|
|
22
|
+
The user-facing product flow should point creators at the official publish Markdown page and the `xiashe-publish` Skill. Direct CLI commands are implementation details for local Agents and developers.
|
|
18
23
|
|
|
19
24
|
`setup --hub all` still writes platform-specific `upload-<hub>.md` checklists for source attribution, but those are internal Agent checklists. Users should not need to pick files manually. When a platform such as Red Skill provides its own upload prompt, paste that official prompt to the Agent and tell it to follow `.xiashe/UPLOAD_HANDOFF.md`.
|
|
20
25
|
|
|
@@ -23,8 +28,12 @@ It does not create upload packages, copy source directories, install background
|
|
|
23
28
|
```bash
|
|
24
29
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs --help
|
|
25
30
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs inspect .
|
|
31
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs doctor .
|
|
32
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs publish . --code XS-XXXX-XXXX --to xiashe,claude
|
|
26
33
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub all
|
|
27
34
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub red
|
|
35
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs verify . --hub red --dry-run --json
|
|
36
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs verify . --hub red
|
|
28
37
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs attach . --code XS-XXXX-XXXX
|
|
29
38
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs attach . --public-token pub_xxx --skill-id sk_xxx
|
|
30
39
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs prompt . --hub red --source-url https://example.com/my-skill-repo
|
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
9
|
-
const VERSION = '0.1.
|
|
9
|
+
const VERSION = '0.1.7';
|
|
10
10
|
const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
|
|
11
11
|
const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
|
|
12
12
|
const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
|
|
@@ -23,8 +23,120 @@ const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_
|
|
|
23
23
|
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
24
24
|
? 'agentpie.skill.event.v1'
|
|
25
25
|
: 'xiashe.skill.event.v1');
|
|
26
|
-
const SUPPORTED_HUBS = ['red', 'clawhub', 'skillhub', 'claude', 'dify', 'coze', 'generic'];
|
|
27
|
-
const DEFAULT_SETUP_HUBS = ['red', 'clawhub', 'skillhub', 'claude', 'dify', 'coze', 'generic'];
|
|
26
|
+
const SUPPORTED_HUBS = ['xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
|
|
27
|
+
const DEFAULT_SETUP_HUBS = ['xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
|
|
28
|
+
const PLATFORM_REVIEW_PROFILES = {
|
|
29
|
+
xiashe: {
|
|
30
|
+
label: 'XiaShe Store',
|
|
31
|
+
packageMode: 'full_skill_package',
|
|
32
|
+
manifestPolicy: 'upload_allowed',
|
|
33
|
+
disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
|
|
34
|
+
runtimeDataPolicy: 'runtime_capable',
|
|
35
|
+
defaultDataSource: 'runtime',
|
|
36
|
+
allowedFilePolicy: 'Upload the complete reviewed Skill package, including the local registry manifest.',
|
|
37
|
+
forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
|
|
38
|
+
},
|
|
39
|
+
red: {
|
|
40
|
+
label: 'Red Skill',
|
|
41
|
+
packageMode: 'markdown_or_text_only',
|
|
42
|
+
manifestPolicy: 'local_only',
|
|
43
|
+
disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
|
|
44
|
+
runtimeDataPolicy: 'attribution_only_by_default',
|
|
45
|
+
defaultDataSource: 'attribution',
|
|
46
|
+
allowedFilePolicy: 'Upload only Markdown/TXT or fields accepted by the official Red Skill flow.',
|
|
47
|
+
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets', 'internal handoff/checklists']
|
|
48
|
+
},
|
|
49
|
+
clawhub: {
|
|
50
|
+
label: 'ClawHub',
|
|
51
|
+
packageMode: 'markdown_or_text_only',
|
|
52
|
+
manifestPolicy: 'local_only',
|
|
53
|
+
disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
|
|
54
|
+
runtimeDataPolicy: 'attribution_only_by_default',
|
|
55
|
+
defaultDataSource: 'attribution',
|
|
56
|
+
allowedFilePolicy: 'Follow ClawHub official file rules. Treat XiaShe registry files as local-only unless ClawHub explicitly accepts them.',
|
|
57
|
+
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets']
|
|
58
|
+
},
|
|
59
|
+
skillhub: {
|
|
60
|
+
label: 'SkillHub',
|
|
61
|
+
packageMode: 'markdown_or_text_only',
|
|
62
|
+
manifestPolicy: 'local_only',
|
|
63
|
+
disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
|
|
64
|
+
runtimeDataPolicy: 'attribution_only_by_default',
|
|
65
|
+
defaultDataSource: 'attribution',
|
|
66
|
+
allowedFilePolicy: 'Upload only files accepted by SkillHub. For Markdown/TXT-only flows, copy disclosure text into an allowed field.',
|
|
67
|
+
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets']
|
|
68
|
+
},
|
|
69
|
+
claude: {
|
|
70
|
+
label: 'Claude Code',
|
|
71
|
+
packageMode: 'local_agent_install',
|
|
72
|
+
manifestPolicy: 'platform_dependent',
|
|
73
|
+
disclosurePlacement: ['SKILL.md', 'README', 'agent install note'],
|
|
74
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
75
|
+
defaultDataSource: 'runtime',
|
|
76
|
+
allowedFilePolicy: 'Install the Skill in Claude-compatible local Skill format. Add MCP/HTTP ack only when the runtime supports it.',
|
|
77
|
+
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
78
|
+
},
|
|
79
|
+
codex: {
|
|
80
|
+
label: 'Codex',
|
|
81
|
+
packageMode: 'local_agent_install',
|
|
82
|
+
manifestPolicy: 'platform_dependent',
|
|
83
|
+
disclosurePlacement: ['SKILL.md', 'README', 'agent install note'],
|
|
84
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
85
|
+
defaultDataSource: 'runtime',
|
|
86
|
+
allowedFilePolicy: 'Install as a local Agent Skill and wire MCP/HTTP ack where supported.',
|
|
87
|
+
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
88
|
+
},
|
|
89
|
+
cursor: {
|
|
90
|
+
label: 'Cursor',
|
|
91
|
+
packageMode: 'local_agent_install',
|
|
92
|
+
manifestPolicy: 'platform_dependent',
|
|
93
|
+
disclosurePlacement: ['rules/skill docs', 'README', 'agent install note'],
|
|
94
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
95
|
+
defaultDataSource: 'runtime',
|
|
96
|
+
allowedFilePolicy: 'Follow Cursor rule/agent capability conventions and keep callbacks explicit.',
|
|
97
|
+
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
98
|
+
},
|
|
99
|
+
workbuddy: {
|
|
100
|
+
label: 'WorkBuddy',
|
|
101
|
+
packageMode: 'local_agent_install',
|
|
102
|
+
manifestPolicy: 'platform_dependent',
|
|
103
|
+
disclosurePlacement: ['SKILL.md', 'README', 'agent install note'],
|
|
104
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
105
|
+
defaultDataSource: 'runtime',
|
|
106
|
+
allowedFilePolicy: 'Follow WorkBuddy agent capability rules and wire ack only through approved runtime boundaries.',
|
|
107
|
+
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
108
|
+
},
|
|
109
|
+
coze: {
|
|
110
|
+
label: 'Coze',
|
|
111
|
+
packageMode: 'static_prompt_or_api_tool',
|
|
112
|
+
manifestPolicy: 'local_only',
|
|
113
|
+
disclosurePlacement: ['workflow description', 'tool description', 'README'],
|
|
114
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
115
|
+
defaultDataSource: 'attribution',
|
|
116
|
+
allowedFilePolicy: 'Static prompt mode is attribution-only. Use an HTTP/API Tool step for real runtime analytics.',
|
|
117
|
+
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, 'public tokens in static prompts', 'signing secrets']
|
|
118
|
+
},
|
|
119
|
+
dify: {
|
|
120
|
+
label: 'Dify',
|
|
121
|
+
packageMode: 'static_prompt_or_api_tool',
|
|
122
|
+
manifestPolicy: 'local_only',
|
|
123
|
+
disclosurePlacement: ['workflow description', 'tool description', 'README'],
|
|
124
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
125
|
+
defaultDataSource: 'attribution',
|
|
126
|
+
allowedFilePolicy: 'Static prompt mode is attribution-only. Use an HTTP/API Tool node for real runtime analytics.',
|
|
127
|
+
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, 'public tokens in static prompts', 'signing secrets']
|
|
128
|
+
},
|
|
129
|
+
generic: {
|
|
130
|
+
label: 'Generic Skill Hub',
|
|
131
|
+
packageMode: 'external_platform_rules',
|
|
132
|
+
manifestPolicy: 'platform_dependent',
|
|
133
|
+
disclosurePlacement: ['README', 'SKILL.md', 'platform description field'],
|
|
134
|
+
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
135
|
+
defaultDataSource: 'attribution',
|
|
136
|
+
allowedFilePolicy: 'Follow the target platform official rules first; merge only safe XiaShe disclosure and callbacks.',
|
|
137
|
+
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
138
|
+
}
|
|
139
|
+
};
|
|
28
140
|
const DEFAULT_IGNORE = new Set([
|
|
29
141
|
'.git',
|
|
30
142
|
'.xiashe',
|
|
@@ -47,11 +159,14 @@ function usage() {
|
|
|
47
159
|
|
|
48
160
|
Usage:
|
|
49
161
|
${COMMAND_NAME} inspect [skill-dir] [--json]
|
|
50
|
-
${COMMAND_NAME}
|
|
51
|
-
${COMMAND_NAME} setup [skill-dir] --
|
|
162
|
+
${COMMAND_NAME} publish [skill-dir] --code <dynamic-code> [--to xiashe,red,claude,codex,cursor,workbuddy,dify,coze]
|
|
163
|
+
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|xiashe|red|clawhub|skillhub|claude|codex|cursor|workbuddy|dify|coze|generic]
|
|
164
|
+
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|xiashe|red|clawhub|skillhub|claude|codex|cursor|workbuddy|dify|coze|generic]
|
|
165
|
+
${COMMAND_NAME} doctor [skill-dir] [--json]
|
|
166
|
+
${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
|
|
52
167
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
53
168
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
54
|
-
${COMMAND_NAME} prompt [skill-dir] --hub <red|clawhub|skillhub|claude|dify|coze|generic> [--source-url <url>] [--out <file>]
|
|
169
|
+
${COMMAND_NAME} prompt [skill-dir] --hub <xiashe|red|clawhub|skillhub|claude|codex|cursor|workbuddy|dify|coze|generic> [--source-url <url>] [--out <file>]
|
|
55
170
|
${COMMAND_NAME} snippet [skill-dir] [--target js|curl] [--out <file>]
|
|
56
171
|
${COMMAND_NAME} track [skill-dir] --event <event> [--hub <hub>] [--installation-id <id>] [--invocation-id <id>]
|
|
57
172
|
|
|
@@ -64,6 +179,7 @@ Options:
|
|
|
64
179
|
--name <name> Skill display name override.
|
|
65
180
|
--description <text> Skill description override.
|
|
66
181
|
--hub <hub> Target Skill hub prompt style. setup defaults to all supported hubs.
|
|
182
|
+
--to <hub[,hub]> High-level publish targets. Alias for --hub in publish mode.
|
|
67
183
|
--event <event> Runtime event to submit for testing.
|
|
68
184
|
--installation-id <id> Stable anonymous install id for runtime analytics.
|
|
69
185
|
--invocation-id <id> Unique invocation id for one skill call.
|
|
@@ -164,11 +280,23 @@ function normalizeHub(value) {
|
|
|
164
280
|
if (hub === 'skill-hub') {
|
|
165
281
|
return 'skillhub';
|
|
166
282
|
}
|
|
283
|
+
if (hub === 'xiashechat' || hub === 'xia-she' || hub === 'xiashe-store') {
|
|
284
|
+
return 'xiashe';
|
|
285
|
+
}
|
|
286
|
+
if (hub === 'claudecode' || hub === 'claude-code') {
|
|
287
|
+
return 'claude';
|
|
288
|
+
}
|
|
289
|
+
if (hub === 'openaicodex' || hub === 'openai-codex') {
|
|
290
|
+
return 'codex';
|
|
291
|
+
}
|
|
292
|
+
if (hub === 'work-buddy') {
|
|
293
|
+
return 'workbuddy';
|
|
294
|
+
}
|
|
167
295
|
return hub;
|
|
168
296
|
}
|
|
169
297
|
|
|
170
298
|
function setupHubsFromFlags(flags) {
|
|
171
|
-
const raw = normalizeText(flags.hub || 'all', 240).toLowerCase();
|
|
299
|
+
const raw = normalizeText(flags.hub || flags.to || 'all', 240).toLowerCase();
|
|
172
300
|
if (!raw || raw === 'all' || raw === '*') {
|
|
173
301
|
return [...DEFAULT_SETUP_HUBS];
|
|
174
302
|
}
|
|
@@ -184,6 +312,103 @@ function setupHubsFromFlags(flags) {
|
|
|
184
312
|
return unique.length > 0 ? unique : [...DEFAULT_SETUP_HUBS];
|
|
185
313
|
}
|
|
186
314
|
|
|
315
|
+
function reviewProfileForHub(hub) {
|
|
316
|
+
const normalized = normalizeHub(hub) || 'generic';
|
|
317
|
+
return {
|
|
318
|
+
hub: normalized,
|
|
319
|
+
...(PLATFORM_REVIEW_PROFILES[normalized] || PLATFORM_REVIEW_PROFILES.generic)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isPublicDocFile(filePath) {
|
|
324
|
+
return /(^|\/)(SKILL|README|LICENSE|CHANGELOG|CONTRIBUTING)(\.[a-z0-9]+)?$/i.test(filePath) ||
|
|
325
|
+
/\.(md|mdx|txt)$/i.test(filePath);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function isLikelyEntrypointFile(filePath) {
|
|
329
|
+
return /(^|\/)(package\.json|requirements\.txt|pyproject\.toml|deno\.json|mcp\.json|manifest\.json|skill\.json|index\.(js|mjs|cjs|ts|tsx|py)|main\.(js|mjs|cjs|ts|tsx|py)|server\.(js|mjs|cjs|ts|tsx|py))$/i.test(filePath);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function packagePlanForHub(inspected, hub) {
|
|
333
|
+
const profile = reviewProfileForHub(hub);
|
|
334
|
+
const localOnly = [];
|
|
335
|
+
const candidateUploadFiles = [];
|
|
336
|
+
const forbiddenPatterns = [
|
|
337
|
+
'.env*',
|
|
338
|
+
`${WORK_DIR}/`,
|
|
339
|
+
'.agentpie/',
|
|
340
|
+
'node_modules/',
|
|
341
|
+
'dist/',
|
|
342
|
+
'build/',
|
|
343
|
+
'coverage/',
|
|
344
|
+
'*.pem',
|
|
345
|
+
'*.key',
|
|
346
|
+
'*secret*',
|
|
347
|
+
'*token*'
|
|
348
|
+
];
|
|
349
|
+
for (const file of inspected.package.files) {
|
|
350
|
+
if (file.path.startsWith(`${WORK_DIR}/`) || file.path.startsWith('.agentpie/')) {
|
|
351
|
+
localOnly.push(file.path);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (profile.packageMode === 'full_skill_package') {
|
|
355
|
+
candidateUploadFiles.push(file.path);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (profile.packageMode === 'markdown_or_text_only') {
|
|
359
|
+
if (isPublicDocFile(file.path)) {
|
|
360
|
+
candidateUploadFiles.push(file.path);
|
|
361
|
+
}
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (profile.packageMode === 'local_agent_install') {
|
|
365
|
+
if (isPublicDocFile(file.path) || isLikelyEntrypointFile(file.path)) {
|
|
366
|
+
candidateUploadFiles.push(file.path);
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (profile.packageMode === 'static_prompt_or_api_tool') {
|
|
371
|
+
if (isPublicDocFile(file.path) || /openapi|swagger|tool|workflow|plugin/i.test(file.path)) {
|
|
372
|
+
candidateUploadFiles.push(file.path);
|
|
373
|
+
}
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (isPublicDocFile(file.path) || isLikelyEntrypointFile(file.path)) {
|
|
377
|
+
candidateUploadFiles.push(file.path);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
hub: profile.hub,
|
|
382
|
+
label: profile.label,
|
|
383
|
+
reviewProfile: profile,
|
|
384
|
+
dataSourcePolicy: {
|
|
385
|
+
runtime: 'Only real Skill execution callbacks, MCP/HTTP/API Tool calls, webhooks, or external Agent ack.',
|
|
386
|
+
attribution: 'Publish/upload workflow, public disclosure, and tracking-link clicks.',
|
|
387
|
+
reported: 'Manual import or public platform numbers such as downloads, installs, favorites, or displayed usage.'
|
|
388
|
+
},
|
|
389
|
+
candidateUploadFiles: Array.from(new Set(candidateUploadFiles)).sort(),
|
|
390
|
+
localOnlyFiles: Array.from(new Set([
|
|
391
|
+
...localOnly,
|
|
392
|
+
`${WORK_DIR}/${MANIFEST_FILE}`,
|
|
393
|
+
`${WORK_DIR}/UPLOAD_HANDOFF.md`,
|
|
394
|
+
`${WORK_DIR}/runtime-events.js`,
|
|
395
|
+
`${WORK_DIR}/platform-profiles.json`
|
|
396
|
+
])).sort(),
|
|
397
|
+
disclosureTargets: profile.disclosurePlacement,
|
|
398
|
+
forbiddenPatterns,
|
|
399
|
+
requiresUserConfirmation: true,
|
|
400
|
+
notes: [
|
|
401
|
+
profile.allowedFilePolicy,
|
|
402
|
+
profile.manifestPolicy === 'local_only'
|
|
403
|
+
? `Keep ${WORK_DIR}/${MANIFEST_FILE} local. Do not upload it unless the target platform explicitly accepts registry JSON and the user confirms.`
|
|
404
|
+
: 'Confirm platform file rules before uploading auxiliary registry metadata.',
|
|
405
|
+
profile.runtimeDataPolicy === 'attribution_only_by_default'
|
|
406
|
+
? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
|
|
407
|
+
: 'Runtime analytics require an explicit callback at the real invocation boundary.'
|
|
408
|
+
]
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
187
412
|
function canonicalSignaturePayload(payload) {
|
|
188
413
|
return [
|
|
189
414
|
payload.schemaVersion || EVENT_SCHEMA_VERSION,
|
|
@@ -388,20 +613,31 @@ async function writeManifest(root, flags) {
|
|
|
388
613
|
]
|
|
389
614
|
}
|
|
390
615
|
},
|
|
391
|
-
platforms: Object.fromEntries(SUPPORTED_HUBS.map((hub) =>
|
|
392
|
-
hub
|
|
393
|
-
|
|
616
|
+
platforms: Object.fromEntries(SUPPORTED_HUBS.map((hub) => {
|
|
617
|
+
const reviewProfile = reviewProfileForHub(hub);
|
|
618
|
+
return [
|
|
394
619
|
hub,
|
|
395
|
-
|
|
396
|
-
uploadPromptFile: `${WORK_DIR}/upload-${safeSkillKey(hub)}.md`,
|
|
397
|
-
eventDefaults: {
|
|
398
|
-
schemaVersion: EVENT_SCHEMA_VERSION,
|
|
620
|
+
{
|
|
399
621
|
hub,
|
|
400
622
|
sourceSurface: hub,
|
|
401
|
-
|
|
623
|
+
uploadPromptFile: `${WORK_DIR}/upload-${safeSkillKey(hub)}.md`,
|
|
624
|
+
packagePlanFile: `${WORK_DIR}/package-${safeSkillKey(hub)}.json`,
|
|
625
|
+
reviewProfile,
|
|
626
|
+
dataSourcePolicy: {
|
|
627
|
+
default: reviewProfile.defaultDataSource,
|
|
628
|
+
runtime: 'Only real Skill execution callbacks, MCP/HTTP/API Tool calls, webhooks, or external Agent ack.',
|
|
629
|
+
attribution: 'Publish/upload workflow, public disclosure, and tracking-link clicks.',
|
|
630
|
+
reported: 'Manual import or public platform numbers such as downloads, installs, favorites, or displayed usage.'
|
|
631
|
+
},
|
|
632
|
+
eventDefaults: {
|
|
633
|
+
schemaVersion: EVENT_SCHEMA_VERSION,
|
|
634
|
+
hub,
|
|
635
|
+
sourceSurface: hub,
|
|
636
|
+
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey)
|
|
637
|
+
}
|
|
402
638
|
}
|
|
403
|
-
|
|
404
|
-
|
|
639
|
+
];
|
|
640
|
+
})),
|
|
405
641
|
sourceFingerprint: {
|
|
406
642
|
sha256: inspected.package.sha256,
|
|
407
643
|
fileCount: inspected.package.fileCount,
|
|
@@ -420,6 +656,9 @@ async function writeManifest(root, flags) {
|
|
|
420
656
|
}
|
|
421
657
|
|
|
422
658
|
function hubInstruction(hub) {
|
|
659
|
+
if (hub === 'xiashe') {
|
|
660
|
+
return '虾舍商店发布是完整 Skill 包和 registry manifest 的主流程。请创建虾舍商店草稿,并保留公开披露、归因和 runtime analytics 回传能力。';
|
|
661
|
+
}
|
|
423
662
|
if (hub === 'red') {
|
|
424
663
|
return 'Red Skill 的官方上传 prompt / 页面流程是主流程。请先从小红书创作服务平台获取或使用用户提供的 Red Skill 上传 prompt,再把下面的虾舍 registry 要求作为附加说明合并进去。';
|
|
425
664
|
}
|
|
@@ -432,6 +671,15 @@ function hubInstruction(hub) {
|
|
|
432
671
|
if (hub === 'claude') {
|
|
433
672
|
return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
|
|
434
673
|
}
|
|
674
|
+
if (hub === 'codex') {
|
|
675
|
+
return 'Codex 的本地 Skill 或 Agent 能力安装流程是主流程。请把 registry disclosure 放进公开说明,并优先通过 MCP/HTTP ack 回传真实调用。';
|
|
676
|
+
}
|
|
677
|
+
if (hub === 'cursor') {
|
|
678
|
+
return 'Cursor 的本地 Agent 能力、规则或工具安装流程是主流程。请把 registry disclosure 放进公开说明,并优先通过 HTTP/MCP callback 回传真实调用。';
|
|
679
|
+
}
|
|
680
|
+
if (hub === 'workbuddy') {
|
|
681
|
+
return 'WorkBuddy 的 Agent 能力安装和发布流程是主流程。请按 WorkBuddy 当前规则放置 SKILL.md/工具说明,并通过 HTTP/MCP callback 回传真实调用。';
|
|
682
|
+
}
|
|
435
683
|
if (hub === 'dify') {
|
|
436
684
|
return 'Dify Plugin/Tool 的官方打包和发布流程是主流程。请先按 Dify 插件规范处理 manifest 和工具定义,再把下面的 registry 披露文本放入允许的 README/说明字段。';
|
|
437
685
|
}
|
|
@@ -442,6 +690,9 @@ function hubInstruction(hub) {
|
|
|
442
690
|
}
|
|
443
691
|
|
|
444
692
|
function platformPromptPlaceholder(hub) {
|
|
693
|
+
if (hub === 'xiashe') {
|
|
694
|
+
return '如果用户要发布到虾舍商店,请优先创建虾舍商店草稿;不要把第三方平台限制套用到虾舍完整 Skill 包。';
|
|
695
|
+
}
|
|
445
696
|
if (hub === 'red') {
|
|
446
697
|
return '如果用户还没有提供 Red Skill 官方上传 prompt,请先请用户从小红书创作服务平台复制该 prompt;不要用虾舍 prompt 替代 Red Skill 官方 prompt。';
|
|
447
698
|
}
|
|
@@ -454,6 +705,15 @@ function platformPromptPlaceholder(hub) {
|
|
|
454
705
|
if (hub === 'claude') {
|
|
455
706
|
return '如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用虾舍 prompt 替代 Claude 官方流程。';
|
|
456
707
|
}
|
|
708
|
+
if (hub === 'codex') {
|
|
709
|
+
return '如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Codex 官方流程。';
|
|
710
|
+
}
|
|
711
|
+
if (hub === 'cursor') {
|
|
712
|
+
return '如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Cursor 官方流程。';
|
|
713
|
+
}
|
|
714
|
+
if (hub === 'workbuddy') {
|
|
715
|
+
return '如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用虾舍 prompt 替代 WorkBuddy 官方流程。';
|
|
716
|
+
}
|
|
457
717
|
if (hub === 'dify') {
|
|
458
718
|
return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
|
|
459
719
|
}
|
|
@@ -465,19 +725,29 @@ function platformPromptPlaceholder(hub) {
|
|
|
465
725
|
|
|
466
726
|
function uploadCompatibilityLines(hub, manifestFile) {
|
|
467
727
|
const localManifest = `${WORK_DIR}/${manifestFile}`;
|
|
728
|
+
if (hub === 'xiashe') {
|
|
729
|
+
return [
|
|
730
|
+
'虾舍商店兼容性要求:',
|
|
731
|
+
`- 虾舍商店可以接收完整 Skill 包和 ${localManifest} registry manifest。`,
|
|
732
|
+
'- 保留公开披露文本、归因链接和 runtime event callback;不要删除 registry manifest。',
|
|
733
|
+
'- 提交草稿前仍需排除 .env、密钥、node_modules、dist/build 和无关本地文件。'
|
|
734
|
+
];
|
|
735
|
+
}
|
|
468
736
|
if (hub === 'red') {
|
|
469
737
|
return [
|
|
470
738
|
'Red Skill 兼容性要求:',
|
|
471
739
|
'- Red Skill 如果只接受 Markdown/TXT,请不要把 JSON manifest 当作源码文件上传。',
|
|
472
740
|
`- ${localManifest} 保留在用户本地用于虾舍 registry claim 和 analytics;只有在 Red Skill 官方流程明确允许附加 JSON 文件时才上传。`,
|
|
473
|
-
'- 如需在 Red Skill 页面披露 registry,请把下面的“公开披露文本”写入 Skill 介绍、README 或平台允许的 Markdown/TXT 字段。'
|
|
741
|
+
'- 如需在 Red Skill 页面披露 registry,请把下面的“公开披露文本”写入 Skill 介绍、README 或平台允许的 Markdown/TXT 字段。',
|
|
742
|
+
'- 不要把 public token、signing secret、runtime-events.js 或 .xiashe 内部 handoff/checklist 文件发布到 Markdown/TXT-only 平台。'
|
|
474
743
|
];
|
|
475
744
|
}
|
|
476
745
|
return [
|
|
477
746
|
'第三方平台兼容性要求:',
|
|
478
747
|
`- 如果目标平台不接受 ${localManifest} 这类 JSON manifest,请不要强行上传该文件。`,
|
|
479
748
|
'- 保留本地 registry manifest,并把“公开披露文本”写入平台允许的说明字段、README、SKILL.md 或 Markdown/TXT 附件。',
|
|
480
|
-
'- 如果平台支持额外 manifest / metadata 文件,可以在提交前向用户确认是否一并上传。'
|
|
749
|
+
'- 如果平台支持额外 manifest / metadata 文件,可以在提交前向用户确认是否一并上传。',
|
|
750
|
+
'- 不要把 public token、signing secret、runtime-events.js 或内部 handoff/checklist 文件发布到不支持运行时回调的平台。'
|
|
481
751
|
];
|
|
482
752
|
}
|
|
483
753
|
|
|
@@ -502,7 +772,8 @@ function publicDisclosure(inspected, registryUrl) {
|
|
|
502
772
|
function skillMdRegistryBlock(inspected, hub) {
|
|
503
773
|
const registry = inspected.registry || {};
|
|
504
774
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
505
|
-
const
|
|
775
|
+
const publicSkillId = registry.skillId || registry.publicSkillId || inspected.registry?.skillId || '<public registry skill id>';
|
|
776
|
+
const publicTokenHint = registry.publicToken ? `${String(registry.publicToken).slice(0, 4)}...${String(registry.publicToken).slice(-4)}` : '<stored in local registry manifest>';
|
|
506
777
|
const marker = `${REGISTRY_PROVIDER}-registry`;
|
|
507
778
|
return [
|
|
508
779
|
`<!-- ${marker}:start -->`,
|
|
@@ -514,12 +785,14 @@ function skillMdRegistryBlock(inspected, hub) {
|
|
|
514
785
|
`- Skill key: ${inspected.skillKey}`,
|
|
515
786
|
`- Target hub: ${hub}`,
|
|
516
787
|
`- Registry event endpoint: ${registryUrl}`,
|
|
517
|
-
`- Public
|
|
788
|
+
`- Public Skill ID: ${publicSkillId}`,
|
|
789
|
+
`- Public token location: local ${WORK_DIR}/${MANIFEST_FILE} only (${publicTokenHint})`,
|
|
518
790
|
`- Event schema version: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
519
791
|
`- Source fingerprint: ${inspected.package.sha256}`,
|
|
520
792
|
'',
|
|
521
793
|
'Analytics boundary:',
|
|
522
|
-
'- The public token can only submit allowed aggregate analytics events. It cannot read creator data or administer the account.',
|
|
794
|
+
'- The local public token can only submit allowed aggregate analytics events. It cannot read creator data or administer the account.',
|
|
795
|
+
'- Do not publish the local public token in Markdown-only hubs. Keep it in the local manifest or runtime configuration.',
|
|
523
796
|
'- Events should include only public metadata: hub, sourceSurface, campaign, scenario, anonymous installationId, invocationId, and status.',
|
|
524
797
|
'- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
|
|
525
798
|
'- The creator can rotate/revoke the token or disable analytics from the dashboard.',
|
|
@@ -564,6 +837,8 @@ async function readPlatformPrompt(flags) {
|
|
|
564
837
|
|
|
565
838
|
async function uploadPrompt(inspected, flags) {
|
|
566
839
|
const hub = normalizeHub(flags.hub || 'generic') || 'generic';
|
|
840
|
+
const reviewProfile = reviewProfileForHub(hub);
|
|
841
|
+
const packagePlan = packagePlanForHub(inspected, hub);
|
|
567
842
|
const sourceUrl = normalizeText(flags['source-url'] || flags['package-url'], 1000);
|
|
568
843
|
const platformCommand = normalizeText(flags['platform-command'], 1000);
|
|
569
844
|
const platformPrompt = await readPlatformPrompt(flags);
|
|
@@ -623,6 +898,31 @@ async function uploadPrompt(inspected, flags) {
|
|
|
623
898
|
'- 最终上传文件、目录结构、压缩包、表单字段和上线版本,必须由你按照目标 Skill 平台的官方要求处理。',
|
|
624
899
|
'- 如果目标平台要求重新整理源码、生成 markdown、压缩包或填写表单,请你在用户授权范围内完成,并在提交前向用户展示将上传的文件和字段。',
|
|
625
900
|
'',
|
|
901
|
+
'平台审核 profile:',
|
|
902
|
+
'```json',
|
|
903
|
+
JSON.stringify(reviewProfile, null, 2),
|
|
904
|
+
'```',
|
|
905
|
+
'',
|
|
906
|
+
'平台包清单 plan:',
|
|
907
|
+
'```json',
|
|
908
|
+
JSON.stringify({
|
|
909
|
+
hub: packagePlan.hub,
|
|
910
|
+
packageMode: packagePlan.reviewProfile.packageMode,
|
|
911
|
+
candidateUploadFiles: packagePlan.candidateUploadFiles,
|
|
912
|
+
localOnlyFiles: packagePlan.localOnlyFiles,
|
|
913
|
+
disclosureTargets: packagePlan.disclosureTargets,
|
|
914
|
+
forbiddenPatterns: packagePlan.forbiddenPatterns,
|
|
915
|
+
dataSourcePolicy: packagePlan.dataSourcePolicy,
|
|
916
|
+
notes: packagePlan.notes
|
|
917
|
+
}, null, 2),
|
|
918
|
+
'```',
|
|
919
|
+
'',
|
|
920
|
+
'数据口径必须分开:',
|
|
921
|
+
'- runtime:只有真实 Skill 执行 callback、MCP/HTTP/API Tool、webhook 或外部 Agent ack 才能计入。',
|
|
922
|
+
'- attribution:发布、上传、归因链接点击、公开披露访问,只代表来源漏斗。',
|
|
923
|
+
'- reported:平台公开数字或手动导入,例如收藏、下载、安装、静态平台显示使用数。',
|
|
924
|
+
'- 如果无法确认 runtime callback 已经接在真实调用边界,就把该平台标记为 attribution 或 reported,不要伪造成 runtime。',
|
|
925
|
+
'',
|
|
626
926
|
...compatibilityLines,
|
|
627
927
|
'',
|
|
628
928
|
...disclosureLines,
|
|
@@ -637,8 +937,10 @@ async function uploadPrompt(inspected, flags) {
|
|
|
637
937
|
'- 不要新增 postinstall hook、后台常驻进程、隐藏脚本或混淆逻辑。',
|
|
638
938
|
`- 如果平台要求安全说明,请明确说明这是用户显式发布的 Skill 项目,${MANIFEST_FILE} 只包含公开 registry 归因和可关闭的 analytics 声明。`,
|
|
639
939
|
`- 如果目标平台支持工具调用、MCP server、webhook 或 Skill runtime HTTP 请求,请在 Skill 被调用时向 ${PRODUCT_NAME} registry 发送聚合事件:skill_invoked / skill_completed / skill_failed。`,
|
|
940
|
+
`- 如果 runtime 没有向 ${registryUrl} 发送事件,${PRODUCT_NAME} 无法看到真实调用量;第三方平台内部显示“已使用”不会自动同步到 ${PRODUCT_NAME}。`,
|
|
640
941
|
'- 事件里只放公开元数据、聚合状态和简短场景标签,不要发送用户输入原文、私密业务内容、账号凭据或文件内容。',
|
|
641
942
|
'- installationId 必须是稳定的匿名实例 ID,用于统计唯一安装实例;不要使用邮箱、手机号、真实用户名或设备序列号。',
|
|
943
|
+
'- 发布前请至少发送一次测试事件,并确认 Dashboard 能看到对应 hub/sourceSurface;否则把这个平台标记为“仅上传/手动上报”。',
|
|
642
944
|
'',
|
|
643
945
|
`发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据和聚合事件,不发送用户内容、私密输入或账号凭据。`,
|
|
644
946
|
'',
|
|
@@ -678,6 +980,9 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
678
980
|
const promptIndex = args.promptResults
|
|
679
981
|
.map((item) => `- ${item.hub}: ${path.relative(root, item.promptPath)}`)
|
|
680
982
|
.join('\n');
|
|
983
|
+
const profileIndex = args.packagePlans
|
|
984
|
+
.map((item) => `- ${item.hub}: ${path.relative(root, item.planPath)} (${item.plan.reviewProfile.packageMode}, ${item.plan.reviewProfile.defaultDataSource})`)
|
|
985
|
+
.join('\n');
|
|
681
986
|
const registry = args.inspected.registry || {};
|
|
682
987
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
683
988
|
const content = [
|
|
@@ -706,6 +1011,14 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
706
1011
|
'',
|
|
707
1012
|
promptIndex,
|
|
708
1013
|
'',
|
|
1014
|
+
'## Platform review profiles',
|
|
1015
|
+
'',
|
|
1016
|
+
`Structured profile index: ${relativeOutDir}/platform-profiles.json`,
|
|
1017
|
+
'',
|
|
1018
|
+
profileIndex,
|
|
1019
|
+
'',
|
|
1020
|
+
'Use each package-<hub>.json as the upload allowlist/review checklist. Files listed as localOnlyFiles must not be uploaded to restrictive platforms unless the platform explicitly accepts them and the user confirms.',
|
|
1021
|
+
'',
|
|
709
1022
|
'## Official prompt merge rule',
|
|
710
1023
|
'',
|
|
711
1024
|
'If the user gives a Red Skill / ClawHub / SkillHub / Claude / Dify / Coze official prompt, do not replace it. Append these registry requirements as constraints, and follow the official prompt for packaging, file format, and upload steps.',
|
|
@@ -719,6 +1032,8 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
719
1032
|
'',
|
|
720
1033
|
'Only add runtime event callbacks when the target platform or runtime explicitly supports HTTP/API/MCP/webhook calls. Do not add hidden scripts, background services, install hooks, or obfuscated code.',
|
|
721
1034
|
'',
|
|
1035
|
+
`No runtime callback means no live ${PRODUCT_NAME} analytics. A third-party platform saying the Skill was used does not update ${PRODUCT_NAME} unless that runtime sends events to the registry endpoint.`,
|
|
1036
|
+
'',
|
|
722
1037
|
'Best-effort callback placement:',
|
|
723
1038
|
'',
|
|
724
1039
|
'- Send `runtime_install_seen` once when a stable anonymous installation/session is first observed.',
|
|
@@ -744,6 +1059,14 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
744
1059
|
'```bash',
|
|
745
1060
|
`${COMMAND_NAME} track . --event hub_upload_succeeded --hub <hub> --platform-skill-url <published-url>`,
|
|
746
1061
|
'```',
|
|
1062
|
+
'',
|
|
1063
|
+
'Before submitting to a hub that supports runtime HTTP/API calls, verify analytics with:',
|
|
1064
|
+
'',
|
|
1065
|
+
'```bash',
|
|
1066
|
+
`${COMMAND_NAME} track . --event skill_invoked --hub <hub> --installation-id <anonymous-install-id> --invocation-id <test-call-id> --scenario test`,
|
|
1067
|
+
'```',
|
|
1068
|
+
'',
|
|
1069
|
+
'If the hub cannot run callbacks, keep upload attribution and use campaign links or manual imports. Do not fake runtime events.',
|
|
747
1070
|
''
|
|
748
1071
|
].join('\n');
|
|
749
1072
|
const handoffPath = path.join(args.outDir, HANDOFF_FILE);
|
|
@@ -891,6 +1214,209 @@ async function submitTrackEvent(root, flags) {
|
|
|
891
1214
|
return { ok: true, registryUrl, payload, result };
|
|
892
1215
|
}
|
|
893
1216
|
|
|
1217
|
+
async function readSmallTextFile(filePath) {
|
|
1218
|
+
const info = await stat(filePath).catch(() => null);
|
|
1219
|
+
if (!info?.isFile() || info.size > MAX_FILE_BYTES) return '';
|
|
1220
|
+
try {
|
|
1221
|
+
return await readFile(filePath, 'utf8');
|
|
1222
|
+
} catch {
|
|
1223
|
+
return '';
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
async function findReadmePath(root) {
|
|
1228
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
1229
|
+
const readme = entries
|
|
1230
|
+
.filter((entry) => entry.isFile() && /^readme(\.|$)/i.test(entry.name))
|
|
1231
|
+
.map((entry) => path.join(root, entry.name))
|
|
1232
|
+
.sort((a, b) => a.localeCompare(b))[0];
|
|
1233
|
+
return readme || path.join(root, 'README.md');
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function registryBlockPresent(text) {
|
|
1237
|
+
return /<!--\s*(?:xiashe|agentpie)-registry:start\s*-->[\s\S]*?<!--\s*(?:xiashe|agentpie)-registry:end\s*-->/i.test(text || '');
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function hasEntryInstructions(text, packageJson) {
|
|
1241
|
+
if (packageJson?.main || packageJson?.bin || packageJson?.scripts) return true;
|
|
1242
|
+
return /(入口|使用|安装|运行|调用|命令|Usage|Install|Quick start|Getting started|CLI|MCP|API|Entrypoint|Run)/i.test(text || '');
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function hasRuntimeCallbackText(text) {
|
|
1246
|
+
return /(\/registry\/skill-events|trackSkillEvent|runtime_install_seen|skill_invoked|skill_completed|skill_failed|XIASHE_SKILL_REGISTRY_URL|AGENTPIE_SKILL_REGISTRY_URL)/i.test(text || '');
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function doctorCheck(id, label, status, message, fix = '') {
|
|
1250
|
+
return { id, label, status, message, fix };
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
async function runDoctor(root, flags) {
|
|
1254
|
+
const inspected = await inspectSkill(root, flags);
|
|
1255
|
+
const skillMdPath = path.join(inspected.root, 'SKILL.md');
|
|
1256
|
+
const readmePath = await findReadmePath(inspected.root);
|
|
1257
|
+
const manifestPath = path.join(inspected.root, MANIFEST_FILE);
|
|
1258
|
+
const localManifestPath = path.join(inspected.root, WORK_DIR, MANIFEST_FILE);
|
|
1259
|
+
const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
1260
|
+
const handoffPath = path.join(inspected.root, WORK_DIR, HANDOFF_FILE);
|
|
1261
|
+
const snippetPath = path.join(inspected.root, WORK_DIR, 'runtime-events.js');
|
|
1262
|
+
const packageJson = await readJsonFile(path.join(inspected.root, 'package.json')).catch(() => null);
|
|
1263
|
+
const skillMd = await readSmallTextFile(skillMdPath);
|
|
1264
|
+
const readme = await readSmallTextFile(readmePath);
|
|
1265
|
+
const snippet = await readSmallTextFile(snippetPath);
|
|
1266
|
+
const scannedCallbackFiles = [];
|
|
1267
|
+
let scannedCallbackText = '';
|
|
1268
|
+
for (const file of inspected.package.files.slice(0, 160)) {
|
|
1269
|
+
if (!/\.(md|mdx|txt|json|ya?ml|toml|js|mjs|cjs|ts|tsx|jsx|py|go|rs|java|rb|php|sh)$/i.test(file.path)) continue;
|
|
1270
|
+
const absolute = path.join(inspected.root, file.path);
|
|
1271
|
+
const text = await readSmallTextFile(absolute);
|
|
1272
|
+
if (hasRuntimeCallbackText(text)) {
|
|
1273
|
+
scannedCallbackFiles.push(file.path);
|
|
1274
|
+
scannedCallbackText += `\n${text}`;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const manifest = inspected.registry
|
|
1278
|
+
? await readJsonFile(manifestPath).catch(() => null) || await readJsonFile(localManifestPath).catch(() => null)
|
|
1279
|
+
: null;
|
|
1280
|
+
const textBundle = `${skillMd}\n${readme}`;
|
|
1281
|
+
const hasManifest = Boolean(manifest);
|
|
1282
|
+
const registry = manifest?.registry || inspected.registry || {};
|
|
1283
|
+
const runtimeCallbackPresent = hasRuntimeCallbackText(snippet) || hasRuntimeCallbackText(textBundle) || hasRuntimeCallbackText(scannedCallbackText);
|
|
1284
|
+
const riskyRootEntries = ['.env', '.env.local', '.env.production', 'node_modules', 'dist', 'build']
|
|
1285
|
+
.filter((name) => existsSync(path.join(inspected.root, name)));
|
|
1286
|
+
const checks = [
|
|
1287
|
+
existsSync(skillMdPath)
|
|
1288
|
+
? doctorCheck('skill_md', 'SKILL.md', 'pass', 'Found SKILL.md.')
|
|
1289
|
+
: doctorCheck('skill_md', 'SKILL.md', 'fail', 'SKILL.md is missing.', 'Create SKILL.md with the Skill purpose, usage, and safety notes.'),
|
|
1290
|
+
existsSync(readmePath)
|
|
1291
|
+
? doctorCheck('readme', 'README', 'pass', `Found ${path.basename(readmePath)}.`)
|
|
1292
|
+
: doctorCheck('readme', 'README', 'warn', 'README is missing.', 'Add README.md if the target hub expects public documentation.'),
|
|
1293
|
+
hasEntryInstructions(textBundle, packageJson)
|
|
1294
|
+
? doctorCheck('entry_instructions', 'Entry instructions', 'pass', 'Usage or entry instructions are present.')
|
|
1295
|
+
: doctorCheck('entry_instructions', 'Entry instructions', 'warn', 'No clear usage/entry instructions detected.', 'Add usage, install, MCP/API, or command instructions to SKILL.md or README.'),
|
|
1296
|
+
hasManifest && registry.publicToken && registry.registryUrl
|
|
1297
|
+
? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
|
|
1298
|
+
: doctorCheck('registry_manifest', 'Registry manifest', 'fail', `Missing usable registry manifest (${MANIFEST_FILE} or ${WORK_DIR}/${MANIFEST_FILE}).`, `Run ${COMMAND_NAME} setup . --code <dashboard-code> or ${COMMAND_NAME} attach . --public-token <token> --skill-id <id>.`),
|
|
1299
|
+
registryBlockPresent(skillMd)
|
|
1300
|
+
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
|
|
1301
|
+
: doctorCheck('registry_block', 'Registry block', 'warn', 'SKILL.md does not contain a registry block.', `Run ${COMMAND_NAME} setup . --code <code> --embed-skill-md, especially for restrictive hubs such as Red Skill.`),
|
|
1302
|
+
runtimeCallbackPresent
|
|
1303
|
+
? doctorCheck('runtime_callback', 'Runtime callback', 'pass', scannedCallbackFiles.length > 0 ? `Runtime callback references found in ${scannedCallbackFiles.slice(0, 5).join(', ')}.` : 'Runtime callback snippet/reference detected.')
|
|
1304
|
+
: doctorCheck('runtime_callback', 'Runtime callback', 'warn', 'No runtime callback reference detected.', `Use ${COMMAND_NAME} snippet . --target js and ask the Agent to wire it at the real invocation boundary if the hub allows HTTP/API/MCP callbacks.`),
|
|
1305
|
+
existsSync(disclosurePath)
|
|
1306
|
+
? doctorCheck('disclosure', 'Read-only disclosure', 'pass', `Found ${path.relative(inspected.root, disclosurePath)}.`)
|
|
1307
|
+
: doctorCheck('disclosure', 'Read-only disclosure', 'warn', 'REGISTRY_DISCLOSURE.md is missing.', `Run ${COMMAND_NAME} setup . --code <code> to generate platform review disclosure.`),
|
|
1308
|
+
existsSync(handoffPath)
|
|
1309
|
+
? doctorCheck('handoff', 'Agent handoff', 'pass', `Found ${path.relative(inspected.root, handoffPath)}.`)
|
|
1310
|
+
: doctorCheck('handoff', 'Agent handoff', 'warn', 'Unified upload handoff is missing.', `Run ${COMMAND_NAME} setup . --code <code> --hub all.`),
|
|
1311
|
+
riskyRootEntries.length === 0
|
|
1312
|
+
? doctorCheck('risky_files', 'Risky local files', 'pass', 'No common risky root files/directories detected.')
|
|
1313
|
+
: doctorCheck('risky_files', 'Risky local files', 'warn', `Found ${riskyRootEntries.join(', ')} in the Skill root.`, 'Do not upload secrets, node_modules, dist/build output, or unrelated local files unless the target hub explicitly requires them.')
|
|
1314
|
+
];
|
|
1315
|
+
const failed = checks.filter((check) => check.status === 'fail').length;
|
|
1316
|
+
const warned = checks.filter((check) => check.status === 'warn').length;
|
|
1317
|
+
const passed = checks.filter((check) => check.status === 'pass').length;
|
|
1318
|
+
const score = Math.max(0, Math.round((passed / checks.length) * 100) - failed * 15 - warned * 4);
|
|
1319
|
+
return {
|
|
1320
|
+
ok: failed === 0,
|
|
1321
|
+
score,
|
|
1322
|
+
root: inspected.root,
|
|
1323
|
+
skillKey: inspected.skillKey,
|
|
1324
|
+
name: inspected.name,
|
|
1325
|
+
package: inspected.package,
|
|
1326
|
+
checks,
|
|
1327
|
+
next: checks
|
|
1328
|
+
.filter((check) => check.status !== 'pass' && check.fix)
|
|
1329
|
+
.map((check) => check.fix)
|
|
1330
|
+
.slice(0, 6)
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function formatDoctor(result) {
|
|
1335
|
+
const statusIcon = { pass: 'PASS', warn: 'WARN', fail: 'FAIL' };
|
|
1336
|
+
const lines = [
|
|
1337
|
+
`${COMMAND_NAME} doctor`,
|
|
1338
|
+
`Skill: ${result.name} (${result.skillKey})`,
|
|
1339
|
+
`Score: ${result.score}/100`,
|
|
1340
|
+
''
|
|
1341
|
+
];
|
|
1342
|
+
for (const check of result.checks) {
|
|
1343
|
+
lines.push(`${statusIcon[check.status] || check.status} ${check.label}: ${check.message}`);
|
|
1344
|
+
if (check.status !== 'pass' && check.fix) lines.push(` Fix: ${check.fix}`);
|
|
1345
|
+
}
|
|
1346
|
+
if (result.next.length > 0) {
|
|
1347
|
+
lines.push('', 'Next:');
|
|
1348
|
+
for (const item of result.next) lines.push(`- ${item}`);
|
|
1349
|
+
}
|
|
1350
|
+
return lines.join('\n');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
async function verifyRegistry(root, flags) {
|
|
1354
|
+
const inspected = await inspectSkill(root, flags);
|
|
1355
|
+
const hub = normalizeHub(flags.hub || 'generic') || 'generic';
|
|
1356
|
+
const scenario = normalizeText(flags.scenario || `${REGISTRY_PROVIDER}-skill-verify`, 120);
|
|
1357
|
+
const timestamp = Date.now();
|
|
1358
|
+
const installationId = normalizeText(flags['installation-id'] || `verify-${REGISTRY_PROVIDER}-${timestamp}`, 220);
|
|
1359
|
+
const invocationId = normalizeText(flags['invocation-id'] || `verify-call-${timestamp}`, 160);
|
|
1360
|
+
const events = normalizeText(flags.events || '', 400)
|
|
1361
|
+
? normalizeText(flags.events, 400).split(',').map((item) => normalizeText(item, 80)).filter(Boolean)
|
|
1362
|
+
: ['runtime_install_seen', 'skill_invoked', 'skill_completed'];
|
|
1363
|
+
const results = [];
|
|
1364
|
+
for (const eventType of events) {
|
|
1365
|
+
const eventResult = await submitTrackEvent(root, {
|
|
1366
|
+
...flags,
|
|
1367
|
+
event: eventType,
|
|
1368
|
+
hub,
|
|
1369
|
+
'event-source': 'runtime',
|
|
1370
|
+
'runtime-kind': 'cli-verify',
|
|
1371
|
+
'source-surface': hub,
|
|
1372
|
+
scenario,
|
|
1373
|
+
'installation-id': installationId,
|
|
1374
|
+
'invocation-id': invocationId,
|
|
1375
|
+
'idempotency-key': `verify:${inspected.skillKey}:${hub}:${invocationId}:${eventType}`
|
|
1376
|
+
});
|
|
1377
|
+
results.push({ eventType, ...eventResult });
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
ok: results.every((result) => result.ok),
|
|
1381
|
+
dryRun: Boolean(flags.dryRun),
|
|
1382
|
+
skillKey: inspected.skillKey,
|
|
1383
|
+
hub,
|
|
1384
|
+
scenario,
|
|
1385
|
+
installationId,
|
|
1386
|
+
invocationId,
|
|
1387
|
+
registryUrl: results[0]?.registryUrl || inspected.registry?.registryUrl || DEFAULT_REGISTRY_URL,
|
|
1388
|
+
events: results,
|
|
1389
|
+
next: [
|
|
1390
|
+
'Open the creator dashboard and check 接入健康度 / Integration health.',
|
|
1391
|
+
'Verified should become active after these cli-verify events are indexed.',
|
|
1392
|
+
'If runtime remains inactive later, the third-party platform is not calling /registry/skill-events from real Skill execution.'
|
|
1393
|
+
]
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function formatVerify(result) {
|
|
1398
|
+
const lines = [
|
|
1399
|
+
`${COMMAND_NAME} verify`,
|
|
1400
|
+
`Skill key: ${result.skillKey}`,
|
|
1401
|
+
`Hub: ${result.hub}`,
|
|
1402
|
+
`Scenario: ${result.scenario}`,
|
|
1403
|
+
`Installation: ${result.installationId}`,
|
|
1404
|
+
`Invocation: ${result.invocationId}`,
|
|
1405
|
+
`Endpoint: ${result.registryUrl}`,
|
|
1406
|
+
''
|
|
1407
|
+
];
|
|
1408
|
+
for (const item of result.events) {
|
|
1409
|
+
if (item.dryRun) {
|
|
1410
|
+
lines.push(`DRY ${item.eventType}: ${JSON.stringify(item.payload)}`);
|
|
1411
|
+
} else {
|
|
1412
|
+
lines.push(`OK ${item.eventType}: ${item.result?.eventId || item.result?.status || 'accepted'}`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
lines.push('', 'Next:');
|
|
1416
|
+
for (const item of result.next) lines.push(`- ${item}`);
|
|
1417
|
+
return lines.join('\n');
|
|
1418
|
+
}
|
|
1419
|
+
|
|
894
1420
|
async function setupAgentWorkflow(root, flags) {
|
|
895
1421
|
const hubs = setupHubsFromFlags(flags);
|
|
896
1422
|
const absoluteRoot = path.resolve(root || '.');
|
|
@@ -903,15 +1429,27 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
903
1429
|
});
|
|
904
1430
|
|
|
905
1431
|
const promptResults = [];
|
|
1432
|
+
const packagePlans = [];
|
|
906
1433
|
for (const hub of hubs) {
|
|
907
1434
|
const promptPath = path.join(outDir, `upload-${safeSkillKey(hub)}.md`);
|
|
908
1435
|
const promptResult = await writePrompt(absoluteRoot, { ...flags, hub, out: promptPath });
|
|
1436
|
+
const inspectedForPlan = await inspectSkill(absoluteRoot, flags);
|
|
1437
|
+
const plan = packagePlanForHub(inspectedForPlan, hub);
|
|
1438
|
+
const planPath = path.join(outDir, `package-${safeSkillKey(hub)}.json`);
|
|
1439
|
+
await writeFile(planPath, `${JSON.stringify(plan, null, 2)}\n`, { mode: 0o600 });
|
|
909
1440
|
promptResults.push({
|
|
910
1441
|
hub,
|
|
911
1442
|
promptPath: typeof promptResult === 'string' ? promptPath : promptResult.outPath
|
|
912
1443
|
});
|
|
1444
|
+
packagePlans.push({ hub, planPath, plan });
|
|
913
1445
|
}
|
|
914
1446
|
const inspectedAfterManifest = await inspectSkill(absoluteRoot, flags);
|
|
1447
|
+
const profilesPath = path.join(outDir, 'platform-profiles.json');
|
|
1448
|
+
await writeFile(
|
|
1449
|
+
profilesPath,
|
|
1450
|
+
`${JSON.stringify(Object.fromEntries(hubs.map((hub) => [hub, reviewProfileForHub(hub)])), null, 2)}\n`,
|
|
1451
|
+
{ mode: 0o600 }
|
|
1452
|
+
);
|
|
915
1453
|
const shouldEmbedSkillMd = Boolean(flags['embed-skill-md']) || (hubs.includes('red') && !flags['no-skill-md']);
|
|
916
1454
|
const skillMdPath = shouldEmbedSkillMd
|
|
917
1455
|
? await writeSkillMdRegistryBlock(absoluteRoot, inspectedAfterManifest, hubs.join(','))
|
|
@@ -937,7 +1475,8 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
937
1475
|
outDir,
|
|
938
1476
|
hubs,
|
|
939
1477
|
inspected: inspectedAfterManifest,
|
|
940
|
-
promptResults
|
|
1478
|
+
promptResults,
|
|
1479
|
+
packagePlans
|
|
941
1480
|
});
|
|
942
1481
|
|
|
943
1482
|
const warnings = [];
|
|
@@ -967,6 +1506,8 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
967
1506
|
manifestPath: manifestResult.manifestPath,
|
|
968
1507
|
handoffPath,
|
|
969
1508
|
promptPaths: promptResults,
|
|
1509
|
+
packagePlanPaths: packagePlans.map((item) => ({ hub: item.hub, planPath: item.planPath })),
|
|
1510
|
+
profilesPath,
|
|
970
1511
|
skillMdPath,
|
|
971
1512
|
disclosurePath,
|
|
972
1513
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
@@ -974,6 +1515,7 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
974
1515
|
warnings,
|
|
975
1516
|
next: [
|
|
976
1517
|
`Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
|
|
1518
|
+
`Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
|
|
977
1519
|
'When the user later pastes a Red Skill / ClawHub / SkillHub / Claude / Dify / Coze official prompt, the Agent should identify the platform and merge the matching registry checklist internally.',
|
|
978
1520
|
'Each prepared upload-<hub>.md pins the correct hub/sourceSurface value so platform analytics stay separated, but those files are internal checklists for the Agent.',
|
|
979
1521
|
skillMdPath
|
|
@@ -1010,6 +1552,47 @@ async function main() {
|
|
|
1010
1552
|
print(result, flags.json);
|
|
1011
1553
|
return;
|
|
1012
1554
|
}
|
|
1555
|
+
if (command === 'doctor') {
|
|
1556
|
+
const result = await runDoctor(root, flags);
|
|
1557
|
+
print(flags.json ? result : formatDoctor(result), flags.json);
|
|
1558
|
+
if (!result.ok) {
|
|
1559
|
+
process.exitCode = 1;
|
|
1560
|
+
}
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
if (command === 'verify') {
|
|
1564
|
+
const result = await verifyRegistry(root, flags);
|
|
1565
|
+
print(flags.json ? result : formatVerify(result), flags.json);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
if (command === 'publish') {
|
|
1569
|
+
const result = await setupAgentWorkflow(root, flags);
|
|
1570
|
+
if (flags.json) {
|
|
1571
|
+
print({ ok: true, mode: 'xiashe-publish', ...result }, true);
|
|
1572
|
+
} else {
|
|
1573
|
+
const lines = [
|
|
1574
|
+
`${PRODUCT_NAME} publish handoff prepared.`,
|
|
1575
|
+
`Targets: ${result.hubs.join(', ')}`,
|
|
1576
|
+
`Manifest: ${result.manifestPath}`,
|
|
1577
|
+
`Agent handoff: ${result.handoffPath}`,
|
|
1578
|
+
`Platform profiles: ${result.profilesPath}`,
|
|
1579
|
+
...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
|
|
1580
|
+
result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
|
|
1581
|
+
`Disclosure: ${result.disclosurePath}`,
|
|
1582
|
+
result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
|
|
1583
|
+
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
1584
|
+
'',
|
|
1585
|
+
'Next:',
|
|
1586
|
+
'- Treat the target platform official upload flow as authoritative.',
|
|
1587
|
+
'- Use the handoff file as the only XiaShe registry checklist.',
|
|
1588
|
+
'- For XiaShe Store, create or update the store draft after the manifest is written.',
|
|
1589
|
+
'- For Markdown/TXT-only platforms, copy the disclosure text into an allowed public field instead of uploading registry JSON.',
|
|
1590
|
+
'- Only count runtime usage when the Skill or Agent callback sends real runtime events.'
|
|
1591
|
+
].filter(Boolean);
|
|
1592
|
+
print(lines.join('\n'), false);
|
|
1593
|
+
}
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1013
1596
|
if (command === 'setup' || command === 'handoff') {
|
|
1014
1597
|
const result = await setupAgentWorkflow(root, flags);
|
|
1015
1598
|
if (flags.json) {
|
|
@@ -1019,6 +1602,8 @@ async function main() {
|
|
|
1019
1602
|
`Wrote ${result.manifestPath}`,
|
|
1020
1603
|
`Wrote ${result.handoffPath}`,
|
|
1021
1604
|
...result.promptPaths.map((item) => `Wrote ${item.promptPath}`),
|
|
1605
|
+
`Wrote ${result.profilesPath}`,
|
|
1606
|
+
...result.packagePlanPaths.map((item) => `Wrote ${item.planPath}`),
|
|
1022
1607
|
`Wrote ${result.disclosurePath}`,
|
|
1023
1608
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|
|
1024
1609
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|