@xiashe/skill 0.1.2 → 0.1.5
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 +6 -3
- package/bin/xiashe-skill.mjs +245 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,22 +9,25 @@ 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 registry setup command
|
|
12
|
+
- run one Agent-friendly registry setup command that prepares all supported hub handoffs
|
|
13
13
|
- write an explicit `xiashe.skill.json` registry manifest
|
|
14
|
-
- generate
|
|
14
|
+
- generate one unified `UPLOAD_HANDOFF.md` that the creator's Agent can use after the user pastes a third-party official upload prompt
|
|
15
15
|
- generate optional runtime analytics snippets
|
|
16
16
|
|
|
17
17
|
It does not create upload packages, copy source directories, install background services, run postinstall hooks, read secrets, or upload to third-party hubs. The creator's Agent is responsible for packaging and uploading according to the target Skill hub's official rules.
|
|
18
18
|
|
|
19
|
+
`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
|
+
|
|
19
21
|
## Local development
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs --help
|
|
23
25
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs inspect .
|
|
26
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub all
|
|
24
27
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub red
|
|
25
28
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs attach . --code XS-XXXX-XXXX
|
|
26
29
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs attach . --public-token pub_xxx --skill-id sk_xxx
|
|
27
30
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs prompt . --hub red --source-url https://example.com/my-skill-repo
|
|
28
31
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs snippet . --target js
|
|
29
|
-
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs track . --event skill_invoked --dry-run --json
|
|
32
|
+
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs track . --event skill_invoked --hub coze --dry-run --json
|
|
30
33
|
```
|
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -6,22 +6,25 @@ 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.5';
|
|
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');
|
|
13
13
|
const DEFAULT_BASE_URL = process.env.XIASHE_SKILL_DEFAULT_BASE_URL || (COMMAND_NAME === 'agentpie-skill'
|
|
14
|
-
? 'https://
|
|
15
|
-
: 'https://
|
|
14
|
+
? 'https://actions.agentpie.app'
|
|
15
|
+
: 'https://actions.xiashe.chat');
|
|
16
16
|
const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME === 'agentpie-skill'
|
|
17
17
|
? 'agentpie.skill.json'
|
|
18
18
|
: 'xiashe.skill.json');
|
|
19
19
|
const WORK_DIR = process.env.XIASHE_SKILL_WORK_DIR || (COMMAND_NAME === 'agentpie-skill' ? '.agentpie' : '.xiashe');
|
|
20
|
+
const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
20
21
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
21
22
|
const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_URL}/registry/skill/claim`;
|
|
22
23
|
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
23
24
|
? 'agentpie.skill.event.v1'
|
|
24
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'];
|
|
25
28
|
const DEFAULT_IGNORE = new Set([
|
|
26
29
|
'.git',
|
|
27
30
|
'.xiashe',
|
|
@@ -44,11 +47,11 @@ function usage() {
|
|
|
44
47
|
|
|
45
48
|
Usage:
|
|
46
49
|
${COMMAND_NAME} inspect [skill-dir] [--json]
|
|
47
|
-
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> --hub
|
|
48
|
-
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> --hub
|
|
50
|
+
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|red|clawhub|skillhub|claude|dify|coze|generic]
|
|
51
|
+
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|red|clawhub|skillhub|claude|dify|coze|generic]
|
|
49
52
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
50
53
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
51
|
-
${COMMAND_NAME} prompt [skill-dir] --hub <red|clawhub|skillhub|claude|dify|generic> [--source-url <url>] [--out <file>]
|
|
54
|
+
${COMMAND_NAME} prompt [skill-dir] --hub <red|clawhub|skillhub|claude|dify|coze|generic> [--source-url <url>] [--out <file>]
|
|
52
55
|
${COMMAND_NAME} snippet [skill-dir] [--target js|curl] [--out <file>]
|
|
53
56
|
${COMMAND_NAME} track [skill-dir] --event <event> [--hub <hub>] [--installation-id <id>] [--invocation-id <id>]
|
|
54
57
|
|
|
@@ -60,7 +63,7 @@ Options:
|
|
|
60
63
|
--registry-url <url> Event endpoint written to the manifest.
|
|
61
64
|
--name <name> Skill display name override.
|
|
62
65
|
--description <text> Skill description override.
|
|
63
|
-
--hub <hub> Target Skill hub prompt style
|
|
66
|
+
--hub <hub> Target Skill hub prompt style. setup defaults to all supported hubs.
|
|
64
67
|
--event <event> Runtime event to submit for testing.
|
|
65
68
|
--installation-id <id> Stable anonymous install id for runtime analytics.
|
|
66
69
|
--invocation-id <id> Unique invocation id for one skill call.
|
|
@@ -75,6 +78,8 @@ Options:
|
|
|
75
78
|
--package-url <url> Deprecated alias for --source-url.
|
|
76
79
|
--out <path> Output prompt or snippet file.
|
|
77
80
|
--out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
|
|
81
|
+
--embed-skill-md Also write a clearly marked registry section into SKILL.md.
|
|
82
|
+
--no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
|
|
78
83
|
--no-snippet Setup should skip writing the runtime analytics snippet.
|
|
79
84
|
--no-track Setup should skip sending upload_prompt_generated.
|
|
80
85
|
--dry-run Print planned event payloads without network writes.
|
|
@@ -91,7 +96,7 @@ function fail(message, code = 1) {
|
|
|
91
96
|
|
|
92
97
|
function parseArgs(argv) {
|
|
93
98
|
const args = { _: [], json: false, dryRun: false };
|
|
94
|
-
const booleanFlags = new Set(['no-snippet', 'no-track']);
|
|
99
|
+
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track']);
|
|
95
100
|
for (let i = 0; i < argv.length; i += 1) {
|
|
96
101
|
const value = argv[i];
|
|
97
102
|
if (value === '--help' || value === '-h') {
|
|
@@ -148,6 +153,37 @@ function safeSkillKey(value) {
|
|
|
148
153
|
.replace(/^-+|-+$/g, '') || 'skill';
|
|
149
154
|
}
|
|
150
155
|
|
|
156
|
+
function normalizeHub(value) {
|
|
157
|
+
const hub = normalizeText(value, 40).toLowerCase().replace(/\s+/g, '').replace(/_/g, '-');
|
|
158
|
+
if (hub === 'redskill' || hub === 'red-skill' || hub === 'xiaohongshu' || hub === 'xhs') {
|
|
159
|
+
return 'red';
|
|
160
|
+
}
|
|
161
|
+
if (hub === 'claw-hub') {
|
|
162
|
+
return 'clawhub';
|
|
163
|
+
}
|
|
164
|
+
if (hub === 'skill-hub') {
|
|
165
|
+
return 'skillhub';
|
|
166
|
+
}
|
|
167
|
+
return hub;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setupHubsFromFlags(flags) {
|
|
171
|
+
const raw = normalizeText(flags.hub || 'all', 240).toLowerCase();
|
|
172
|
+
if (!raw || raw === 'all' || raw === '*') {
|
|
173
|
+
return [...DEFAULT_SETUP_HUBS];
|
|
174
|
+
}
|
|
175
|
+
const hubs = raw
|
|
176
|
+
.split(',')
|
|
177
|
+
.map((item) => normalizeHub(item))
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
const unique = Array.from(new Set(hubs));
|
|
180
|
+
const unsupported = unique.filter((hub) => !SUPPORTED_HUBS.includes(hub));
|
|
181
|
+
if (unsupported.length > 0) {
|
|
182
|
+
throw new Error(`Unsupported --hub value: ${unsupported.join(', ')}. Use all, ${SUPPORTED_HUBS.join(', ')}.`);
|
|
183
|
+
}
|
|
184
|
+
return unique.length > 0 ? unique : [...DEFAULT_SETUP_HUBS];
|
|
185
|
+
}
|
|
186
|
+
|
|
151
187
|
function canonicalSignaturePayload(payload) {
|
|
152
188
|
return [
|
|
153
189
|
payload.schemaVersion || EVENT_SCHEMA_VERSION,
|
|
@@ -338,6 +374,7 @@ async function writeManifest(root, flags) {
|
|
|
338
374
|
},
|
|
339
375
|
analytics: {
|
|
340
376
|
enabled: true,
|
|
377
|
+
supportedHubs: [...SUPPORTED_HUBS],
|
|
341
378
|
allowedEvents: [
|
|
342
379
|
'upload_prompt_generated',
|
|
343
380
|
'hub_upload_attempted',
|
|
@@ -351,6 +388,20 @@ async function writeManifest(root, flags) {
|
|
|
351
388
|
]
|
|
352
389
|
}
|
|
353
390
|
},
|
|
391
|
+
platforms: Object.fromEntries(SUPPORTED_HUBS.map((hub) => [
|
|
392
|
+
hub,
|
|
393
|
+
{
|
|
394
|
+
hub,
|
|
395
|
+
sourceSurface: hub,
|
|
396
|
+
uploadPromptFile: `${WORK_DIR}/upload-${safeSkillKey(hub)}.md`,
|
|
397
|
+
eventDefaults: {
|
|
398
|
+
schemaVersion: EVENT_SCHEMA_VERSION,
|
|
399
|
+
hub,
|
|
400
|
+
sourceSurface: hub,
|
|
401
|
+
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
])),
|
|
354
405
|
sourceFingerprint: {
|
|
355
406
|
sha256: inspected.package.sha256,
|
|
356
407
|
fileCount: inspected.package.fileCount,
|
|
@@ -384,6 +435,9 @@ function hubInstruction(hub) {
|
|
|
384
435
|
if (hub === 'dify') {
|
|
385
436
|
return 'Dify Plugin/Tool 的官方打包和发布流程是主流程。请先按 Dify 插件规范处理 manifest 和工具定义,再把下面的 registry 披露文本放入允许的 README/说明字段。';
|
|
386
437
|
}
|
|
438
|
+
if (hub === 'coze') {
|
|
439
|
+
return 'Coze Agent/Plugin/Workflow 的官方配置和发布流程是主流程。请先按 Coze 当前工具、插件或知识库要求配置,再把下面的 registry 回调作为可选 HTTP/API 步骤接入。';
|
|
440
|
+
}
|
|
387
441
|
return '目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
|
|
388
442
|
}
|
|
389
443
|
|
|
@@ -403,6 +457,9 @@ function platformPromptPlaceholder(hub) {
|
|
|
403
457
|
if (hub === 'dify') {
|
|
404
458
|
return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
|
|
405
459
|
}
|
|
460
|
+
if (hub === 'coze') {
|
|
461
|
+
return '如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用虾舍 prompt 替代 Coze 官方流程。';
|
|
462
|
+
}
|
|
406
463
|
return '如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代平台官方流程。';
|
|
407
464
|
}
|
|
408
465
|
|
|
@@ -442,6 +499,55 @@ function publicDisclosure(inspected, registryUrl) {
|
|
|
442
499
|
];
|
|
443
500
|
}
|
|
444
501
|
|
|
502
|
+
function skillMdRegistryBlock(inspected, hub) {
|
|
503
|
+
const registry = inspected.registry || {};
|
|
504
|
+
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
505
|
+
const publicToken = registry.publicToken || '<public token from registry manifest>';
|
|
506
|
+
const marker = `${REGISTRY_PROVIDER}-registry`;
|
|
507
|
+
return [
|
|
508
|
+
`<!-- ${marker}:start -->`,
|
|
509
|
+
`## ${PRODUCT_NAME} Registry / Analytics`,
|
|
510
|
+
'',
|
|
511
|
+
`This Skill is explicitly registered by its creator with ${PRODUCT_NAME} for public attribution and aggregated analytics.`,
|
|
512
|
+
'',
|
|
513
|
+
`- Registry provider: ${REGISTRY_PROVIDER}`,
|
|
514
|
+
`- Skill key: ${inspected.skillKey}`,
|
|
515
|
+
`- Target hub: ${hub}`,
|
|
516
|
+
`- Registry event endpoint: ${registryUrl}`,
|
|
517
|
+
`- Public write token: ${publicToken}`,
|
|
518
|
+
`- Event schema version: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
519
|
+
`- Source fingerprint: ${inspected.package.sha256}`,
|
|
520
|
+
'',
|
|
521
|
+
'Analytics boundary:',
|
|
522
|
+
'- The public token can only submit allowed aggregate analytics events. It cannot read creator data or administer the account.',
|
|
523
|
+
'- Events should include only public metadata: hub, sourceSurface, campaign, scenario, anonymous installationId, invocationId, and status.',
|
|
524
|
+
'- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
|
|
525
|
+
'- The creator can rotate/revoke the token or disable analytics from the dashboard.',
|
|
526
|
+
'',
|
|
527
|
+
'Recommended runtime events, if the target platform supports callbacks:',
|
|
528
|
+
'- `runtime_install_seen` when a stable anonymous installation is first observed.',
|
|
529
|
+
'- `skill_invoked` when a Skill invocation starts.',
|
|
530
|
+
'- `skill_completed` when an invocation completes.',
|
|
531
|
+
'- `skill_failed` when an invocation fails.',
|
|
532
|
+
'',
|
|
533
|
+
`<!-- ${marker}:end -->`
|
|
534
|
+
].join('\n');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function writeSkillMdRegistryBlock(root, inspected, hub) {
|
|
538
|
+
const skillMdPath = path.join(root, 'SKILL.md');
|
|
539
|
+
const existing = existsSync(skillMdPath)
|
|
540
|
+
? await readFile(skillMdPath, 'utf8')
|
|
541
|
+
: `# ${inspected.name}\n\n${inspected.description || ''}\n`;
|
|
542
|
+
const block = skillMdRegistryBlock(inspected, hub);
|
|
543
|
+
const pattern = /<!-- (?:xiashe|agentpie)-registry:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-registry:end -->/m;
|
|
544
|
+
const next = pattern.test(existing)
|
|
545
|
+
? existing.replace(pattern, block)
|
|
546
|
+
: `${existing.trimEnd()}\n\n${block}\n`;
|
|
547
|
+
await writeFile(skillMdPath, next, { mode: 0o600 });
|
|
548
|
+
return skillMdPath;
|
|
549
|
+
}
|
|
550
|
+
|
|
445
551
|
async function readPlatformPrompt(flags) {
|
|
446
552
|
const promptPath = normalizeText(flags['platform-prompt-file'], 1000);
|
|
447
553
|
if (!promptPath) return '';
|
|
@@ -457,7 +563,7 @@ async function readPlatformPrompt(flags) {
|
|
|
457
563
|
}
|
|
458
564
|
|
|
459
565
|
async function uploadPrompt(inspected, flags) {
|
|
460
|
-
const hub =
|
|
566
|
+
const hub = normalizeHub(flags.hub || 'generic') || 'generic';
|
|
461
567
|
const sourceUrl = normalizeText(flags['source-url'] || flags['package-url'], 1000);
|
|
462
568
|
const platformCommand = normalizeText(flags['platform-command'], 1000);
|
|
463
569
|
const platformPrompt = await readPlatformPrompt(flags);
|
|
@@ -554,7 +660,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
554
660
|
|
|
555
661
|
async function writePrompt(root, flags) {
|
|
556
662
|
if (!flags.hub) {
|
|
557
|
-
throw new Error(
|
|
663
|
+
throw new Error(`Missing --hub <${SUPPORTED_HUBS.join('|')}>.`);
|
|
558
664
|
}
|
|
559
665
|
const inspected = await inspectSkill(root, flags);
|
|
560
666
|
const prompt = await uploadPrompt(inspected, flags);
|
|
@@ -567,6 +673,84 @@ async function writePrompt(root, flags) {
|
|
|
567
673
|
return prompt;
|
|
568
674
|
}
|
|
569
675
|
|
|
676
|
+
async function writeUnifiedHandoff(root, args) {
|
|
677
|
+
const relativeOutDir = path.relative(root, args.outDir) || WORK_DIR;
|
|
678
|
+
const promptIndex = args.promptResults
|
|
679
|
+
.map((item) => `- ${item.hub}: ${path.relative(root, item.promptPath)}`)
|
|
680
|
+
.join('\n');
|
|
681
|
+
const registry = args.inspected.registry || {};
|
|
682
|
+
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
683
|
+
const content = [
|
|
684
|
+
`# ${PRODUCT_NAME} Skill Upload Handoff`,
|
|
685
|
+
'',
|
|
686
|
+
`Skill: ${args.inspected.name}`,
|
|
687
|
+
`Skill Key: ${args.inspected.skillKey}`,
|
|
688
|
+
`Supported hubs prepared: ${args.hubs.join(', ')}`,
|
|
689
|
+
'',
|
|
690
|
+
'## How to use this file',
|
|
691
|
+
'',
|
|
692
|
+
'This is the single handoff file for the user\'s Agent. Do not ask the user to manually choose or inspect registry files.',
|
|
693
|
+
'',
|
|
694
|
+
'When the user later pastes an official third-party platform upload prompt, CLI command, or page instructions:',
|
|
695
|
+
'',
|
|
696
|
+
'1. Identify the target platform from that official prompt, URL, command, or user message.',
|
|
697
|
+
`2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
|
|
698
|
+
'3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
|
|
699
|
+
`4. Merge only the safe ${PRODUCT_NAME} registry additions: public disclosure, source attribution, hub value, and optional runtime event callback.`,
|
|
700
|
+
'5. If the platform rejects extra files, do not upload registry JSON/snippets. Put the disclosure text into SKILL.md, README, or the platform description field only when allowed.',
|
|
701
|
+
'6. If the Skill exposes an MCP tool, API endpoint, webhook, script entrypoint, or Agent-executable function, add best-effort runtime event callbacks at the real invocation boundary.',
|
|
702
|
+
'7. Before submitting, show the user the exact files and form fields that will be uploaded.',
|
|
703
|
+
'8. Never upload .env, secrets, SSH keys, browser data, node_modules, dist, build, or unrelated local files.',
|
|
704
|
+
'',
|
|
705
|
+
'## Platform mapping',
|
|
706
|
+
'',
|
|
707
|
+
promptIndex,
|
|
708
|
+
'',
|
|
709
|
+
'## Official prompt merge rule',
|
|
710
|
+
'',
|
|
711
|
+
'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.',
|
|
712
|
+
'',
|
|
713
|
+
'If the target platform is unclear, ask one short question: "Which Skill platform should I upload to?"',
|
|
714
|
+
'',
|
|
715
|
+
'## Analytics callback',
|
|
716
|
+
'',
|
|
717
|
+
`Registry endpoint: ${registryUrl}`,
|
|
718
|
+
`Event schema: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
719
|
+
'',
|
|
720
|
+
'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
|
+
'',
|
|
722
|
+
'Best-effort callback placement:',
|
|
723
|
+
'',
|
|
724
|
+
'- Send `runtime_install_seen` once when a stable anonymous installation/session is first observed.',
|
|
725
|
+
'- Send `skill_invoked` at the start of the actual Skill execution, not when the upload prompt is generated.',
|
|
726
|
+
'- Send `skill_completed` after successful completion and `skill_failed` after handled failure.',
|
|
727
|
+
'- Use one stable anonymous `installationId` per installed copy/runtime connection. Never use email, phone, real name, device serial, raw prompt text, or private content.',
|
|
728
|
+
'- Use one `invocationId` per execution and use it in the idempotency key so retries do not double count.',
|
|
729
|
+
'',
|
|
730
|
+
'Required source separation:',
|
|
731
|
+
'',
|
|
732
|
+
'```json',
|
|
733
|
+
JSON.stringify({
|
|
734
|
+
hub: '<red|clawhub|skillhub|claude|dify|coze|generic>',
|
|
735
|
+
sourceSurface: '<same as hub unless the platform requires another public label>',
|
|
736
|
+
scenario: '<short public label only>',
|
|
737
|
+
installationId: '<stable anonymous install id>',
|
|
738
|
+
invocationId: '<unique invocation id>'
|
|
739
|
+
}, null, 2),
|
|
740
|
+
'```',
|
|
741
|
+
'',
|
|
742
|
+
'After a successful publication, record the public URL with:',
|
|
743
|
+
'',
|
|
744
|
+
'```bash',
|
|
745
|
+
`${COMMAND_NAME} track . --event hub_upload_succeeded --hub <hub> --platform-skill-url <published-url>`,
|
|
746
|
+
'```',
|
|
747
|
+
''
|
|
748
|
+
].join('\n');
|
|
749
|
+
const handoffPath = path.join(args.outDir, HANDOFF_FILE);
|
|
750
|
+
await writeFile(handoffPath, content, { mode: 0o600 });
|
|
751
|
+
return handoffPath;
|
|
752
|
+
}
|
|
753
|
+
|
|
570
754
|
async function writeRuntimeSnippet(root, flags) {
|
|
571
755
|
const inspected = await inspectSkill(root, flags);
|
|
572
756
|
const registry = inspected.registry || {};
|
|
@@ -676,7 +860,7 @@ async function submitTrackEvent(root, flags) {
|
|
|
676
860
|
const occurredAt = Number(flags['occurred-at'] || Date.now());
|
|
677
861
|
const invocationId = normalizeText(flags['invocation-id'] || `cli-${Date.now()}`, 160);
|
|
678
862
|
const installationId = normalizeText(flags['installation-id'] || 'local-test-install', 220);
|
|
679
|
-
const hub =
|
|
863
|
+
const hub = normalizeHub(flags.hub || 'local-test') || 'local-test';
|
|
680
864
|
const payload = {
|
|
681
865
|
publicToken,
|
|
682
866
|
schemaVersion: normalizeText(flags['schema-version'], 80) || EVENT_SCHEMA_VERSION,
|
|
@@ -708,7 +892,7 @@ async function submitTrackEvent(root, flags) {
|
|
|
708
892
|
}
|
|
709
893
|
|
|
710
894
|
async function setupAgentWorkflow(root, flags) {
|
|
711
|
-
const
|
|
895
|
+
const hubs = setupHubsFromFlags(flags);
|
|
712
896
|
const absoluteRoot = path.resolve(root || '.');
|
|
713
897
|
const outDir = path.resolve(flags['out-dir'] || path.join(absoluteRoot, WORK_DIR));
|
|
714
898
|
await mkdir(outDir, { recursive: true });
|
|
@@ -718,9 +902,20 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
718
902
|
'manifest-path': flags['manifest-path'] || path.join(outDir, MANIFEST_FILE)
|
|
719
903
|
});
|
|
720
904
|
|
|
721
|
-
const
|
|
722
|
-
|
|
905
|
+
const promptResults = [];
|
|
906
|
+
for (const hub of hubs) {
|
|
907
|
+
const promptPath = path.join(outDir, `upload-${safeSkillKey(hub)}.md`);
|
|
908
|
+
const promptResult = await writePrompt(absoluteRoot, { ...flags, hub, out: promptPath });
|
|
909
|
+
promptResults.push({
|
|
910
|
+
hub,
|
|
911
|
+
promptPath: typeof promptResult === 'string' ? promptPath : promptResult.outPath
|
|
912
|
+
});
|
|
913
|
+
}
|
|
723
914
|
const inspectedAfterManifest = await inspectSkill(absoluteRoot, flags);
|
|
915
|
+
const shouldEmbedSkillMd = Boolean(flags['embed-skill-md']) || (hubs.includes('red') && !flags['no-skill-md']);
|
|
916
|
+
const skillMdPath = shouldEmbedSkillMd
|
|
917
|
+
? await writeSkillMdRegistryBlock(absoluteRoot, inspectedAfterManifest, hubs.join(','))
|
|
918
|
+
: null;
|
|
724
919
|
const disclosurePath = path.join(outDir, 'REGISTRY_DISCLOSURE.md');
|
|
725
920
|
await writeFile(
|
|
726
921
|
disclosurePath,
|
|
@@ -738,39 +933,55 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
738
933
|
out: snippetPath
|
|
739
934
|
});
|
|
740
935
|
}
|
|
936
|
+
const handoffPath = await writeUnifiedHandoff(absoluteRoot, {
|
|
937
|
+
outDir,
|
|
938
|
+
hubs,
|
|
939
|
+
inspected: inspectedAfterManifest,
|
|
940
|
+
promptResults
|
|
941
|
+
});
|
|
741
942
|
|
|
742
943
|
const warnings = [];
|
|
743
|
-
|
|
944
|
+
const promptEvents = [];
|
|
744
945
|
if (!flags['no-track']) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
946
|
+
for (const hub of hubs) {
|
|
947
|
+
try {
|
|
948
|
+
const promptEvent = await submitTrackEvent(absoluteRoot, {
|
|
949
|
+
...flags,
|
|
950
|
+
hub,
|
|
951
|
+
event: 'upload_prompt_generated',
|
|
952
|
+
'event-source': 'cli',
|
|
953
|
+
'runtime-kind': 'agent-handoff',
|
|
954
|
+
scenario: flags.scenario || `third-party-skill-upload:${hub}`,
|
|
955
|
+
'idempotency-key': `upload_prompt_generated:${hub}:${inspectedAfterManifest.package.sha256}`
|
|
956
|
+
});
|
|
957
|
+
promptEvents.push({ hub, promptEvent });
|
|
958
|
+
} catch (error) {
|
|
959
|
+
warnings.push(`upload_prompt_generated for ${hub} was not recorded: ${error instanceof Error ? error.message : String(error)}`);
|
|
960
|
+
}
|
|
756
961
|
}
|
|
757
962
|
}
|
|
758
963
|
|
|
759
964
|
return {
|
|
760
965
|
ok: true,
|
|
761
|
-
|
|
966
|
+
hubs,
|
|
762
967
|
manifestPath: manifestResult.manifestPath,
|
|
763
|
-
|
|
968
|
+
handoffPath,
|
|
969
|
+
promptPaths: promptResults,
|
|
970
|
+
skillMdPath,
|
|
764
971
|
disclosurePath,
|
|
765
972
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
766
|
-
|
|
973
|
+
promptEvents,
|
|
767
974
|
warnings,
|
|
768
975
|
next: [
|
|
769
|
-
`
|
|
770
|
-
|
|
976
|
+
`Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
|
|
977
|
+
'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
|
+
'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
|
+
skillMdPath
|
|
980
|
+
? `Registry disclosure was also embedded into ${path.relative(absoluteRoot, skillMdPath)} for restrictive hubs.`
|
|
981
|
+
: `If the hub rejects extra files, rerun with --embed-skill-md to place registry disclosure inside SKILL.md.`,
|
|
771
982
|
`Use ${path.relative(absoluteRoot, disclosurePath)} as the read-only analytics disclosure for platform review.`,
|
|
772
983
|
'Do not upload secrets, .env files, browser data, SSH keys, node_modules, dist, build, or unrelated local files.',
|
|
773
|
-
`If the Skill is published, record the public URL with: ${COMMAND_NAME} track . --event hub_upload_succeeded --hub
|
|
984
|
+
`If the Skill is published, record the public URL with: ${COMMAND_NAME} track . --event hub_upload_succeeded --hub <red|clawhub|skillhub|claude|dify|coze|generic> --platform-skill-url <url>`,
|
|
774
985
|
snippetResult
|
|
775
986
|
? `If the runtime supports HTTP/MCP/webhook, adapt ${path.relative(absoluteRoot, snippetPath)} into the Skill runtime.`
|
|
776
987
|
: 'Runtime snippet was skipped.'
|
|
@@ -806,7 +1017,8 @@ async function main() {
|
|
|
806
1017
|
} else {
|
|
807
1018
|
const lines = [
|
|
808
1019
|
`Wrote ${result.manifestPath}`,
|
|
809
|
-
`Wrote ${result.
|
|
1020
|
+
`Wrote ${result.handoffPath}`,
|
|
1021
|
+
...result.promptPaths.map((item) => `Wrote ${item.promptPath}`),
|
|
810
1022
|
`Wrote ${result.disclosurePath}`,
|
|
811
1023
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|
|
812
1024
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|