@xiashe/skill 0.1.1 → 0.1.4
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/bin/xiashe-skill.mjs +187 -27
- package/package.json +1 -1
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
3
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
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.4';
|
|
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
20
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
21
21
|
const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_URL}/registry/skill/claim`;
|
|
22
|
+
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
23
|
+
? 'agentpie.skill.event.v1'
|
|
24
|
+
: 'xiashe.skill.event.v1');
|
|
22
25
|
const DEFAULT_IGNORE = new Set([
|
|
23
26
|
'.git',
|
|
24
27
|
'.xiashe',
|
|
@@ -41,11 +44,11 @@ function usage() {
|
|
|
41
44
|
|
|
42
45
|
Usage:
|
|
43
46
|
${COMMAND_NAME} inspect [skill-dir] [--json]
|
|
44
|
-
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> --hub <red|clawhub|skillhub|generic>
|
|
45
|
-
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> --hub <red|clawhub|skillhub|generic>
|
|
47
|
+
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> --hub <red|clawhub|skillhub|claude|dify|generic>
|
|
48
|
+
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> --hub <red|clawhub|skillhub|claude|dify|generic>
|
|
46
49
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
47
50
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
48
|
-
${COMMAND_NAME} prompt [skill-dir] --hub <red|clawhub|skillhub|generic> [--source-url <url>] [--out <file>]
|
|
51
|
+
${COMMAND_NAME} prompt [skill-dir] --hub <red|clawhub|skillhub|claude|dify|generic> [--source-url <url>] [--out <file>]
|
|
49
52
|
${COMMAND_NAME} snippet [skill-dir] [--target js|curl] [--out <file>]
|
|
50
53
|
${COMMAND_NAME} track [skill-dir] --event <event> [--hub <hub>] [--installation-id <id>] [--invocation-id <id>]
|
|
51
54
|
|
|
@@ -57,7 +60,7 @@ Options:
|
|
|
57
60
|
--registry-url <url> Event endpoint written to the manifest.
|
|
58
61
|
--name <name> Skill display name override.
|
|
59
62
|
--description <text> Skill description override.
|
|
60
|
-
--hub <hub> Target Skill hub prompt style: red, clawhub, skillhub, or generic.
|
|
63
|
+
--hub <hub> Target Skill hub prompt style: red, clawhub, skillhub, claude, dify, or generic.
|
|
61
64
|
--event <event> Runtime event to submit for testing.
|
|
62
65
|
--installation-id <id> Stable anonymous install id for runtime analytics.
|
|
63
66
|
--invocation-id <id> Unique invocation id for one skill call.
|
|
@@ -67,10 +70,13 @@ Options:
|
|
|
67
70
|
--platform-prompt-file <p> Official third-party hub prompt file to embed in the handoff.
|
|
68
71
|
--platform-command <cmd> Official third-party hub CLI/upload command to include.
|
|
69
72
|
--manifest-path <path> Registry manifest output path. Defaults to ${MANIFEST_FILE} for attach, ${WORK_DIR}/${MANIFEST_FILE} for setup.
|
|
73
|
+
--signing-secret <secret> Optional HMAC secret for signed runtime events.
|
|
70
74
|
--source-url <url> User-provided source URL an Agent can use during third-party upload.
|
|
71
75
|
--package-url <url> Deprecated alias for --source-url.
|
|
72
76
|
--out <path> Output prompt or snippet file.
|
|
73
77
|
--out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
|
|
78
|
+
--embed-skill-md Also write a clearly marked registry section into SKILL.md.
|
|
79
|
+
--no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
|
|
74
80
|
--no-snippet Setup should skip writing the runtime analytics snippet.
|
|
75
81
|
--no-track Setup should skip sending upload_prompt_generated.
|
|
76
82
|
--dry-run Print planned event payloads without network writes.
|
|
@@ -87,7 +93,7 @@ function fail(message, code = 1) {
|
|
|
87
93
|
|
|
88
94
|
function parseArgs(argv) {
|
|
89
95
|
const args = { _: [], json: false, dryRun: false };
|
|
90
|
-
const booleanFlags = new Set(['no-snippet', 'no-track']);
|
|
96
|
+
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track']);
|
|
91
97
|
for (let i = 0; i < argv.length; i += 1) {
|
|
92
98
|
const value = argv[i];
|
|
93
99
|
if (value === '--help' || value === '-h') {
|
|
@@ -144,6 +150,26 @@ function safeSkillKey(value) {
|
|
|
144
150
|
.replace(/^-+|-+$/g, '') || 'skill';
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
function canonicalSignaturePayload(payload) {
|
|
154
|
+
return [
|
|
155
|
+
payload.schemaVersion || EVENT_SCHEMA_VERSION,
|
|
156
|
+
payload.eventType || '',
|
|
157
|
+
payload.idempotencyKey || '',
|
|
158
|
+
Number.isFinite(payload.occurredAt) ? String(Math.floor(payload.occurredAt)) : '',
|
|
159
|
+
payload.installationId || '',
|
|
160
|
+
payload.invocationId || '',
|
|
161
|
+
payload.hub || '',
|
|
162
|
+
payload.skillKey || ''
|
|
163
|
+
].join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function signEventPayload(payload, signingSecret) {
|
|
167
|
+
if (!signingSecret) return undefined;
|
|
168
|
+
return createHmac('sha256', signingSecret)
|
|
169
|
+
.update(canonicalSignaturePayload(payload))
|
|
170
|
+
.digest('hex');
|
|
171
|
+
}
|
|
172
|
+
|
|
147
173
|
async function readJsonFile(filePath) {
|
|
148
174
|
if (!existsSync(filePath)) return null;
|
|
149
175
|
try {
|
|
@@ -307,6 +333,11 @@ async function writeManifest(root, flags) {
|
|
|
307
333
|
skillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
|
|
308
334
|
publicToken,
|
|
309
335
|
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
336
|
+
eventSchemaVersion: EVENT_SCHEMA_VERSION,
|
|
337
|
+
signing: {
|
|
338
|
+
mode: normalizeText(flags['signing-secret'], 20) ? 'optional' : 'off',
|
|
339
|
+
secretEnv: `${REGISTRY_PROVIDER.toUpperCase()}_SKILL_SIGNING_SECRET`
|
|
340
|
+
},
|
|
310
341
|
analytics: {
|
|
311
342
|
enabled: true,
|
|
312
343
|
allowedEvents: [
|
|
@@ -349,6 +380,12 @@ function hubInstruction(hub) {
|
|
|
349
380
|
if (hub === 'skillhub') {
|
|
350
381
|
return 'SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
|
|
351
382
|
}
|
|
383
|
+
if (hub === 'claude') {
|
|
384
|
+
return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
|
|
385
|
+
}
|
|
386
|
+
if (hub === 'dify') {
|
|
387
|
+
return 'Dify Plugin/Tool 的官方打包和发布流程是主流程。请先按 Dify 插件规范处理 manifest 和工具定义,再把下面的 registry 披露文本放入允许的 README/说明字段。';
|
|
388
|
+
}
|
|
352
389
|
return '目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
|
|
353
390
|
}
|
|
354
391
|
|
|
@@ -362,6 +399,12 @@ function platformPromptPlaceholder(hub) {
|
|
|
362
399
|
if (hub === 'skillhub') {
|
|
363
400
|
return '如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 SkillHub 官方流程。';
|
|
364
401
|
}
|
|
402
|
+
if (hub === 'claude') {
|
|
403
|
+
return '如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用虾舍 prompt 替代 Claude 官方流程。';
|
|
404
|
+
}
|
|
405
|
+
if (hub === 'dify') {
|
|
406
|
+
return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
|
|
407
|
+
}
|
|
365
408
|
return '如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代平台官方流程。';
|
|
366
409
|
}
|
|
367
410
|
|
|
@@ -401,6 +444,55 @@ function publicDisclosure(inspected, registryUrl) {
|
|
|
401
444
|
];
|
|
402
445
|
}
|
|
403
446
|
|
|
447
|
+
function skillMdRegistryBlock(inspected, hub) {
|
|
448
|
+
const registry = inspected.registry || {};
|
|
449
|
+
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
450
|
+
const publicToken = registry.publicToken || '<public token from registry manifest>';
|
|
451
|
+
const marker = `${REGISTRY_PROVIDER}-registry`;
|
|
452
|
+
return [
|
|
453
|
+
`<!-- ${marker}:start -->`,
|
|
454
|
+
`## ${PRODUCT_NAME} Registry / Analytics`,
|
|
455
|
+
'',
|
|
456
|
+
`This Skill is explicitly registered by its creator with ${PRODUCT_NAME} for public attribution and aggregated analytics.`,
|
|
457
|
+
'',
|
|
458
|
+
`- Registry provider: ${REGISTRY_PROVIDER}`,
|
|
459
|
+
`- Skill key: ${inspected.skillKey}`,
|
|
460
|
+
`- Target hub: ${hub}`,
|
|
461
|
+
`- Registry event endpoint: ${registryUrl}`,
|
|
462
|
+
`- Public write token: ${publicToken}`,
|
|
463
|
+
`- Event schema version: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
464
|
+
`- Source fingerprint: ${inspected.package.sha256}`,
|
|
465
|
+
'',
|
|
466
|
+
'Analytics boundary:',
|
|
467
|
+
'- The public token can only submit allowed aggregate analytics events. It cannot read creator data or administer the account.',
|
|
468
|
+
'- Events should include only public metadata: hub, sourceSurface, campaign, scenario, anonymous installationId, invocationId, and status.',
|
|
469
|
+
'- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
|
|
470
|
+
'- The creator can rotate/revoke the token or disable analytics from the dashboard.',
|
|
471
|
+
'',
|
|
472
|
+
'Recommended runtime events, if the target platform supports callbacks:',
|
|
473
|
+
'- `runtime_install_seen` when a stable anonymous installation is first observed.',
|
|
474
|
+
'- `skill_invoked` when a Skill invocation starts.',
|
|
475
|
+
'- `skill_completed` when an invocation completes.',
|
|
476
|
+
'- `skill_failed` when an invocation fails.',
|
|
477
|
+
'',
|
|
478
|
+
`<!-- ${marker}:end -->`
|
|
479
|
+
].join('\n');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function writeSkillMdRegistryBlock(root, inspected, hub) {
|
|
483
|
+
const skillMdPath = path.join(root, 'SKILL.md');
|
|
484
|
+
const existing = existsSync(skillMdPath)
|
|
485
|
+
? await readFile(skillMdPath, 'utf8')
|
|
486
|
+
: `# ${inspected.name}\n\n${inspected.description || ''}\n`;
|
|
487
|
+
const block = skillMdRegistryBlock(inspected, hub);
|
|
488
|
+
const pattern = /<!-- (?:xiashe|agentpie)-registry:start -->[\s\S]*?<!-- (?:xiashe|agentpie)-registry:end -->/m;
|
|
489
|
+
const next = pattern.test(existing)
|
|
490
|
+
? existing.replace(pattern, block)
|
|
491
|
+
: `${existing.trimEnd()}\n\n${block}\n`;
|
|
492
|
+
await writeFile(skillMdPath, next, { mode: 0o600 });
|
|
493
|
+
return skillMdPath;
|
|
494
|
+
}
|
|
495
|
+
|
|
404
496
|
async function readPlatformPrompt(flags) {
|
|
405
497
|
const promptPath = normalizeText(flags['platform-prompt-file'], 1000);
|
|
406
498
|
if (!promptPath) return '';
|
|
@@ -424,15 +516,18 @@ async function uploadPrompt(inspected, flags) {
|
|
|
424
516
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
425
517
|
const eventPayload = {
|
|
426
518
|
publicToken: registry.publicToken || '<xiashe public token from xiashe.skill.json>',
|
|
519
|
+
schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
|
|
427
520
|
eventType: 'hub_upload_succeeded',
|
|
428
521
|
hub,
|
|
429
522
|
skillKey: inspected.skillKey,
|
|
523
|
+
idempotencyKey: '<hub-upload-succeeded-stable-key>',
|
|
430
524
|
packageSha256: inspected.package.sha256,
|
|
431
525
|
platformSkillUrl: '<published skill url>',
|
|
432
526
|
scenario: '<short usage scenario if the hub asks for one>'
|
|
433
527
|
};
|
|
434
528
|
const runtimePayload = {
|
|
435
529
|
publicToken: registry.publicToken || '<public token from registry manifest>',
|
|
530
|
+
schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
|
|
436
531
|
eventType: 'skill_invoked',
|
|
437
532
|
eventSource: 'runtime',
|
|
438
533
|
runtimeKind: '<mcp|api|agent|webhook|platform>',
|
|
@@ -440,6 +535,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
440
535
|
skillKey: inspected.skillKey,
|
|
441
536
|
installationId: '<stable anonymous install id>',
|
|
442
537
|
invocationId: '<unique invocation id>',
|
|
538
|
+
idempotencyKey: '<unique invocation id>:skill_invoked',
|
|
443
539
|
scenario: '<short usage scenario label only>',
|
|
444
540
|
sourceSurface: '<profile|hub|agent|direct>',
|
|
445
541
|
campaign: '<optional promotion label>'
|
|
@@ -527,6 +623,7 @@ async function writeRuntimeSnippet(root, flags) {
|
|
|
527
623
|
const registry = inspected.registry || {};
|
|
528
624
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
529
625
|
const publicToken = registry.publicToken || '<public token from registry manifest>';
|
|
626
|
+
const eventSchemaVersion = registry.eventSchemaVersion || EVENT_SCHEMA_VERSION;
|
|
530
627
|
const target = normalizeText(flags.target || 'js', 20).toLowerCase();
|
|
531
628
|
const snippet = target === 'curl'
|
|
532
629
|
? [
|
|
@@ -534,6 +631,7 @@ async function writeRuntimeSnippet(root, flags) {
|
|
|
534
631
|
" -H 'content-type: application/json' \\",
|
|
535
632
|
' -d ' + JSON.stringify(JSON.stringify({
|
|
536
633
|
publicToken,
|
|
634
|
+
schemaVersion: eventSchemaVersion,
|
|
537
635
|
eventType: 'skill_invoked',
|
|
538
636
|
eventSource: 'runtime',
|
|
539
637
|
runtimeKind: 'mcp',
|
|
@@ -541,31 +639,62 @@ async function writeRuntimeSnippet(root, flags) {
|
|
|
541
639
|
skillKey: inspected.skillKey,
|
|
542
640
|
installationId: '<anonymous-stable-install-id>',
|
|
543
641
|
invocationId: '<unique-call-id>',
|
|
642
|
+
idempotencyKey: '<unique-call-id>:skill_invoked',
|
|
544
643
|
scenario: '<short-scenario-label>'
|
|
545
644
|
}))
|
|
546
645
|
].join('\n')
|
|
547
646
|
: [
|
|
548
647
|
`const XIASHE_SKILL_REGISTRY_URL = ${JSON.stringify(registryUrl)};`,
|
|
549
648
|
`const XIASHE_SKILL_PUBLIC_TOKEN = ${JSON.stringify(publicToken)};`,
|
|
649
|
+
`const XIASHE_SKILL_EVENT_SCHEMA_VERSION = ${JSON.stringify(eventSchemaVersion)};`,
|
|
650
|
+
`const XIASHE_SKILL_SIGNING_SECRET = ${JSON.stringify(normalizeText(flags['signing-secret'], 512))} || globalThis.process?.env?.XIASHE_SKILL_SIGNING_SECRET || globalThis.process?.env?.AGENTPIE_SKILL_SIGNING_SECRET || '';`,
|
|
651
|
+
'',
|
|
652
|
+
'async function hmacSha256(secret, message) {',
|
|
653
|
+
' if (!secret || !globalThis.crypto?.subtle) return undefined;',
|
|
654
|
+
" const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);",
|
|
655
|
+
' const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));',
|
|
656
|
+
" return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, '0')).join('');",
|
|
657
|
+
'}',
|
|
658
|
+
'',
|
|
659
|
+
'function canonicalSignaturePayload(payload) {',
|
|
660
|
+
' return [',
|
|
661
|
+
' payload.schemaVersion || XIASHE_SKILL_EVENT_SCHEMA_VERSION,',
|
|
662
|
+
" payload.eventType || '',",
|
|
663
|
+
" payload.idempotencyKey || '',",
|
|
664
|
+
" Number.isFinite(payload.occurredAt) ? String(Math.floor(payload.occurredAt)) : '',",
|
|
665
|
+
" payload.installationId || '',",
|
|
666
|
+
" payload.invocationId || '',",
|
|
667
|
+
" payload.hub || '',",
|
|
668
|
+
" payload.skillKey || ''",
|
|
669
|
+
" ].join('\\n');",
|
|
670
|
+
'}',
|
|
550
671
|
'',
|
|
551
672
|
'export async function trackSkillEvent(eventType, options = {}) {',
|
|
673
|
+
' const occurredAt = options.occurredAt || Date.now();',
|
|
674
|
+
' const invocationId = options.invocationId;',
|
|
675
|
+
' const payload = {',
|
|
676
|
+
' publicToken: XIASHE_SKILL_PUBLIC_TOKEN,',
|
|
677
|
+
' schemaVersion: XIASHE_SKILL_EVENT_SCHEMA_VERSION,',
|
|
678
|
+
' eventType,',
|
|
679
|
+
" eventSource: options.eventSource || 'runtime',",
|
|
680
|
+
" runtimeKind: options.runtimeKind || 'mcp',",
|
|
681
|
+
` skillKey: ${JSON.stringify(inspected.skillKey)},`,
|
|
682
|
+
' hub: options.hub,',
|
|
683
|
+
' installationId: options.installationId,',
|
|
684
|
+
' invocationId,',
|
|
685
|
+
' idempotencyKey: options.idempotencyKey || `${invocationId || options.installationId || "runtime"}:${eventType}:${occurredAt}`,',
|
|
686
|
+
' scenario: options.scenario,',
|
|
687
|
+
' sourceSurface: options.sourceSurface,',
|
|
688
|
+
' campaign: options.campaign,',
|
|
689
|
+
' packageSha256: options.packageSha256,',
|
|
690
|
+
' occurredAt',
|
|
691
|
+
' };',
|
|
692
|
+
' const signature = await hmacSha256(XIASHE_SKILL_SIGNING_SECRET, canonicalSignaturePayload(payload));',
|
|
693
|
+
' if (signature) payload.signature = signature;',
|
|
552
694
|
' await fetch(XIASHE_SKILL_REGISTRY_URL, {',
|
|
553
695
|
" method: 'POST',",
|
|
554
696
|
" headers: { 'content-type': 'application/json' },",
|
|
555
|
-
' body: JSON.stringify(
|
|
556
|
-
' publicToken: XIASHE_SKILL_PUBLIC_TOKEN,',
|
|
557
|
-
' eventType,',
|
|
558
|
-
" eventSource: options.eventSource || 'runtime',",
|
|
559
|
-
" runtimeKind: options.runtimeKind || 'mcp',",
|
|
560
|
-
` skillKey: ${JSON.stringify(inspected.skillKey)},`,
|
|
561
|
-
' hub: options.hub,',
|
|
562
|
-
' installationId: options.installationId,',
|
|
563
|
-
' invocationId: options.invocationId,',
|
|
564
|
-
' scenario: options.scenario,',
|
|
565
|
-
' sourceSurface: options.sourceSurface,',
|
|
566
|
-
' campaign: options.campaign,',
|
|
567
|
-
' packageSha256: options.packageSha256',
|
|
568
|
-
' })',
|
|
697
|
+
' body: JSON.stringify(payload)',
|
|
569
698
|
' });',
|
|
570
699
|
'}',
|
|
571
700
|
'',
|
|
@@ -595,22 +724,33 @@ async function submitTrackEvent(root, flags) {
|
|
|
595
724
|
if (!eventType) {
|
|
596
725
|
throw new Error('Missing --event.');
|
|
597
726
|
}
|
|
727
|
+
const occurredAt = Number(flags['occurred-at'] || Date.now());
|
|
728
|
+
const invocationId = normalizeText(flags['invocation-id'] || `cli-${Date.now()}`, 160);
|
|
729
|
+
const installationId = normalizeText(flags['installation-id'] || 'local-test-install', 220);
|
|
730
|
+
const hub = normalizeText(flags.hub || 'local-test', 80);
|
|
598
731
|
const payload = {
|
|
599
732
|
publicToken,
|
|
733
|
+
schemaVersion: normalizeText(flags['schema-version'], 80) || EVENT_SCHEMA_VERSION,
|
|
600
734
|
eventType,
|
|
601
735
|
eventSource: normalizeText(flags['event-source'] || 'manual', 40),
|
|
602
736
|
runtimeKind: normalizeText(flags['runtime-kind'] || 'cli-test', 80),
|
|
603
|
-
hub
|
|
737
|
+
hub,
|
|
604
738
|
skillKey: inspected.skillKey,
|
|
605
|
-
installationId
|
|
606
|
-
invocationId
|
|
739
|
+
installationId,
|
|
740
|
+
invocationId,
|
|
607
741
|
scenario: normalizeText(flags.scenario || 'local-test', 120),
|
|
608
742
|
sourceSurface: normalizeText(flags['source-surface'] || 'cli', 120),
|
|
609
743
|
campaign: normalizeText(flags.campaign, 120) || undefined,
|
|
610
744
|
platformSkillUrl: normalizeText(flags['platform-skill-url'], 1000) || undefined,
|
|
611
745
|
packageSha256: inspected.package.sha256,
|
|
612
|
-
idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) ||
|
|
746
|
+
idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) || `${invocationId}:${eventType}`,
|
|
747
|
+
occurredAt: Number.isFinite(occurredAt) ? occurredAt : Date.now()
|
|
613
748
|
};
|
|
749
|
+
const signingSecret = normalizeText(flags['signing-secret'], 512);
|
|
750
|
+
const signature = signEventPayload(payload, signingSecret);
|
|
751
|
+
if (signature) {
|
|
752
|
+
payload.signature = signature;
|
|
753
|
+
}
|
|
614
754
|
if (flags.dryRun) {
|
|
615
755
|
return { ok: true, dryRun: true, registryUrl, payload };
|
|
616
756
|
}
|
|
@@ -631,6 +771,19 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
631
771
|
|
|
632
772
|
const promptPath = path.join(outDir, `upload-${safeSkillKey(hub)}.md`);
|
|
633
773
|
const promptResult = await writePrompt(absoluteRoot, { ...flags, hub, out: promptPath });
|
|
774
|
+
const inspectedAfterManifest = await inspectSkill(absoluteRoot, flags);
|
|
775
|
+
const shouldEmbedSkillMd = Boolean(flags['embed-skill-md']) || (hub === 'red' && !flags['no-skill-md']);
|
|
776
|
+
const skillMdPath = shouldEmbedSkillMd
|
|
777
|
+
? await writeSkillMdRegistryBlock(absoluteRoot, inspectedAfterManifest, hub)
|
|
778
|
+
: null;
|
|
779
|
+
const disclosurePath = path.join(outDir, 'REGISTRY_DISCLOSURE.md');
|
|
780
|
+
await writeFile(
|
|
781
|
+
disclosurePath,
|
|
782
|
+
`${publicDisclosure(inspectedAfterManifest, inspectedAfterManifest.registry?.registryUrl || DEFAULT_REGISTRY_URL)
|
|
783
|
+
.filter((line) => line !== '公开披露文本:' && line !== '```markdown' && line !== '```')
|
|
784
|
+
.join('\n')}\n`,
|
|
785
|
+
{ mode: 0o600 }
|
|
786
|
+
);
|
|
634
787
|
const snippetPath = path.join(outDir, 'runtime-events.js');
|
|
635
788
|
let snippetResult = null;
|
|
636
789
|
if (!flags['no-snippet']) {
|
|
@@ -663,12 +816,18 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
663
816
|
hub,
|
|
664
817
|
manifestPath: manifestResult.manifestPath,
|
|
665
818
|
promptPath: typeof promptResult === 'string' ? promptPath : promptResult.outPath,
|
|
819
|
+
skillMdPath,
|
|
820
|
+
disclosurePath,
|
|
666
821
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
667
822
|
promptEvent,
|
|
668
823
|
warnings,
|
|
669
824
|
next: [
|
|
670
825
|
`Open ${path.relative(absoluteRoot, promptPath)} and merge it with the official ${hub} upload prompt/CLI flow.`,
|
|
671
826
|
`If ${hub} provides its own upload prompt or CLI, treat that official flow as authoritative.`,
|
|
827
|
+
skillMdPath
|
|
828
|
+
? `Registry disclosure was also embedded into ${path.relative(absoluteRoot, skillMdPath)} for restrictive hubs.`
|
|
829
|
+
: `If the hub rejects extra files, rerun with --embed-skill-md to place registry disclosure inside SKILL.md.`,
|
|
830
|
+
`Use ${path.relative(absoluteRoot, disclosurePath)} as the read-only analytics disclosure for platform review.`,
|
|
672
831
|
'Do not upload secrets, .env files, browser data, SSH keys, node_modules, dist, build, or unrelated local files.',
|
|
673
832
|
`If the Skill is published, record the public URL with: ${COMMAND_NAME} track . --event hub_upload_succeeded --hub ${hub} --platform-skill-url <url>`,
|
|
674
833
|
snippetResult
|
|
@@ -707,6 +866,7 @@ async function main() {
|
|
|
707
866
|
const lines = [
|
|
708
867
|
`Wrote ${result.manifestPath}`,
|
|
709
868
|
`Wrote ${result.promptPath}`,
|
|
869
|
+
`Wrote ${result.disclosurePath}`,
|
|
710
870
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|
|
711
871
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
712
872
|
'',
|