@xiashe/skill 0.1.22 → 0.1.24

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.
@@ -68,7 +68,7 @@ function isTransportError(error) {
68
68
  }
69
69
 
70
70
  const LOCAL_ENV_DEFAULTS = loadLocalEnvDefaults();
71
- const VERSION = process.env.XIASHE_SKILL_CLI_VERSION || '0.1.22';
71
+ const VERSION = process.env.XIASHE_SKILL_CLI_VERSION || '0.1.24';
72
72
  const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
73
73
  const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
74
74
  const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
@@ -98,12 +98,11 @@ const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME ==
98
98
  : 'xiashe.skill.json');
99
99
  const WORK_DIR = process.env.XIASHE_SKILL_WORK_DIR || (COMMAND_NAME === 'agentpie-skill' ? '.agentpie' : '.xiashe');
100
100
  const PUBLIC_PROTOCOL_DIR = process.env.XIASHE_SKILL_PUBLIC_PROTOCOL_DIR || (REGISTRY_PROVIDER === 'agentpie' ? 'agentpie' : 'xiashe');
101
+ const PUBLIC_PACKAGE_MANIFEST_FILE = 'package-manifest.json';
101
102
  const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
102
103
  const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
103
104
  const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
104
105
  const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_URL}/registry/skill/claim`;
105
- const DEFAULT_PACKAGE_UPLOAD_URL = process.env.XIASHE_SKILL_PACKAGE_UPLOAD_URL || `${DEFAULT_BASE_URL}/registry/skill/package-upload-url`;
106
- const DEFAULT_PACKAGE_ARTIFACT_URL = process.env.XIASHE_SKILL_PACKAGE_ARTIFACT_URL || `${DEFAULT_BASE_URL}/registry/skill/package-artifact`;
107
106
  const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
108
107
  ? 'agentpie.skill.event.v1'
109
108
  : 'xiashe.skill.event.v1');
@@ -165,8 +164,8 @@ const PLATFORM_REVIEW_PROFILES = {
165
164
  disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
166
165
  runtimeDataPolicy: 'runtime_capable',
167
166
  defaultDataSource: 'runtime',
168
- allowedFilePolicy: 'Upload the complete reviewed Skill package, including the local registry manifest.',
169
- forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
167
+ allowedFilePolicy: 'Upload the complete reviewed Skill package together with the generated no-secret package manifest. Keep the local registry manifest and runtime credentials on the creator device.',
168
+ forbiddenPublicFiles: [WORK_DIR, '.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys', 'public tokens', 'signing secrets']
170
169
  },
171
170
  xiashe: {
172
171
  label: 'XiaShe Store',
@@ -175,8 +174,8 @@ const PLATFORM_REVIEW_PROFILES = {
175
174
  disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
176
175
  runtimeDataPolicy: 'runtime_capable',
177
176
  defaultDataSource: 'runtime',
178
- allowedFilePolicy: 'Upload the complete reviewed Skill package, including the local registry manifest.',
179
- forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
177
+ allowedFilePolicy: 'Upload the complete reviewed Skill package together with the generated no-secret package manifest. Keep the local registry manifest and runtime credentials on the creator device.',
178
+ forbiddenPublicFiles: [WORK_DIR, '.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys', 'public tokens', 'signing secrets']
180
179
  },
181
180
  red: {
182
181
  label: 'Red Skill',
@@ -185,8 +184,8 @@ const PLATFORM_REVIEW_PROFILES = {
185
184
  disclosurePlacement: ['platform description field', 'README', 'SKILL.md', `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`, `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`],
186
185
  runtimeDataPolicy: 'agent_ack_supported',
187
186
  defaultDataSource: 'runtime',
188
- allowedFilePolicy: `Follow the official Red Skill uploader.md / skillhub-upload flow. Put public source, safety, creator-card attribution, and the no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml + ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md + ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md protocol files in the public package when Markdown/YAML files are accepted. Keep .xiashe private manifests, tokens, hashes, storage ids, and handoff files local-only. Send Agent Ack events only when the host can safely call HTTP/MCP.`,
189
- xiasheLocalOnlyFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
187
+ allowedFilePolicy: `Follow the official Red Skill uploader.md / skillhub-upload flow. Put public source, safety, creator-card attribution, and the no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml + ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md + ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md protocol files in the public package when Markdown/YAML files are accepted. Keep ${WORK_DIR} private manifests, tokens, hashes, storage ids, and handoff files local-only. Send Agent Ack events only when the host can safely call HTTP/MCP.`,
188
+ localOnlyRegistryFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
190
189
  forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
191
190
  safetyChecklist: [
192
191
  '功能描述真实、清晰、有实际使用价值,不夸大或误导用户。',
@@ -205,7 +204,7 @@ const PLATFORM_REVIEW_PROFILES = {
205
204
  disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
206
205
  runtimeDataPolicy: 'agent_ack_supported',
207
206
  defaultDataSource: 'runtime',
208
- allowedFilePolicy: 'Follow ClawHub official file rules. Treat XiaShe registry files as local-only unless ClawHub explicitly accepts them. Add public Agent Ack instructions where ClawHub allows Skill instructions.',
207
+ allowedFilePolicy: `Follow ClawHub official file rules. Treat ${PRODUCT_NAME} registry files as local-only unless ClawHub explicitly accepts them. Add public Agent Ack instructions where ClawHub allows Skill instructions.`,
209
208
  forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets']
210
209
  },
211
210
  skillhub: {
@@ -285,7 +284,7 @@ const PLATFORM_REVIEW_PROFILES = {
285
284
  disclosurePlacement: ['README', 'SKILL.md', 'platform description field'],
286
285
  runtimeDataPolicy: 'runtime_if_http_or_mcp',
287
286
  defaultDataSource: 'attribution',
288
- allowedFilePolicy: 'Follow the target platform official rules first; merge only safe XiaShe disclosure and callbacks.',
287
+ allowedFilePolicy: `Follow the target platform official rules first; merge only safe ${PRODUCT_NAME} disclosure and callbacks.`,
289
288
  forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
290
289
  }
291
290
  };
@@ -317,6 +316,7 @@ Usage:
317
316
  ${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|${FIRST_PARTY_HUB}|red]
318
317
  ${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|${FIRST_PARTY_HUB}|red]
319
318
  ${COMMAND_NAME} doctor [skill-dir] [--json]
319
+ ${COMMAND_NAME} preflight --claim-url <url> [--json]
320
320
  ${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
321
321
  ${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
322
322
  ${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
@@ -328,6 +328,7 @@ Options:
328
328
  --public-token <token> Public write token issued by ${PRODUCT_NAME} registry.
329
329
  --code <dynamic-code> One-time ${PRODUCT_NAME} dashboard code used to claim registry identity.
330
330
  --claim-url <url> Code claim endpoint. Defaults to XIASHE_SKILL_CLAIM_URL or ${DEFAULT_CLAIM_URL}
331
+ --registry-doctor-url <url> Registry preflight endpoint. Defaults to the claim URL origin.
331
332
  --skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
332
333
  --registry-url <url> Event endpoint written to the manifest.
333
334
  --agent-ack-url <url> No-secret Agent Ack endpoint written to the local manifest.
@@ -356,11 +357,13 @@ Options:
356
357
  --source-url <url> User-provided source URL an Agent can use during third-party upload.
357
358
  --package-url <url> Deprecated alias for --source-url.
358
359
  --skill-version <version> Override the Skill release version without editing package.json.
359
- --package-upload-url <url> Package upload ticket endpoint. Defaults to ${DEFAULT_PACKAGE_UPLOAD_URL}
360
- --package-artifact-url <url> Package artifact attach endpoint. Defaults to ${DEFAULT_PACKAGE_ARTIFACT_URL}
360
+ --package-upload-url <url> Package upload ticket endpoint on the same registry origin.
361
+ --package-artifact-url <url> Package artifact attach endpoint on the same registry origin.
361
362
  --out <path> Output prompt or snippet file.
362
363
  --out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
363
364
  --no-package-upload Skip automatic ${PRODUCT_NAME} complete package upload.
365
+ --paid-skill Treat third-party packages as protected paid-Skill connectors only.
366
+ --protected-skill Alias for --paid-skill. Never upload full paid prompt/source to third-party hubs.
364
367
  --embed-skill-md Also write a clearly marked registry section into SKILL.md.
365
368
  --no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
366
369
  --no-snippet Setup should skip writing the runtime analytics snippet.
@@ -379,7 +382,7 @@ function fail(message, code = 1) {
379
382
 
380
383
  function parseArgs(argv) {
381
384
  const args = { _: [], json: false, dryRun: false };
382
- const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track', 'no-package-upload', 'no-xiashe-package-upload']);
385
+ const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track', 'no-package-upload', 'no-xiashe-package-upload', 'paid-skill', 'protected-skill']);
383
386
  for (let i = 0; i < argv.length; i += 1) {
384
387
  const value = argv[i];
385
388
  if (value === '--help' || value === '-h') {
@@ -534,7 +537,7 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
534
537
 
535
538
  function creatorFooterTemplate(inspected, language = 'zh') {
536
539
  const registry = inspected.registry || {};
537
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
540
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
538
541
  const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '待创作者补充';
539
542
  const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80);
540
543
  if (language === 'en') {
@@ -632,8 +635,48 @@ function isLikelyEntrypointFile(filePath) {
632
635
  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);
633
636
  }
634
637
 
638
+ function truthyPaidValue(value) {
639
+ if (value === true) return true;
640
+ if (typeof value === 'number') return value > 0;
641
+ const text = normalizeText(value, 120).toLowerCase();
642
+ return [
643
+ 'paid',
644
+ 'protected',
645
+ 'requires_payment',
646
+ 'requires-entitlement',
647
+ 'entitlement',
648
+ 'alipay_ai_collect',
649
+ 'stripe',
650
+ 'charging',
651
+ '收费',
652
+ '付费'
653
+ ].includes(text);
654
+ }
655
+
656
+ function isProtectedPaidSkill(inspected) {
657
+ const registry = inspected.registry || {};
658
+ const candidates = [
659
+ inspected.isPaid,
660
+ inspected.requiresEntitlement,
661
+ inspected.billingModel,
662
+ inspected.monetizationMode,
663
+ inspected.accessMode,
664
+ registry.isPaid,
665
+ registry.paidAccess,
666
+ registry.requiresEntitlement,
667
+ registry.billingModel,
668
+ registry.monetizationMode,
669
+ registry.accessMode,
670
+ registry.payment?.required,
671
+ registry.payment?.channel
672
+ ];
673
+ return candidates.some(truthyPaidValue);
674
+ }
675
+
635
676
  function packagePlanForHub(inspected, hub) {
636
677
  const profile = reviewProfileForHub(hub);
678
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
679
+ const thirdPartyProtectedPackage = protectedPaidSkill && !['agentpie', 'xiashe'].includes(profile.hub);
637
680
  const localOnly = [];
638
681
  const candidateUploadFiles = [];
639
682
  const forbiddenPatterns = [
@@ -654,6 +697,14 @@ function packagePlanForHub(inspected, hub) {
654
697
  localOnly.push(file.path);
655
698
  continue;
656
699
  }
700
+ if (thirdPartyProtectedPackage) {
701
+ if (isPublicRuntimeProtocolFile(file.path) || /^README(\.[a-z0-9]+)?$/i.test(file.path)) {
702
+ candidateUploadFiles.push(file.path);
703
+ } else {
704
+ localOnly.push(file.path);
705
+ }
706
+ continue;
707
+ }
657
708
  if (profile.packageMode === 'full_skill_package') {
658
709
  candidateUploadFiles.push(file.path);
659
710
  continue;
@@ -683,6 +734,8 @@ function packagePlanForHub(inspected, hub) {
683
734
  return {
684
735
  hub: profile.hub,
685
736
  label: profile.label,
737
+ protectedPaidSkill,
738
+ thirdPartyProtectedPackage,
686
739
  reviewProfile: profile,
687
740
  dataSourcePolicy: {
688
741
  runtime: 'Only real Skill execution callbacks, MCP/HTTP/API Tool calls, webhooks, or external Agent ack.',
@@ -698,17 +751,24 @@ function packagePlanForHub(inspected, hub) {
698
751
  `${WORK_DIR}/runtime-events.js`,
699
752
  `${WORK_DIR}/platform-profiles.json`
700
753
  ])).sort(),
701
- disclosureTargets: profile.disclosurePlacement,
754
+ disclosureTargets: thirdPartyProtectedPackage
755
+ ? ['README', `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`, `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`, 'platform private/admin field']
756
+ : profile.disclosurePlacement,
702
757
  forbiddenPatterns,
703
758
  safetyChecklist: profile.safetyChecklist || [],
704
759
  requiresUserConfirmation: true,
705
760
  notes: [
761
+ thirdPartyProtectedPackage
762
+ ? `Protected paid Skill mode is active. Third-party packages must be connector/bootstrap only: do not upload the complete prompt, source package, ${WORK_DIR}/ files, package URLs, storage IDs, or install artifacts. The complete protected Skill package is delivered only through the ${PRODUCT_NAME} Gateway after entitlement is verified.`
763
+ : '',
706
764
  profile.allowedFilePolicy,
707
765
  profile.manifestPolicy === 'local_only'
708
- ? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include XiaShe metadata if the target platform explicitly accepts it and the user confirms.`
766
+ ? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include ${PRODUCT_NAME} metadata if the target platform explicitly accepts it and the user confirms.`
709
767
  : 'Confirm platform file rules before uploading auxiliary registry metadata.',
710
768
  hub === 'red'
711
- ? `For Red Skill, include only public source files plus no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md, and ${PUBLIC_PROTOCOL_DIR}/runtime.yaml when Markdown/YAML files are accepted. Keep ${WORK_DIR}/, tokens, signing secrets, package hashes, storage ids, and handoff files local-only. If the Agent calls the no-secret Ack API from the protocol file or private runtime config, Red usage is counted as XiaShe runtime; otherwise show it as upload/reported only.`
769
+ ? thirdPartyProtectedPackage
770
+ ? `For paid Red Skill distribution, publish only the ${PRODUCT_NAME} connector/bootstrap description and no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md. The external Agent must obtain a user-specific access session from ${PRODUCT_NAME}; after entitlement is confirmed, it must retrieve, verify, unpack, and read the complete protected Skill package locally. Do not mirror that package into Red.`
771
+ : `For Red Skill, include only public source files plus no-secret ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md, and ${PUBLIC_PROTOCOL_DIR}/runtime.yaml when Markdown/YAML files are accepted. Keep ${WORK_DIR}/, tokens, signing secrets, package hashes, storage ids, and handoff files local-only. If the Agent calls the no-secret Ack API from the protocol file or private runtime config, Red usage is counted as ${PRODUCT_NAME} runtime; otherwise show it as upload/reported only.`
712
772
  : '',
713
773
  profile.runtimeDataPolicy === 'attribution_only_by_default'
714
774
  ? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
@@ -770,12 +830,22 @@ async function inferSkillMetadata(root, flags = {}) {
770
830
  skillKey: safeSkillKey(flags['skill-key'] || manifest?.skillKey || packageJson?.name || name),
771
831
  description: normalizeText(flags.description || manifest?.description || packageJson?.description || firstParagraph || '', 500),
772
832
  version: normalizeText(flags['skill-version'] || packageJson?.version || manifest?.version || '0.1.0', 80),
833
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || manifest?.isPaid || manifest?.registry?.isPaid || manifest?.registry?.paidAccess),
834
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || manifest?.requiresEntitlement || manifest?.registry?.requiresEntitlement),
835
+ billingModel: normalizeText(flags['billing-model'] || manifest?.billingModel || manifest?.registry?.billingModel, 80) || null,
836
+ monetizationMode: normalizeText(flags['monetization-mode'] || manifest?.monetizationMode || manifest?.registry?.monetizationMode, 80) || null,
837
+ accessMode: normalizeText(flags['access-mode'] || manifest?.accessMode || manifest?.registry?.accessMode, 80) || null,
773
838
  registry: manifest?.registry || null
774
839
  };
775
840
  }
776
841
 
777
842
  function shouldIgnore(name) {
778
- return DEFAULT_IGNORE.has(name) || name.endsWith('~') || name.endsWith('.tmp') || name.endsWith('.log');
843
+ return DEFAULT_IGNORE.has(name) ||
844
+ name.startsWith('.env.') ||
845
+ /\.(pem|key)$/i.test(name) ||
846
+ name.endsWith('~') ||
847
+ name.endsWith('.tmp') ||
848
+ name.endsWith('.log');
779
849
  }
780
850
 
781
851
  async function walkFiles(root, current = root, out = []) {
@@ -821,6 +891,7 @@ async function postJson(url, body, timeoutMs = 20_000) {
821
891
  method: 'POST',
822
892
  headers: { 'content-type': 'application/json' },
823
893
  body: JSON.stringify(body),
894
+ redirect: 'error',
824
895
  signal: controller.signal
825
896
  });
826
897
  const text = await response.text();
@@ -845,13 +916,166 @@ async function postJson(url, body, timeoutMs = 20_000) {
845
916
  }
846
917
  }
847
918
 
848
- function urlForRegistryPath(baseUrl, pathname, fallback) {
849
- const base = normalizeText(baseUrl, 1_000) || fallback;
919
+ function registryClaimEndpoint(claimUrl) {
920
+ const raw = normalizeText(claimUrl, 1_000) || DEFAULT_CLAIM_URL;
921
+ let url;
922
+ try {
923
+ url = new URL(raw);
924
+ } catch {
925
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INVALID: claim URL is not a valid URL.');
926
+ }
927
+ const pathname = url.pathname.replace(/\/+$/, '');
928
+ if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || pathname !== '/registry/skill/claim') {
929
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INVALID: claim URL must be a clean https://<trusted-host>/registry/skill/claim endpoint.');
930
+ }
931
+ const host = url.hostname.toLowerCase();
932
+ const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
933
+ if (url.protocol !== 'https:' && !(isLocalhost && process.env.XIASHE_SKILL_ALLOW_INSECURE_LOCALHOST === '1')) {
934
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INSECURE: only HTTPS registry endpoints are accepted.');
935
+ }
936
+ return url;
937
+ }
938
+
939
+ function explicitClaimUrl(flags = {}) {
940
+ return normalizeText(
941
+ flags['claim-url'] ||
942
+ process.env.XIASHE_SKILL_CLAIM_URL ||
943
+ LOCAL_ENV_DEFAULTS.XIASHE_SKILL_CLAIM_URL,
944
+ 1_000
945
+ );
946
+ }
947
+
948
+ function claimUrlFromRegistryOrigin(registryOrigin) {
949
+ const origin = normalizeBaseUrl(registryOrigin);
950
+ return origin ? `${origin}/registry/skill/claim` : '';
951
+ }
952
+
953
+ function claimUrlForCode(flags = {}) {
954
+ const claimUrl = explicitClaimUrl(flags);
955
+ if (!claimUrl) {
956
+ throw new Error(
957
+ 'CREATOR_SKILL_CLAIM_URL_REQUIRED: a dynamic code must be used with the exact --claim-url issued by the dashboard. ' +
958
+ 'This prevents dev, CN, and global registry codes from being sent to the wrong environment.'
959
+ );
960
+ }
961
+ return claimUrl;
962
+ }
963
+
964
+ function claimUrlForExistingManifest(flags = {}, registry = {}) {
965
+ return (
966
+ explicitClaimUrl(flags) ||
967
+ claimUrlFromRegistryOrigin(registry.registryOrigin) ||
968
+ DEFAULT_CLAIM_URL
969
+ );
970
+ }
971
+
972
+ function configuredRegistryOrigins() {
973
+ const supplied = [
974
+ process.env.XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS,
975
+ LOCAL_ENV_DEFAULTS.XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS,
976
+ DEFAULT_BASE_URL
977
+ ]
978
+ .filter(Boolean)
979
+ .flatMap((value) => String(value).split(','))
980
+ .map((value) => normalizeBaseUrl(value))
981
+ .filter(Boolean)
982
+ .map((value) => new URL(value).origin.toLowerCase());
983
+ return new Set(supplied);
984
+ }
985
+
986
+ function isTrustedRegistryClaimUrl(claimUrl) {
987
+ const endpoint = registryClaimEndpoint(claimUrl);
988
+ const host = endpoint.hostname.toLowerCase();
989
+ const isOfficialHost =
990
+ host === 'xiashe.chat' || host.endsWith('.xiashe.chat') ||
991
+ host === 'agentpie.app' || host.endsWith('.agentpie.app');
992
+ const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
993
+ const trustedOrigins = configuredRegistryOrigins();
994
+ if (!isOfficialHost && !isLocalhost && !trustedOrigins.has(endpoint.origin.toLowerCase())) {
995
+ throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_UNTRUSTED: this dynamic code points to an untrusted registry origin. Use the exact dashboard command, or configure XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS for an approved self-hosted endpoint.');
996
+ }
997
+ return endpoint;
998
+ }
999
+
1000
+ async function getJson(url, timeoutMs = 12_000) {
1001
+ const controller = new AbortController();
1002
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1003
+ try {
1004
+ const response = await fetch(url, { method: 'GET', redirect: 'error', signal: controller.signal });
1005
+ const text = await response.text();
1006
+ let payload = {};
1007
+ try {
1008
+ payload = text ? JSON.parse(text) : {};
1009
+ } catch {
1010
+ throw new Error(`Bad JSON response from ${url}`);
1011
+ }
1012
+ if (!response.ok || payload.ok === false) {
1013
+ throw new Error(payload.error || payload.message || `HTTP ${response.status}`);
1014
+ }
1015
+ return payload;
1016
+ } catch (error) {
1017
+ const message = error instanceof Error ? error.message : String(error || 'unknown error');
1018
+ if (!isTransportError(error)) throw error;
1019
+ throw new Error(`Network request failed for ${url}: ${message}`);
1020
+ } finally {
1021
+ clearTimeout(timer);
1022
+ }
1023
+ }
1024
+
1025
+ async function preflightRegistryClaim(claimUrl, flags = {}) {
1026
+ const endpoint = isTrustedRegistryClaimUrl(claimUrl);
1027
+ const expectedOrigin = endpoint.origin.replace(/\/+$/, '');
1028
+ const doctorUrl = registryEndpointUrl(
1029
+ endpoint,
1030
+ '/registry/skill/doctor',
1031
+ flags['registry-doctor-url'],
1032
+ 'registry doctor'
1033
+ );
1034
+ const doctor = await getJson(doctorUrl, Number(flags['timeout-ms'] || 20_000));
1035
+ const registryOrigin = normalizeBaseUrl(doctor.registryOrigin);
1036
+ const protocolVersion = normalizeText(doctor.protocolVersion, 120);
1037
+ const environmentId = normalizeText(doctor.environmentId, 160);
1038
+ if (!registryOrigin || registryOrigin !== expectedOrigin) {
1039
+ throw new Error(`CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: claim endpoint ${expectedOrigin} does not match the registry doctor origin ${registryOrigin || 'missing'}. Do not consume this code; regenerate it from the same environment.`);
1040
+ }
1041
+ if (!protocolVersion || !environmentId) {
1042
+ throw new Error('CREATOR_SKILL_REGISTRY_DOCTOR_INCOMPLETE: update the registry backend before using a dynamic code.');
1043
+ }
1044
+ return {
1045
+ ok: true,
1046
+ claimUrl: endpoint.toString(),
1047
+ doctorUrl,
1048
+ registryOrigin,
1049
+ protocolVersion,
1050
+ environmentId,
1051
+ provider: normalizeText(doctor.provider, 80) || REGISTRY_PROVIDER
1052
+ };
1053
+ }
1054
+
1055
+ function registryEndpointUrl(claimEndpoint, pathname, suppliedUrl, label) {
1056
+ const endpoint = isTrustedRegistryClaimUrl(claimEndpoint);
1057
+ const expected = new URL(pathname, endpoint.origin);
1058
+ const raw = normalizeText(suppliedUrl, 1_000);
1059
+ if (!raw) return expected.toString();
1060
+ let candidate;
850
1061
  try {
851
- return new URL(pathname, base).toString();
1062
+ candidate = new URL(raw);
852
1063
  } catch {
853
- return fallback;
1064
+ throw new Error(`CREATOR_SKILL_REGISTRY_URL_INVALID: ${label} URL is not valid.`);
1065
+ }
1066
+ if (
1067
+ candidate.origin !== endpoint.origin ||
1068
+ candidate.pathname.replace(/\/+$/, '') !== expected.pathname.replace(/\/+$/, '') ||
1069
+ candidate.username ||
1070
+ candidate.password ||
1071
+ candidate.search ||
1072
+ candidate.hash
1073
+ ) {
1074
+ throw new Error(
1075
+ `CREATOR_SKILL_REGISTRY_ENDPOINT_MISMATCH: ${label} must stay on the exact registry origin and path bound to the dynamic code.`
1076
+ );
854
1077
  }
1078
+ return candidate.toString();
855
1079
  }
856
1080
 
857
1081
  function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false) {
@@ -867,11 +1091,16 @@ function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false)
867
1091
  if (entryName === '.env' || entryName.startsWith('.env.')) return true;
868
1092
  if (/\.(pem|key)$/i.test(entryName)) return true;
869
1093
  if (packageArtifactPrefix && normalized.startsWith(packageArtifactPrefix)) return true;
870
- if (isDirectory && (normalized === WORK_DIR || normalized === PUBLIC_PROTOCOL_DIR)) return false;
871
- if (isDirectory && (normalized.startsWith(`${WORK_DIR}/`) || normalized.startsWith(`${PUBLIC_PROTOCOL_DIR}/`))) return false;
872
- if ((normalized === WORK_DIR || normalized.startsWith(localRegistryPrefix)) && !isXiashePackageRegistryFile(normalized)) {
1094
+ // The local registry workspace carries the public token, signing key and
1095
+ // resumable-upload state. It is intentionally never part of an installable
1096
+ // archive, even for first-party distribution. A separate, no-secret
1097
+ // package manifest is written below `PUBLIC_PROTOCOL_DIR` instead.
1098
+ if (normalized === WORK_DIR || normalized.startsWith(localRegistryPrefix)) {
873
1099
  return true;
874
1100
  }
1101
+ if (normalized === PUBLIC_PROTOCOL_DIR || normalized.startsWith(`${PUBLIC_PROTOCOL_DIR}/`)) {
1102
+ return isDirectory ? false : !isXiashePackageRegistryFile(normalized);
1103
+ }
875
1104
  const otherRegistryDir = WORK_DIR === '.xiashe' ? '.agentpie' : '.xiashe';
876
1105
  if (normalized === otherRegistryDir || normalized.startsWith(`${otherRegistryDir}/`)) return true;
877
1106
  return false;
@@ -880,17 +1109,45 @@ function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false)
880
1109
  function isXiashePackageRegistryFile(relativePath) {
881
1110
  const normalized = String(relativePath || '').replace(/\\/g, '/');
882
1111
  const allowed = new Set([
883
- `${WORK_DIR}/${MANIFEST_FILE}`,
884
- `${WORK_DIR}/AGENT_ACK.md`,
885
- `${WORK_DIR}/REGISTRY_DISCLOSURE.md`,
886
- `${WORK_DIR}/runtime-events.js`,
887
1112
  `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`,
888
1113
  `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`,
889
- `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`
1114
+ `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`,
1115
+ `${PUBLIC_PROTOCOL_DIR}/${PUBLIC_PACKAGE_MANIFEST_FILE}`
890
1116
  ]);
891
1117
  return allowed.has(normalized);
892
1118
  }
893
1119
 
1120
+ function publicPackageManifestRelativePath() {
1121
+ return `${PUBLIC_PROTOCOL_DIR}/${PUBLIC_PACKAGE_MANIFEST_FILE}`;
1122
+ }
1123
+
1124
+ async function writePublicPackageManifest(root, inspected) {
1125
+ const registry = inspected.registry || {};
1126
+ const outputPath = path.join(root, PUBLIC_PROTOCOL_DIR, PUBLIC_PACKAGE_MANIFEST_FILE);
1127
+ const manifest = {
1128
+ schemaVersion: `${REGISTRY_PROVIDER}.skill.package.v1`,
1129
+ skill: {
1130
+ name: inspected.name,
1131
+ skillKey: inspected.skillKey,
1132
+ description: inspected.description || null,
1133
+ version: inspected.version || null,
1134
+ isPaid: Boolean(inspected.isPaid),
1135
+ requiresEntitlement: Boolean(inspected.requiresEntitlement)
1136
+ },
1137
+ creator: {
1138
+ cardUrl: normalizeText(registry.creatorCardUrl || registry.creatorProfile?.cardUrl, 800) || null
1139
+ },
1140
+ safety: {
1141
+ localRegistryStateIncluded: false,
1142
+ credentialsIncluded: false,
1143
+ signingMaterialIncluded: false
1144
+ }
1145
+ };
1146
+ await mkdir(path.dirname(outputPath), { recursive: true });
1147
+ await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o644 });
1148
+ return { outputPath, manifest };
1149
+ }
1150
+
894
1151
  async function walkXiashePackageFiles(root, current = root, out = []) {
895
1152
  const entries = await readdir(current, { withFileTypes: true });
896
1153
  for (const entry of entries) {
@@ -936,11 +1193,14 @@ function tarHeader(file, size) {
936
1193
  const header = Buffer.alloc(512, 0);
937
1194
  const split = splitTarPath(file.relative);
938
1195
  header.write(split.name, 0, 100, 'utf8');
939
- header.write(octal(file.mode & 0o777 || 0o644, 8), 100, 8, 'ascii');
1196
+ // Keep archive bytes reproducible. Local mtimes and group permissions are
1197
+ // machine-specific and must not make a valid retry look like a new package.
1198
+ const normalizedMode = file.mode & 0o111 ? 0o755 : 0o644;
1199
+ header.write(octal(normalizedMode, 8), 100, 8, 'ascii');
940
1200
  header.write(octal(0, 8), 108, 8, 'ascii');
941
1201
  header.write(octal(0, 8), 116, 8, 'ascii');
942
1202
  header.write(octal(size, 12), 124, 12, 'ascii');
943
- header.write(octal(Math.floor(new Date(file.mtime).getTime() / 1000), 12), 136, 12, 'ascii');
1203
+ header.write(octal(0, 12), 136, 12, 'ascii');
944
1204
  header.fill(0x20, 148, 156);
945
1205
  header.write('0', 156, 1, 'ascii');
946
1206
  header.write('ustar', 257, 6, 'ascii');
@@ -975,7 +1235,10 @@ async function createTarGz(files) {
975
1235
  }
976
1236
  chunks.push(Buffer.alloc(1024, 0));
977
1237
  const tar = Buffer.concat(chunks);
978
- const gzipped = await gzipAsync(tar, { level: 9 });
1238
+ // Node's current gzip implementation defaults to deterministic metadata,
1239
+ // but make the invariant explicit so retries on another host have the same
1240
+ // archive checksum when the files are unchanged.
1241
+ const gzipped = await gzipAsync(tar, { level: 9, mtime: 0 });
979
1242
  if (gzipped.length > MAX_XIASHE_PACKAGE_BYTES) {
980
1243
  throw new Error(`Package artifact exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
981
1244
  }
@@ -989,8 +1252,9 @@ async function createTarGz(files) {
989
1252
 
990
1253
  async function buildXiashePackageArtifact(root, inspected, flags = {}) {
991
1254
  const absoluteRoot = path.resolve(root || '.');
1255
+ await writePublicPackageManifest(absoluteRoot, inspected);
992
1256
  const files = await walkXiashePackageFiles(absoluteRoot);
993
- const manifestRelative = `${WORK_DIR}/${MANIFEST_FILE}`;
1257
+ const manifestRelative = publicPackageManifestRelativePath();
994
1258
  const skillMarkdownPresent = files.some((file) => /^SKILL(\.[a-z0-9]+)?$/i.test(file.relative));
995
1259
  const manifestPresent = files.some((file) => file.relative === manifestRelative);
996
1260
  if (!skillMarkdownPresent) {
@@ -1028,6 +1292,7 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
1028
1292
  method: 'POST',
1029
1293
  headers: { 'content-type': contentType || 'application/octet-stream' },
1030
1294
  body: bytes,
1295
+ redirect: 'error',
1031
1296
  signal: controller.signal
1032
1297
  });
1033
1298
  const text = await response.text();
@@ -1056,6 +1321,29 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
1056
1321
  }
1057
1322
  }
1058
1323
 
1324
+ async function getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags = {}) {
1325
+ return await postJson(
1326
+ registryEndpointUrl(claimUrl, '/registry/skill/package-status', flags['package-status-url'], 'package status'),
1327
+ {
1328
+ publicToken,
1329
+ uploadJobId: normalizeText(uploadJobId, 160) || undefined,
1330
+ idempotencyKey: normalizeText(idempotencyKey, 220) || undefined
1331
+ },
1332
+ Number(flags['timeout-ms'] || 20_000)
1333
+ );
1334
+ }
1335
+
1336
+ async function waitForPackagePublish(claimUrl, publicToken, uploadJobId, idempotencyKey, flags = {}) {
1337
+ const timeoutMs = Math.max(5_000, Math.min(90_000, Number(flags['publish-status-timeout-ms'] || 45_000)));
1338
+ const deadline = Date.now() + timeoutMs;
1339
+ let status = await getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags);
1340
+ while (status.found && (status.state === 'queued' || status.state === 'processing') && Date.now() < deadline) {
1341
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
1342
+ status = await getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags);
1343
+ }
1344
+ return status;
1345
+ }
1346
+
1059
1347
  async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1060
1348
  if (flags.dryRun || flags['no-package-upload'] || flags['no-xiashe-package-upload']) {
1061
1349
  return {
@@ -1075,32 +1363,86 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1075
1363
  normalizeText(manifestResult.manifest?.sourceFingerprint?.sha256, 128) ||
1076
1364
  normalizeText(inspected.package?.sha256, 128) ||
1077
1365
  artifact.sourceSha256;
1078
- const claimUrl = normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL;
1366
+ // A resumed upload has no dynamic code to protect it. Bind it back to the
1367
+ // origin recorded in the private manifest instead of silently falling back
1368
+ // to the production registry, then verify that origin before any write.
1369
+ const claimUrl = claimUrlForExistingManifest(flags, registry);
1370
+ const registryPreflight = await preflightRegistryClaim(claimUrl, flags);
1371
+ const expectedRegistryOrigin = normalizeBaseUrl(registry.registryOrigin);
1372
+ const expectedEnvironmentId = normalizeText(registry.environmentId, 160);
1373
+ if (
1374
+ (expectedRegistryOrigin && expectedRegistryOrigin !== registryPreflight.registryOrigin) ||
1375
+ (expectedEnvironmentId && expectedEnvironmentId !== registryPreflight.environmentId)
1376
+ ) {
1377
+ throw new Error(
1378
+ 'CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the local manifest belongs to a different registry environment. ' +
1379
+ 'Do not upload or claim a new code; switch to the original environment or start a new dashboard task.'
1380
+ );
1381
+ }
1079
1382
  const uploadTicket =
1080
1383
  manifestResult.claimPackageArtifactUpload ||
1081
1384
  await postJson(
1082
- normalizeText(flags['package-upload-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-upload-url', DEFAULT_PACKAGE_UPLOAD_URL),
1385
+ registryEndpointUrl(claimUrl, '/registry/skill/package-upload-url', flags['package-upload-url'], 'package upload'),
1083
1386
  {
1084
1387
  publicToken,
1085
1388
  idempotencyKey: registrySourceSha256
1086
1389
  },
1087
1390
  Number(flags['timeout-ms'] || 20_000)
1088
1391
  );
1089
- const uploadUrl = normalizeText(uploadTicket?.uploadUrl, 2_000);
1392
+ let activeUploadTicket = uploadTicket;
1393
+ let uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
1090
1394
  if (!uploadUrl) {
1091
- throw new Error('XiaShe package upload URL was not returned by registry.');
1395
+ const priorStatus = await waitForPackagePublish(
1396
+ claimUrl,
1397
+ publicToken,
1398
+ normalizeText(activeUploadTicket?.uploadJobId, 160),
1399
+ registrySourceSha256,
1400
+ flags
1401
+ );
1402
+ if (priorStatus.found && priorStatus.state === 'succeeded') {
1403
+ return {
1404
+ ok: true,
1405
+ skipped: false,
1406
+ resumed: true,
1407
+ artifactPath: artifact.artifactPath,
1408
+ fileName: artifact.fileName,
1409
+ sizeBytes: artifact.sizeBytes,
1410
+ sourceSha256: registrySourceSha256,
1411
+ archiveSourceSha256: artifact.sourceSha256,
1412
+ artifactSha256: artifact.artifactSha256,
1413
+ fileCount: artifact.fileCount,
1414
+ storageId: priorStatus.storageId ?? null,
1415
+ publishStatus: priorStatus.status,
1416
+ uploadJobId: priorStatus.uploadJobId,
1417
+ attachResult: priorStatus
1418
+ };
1419
+ }
1420
+ if (priorStatus.found && (priorStatus.state === 'queued' || priorStatus.state === 'processing')) {
1421
+ throw new Error(`${PRODUCT_NAME} package publish is still processing (job ${priorStatus.uploadJobId}). Run the same publish command again to resume; it will not create a second job.`);
1422
+ }
1423
+ activeUploadTicket = await postJson(
1424
+ registryEndpointUrl(claimUrl, '/registry/skill/package-upload-url', flags['package-upload-url'], 'package upload'),
1425
+ { publicToken, idempotencyKey: registrySourceSha256 },
1426
+ Number(flags['timeout-ms'] || 20_000)
1427
+ );
1428
+ uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
1429
+ if (!uploadUrl) {
1430
+ throw new Error(`${PRODUCT_NAME} package upload cannot resume yet. Query /registry/skill/package-status with the existing publicToken and uploadJobId; do not claim a new code.`);
1431
+ }
1092
1432
  }
1093
1433
  const bytes = await readFile(artifact.artifactPath);
1094
1434
  const uploaded = await uploadBytesToStorage(uploadUrl, bytes, artifact.contentType, Number(flags['upload-timeout-ms'] || 90_000));
1095
1435
  const attachResult = await postJson(
1096
- normalizeText(flags['package-artifact-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-artifact', DEFAULT_PACKAGE_ARTIFACT_URL),
1436
+ registryEndpointUrl(claimUrl, '/registry/skill/package-artifact', flags['package-artifact-url'], 'package artifact'),
1097
1437
  {
1098
1438
  publicToken,
1099
1439
  storageId: uploaded.storageId,
1100
- uploadJobId: normalizeText(uploadTicket?.uploadJobId, 160) || undefined,
1440
+ uploadJobId: normalizeText(activeUploadTicket?.uploadJobId, 160) || undefined,
1101
1441
  idempotencyKey: registrySourceSha256,
1102
1442
  fileName: artifact.fileName,
1103
1443
  contentType: artifact.contentType,
1444
+ sourceSha256: registrySourceSha256,
1445
+ artifactSha256: artifact.artifactSha256,
1104
1446
  packageSha256: registrySourceSha256,
1105
1447
  packageFileCount: artifact.fileCount,
1106
1448
  packageTotalBytes: artifact.sourceTotalBytes,
@@ -1113,6 +1455,16 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1113
1455
  },
1114
1456
  Number(flags['timeout-ms'] || 20_000)
1115
1457
  );
1458
+ const finalStatus = await waitForPackagePublish(
1459
+ claimUrl,
1460
+ publicToken,
1461
+ normalizeText(attachResult.uploadJobId || activeUploadTicket?.uploadJobId, 160),
1462
+ registrySourceSha256,
1463
+ flags
1464
+ );
1465
+ if (finalStatus.found && finalStatus.state === 'failed') {
1466
+ throw new Error(`${PRODUCT_NAME} package publish failed: ${normalizeText(finalStatus.lastError, 500) || 'unknown validation error'}. Rerun the same command after correcting the reported package issue; do not claim a new code.`);
1467
+ }
1116
1468
  return {
1117
1469
  ok: true,
1118
1470
  skipped: false,
@@ -1120,13 +1472,14 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1120
1472
  fileName: artifact.fileName,
1121
1473
  sizeBytes: artifact.sizeBytes,
1122
1474
  sourceSha256: registrySourceSha256,
1123
- packageSourceSha256: artifact.sourceSha256,
1475
+ archiveSourceSha256: artifact.sourceSha256,
1124
1476
  artifactSha256: artifact.artifactSha256,
1125
1477
  fileCount: artifact.fileCount,
1126
1478
  storageId: uploaded.storageId,
1127
- publishStatus: attachResult.publishStatus ?? null,
1128
- uploadJobId: attachResult.uploadJobId ?? normalizeText(uploadTicket?.uploadJobId, 160) ?? null,
1129
- attachResult
1479
+ publishStatus: finalStatus.status ?? attachResult.publishStatus ?? null,
1480
+ uploadJobId: attachResult.uploadJobId ?? normalizeText(activeUploadTicket?.uploadJobId, 160) ?? null,
1481
+ attachResult,
1482
+ finalStatus
1130
1483
  };
1131
1484
  }
1132
1485
 
@@ -1155,9 +1508,19 @@ async function inspectSkill(root, flags = {}) {
1155
1508
  async function writeManifest(root, flags) {
1156
1509
  const inspected = await inspectSkill(root, flags);
1157
1510
  const code = normalizeText(flags.code, 80);
1511
+ const existingRegistry = inspected.registry || {};
1512
+ const suppliedPublicToken = normalizeText(flags['public-token'], 512);
1513
+ if (!code && suppliedPublicToken && !explicitClaimUrl(flags) && !claimUrlFromRegistryOrigin(existingRegistry.registryOrigin)) {
1514
+ throw new Error(
1515
+ 'CREATOR_SKILL_CLAIM_URL_REQUIRED: a new manifest created from --public-token must include --claim-url. ' +
1516
+ 'Existing manifests resume through their saved registry origin.'
1517
+ );
1518
+ }
1519
+ const claimUrl = code ? claimUrlForCode(flags) : claimUrlForExistingManifest(flags, existingRegistry);
1520
+ const registryPreflight = code ? await preflightRegistryClaim(claimUrl, flags) : null;
1158
1521
  const claim = code
1159
1522
  ? await postJson(
1160
- normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL,
1523
+ registryPreflight.claimUrl,
1161
1524
  {
1162
1525
  code,
1163
1526
  skill: {
@@ -1165,6 +1528,9 @@ async function writeManifest(root, flags) {
1165
1528
  skillKey: inspected.skillKey,
1166
1529
  description: inspected.description,
1167
1530
  version: inspected.version,
1531
+ sourceSha256: inspected.package.sha256,
1532
+ // Compatibility with registries published before v2. The value is
1533
+ // still a source fingerprint, never a compressed archive hash.
1168
1534
  packageSha256: inspected.package.sha256,
1169
1535
  packageFileCount: inspected.package.fileCount,
1170
1536
  packageTotalBytes: inspected.package.totalBytes
@@ -1177,7 +1543,13 @@ async function writeManifest(root, flags) {
1177
1543
  Number(flags['timeout-ms'] || 20_000)
1178
1544
  )
1179
1545
  : null;
1180
- const existingRegistry = inspected.registry || {};
1546
+ if (claim && registryPreflight) {
1547
+ const claimEnvironment = normalizeText(claim.registry?.environmentId, 160);
1548
+ const claimOrigin = normalizeBaseUrl(claim.registry?.registryOrigin);
1549
+ if (claimEnvironment !== registryPreflight.environmentId || claimOrigin !== registryPreflight.registryOrigin) {
1550
+ throw new Error('CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the claimed Skill identity was returned by a different registry environment. No package upload was attempted.');
1551
+ }
1552
+ }
1181
1553
  const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
1182
1554
  if (!publicToken) {
1183
1555
  throw new Error('Missing --code or --public-token.');
@@ -1204,10 +1576,23 @@ async function writeManifest(root, flags) {
1204
1576
  skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
1205
1577
  description: normalizeText(claim?.description, 500) || inspected.description,
1206
1578
  version: normalizeText(claim?.version, 80) || inspected.version,
1579
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
1580
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.requiresEntitlement || existingRegistry.requiresEntitlement),
1581
+ billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
1582
+ monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
1583
+ accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
1207
1584
  registry: {
1208
1585
  provider: REGISTRY_PROVIDER,
1586
+ protocolVersion: normalizeText(claim?.registry?.protocolVersion || registryPreflight?.protocolVersion, 120) || null,
1587
+ environmentId: normalizeText(claim?.registry?.environmentId || registryPreflight?.environmentId, 160) || null,
1588
+ registryOrigin: normalizeBaseUrl(claim?.registry?.registryOrigin || registryPreflight?.registryOrigin) || null,
1209
1589
  skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
1210
1590
  publicToken,
1591
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
1592
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.requiresEntitlement || existingRegistry.requiresEntitlement),
1593
+ billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
1594
+ monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
1595
+ accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
1211
1596
  registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || DEFAULT_REGISTRY_URL,
1212
1597
  agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
1213
1598
  creatorCardUrl,
@@ -1282,25 +1667,26 @@ async function writeManifest(root, flags) {
1282
1667
  ok: true,
1283
1668
  manifestPath,
1284
1669
  manifest,
1285
- claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
1670
+ claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null,
1671
+ registryPreflight
1286
1672
  };
1287
1673
  }
1288
1674
 
1289
1675
  function hubInstruction(hub) {
1290
1676
  if (hub === 'agentpie') {
1291
- return 'AgentPie Registry publish is the full Skill package and registry manifest flow. Keep public disclosure, attribution, and runtime analytics callbacks; if the local manifest has a registry public token or a creator-authenticated CLI/API session exists, create or reuse the AgentPie registry draft.';
1677
+ return 'AgentPie Registry publish is the full Skill package flow. The local registry manifest stays on the creator device; the installable package contains only the generated no-secret package manifest. Keep public disclosure, attribution, and runtime analytics callbacks; if a creator-authenticated CLI/API session exists, create or reuse the AgentPie registry draft.';
1292
1678
  }
1293
1679
  if (hub === 'xiashe') {
1294
- return '虾舍商店发布是完整 Skill 包和 registry manifest 的主流程。请保留公开披露、归因和 runtime analytics 回传能力;如果本地 manifest registry public token 或已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
1680
+ return '虾舍商店发布是完整 Skill 包的主流程。本地 registry manifest 仅保留在创作者设备;安装包只包含自动生成的不含敏感信息的 package manifest。请保留公开披露、归因和 runtime analytics 回传能力;如果已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
1295
1681
  }
1296
1682
  if (hub === 'red') {
1297
- return 'Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;虾舍负责在 SKILL.md 顶部加入公开来源与安全使用说明,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。';
1683
+ return `Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;${PRODUCT_NAME} 负责生成平台安全披露、connector/运行时协议和本地 registry handoff,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。`;
1298
1684
  }
1299
1685
  if (hub === 'clawhub') {
1300
- return 'ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1686
+ return `ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1301
1687
  }
1302
1688
  if (hub === 'skillhub') {
1303
- return 'SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1689
+ return `SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1304
1690
  }
1305
1691
  if (hub === 'claude') {
1306
1692
  return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
@@ -1320,7 +1706,7 @@ function hubInstruction(hub) {
1320
1706
  if (hub === 'coze') {
1321
1707
  return 'Coze Agent/Plugin/Workflow 的官方配置和发布流程是主流程。请先按 Coze 当前工具、插件或知识库要求配置,再把下面的 registry 回调作为可选 HTTP/API 步骤接入。';
1322
1708
  }
1323
- return '目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1709
+ return `目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1324
1710
  }
1325
1711
 
1326
1712
  function platformPromptPlaceholder(hub) {
@@ -1331,64 +1717,66 @@ function platformPromptPlaceholder(hub) {
1331
1717
  return '如果用户要发布到虾舍商店,并且当前环境已有创作者认证 CLI/API 会话,请优先创建虾舍商店草稿;如果没有认证会话,请完成 registry setup、披露、平台 profile 和 handoff 文件,不要要求 Agent 宿主登录或索取账号凭证。不要把第三方平台限制套用到虾舍完整 Skill 包。';
1332
1718
  }
1333
1719
  if (hub === 'red') {
1334
- return '如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用虾舍 prompt 替代 Red Skill 官方流程。';
1720
+ return `如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用 ${PRODUCT_NAME} prompt 替代 Red Skill 官方流程。`;
1335
1721
  }
1336
1722
  if (hub === 'clawhub') {
1337
- return '如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 ClawHub 官方流程。';
1723
+ return `如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 ClawHub 官方流程。`;
1338
1724
  }
1339
1725
  if (hub === 'skillhub') {
1340
- return '如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 SkillHub 官方流程。';
1726
+ return `如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 SkillHub 官方流程。`;
1341
1727
  }
1342
1728
  if (hub === 'claude') {
1343
- return '如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用虾舍 prompt 替代 Claude 官方流程。';
1729
+ return `如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Claude 官方流程。`;
1344
1730
  }
1345
1731
  if (hub === 'codex') {
1346
- return '如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Codex 官方流程。';
1732
+ return `如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Codex 官方流程。`;
1347
1733
  }
1348
1734
  if (hub === 'cursor') {
1349
- return '如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Cursor 官方流程。';
1735
+ return `如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Cursor 官方流程。`;
1350
1736
  }
1351
1737
  if (hub === 'workbuddy') {
1352
- return '如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用虾舍 prompt 替代 WorkBuddy 官方流程。';
1738
+ return `如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用 ${PRODUCT_NAME} prompt 替代 WorkBuddy 官方流程。`;
1353
1739
  }
1354
1740
  if (hub === 'dify') {
1355
- return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
1741
+ return `如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用 ${PRODUCT_NAME} prompt 替代 Dify 官方流程。`;
1356
1742
  }
1357
1743
  if (hub === 'coze') {
1358
- return '如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用虾舍 prompt 替代 Coze 官方流程。';
1744
+ return `如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用 ${PRODUCT_NAME} prompt 替代 Coze 官方流程。`;
1359
1745
  }
1360
- return '如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代平台官方流程。';
1746
+ return `如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代平台官方流程。`;
1361
1747
  }
1362
1748
 
1363
- function uploadCompatibilityLines(hub, manifestFile) {
1749
+ function uploadCompatibilityLines(hub, manifestFile, protectedPaidSkill = false) {
1364
1750
  const localManifest = `${WORK_DIR}/${manifestFile}`;
1365
1751
  if (hub === 'agentpie') {
1366
1752
  return [
1367
1753
  'AgentPie Registry compatibility requirements:',
1368
- `- AgentPie Registry can receive the complete Skill package and ${localManifest} registry manifest.`,
1369
- '- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the registry manifest.',
1754
+ `- AgentPie Registry receives the complete Skill package. Keep ${localManifest} on the creator device; it is not an installable-package file.`,
1755
+ '- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the generated public protocol files.',
1370
1756
  '- Before submitting a draft, still exclude .env, keys, node_modules, dist/build, and unrelated local files.'
1371
1757
  ];
1372
1758
  }
1373
1759
  if (hub === 'xiashe') {
1374
1760
  return [
1375
1761
  '虾舍商店兼容性要求:',
1376
- `- 虾舍商店可以接收完整 Skill 包和 ${localManifest} registry manifest。`,
1377
- '- 保留公开披露文本、归因链接和 runtime event callback;不要删除 registry manifest。',
1762
+ `- 虾舍商店接收完整 Skill 包;${localManifest} 仅保留在创作者设备,不属于安装包。`,
1763
+ '- 保留公开披露文本、归因链接和 runtime event callback;不要删除生成的公开协议文件。',
1378
1764
  '- 提交草稿前仍需排除 .env、密钥、node_modules、dist/build 和无关本地文件。'
1379
1765
  ];
1380
1766
  }
1381
1767
  if (hub === 'red') {
1382
1768
  return [
1383
1769
  'Red Skill 兼容性要求:',
1384
- '- 以小红书官方 `uploader.md`、`skillhub-upload` 或页面上传流程为准;不要用虾舍 prompt 替代官方流程。',
1385
- `- 上传包必须在 SKILL.md 最开头写清楚 ${PRODUCT_NAME} Required Runtime Contract:更新检查、创作者名片、强制 footer、公开来源和安全使用边界。`,
1770
+ `- 以小红书官方 \`uploader.md\`、\`skillhub-upload\` 或页面上传流程为准;不要用 ${PRODUCT_NAME} prompt 替代官方流程。`,
1771
+ protectedPaidSkill
1772
+ ? `- 付费受保护 Skill 的 Red 公开包只能是 connector/bootstrap:写清楚 ${PRODUCT_NAME} Gateway 接入、付费访问边界、创作者名片和安全使用边界,不上传完整 SKILL.md/prompt/source。`
1773
+ : `- 上传包必须在 SKILL.md 最开头写清楚 ${PRODUCT_NAME} Required Runtime Contract:更新检查、创作者名片、强制 footer、公开来源与安全使用边界。`,
1386
1774
  `- 如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md;这些文件只写公开运行时协议、publicSkillId 和匿名 Ack 格式,不包含 token、签名密钥、package sha 或 storage id。`,
1387
1775
  '- 不要要求用户可见回复追加广告占位、telemetry JSON、包指纹、后台字段或本地路径。',
1388
- `- ${localManifest} 默认只保留在用户本地,用于虾舍 registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
1389
- '- public token、signing secret、registry id、package sha、storage id、runtime-events.js、.xiashe 内部 handoff/checklist 不进入 Red 公开介绍或公开包。',
1776
+ `- ${localManifest} 默认只保留在用户本地,用于 ${PRODUCT_NAME} registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
1777
+ `- public token、signing secret、registry id、package sha、storage id、runtime-events.js、${WORK_DIR} 内部 handoff/checklist 不进入 Red 公开介绍或公开包。`,
1390
1778
  '- publicSkillId 与 Agent Ack endpoint 只能出现在专门的 no-secret 协议文件或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。',
1391
- '- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,虾舍才会记录 installed / used / completed / failed 真实统计。',
1779
+ `- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,${PRODUCT_NAME} 才会记录 installed / used / completed / failed 真实统计。`,
1392
1780
  '- 如果 Red 平台或运行环境没有执行 Agent Ack,则不要伪造 runtime;只保留上传成功和 reported 数据。',
1393
1781
  '',
1394
1782
  'Red Skill 基础安全规范:',
@@ -1413,20 +1801,22 @@ function uploadCompatibilityLines(hub, manifestFile) {
1413
1801
  function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1414
1802
  const creatorCardUrl = normalizeText(inspected.registry?.creatorCardUrl, 800);
1415
1803
  const creatorLines = creatorProfileSummaryLines(inspected.registry || {}, 'en');
1804
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1416
1805
  if (hub === 'red') {
1417
1806
  return [
1418
1807
  '公开披露文本:',
1419
1808
  '```markdown',
1420
1809
  `### ${PRODUCT_NAME} Attribution Disclosure`,
1421
1810
  '',
1422
- `This Skill may include a ${PRODUCT_NAME} creator attribution or disclosure link for public listing and aggregate reporting.`,
1423
- creatorCardUrl ? `Creator card: ${creatorCardUrl}` : 'Creator card: <creator card link from XiaShe dashboard>',
1811
+ `This Skill may include ${PRODUCT_NAME} creator attribution or a disclosure link for public listing and aggregate reporting.`,
1812
+ creatorCardUrl ? `Creator card: ${creatorCardUrl}` : `Creator card: <creator card link from ${PRODUCT_NAME} dashboard>`,
1424
1813
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
1425
1814
  '',
1426
1815
  `- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
1816
+ protectedPaidSkill ? `- Paid access: this Red package is a connector/bootstrap for ${PRODUCT_NAME}. The complete paid Skill package is delivered by ${PRODUCT_NAME} Gateway only after entitlement verification, then retrieved into the current Agent private workspace.` : null,
1427
1817
  '- Data mode for Red Skill upload: Agent Ack runtime only when the host Agent calls a no-secret Ack protocol; upload/reported otherwise.',
1428
- '- Do not publish XiaShe private registry ids, public tokens, signing secrets, package hashes, storage ids, hidden telemetry, cookies, account sessions, or local private files in Red public fields.',
1429
- '- Runtime usage is counted by XiaShe only when the Agent calls the Ack API from an allowed protocol file/private config, or when a platform-approved runtime API/MCP/webhook boundary is explicitly wired.',
1818
+ `- Do not publish ${PRODUCT_NAME} private registry ids, public tokens, signing secrets, package hashes, storage ids, hidden telemetry, cookies, account sessions, or local private files in Red public fields.`,
1819
+ `- Runtime usage is counted by ${PRODUCT_NAME} only when the Agent calls the Ack API from an allowed protocol file/private config, or when a platform-approved runtime API/MCP/webhook boundary is explicitly wired.`,
1430
1820
  '- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
1431
1821
  '```'
1432
1822
  ];
@@ -1440,7 +1830,7 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1440
1830
  '',
1441
1831
  `- Registry provider: ${REGISTRY_PROVIDER}`,
1442
1832
  `- Skill key: ${inspected.skillKey}`,
1443
- `- Creator card: ${creatorCardUrl || '<creator card link from XiaShe dashboard>'}`,
1833
+ `- Creator card: ${creatorCardUrl || `<creator card link from ${PRODUCT_NAME} dashboard>`}`,
1444
1834
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
1445
1835
  '- Events, if enabled by the runtime/platform, should contain only public metadata and aggregate status.',
1446
1836
  '- Do not expose package hashes, storage IDs, upload job IDs, public tokens, private endpoints, local paths, hidden telemetry files, credentials, account sessions, or environment variables.',
@@ -1452,9 +1842,10 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1452
1842
 
1453
1843
  function agentAckInstructionBlock(inspected, hub = 'generic') {
1454
1844
  const registry = inspected.registry || {};
1845
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1455
1846
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
1456
1847
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
1457
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1848
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1458
1849
  const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
1459
1850
  const normalizedHub = normalizeHub(hub) || hub || 'generic';
1460
1851
  const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
@@ -1473,10 +1864,16 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1473
1864
  `- Runtime schema: ${eventSchemaVersion}`,
1474
1865
  `- Platform hub: ${normalizedHub}`,
1475
1866
  `- Distribution platform: ${distributionPlatform}`,
1867
+ protectedPaidSkill
1868
+ ? `- Access mode: protected paid Skill. This public package is only a connector/bootstrap. The complete protected Skill package is delivered by the ${PRODUCT_NAME} Gateway only after the user's entitlement is verified, then must be downloaded, verified, unpacked, and read locally.`
1869
+ : `- Access mode: public/free Skill instructions may be used according to this package and the target platform rules.`,
1870
+ protectedPaidSkill
1871
+ ? `- On first real use, call the ${PRODUCT_NAME} Gateway to obtain the complete package. If it returns \`paymentRequired\`, present its explicit price and purpose, wait for the user's confirmation, complete the official payment flow, poll status, then retry. Do not treat a hosted status page as a payment page, and do not complete the task before the complete package is locally available.`
1872
+ : null,
1476
1873
  '- Send only anonymous aggregate usage status. Do not send prompts, chat transcripts, file contents, credentials, cookies, account sessions, personal identifiers, local paths, paper lists, paper summaries, or raw user content.',
1477
1874
  '- User-facing replies should be concise and useful. Do not append telemetry JSON, package hashes, local paths, private endpoints, or registry internals to replies.',
1478
1875
  `- Read \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` first when it exists, then read this file. If \`runtime.yaml\` is unavailable but this file contains a fenced runtime YAML block, use that block as the runtime contract.`,
1479
- '- Match XiaShe display text to the current conversation language. Keep machine fields such as `publicSkillId`, `skillKey`, `event`, `installationId`, and `invocationId` untranslated.',
1876
+ `- Match ${PRODUCT_NAME} display text to the current conversation language. Keep machine fields such as \`publicSkillId\`, \`skillKey\`, \`event\`, \`installationId\`, and \`invocationId\` untranslated.`,
1480
1877
  '- Every completed Skill turn response must include the localized footer from the runtime contract. Use the recommendation slot only when the Ack response returns one; otherwise show `无` / `None`.',
1481
1878
  '- Before first use or once per day when network is available, check the latest version endpoint. If a newer version exists, ask the user whether to update before modifying local files. If the version is blocked, tell the user and do not continue silently.',
1482
1879
  '- If the answer contains numbers, prices, percentages, dates, places, or URLs, present them in a clear structured list or table and preserve the source links.',
@@ -1530,8 +1927,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1530
1927
 
1531
1928
  function redPublicTrackingContractBlock(inspected) {
1532
1929
  const registry = inspected.registry || {};
1930
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1533
1931
  const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
1534
- const cardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1932
+ const cardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1535
1933
  const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
1536
1934
  const updateEndpoint = latestVersionEndpoint(inspected);
1537
1935
  const footer = creatorFooterTemplate(inspected, 'zh');
@@ -1540,6 +1938,9 @@ function redPublicTrackingContractBlock(inspected) {
1540
1938
  `## ${PRODUCT_NAME} Required Runtime Contract`,
1541
1939
  '',
1542
1940
  '使用本 Skill 时,必须优先遵循本小节。这个区块是给下游 Agent 读取的公开运行时契约,不是内部注释。',
1941
+ protectedPaidSkill
1942
+ ? `\n### 付费访问边界\n\n- 这是受保护的付费 Skill 连接器,不是完整 Skill prompt/source 包。\n- 第一次真实调用时,必须通过 ${PRODUCT_NAME} Gateway 获取完整本地包;如果返回 paymentRequired,向用户展示购买信息,等待支付确认后轮询状态并重试。\n- 付费权益确认后,必须下载、校验、解压并阅读 Gateway 返回的完整包中的 SKILL.md;完成前不要自行完成任务,也不要把公开 connector 当成完整 Skill 使用。\n- 同一 ${PRODUCT_NAME} 账户再次在其他 Agent 使用时,必须新领接入凭证和当前 Agent 的交付地址;已有权益会复用,不得共享 runtime token、下载地址或完整包。\n- 不要把完整 prompt、源码包、包下载地址、storage id、public token 或内部 manifest 上传到 Red Skill。\n`
1943
+ : '',
1543
1944
  '',
1544
1945
  '### 检查更新',
1545
1946
  '',
@@ -1558,7 +1959,7 @@ function redPublicTrackingContractBlock(inspected) {
1558
1959
  footer,
1559
1960
  '```',
1560
1961
  '',
1561
- '如果当前对话使用英文,可以使用 runtime.yaml 里的英文 footer;否则默认使用上面的中文 footer。推荐位只使用 XiaShe Ack 响应返回的内容,没有返回时写“无”。',
1962
+ `如果当前对话使用英文,可以使用 runtime.yaml 里的英文 footer;否则默认使用上面的中文 footer。推荐位只使用 ${PRODUCT_NAME} Ack 响应返回的内容,没有返回时写“无”。`,
1562
1963
  '',
1563
1964
  '### 安全边界',
1564
1965
  '',
@@ -1579,7 +1980,8 @@ function skillMdRegistryBlock(inspected, hub) {
1579
1980
  return redPublicTrackingContractBlock(inspected);
1580
1981
  }
1581
1982
  const registry = inspected.registry || {};
1582
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1983
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1984
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1583
1985
  const marker = `${REGISTRY_PROVIDER}-registry`;
1584
1986
  const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
1585
1987
  const updateEndpoint = latestVersionEndpoint(inspected);
@@ -1588,6 +1990,9 @@ function skillMdRegistryBlock(inspected, hub) {
1588
1990
  `## ${PRODUCT_NAME} Required Runtime Contract`,
1589
1991
  '',
1590
1992
  `This Skill is published by its creator through ${PRODUCT_NAME}. Follow this section before using the Skill.`,
1993
+ protectedPaidSkill
1994
+ ? `\n### Protected Paid Access\n\n- This public package is a connector/bootstrap, not the complete paid Skill package or source package.\n- On first real use, call the ${PRODUCT_NAME} Gateway. If it returns \`paymentRequired\`, present the returned price and purpose, wait for confirmation, complete the official payment flow, poll status, and retry. A hosted status page is not a payment page.\n- After entitlement verification, download, verify, unpack, and read the complete package locally. Do not complete the task before that delivery succeeds.\n`
1995
+ : '',
1591
1996
  '',
1592
1997
  '### Update Check',
1593
1998
  '',
@@ -1669,8 +2074,9 @@ function runtimeDistributionPlatformForHub(hub) {
1669
2074
 
1670
2075
  function buildRuntimeYaml(inspected, hub = 'red') {
1671
2076
  const registry = inspected.registry || {};
1672
- const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
1673
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
2077
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
2078
+ const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
2079
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1674
2080
  const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '<creator display name>';
1675
2081
  const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80) || '<creator signal>';
1676
2082
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
@@ -1682,6 +2088,11 @@ function buildRuntimeYaml(inspected, hub = 'red') {
1682
2088
  `publicSkillId: ${yamlString(publicSkillId)}`,
1683
2089
  `skillKey: ${yamlString(inspected.skillKey)}`,
1684
2090
  `skillVersion: ${yamlString(skillVersion)}`,
2091
+ 'access:',
2092
+ ` mode: ${yamlString(protectedPaidSkill ? 'protected_paid_connector' : 'public_or_free')}`,
2093
+ ` paidPromptDeliveredBy: ${yamlString(protectedPaidSkill ? `${PRODUCT_NAME} Gateway after entitlement verification` : 'public package or target platform rules')}`,
2094
+ ` firstUsePayment: ${yamlString(protectedPaidSkill ? 'show hosted payment session returned by gateway, poll status, retry prompt after entitlement' : 'not_required_by_protocol')}`,
2095
+ ` neverUploadToThirdParty: ${yamlString(protectedPaidSkill ? 'full prompt, source package, package URL, storage id, public token, internal manifest' : 'secrets, private manifest, tokens, package hashes, storage ids')}`,
1685
2096
  'updateCheck:',
1686
2097
  " mode: 'prompt_user'",
1687
2098
  ` currentVersion: ${yamlString(skillVersion)}`,
@@ -1801,7 +2212,7 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1801
2212
  const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
1802
2213
  const runtimePath = path.join(protocolDir, 'runtime.yaml');
1803
2214
  const registry = inspected.registry || {};
1804
- const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
2215
+ const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
1805
2216
  const agentAck = [
1806
2217
  agentAckInstructionBlock(inspected, hub),
1807
2218
  '',
@@ -1871,6 +2282,7 @@ async function uploadPrompt(inspected, flags) {
1871
2282
  const hub = normalizeHub(flags.hub || 'generic') || 'generic';
1872
2283
  const reviewProfile = reviewProfileForHub(hub);
1873
2284
  const packagePlan = packagePlanForHub(inspected, hub);
2285
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1874
2286
  const sourceUrl = normalizeText(flags['source-url'] || flags['package-url'], 1000);
1875
2287
  const platformCommand = normalizeText(flags['platform-command'], 1000);
1876
2288
  const platformPrompt = await readPlatformPrompt(flags);
@@ -1878,11 +2290,11 @@ async function uploadPrompt(inspected, flags) {
1878
2290
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
1879
2291
  const agentAckUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
1880
2292
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
1881
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
2293
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1882
2294
  const creatorProfileLines = creatorProfileSummaryLines(registry, 'zh').filter((line) => !line.startsWith('- 创作者名片:'));
1883
2295
  const redHub = hub === 'red';
1884
2296
  const eventPayload = {
1885
- publicToken: registry.publicToken || '<xiashe public token from xiashe.skill.json>',
2297
+ publicToken: registry.publicToken || `<public token from ${WORK_DIR}/${MANIFEST_FILE}>`,
1886
2298
  schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
1887
2299
  eventType: 'hub_upload_succeeded',
1888
2300
  eventSource: 'cli',
@@ -1934,15 +2346,15 @@ async function uploadPrompt(inspected, flags) {
1934
2346
  '第三方平台官方流程:',
1935
2347
  `- ${hubLine}`,
1936
2348
  redHub
1937
- ? '- 平台官方 prompt / CLI / 页面表单是上传依据;虾舍提供本地 registry、Agent Ack 真实统计、安全披露和上传状态记录。'
1938
- : '- 平台官方 prompt / CLI / 页面表单是上传依据;虾舍只提供 registry 和 analytics 附加要求。',
1939
- '- 如果平台官方要求与虾舍附加说明冲突,以平台官方上传要求为准;不要为了 analytics 绕过平台规则。',
2349
+ ? `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 提供本地 registry、Agent Ack 真实统计、安全披露和上传状态记录。`
2350
+ : `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 只提供 registry 和 analytics 附加要求。`,
2351
+ `- 如果平台官方要求与 ${PRODUCT_NAME} 附加说明冲突,以平台官方上传要求为准;不要为了 analytics 绕过平台规则。`,
1940
2352
  platformCommand ? `- 用户提供的官方命令:${platformCommand}` : `- ${platformPromptPlaceholder(hub)}`
1941
2353
  ];
1942
2354
  if (platformPrompt) {
1943
2355
  officialFlowLines.push('', '用户提供的第三方平台官方 prompt:', '```text', platformPrompt.trim(), '```');
1944
2356
  }
1945
- const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE);
2357
+ const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE, protectedPaidSkill);
1946
2358
  const disclosureLines = publicDisclosure(inspected, registryUrl, hub);
1947
2359
  const runtimeGuidanceLines = redHub
1948
2360
  ? [
@@ -1953,14 +2365,21 @@ async function uploadPrompt(inspected, flags) {
1953
2365
  `- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
1954
2366
  `- 创作者名片:${creatorCardUrl}`,
1955
2367
  ...creatorProfileLines,
1956
- `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
2368
+ protectedPaidSkill
2369
+ ? `- 这是付费受保护 Skill:Red Skill 公开包只能是 ${PRODUCT_NAME} connector/bootstrap。不要上传完整 prompt、源码包、包下载链接、storage id、内部 manifest 或任何可绕过网关的材料。`
2370
+ : `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
2371
+ protectedPaidSkill
2372
+ ? `- 外部 Agent 第一次真实使用时必须回到 ${PRODUCT_NAME} Gateway 获取完整受保护包;如果返回 paymentRequired,就展示购买信息、等待用户确认支付、轮询 status,再重试 get_skill_prompt,然后下载、校验、解压并读取 SKILL.md。`
2373
+ : null,
1957
2374
  `- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
1958
2375
  `- publicSkillId 和 Agent Ack endpoint 只能出现在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。`,
1959
- `- SKILL.md 最开头必须写 ${PRODUCT_NAME} Required Runtime Contract:当前版本、latest version endpoint、用户确认后更新、创作者名片链接、强制 footer 和安全边界;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。`,
2376
+ protectedPaidSkill
2377
+ ? `- 公开 connector 说明必须写清楚 ${PRODUCT_NAME} Gateway 接入、付费访问边界、latest version endpoint、用户确认后更新、创作者名片链接和安全边界;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。`
2378
+ : `- SKILL.md 最开头必须写 ${PRODUCT_NAME} Required Runtime Contract:当前版本、latest version endpoint、用户确认后更新、创作者名片链接、强制 footer 和安全边界;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。`,
1960
2379
  '- 使用 Skill 前如果能联网,Agent 应先检查最新版本;有新版本就提示用户是否更新,无法联网则继续但说明更新状态未检查。',
1961
- '- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造虾舍 runtime 数据。',
2380
+ `- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造 ${PRODUCT_NAME} runtime 数据。`,
1962
2381
  '- 上传成功后可以用 hub_upload_succeeded 记录公开 URL;这属于 attribution,不代表真实运行次数。',
1963
- `- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入虾舍 runtime 统计。`,
2382
+ `- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入 ${PRODUCT_NAME} runtime 统计。`,
1964
2383
  '- 如果平台公开展示下载、收藏、安装或使用数字,可以后续按 reported 数据手动导入;不要把 reported 伪造成 runtime。',
1965
2384
  '- 先检查“能否安全上传”,再检查“Agent Ack 能否被执行”;二者在报告里分开。',
1966
2385
  `- Agent Ack 的字段要拆清楚:distributionPlatform 是分发平台,runtimeAgent 是实际运行的 Agent,runtimeHost 是宿主环境,sourceSurface 是发现入口,scenario 是枚举键,scenarioLabel 是短的人类可读场景。`,
@@ -1998,7 +2417,9 @@ async function uploadPrompt(inspected, flags) {
1998
2417
  '',
1999
2418
  JSON.stringify(eventPayload, null, 2),
2000
2419
  '',
2001
- 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
2420
+ protectedPaidSkill
2421
+ ? `Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 connector 只保留 ${PRODUCT_NAME} Gateway 接入、创作者名片和安全使用边界。`
2422
+ : 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
2002
2423
  ]
2003
2424
  : [
2004
2425
  `发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据和聚合事件,不发送用户内容、私密输入或账号凭据。`,
@@ -2054,7 +2475,7 @@ async function uploadPrompt(inspected, flags) {
2054
2475
  '- attribution:发布、上传、归因链接点击、公开披露访问,只代表来源漏斗。',
2055
2476
  '- reported:平台公开数字或手动导入,例如收藏、下载、安装、静态平台显示使用数。',
2056
2477
  '- 如果无法确认 runtime callback 已经接在真实调用边界,就把该平台标记为 attribution 或 reported,不要伪造成 runtime。',
2057
- '- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入虾舍实时使用数据。',
2478
+ `- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入 ${PRODUCT_NAME} 实时使用数据。`,
2058
2479
  '',
2059
2480
  ...compatibilityLines,
2060
2481
  '',
@@ -2064,10 +2485,12 @@ async function uploadPrompt(inspected, flags) {
2064
2485
  ? [
2065
2486
  'Red 公开包边界:',
2066
2487
  '',
2067
- `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
2068
- `- 不要包含:${WORK_DIR}/、${WORK_DIR}/${MANIFEST_FILE}、runtime-events.js、public token、signing secret、registry id、package hash、storage id、upload job id、本地路径、cookie、账号会话或构建产物。`,
2488
+ protectedPaidSkill
2489
+ ? `- 付费 Skill 只能包含:README/公开 connector 说明,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。不要把完整 SKILL.md/prompt/source 放入 Red 公开包。`
2490
+ : `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
2491
+ `- 不要包含:${WORK_DIR}/、${WORK_DIR}/${MANIFEST_FILE}、runtime-events.js、public token、signing secret、registry id、package hash、storage id、upload job id、本地路径、cookie、账号会话、构建产物${protectedPaidSkill ? '、完整 prompt 或源码包' : ''}。`,
2069
2492
  `- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
2070
- '- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成虾舍 runtime 数据。'
2493
+ `- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成 ${PRODUCT_NAME} runtime 数据。`
2071
2494
  ]
2072
2495
  : [
2073
2496
  'Agent Ack 指令(只放在私有 runtime 配置、平台后台字段,或明确不会公开展示的安装说明中):',
@@ -2106,6 +2529,7 @@ async function writePrompt(root, flags) {
2106
2529
 
2107
2530
  async function writeUnifiedHandoff(root, args) {
2108
2531
  const relativeOutDir = path.relative(root, args.outDir) || WORK_DIR;
2532
+ const protectedPaidSkill = isProtectedPaidSkill(args.inspected);
2109
2533
  const promptIndex = args.promptResults
2110
2534
  .map((item) => `- ${item.hub}: ${path.relative(root, item.promptPath)}`)
2111
2535
  .join('\n');
@@ -2114,7 +2538,7 @@ async function writeUnifiedHandoff(root, args) {
2114
2538
  .join('\n');
2115
2539
  const registry = args.inspected.registry || {};
2116
2540
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
2117
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
2541
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
2118
2542
  const content = [
2119
2543
  `# ${PRODUCT_NAME} Skill Upload Handoff`,
2120
2544
  '',
@@ -2131,7 +2555,7 @@ async function writeUnifiedHandoff(root, args) {
2131
2555
  '1. Identify the target platform from that official prompt, URL, command, or user message.',
2132
2556
  `2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
2133
2557
  '3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
2134
- `4. For XiaShe Store, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
2558
+ `4. For ${PRODUCT_NAME} Registry, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
2135
2559
  `5. For Red Skill, include no-secret ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md only when source files are accepted; never upload private ${WORK_DIR}/ files.`,
2136
2560
  `6. Ensure SKILL.md starts with ${PRODUCT_NAME} Required Runtime Contract: current version, latest version endpoint, user-approved update rule, creator card, required footer, and safety boundary.`,
2137
2561
  '7. Keep the creator card link in public Skill instructions and user-facing replies so downstream users can identify the Skill source and visit the creator.',
@@ -2141,10 +2565,15 @@ async function writeUnifiedHandoff(root, args) {
2141
2565
  '',
2142
2566
  'Red Skill exception:',
2143
2567
  '',
2568
+ protectedPaidSkill
2569
+ ? `- This is a protected paid Skill. Red Skill receives only a ${PRODUCT_NAME} connector/bootstrap package, not the full prompt/source package. Users must obtain access through ${PRODUCT_NAME} Gateway; payment and entitlement are enforced server-side before the complete protected package is delivered, downloaded, verified, and read locally.`
2570
+ : null,
2144
2571
  '- Red Skill should use the official uploader.md / skillhub-upload / web upload flow as the source of truth.',
2145
- `- Public Red Markdown/YAML packages may include ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md as no-secret runtime protocol/disclosure files.`,
2572
+ protectedPaidSkill
2573
+ ? `- Public Red Markdown/YAML packages may include only connector docs plus ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md as no-secret runtime protocol/disclosure files. Do not include the complete SKILL.md/prompt/source package.`
2574
+ : `- Public Red Markdown/YAML packages may include ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md as no-secret runtime protocol/disclosure files.`,
2146
2575
  `- Keep ${WORK_DIR}/${MANIFEST_FILE}, ${WORK_DIR}/runtime-events.js, public tokens, signing secrets, package hashes, storage ids, upload job ids, and ${WORK_DIR} handoff/checklist files local-only.`,
2147
- '- Red Skill reports XiaShe runtime through Agent Ack when the host Agent calls it from the no-secret protocol file or private runtime config. If no Agent Ack occurs, show only upload/reported data.',
2576
+ `- Red Skill reports ${PRODUCT_NAME} runtime through Agent Ack when the host Agent calls it from the no-secret protocol file or private runtime config. If no Agent Ack occurs, show only upload/reported data.`,
2148
2577
  '- Check only the basic Red Skill safety rules before submission: truthful description, clear permission/use disclosure, no hidden behavior, no malicious code, no account/cookie/local-data collection, no automated Red account operation, no illegal/violating content, and no plagiarism.',
2149
2578
  '',
2150
2579
  '## Platform mapping',
@@ -2224,11 +2653,11 @@ async function writeUnifiedHandoff(root, args) {
2224
2653
  '',
2225
2654
  'If the hub cannot run callbacks, keep upload attribution and use campaign links or manual imports. Do not fake runtime events.',
2226
2655
  '',
2227
- '## XiaShe Store draft auth',
2656
+ `## ${PRODUCT_NAME} Registry draft auth`,
2228
2657
  '',
2229
- `For XiaShe Store, first check whether a submission already exists for this registry. ${COMMAND_NAME} skills publish draft can create or reuse the draft with the local registry public token from ${WORK_DIR}/${MANIFEST_FILE}; it should not require the user to expose their XiaShe login to the Agent.`,
2658
+ `For ${PRODUCT_NAME} Registry, first check whether a submission already exists for this registry. ${COMMAND_NAME} skills publish draft can create or reuse the draft with the local registry public token from ${WORK_DIR}/${MANIFEST_FILE}; it should not require the user to expose their ${PRODUCT_NAME} login to the Agent.`,
2230
2659
  '',
2231
- 'If the registry token is missing or rejected, stop and ask the user to run `xiashe login` locally or generate a new Add Skill task. Do not upload the package as another creator.',
2660
+ `If the registry token is missing or rejected, stop and ask the user to run the local ${PRODUCT_NAME} login command or generate a new Add Skill task. Do not upload the package as another creator.`,
2232
2661
  '',
2233
2662
  'If the claim code expires while you are working, ask for a new code and reuse the existing scan, package plans, disclosure text, and review results instead of starting over.',
2234
2663
  ''
@@ -2519,7 +2948,7 @@ async function runDoctor(root, flags) {
2519
2948
  hasManifest && registry.publicToken && registry.registryUrl
2520
2949
  ? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
2521
2950
  : redHub
2522
- ? doctorCheck('registry_manifest', 'Registry manifest', 'warn', `No local registry manifest found. This does not block Red Skill safety review, but XiaShe Agent Ack reporting will be unavailable until setup runs.`, `Run ${COMMAND_NAME} setup . --code <dashboard-code> --hub red if XiaShe tracking is needed.`)
2951
+ ? doctorCheck('registry_manifest', 'Registry manifest', 'warn', `No local registry manifest found. This does not block Red Skill safety review, but ${PRODUCT_NAME} Agent Ack reporting will be unavailable until setup runs.`, `Run ${COMMAND_NAME} setup . --code <dashboard-code> --hub red if ${PRODUCT_NAME} tracking is needed.`)
2523
2952
  : 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>.`),
2524
2953
  registryBlockPresent(skillMd)
2525
2954
  ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
@@ -2534,7 +2963,7 @@ async function runDoctor(root, flags) {
2534
2963
  ? doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'pass', 'SKILL.md contains the current runtime contract, required footer, and update check endpoint.')
2535
2964
  : redHub
2536
2965
  ? doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'fail', 'SKILL.md is missing the Required Runtime Contract, footer, or update check endpoint.', `Run ${COMMAND_NAME} setup . --code <code> --hub red to rewrite SKILL.md with the current contract.`)
2537
- : doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'warn', 'SKILL.md does not yet contain the Required Runtime Contract.', `Run ${COMMAND_NAME} setup . --code <code> --embed-skill-md if this hub allows XiaShe public runtime instructions.`),
2966
+ : doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'warn', 'SKILL.md does not yet contain the Required Runtime Contract.', `Run ${COMMAND_NAME} setup . --code <code> --embed-skill-md if this hub allows ${PRODUCT_NAME} public runtime instructions.`),
2538
2967
  runtimeCallbackPresent
2539
2968
  ? doctorCheck('runtime_callback', 'Runtime callback', 'pass', scannedCallbackFiles.length > 0 ? `Runtime callback references found in ${scannedCallbackFiles.slice(0, 5).join(', ')}.` : 'Runtime callback snippet/reference detected.')
2540
2969
  : redHub
@@ -2562,6 +2991,11 @@ async function runDoctor(root, flags) {
2562
2991
  : redHub
2563
2992
  ? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
2564
2993
  : doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Not required for this hub.'),
2994
+ redHub && protectedPaidSkill && packagePlan.candidateUploadFiles.some((file) => /(^|\/)SKILL(\.[a-z0-9]+)?$/i.test(file) || (!isPublicRuntimeProtocolFile(file) && !/^README(\.[a-z0-9]+)?$/i.test(file)))
2995
+ ? doctorCheck('red_paid_connector_boundary', 'Red paid connector boundary', 'fail', `Paid Red candidate upload files include full Skill content: ${packagePlan.candidateUploadFiles.join(', ')}`, `Use protected connector packaging only. Regenerate with ${COMMAND_NAME} setup . --code <code> --hub red --paid-skill.`)
2996
+ : redHub && protectedPaidSkill
2997
+ ? doctorCheck('red_paid_connector_boundary', 'Red paid connector boundary', 'pass', 'Protected paid Red package is connector/protocol only and excludes full SKILL.md/source content.')
2998
+ : doctorCheck('red_paid_connector_boundary', 'Red paid connector boundary', 'pass', 'Not required for this hub/Skill.'),
2565
2999
  redHub && ['AGENT_ACK.md', 'REGISTRY_DISCLOSURE.md', 'runtime.yaml'].every((name) => packagePlan.candidateUploadFiles.includes(`${PUBLIC_PROTOCOL_DIR}/${name}`))
2566
3000
  ? doctorCheck('red_runtime_allowlist', 'Red runtime allowlist', 'pass', `Red candidate upload files include ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md, and ${PUBLIC_PROTOCOL_DIR}/runtime.yaml.`)
2567
3001
  : redHub
@@ -2868,6 +3302,23 @@ async function main() {
2868
3302
  }
2869
3303
  return;
2870
3304
  }
3305
+ if (command === 'preflight') {
3306
+ const claimUrl = explicitClaimUrl(flags);
3307
+ if (!claimUrl) {
3308
+ throw new Error('CREATOR_SKILL_CLAIM_URL_REQUIRED: pass the exact --claim-url issued by the dashboard.');
3309
+ }
3310
+ const result = await preflightRegistryClaim(
3311
+ claimUrl,
3312
+ flags
3313
+ );
3314
+ print(flags.json ? result : [
3315
+ `${PRODUCT_NAME} registry preflight passed.`,
3316
+ `Environment: ${result.environmentId}`,
3317
+ `Registry: ${result.registryOrigin}`,
3318
+ `Protocol: ${result.protocolVersion}`
3319
+ ].join('\n'), flags.json);
3320
+ return;
3321
+ }
2871
3322
  if (command === 'verify') {
2872
3323
  const result = await verifyRegistry(root, flags);
2873
3324
  print(flags.json ? result : formatVerify(result), flags.json);