@xiashe/skill 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/xiashe-skill.mjs +125 -24
  2. package/package.json +1 -1
@@ -1,12 +1,12 @@
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.1';
9
+ const VERSION = '0.1.2';
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');
@@ -19,6 +19,9 @@ const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME ==
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,6 +70,7 @@ 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.
@@ -144,6 +148,26 @@ function safeSkillKey(value) {
144
148
  .replace(/^-+|-+$/g, '') || 'skill';
145
149
  }
146
150
 
151
+ function canonicalSignaturePayload(payload) {
152
+ return [
153
+ payload.schemaVersion || EVENT_SCHEMA_VERSION,
154
+ payload.eventType || '',
155
+ payload.idempotencyKey || '',
156
+ Number.isFinite(payload.occurredAt) ? String(Math.floor(payload.occurredAt)) : '',
157
+ payload.installationId || '',
158
+ payload.invocationId || '',
159
+ payload.hub || '',
160
+ payload.skillKey || ''
161
+ ].join('\n');
162
+ }
163
+
164
+ function signEventPayload(payload, signingSecret) {
165
+ if (!signingSecret) return undefined;
166
+ return createHmac('sha256', signingSecret)
167
+ .update(canonicalSignaturePayload(payload))
168
+ .digest('hex');
169
+ }
170
+
147
171
  async function readJsonFile(filePath) {
148
172
  if (!existsSync(filePath)) return null;
149
173
  try {
@@ -307,6 +331,11 @@ async function writeManifest(root, flags) {
307
331
  skillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
308
332
  publicToken,
309
333
  registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl, 800) || DEFAULT_REGISTRY_URL,
334
+ eventSchemaVersion: EVENT_SCHEMA_VERSION,
335
+ signing: {
336
+ mode: normalizeText(flags['signing-secret'], 20) ? 'optional' : 'off',
337
+ secretEnv: `${REGISTRY_PROVIDER.toUpperCase()}_SKILL_SIGNING_SECRET`
338
+ },
310
339
  analytics: {
311
340
  enabled: true,
312
341
  allowedEvents: [
@@ -349,6 +378,12 @@ function hubInstruction(hub) {
349
378
  if (hub === 'skillhub') {
350
379
  return 'SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
351
380
  }
381
+ if (hub === 'claude') {
382
+ return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
383
+ }
384
+ if (hub === 'dify') {
385
+ return 'Dify Plugin/Tool 的官方打包和发布流程是主流程。请先按 Dify 插件规范处理 manifest 和工具定义,再把下面的 registry 披露文本放入允许的 README/说明字段。';
386
+ }
352
387
  return '目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
353
388
  }
354
389
 
@@ -362,6 +397,12 @@ function platformPromptPlaceholder(hub) {
362
397
  if (hub === 'skillhub') {
363
398
  return '如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 SkillHub 官方流程。';
364
399
  }
400
+ if (hub === 'claude') {
401
+ return '如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用虾舍 prompt 替代 Claude 官方流程。';
402
+ }
403
+ if (hub === 'dify') {
404
+ return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
405
+ }
365
406
  return '如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代平台官方流程。';
366
407
  }
367
408
 
@@ -424,15 +465,18 @@ async function uploadPrompt(inspected, flags) {
424
465
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
425
466
  const eventPayload = {
426
467
  publicToken: registry.publicToken || '<xiashe public token from xiashe.skill.json>',
468
+ schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
427
469
  eventType: 'hub_upload_succeeded',
428
470
  hub,
429
471
  skillKey: inspected.skillKey,
472
+ idempotencyKey: '<hub-upload-succeeded-stable-key>',
430
473
  packageSha256: inspected.package.sha256,
431
474
  platformSkillUrl: '<published skill url>',
432
475
  scenario: '<short usage scenario if the hub asks for one>'
433
476
  };
434
477
  const runtimePayload = {
435
478
  publicToken: registry.publicToken || '<public token from registry manifest>',
479
+ schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
436
480
  eventType: 'skill_invoked',
437
481
  eventSource: 'runtime',
438
482
  runtimeKind: '<mcp|api|agent|webhook|platform>',
@@ -440,6 +484,7 @@ async function uploadPrompt(inspected, flags) {
440
484
  skillKey: inspected.skillKey,
441
485
  installationId: '<stable anonymous install id>',
442
486
  invocationId: '<unique invocation id>',
487
+ idempotencyKey: '<unique invocation id>:skill_invoked',
443
488
  scenario: '<short usage scenario label only>',
444
489
  sourceSurface: '<profile|hub|agent|direct>',
445
490
  campaign: '<optional promotion label>'
@@ -527,6 +572,7 @@ async function writeRuntimeSnippet(root, flags) {
527
572
  const registry = inspected.registry || {};
528
573
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
529
574
  const publicToken = registry.publicToken || '<public token from registry manifest>';
575
+ const eventSchemaVersion = registry.eventSchemaVersion || EVENT_SCHEMA_VERSION;
530
576
  const target = normalizeText(flags.target || 'js', 20).toLowerCase();
531
577
  const snippet = target === 'curl'
532
578
  ? [
@@ -534,6 +580,7 @@ async function writeRuntimeSnippet(root, flags) {
534
580
  " -H 'content-type: application/json' \\",
535
581
  ' -d ' + JSON.stringify(JSON.stringify({
536
582
  publicToken,
583
+ schemaVersion: eventSchemaVersion,
537
584
  eventType: 'skill_invoked',
538
585
  eventSource: 'runtime',
539
586
  runtimeKind: 'mcp',
@@ -541,31 +588,62 @@ async function writeRuntimeSnippet(root, flags) {
541
588
  skillKey: inspected.skillKey,
542
589
  installationId: '<anonymous-stable-install-id>',
543
590
  invocationId: '<unique-call-id>',
591
+ idempotencyKey: '<unique-call-id>:skill_invoked',
544
592
  scenario: '<short-scenario-label>'
545
593
  }))
546
594
  ].join('\n')
547
595
  : [
548
596
  `const XIASHE_SKILL_REGISTRY_URL = ${JSON.stringify(registryUrl)};`,
549
597
  `const XIASHE_SKILL_PUBLIC_TOKEN = ${JSON.stringify(publicToken)};`,
598
+ `const XIASHE_SKILL_EVENT_SCHEMA_VERSION = ${JSON.stringify(eventSchemaVersion)};`,
599
+ `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 || '';`,
600
+ '',
601
+ 'async function hmacSha256(secret, message) {',
602
+ ' if (!secret || !globalThis.crypto?.subtle) return undefined;',
603
+ " const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);",
604
+ ' const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));',
605
+ " return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, '0')).join('');",
606
+ '}',
607
+ '',
608
+ 'function canonicalSignaturePayload(payload) {',
609
+ ' return [',
610
+ ' payload.schemaVersion || XIASHE_SKILL_EVENT_SCHEMA_VERSION,',
611
+ " payload.eventType || '',",
612
+ " payload.idempotencyKey || '',",
613
+ " Number.isFinite(payload.occurredAt) ? String(Math.floor(payload.occurredAt)) : '',",
614
+ " payload.installationId || '',",
615
+ " payload.invocationId || '',",
616
+ " payload.hub || '',",
617
+ " payload.skillKey || ''",
618
+ " ].join('\\n');",
619
+ '}',
550
620
  '',
551
621
  'export async function trackSkillEvent(eventType, options = {}) {',
622
+ ' const occurredAt = options.occurredAt || Date.now();',
623
+ ' const invocationId = options.invocationId;',
624
+ ' const payload = {',
625
+ ' publicToken: XIASHE_SKILL_PUBLIC_TOKEN,',
626
+ ' schemaVersion: XIASHE_SKILL_EVENT_SCHEMA_VERSION,',
627
+ ' eventType,',
628
+ " eventSource: options.eventSource || 'runtime',",
629
+ " runtimeKind: options.runtimeKind || 'mcp',",
630
+ ` skillKey: ${JSON.stringify(inspected.skillKey)},`,
631
+ ' hub: options.hub,',
632
+ ' installationId: options.installationId,',
633
+ ' invocationId,',
634
+ ' idempotencyKey: options.idempotencyKey || `${invocationId || options.installationId || "runtime"}:${eventType}:${occurredAt}`,',
635
+ ' scenario: options.scenario,',
636
+ ' sourceSurface: options.sourceSurface,',
637
+ ' campaign: options.campaign,',
638
+ ' packageSha256: options.packageSha256,',
639
+ ' occurredAt',
640
+ ' };',
641
+ ' const signature = await hmacSha256(XIASHE_SKILL_SIGNING_SECRET, canonicalSignaturePayload(payload));',
642
+ ' if (signature) payload.signature = signature;',
552
643
  ' await fetch(XIASHE_SKILL_REGISTRY_URL, {',
553
644
  " method: 'POST',",
554
645
  " 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
- ' })',
646
+ ' body: JSON.stringify(payload)',
569
647
  ' });',
570
648
  '}',
571
649
  '',
@@ -595,22 +673,33 @@ async function submitTrackEvent(root, flags) {
595
673
  if (!eventType) {
596
674
  throw new Error('Missing --event.');
597
675
  }
676
+ const occurredAt = Number(flags['occurred-at'] || Date.now());
677
+ const invocationId = normalizeText(flags['invocation-id'] || `cli-${Date.now()}`, 160);
678
+ const installationId = normalizeText(flags['installation-id'] || 'local-test-install', 220);
679
+ const hub = normalizeText(flags.hub || 'local-test', 80);
598
680
  const payload = {
599
681
  publicToken,
682
+ schemaVersion: normalizeText(flags['schema-version'], 80) || EVENT_SCHEMA_VERSION,
600
683
  eventType,
601
684
  eventSource: normalizeText(flags['event-source'] || 'manual', 40),
602
685
  runtimeKind: normalizeText(flags['runtime-kind'] || 'cli-test', 80),
603
- hub: normalizeText(flags.hub || 'local-test', 80),
686
+ hub,
604
687
  skillKey: inspected.skillKey,
605
- installationId: normalizeText(flags['installation-id'] || 'local-test-install', 220),
606
- invocationId: normalizeText(flags['invocation-id'] || `cli-${Date.now()}`, 160),
688
+ installationId,
689
+ invocationId,
607
690
  scenario: normalizeText(flags.scenario || 'local-test', 120),
608
691
  sourceSurface: normalizeText(flags['source-surface'] || 'cli', 120),
609
692
  campaign: normalizeText(flags.campaign, 120) || undefined,
610
693
  platformSkillUrl: normalizeText(flags['platform-skill-url'], 1000) || undefined,
611
694
  packageSha256: inspected.package.sha256,
612
- idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) || undefined
695
+ idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) || `${invocationId}:${eventType}`,
696
+ occurredAt: Number.isFinite(occurredAt) ? occurredAt : Date.now()
613
697
  };
698
+ const signingSecret = normalizeText(flags['signing-secret'], 512);
699
+ const signature = signEventPayload(payload, signingSecret);
700
+ if (signature) {
701
+ payload.signature = signature;
702
+ }
614
703
  if (flags.dryRun) {
615
704
  return { ok: true, dryRun: true, registryUrl, payload };
616
705
  }
@@ -631,6 +720,15 @@ async function setupAgentWorkflow(root, flags) {
631
720
 
632
721
  const promptPath = path.join(outDir, `upload-${safeSkillKey(hub)}.md`);
633
722
  const promptResult = await writePrompt(absoluteRoot, { ...flags, hub, out: promptPath });
723
+ const inspectedAfterManifest = await inspectSkill(absoluteRoot, flags);
724
+ const disclosurePath = path.join(outDir, 'REGISTRY_DISCLOSURE.md');
725
+ await writeFile(
726
+ disclosurePath,
727
+ `${publicDisclosure(inspectedAfterManifest, inspectedAfterManifest.registry?.registryUrl || DEFAULT_REGISTRY_URL)
728
+ .filter((line) => line !== '公开披露文本:' && line !== '```markdown' && line !== '```')
729
+ .join('\n')}\n`,
730
+ { mode: 0o600 }
731
+ );
634
732
  const snippetPath = path.join(outDir, 'runtime-events.js');
635
733
  let snippetResult = null;
636
734
  if (!flags['no-snippet']) {
@@ -663,12 +761,14 @@ async function setupAgentWorkflow(root, flags) {
663
761
  hub,
664
762
  manifestPath: manifestResult.manifestPath,
665
763
  promptPath: typeof promptResult === 'string' ? promptPath : promptResult.outPath,
764
+ disclosurePath,
666
765
  snippetPath: snippetResult ? snippetResult.outPath : null,
667
766
  promptEvent,
668
767
  warnings,
669
768
  next: [
670
769
  `Open ${path.relative(absoluteRoot, promptPath)} and merge it with the official ${hub} upload prompt/CLI flow.`,
671
770
  `If ${hub} provides its own upload prompt or CLI, treat that official flow as authoritative.`,
771
+ `Use ${path.relative(absoluteRoot, disclosurePath)} as the read-only analytics disclosure for platform review.`,
672
772
  'Do not upload secrets, .env files, browser data, SSH keys, node_modules, dist, build, or unrelated local files.',
673
773
  `If the Skill is published, record the public URL with: ${COMMAND_NAME} track . --event hub_upload_succeeded --hub ${hub} --platform-skill-url <url>`,
674
774
  snippetResult
@@ -707,6 +807,7 @@ async function main() {
707
807
  const lines = [
708
808
  `Wrote ${result.manifestPath}`,
709
809
  `Wrote ${result.promptPath}`,
810
+ `Wrote ${result.disclosurePath}`,
710
811
  result.snippetPath ? `Wrote ${result.snippetPath}` : null,
711
812
  ...result.warnings.map((warning) => `Warning: ${warning}`),
712
813
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/skill",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"